bumble 0.0.169__py3-none-any.whl → 0.0.172__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +6 -2
- bumble/apps/console.py +1 -1
- bumble/apps/controller_info.py +2 -1
- bumble/apps/pandora_server.py +6 -4
- bumble/apps/speaker/speaker.py +2 -2
- bumble/att.py +77 -51
- bumble/device.py +5 -3
- bumble/gatt.py +22 -20
- bumble/gatt_client.py +67 -32
- bumble/gatt_server.py +75 -31
- bumble/l2cap.py +92 -125
- bumble/pandora/security.py +49 -17
- bumble/smp.py +20 -7
- bumble/transport/android_emulator.py +7 -4
- bumble/transport/android_netsim.py +84 -50
- bumble/transport/common.py +3 -8
- bumble/transport/ws_client.py +9 -7
- bumble/utils.py +109 -1
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/METADATA +2 -1
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/RECORD +24 -24
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/LICENSE +0 -0
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/WHEEL +0 -0
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.169.dist-info → bumble-0.0.172.dist-info}/top_level.txt +0 -0
bumble/gatt_client.py
CHANGED
|
@@ -28,7 +28,18 @@ import asyncio
|
|
|
28
28
|
import logging
|
|
29
29
|
import struct
|
|
30
30
|
from datetime import datetime
|
|
31
|
-
from typing import
|
|
31
|
+
from typing import (
|
|
32
|
+
List,
|
|
33
|
+
Optional,
|
|
34
|
+
Dict,
|
|
35
|
+
Tuple,
|
|
36
|
+
Callable,
|
|
37
|
+
Union,
|
|
38
|
+
Any,
|
|
39
|
+
Iterable,
|
|
40
|
+
Type,
|
|
41
|
+
TYPE_CHECKING,
|
|
42
|
+
)
|
|
32
43
|
|
|
33
44
|
from pyee import EventEmitter
|
|
34
45
|
|
|
@@ -66,8 +77,12 @@ from .gatt import (
|
|
|
66
77
|
GATT_INCLUDE_ATTRIBUTE_TYPE,
|
|
67
78
|
Characteristic,
|
|
68
79
|
ClientCharacteristicConfigurationBits,
|
|
80
|
+
TemplateService,
|
|
69
81
|
)
|
|
70
82
|
|
|
83
|
+
if TYPE_CHECKING:
|
|
84
|
+
from bumble.device import Connection
|
|
85
|
+
|
|
71
86
|
# -----------------------------------------------------------------------------
|
|
72
87
|
# Logging
|
|
73
88
|
# -----------------------------------------------------------------------------
|
|
@@ -78,16 +93,16 @@ logger = logging.getLogger(__name__)
|
|
|
78
93
|
# Proxies
|
|
79
94
|
# -----------------------------------------------------------------------------
|
|
80
95
|
class AttributeProxy(EventEmitter):
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
def __init__(
|
|
97
|
+
self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID
|
|
98
|
+
) -> None:
|
|
84
99
|
EventEmitter.__init__(self)
|
|
85
100
|
self.client = client
|
|
86
101
|
self.handle = handle
|
|
87
102
|
self.end_group_handle = end_group_handle
|
|
88
103
|
self.type = attribute_type
|
|
89
104
|
|
|
90
|
-
async def read_value(self, no_long_read=False):
|
|
105
|
+
async def read_value(self, no_long_read: bool = False) -> bytes:
|
|
91
106
|
return self.decode_value(
|
|
92
107
|
await self.client.read_value(self.handle, no_long_read)
|
|
93
108
|
)
|
|
@@ -97,13 +112,13 @@ class AttributeProxy(EventEmitter):
|
|
|
97
112
|
self.handle, self.encode_value(value), with_response
|
|
98
113
|
)
|
|
99
114
|
|
|
100
|
-
def encode_value(self, value):
|
|
115
|
+
def encode_value(self, value: Any) -> bytes:
|
|
101
116
|
return value
|
|
102
117
|
|
|
103
|
-
def decode_value(self, value_bytes):
|
|
118
|
+
def decode_value(self, value_bytes: bytes) -> Any:
|
|
104
119
|
return value_bytes
|
|
105
120
|
|
|
106
|
-
def __str__(self):
|
|
121
|
+
def __str__(self) -> str:
|
|
107
122
|
return f'Attribute(handle=0x{self.handle:04X}, type={self.type})'
|
|
108
123
|
|
|
109
124
|
|
|
@@ -136,14 +151,14 @@ class ServiceProxy(AttributeProxy):
|
|
|
136
151
|
def get_characteristics_by_uuid(self, uuid):
|
|
137
152
|
return self.client.get_characteristics_by_uuid(uuid, self)
|
|
138
153
|
|
|
139
|
-
def __str__(self):
|
|
154
|
+
def __str__(self) -> str:
|
|
140
155
|
return f'Service(handle=0x{self.handle:04X}, uuid={self.uuid})'
|
|
141
156
|
|
|
142
157
|
|
|
143
158
|
class CharacteristicProxy(AttributeProxy):
|
|
144
159
|
properties: Characteristic.Properties
|
|
145
160
|
descriptors: List[DescriptorProxy]
|
|
146
|
-
subscribers: Dict[Any, Callable]
|
|
161
|
+
subscribers: Dict[Any, Callable[[bytes], Any]]
|
|
147
162
|
|
|
148
163
|
def __init__(
|
|
149
164
|
self,
|
|
@@ -171,7 +186,9 @@ class CharacteristicProxy(AttributeProxy):
|
|
|
171
186
|
return await self.client.discover_descriptors(self)
|
|
172
187
|
|
|
173
188
|
async def subscribe(
|
|
174
|
-
self,
|
|
189
|
+
self,
|
|
190
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
191
|
+
prefer_notify: bool = True,
|
|
175
192
|
):
|
|
176
193
|
if subscriber is not None:
|
|
177
194
|
if subscriber in self.subscribers:
|
|
@@ -195,7 +212,7 @@ class CharacteristicProxy(AttributeProxy):
|
|
|
195
212
|
|
|
196
213
|
return await self.client.unsubscribe(self, subscriber)
|
|
197
214
|
|
|
198
|
-
def __str__(self):
|
|
215
|
+
def __str__(self) -> str:
|
|
199
216
|
return (
|
|
200
217
|
f'Characteristic(handle=0x{self.handle:04X}, '
|
|
201
218
|
f'uuid={self.uuid}, '
|
|
@@ -207,7 +224,7 @@ class DescriptorProxy(AttributeProxy):
|
|
|
207
224
|
def __init__(self, client, handle, descriptor_type):
|
|
208
225
|
super().__init__(client, handle, 0, descriptor_type)
|
|
209
226
|
|
|
210
|
-
def __str__(self):
|
|
227
|
+
def __str__(self) -> str:
|
|
211
228
|
return f'Descriptor(handle=0x{self.handle:04X}, type={self.type})'
|
|
212
229
|
|
|
213
230
|
|
|
@@ -216,8 +233,10 @@ class ProfileServiceProxy:
|
|
|
216
233
|
Base class for profile-specific service proxies
|
|
217
234
|
'''
|
|
218
235
|
|
|
236
|
+
SERVICE_CLASS: Type[TemplateService]
|
|
237
|
+
|
|
219
238
|
@classmethod
|
|
220
|
-
def from_client(cls, client):
|
|
239
|
+
def from_client(cls, client: Client) -> ProfileServiceProxy:
|
|
221
240
|
return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
|
|
222
241
|
|
|
223
242
|
|
|
@@ -227,8 +246,12 @@ class ProfileServiceProxy:
|
|
|
227
246
|
class Client:
|
|
228
247
|
services: List[ServiceProxy]
|
|
229
248
|
cached_values: Dict[int, Tuple[datetime, bytes]]
|
|
249
|
+
notification_subscribers: Dict[int, Callable[[bytes], Any]]
|
|
250
|
+
indication_subscribers: Dict[int, Callable[[bytes], Any]]
|
|
251
|
+
pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
|
|
252
|
+
pending_request: Optional[ATT_PDU]
|
|
230
253
|
|
|
231
|
-
def __init__(self, connection):
|
|
254
|
+
def __init__(self, connection: Connection) -> None:
|
|
232
255
|
self.connection = connection
|
|
233
256
|
self.mtu_exchange_done = False
|
|
234
257
|
self.request_semaphore = asyncio.Semaphore(1)
|
|
@@ -241,16 +264,16 @@ class Client:
|
|
|
241
264
|
self.services = []
|
|
242
265
|
self.cached_values = {}
|
|
243
266
|
|
|
244
|
-
def send_gatt_pdu(self, pdu):
|
|
267
|
+
def send_gatt_pdu(self, pdu: bytes) -> None:
|
|
245
268
|
self.connection.send_l2cap_pdu(ATT_CID, pdu)
|
|
246
269
|
|
|
247
|
-
async def send_command(self, command):
|
|
270
|
+
async def send_command(self, command: ATT_PDU) -> None:
|
|
248
271
|
logger.debug(
|
|
249
272
|
f'GATT Command from client: [0x{self.connection.handle:04X}] {command}'
|
|
250
273
|
)
|
|
251
274
|
self.send_gatt_pdu(command.to_bytes())
|
|
252
275
|
|
|
253
|
-
async def send_request(self, request):
|
|
276
|
+
async def send_request(self, request: ATT_PDU):
|
|
254
277
|
logger.debug(
|
|
255
278
|
f'GATT Request from client: [0x{self.connection.handle:04X}] {request}'
|
|
256
279
|
)
|
|
@@ -279,14 +302,14 @@ class Client:
|
|
|
279
302
|
|
|
280
303
|
return response
|
|
281
304
|
|
|
282
|
-
def send_confirmation(self, confirmation):
|
|
305
|
+
def send_confirmation(self, confirmation: ATT_Handle_Value_Confirmation) -> None:
|
|
283
306
|
logger.debug(
|
|
284
307
|
f'GATT Confirmation from client: [0x{self.connection.handle:04X}] '
|
|
285
308
|
f'{confirmation}'
|
|
286
309
|
)
|
|
287
310
|
self.send_gatt_pdu(confirmation.to_bytes())
|
|
288
311
|
|
|
289
|
-
async def request_mtu(self, mtu):
|
|
312
|
+
async def request_mtu(self, mtu: int) -> int:
|
|
290
313
|
# Check the range
|
|
291
314
|
if mtu < ATT_DEFAULT_MTU:
|
|
292
315
|
raise ValueError(f'MTU must be >= {ATT_DEFAULT_MTU}')
|
|
@@ -313,10 +336,12 @@ class Client:
|
|
|
313
336
|
|
|
314
337
|
return self.connection.att_mtu
|
|
315
338
|
|
|
316
|
-
def get_services_by_uuid(self, uuid):
|
|
339
|
+
def get_services_by_uuid(self, uuid: UUID) -> List[ServiceProxy]:
|
|
317
340
|
return [service for service in self.services if service.uuid == uuid]
|
|
318
341
|
|
|
319
|
-
def get_characteristics_by_uuid(
|
|
342
|
+
def get_characteristics_by_uuid(
|
|
343
|
+
self, uuid: UUID, service: Optional[ServiceProxy] = None
|
|
344
|
+
) -> List[CharacteristicProxy]:
|
|
320
345
|
services = [service] if service else self.services
|
|
321
346
|
return [
|
|
322
347
|
c
|
|
@@ -363,7 +388,7 @@ class Client:
|
|
|
363
388
|
if not already_known:
|
|
364
389
|
self.services.append(service)
|
|
365
390
|
|
|
366
|
-
async def discover_services(self, uuids=
|
|
391
|
+
async def discover_services(self, uuids: Iterable[UUID] = []) -> List[ServiceProxy]:
|
|
367
392
|
'''
|
|
368
393
|
See Vol 3, Part G - 4.4.1 Discover All Primary Services
|
|
369
394
|
'''
|
|
@@ -435,7 +460,7 @@ class Client:
|
|
|
435
460
|
|
|
436
461
|
return services
|
|
437
462
|
|
|
438
|
-
async def discover_service(self, uuid):
|
|
463
|
+
async def discover_service(self, uuid: Union[str, UUID]) -> List[ServiceProxy]:
|
|
439
464
|
'''
|
|
440
465
|
See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
|
|
441
466
|
'''
|
|
@@ -468,7 +493,7 @@ class Client:
|
|
|
468
493
|
f'{HCI_Constant.error_name(response.error_code)}'
|
|
469
494
|
)
|
|
470
495
|
# TODO raise appropriate exception
|
|
471
|
-
return
|
|
496
|
+
return []
|
|
472
497
|
break
|
|
473
498
|
|
|
474
499
|
for attribute_handle, end_group_handle in response.handles_information:
|
|
@@ -480,7 +505,7 @@ class Client:
|
|
|
480
505
|
logger.warning(
|
|
481
506
|
f'bogus handle values: {attribute_handle} {end_group_handle}'
|
|
482
507
|
)
|
|
483
|
-
return
|
|
508
|
+
return []
|
|
484
509
|
|
|
485
510
|
# Create a service proxy for this service
|
|
486
511
|
service = ServiceProxy(
|
|
@@ -721,7 +746,7 @@ class Client:
|
|
|
721
746
|
|
|
722
747
|
return descriptors
|
|
723
748
|
|
|
724
|
-
async def discover_attributes(self):
|
|
749
|
+
async def discover_attributes(self) -> List[AttributeProxy]:
|
|
725
750
|
'''
|
|
726
751
|
Discover all attributes, regardless of type
|
|
727
752
|
'''
|
|
@@ -844,7 +869,9 @@ class Client:
|
|
|
844
869
|
# No more subscribers left
|
|
845
870
|
await self.write_value(cccd, b'\x00\x00', with_response=True)
|
|
846
871
|
|
|
847
|
-
async def read_value(
|
|
872
|
+
async def read_value(
|
|
873
|
+
self, attribute: Union[int, AttributeProxy], no_long_read: bool = False
|
|
874
|
+
) -> Any:
|
|
848
875
|
'''
|
|
849
876
|
See Vol 3, Part G - 4.8.1 Read Characteristic Value
|
|
850
877
|
|
|
@@ -905,7 +932,9 @@ class Client:
|
|
|
905
932
|
# Return the value as bytes
|
|
906
933
|
return attribute_value
|
|
907
934
|
|
|
908
|
-
async def read_characteristics_by_uuid(
|
|
935
|
+
async def read_characteristics_by_uuid(
|
|
936
|
+
self, uuid: UUID, service: Optional[ServiceProxy]
|
|
937
|
+
) -> List[bytes]:
|
|
909
938
|
'''
|
|
910
939
|
See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
|
|
911
940
|
'''
|
|
@@ -960,7 +989,12 @@ class Client:
|
|
|
960
989
|
|
|
961
990
|
return characteristics_values
|
|
962
991
|
|
|
963
|
-
async def write_value(
|
|
992
|
+
async def write_value(
|
|
993
|
+
self,
|
|
994
|
+
attribute: Union[int, AttributeProxy],
|
|
995
|
+
value: bytes,
|
|
996
|
+
with_response: bool = False,
|
|
997
|
+
) -> None:
|
|
964
998
|
'''
|
|
965
999
|
See Vol 3, Part G - 4.9.1 Write Without Response & 4.9.3 Write Characteristic
|
|
966
1000
|
Value
|
|
@@ -990,7 +1024,7 @@ class Client:
|
|
|
990
1024
|
)
|
|
991
1025
|
)
|
|
992
1026
|
|
|
993
|
-
def on_gatt_pdu(self, att_pdu):
|
|
1027
|
+
def on_gatt_pdu(self, att_pdu: ATT_PDU) -> None:
|
|
994
1028
|
logger.debug(
|
|
995
1029
|
f'GATT Response to client: [0x{self.connection.handle:04X}] {att_pdu}'
|
|
996
1030
|
)
|
|
@@ -1013,6 +1047,7 @@ class Client:
|
|
|
1013
1047
|
return
|
|
1014
1048
|
|
|
1015
1049
|
# Return the response to the coroutine that is waiting for it
|
|
1050
|
+
assert self.pending_response is not None
|
|
1016
1051
|
self.pending_response.set_result(att_pdu)
|
|
1017
1052
|
else:
|
|
1018
1053
|
handler_name = f'on_{att_pdu.name.lower()}'
|
|
@@ -1060,7 +1095,7 @@ class Client:
|
|
|
1060
1095
|
# Confirm that we received the indication
|
|
1061
1096
|
self.send_confirmation(ATT_Handle_Value_Confirmation())
|
|
1062
1097
|
|
|
1063
|
-
def cache_value(self, attribute_handle: int, value: bytes):
|
|
1098
|
+
def cache_value(self, attribute_handle: int, value: bytes) -> None:
|
|
1064
1099
|
self.cached_values[attribute_handle] = (
|
|
1065
1100
|
datetime.now(),
|
|
1066
1101
|
value,
|
bumble/gatt_server.py
CHANGED
|
@@ -23,11 +23,12 @@
|
|
|
23
23
|
# -----------------------------------------------------------------------------
|
|
24
24
|
# Imports
|
|
25
25
|
# -----------------------------------------------------------------------------
|
|
26
|
+
from __future__ import annotations
|
|
26
27
|
import asyncio
|
|
27
28
|
import logging
|
|
28
29
|
from collections import defaultdict
|
|
29
30
|
import struct
|
|
30
|
-
from typing import List, Tuple, Optional, TypeVar, Type
|
|
31
|
+
from typing import List, Tuple, Optional, TypeVar, Type, Dict, Iterable, TYPE_CHECKING
|
|
31
32
|
from pyee import EventEmitter
|
|
32
33
|
|
|
33
34
|
from .colors import color
|
|
@@ -42,6 +43,7 @@ from .att import (
|
|
|
42
43
|
ATT_INVALID_OFFSET_ERROR,
|
|
43
44
|
ATT_REQUEST_NOT_SUPPORTED_ERROR,
|
|
44
45
|
ATT_REQUESTS,
|
|
46
|
+
ATT_PDU,
|
|
45
47
|
ATT_UNLIKELY_ERROR_ERROR,
|
|
46
48
|
ATT_UNSUPPORTED_GROUP_TYPE_ERROR,
|
|
47
49
|
ATT_Error,
|
|
@@ -73,6 +75,8 @@ from .gatt import (
|
|
|
73
75
|
Service,
|
|
74
76
|
)
|
|
75
77
|
|
|
78
|
+
if TYPE_CHECKING:
|
|
79
|
+
from bumble.device import Device, Connection
|
|
76
80
|
|
|
77
81
|
# -----------------------------------------------------------------------------
|
|
78
82
|
# Logging
|
|
@@ -91,8 +95,13 @@ GATT_SERVER_DEFAULT_MAX_MTU = 517
|
|
|
91
95
|
# -----------------------------------------------------------------------------
|
|
92
96
|
class Server(EventEmitter):
|
|
93
97
|
attributes: List[Attribute]
|
|
98
|
+
services: List[Service]
|
|
99
|
+
attributes_by_handle: Dict[int, Attribute]
|
|
100
|
+
subscribers: Dict[int, Dict[int, bytes]]
|
|
101
|
+
indication_semaphores: defaultdict[int, asyncio.Semaphore]
|
|
102
|
+
pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
|
|
94
103
|
|
|
95
|
-
def __init__(self, device):
|
|
104
|
+
def __init__(self, device: Device) -> None:
|
|
96
105
|
super().__init__()
|
|
97
106
|
self.device = device
|
|
98
107
|
self.services = []
|
|
@@ -107,16 +116,16 @@ class Server(EventEmitter):
|
|
|
107
116
|
self.indication_semaphores = defaultdict(lambda: asyncio.Semaphore(1))
|
|
108
117
|
self.pending_confirmations = defaultdict(lambda: None)
|
|
109
118
|
|
|
110
|
-
def __str__(self):
|
|
119
|
+
def __str__(self) -> str:
|
|
111
120
|
return "\n".join(map(str, self.attributes))
|
|
112
121
|
|
|
113
|
-
def send_gatt_pdu(self, connection_handle, pdu):
|
|
122
|
+
def send_gatt_pdu(self, connection_handle: int, pdu: bytes) -> None:
|
|
114
123
|
self.device.send_l2cap_pdu(connection_handle, ATT_CID, pdu)
|
|
115
124
|
|
|
116
|
-
def next_handle(self):
|
|
125
|
+
def next_handle(self) -> int:
|
|
117
126
|
return 1 + len(self.attributes)
|
|
118
127
|
|
|
119
|
-
def get_advertising_service_data(self):
|
|
128
|
+
def get_advertising_service_data(self) -> Dict[Attribute, bytes]:
|
|
120
129
|
return {
|
|
121
130
|
attribute: data
|
|
122
131
|
for attribute in self.attributes
|
|
@@ -124,7 +133,7 @@ class Server(EventEmitter):
|
|
|
124
133
|
and (data := attribute.get_advertising_data())
|
|
125
134
|
}
|
|
126
135
|
|
|
127
|
-
def get_attribute(self, handle):
|
|
136
|
+
def get_attribute(self, handle: int) -> Optional[Attribute]:
|
|
128
137
|
attribute = self.attributes_by_handle.get(handle)
|
|
129
138
|
if attribute:
|
|
130
139
|
return attribute
|
|
@@ -173,12 +182,17 @@ class Server(EventEmitter):
|
|
|
173
182
|
|
|
174
183
|
return next(
|
|
175
184
|
(
|
|
176
|
-
(
|
|
185
|
+
(
|
|
186
|
+
attribute,
|
|
187
|
+
self.get_attribute(attribute.characteristic.handle),
|
|
188
|
+
) # type: ignore
|
|
177
189
|
for attribute in map(
|
|
178
190
|
self.get_attribute,
|
|
179
191
|
range(service_handle.handle, service_handle.end_group_handle + 1),
|
|
180
192
|
)
|
|
181
|
-
if attribute
|
|
193
|
+
if attribute is not None
|
|
194
|
+
and attribute.type == GATT_CHARACTERISTIC_ATTRIBUTE_TYPE
|
|
195
|
+
and isinstance(attribute, CharacteristicDeclaration)
|
|
182
196
|
and attribute.characteristic.uuid == characteristic_uuid
|
|
183
197
|
),
|
|
184
198
|
None,
|
|
@@ -197,7 +211,7 @@ class Server(EventEmitter):
|
|
|
197
211
|
|
|
198
212
|
return next(
|
|
199
213
|
(
|
|
200
|
-
attribute
|
|
214
|
+
attribute # type: ignore
|
|
201
215
|
for attribute in map(
|
|
202
216
|
self.get_attribute,
|
|
203
217
|
range(
|
|
@@ -205,12 +219,12 @@ class Server(EventEmitter):
|
|
|
205
219
|
characteristic_value.end_group_handle + 1,
|
|
206
220
|
),
|
|
207
221
|
)
|
|
208
|
-
if attribute.type == descriptor_uuid
|
|
222
|
+
if attribute is not None and attribute.type == descriptor_uuid
|
|
209
223
|
),
|
|
210
224
|
None,
|
|
211
225
|
)
|
|
212
226
|
|
|
213
|
-
def add_attribute(self, attribute):
|
|
227
|
+
def add_attribute(self, attribute: Attribute) -> None:
|
|
214
228
|
# Assign a handle to this attribute
|
|
215
229
|
attribute.handle = self.next_handle()
|
|
216
230
|
attribute.end_group_handle = (
|
|
@@ -220,7 +234,7 @@ class Server(EventEmitter):
|
|
|
220
234
|
# Add this attribute to the list
|
|
221
235
|
self.attributes.append(attribute)
|
|
222
236
|
|
|
223
|
-
def add_service(self, service: Service):
|
|
237
|
+
def add_service(self, service: Service) -> None:
|
|
224
238
|
# Add the service attribute to the DB
|
|
225
239
|
self.add_attribute(service)
|
|
226
240
|
|
|
@@ -285,11 +299,13 @@ class Server(EventEmitter):
|
|
|
285
299
|
service.end_group_handle = self.attributes[-1].handle
|
|
286
300
|
self.services.append(service)
|
|
287
301
|
|
|
288
|
-
def add_services(self, services):
|
|
302
|
+
def add_services(self, services: Iterable[Service]) -> None:
|
|
289
303
|
for service in services:
|
|
290
304
|
self.add_service(service)
|
|
291
305
|
|
|
292
|
-
def read_cccd(
|
|
306
|
+
def read_cccd(
|
|
307
|
+
self, connection: Optional[Connection], characteristic: Characteristic
|
|
308
|
+
) -> bytes:
|
|
293
309
|
if connection is None:
|
|
294
310
|
return bytes([0, 0])
|
|
295
311
|
|
|
@@ -300,7 +316,12 @@ class Server(EventEmitter):
|
|
|
300
316
|
|
|
301
317
|
return cccd or bytes([0, 0])
|
|
302
318
|
|
|
303
|
-
def write_cccd(
|
|
319
|
+
def write_cccd(
|
|
320
|
+
self,
|
|
321
|
+
connection: Connection,
|
|
322
|
+
characteristic: Characteristic,
|
|
323
|
+
value: bytes,
|
|
324
|
+
) -> None:
|
|
304
325
|
logger.debug(
|
|
305
326
|
f'Subscription update for connection=0x{connection.handle:04X}, '
|
|
306
327
|
f'handle=0x{characteristic.handle:04X}: {value.hex()}'
|
|
@@ -327,13 +348,19 @@ class Server(EventEmitter):
|
|
|
327
348
|
indicate_enabled,
|
|
328
349
|
)
|
|
329
350
|
|
|
330
|
-
def send_response(self, connection, response):
|
|
351
|
+
def send_response(self, connection: Connection, response: ATT_PDU) -> None:
|
|
331
352
|
logger.debug(
|
|
332
353
|
f'GATT Response from server: [0x{connection.handle:04X}] {response}'
|
|
333
354
|
)
|
|
334
355
|
self.send_gatt_pdu(connection.handle, response.to_bytes())
|
|
335
356
|
|
|
336
|
-
async def notify_subscriber(
|
|
357
|
+
async def notify_subscriber(
|
|
358
|
+
self,
|
|
359
|
+
connection: Connection,
|
|
360
|
+
attribute: Attribute,
|
|
361
|
+
value: Optional[bytes] = None,
|
|
362
|
+
force: bool = False,
|
|
363
|
+
) -> None:
|
|
337
364
|
# Check if there's a subscriber
|
|
338
365
|
if not force:
|
|
339
366
|
subscribers = self.subscribers.get(connection.handle)
|
|
@@ -370,7 +397,13 @@ class Server(EventEmitter):
|
|
|
370
397
|
)
|
|
371
398
|
self.send_gatt_pdu(connection.handle, bytes(notification))
|
|
372
399
|
|
|
373
|
-
async def indicate_subscriber(
|
|
400
|
+
async def indicate_subscriber(
|
|
401
|
+
self,
|
|
402
|
+
connection: Connection,
|
|
403
|
+
attribute: Attribute,
|
|
404
|
+
value: Optional[bytes] = None,
|
|
405
|
+
force: bool = False,
|
|
406
|
+
) -> None:
|
|
374
407
|
# Check if there's a subscriber
|
|
375
408
|
if not force:
|
|
376
409
|
subscribers = self.subscribers.get(connection.handle)
|
|
@@ -411,15 +444,13 @@ class Server(EventEmitter):
|
|
|
411
444
|
assert self.pending_confirmations[connection.handle] is None
|
|
412
445
|
|
|
413
446
|
# Create a future value to hold the eventual response
|
|
414
|
-
self.pending_confirmations[
|
|
447
|
+
pending_confirmation = self.pending_confirmations[
|
|
415
448
|
connection.handle
|
|
416
449
|
] = asyncio.get_running_loop().create_future()
|
|
417
450
|
|
|
418
451
|
try:
|
|
419
452
|
self.send_gatt_pdu(connection.handle, indication.to_bytes())
|
|
420
|
-
await asyncio.wait_for(
|
|
421
|
-
self.pending_confirmations[connection.handle], GATT_REQUEST_TIMEOUT
|
|
422
|
-
)
|
|
453
|
+
await asyncio.wait_for(pending_confirmation, GATT_REQUEST_TIMEOUT)
|
|
423
454
|
except asyncio.TimeoutError as error:
|
|
424
455
|
logger.warning(color('!!! GATT Indicate timeout', 'red'))
|
|
425
456
|
raise TimeoutError(f'GATT timeout for {indication.name}') from error
|
|
@@ -427,8 +458,12 @@ class Server(EventEmitter):
|
|
|
427
458
|
self.pending_confirmations[connection.handle] = None
|
|
428
459
|
|
|
429
460
|
async def notify_or_indicate_subscribers(
|
|
430
|
-
self,
|
|
431
|
-
|
|
461
|
+
self,
|
|
462
|
+
indicate: bool,
|
|
463
|
+
attribute: Attribute,
|
|
464
|
+
value: Optional[bytes] = None,
|
|
465
|
+
force: bool = False,
|
|
466
|
+
) -> None:
|
|
432
467
|
# Get all the connections for which there's at least one subscription
|
|
433
468
|
connections = [
|
|
434
469
|
connection
|
|
@@ -450,13 +485,23 @@ class Server(EventEmitter):
|
|
|
450
485
|
]
|
|
451
486
|
)
|
|
452
487
|
|
|
453
|
-
async def notify_subscribers(
|
|
488
|
+
async def notify_subscribers(
|
|
489
|
+
self,
|
|
490
|
+
attribute: Attribute,
|
|
491
|
+
value: Optional[bytes] = None,
|
|
492
|
+
force: bool = False,
|
|
493
|
+
):
|
|
454
494
|
return await self.notify_or_indicate_subscribers(False, attribute, value, force)
|
|
455
495
|
|
|
456
|
-
async def indicate_subscribers(
|
|
496
|
+
async def indicate_subscribers(
|
|
497
|
+
self,
|
|
498
|
+
attribute: Attribute,
|
|
499
|
+
value: Optional[bytes] = None,
|
|
500
|
+
force: bool = False,
|
|
501
|
+
):
|
|
457
502
|
return await self.notify_or_indicate_subscribers(True, attribute, value, force)
|
|
458
503
|
|
|
459
|
-
def on_disconnection(self, connection):
|
|
504
|
+
def on_disconnection(self, connection: Connection) -> None:
|
|
460
505
|
if connection.handle in self.subscribers:
|
|
461
506
|
del self.subscribers[connection.handle]
|
|
462
507
|
if connection.handle in self.indication_semaphores:
|
|
@@ -464,7 +509,7 @@ class Server(EventEmitter):
|
|
|
464
509
|
if connection.handle in self.pending_confirmations:
|
|
465
510
|
del self.pending_confirmations[connection.handle]
|
|
466
511
|
|
|
467
|
-
def on_gatt_pdu(self, connection, att_pdu):
|
|
512
|
+
def on_gatt_pdu(self, connection: Connection, att_pdu: ATT_PDU) -> None:
|
|
468
513
|
logger.debug(f'GATT Request to server: [0x{connection.handle:04X}] {att_pdu}')
|
|
469
514
|
handler_name = f'on_{att_pdu.name.lower()}'
|
|
470
515
|
handler = getattr(self, handler_name, None)
|
|
@@ -506,7 +551,7 @@ class Server(EventEmitter):
|
|
|
506
551
|
#######################################################
|
|
507
552
|
# ATT handlers
|
|
508
553
|
#######################################################
|
|
509
|
-
def on_att_request(self, connection, pdu):
|
|
554
|
+
def on_att_request(self, connection: Connection, pdu: ATT_PDU) -> None:
|
|
510
555
|
'''
|
|
511
556
|
Handler for requests without a more specific handler
|
|
512
557
|
'''
|
|
@@ -679,7 +724,6 @@ class Server(EventEmitter):
|
|
|
679
724
|
and attribute.handle <= request.ending_handle
|
|
680
725
|
and pdu_space_available
|
|
681
726
|
):
|
|
682
|
-
|
|
683
727
|
try:
|
|
684
728
|
attribute_value = attribute.read_value(connection)
|
|
685
729
|
except ATT_Error as error:
|