code.py
  1  # SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import time
  6  import board
  7  import busio
  8  import neopixel
  9  import adafruit_drv2605
 10  import adafruit_led_animation.color as color
 11  import adafruit_ble
 12  from adafruit_ble.advertising.standard import SolicitServicesAdvertisement
 13  from adafruit_ble.services.standard import CurrentTimeService
 14  from adafruit_ble_apple_notification_center import AppleNotificationCenterService
 15  from digitalio import DigitalInOut, Direction
 16  
 17  #  setup for onboard NeoPixel
 18  pixel_pin = board.NEOPIXEL
 19  num_pixels = 1
 20  
 21  pixel = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)
 22  
 23  #  setup for haptic motor driver
 24  i2c = busio.I2C(board.SCL, board.SDA)
 25  drv = adafruit_drv2605.DRV2605(i2c)
 26  
 27  #  onboard blue LED
 28  blue_led = DigitalInOut(board.BLUE_LED)
 29  blue_led.direction = Direction.OUTPUT
 30  
 31  #  setup for BLE
 32  ble = adafruit_ble.BLERadio()
 33  if ble.connected:
 34      for c in ble.connections:
 35          c.disconnect()
 36  
 37  advertisement = SolicitServicesAdvertisement()
 38  
 39  #  adds ANCS and current time services for BLE to advertise
 40  advertisement.solicited_services.append(AppleNotificationCenterService)
 41  advertisement.solicited_services.append(CurrentTimeService)
 42  
 43  #  state machines
 44  current_notification = None #  tracks the current notification from ANCS
 45  current_notifications = {} #  array to hold all current notifications from ANCS
 46  cleared = False #  state to track if notifications have been cleared from ANCS
 47  notification_service = None #  holds the array of active notifications from ANCS
 48  all_ids = [] #  array to hold all of the ids from ANCS
 49  hour = 0 #  used to track when it is on the hour for the mindfulness reminder
 50  mindful = False #  state used to track if it is time for mindfulness
 51  vibration = 16 #  vibration effect being used for the haptic motor
 52  
 53  APP_COLORS = {
 54      "com.basecamp.bc3-ios": color.YELLOW, #  Basecamp
 55      "com.apple.MobileSMS": color.GREEN, #  Texts
 56      "com.hammerandchisel.discord": color.PURPLE, #  Discord
 57      "com.apple.mobilecal": color.CYAN, #  Calendar
 58      "com.apple.mobilephone": color.GREEN, #  Phone
 59      "com.google.ios.youtube": color.ORANGE, #  YouTube
 60      "com.burbn.instagram": color.MAGENTA, #  Instagram
 61      "com.apple.mobilemail": color.CYAN #  Apple Email
 62  }
 63  
 64  #  function for blinking NeoPixel
 65  #  blinks: # of blinks
 66  #  speed: how fast/slow blinks
 67  #  color1: first color
 68  #  color2: second color
 69  def blink_pixel(blinks, speed, color1, color2):
 70      for _ in range(0, blinks):
 71          pixel.fill(color1)
 72          pixel.show()
 73          time.sleep(speed)
 74          pixel.fill(color2)
 75          pixel.show()
 76          time.sleep(speed)
 77  
 78  #  function for haptic motor vibration
 79  #  num_zzz: # of times vibrates
 80  #  effect: type of vibration
 81  #  delay: time between vibrations
 82  def vibe(num_zzz, effect, delay):
 83      drv.sequence[0] = adafruit_drv2605.Effect(effect)
 84      for _ in range(0, num_zzz):
 85          drv.play()  # play the effect
 86          time.sleep(delay)  # for 0.5 seconds
 87          drv.stop()
 88  
 89  #  start BLE
 90  ble.start_advertising(advertisement)
 91  
 92  while True:
 93  
 94      blue_led.value = False
 95      print("Waiting for connection")
 96  
 97      #  NeoPixel is red when not connected to BLE
 98      while not ble.connected:
 99          blue_led.value = False
100          pixel.fill(color.RED)
101          pixel.show()
102      print("Connected")
103  
104      while ble.connected:
105          blue_led.value = True #  blue LED is on when connected
106          all_ids.clear()
107          for connection in ble.connections:
108              if not connection.paired:
109                  #  pairs to phone
110                  connection.pair()
111                  print("paired")
112              #  allows connection to CurrentTimeService
113              cts = connection[CurrentTimeService]
114              notification_service = connection[AppleNotificationCenterService]
115          #  grabs notifications from ANCS
116          current_notifications = notification_service.active_notifications
117  
118          for notif_id in current_notifications:
119              #  adds notifications into array
120              notification = current_notifications[notif_id]
121              all_ids.append(notif_id)
122  
123          #  all_ids.sort(key=lambda x: current_notifications[x]._raw_date)
124  
125          if current_notification and current_notification.removed:
126              # Stop showing the latest and show that there are no new notifications.
127              current_notification = None
128              pixel.fill(color.BLACK)
129              pixel.show()
130  
131          if not current_notification and not all_ids and not cleared:
132              #  updates cleared state for notification
133              cleared = True
134              #  turns off NeoPixel when notifications are clear
135              pixel.fill(color.BLACK)
136              pixel.show()
137  
138          elif all_ids:
139              cleared = False
140              if current_notification and current_notification.id in all_ids:
141                  index = all_ids.index(current_notification.id)
142              else:
143                  index = len(all_ids) - 1
144                  notif_id = all_ids[index]
145              #  if there is a notification:
146              if not current_notification or current_notification.id != notif_id:
147                  current_notification = current_notifications[notif_id]
148                  #  if the notification is from an app that is not
149                  #  defined in APP_COLORS then the NeoPixel will be white
150                  if current_notification.app_id not in APP_COLORS:
151                      notif_color = color.WHITE
152                  #  if the notification is from an app defined in
153                  #  APP_COLORS then the assigned color will show
154                  else:
155                      notif_color = APP_COLORS[current_notification.app_id]
156                  #  parses notification info into a string
157                  category = str(notification).split(" ", 1)[0]
158                  #  haptic motor vibrates
159                  vibe(2, vibration, 0.5)
160                  #  all info for notification is printed to REPL
161                  print('-'*36)
162                  print("Msg #%d - Category %s" % (notification.id, category))
163                  print("From app:", notification.app_id)
164                  if notification.title:
165                      print("Title:", notification.title)
166                  if notification.subtitle:
167                      print("Subtitle:", notification.subtitle)
168                  if notification.message:
169                      print("Message:", notification.message)
170                  #  NeoPixel blinks and then stays on until cleared
171                  blink_pixel(2, 0.5, notif_color, color.BLACK)
172                  pixel.fill(notif_color)
173                  pixel.show()
174          #  if it's on the hour:
175          if cts.current_time[4] == hour and not mindful:
176              print(cts.current_time[4])
177              print("mindful time")
178              #  haptic motor vibrates
179              vibe(5, vibration, 1)
180              #  NeoPixel blinks and then stays on
181              blink_pixel(5, 1, color.BLUE, color.BLACK)
182              mindful = True
183              pixel.fill(color.BLUE)
184              pixel.show()
185              print("hour = ", hour)
186          #  if it's no longer on the hour:
187          if cts.current_time[4] == (hour + 1) and mindful:
188              #  NeoPixel turns off
189              mindful = False
190              pixel.fill(color.BLACK)
191              pixel.show()
192              print("mindful time over")
193  
194      #  if BLE becomes disconnected then blue LED turns off
195      #  and BLE begins advertising again to reconnect
196      print("Disconnected")
197      blue_led.value = False
198      print()
199      ble.start_advertising(advertisement)
200      notification_service = None