bumble 0.0.219__py3-none-any.whl → 0.0.221__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 +2 -2
- bumble/a2dp.py +5 -5
- bumble/apps/auracast.py +746 -479
- bumble/apps/bench.py +4 -5
- bumble/apps/console.py +5 -10
- bumble/apps/controller_info.py +12 -7
- bumble/apps/controller_loopback.py +1 -2
- bumble/apps/device_info.py +2 -3
- bumble/apps/gatt_dump.py +0 -1
- bumble/apps/lea_unicast/app.py +1 -1
- bumble/apps/pair.py +49 -46
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +10 -12
- bumble/apps/rfcomm_bridge.py +10 -11
- bumble/apps/scan.py +1 -3
- bumble/apps/speaker/speaker.py +3 -4
- bumble/at.py +4 -5
- bumble/att.py +91 -25
- bumble/audio/io.py +8 -6
- bumble/avc.py +1 -2
- bumble/avctp.py +2 -3
- bumble/avdtp.py +53 -57
- bumble/avrcp.py +25 -27
- bumble/codecs.py +15 -15
- bumble/colors.py +7 -8
- bumble/controller.py +1201 -643
- bumble/core.py +41 -49
- bumble/crypto/__init__.py +2 -1
- bumble/crypto/builtin.py +2 -8
- bumble/data_types.py +2 -1
- bumble/decoder.py +2 -3
- bumble/device.py +278 -325
- bumble/drivers/__init__.py +3 -2
- bumble/drivers/intel.py +6 -8
- bumble/drivers/rtk.py +1 -1
- bumble/gatt.py +9 -9
- bumble/gatt_adapters.py +6 -6
- bumble/gatt_client.py +110 -60
- bumble/gatt_server.py +209 -139
- bumble/hci.py +87 -74
- bumble/helpers.py +5 -5
- bumble/hfp.py +27 -26
- bumble/hid.py +9 -9
- bumble/host.py +44 -50
- bumble/keys.py +17 -17
- bumble/l2cap.py +1015 -218
- bumble/link.py +54 -284
- bumble/ll.py +200 -0
- bumble/lmp.py +324 -0
- bumble/pairing.py +14 -15
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/device.py +6 -4
- bumble/pandora/host.py +19 -10
- bumble/pandora/l2cap.py +8 -9
- bumble/pandora/security.py +18 -16
- bumble/pandora/utils.py +4 -4
- bumble/profiles/aics.py +6 -8
- bumble/profiles/ams.py +3 -5
- bumble/profiles/ancs.py +11 -11
- bumble/profiles/ascs.py +5 -5
- bumble/profiles/asha.py +10 -9
- bumble/profiles/bass.py +9 -3
- bumble/profiles/battery_service.py +1 -2
- bumble/profiles/csip.py +9 -10
- bumble/profiles/device_information_service.py +16 -17
- bumble/profiles/gap.py +3 -4
- bumble/profiles/gatt_service.py +0 -1
- bumble/profiles/gmap.py +12 -13
- bumble/profiles/hap.py +3 -3
- bumble/profiles/heart_rate_service.py +7 -8
- bumble/profiles/le_audio.py +1 -1
- bumble/profiles/mcp.py +28 -28
- bumble/profiles/pacs.py +13 -17
- bumble/profiles/pbp.py +16 -0
- bumble/profiles/vcs.py +2 -2
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +19 -18
- bumble/sdp.py +12 -11
- bumble/smp.py +20 -30
- bumble/snoop.py +12 -5
- bumble/tools/generate_company_id_list.py +1 -1
- bumble/tools/intel_util.py +2 -2
- bumble/tools/rtk_fw_download.py +1 -1
- bumble/tools/rtk_util.py +1 -1
- bumble/transport/__init__.py +1 -2
- bumble/transport/android_emulator.py +2 -3
- bumble/transport/android_netsim.py +49 -40
- bumble/transport/common.py +9 -9
- bumble/transport/file.py +1 -2
- bumble/transport/hci_socket.py +2 -3
- bumble/transport/pty.py +3 -5
- bumble/transport/pyusb.py +8 -5
- bumble/transport/serial.py +1 -2
- bumble/transport/vhci.py +1 -2
- bumble/transport/ws_server.py +2 -3
- bumble/utils.py +23 -14
- bumble/vendor/android/hci.py +4 -2
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/METADATA +4 -3
- bumble-0.0.221.dist-info/RECORD +185 -0
- bumble-0.0.219.dist-info/RECORD +0 -183
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/gatt_server.py
CHANGED
|
@@ -29,11 +29,11 @@ import asyncio
|
|
|
29
29
|
import logging
|
|
30
30
|
import struct
|
|
31
31
|
from collections import defaultdict
|
|
32
|
-
from
|
|
32
|
+
from collections.abc import Iterable
|
|
33
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
33
34
|
|
|
34
|
-
from bumble import att, utils
|
|
35
|
+
from bumble import att, core, l2cap, utils
|
|
35
36
|
from bumble.colors import color
|
|
36
|
-
from bumble.core import UUID
|
|
37
37
|
from bumble.gatt import (
|
|
38
38
|
GATT_CHARACTERISTIC_ATTRIBUTE_TYPE,
|
|
39
39
|
GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
|
|
@@ -43,14 +43,13 @@ from bumble.gatt import (
|
|
|
43
43
|
GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
|
|
44
44
|
Characteristic,
|
|
45
45
|
CharacteristicDeclaration,
|
|
46
|
-
CharacteristicValue,
|
|
47
46
|
Descriptor,
|
|
48
47
|
IncludedServiceDeclaration,
|
|
49
48
|
Service,
|
|
50
49
|
)
|
|
51
50
|
|
|
52
51
|
if TYPE_CHECKING:
|
|
53
|
-
from bumble.device import
|
|
52
|
+
from bumble.device import Device
|
|
54
53
|
|
|
55
54
|
# -----------------------------------------------------------------------------
|
|
56
55
|
# Logging
|
|
@@ -64,6 +63,18 @@ logger = logging.getLogger(__name__)
|
|
|
64
63
|
GATT_SERVER_DEFAULT_MAX_MTU = 517
|
|
65
64
|
|
|
66
65
|
|
|
66
|
+
# -----------------------------------------------------------------------------
|
|
67
|
+
# Helpers
|
|
68
|
+
# -----------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _bearer_id(bearer: att.Bearer) -> str:
|
|
72
|
+
if att.is_enhanced_bearer(bearer):
|
|
73
|
+
return f'[0x{bearer.connection.handle:04X}|CID=0x{bearer.source_cid:04X}]'
|
|
74
|
+
else:
|
|
75
|
+
return f'[0x{bearer.handle:04X}]'
|
|
76
|
+
|
|
77
|
+
|
|
67
78
|
# -----------------------------------------------------------------------------
|
|
68
79
|
# GATT Server
|
|
69
80
|
# -----------------------------------------------------------------------------
|
|
@@ -71,9 +82,9 @@ class Server(utils.EventEmitter):
|
|
|
71
82
|
attributes: list[att.Attribute]
|
|
72
83
|
services: list[Service]
|
|
73
84
|
attributes_by_handle: dict[int, att.Attribute]
|
|
74
|
-
subscribers: dict[
|
|
75
|
-
indication_semaphores: defaultdict[
|
|
76
|
-
pending_confirmations: defaultdict[
|
|
85
|
+
subscribers: dict[att.Bearer, dict[int, bytes]]
|
|
86
|
+
indication_semaphores: defaultdict[att.Bearer, asyncio.Semaphore]
|
|
87
|
+
pending_confirmations: defaultdict[att.Bearer, asyncio.futures.Future | None]
|
|
77
88
|
|
|
78
89
|
EVENT_CHARACTERISTIC_SUBSCRIPTION = "characteristic_subscription"
|
|
79
90
|
|
|
@@ -95,8 +106,29 @@ class Server(utils.EventEmitter):
|
|
|
95
106
|
def __str__(self) -> str:
|
|
96
107
|
return "\n".join(map(str, self.attributes))
|
|
97
108
|
|
|
98
|
-
def
|
|
99
|
-
self
|
|
109
|
+
def register_eatt(
|
|
110
|
+
self, spec: l2cap.LeCreditBasedChannelSpec | None = None
|
|
111
|
+
) -> l2cap.LeCreditBasedChannelServer:
|
|
112
|
+
def on_channel(channel: l2cap.LeCreditBasedChannel):
|
|
113
|
+
logger.debug(
|
|
114
|
+
"New EATT Bearer Connection=0x%04X CID=0x%04X",
|
|
115
|
+
channel.connection.handle,
|
|
116
|
+
channel.source_cid,
|
|
117
|
+
)
|
|
118
|
+
channel.att_mtu = att.ATT_DEFAULT_MTU
|
|
119
|
+
channel.sink = lambda pdu: self.on_gatt_pdu(
|
|
120
|
+
channel, att.ATT_PDU.from_bytes(pdu)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return self.device.create_l2cap_server(
|
|
124
|
+
spec or l2cap.LeCreditBasedChannelSpec(psm=att.EATT_PSM), handler=on_channel
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def send_gatt_pdu(self, bearer: att.Bearer, pdu: bytes) -> None:
|
|
128
|
+
if att.is_enhanced_bearer(bearer):
|
|
129
|
+
bearer.write(pdu)
|
|
130
|
+
else:
|
|
131
|
+
self.device.send_l2cap_pdu(bearer.handle, att.ATT_CID, pdu)
|
|
100
132
|
|
|
101
133
|
def next_handle(self) -> int:
|
|
102
134
|
return 1 + len(self.attributes)
|
|
@@ -109,7 +141,7 @@ class Server(utils.EventEmitter):
|
|
|
109
141
|
and (data := attribute.get_advertising_data())
|
|
110
142
|
}
|
|
111
143
|
|
|
112
|
-
def get_attribute(self, handle: int) ->
|
|
144
|
+
def get_attribute(self, handle: int) -> att.Attribute | None:
|
|
113
145
|
attribute = self.attributes_by_handle.get(handle)
|
|
114
146
|
if attribute:
|
|
115
147
|
return attribute
|
|
@@ -126,7 +158,7 @@ class Server(utils.EventEmitter):
|
|
|
126
158
|
|
|
127
159
|
def get_attribute_group(
|
|
128
160
|
self, handle: int, group_type: type[AttributeGroupType]
|
|
129
|
-
) ->
|
|
161
|
+
) -> AttributeGroupType | None:
|
|
130
162
|
return next(
|
|
131
163
|
(
|
|
132
164
|
attribute
|
|
@@ -137,7 +169,7 @@ class Server(utils.EventEmitter):
|
|
|
137
169
|
None,
|
|
138
170
|
)
|
|
139
171
|
|
|
140
|
-
def get_service_attribute(self, service_uuid: UUID) ->
|
|
172
|
+
def get_service_attribute(self, service_uuid: core.UUID) -> Service | None:
|
|
141
173
|
return next(
|
|
142
174
|
(
|
|
143
175
|
attribute
|
|
@@ -150,8 +182,8 @@ class Server(utils.EventEmitter):
|
|
|
150
182
|
)
|
|
151
183
|
|
|
152
184
|
def get_characteristic_attributes(
|
|
153
|
-
self, service_uuid: UUID, characteristic_uuid: UUID
|
|
154
|
-
) ->
|
|
185
|
+
self, service_uuid: core.UUID, characteristic_uuid: core.UUID
|
|
186
|
+
) -> tuple[CharacteristicDeclaration, Characteristic] | None:
|
|
155
187
|
service_handle = self.get_service_attribute(service_uuid)
|
|
156
188
|
if not service_handle:
|
|
157
189
|
return None
|
|
@@ -175,8 +207,11 @@ class Server(utils.EventEmitter):
|
|
|
175
207
|
)
|
|
176
208
|
|
|
177
209
|
def get_descriptor_attribute(
|
|
178
|
-
self,
|
|
179
|
-
|
|
210
|
+
self,
|
|
211
|
+
service_uuid: core.UUID,
|
|
212
|
+
characteristic_uuid: core.UUID,
|
|
213
|
+
descriptor_uuid: core.UUID,
|
|
214
|
+
) -> Descriptor | None:
|
|
180
215
|
characteristics = self.get_characteristic_attributes(
|
|
181
216
|
service_uuid, characteristic_uuid
|
|
182
217
|
)
|
|
@@ -256,14 +291,7 @@ class Server(utils.EventEmitter):
|
|
|
256
291
|
Descriptor(
|
|
257
292
|
GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
|
|
258
293
|
att.Attribute.READABLE | att.Attribute.WRITEABLE,
|
|
259
|
-
|
|
260
|
-
read=lambda connection, characteristic=characteristic: self.read_cccd(
|
|
261
|
-
connection, characteristic
|
|
262
|
-
),
|
|
263
|
-
write=lambda connection, value, characteristic=characteristic: self.write_cccd(
|
|
264
|
-
connection, characteristic, value
|
|
265
|
-
),
|
|
266
|
-
),
|
|
294
|
+
self.make_descriptor_value(characteristic),
|
|
267
295
|
)
|
|
268
296
|
)
|
|
269
297
|
|
|
@@ -279,10 +307,21 @@ class Server(utils.EventEmitter):
|
|
|
279
307
|
for service in services:
|
|
280
308
|
self.add_service(service)
|
|
281
309
|
|
|
282
|
-
def
|
|
283
|
-
self,
|
|
284
|
-
) ->
|
|
285
|
-
|
|
310
|
+
def make_descriptor_value(
|
|
311
|
+
self, characteristic: Characteristic
|
|
312
|
+
) -> att.AttributeValueV2:
|
|
313
|
+
# It is necessary to use Attribute Value V2 here to identify the bearer of CCCD.
|
|
314
|
+
return att.AttributeValueV2(
|
|
315
|
+
lambda bearer, characteristic=characteristic: self.read_cccd(
|
|
316
|
+
bearer, characteristic
|
|
317
|
+
),
|
|
318
|
+
write=lambda bearer, value, characteristic=characteristic: self.write_cccd(
|
|
319
|
+
bearer, characteristic, value
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def read_cccd(self, bearer: att.Bearer, characteristic: Characteristic) -> bytes:
|
|
324
|
+
subscribers = self.subscribers.get(bearer)
|
|
286
325
|
cccd = None
|
|
287
326
|
if subscribers:
|
|
288
327
|
cccd = subscribers.get(characteristic.handle)
|
|
@@ -291,12 +330,12 @@ class Server(utils.EventEmitter):
|
|
|
291
330
|
|
|
292
331
|
def write_cccd(
|
|
293
332
|
self,
|
|
294
|
-
|
|
333
|
+
bearer: att.Bearer,
|
|
295
334
|
characteristic: Characteristic,
|
|
296
335
|
value: bytes,
|
|
297
336
|
) -> None:
|
|
298
337
|
logger.debug(
|
|
299
|
-
f'Subscription update for connection=
|
|
338
|
+
f'Subscription update for connection={_bearer_id(bearer)}, '
|
|
300
339
|
f'handle=0x{characteristic.handle:04X}: {value.hex()}'
|
|
301
340
|
)
|
|
302
341
|
|
|
@@ -305,41 +344,60 @@ class Server(utils.EventEmitter):
|
|
|
305
344
|
logger.warning('CCCD value not 2 bytes long')
|
|
306
345
|
return
|
|
307
346
|
|
|
308
|
-
cccds = self.subscribers.setdefault(
|
|
347
|
+
cccds = self.subscribers.setdefault(bearer, {})
|
|
309
348
|
cccds[characteristic.handle] = value
|
|
310
349
|
logger.debug(f'CCCDs: {cccds}')
|
|
311
350
|
notify_enabled = value[0] & 0x01 != 0
|
|
312
351
|
indicate_enabled = value[0] & 0x02 != 0
|
|
313
352
|
characteristic.emit(
|
|
314
353
|
characteristic.EVENT_SUBSCRIPTION,
|
|
315
|
-
|
|
354
|
+
bearer,
|
|
316
355
|
notify_enabled,
|
|
317
356
|
indicate_enabled,
|
|
318
357
|
)
|
|
319
358
|
self.emit(
|
|
320
359
|
self.EVENT_CHARACTERISTIC_SUBSCRIPTION,
|
|
321
|
-
|
|
360
|
+
bearer,
|
|
322
361
|
characteristic,
|
|
323
362
|
notify_enabled,
|
|
324
363
|
indicate_enabled,
|
|
325
364
|
)
|
|
326
365
|
|
|
327
|
-
def send_response(self,
|
|
328
|
-
logger.debug(
|
|
329
|
-
|
|
330
|
-
)
|
|
331
|
-
self.send_gatt_pdu(connection.handle, bytes(response))
|
|
366
|
+
def send_response(self, bearer: att.Bearer, response: att.ATT_PDU) -> None:
|
|
367
|
+
logger.debug(f'GATT Response from server: {_bearer_id(bearer)} {response}')
|
|
368
|
+
self.send_gatt_pdu(bearer, bytes(response))
|
|
332
369
|
|
|
333
370
|
async def notify_subscriber(
|
|
334
371
|
self,
|
|
335
|
-
|
|
372
|
+
bearer: att.Bearer,
|
|
336
373
|
attribute: att.Attribute,
|
|
337
|
-
value:
|
|
374
|
+
value: bytes | None = None,
|
|
338
375
|
force: bool = False,
|
|
376
|
+
) -> None:
|
|
377
|
+
if att.is_enhanced_bearer(bearer) or force:
|
|
378
|
+
return await self._notify_single_subscriber(bearer, attribute, value, force)
|
|
379
|
+
else:
|
|
380
|
+
# If API is called to a Connection and not forced, try to notify all subscribed bearers on it.
|
|
381
|
+
bearers = [
|
|
382
|
+
channel
|
|
383
|
+
for channel in self.device.l2cap_channel_manager.le_coc_channels.get(
|
|
384
|
+
bearer.handle, {}
|
|
385
|
+
).values()
|
|
386
|
+
if channel.psm == att.EATT_PSM
|
|
387
|
+
] + [bearer]
|
|
388
|
+
for bearer in bearers:
|
|
389
|
+
await self._notify_single_subscriber(bearer, attribute, value, force)
|
|
390
|
+
|
|
391
|
+
async def _notify_single_subscriber(
|
|
392
|
+
self,
|
|
393
|
+
bearer: att.Bearer,
|
|
394
|
+
attribute: att.Attribute,
|
|
395
|
+
value: bytes | None,
|
|
396
|
+
force: bool,
|
|
339
397
|
) -> None:
|
|
340
398
|
# Check if there's a subscriber
|
|
341
399
|
if not force:
|
|
342
|
-
subscribers = self.subscribers.get(
|
|
400
|
+
subscribers = self.subscribers.get(bearer)
|
|
343
401
|
if not subscribers:
|
|
344
402
|
logger.debug('not notifying, no subscribers')
|
|
345
403
|
return
|
|
@@ -355,34 +413,53 @@ class Server(utils.EventEmitter):
|
|
|
355
413
|
|
|
356
414
|
# Get or encode the value
|
|
357
415
|
value = (
|
|
358
|
-
await attribute.read_value(
|
|
416
|
+
await attribute.read_value(bearer)
|
|
359
417
|
if value is None
|
|
360
418
|
else attribute.encode_value(value)
|
|
361
419
|
)
|
|
362
420
|
|
|
363
421
|
# Truncate if needed
|
|
364
|
-
if len(value) >
|
|
365
|
-
value = value[:
|
|
422
|
+
if len(value) > bearer.att_mtu - 3:
|
|
423
|
+
value = value[: bearer.att_mtu - 3]
|
|
366
424
|
|
|
367
425
|
# Notify
|
|
368
426
|
notification = att.ATT_Handle_Value_Notification(
|
|
369
427
|
attribute_handle=attribute.handle, attribute_value=value
|
|
370
428
|
)
|
|
371
|
-
logger.debug(
|
|
372
|
-
|
|
373
|
-
)
|
|
374
|
-
self.send_gatt_pdu(connection.handle, bytes(notification))
|
|
429
|
+
logger.debug(f'GATT Notify from server: {_bearer_id(bearer)} {notification}')
|
|
430
|
+
self.send_gatt_pdu(bearer, bytes(notification))
|
|
375
431
|
|
|
376
432
|
async def indicate_subscriber(
|
|
377
433
|
self,
|
|
378
|
-
|
|
434
|
+
bearer: att.Bearer,
|
|
379
435
|
attribute: att.Attribute,
|
|
380
|
-
value:
|
|
436
|
+
value: bytes | None = None,
|
|
381
437
|
force: bool = False,
|
|
438
|
+
) -> None:
|
|
439
|
+
if att.is_enhanced_bearer(bearer) or force:
|
|
440
|
+
return await self._notify_single_subscriber(bearer, attribute, value, force)
|
|
441
|
+
else:
|
|
442
|
+
# If API is called to a Connection and not forced, try to indicate all subscribed bearers on it.
|
|
443
|
+
bearers = [
|
|
444
|
+
channel
|
|
445
|
+
for channel in self.device.l2cap_channel_manager.le_coc_channels.get(
|
|
446
|
+
bearer.handle, {}
|
|
447
|
+
).values()
|
|
448
|
+
if channel.psm == att.EATT_PSM
|
|
449
|
+
] + [bearer]
|
|
450
|
+
for bearer in bearers:
|
|
451
|
+
await self._indicate_single_bearer(bearer, attribute, value, force)
|
|
452
|
+
|
|
453
|
+
async def _indicate_single_bearer(
|
|
454
|
+
self,
|
|
455
|
+
bearer: att.Bearer,
|
|
456
|
+
attribute: att.Attribute,
|
|
457
|
+
value: bytes | None,
|
|
458
|
+
force: bool,
|
|
382
459
|
) -> None:
|
|
383
460
|
# Check if there's a subscriber
|
|
384
461
|
if not force:
|
|
385
|
-
subscribers = self.subscribers.get(
|
|
462
|
+
subscribers = self.subscribers.get(bearer)
|
|
386
463
|
if not subscribers:
|
|
387
464
|
logger.debug('not indicating, no subscribers')
|
|
388
465
|
return
|
|
@@ -398,73 +475,71 @@ class Server(utils.EventEmitter):
|
|
|
398
475
|
|
|
399
476
|
# Get or encode the value
|
|
400
477
|
value = (
|
|
401
|
-
await attribute.read_value(
|
|
478
|
+
await attribute.read_value(bearer)
|
|
402
479
|
if value is None
|
|
403
480
|
else attribute.encode_value(value)
|
|
404
481
|
)
|
|
405
482
|
|
|
406
483
|
# Truncate if needed
|
|
407
|
-
if len(value) >
|
|
408
|
-
value = value[:
|
|
484
|
+
if len(value) > bearer.att_mtu - 3:
|
|
485
|
+
value = value[: bearer.att_mtu - 3]
|
|
409
486
|
|
|
410
487
|
# Indicate
|
|
411
488
|
indication = att.ATT_Handle_Value_Indication(
|
|
412
489
|
attribute_handle=attribute.handle, attribute_value=value
|
|
413
490
|
)
|
|
414
|
-
logger.debug(
|
|
415
|
-
f'GATT Indicate from server: [0x{connection.handle:04X}] {indication}'
|
|
416
|
-
)
|
|
491
|
+
logger.debug(f'GATT Indicate from server: {_bearer_id(bearer)} {indication}')
|
|
417
492
|
|
|
418
493
|
# Wait until we can send (only one pending indication at a time per connection)
|
|
419
|
-
async with self.indication_semaphores[
|
|
420
|
-
assert self.pending_confirmations[
|
|
494
|
+
async with self.indication_semaphores[bearer]:
|
|
495
|
+
assert self.pending_confirmations[bearer] is None
|
|
421
496
|
|
|
422
497
|
# Create a future value to hold the eventual response
|
|
423
|
-
pending_confirmation = self.pending_confirmations[
|
|
498
|
+
pending_confirmation = self.pending_confirmations[bearer] = (
|
|
424
499
|
asyncio.get_running_loop().create_future()
|
|
425
500
|
)
|
|
426
501
|
|
|
427
502
|
try:
|
|
428
|
-
self.send_gatt_pdu(
|
|
503
|
+
self.send_gatt_pdu(bearer, bytes(indication))
|
|
429
504
|
await asyncio.wait_for(pending_confirmation, GATT_REQUEST_TIMEOUT)
|
|
430
505
|
except asyncio.TimeoutError as error:
|
|
431
506
|
logger.warning(color('!!! GATT Indicate timeout', 'red'))
|
|
432
507
|
raise TimeoutError(f'GATT timeout for {indication.name}') from error
|
|
433
508
|
finally:
|
|
434
|
-
self.pending_confirmations[
|
|
509
|
+
self.pending_confirmations[bearer] = None
|
|
435
510
|
|
|
436
511
|
async def _notify_or_indicate_subscribers(
|
|
437
512
|
self,
|
|
438
513
|
indicate: bool,
|
|
439
514
|
attribute: att.Attribute,
|
|
440
|
-
value:
|
|
515
|
+
value: bytes | None = None,
|
|
441
516
|
force: bool = False,
|
|
442
517
|
) -> None:
|
|
443
|
-
# Get all the
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
for
|
|
447
|
-
|
|
448
|
-
for (connection_handle, subscribers) in self.subscribers.items()
|
|
449
|
-
if force or subscribers.get(attribute.handle)
|
|
450
|
-
]
|
|
451
|
-
if connection is not None
|
|
518
|
+
# Get all the bearers for which there's at least one subscription
|
|
519
|
+
bearers: list[att.Bearer] = [
|
|
520
|
+
bearer
|
|
521
|
+
for bearer, subscribers in self.subscribers.items()
|
|
522
|
+
if force or subscribers.get(attribute.handle)
|
|
452
523
|
]
|
|
453
524
|
|
|
454
525
|
# Indicate or notify for each connection
|
|
455
|
-
if
|
|
456
|
-
coroutine =
|
|
526
|
+
if bearers:
|
|
527
|
+
coroutine = (
|
|
528
|
+
self._indicate_single_bearer
|
|
529
|
+
if indicate
|
|
530
|
+
else self._notify_single_subscriber
|
|
531
|
+
)
|
|
457
532
|
await asyncio.wait(
|
|
458
533
|
[
|
|
459
|
-
asyncio.create_task(coroutine(
|
|
460
|
-
for
|
|
534
|
+
asyncio.create_task(coroutine(bearer, attribute, value, force))
|
|
535
|
+
for bearer in bearers
|
|
461
536
|
]
|
|
462
537
|
)
|
|
463
538
|
|
|
464
539
|
async def notify_subscribers(
|
|
465
540
|
self,
|
|
466
541
|
attribute: att.Attribute,
|
|
467
|
-
value:
|
|
542
|
+
value: bytes | None = None,
|
|
468
543
|
force: bool = False,
|
|
469
544
|
):
|
|
470
545
|
return await self._notify_or_indicate_subscribers(
|
|
@@ -474,26 +549,23 @@ class Server(utils.EventEmitter):
|
|
|
474
549
|
async def indicate_subscribers(
|
|
475
550
|
self,
|
|
476
551
|
attribute: att.Attribute,
|
|
477
|
-
value:
|
|
552
|
+
value: bytes | None = None,
|
|
478
553
|
force: bool = False,
|
|
479
554
|
):
|
|
480
555
|
return await self._notify_or_indicate_subscribers(True, attribute, value, force)
|
|
481
556
|
|
|
482
|
-
def on_disconnection(self,
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
del self.indication_semaphores[connection.handle]
|
|
487
|
-
if connection.handle in self.pending_confirmations:
|
|
488
|
-
del self.pending_confirmations[connection.handle]
|
|
557
|
+
def on_disconnection(self, bearer: att.Bearer) -> None:
|
|
558
|
+
self.subscribers.pop(bearer, None)
|
|
559
|
+
self.indication_semaphores.pop(bearer, None)
|
|
560
|
+
self.pending_confirmations.pop(bearer, None)
|
|
489
561
|
|
|
490
|
-
def on_gatt_pdu(self,
|
|
491
|
-
logger.debug(f'GATT Request to server:
|
|
562
|
+
def on_gatt_pdu(self, bearer: att.Bearer, att_pdu: att.ATT_PDU) -> None:
|
|
563
|
+
logger.debug(f'GATT Request to server: {_bearer_id(bearer)} {att_pdu}')
|
|
492
564
|
handler_name = f'on_{att_pdu.name.lower()}'
|
|
493
565
|
handler = getattr(self, handler_name, None)
|
|
494
566
|
if handler is not None:
|
|
495
567
|
try:
|
|
496
|
-
handler(
|
|
568
|
+
handler(bearer, att_pdu)
|
|
497
569
|
except att.ATT_Error as error:
|
|
498
570
|
logger.debug(f'normal exception returned by handler: {error}')
|
|
499
571
|
response = att.ATT_Error_Response(
|
|
@@ -501,7 +573,7 @@ class Server(utils.EventEmitter):
|
|
|
501
573
|
attribute_handle_in_error=error.att_handle,
|
|
502
574
|
error_code=error.error_code,
|
|
503
575
|
)
|
|
504
|
-
self.send_response(
|
|
576
|
+
self.send_response(bearer, response)
|
|
505
577
|
except Exception:
|
|
506
578
|
logger.exception(color("!!! Exception in handler:", "red"))
|
|
507
579
|
response = att.ATT_Error_Response(
|
|
@@ -509,18 +581,18 @@ class Server(utils.EventEmitter):
|
|
|
509
581
|
attribute_handle_in_error=0x0000,
|
|
510
582
|
error_code=att.ATT_UNLIKELY_ERROR_ERROR,
|
|
511
583
|
)
|
|
512
|
-
self.send_response(
|
|
584
|
+
self.send_response(bearer, response)
|
|
513
585
|
raise
|
|
514
586
|
else:
|
|
515
587
|
# No specific handler registered
|
|
516
588
|
if att_pdu.op_code in att.ATT_REQUESTS:
|
|
517
589
|
# Invoke the generic handler
|
|
518
|
-
self.on_att_request(
|
|
590
|
+
self.on_att_request(bearer, att_pdu)
|
|
519
591
|
else:
|
|
520
592
|
# Just ignore
|
|
521
593
|
logger.warning(
|
|
522
594
|
color(
|
|
523
|
-
f'--- Ignoring GATT Request from
|
|
595
|
+
f'--- Ignoring GATT Request from {_bearer_id(bearer)}: ',
|
|
524
596
|
'red',
|
|
525
597
|
)
|
|
526
598
|
+ str(att_pdu)
|
|
@@ -529,13 +601,14 @@ class Server(utils.EventEmitter):
|
|
|
529
601
|
#######################################################
|
|
530
602
|
# ATT handlers
|
|
531
603
|
#######################################################
|
|
532
|
-
def on_att_request(self,
|
|
604
|
+
def on_att_request(self, bearer: att.Bearer, pdu: att.ATT_PDU) -> None:
|
|
533
605
|
'''
|
|
534
606
|
Handler for requests without a more specific handler
|
|
535
607
|
'''
|
|
536
608
|
logger.warning(
|
|
537
609
|
color(
|
|
538
|
-
f'--- Unsupported ATT Request from
|
|
610
|
+
f'--- Unsupported ATT Request from {_bearer_id(bearer)}: ',
|
|
611
|
+
'red',
|
|
539
612
|
)
|
|
540
613
|
+ str(pdu)
|
|
541
614
|
)
|
|
@@ -544,29 +617,28 @@ class Server(utils.EventEmitter):
|
|
|
544
617
|
attribute_handle_in_error=0x0000,
|
|
545
618
|
error_code=att.ATT_REQUEST_NOT_SUPPORTED_ERROR,
|
|
546
619
|
)
|
|
547
|
-
self.send_response(
|
|
620
|
+
self.send_response(bearer, response)
|
|
548
621
|
|
|
549
622
|
def on_att_exchange_mtu_request(
|
|
550
|
-
self,
|
|
623
|
+
self, bearer: att.Bearer, request: att.ATT_Exchange_MTU_Request
|
|
551
624
|
):
|
|
552
625
|
'''
|
|
553
626
|
See Bluetooth spec Vol 3, Part F - 3.4.2.1 Exchange MTU Request
|
|
554
627
|
'''
|
|
555
628
|
self.send_response(
|
|
556
|
-
|
|
629
|
+
bearer, att.ATT_Exchange_MTU_Response(server_rx_mtu=self.max_mtu)
|
|
557
630
|
)
|
|
558
631
|
|
|
559
632
|
# Compute the final MTU
|
|
560
633
|
if request.client_rx_mtu >= att.ATT_DEFAULT_MTU:
|
|
561
634
|
mtu = min(self.max_mtu, request.client_rx_mtu)
|
|
562
635
|
|
|
563
|
-
|
|
564
|
-
self.device.on_connection_att_mtu_update(connection.handle, mtu)
|
|
636
|
+
bearer.on_att_mtu_update(mtu)
|
|
565
637
|
else:
|
|
566
638
|
logger.warning('invalid client_rx_mtu received, MTU not changed')
|
|
567
639
|
|
|
568
640
|
def on_att_find_information_request(
|
|
569
|
-
self,
|
|
641
|
+
self, bearer: att.Bearer, request: att.ATT_Find_Information_Request
|
|
570
642
|
):
|
|
571
643
|
'''
|
|
572
644
|
See Bluetooth spec Vol 3, Part F - 3.4.3.1 Find Information Request
|
|
@@ -579,7 +651,7 @@ class Server(utils.EventEmitter):
|
|
|
579
651
|
or request.starting_handle > request.ending_handle
|
|
580
652
|
):
|
|
581
653
|
self.send_response(
|
|
582
|
-
|
|
654
|
+
bearer,
|
|
583
655
|
att.ATT_Error_Response(
|
|
584
656
|
request_opcode_in_error=request.op_code,
|
|
585
657
|
attribute_handle_in_error=request.starting_handle,
|
|
@@ -589,7 +661,7 @@ class Server(utils.EventEmitter):
|
|
|
589
661
|
return
|
|
590
662
|
|
|
591
663
|
# Build list of returned attributes
|
|
592
|
-
pdu_space_available =
|
|
664
|
+
pdu_space_available = bearer.att_mtu - 2
|
|
593
665
|
attributes: list[att.Attribute] = []
|
|
594
666
|
uuid_size = 0
|
|
595
667
|
for attribute in (
|
|
@@ -631,18 +703,18 @@ class Server(utils.EventEmitter):
|
|
|
631
703
|
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
|
632
704
|
)
|
|
633
705
|
|
|
634
|
-
self.send_response(
|
|
706
|
+
self.send_response(bearer, response)
|
|
635
707
|
|
|
636
708
|
@utils.AsyncRunner.run_in_task()
|
|
637
709
|
async def on_att_find_by_type_value_request(
|
|
638
|
-
self,
|
|
710
|
+
self, bearer: att.Bearer, request: att.ATT_Find_By_Type_Value_Request
|
|
639
711
|
):
|
|
640
712
|
'''
|
|
641
713
|
See Bluetooth spec Vol 3, Part F - 3.4.3.3 Find By Type Value Request
|
|
642
714
|
'''
|
|
643
715
|
|
|
644
716
|
# Build list of returned attributes
|
|
645
|
-
pdu_space_available =
|
|
717
|
+
pdu_space_available = bearer.att_mtu - 2
|
|
646
718
|
attributes = []
|
|
647
719
|
response: att.ATT_PDU
|
|
648
720
|
async for attribute in (
|
|
@@ -651,7 +723,7 @@ class Server(utils.EventEmitter):
|
|
|
651
723
|
if attribute.handle >= request.starting_handle
|
|
652
724
|
and attribute.handle <= request.ending_handle
|
|
653
725
|
and attribute.type == request.attribute_type
|
|
654
|
-
and (await attribute.read_value(
|
|
726
|
+
and (await attribute.read_value(bearer)) == request.attribute_value
|
|
655
727
|
and pdu_space_available >= 4
|
|
656
728
|
):
|
|
657
729
|
# TODO: check permissions
|
|
@@ -687,17 +759,17 @@ class Server(utils.EventEmitter):
|
|
|
687
759
|
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
|
688
760
|
)
|
|
689
761
|
|
|
690
|
-
self.send_response(
|
|
762
|
+
self.send_response(bearer, response)
|
|
691
763
|
|
|
692
764
|
@utils.AsyncRunner.run_in_task()
|
|
693
765
|
async def on_att_read_by_type_request(
|
|
694
|
-
self,
|
|
766
|
+
self, bearer: att.Bearer, request: att.ATT_Read_By_Type_Request
|
|
695
767
|
):
|
|
696
768
|
'''
|
|
697
769
|
See Bluetooth spec Vol 3, Part F - 3.4.4.1 Read By Type Request
|
|
698
770
|
'''
|
|
699
771
|
|
|
700
|
-
pdu_space_available =
|
|
772
|
+
pdu_space_available = bearer.att_mtu - 2
|
|
701
773
|
|
|
702
774
|
response: att.ATT_PDU = att.ATT_Error_Response(
|
|
703
775
|
request_opcode_in_error=request.op_code,
|
|
@@ -715,7 +787,7 @@ class Server(utils.EventEmitter):
|
|
|
715
787
|
and pdu_space_available
|
|
716
788
|
):
|
|
717
789
|
try:
|
|
718
|
-
attribute_value = await attribute.read_value(
|
|
790
|
+
attribute_value = await attribute.read_value(bearer)
|
|
719
791
|
except att.ATT_Error as error:
|
|
720
792
|
# If the first attribute is unreadable, return an error
|
|
721
793
|
# Otherwise return attributes up to this point
|
|
@@ -728,7 +800,7 @@ class Server(utils.EventEmitter):
|
|
|
728
800
|
break
|
|
729
801
|
|
|
730
802
|
# Check the attribute value size
|
|
731
|
-
max_attribute_size = min(
|
|
803
|
+
max_attribute_size = min(bearer.att_mtu - 4, 253)
|
|
732
804
|
if len(attribute_value) > max_attribute_size:
|
|
733
805
|
# We need to truncate
|
|
734
806
|
attribute_value = attribute_value[:max_attribute_size]
|
|
@@ -755,11 +827,11 @@ class Server(utils.EventEmitter):
|
|
|
755
827
|
else:
|
|
756
828
|
logging.debug(f"not found {request}")
|
|
757
829
|
|
|
758
|
-
self.send_response(
|
|
830
|
+
self.send_response(bearer, response)
|
|
759
831
|
|
|
760
832
|
@utils.AsyncRunner.run_in_task()
|
|
761
833
|
async def on_att_read_request(
|
|
762
|
-
self,
|
|
834
|
+
self, bearer: att.Bearer, request: att.ATT_Read_Request
|
|
763
835
|
):
|
|
764
836
|
'''
|
|
765
837
|
See Bluetooth spec Vol 3, Part F - 3.4.4.3 Read Request
|
|
@@ -768,7 +840,7 @@ class Server(utils.EventEmitter):
|
|
|
768
840
|
response: att.ATT_PDU
|
|
769
841
|
if attribute := self.get_attribute(request.attribute_handle):
|
|
770
842
|
try:
|
|
771
|
-
value = await attribute.read_value(
|
|
843
|
+
value = await attribute.read_value(bearer)
|
|
772
844
|
except att.ATT_Error as error:
|
|
773
845
|
response = att.ATT_Error_Response(
|
|
774
846
|
request_opcode_in_error=request.op_code,
|
|
@@ -776,7 +848,7 @@ class Server(utils.EventEmitter):
|
|
|
776
848
|
error_code=error.error_code,
|
|
777
849
|
)
|
|
778
850
|
else:
|
|
779
|
-
value_size = min(
|
|
851
|
+
value_size = min(bearer.att_mtu - 1, len(value))
|
|
780
852
|
response = att.ATT_Read_Response(attribute_value=value[:value_size])
|
|
781
853
|
else:
|
|
782
854
|
response = att.ATT_Error_Response(
|
|
@@ -784,11 +856,11 @@ class Server(utils.EventEmitter):
|
|
|
784
856
|
attribute_handle_in_error=request.attribute_handle,
|
|
785
857
|
error_code=att.ATT_INVALID_HANDLE_ERROR,
|
|
786
858
|
)
|
|
787
|
-
self.send_response(
|
|
859
|
+
self.send_response(bearer, response)
|
|
788
860
|
|
|
789
861
|
@utils.AsyncRunner.run_in_task()
|
|
790
862
|
async def on_att_read_blob_request(
|
|
791
|
-
self,
|
|
863
|
+
self, bearer: att.Bearer, request: att.ATT_Read_Blob_Request
|
|
792
864
|
):
|
|
793
865
|
'''
|
|
794
866
|
See Bluetooth spec Vol 3, Part F - 3.4.4.5 Read Blob Request
|
|
@@ -797,7 +869,7 @@ class Server(utils.EventEmitter):
|
|
|
797
869
|
response: att.ATT_PDU
|
|
798
870
|
if attribute := self.get_attribute(request.attribute_handle):
|
|
799
871
|
try:
|
|
800
|
-
value = await attribute.read_value(
|
|
872
|
+
value = await attribute.read_value(bearer)
|
|
801
873
|
except att.ATT_Error as error:
|
|
802
874
|
response = att.ATT_Error_Response(
|
|
803
875
|
request_opcode_in_error=request.op_code,
|
|
@@ -811,7 +883,7 @@ class Server(utils.EventEmitter):
|
|
|
811
883
|
attribute_handle_in_error=request.attribute_handle,
|
|
812
884
|
error_code=att.ATT_INVALID_OFFSET_ERROR,
|
|
813
885
|
)
|
|
814
|
-
elif len(value) <=
|
|
886
|
+
elif len(value) <= bearer.att_mtu - 1:
|
|
815
887
|
response = att.ATT_Error_Response(
|
|
816
888
|
request_opcode_in_error=request.op_code,
|
|
817
889
|
attribute_handle_in_error=request.attribute_handle,
|
|
@@ -819,7 +891,7 @@ class Server(utils.EventEmitter):
|
|
|
819
891
|
)
|
|
820
892
|
else:
|
|
821
893
|
part_size = min(
|
|
822
|
-
|
|
894
|
+
bearer.att_mtu - 1, len(value) - request.value_offset
|
|
823
895
|
)
|
|
824
896
|
response = att.ATT_Read_Blob_Response(
|
|
825
897
|
part_attribute_value=value[
|
|
@@ -832,11 +904,11 @@ class Server(utils.EventEmitter):
|
|
|
832
904
|
attribute_handle_in_error=request.attribute_handle,
|
|
833
905
|
error_code=att.ATT_INVALID_HANDLE_ERROR,
|
|
834
906
|
)
|
|
835
|
-
self.send_response(
|
|
907
|
+
self.send_response(bearer, response)
|
|
836
908
|
|
|
837
909
|
@utils.AsyncRunner.run_in_task()
|
|
838
910
|
async def on_att_read_by_group_type_request(
|
|
839
|
-
self,
|
|
911
|
+
self, bearer: att.Bearer, request: att.ATT_Read_By_Group_Type_Request
|
|
840
912
|
):
|
|
841
913
|
'''
|
|
842
914
|
See Bluetooth spec Vol 3, Part F - 3.4.4.9 Read by Group Type Request
|
|
@@ -851,10 +923,10 @@ class Server(utils.EventEmitter):
|
|
|
851
923
|
attribute_handle_in_error=request.starting_handle,
|
|
852
924
|
error_code=att.ATT_UNSUPPORTED_GROUP_TYPE_ERROR,
|
|
853
925
|
)
|
|
854
|
-
self.send_response(
|
|
926
|
+
self.send_response(bearer, response)
|
|
855
927
|
return
|
|
856
928
|
|
|
857
|
-
pdu_space_available =
|
|
929
|
+
pdu_space_available = bearer.att_mtu - 2
|
|
858
930
|
attributes: list[tuple[int, int, bytes]] = []
|
|
859
931
|
for attribute in (
|
|
860
932
|
attribute
|
|
@@ -866,9 +938,9 @@ class Server(utils.EventEmitter):
|
|
|
866
938
|
):
|
|
867
939
|
# No need to catch permission errors here, since these attributes
|
|
868
940
|
# must all be world-readable
|
|
869
|
-
attribute_value = await attribute.read_value(
|
|
941
|
+
attribute_value = await attribute.read_value(bearer)
|
|
870
942
|
# Check the attribute value size
|
|
871
|
-
max_attribute_size = min(
|
|
943
|
+
max_attribute_size = min(bearer.att_mtu - 6, 251)
|
|
872
944
|
if len(attribute_value) > max_attribute_size:
|
|
873
945
|
# We need to truncate
|
|
874
946
|
attribute_value = attribute_value[:max_attribute_size]
|
|
@@ -903,11 +975,11 @@ class Server(utils.EventEmitter):
|
|
|
903
975
|
error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
|
904
976
|
)
|
|
905
977
|
|
|
906
|
-
self.send_response(
|
|
978
|
+
self.send_response(bearer, response)
|
|
907
979
|
|
|
908
980
|
@utils.AsyncRunner.run_in_task()
|
|
909
981
|
async def on_att_write_request(
|
|
910
|
-
self,
|
|
982
|
+
self, bearer: att.Bearer, request: att.ATT_Write_Request
|
|
911
983
|
):
|
|
912
984
|
'''
|
|
913
985
|
See Bluetooth spec Vol 3, Part F - 3.4.5.1 Write Request
|
|
@@ -917,7 +989,7 @@ class Server(utils.EventEmitter):
|
|
|
917
989
|
attribute = self.get_attribute(request.attribute_handle)
|
|
918
990
|
if attribute is None:
|
|
919
991
|
self.send_response(
|
|
920
|
-
|
|
992
|
+
bearer,
|
|
921
993
|
att.ATT_Error_Response(
|
|
922
994
|
request_opcode_in_error=request.op_code,
|
|
923
995
|
attribute_handle_in_error=request.attribute_handle,
|
|
@@ -931,7 +1003,7 @@ class Server(utils.EventEmitter):
|
|
|
931
1003
|
# Check the request parameters
|
|
932
1004
|
if len(request.attribute_value) > GATT_MAX_ATTRIBUTE_VALUE_SIZE:
|
|
933
1005
|
self.send_response(
|
|
934
|
-
|
|
1006
|
+
bearer,
|
|
935
1007
|
att.ATT_Error_Response(
|
|
936
1008
|
request_opcode_in_error=request.op_code,
|
|
937
1009
|
attribute_handle_in_error=request.attribute_handle,
|
|
@@ -943,7 +1015,7 @@ class Server(utils.EventEmitter):
|
|
|
943
1015
|
response: att.ATT_PDU
|
|
944
1016
|
try:
|
|
945
1017
|
# Accept the value
|
|
946
|
-
await attribute.write_value(
|
|
1018
|
+
await attribute.write_value(bearer, request.attribute_value)
|
|
947
1019
|
except att.ATT_Error as error:
|
|
948
1020
|
response = att.ATT_Error_Response(
|
|
949
1021
|
request_opcode_in_error=request.op_code,
|
|
@@ -953,11 +1025,11 @@ class Server(utils.EventEmitter):
|
|
|
953
1025
|
else:
|
|
954
1026
|
# Done
|
|
955
1027
|
response = att.ATT_Write_Response()
|
|
956
|
-
self.send_response(
|
|
1028
|
+
self.send_response(bearer, response)
|
|
957
1029
|
|
|
958
1030
|
@utils.AsyncRunner.run_in_task()
|
|
959
1031
|
async def on_att_write_command(
|
|
960
|
-
self,
|
|
1032
|
+
self, bearer: att.Bearer, request: att.ATT_Write_Command
|
|
961
1033
|
):
|
|
962
1034
|
'''
|
|
963
1035
|
See Bluetooth spec Vol 3, Part F - 3.4.5.3 Write Command
|
|
@@ -976,22 +1048,20 @@ class Server(utils.EventEmitter):
|
|
|
976
1048
|
|
|
977
1049
|
# Accept the value
|
|
978
1050
|
try:
|
|
979
|
-
await attribute.write_value(
|
|
1051
|
+
await attribute.write_value(bearer, request.attribute_value)
|
|
980
1052
|
except Exception:
|
|
981
1053
|
logger.exception('!!! ignoring exception')
|
|
982
1054
|
|
|
983
1055
|
def on_att_handle_value_confirmation(
|
|
984
1056
|
self,
|
|
985
|
-
|
|
1057
|
+
bearer: att.Bearer,
|
|
986
1058
|
confirmation: att.ATT_Handle_Value_Confirmation,
|
|
987
1059
|
):
|
|
988
1060
|
'''
|
|
989
1061
|
See Bluetooth spec Vol 3, Part F - 3.4.7.3 Handle Value Confirmation
|
|
990
1062
|
'''
|
|
991
1063
|
del confirmation # Unused.
|
|
992
|
-
if (
|
|
993
|
-
pending_confirmation := self.pending_confirmations[connection.handle]
|
|
994
|
-
) is None:
|
|
1064
|
+
if (pending_confirmation := self.pending_confirmations[bearer]) is None:
|
|
995
1065
|
# Not expected!
|
|
996
1066
|
logger.warning(
|
|
997
1067
|
'!!! unexpected confirmation, there is no pending indication'
|