Skip to content

Commit eff333c

Browse files
authored
Merge branch 'master' into master
2 parents 1020928 + 4988510 commit eff333c

File tree

12 files changed

+5874
-3
lines changed

12 files changed

+5874
-3
lines changed

.github/workflows/githubci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ on: [pull_request, push, repository_dispatch]
44

55
jobs:
66
arduino:
7-
needs: pylint
87
strategy:
98
fail-fast: false
109
matrix:
1110
arduino-platform: ["uno", "nrf52832", "cpx_ada", "pyportal", "protrinket_3v", "protrinket_5v", "metro_m0", "esp8266", "esp32", "trinket_3v", "trinket_5v", "gemma", "flora", "feather32u4", "feather_m0_express", "gemma_m0", "trinket_m0", "hallowing_m0", "monster_m4sk", "hallowing_m4", "neotrellis_m4", "pybadge", "cpb"]
1211

1312
runs-on: ubuntu-latest
14-
#needs: pylint
1513

1614
steps:
1715
- uses: actions/setup-python@v1

BLE_Vibration_Bracelet/code.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import time
2+
import board
3+
import busio
4+
import neopixel
5+
import adafruit_drv2605
6+
import adafruit_led_animation.color as color
7+
import adafruit_ble
8+
from adafruit_ble.advertising.standard import SolicitServicesAdvertisement
9+
from adafruit_ble.services.standard import CurrentTimeService
10+
from adafruit_ble_apple_notification_center import AppleNotificationCenterService
11+
from digitalio import DigitalInOut, Direction
12+
13+
# setup for onboard NeoPixel
14+
pixel_pin = board.NEOPIXEL
15+
num_pixels = 1
16+
17+
pixel = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)
18+
19+
# setup for haptic motor driver
20+
i2c = busio.I2C(board.SCL, board.SDA)
21+
drv = adafruit_drv2605.DRV2605(i2c)
22+
23+
# onboard blue LED
24+
blue_led = DigitalInOut(board.BLUE_LED)
25+
blue_led.direction = Direction.OUTPUT
26+
27+
# setup for BLE
28+
ble = adafruit_ble.BLERadio()
29+
if ble.connected:
30+
for c in ble.connections:
31+
c.disconnect()
32+
33+
advertisement = SolicitServicesAdvertisement()
34+
35+
# adds ANCS and current time services for BLE to advertise
36+
advertisement.solicited_services.append(AppleNotificationCenterService)
37+
advertisement.solicited_services.append(CurrentTimeService)
38+
39+
# state machines
40+
current_notification = None # tracks the current notification from ANCS
41+
current_notifications = {} # array to hold all current notifications from ANCS
42+
cleared = False # state to track if notifications have been cleared from ANCS
43+
notification_service = None # holds the array of active notifications from ANCS
44+
all_ids = [] # array to hold all of the ids from ANCS
45+
hour = 0 # used to track when it is on the hour for the mindfulness reminder
46+
mindful = False # state used to track if it is time for mindfulness
47+
vibration = 16 # vibration effect being used for the haptic motor
48+
49+
APP_COLORS = {
50+
"com.basecamp.bc3-ios": color.YELLOW, # Basecamp
51+
"com.apple.MobileSMS": color.GREEN, # Texts
52+
"com.hammerandchisel.discord": color.PURPLE, # Discord
53+
"com.apple.mobilecal": color.CYAN, # Calendar
54+
"com.apple.mobilephone": color.GREEN, # Phone
55+
"com.google.ios.youtube": color.ORANGE, # YouTube
56+
"com.burbn.instagram": color.MAGENTA, # Instagram
57+
"com.apple.mobilemail": color.CYAN # Apple Email
58+
}
59+
60+
# function for blinking NeoPixel
61+
# blinks: # of blinks
62+
# speed: how fast/slow blinks
63+
# color1: first color
64+
# color2: second color
65+
def blink_pixel(blinks, speed, color1, color2):
66+
for _ in range(0, blinks):
67+
pixel.fill(color1)
68+
pixel.show()
69+
time.sleep(speed)
70+
pixel.fill(color2)
71+
pixel.show()
72+
time.sleep(speed)
73+
74+
# function for haptic motor vibration
75+
# num_zzz: # of times vibrates
76+
# effect: type of vibration
77+
# delay: time between vibrations
78+
def vibe(num_zzz, effect, delay):
79+
drv.sequence[0] = adafruit_drv2605.Effect(effect)
80+
for _ in range(0, num_zzz):
81+
drv.play() # play the effect
82+
time.sleep(delay) # for 0.5 seconds
83+
drv.stop()
84+
85+
# start BLE
86+
ble.start_advertising(advertisement)
87+
88+
while True:
89+
90+
blue_led.value = False
91+
print("Waiting for connection")
92+
93+
# NeoPixel is red when not connected to BLE
94+
while not ble.connected:
95+
blue_led.value = False
96+
pixel.fill(color.RED)
97+
pixel.show()
98+
print("Connected")
99+
100+
while ble.connected:
101+
blue_led.value = True # blue LED is on when connected
102+
all_ids.clear()
103+
for connection in ble.connections:
104+
if not connection.paired:
105+
# pairs to phone
106+
connection.pair()
107+
print("paired")
108+
# allows connection to CurrentTimeService
109+
cts = connection[CurrentTimeService]
110+
notification_service = connection[AppleNotificationCenterService]
111+
# grabs notifications from ANCS
112+
current_notifications = notification_service.active_notifications
113+
114+
for notif_id in current_notifications:
115+
# adds notifications into array
116+
notification = current_notifications[notif_id]
117+
all_ids.append(notif_id)
118+
119+
# all_ids.sort(key=lambda x: current_notifications[x]._raw_date)
120+
121+
if current_notification and current_notification.removed:
122+
# Stop showing the latest and show that there are no new notifications.
123+
current_notification = None
124+
pixel.fill(color.BLACK)
125+
pixel.show()
126+
127+
if not current_notification and not all_ids and not cleared:
128+
# updates cleared state for notification
129+
cleared = True
130+
# turns off NeoPixel when notifications are clear
131+
pixel.fill(color.BLACK)
132+
pixel.show()
133+
134+
elif all_ids:
135+
cleared = False
136+
if current_notification and current_notification.id in all_ids:
137+
index = all_ids.index(current_notification.id)
138+
else:
139+
index = len(all_ids) - 1
140+
notif_id = all_ids[index]
141+
# if there is a notification:
142+
if not current_notification or current_notification.id != notif_id:
143+
current_notification = current_notifications[notif_id]
144+
# if the notification is from an app that is not
145+
# defined in APP_COLORS then the NeoPixel will be white
146+
if current_notification.app_id not in APP_COLORS:
147+
notif_color = color.WHITE
148+
# if the notification is from an app defined in
149+
# APP_COLORS then the assigned color will show
150+
else:
151+
notif_color = APP_COLORS[current_notification.app_id]
152+
# parses notification info into a string
153+
category = str(notification).split(" ", 1)[0]
154+
# haptic motor vibrates
155+
vibe(2, vibration, 0.5)
156+
# all info for notification is printed to REPL
157+
print('-'*36)
158+
print("Msg #%d - Category %s" % (notification.id, category))
159+
print("From app:", notification.app_id)
160+
if notification.title:
161+
print("Title:", notification.title)
162+
if notification.subtitle:
163+
print("Subtitle:", notification.subtitle)
164+
if notification.message:
165+
print("Message:", notification.message)
166+
# NeoPixel blinks and then stays on until cleared
167+
blink_pixel(2, 0.5, notif_color, color.BLACK)
168+
pixel.fill(notif_color)
169+
pixel.show()
170+
# if it's on the hour:
171+
if cts.current_time[4] == hour and not mindful:
172+
print(cts.current_time[4])
173+
print("mindful time")
174+
# haptic motor vibrates
175+
vibe(5, vibration, 1)
176+
# NeoPixel blinks and then stays on
177+
blink_pixel(5, 1, color.BLUE, color.BLACK)
178+
mindful = True
179+
pixel.fill(color.BLUE)
180+
pixel.show()
181+
print("hour = ", hour)
182+
# if it's no longer on the hour:
183+
if cts.current_time[4] == (hour + 1) and mindful:
184+
# NeoPixel turns off
185+
mindful = False
186+
pixel.fill(color.BLACK)
187+
pixel.show()
188+
print("mindful time over")
189+
190+
# if BLE becomes disconnected then blue LED turns off
191+
# and BLE begins advertising again to reconnect
192+
print("Disconnected")
193+
blue_led.value = False
194+
print()
195+
ble.start_advertising(advertisement)
196+
notification_service = None
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2020 Jeff Epler for Adafruit Industries LLC
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
"""
23+
Convert an analog joystick to digital
24+
"""
25+
26+
import analogio
27+
import board
28+
29+
class AnalogJoystick:
30+
"""Convert an analog joystick to digital"""
31+
def __init__(self, pin_x=None, pin_y=None, x_invert=False, y_invert=True, deadzone=8000):
32+
self._x = analogio.AnalogIn(pin_x or board.JOYSTICK_X)
33+
self._y = analogio.AnalogIn(pin_y or board.JOYSTICK_Y)
34+
self.x_invert = x_invert
35+
self.y_invert = y_invert
36+
self.deadzone = deadzone
37+
self.recenter()
38+
self.poll()
39+
40+
def poll(self):
41+
"""Read the analog values and update the digital outputs"""
42+
self.x = (self._x.value - self.x_center) * (-1 if self.x_invert else 1)
43+
self.y = (self._y.value - self.y_center) * (-1 if self.y_invert else 1)
44+
return [self.up, self.down, self.left, self.right]
45+
46+
# pylint: disable=invalid-name
47+
@property
48+
def up(self):
49+
"""Return true when the stick was pressed up at the last poll"""
50+
return self.y > self.deadzone
51+
# pylint: enable=invalid-name
52+
53+
@property
54+
def down(self):
55+
"""Return true when the stick was pressed down at the last poll"""
56+
return self.y < -self.deadzone
57+
58+
@property
59+
def left(self):
60+
"""Return true when the stick was pressed left at the last poll"""
61+
return self.x < -self.deadzone
62+
63+
@property
64+
def right(self):
65+
"""Return true when the stick was pressed right at the last poll"""
66+
return self.x > self.deadzone
67+
68+
def recenter(self):
69+
"""Use the current position of the analog joystick as the center"""
70+
self.x_center = self._x.value
71+
self.y_center = self._y.value

0 commit comments

Comments
 (0)