bumble 0.0.211__py3-none-any.whl → 0.0.212__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/apps/bench.py +4 -2
- bumble/apps/console.py +2 -2
- bumble/apps/pair.py +185 -32
- bumble/att.py +13 -12
- bumble/avctp.py +2 -2
- bumble/avdtp.py +122 -68
- bumble/avrcp.py +11 -5
- bumble/core.py +13 -7
- bumble/{crypto.py → crypto/__init__.py} +11 -95
- bumble/crypto/builtin.py +652 -0
- bumble/crypto/cryptography.py +84 -0
- bumble/device.py +362 -185
- bumble/drivers/intel.py +3 -0
- bumble/gatt.py +3 -5
- bumble/gatt_client.py +5 -3
- bumble/gatt_server.py +8 -6
- bumble/hci.py +67 -2
- bumble/hfp.py +44 -20
- bumble/hid.py +24 -12
- bumble/host.py +24 -0
- bumble/keys.py +64 -48
- bumble/l2cap.py +19 -9
- bumble/pandora/host.py +11 -11
- bumble/pandora/l2cap.py +2 -2
- bumble/pandora/security.py +72 -56
- bumble/profiles/aics.py +3 -5
- bumble/profiles/ancs.py +3 -1
- bumble/profiles/ascs.py +11 -5
- bumble/profiles/asha.py +11 -6
- bumble/profiles/csip.py +1 -3
- bumble/profiles/gatt_service.py +1 -3
- bumble/profiles/hap.py +16 -33
- bumble/profiles/mcp.py +12 -9
- bumble/profiles/vcs.py +5 -5
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +17 -8
- bumble/smp.py +14 -8
- {bumble-0.0.211.dist-info → bumble-0.0.212.dist-info}/METADATA +4 -4
- {bumble-0.0.211.dist-info → bumble-0.0.212.dist-info}/RECORD +44 -42
- {bumble-0.0.211.dist-info → bumble-0.0.212.dist-info}/WHEEL +1 -1
- {bumble-0.0.211.dist-info → bumble-0.0.212.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.211.dist-info → bumble-0.0.212.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.211.dist-info → bumble-0.0.212.dist-info}/top_level.txt +0 -0
bumble/profiles/ancs.py
CHANGED
|
@@ -250,6 +250,8 @@ class AncsClient(utils.EventEmitter):
|
|
|
250
250
|
_expected_response_tuples: int
|
|
251
251
|
_response_accumulator: bytes
|
|
252
252
|
|
|
253
|
+
EVENT_NOTIFICATION = "notification"
|
|
254
|
+
|
|
253
255
|
def __init__(self, ancs_proxy: AncsProxy) -> None:
|
|
254
256
|
super().__init__()
|
|
255
257
|
self._ancs_proxy = ancs_proxy
|
|
@@ -284,7 +286,7 @@ class AncsClient(utils.EventEmitter):
|
|
|
284
286
|
|
|
285
287
|
def _on_notification(self, notification: Notification) -> None:
|
|
286
288
|
logger.debug(f"ANCS NOTIFICATION: {notification}")
|
|
287
|
-
self.emit(
|
|
289
|
+
self.emit(self.EVENT_NOTIFICATION, notification)
|
|
288
290
|
|
|
289
291
|
def _on_data(self, data: bytes) -> None:
|
|
290
292
|
logger.debug(f"ANCS DATA: {data.hex()}")
|
bumble/profiles/ascs.py
CHANGED
|
@@ -276,6 +276,8 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
276
276
|
DISABLING = 0x05
|
|
277
277
|
RELEASING = 0x06
|
|
278
278
|
|
|
279
|
+
EVENT_STATE_CHANGE = "state_change"
|
|
280
|
+
|
|
279
281
|
cis_link: Optional[device.CisLink] = None
|
|
280
282
|
|
|
281
283
|
# Additional parameters in CODEC_CONFIGURED State
|
|
@@ -329,8 +331,12 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
329
331
|
value=gatt.CharacteristicValue(read=self.on_read),
|
|
330
332
|
)
|
|
331
333
|
|
|
332
|
-
self.service.device.on(
|
|
333
|
-
|
|
334
|
+
self.service.device.on(
|
|
335
|
+
self.service.device.EVENT_CIS_REQUEST, self.on_cis_request
|
|
336
|
+
)
|
|
337
|
+
self.service.device.on(
|
|
338
|
+
self.service.device.EVENT_CIS_ESTABLISHMENT, self.on_cis_establishment
|
|
339
|
+
)
|
|
334
340
|
|
|
335
341
|
def on_cis_request(
|
|
336
342
|
self,
|
|
@@ -356,7 +362,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
356
362
|
and cis_link.cis_id == self.cis_id
|
|
357
363
|
and self.state == self.State.ENABLING
|
|
358
364
|
):
|
|
359
|
-
cis_link.on(
|
|
365
|
+
cis_link.on(cis_link.EVENT_DISCONNECTION, self.on_cis_disconnection)
|
|
360
366
|
|
|
361
367
|
async def post_cis_established():
|
|
362
368
|
await cis_link.setup_data_path(direction=self.role)
|
|
@@ -525,7 +531,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
525
531
|
def state(self, new_state: State) -> None:
|
|
526
532
|
logger.debug(f'{self} state change -> {colors.color(new_state.name, "cyan")}')
|
|
527
533
|
self._state = new_state
|
|
528
|
-
self.emit(
|
|
534
|
+
self.emit(self.EVENT_STATE_CHANGE)
|
|
529
535
|
|
|
530
536
|
@property
|
|
531
537
|
def value(self):
|
|
@@ -584,7 +590,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
584
590
|
# Readonly. Do nothing in the setter.
|
|
585
591
|
pass
|
|
586
592
|
|
|
587
|
-
def on_read(self, _:
|
|
593
|
+
def on_read(self, _: device.Connection) -> bytes:
|
|
588
594
|
return self.value
|
|
589
595
|
|
|
590
596
|
def __str__(self) -> str:
|
bumble/profiles/asha.py
CHANGED
|
@@ -88,6 +88,11 @@ class AudioStatus(utils.OpenIntEnum):
|
|
|
88
88
|
class AshaService(gatt.TemplateService):
|
|
89
89
|
UUID = gatt.GATT_ASHA_SERVICE
|
|
90
90
|
|
|
91
|
+
EVENT_STARTED = "started"
|
|
92
|
+
EVENT_STOPPED = "stopped"
|
|
93
|
+
EVENT_DISCONNECTED = "disconnected"
|
|
94
|
+
EVENT_VOLUME_CHANGED = "volume_changed"
|
|
95
|
+
|
|
91
96
|
audio_sink: Optional[Callable[[bytes], Any]]
|
|
92
97
|
active_codec: Optional[Codec] = None
|
|
93
98
|
audio_type: Optional[AudioType] = None
|
|
@@ -195,7 +200,7 @@ class AshaService(gatt.TemplateService):
|
|
|
195
200
|
|
|
196
201
|
# Handler for audio control commands
|
|
197
202
|
async def _on_audio_control_point_write(
|
|
198
|
-
self, connection:
|
|
203
|
+
self, connection: Connection, value: bytes
|
|
199
204
|
) -> None:
|
|
200
205
|
_logger.debug(f'--- AUDIO CONTROL POINT Write:{value.hex()}')
|
|
201
206
|
opcode = value[0]
|
|
@@ -211,14 +216,14 @@ class AshaService(gatt.TemplateService):
|
|
|
211
216
|
f'volume={self.volume}, '
|
|
212
217
|
f'other_state={self.other_state}'
|
|
213
218
|
)
|
|
214
|
-
self.emit(
|
|
219
|
+
self.emit(self.EVENT_STARTED)
|
|
215
220
|
elif opcode == OpCode.STOP:
|
|
216
221
|
_logger.debug('### STOP')
|
|
217
222
|
self.active_codec = None
|
|
218
223
|
self.audio_type = None
|
|
219
224
|
self.volume = None
|
|
220
225
|
self.other_state = None
|
|
221
|
-
self.emit(
|
|
226
|
+
self.emit(self.EVENT_STOPPED)
|
|
222
227
|
elif opcode == OpCode.STATUS:
|
|
223
228
|
_logger.debug('### STATUS: %s', PeripheralStatus(value[1]).name)
|
|
224
229
|
|
|
@@ -231,7 +236,7 @@ class AshaService(gatt.TemplateService):
|
|
|
231
236
|
self.audio_type = None
|
|
232
237
|
self.volume = None
|
|
233
238
|
self.other_state = None
|
|
234
|
-
self.emit(
|
|
239
|
+
self.emit(self.EVENT_DISCONNECTED)
|
|
235
240
|
|
|
236
241
|
connection.once('disconnection', on_disconnection)
|
|
237
242
|
|
|
@@ -242,10 +247,10 @@ class AshaService(gatt.TemplateService):
|
|
|
242
247
|
)
|
|
243
248
|
|
|
244
249
|
# Handler for volume control
|
|
245
|
-
def _on_volume_write(self, connection:
|
|
250
|
+
def _on_volume_write(self, connection: Connection, value: bytes) -> None:
|
|
246
251
|
_logger.debug(f'--- VOLUME Write:{value[0]}')
|
|
247
252
|
self.volume = value[0]
|
|
248
|
-
self.emit(
|
|
253
|
+
self.emit(self.EVENT_VOLUME_CHANGED)
|
|
249
254
|
|
|
250
255
|
# Register an L2CAP CoC server
|
|
251
256
|
def _on_connection(self, channel: l2cap.LeCreditBasedChannel) -> None:
|
bumble/profiles/csip.py
CHANGED
|
@@ -164,12 +164,10 @@ class CoordinatedSetIdentificationService(gatt.TemplateService):
|
|
|
164
164
|
|
|
165
165
|
super().__init__(characteristics)
|
|
166
166
|
|
|
167
|
-
async def on_sirk_read(self, connection:
|
|
167
|
+
async def on_sirk_read(self, connection: device.Connection) -> bytes:
|
|
168
168
|
if self.set_identity_resolving_key_type == SirkType.PLAINTEXT:
|
|
169
169
|
sirk_bytes = self.set_identity_resolving_key
|
|
170
170
|
else:
|
|
171
|
-
assert connection
|
|
172
|
-
|
|
173
171
|
if connection.transport == core.PhysicalTransport.LE:
|
|
174
172
|
key = await connection.device.get_long_term_key(
|
|
175
173
|
connection_handle=connection.handle, rand=b'', ediv=0
|
bumble/profiles/gatt_service.py
CHANGED
|
@@ -127,9 +127,7 @@ class GenericAttributeProfileService(gatt.TemplateService):
|
|
|
127
127
|
|
|
128
128
|
return b''
|
|
129
129
|
|
|
130
|
-
def get_database_hash(self, connection: device.Connection
|
|
131
|
-
assert connection
|
|
132
|
-
|
|
130
|
+
def get_database_hash(self, connection: device.Connection) -> bytes:
|
|
133
131
|
m = b''.join(
|
|
134
132
|
[
|
|
135
133
|
self.get_attribute_data(attribute)
|
bumble/profiles/hap.py
CHANGED
|
@@ -266,13 +266,13 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
266
266
|
# associate the lowest index as the current active preset at startup
|
|
267
267
|
self.active_preset_index = sorted(self.preset_records.keys())[0]
|
|
268
268
|
|
|
269
|
-
@device.on(
|
|
269
|
+
@device.on(device.EVENT_CONNECTION)
|
|
270
270
|
def on_connection(connection: Connection) -> None:
|
|
271
|
-
@connection.on(
|
|
271
|
+
@connection.on(connection.EVENT_DISCONNECTION)
|
|
272
272
|
def on_disconnection(_reason) -> None:
|
|
273
273
|
self.currently_connected_clients.remove(connection)
|
|
274
274
|
|
|
275
|
-
@connection.on(
|
|
275
|
+
@connection.on(connection.EVENT_PAIRING)
|
|
276
276
|
def on_pairing(*_: Any) -> None:
|
|
277
277
|
self.on_incoming_paired_connection(connection)
|
|
278
278
|
|
|
@@ -335,9 +335,8 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
335
335
|
|
|
336
336
|
utils.cancel_on_event(connection, 'disconnection', on_connection_async())
|
|
337
337
|
|
|
338
|
-
def _on_read_active_preset_index(
|
|
339
|
-
|
|
340
|
-
) -> bytes:
|
|
338
|
+
def _on_read_active_preset_index(self, connection: Connection) -> bytes:
|
|
339
|
+
del connection # Unused
|
|
341
340
|
return bytes([self.active_preset_index])
|
|
342
341
|
|
|
343
342
|
# TODO this need to be triggered when device is unbonded
|
|
@@ -345,18 +344,13 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
345
344
|
self.preset_changed_operations_history_per_device.pop(addr)
|
|
346
345
|
|
|
347
346
|
async def _on_write_hearing_aid_preset_control_point(
|
|
348
|
-
self, connection:
|
|
347
|
+
self, connection: Connection, value: bytes
|
|
349
348
|
):
|
|
350
|
-
assert connection
|
|
351
|
-
|
|
352
349
|
opcode = HearingAidPresetControlPointOpcode(value[0])
|
|
353
350
|
handler = getattr(self, '_on_' + opcode.name.lower())
|
|
354
351
|
await handler(connection, value)
|
|
355
352
|
|
|
356
|
-
async def _on_read_presets_request(
|
|
357
|
-
self, connection: Optional[Connection], value: bytes
|
|
358
|
-
):
|
|
359
|
-
assert connection
|
|
353
|
+
async def _on_read_presets_request(self, connection: Connection, value: bytes):
|
|
360
354
|
if connection.att_mtu < 49: # 2.5. GATT sub-procedure requirements
|
|
361
355
|
logging.warning(f'HAS require MTU >= 49: {connection}')
|
|
362
356
|
|
|
@@ -471,10 +465,7 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
471
465
|
for connection in self.currently_connected_clients:
|
|
472
466
|
await self._preset_changed_operation(connection)
|
|
473
467
|
|
|
474
|
-
async def _on_write_preset_name(
|
|
475
|
-
self, connection: Optional[Connection], value: bytes
|
|
476
|
-
):
|
|
477
|
-
assert connection
|
|
468
|
+
async def _on_write_preset_name(self, connection: Connection, value: bytes):
|
|
478
469
|
|
|
479
470
|
if self.read_presets_request_in_progress:
|
|
480
471
|
raise att.ATT_Error(att.ErrorCode.PROCEDURE_ALREADY_IN_PROGRESS)
|
|
@@ -522,10 +513,7 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
522
513
|
for connection in self.currently_connected_clients:
|
|
523
514
|
await self.notify_active_preset_for_connection(connection)
|
|
524
515
|
|
|
525
|
-
async def set_active_preset(
|
|
526
|
-
self, connection: Optional[Connection], value: bytes
|
|
527
|
-
) -> None:
|
|
528
|
-
assert connection
|
|
516
|
+
async def set_active_preset(self, connection: Connection, value: bytes) -> None:
|
|
529
517
|
index = value[1]
|
|
530
518
|
preset = self.preset_records.get(index, None)
|
|
531
519
|
if (
|
|
@@ -542,16 +530,11 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
542
530
|
self.active_preset_index = index
|
|
543
531
|
await self.notify_active_preset()
|
|
544
532
|
|
|
545
|
-
async def _on_set_active_preset(
|
|
546
|
-
self, connection: Optional[Connection], value: bytes
|
|
547
|
-
):
|
|
533
|
+
async def _on_set_active_preset(self, connection: Connection, value: bytes):
|
|
548
534
|
await self.set_active_preset(connection, value)
|
|
549
535
|
|
|
550
|
-
async def set_next_or_previous_preset(
|
|
551
|
-
self, connection: Optional[Connection], is_previous
|
|
552
|
-
):
|
|
536
|
+
async def set_next_or_previous_preset(self, connection: Connection, is_previous):
|
|
553
537
|
'''Set the next or the previous preset as active'''
|
|
554
|
-
assert connection
|
|
555
538
|
|
|
556
539
|
if self.active_preset_index == 0x00:
|
|
557
540
|
raise att.ATT_Error(ErrorCode.PRESET_OPERATION_NOT_POSSIBLE)
|
|
@@ -581,17 +564,17 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
581
564
|
await self.notify_active_preset()
|
|
582
565
|
|
|
583
566
|
async def _on_set_next_preset(
|
|
584
|
-
self, connection:
|
|
567
|
+
self, connection: Connection, __value__: bytes
|
|
585
568
|
) -> None:
|
|
586
569
|
await self.set_next_or_previous_preset(connection, False)
|
|
587
570
|
|
|
588
571
|
async def _on_set_previous_preset(
|
|
589
|
-
self, connection:
|
|
572
|
+
self, connection: Connection, __value__: bytes
|
|
590
573
|
) -> None:
|
|
591
574
|
await self.set_next_or_previous_preset(connection, True)
|
|
592
575
|
|
|
593
576
|
async def _on_set_active_preset_synchronized_locally(
|
|
594
|
-
self, connection:
|
|
577
|
+
self, connection: Connection, value: bytes
|
|
595
578
|
):
|
|
596
579
|
if (
|
|
597
580
|
self.server_features.preset_synchronization_support
|
|
@@ -602,7 +585,7 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
602
585
|
# TODO (low priority) inform other server of the change
|
|
603
586
|
|
|
604
587
|
async def _on_set_next_preset_synchronized_locally(
|
|
605
|
-
self, connection:
|
|
588
|
+
self, connection: Connection, __value__: bytes
|
|
606
589
|
):
|
|
607
590
|
if (
|
|
608
591
|
self.server_features.preset_synchronization_support
|
|
@@ -613,7 +596,7 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
613
596
|
# TODO (low priority) inform other server of the change
|
|
614
597
|
|
|
615
598
|
async def _on_set_previous_preset_synchronized_locally(
|
|
616
|
-
self, connection:
|
|
599
|
+
self, connection: Connection, __value__: bytes
|
|
617
600
|
):
|
|
618
601
|
if (
|
|
619
602
|
self.server_features.preset_synchronization_support
|
bumble/profiles/mcp.py
CHANGED
|
@@ -287,11 +287,8 @@ class MediaControlService(gatt.TemplateService):
|
|
|
287
287
|
)
|
|
288
288
|
|
|
289
289
|
async def on_media_control_point(
|
|
290
|
-
self, connection:
|
|
290
|
+
self, connection: device.Connection, data: bytes
|
|
291
291
|
) -> None:
|
|
292
|
-
if not connection:
|
|
293
|
-
raise core.InvalidStateError()
|
|
294
|
-
|
|
295
292
|
opcode = MediaControlPointOpcode(data[0])
|
|
296
293
|
|
|
297
294
|
await connection.device.notify_subscriber(
|
|
@@ -338,6 +335,12 @@ class MediaControlServiceProxy(
|
|
|
338
335
|
'content_control_id': gatt.GATT_CONTENT_CONTROL_ID_CHARACTERISTIC,
|
|
339
336
|
}
|
|
340
337
|
|
|
338
|
+
EVENT_MEDIA_STATE = "media_state"
|
|
339
|
+
EVENT_TRACK_CHANGED = "track_changed"
|
|
340
|
+
EVENT_TRACK_TITLE = "track_title"
|
|
341
|
+
EVENT_TRACK_DURATION = "track_duration"
|
|
342
|
+
EVENT_TRACK_POSITION = "track_position"
|
|
343
|
+
|
|
341
344
|
media_player_name: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
342
345
|
media_player_icon_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
343
346
|
media_player_icon_url: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
@@ -432,20 +435,20 @@ class MediaControlServiceProxy(
|
|
|
432
435
|
self.media_control_point_notifications.put_nowait(data)
|
|
433
436
|
|
|
434
437
|
def _on_media_state(self, data: bytes) -> None:
|
|
435
|
-
self.emit(
|
|
438
|
+
self.emit(self.EVENT_MEDIA_STATE, MediaState(data[0]))
|
|
436
439
|
|
|
437
440
|
def _on_track_changed(self, data: bytes) -> None:
|
|
438
441
|
del data
|
|
439
|
-
self.emit(
|
|
442
|
+
self.emit(self.EVENT_TRACK_CHANGED)
|
|
440
443
|
|
|
441
444
|
def _on_track_title(self, data: bytes) -> None:
|
|
442
|
-
self.emit(
|
|
445
|
+
self.emit(self.EVENT_TRACK_TITLE, data.decode("utf-8"))
|
|
443
446
|
|
|
444
447
|
def _on_track_duration(self, data: bytes) -> None:
|
|
445
|
-
self.emit(
|
|
448
|
+
self.emit(self.EVENT_TRACK_DURATION, struct.unpack_from('<i', data)[0])
|
|
446
449
|
|
|
447
450
|
def _on_track_position(self, data: bytes) -> None:
|
|
448
|
-
self.emit(
|
|
451
|
+
self.emit(self.EVENT_TRACK_POSITION, struct.unpack_from('<i', data)[0])
|
|
449
452
|
|
|
450
453
|
|
|
451
454
|
class GenericMediaControlServiceProxy(MediaControlServiceProxy):
|
bumble/profiles/vcs.py
CHANGED
|
@@ -91,6 +91,8 @@ class VolumeState:
|
|
|
91
91
|
class VolumeControlService(gatt.TemplateService):
|
|
92
92
|
UUID = gatt.GATT_VOLUME_CONTROL_SERVICE
|
|
93
93
|
|
|
94
|
+
EVENT_VOLUME_STATE_CHANGE = "volume_state_change"
|
|
95
|
+
|
|
94
96
|
volume_state: gatt.Characteristic[bytes]
|
|
95
97
|
volume_control_point: gatt.Characteristic[bytes]
|
|
96
98
|
volume_flags: gatt.Characteristic[bytes]
|
|
@@ -144,14 +146,12 @@ class VolumeControlService(gatt.TemplateService):
|
|
|
144
146
|
included_services=list(included_services),
|
|
145
147
|
)
|
|
146
148
|
|
|
147
|
-
def _on_read_volume_state(self, _connection:
|
|
149
|
+
def _on_read_volume_state(self, _connection: device.Connection) -> bytes:
|
|
148
150
|
return bytes(VolumeState(self.volume_setting, self.muted, self.change_counter))
|
|
149
151
|
|
|
150
152
|
def _on_write_volume_control_point(
|
|
151
|
-
self, connection:
|
|
153
|
+
self, connection: device.Connection, value: bytes
|
|
152
154
|
) -> None:
|
|
153
|
-
assert connection
|
|
154
|
-
|
|
155
155
|
opcode = VolumeControlPointOpcode(value[0])
|
|
156
156
|
change_counter = value[1]
|
|
157
157
|
|
|
@@ -166,7 +166,7 @@ class VolumeControlService(gatt.TemplateService):
|
|
|
166
166
|
'disconnection',
|
|
167
167
|
connection.device.notify_subscribers(attribute=self.volume_state),
|
|
168
168
|
)
|
|
169
|
-
self.emit(
|
|
169
|
+
self.emit(self.EVENT_VOLUME_STATE_CHANGE)
|
|
170
170
|
|
|
171
171
|
def _on_relative_volume_down(self) -> bool:
|
|
172
172
|
old_volume = self.volume_setting
|
bumble/profiles/vocs.py
CHANGED
|
@@ -86,7 +86,7 @@ class VolumeOffsetState:
|
|
|
86
86
|
assert self.attribute is not None
|
|
87
87
|
await connection.device.notify_subscribers(attribute=self.attribute)
|
|
88
88
|
|
|
89
|
-
def on_read(self, _connection:
|
|
89
|
+
def on_read(self, _connection: Connection) -> bytes:
|
|
90
90
|
return bytes(self)
|
|
91
91
|
|
|
92
92
|
|
|
@@ -103,11 +103,10 @@ class VocsAudioLocation:
|
|
|
103
103
|
audio_location = AudioLocation(struct.unpack('<I', data)[0])
|
|
104
104
|
return cls(audio_location)
|
|
105
105
|
|
|
106
|
-
def on_read(self, _connection:
|
|
106
|
+
def on_read(self, _connection: Connection) -> bytes:
|
|
107
107
|
return bytes(self)
|
|
108
108
|
|
|
109
|
-
async def on_write(self, connection:
|
|
110
|
-
assert connection
|
|
109
|
+
async def on_write(self, connection: Connection, value: bytes) -> None:
|
|
111
110
|
assert self.attribute
|
|
112
111
|
|
|
113
112
|
self.audio_location = AudioLocation(int.from_bytes(value, 'little'))
|
|
@@ -118,8 +117,7 @@ class VocsAudioLocation:
|
|
|
118
117
|
class VolumeOffsetControlPoint:
|
|
119
118
|
volume_offset_state: VolumeOffsetState
|
|
120
119
|
|
|
121
|
-
async def on_write(self, connection:
|
|
122
|
-
assert connection
|
|
120
|
+
async def on_write(self, connection: Connection, value: bytes) -> None:
|
|
123
121
|
|
|
124
122
|
opcode = value[0]
|
|
125
123
|
if opcode != SetVolumeOffsetOpCode.SET_VOLUME_OFFSET:
|
|
@@ -159,11 +157,10 @@ class AudioOutputDescription:
|
|
|
159
157
|
def __bytes__(self) -> bytes:
|
|
160
158
|
return self.audio_output_description.encode('utf-8')
|
|
161
159
|
|
|
162
|
-
def on_read(self, _connection:
|
|
160
|
+
def on_read(self, _connection: Connection) -> bytes:
|
|
163
161
|
return bytes(self)
|
|
164
162
|
|
|
165
|
-
async def on_write(self, connection:
|
|
166
|
-
assert connection
|
|
163
|
+
async def on_write(self, connection: Connection, value: bytes) -> None:
|
|
167
164
|
assert self.attribute
|
|
168
165
|
|
|
169
166
|
self.audio_output_description = value.decode('utf-8')
|
bumble/rfcomm.py
CHANGED
|
@@ -442,6 +442,9 @@ class RFCOMM_MCC_MSC:
|
|
|
442
442
|
|
|
443
443
|
# -----------------------------------------------------------------------------
|
|
444
444
|
class DLC(utils.EventEmitter):
|
|
445
|
+
EVENT_OPEN = "open"
|
|
446
|
+
EVENT_CLOSE = "close"
|
|
447
|
+
|
|
445
448
|
class State(enum.IntEnum):
|
|
446
449
|
INIT = 0x00
|
|
447
450
|
CONNECTING = 0x01
|
|
@@ -529,7 +532,7 @@ class DLC(utils.EventEmitter):
|
|
|
529
532
|
self.send_frame(RFCOMM_Frame.uih(c_r=self.c_r, dlci=0, information=mcc))
|
|
530
533
|
|
|
531
534
|
self.change_state(DLC.State.CONNECTED)
|
|
532
|
-
self.emit(
|
|
535
|
+
self.emit(self.EVENT_OPEN)
|
|
533
536
|
|
|
534
537
|
def on_ua_frame(self, _frame: RFCOMM_Frame) -> None:
|
|
535
538
|
if self.state == DLC.State.CONNECTING:
|
|
@@ -550,7 +553,7 @@ class DLC(utils.EventEmitter):
|
|
|
550
553
|
self.disconnection_result.set_result(None)
|
|
551
554
|
self.disconnection_result = None
|
|
552
555
|
self.multiplexer.on_dlc_disconnection(self)
|
|
553
|
-
self.emit(
|
|
556
|
+
self.emit(self.EVENT_CLOSE)
|
|
554
557
|
else:
|
|
555
558
|
logger.warning(
|
|
556
559
|
color(
|
|
@@ -733,7 +736,7 @@ class DLC(utils.EventEmitter):
|
|
|
733
736
|
self.disconnection_result.cancel()
|
|
734
737
|
self.disconnection_result = None
|
|
735
738
|
self.change_state(DLC.State.RESET)
|
|
736
|
-
self.emit(
|
|
739
|
+
self.emit(self.EVENT_CLOSE)
|
|
737
740
|
|
|
738
741
|
def __str__(self) -> str:
|
|
739
742
|
return (
|
|
@@ -763,6 +766,8 @@ class Multiplexer(utils.EventEmitter):
|
|
|
763
766
|
DISCONNECTED = 0x05
|
|
764
767
|
RESET = 0x06
|
|
765
768
|
|
|
769
|
+
EVENT_DLC = "dlc"
|
|
770
|
+
|
|
766
771
|
connection_result: Optional[asyncio.Future]
|
|
767
772
|
disconnection_result: Optional[asyncio.Future]
|
|
768
773
|
open_result: Optional[asyncio.Future]
|
|
@@ -785,7 +790,7 @@ class Multiplexer(utils.EventEmitter):
|
|
|
785
790
|
# Become a sink for the L2CAP channel
|
|
786
791
|
l2cap_channel.sink = self.on_pdu
|
|
787
792
|
|
|
788
|
-
l2cap_channel.on(
|
|
793
|
+
l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
|
|
789
794
|
|
|
790
795
|
def change_state(self, new_state: State) -> None:
|
|
791
796
|
logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
|
|
@@ -901,7 +906,7 @@ class Multiplexer(utils.EventEmitter):
|
|
|
901
906
|
self.dlcs[pn.dlci] = dlc
|
|
902
907
|
|
|
903
908
|
# Re-emit the handshake completion event
|
|
904
|
-
dlc.on(
|
|
909
|
+
dlc.on(dlc.EVENT_OPEN, lambda: self.emit(self.EVENT_DLC, dlc))
|
|
905
910
|
|
|
906
911
|
# Respond to complete the handshake
|
|
907
912
|
dlc.accept()
|
|
@@ -1076,6 +1081,8 @@ class Client:
|
|
|
1076
1081
|
|
|
1077
1082
|
# -----------------------------------------------------------------------------
|
|
1078
1083
|
class Server(utils.EventEmitter):
|
|
1084
|
+
EVENT_START = "start"
|
|
1085
|
+
|
|
1079
1086
|
def __init__(
|
|
1080
1087
|
self, device: Device, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
|
|
1081
1088
|
) -> None:
|
|
@@ -1122,7 +1129,9 @@ class Server(utils.EventEmitter):
|
|
|
1122
1129
|
|
|
1123
1130
|
def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
|
1124
1131
|
logger.debug(f'+++ new L2CAP connection: {l2cap_channel}')
|
|
1125
|
-
l2cap_channel.on(
|
|
1132
|
+
l2cap_channel.on(
|
|
1133
|
+
l2cap_channel.EVENT_OPEN, lambda: self.on_l2cap_channel_open(l2cap_channel)
|
|
1134
|
+
)
|
|
1126
1135
|
|
|
1127
1136
|
def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
|
1128
1137
|
logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
|
|
@@ -1130,10 +1139,10 @@ class Server(utils.EventEmitter):
|
|
|
1130
1139
|
# Create a new multiplexer for the channel
|
|
1131
1140
|
multiplexer = Multiplexer(l2cap_channel, Multiplexer.Role.RESPONDER)
|
|
1132
1141
|
multiplexer.acceptor = self.accept_dlc
|
|
1133
|
-
multiplexer.on(
|
|
1142
|
+
multiplexer.on(multiplexer.EVENT_DLC, self.on_dlc)
|
|
1134
1143
|
|
|
1135
1144
|
# Notify
|
|
1136
|
-
self.emit(
|
|
1145
|
+
self.emit(self.EVENT_START, multiplexer)
|
|
1137
1146
|
|
|
1138
1147
|
def accept_dlc(self, channel_number: int) -> Optional[Tuple[int, int]]:
|
|
1139
1148
|
return self.dlc_configs.get(channel_number)
|
bumble/smp.py
CHANGED
|
@@ -724,12 +724,13 @@ class Session:
|
|
|
724
724
|
self.is_responder = not self.is_initiator
|
|
725
725
|
|
|
726
726
|
# Listen for connection events
|
|
727
|
-
connection.on(
|
|
727
|
+
connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
|
|
728
728
|
connection.on(
|
|
729
|
-
|
|
729
|
+
connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
|
|
730
|
+
self.on_connection_encryption_change,
|
|
730
731
|
)
|
|
731
732
|
connection.on(
|
|
732
|
-
|
|
733
|
+
connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
|
|
733
734
|
self.on_connection_encryption_key_refresh,
|
|
734
735
|
)
|
|
735
736
|
|
|
@@ -1310,12 +1311,15 @@ class Session:
|
|
|
1310
1311
|
)
|
|
1311
1312
|
|
|
1312
1313
|
def on_disconnection(self, _: int) -> None:
|
|
1313
|
-
self.connection.remove_listener('disconnection', self.on_disconnection)
|
|
1314
1314
|
self.connection.remove_listener(
|
|
1315
|
-
|
|
1315
|
+
self.connection.EVENT_DISCONNECTION, self.on_disconnection
|
|
1316
1316
|
)
|
|
1317
1317
|
self.connection.remove_listener(
|
|
1318
|
-
|
|
1318
|
+
self.connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
|
|
1319
|
+
self.on_connection_encryption_change,
|
|
1320
|
+
)
|
|
1321
|
+
self.connection.remove_listener(
|
|
1322
|
+
self.connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
|
|
1319
1323
|
self.on_connection_encryption_key_refresh,
|
|
1320
1324
|
)
|
|
1321
1325
|
self.manager.on_session_end(self)
|
|
@@ -1376,8 +1380,10 @@ class Session:
|
|
|
1376
1380
|
ediv=self.ltk_ediv,
|
|
1377
1381
|
rand=self.ltk_rand,
|
|
1378
1382
|
)
|
|
1383
|
+
if not self.peer_ltk:
|
|
1384
|
+
logger.error("peer_ltk is None")
|
|
1379
1385
|
peer_ltk_key = PairingKeys.Key(
|
|
1380
|
-
value=self.peer_ltk,
|
|
1386
|
+
value=self.peer_ltk or b'',
|
|
1381
1387
|
authenticated=authenticated,
|
|
1382
1388
|
ediv=self.peer_ediv,
|
|
1383
1389
|
rand=self.peer_rand,
|
|
@@ -1962,7 +1968,7 @@ class Manager(utils.EventEmitter):
|
|
|
1962
1968
|
def on_smp_security_request_command(
|
|
1963
1969
|
self, connection: Connection, request: SMP_Security_Request_Command
|
|
1964
1970
|
) -> None:
|
|
1965
|
-
connection.emit(
|
|
1971
|
+
connection.emit(connection.EVENT_SECURITY_REQUEST, request.auth_req)
|
|
1966
1972
|
|
|
1967
1973
|
def on_smp_pdu(self, connection: Connection, pdu: bytes) -> None:
|
|
1968
1974
|
# Parse the L2CAP payload into an SMP Command object
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bumble
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.212
|
|
4
4
|
Summary: Bluetooth Stack for Apps, Emulation, Test and Experimentation
|
|
5
5
|
Author-email: Google <bumble-dev@google.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/google/bumble
|
|
@@ -10,8 +10,8 @@ License-File: LICENSE
|
|
|
10
10
|
Requires-Dist: aiohttp~=3.8; platform_system != "Emscripten"
|
|
11
11
|
Requires-Dist: appdirs>=1.4; platform_system != "Emscripten"
|
|
12
12
|
Requires-Dist: click>=8.1.3; platform_system != "Emscripten"
|
|
13
|
-
Requires-Dist: cryptography>=
|
|
14
|
-
Requires-Dist: cryptography>=
|
|
13
|
+
Requires-Dist: cryptography>=44.0.3; platform_system != "Emscripten"
|
|
14
|
+
Requires-Dist: cryptography>=44.0.3; platform_system == "Emscripten"
|
|
15
15
|
Requires-Dist: grpcio>=1.62.1; platform_system != "Emscripten"
|
|
16
16
|
Requires-Dist: humanize>=4.6.0; platform_system != "Emscripten"
|
|
17
17
|
Requires-Dist: libusb1>=2.0.1; platform_system != "Emscripten"
|
|
@@ -20,7 +20,7 @@ Requires-Dist: platformdirs>=3.10.0; platform_system != "Emscripten"
|
|
|
20
20
|
Requires-Dist: prompt_toolkit>=3.0.16; platform_system != "Emscripten"
|
|
21
21
|
Requires-Dist: prettytable>=3.6.0; platform_system != "Emscripten"
|
|
22
22
|
Requires-Dist: protobuf>=3.12.4; platform_system != "Emscripten"
|
|
23
|
-
Requires-Dist: pyee>=
|
|
23
|
+
Requires-Dist: pyee>=13.0.0
|
|
24
24
|
Requires-Dist: pyserial-asyncio>=0.5; platform_system != "Emscripten"
|
|
25
25
|
Requires-Dist: pyserial>=3.5; platform_system != "Emscripten"
|
|
26
26
|
Requires-Dist: pyusb>=1.2; platform_system != "Emscripten"
|