Skip to content

Commit f0bd266

Browse files
committed
Support all ANCS attributes
1 parent 479771f commit f0bd266

File tree

2 files changed

+65
-27
lines changed

2 files changed

+65
-27
lines changed

adafruit_ble/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def connections(self):
251251
"""A tuple of active `BLEConnection` objects."""
252252
connections = self._adapter.connections
253253
wrapped_connections = [None] * len(connections)
254-
for i, connection in enumerate(self._adapter.connections):
254+
for i, connection in enumerate(connections):
255255
if connection not in self._connection_cache:
256256
self._connection_cache[connection] = BLEConnection(connection)
257257
wrapped_connections[i] = self._connection_cache[connection]

adafruit_ble/services/apple.py

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"""
2929

3030
import struct
31+
import time
3132

3233
from . import Service
3334
from ..uuid import VendorUUID
@@ -44,39 +45,63 @@ class UnknownApple1Service(Service):
4445
"""Unknown service. Unimplemented."""
4546
uuid = VendorUUID("9fa480e0-4967-4542-9390-d343dc5d04ae")
4647

48+
class _NotificationAttribute:
49+
def __init__(self, id, *, max_length=False):
50+
self._id = id
51+
self._max_length = max_length
52+
53+
def __get__(self, notification, cls):
54+
if self._id in notification._attribute_cache:
55+
return notification._attribute_cache[self._id]
56+
57+
if self._max_length:
58+
command = struct.pack("<BIBH", 0, notification.id, self._id, 255)
59+
else:
60+
command = struct.pack("<BIB", 0, notification.id, self._id)
61+
notification.control_point.write(command)
62+
while notification.data_source.in_waiting == 0:
63+
pass
64+
65+
_, _ = struct.unpack("<BI", notification.data_source.read(5))
66+
attribute_id, attribute_length = struct.unpack("<BH", notification.data_source.read(3))
67+
if attribute_id != self._id:
68+
raise RuntimeError("Data for other attribute")
69+
value = notification.data_source.read(attribute_length)
70+
value = value.decode("utf-8")
71+
notification._attribute_cache[self._id] = value
72+
return value
73+
4774
class Notification:
75+
app_id = _NotificationAttribute(0)
76+
title = _NotificationAttribute(1, max_length=True)
77+
subtitle = _NotificationAttribute(2, max_length=True)
78+
message = _NotificationAttribute(3, max_length=True)
79+
message_size = _NotificationAttribute(4)
80+
_raw_date = _NotificationAttribute(5)
81+
positive_action_label = _NotificationAttribute(6)
82+
negative_action_label = _NotificationAttribute(7)
83+
4884
def __init__(self, id, event_flags, category_id, category_count, *, control_point, data_source):
4985
self.id = id
50-
self.category_id = category_id
5186
self.removed = False
5287

53-
self.silent = (event_flags & (1 << 0)) != 0
54-
self.important = (event_flags & (1 << 1)) != 0
55-
self.preexisting = (event_flags & (1 << 2)) != 0
56-
self.positive_action = (event_flags & (1 << 3)) != 0
57-
self.negative_action = (event_flags & (1 << 4)) != 0
88+
self.update(event_flags, category_id, category_count)
5889

59-
self.subtitle = None
60-
self.message = None
90+
self._attribute_cache = {}
6191

6292
self.control_point = control_point
6393
self.data_source = data_source
6494

6595
def update(self, event_flags, category_id, category_count):
66-
pass
96+
self.category_id = category_id
6797

68-
def _fetch_all(self):
69-
self.control_point.write(struct.pack("<BIBBHBHBHBBBB", 0, self.id, 0, 1, 32, 2, 32, 3, 255, 4, 5, 6, 7))
70-
while self.data_source.in_waiting == 0:
71-
pass
72-
_, _ = struct.unpack("<BI", self.data_source.read(5))
98+
self.silent = (event_flags & (1 << 0)) != 0
99+
self.important = (event_flags & (1 << 1)) != 0
100+
self.preexisting = (event_flags & (1 << 2)) != 0
101+
self.positive_action = (event_flags & (1 << 3)) != 0
102+
self.negative_action = (event_flags & (1 << 4)) != 0
73103

74-
for attribute in ["app_id", "title", "subtitle", "message", "message_size", "date", "positive_action_label", "negative_action_label"]:
75-
attribute_id, attribute_length = struct.unpack("<BH", self.data_source.read(3))
76-
if attribute_length == 0:
77-
continue
78-
value = self.data_source.read(attribute_length).decode("utf-8")
79-
setattr(self, attribute, value)
104+
self._attribute_cache = {}
80105

81106
@property
82107
def app(self):
@@ -89,7 +114,6 @@ def app(self):
89114

90115

91116
def __str__(self):
92-
self._fetch_all()
93117
flags = []
94118
category = None
95119
if self.category_id == 0:
@@ -127,7 +151,7 @@ def __str__(self):
127151
flags.append("positive_action")
128152
if self.negative_action:
129153
flags.append("negative_action")
130-
return category + " " + " ".join(flags) + " " + self.app_id + " " + self.title + " " + str(self.subtitle) + " " + str(self.message) + " " + self.date
154+
return category + " " + " ".join(flags) + " " + self.app_id + " " + str(self.title) + " " + str(self.subtitle) + " " + str(self.message) + " "# + self.date
131155

132156
class AppleNotificationService(Service):
133157
"""Notification service."""
@@ -141,23 +165,37 @@ def __init__(self, service=None):
141165
super().__init__(service=service)
142166
self._active_notifications = {}
143167

144-
def _update_notifications(self):
168+
def _update(self):
145169
while self.notification_source.in_waiting > 7:
146170
buffer = self.notification_source.read(8)
147171
event_id, event_flags, category_id, category_count, id = struct.unpack("<BBBBI", buffer)
148172
if event_id == 0:
149173
self._active_notifications[id] = Notification(id, event_flags, category_id,
150174
category_count, control_point=self.control_point, data_source=self.data_source)
175+
yield self._active_notifications[id]
151176
elif event_id == 1:
152177
self._active_notifications[id].update(event_flags, category_id, category_count)
178+
yield None
153179
elif event_id == 2:
154180
self._active_notifications[id].removed = True
155181
del self._active_notifications[id]
182+
yield None
156183
#print(event_id, event_flags, category_id, category_count)
157184

158-
def __iter__(self):
159-
self._update_notifications()
160-
return iter(self._active_notifications.values())
185+
186+
def wait_for_new_notifications(self, timeout=None):
187+
start_time = time.monotonic()
188+
while timeout is None or timeout > time.monotonic() - start_time:
189+
new_notification = next(self._update())
190+
if new_notification:
191+
yield new_notification
192+
return
193+
194+
@property
195+
def active_notifications(self):
196+
for _ in self._update():
197+
pass
198+
return self._active_notifications
161199

162200
class AppleMediaService(Service):
163201
"""View and control currently playing media. Unimplemented."""

0 commit comments

Comments
 (0)