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/drivers/intel.py
CHANGED
|
@@ -50,6 +50,7 @@ logger = logging.getLogger(__name__)
|
|
|
50
50
|
|
|
51
51
|
INTEL_USB_PRODUCTS = {
|
|
52
52
|
(0x8087, 0x0032), # AX210
|
|
53
|
+
(0x8087, 0x0033), # AX211
|
|
53
54
|
(0x8087, 0x0036), # BE200
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -293,6 +294,7 @@ class HardwareVariant(utils.OpenIntEnum):
|
|
|
293
294
|
# This is a just a partial list.
|
|
294
295
|
# Add other constants here as new hardware is encountered and tested.
|
|
295
296
|
TYPHOON_PEAK = 0x17
|
|
297
|
+
GARFIELD_PEAK = 0x19
|
|
296
298
|
GALE_PEAK = 0x1C
|
|
297
299
|
|
|
298
300
|
|
|
@@ -471,6 +473,7 @@ class Driver(common.Driver):
|
|
|
471
473
|
raise DriverError("hardware platform not supported")
|
|
472
474
|
if hardware_info.variant not in (
|
|
473
475
|
HardwareVariant.TYPHOON_PEAK,
|
|
476
|
+
HardwareVariant.GARFIELD_PEAK,
|
|
474
477
|
HardwareVariant.GALE_PEAK,
|
|
475
478
|
):
|
|
476
479
|
raise DriverError("hardware variant not supported")
|
bumble/gatt.py
CHANGED
|
@@ -448,6 +448,8 @@ class Characteristic(Attribute[_T]):
|
|
|
448
448
|
uuid: UUID
|
|
449
449
|
properties: Characteristic.Properties
|
|
450
450
|
|
|
451
|
+
EVENT_SUBSCRIPTION = "subscription"
|
|
452
|
+
|
|
451
453
|
class Properties(enum.IntFlag):
|
|
452
454
|
"""Property flags"""
|
|
453
455
|
|
|
@@ -577,11 +579,7 @@ class Descriptor(Attribute):
|
|
|
577
579
|
if isinstance(self.value, bytes):
|
|
578
580
|
value_str = self.value.hex()
|
|
579
581
|
elif isinstance(self.value, CharacteristicValue):
|
|
580
|
-
|
|
581
|
-
if isinstance(value, bytes):
|
|
582
|
-
value_str = value.hex()
|
|
583
|
-
else:
|
|
584
|
-
value_str = '<async>'
|
|
582
|
+
value_str = '<dynamic>'
|
|
585
583
|
else:
|
|
586
584
|
value_str = '<...>'
|
|
587
585
|
return (
|
bumble/gatt_client.py
CHANGED
|
@@ -202,6 +202,8 @@ class CharacteristicProxy(AttributeProxy[_T]):
|
|
|
202
202
|
descriptors: List[DescriptorProxy]
|
|
203
203
|
subscribers: Dict[Any, Callable[[_T], Any]]
|
|
204
204
|
|
|
205
|
+
EVENT_UPDATE = "update"
|
|
206
|
+
|
|
205
207
|
def __init__(
|
|
206
208
|
self,
|
|
207
209
|
client: Client,
|
|
@@ -308,7 +310,7 @@ class Client:
|
|
|
308
310
|
self.services = []
|
|
309
311
|
self.cached_values = {}
|
|
310
312
|
|
|
311
|
-
connection.on(
|
|
313
|
+
connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
|
|
312
314
|
|
|
313
315
|
def send_gatt_pdu(self, pdu: bytes) -> None:
|
|
314
316
|
self.connection.send_l2cap_pdu(ATT_CID, pdu)
|
|
@@ -1142,7 +1144,7 @@ class Client:
|
|
|
1142
1144
|
if callable(subscriber):
|
|
1143
1145
|
subscriber(notification.attribute_value)
|
|
1144
1146
|
else:
|
|
1145
|
-
subscriber.emit(
|
|
1147
|
+
subscriber.emit(subscriber.EVENT_UPDATE, notification.attribute_value)
|
|
1146
1148
|
|
|
1147
1149
|
def on_att_handle_value_indication(self, indication):
|
|
1148
1150
|
# Call all subscribers
|
|
@@ -1157,7 +1159,7 @@ class Client:
|
|
|
1157
1159
|
if callable(subscriber):
|
|
1158
1160
|
subscriber(indication.attribute_value)
|
|
1159
1161
|
else:
|
|
1160
|
-
subscriber.emit(
|
|
1162
|
+
subscriber.emit(subscriber.EVENT_UPDATE, indication.attribute_value)
|
|
1161
1163
|
|
|
1162
1164
|
# Confirm that we received the indication
|
|
1163
1165
|
self.send_confirmation(ATT_Handle_Value_Confirmation())
|
bumble/gatt_server.py
CHANGED
|
@@ -110,6 +110,8 @@ class Server(utils.EventEmitter):
|
|
|
110
110
|
indication_semaphores: defaultdict[int, asyncio.Semaphore]
|
|
111
111
|
pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
|
|
112
112
|
|
|
113
|
+
EVENT_CHARACTERISTIC_SUBSCRIPTION = "characteristic_subscription"
|
|
114
|
+
|
|
113
115
|
def __init__(self, device: Device) -> None:
|
|
114
116
|
super().__init__()
|
|
115
117
|
self.device = device
|
|
@@ -313,11 +315,8 @@ class Server(utils.EventEmitter):
|
|
|
313
315
|
self.add_service(service)
|
|
314
316
|
|
|
315
317
|
def read_cccd(
|
|
316
|
-
self, connection:
|
|
318
|
+
self, connection: Connection, characteristic: Characteristic
|
|
317
319
|
) -> bytes:
|
|
318
|
-
if connection is None:
|
|
319
|
-
return bytes([0, 0])
|
|
320
|
-
|
|
321
320
|
subscribers = self.subscribers.get(connection.handle)
|
|
322
321
|
cccd = None
|
|
323
322
|
if subscribers:
|
|
@@ -347,10 +346,13 @@ class Server(utils.EventEmitter):
|
|
|
347
346
|
notify_enabled = value[0] & 0x01 != 0
|
|
348
347
|
indicate_enabled = value[0] & 0x02 != 0
|
|
349
348
|
characteristic.emit(
|
|
350
|
-
|
|
349
|
+
characteristic.EVENT_SUBSCRIPTION,
|
|
350
|
+
connection,
|
|
351
|
+
notify_enabled,
|
|
352
|
+
indicate_enabled,
|
|
351
353
|
)
|
|
352
354
|
self.emit(
|
|
353
|
-
|
|
355
|
+
self.EVENT_CHARACTERISTIC_SUBSCRIPTION,
|
|
354
356
|
connection,
|
|
355
357
|
characteristic,
|
|
356
358
|
notify_enabled,
|
bumble/hci.py
CHANGED
|
@@ -29,13 +29,12 @@ from typing_extensions import Self
|
|
|
29
29
|
from bumble import crypto
|
|
30
30
|
from bumble.colors import color
|
|
31
31
|
from bumble.core import (
|
|
32
|
-
PhysicalTransport,
|
|
33
32
|
AdvertisingData,
|
|
34
33
|
DeviceClass,
|
|
35
34
|
InvalidArgumentError,
|
|
36
35
|
InvalidPacketError,
|
|
37
|
-
ProtocolError,
|
|
38
36
|
PhysicalTransport,
|
|
37
|
+
ProtocolError,
|
|
39
38
|
bit_flags_to_strings,
|
|
40
39
|
name_or_number,
|
|
41
40
|
padded_bytes,
|
|
@@ -225,6 +224,7 @@ HCI_CONNECTIONLESS_PERIPHERAL_BROADCAST_CHANNEL_MAP_CHANGE_EVENT = 0X55
|
|
|
225
224
|
HCI_INQUIRY_RESPONSE_NOTIFICATION_EVENT = 0X56
|
|
226
225
|
HCI_AUTHENTICATED_PAYLOAD_TIMEOUT_EXPIRED_EVENT = 0X57
|
|
227
226
|
HCI_SAM_STATUS_CHANGE_EVENT = 0X58
|
|
227
|
+
HCI_ENCRYPTION_CHANGE_V2_EVENT = 0x59
|
|
228
228
|
|
|
229
229
|
HCI_VENDOR_EVENT = 0xFF
|
|
230
230
|
|
|
@@ -3364,6 +3364,20 @@ class HCI_Set_Event_Mask_Page_2_Command(HCI_Command):
|
|
|
3364
3364
|
See Bluetooth spec @ 7.3.69 Set Event Mask Page 2 Command
|
|
3365
3365
|
'''
|
|
3366
3366
|
|
|
3367
|
+
@staticmethod
|
|
3368
|
+
def mask(event_codes: Iterable[int]) -> bytes:
|
|
3369
|
+
'''
|
|
3370
|
+
Compute the event mask value for a list of events.
|
|
3371
|
+
'''
|
|
3372
|
+
# NOTE: this implementation takes advantage of the fact that as of version 6.0
|
|
3373
|
+
# of the core specification, the bit number for each event code is equal to 64
|
|
3374
|
+
# less than the event code.
|
|
3375
|
+
# If future versions of the specification deviate from that, a different
|
|
3376
|
+
# implementation would be needed.
|
|
3377
|
+
return sum((1 << event_code - 64) for event_code in event_codes).to_bytes(
|
|
3378
|
+
8, 'little'
|
|
3379
|
+
)
|
|
3380
|
+
|
|
3367
3381
|
|
|
3368
3382
|
# -----------------------------------------------------------------------------
|
|
3369
3383
|
@HCI_Command.command(
|
|
@@ -5971,6 +5985,33 @@ class HCI_LE_Enhanced_Connection_Complete_Event(HCI_LE_Meta_Event):
|
|
|
5971
5985
|
'''
|
|
5972
5986
|
|
|
5973
5987
|
|
|
5988
|
+
# -----------------------------------------------------------------------------
|
|
5989
|
+
@HCI_LE_Meta_Event.event(
|
|
5990
|
+
[
|
|
5991
|
+
('status', STATUS_SPEC),
|
|
5992
|
+
('connection_handle', 2),
|
|
5993
|
+
(
|
|
5994
|
+
'role',
|
|
5995
|
+
{'size': 1, 'mapper': lambda x: 'CENTRAL' if x == 0 else 'PERIPHERAL'},
|
|
5996
|
+
),
|
|
5997
|
+
('peer_address_type', Address.ADDRESS_TYPE_SPEC),
|
|
5998
|
+
('peer_address', Address.parse_address_preceded_by_type),
|
|
5999
|
+
('local_resolvable_private_address', Address.parse_random_address),
|
|
6000
|
+
('peer_resolvable_private_address', Address.parse_random_address),
|
|
6001
|
+
('connection_interval', 2),
|
|
6002
|
+
('peripheral_latency', 2),
|
|
6003
|
+
('supervision_timeout', 2),
|
|
6004
|
+
('central_clock_accuracy', 1),
|
|
6005
|
+
('advertising_handle', 1),
|
|
6006
|
+
('sync_handle', 2),
|
|
6007
|
+
]
|
|
6008
|
+
)
|
|
6009
|
+
class HCI_LE_Enhanced_Connection_Complete_V2_Event(HCI_LE_Meta_Event):
|
|
6010
|
+
'''
|
|
6011
|
+
See Bluetooth spec @ 7.7.65.10 LE Enhanced Connection Complete Event
|
|
6012
|
+
'''
|
|
6013
|
+
|
|
6014
|
+
|
|
5974
6015
|
# -----------------------------------------------------------------------------
|
|
5975
6016
|
@HCI_LE_Meta_Event.event(
|
|
5976
6017
|
[
|
|
@@ -6950,6 +6991,30 @@ class HCI_Encryption_Change_Event(HCI_Event):
|
|
|
6950
6991
|
)
|
|
6951
6992
|
|
|
6952
6993
|
|
|
6994
|
+
# -----------------------------------------------------------------------------
|
|
6995
|
+
@HCI_Event.event(
|
|
6996
|
+
[
|
|
6997
|
+
('status', STATUS_SPEC),
|
|
6998
|
+
('connection_handle', 2),
|
|
6999
|
+
(
|
|
7000
|
+
'encryption_enabled',
|
|
7001
|
+
{
|
|
7002
|
+
'size': 1,
|
|
7003
|
+
# pylint: disable-next=unnecessary-lambda
|
|
7004
|
+
'mapper': lambda x: HCI_Encryption_Change_Event.encryption_enabled_name(
|
|
7005
|
+
x
|
|
7006
|
+
),
|
|
7007
|
+
},
|
|
7008
|
+
),
|
|
7009
|
+
('encryption_key_size', 1),
|
|
7010
|
+
]
|
|
7011
|
+
)
|
|
7012
|
+
class HCI_Encryption_Change_V2_Event(HCI_Event):
|
|
7013
|
+
'''
|
|
7014
|
+
See Bluetooth spec @ 7.7.8 Encryption Change Event
|
|
7015
|
+
'''
|
|
7016
|
+
|
|
7017
|
+
|
|
6953
7018
|
# -----------------------------------------------------------------------------
|
|
6954
7019
|
@HCI_Event.event(
|
|
6955
7020
|
[('status', STATUS_SPEC), ('connection_handle', 2), ('lmp_features', 8)]
|
bumble/hfp.py
CHANGED
|
@@ -720,6 +720,14 @@ class HfProtocol(utils.EventEmitter):
|
|
|
720
720
|
vrec: VoiceRecognitionState
|
|
721
721
|
"""
|
|
722
722
|
|
|
723
|
+
EVENT_CODEC_NEGOTIATION = "codec_negotiation"
|
|
724
|
+
EVENT_AG_INDICATOR = "ag_indicator"
|
|
725
|
+
EVENT_SPEAKER_VOLUME = "speaker_volume"
|
|
726
|
+
EVENT_MICROPHONE_VOLUME = "microphone_volume"
|
|
727
|
+
EVENT_RING = "ring"
|
|
728
|
+
EVENT_CLI_NOTIFICATION = "cli_notification"
|
|
729
|
+
EVENT_VOICE_RECOGNITION = "voice_recognition"
|
|
730
|
+
|
|
723
731
|
class HfLoopTermination(HfpProtocolError):
|
|
724
732
|
"""Termination signal for run() loop."""
|
|
725
733
|
|
|
@@ -777,7 +785,8 @@ class HfProtocol(utils.EventEmitter):
|
|
|
777
785
|
self.dlc.sink = self._read_at
|
|
778
786
|
# Stop the run() loop when L2CAP is closed.
|
|
779
787
|
self.dlc.multiplexer.l2cap_channel.on(
|
|
780
|
-
|
|
788
|
+
self.dlc.multiplexer.l2cap_channel.EVENT_CLOSE,
|
|
789
|
+
lambda: self.unsolicited_queue.put_nowait(None),
|
|
781
790
|
)
|
|
782
791
|
|
|
783
792
|
def supports_hf_feature(self, feature: HfFeature) -> bool:
|
|
@@ -1034,7 +1043,7 @@ class HfProtocol(utils.EventEmitter):
|
|
|
1034
1043
|
# ID. The HF shall be ready to accept the synchronous connection
|
|
1035
1044
|
# establishment as soon as it has sent the AT commands AT+BCS=<Codec ID>.
|
|
1036
1045
|
self.active_codec = AudioCodec(codec_id)
|
|
1037
|
-
self.emit(
|
|
1046
|
+
self.emit(self.EVENT_CODEC_NEGOTIATION, self.active_codec)
|
|
1038
1047
|
|
|
1039
1048
|
logger.info("codec connection setup completed")
|
|
1040
1049
|
|
|
@@ -1095,7 +1104,7 @@ class HfProtocol(utils.EventEmitter):
|
|
|
1095
1104
|
# CIEV is in 1-index, while ag_indicators is in 0-index.
|
|
1096
1105
|
ag_indicator = self.ag_indicators[index - 1]
|
|
1097
1106
|
ag_indicator.current_status = value
|
|
1098
|
-
self.emit(
|
|
1107
|
+
self.emit(self.EVENT_AG_INDICATOR, ag_indicator)
|
|
1099
1108
|
logger.info(f"AG indicator updated: {ag_indicator.indicator}, {value}")
|
|
1100
1109
|
|
|
1101
1110
|
async def handle_unsolicited(self):
|
|
@@ -1110,19 +1119,21 @@ class HfProtocol(utils.EventEmitter):
|
|
|
1110
1119
|
int(result.parameters[0]), int(result.parameters[1])
|
|
1111
1120
|
)
|
|
1112
1121
|
elif result.code == "+VGS":
|
|
1113
|
-
self.emit(
|
|
1122
|
+
self.emit(self.EVENT_SPEAKER_VOLUME, int(result.parameters[0]))
|
|
1114
1123
|
elif result.code == "+VGM":
|
|
1115
|
-
self.emit(
|
|
1124
|
+
self.emit(self.EVENT_MICROPHONE_VOLUME, int(result.parameters[0]))
|
|
1116
1125
|
elif result.code == "RING":
|
|
1117
|
-
self.emit(
|
|
1126
|
+
self.emit(self.EVENT_RING)
|
|
1118
1127
|
elif result.code == "+CLIP":
|
|
1119
1128
|
self.emit(
|
|
1120
|
-
|
|
1129
|
+
self.EVENT_CLI_NOTIFICATION,
|
|
1130
|
+
CallLineIdentification.parse_from(result.parameters),
|
|
1121
1131
|
)
|
|
1122
1132
|
elif result.code == "+BVRA":
|
|
1123
1133
|
# TODO: Support Enhanced Voice Recognition.
|
|
1124
1134
|
self.emit(
|
|
1125
|
-
|
|
1135
|
+
self.EVENT_VOICE_RECOGNITION,
|
|
1136
|
+
VoiceRecognitionState(int(result.parameters[0])),
|
|
1126
1137
|
)
|
|
1127
1138
|
else:
|
|
1128
1139
|
logging.info(f"unhandled unsolicited response {result.code}")
|
|
@@ -1179,6 +1190,19 @@ class AgProtocol(utils.EventEmitter):
|
|
|
1179
1190
|
volume: Int
|
|
1180
1191
|
"""
|
|
1181
1192
|
|
|
1193
|
+
EVENT_SLC_COMPLETE = "slc_complete"
|
|
1194
|
+
EVENT_SUPPORTED_AUDIO_CODECS = "supported_audio_codecs"
|
|
1195
|
+
EVENT_CODEC_NEGOTIATION = "codec_negotiation"
|
|
1196
|
+
EVENT_VOICE_RECOGNITION = "voice_recognition"
|
|
1197
|
+
EVENT_CALL_HOLD = "call_hold"
|
|
1198
|
+
EVENT_HF_INDICATOR = "hf_indicator"
|
|
1199
|
+
EVENT_CODEC_CONNECTION_REQUEST = "codec_connection_request"
|
|
1200
|
+
EVENT_ANSWER = "answer"
|
|
1201
|
+
EVENT_DIAL = "dial"
|
|
1202
|
+
EVENT_HANG_UP = "hang_up"
|
|
1203
|
+
EVENT_SPEAKER_VOLUME = "speaker_volume"
|
|
1204
|
+
EVENT_MICROPHONE_VOLUME = "microphone_volume"
|
|
1205
|
+
|
|
1182
1206
|
supported_hf_features: int
|
|
1183
1207
|
supported_hf_indicators: Set[HfIndicator]
|
|
1184
1208
|
supported_audio_codecs: List[AudioCodec]
|
|
@@ -1371,7 +1395,7 @@ class AgProtocol(utils.EventEmitter):
|
|
|
1371
1395
|
|
|
1372
1396
|
def _check_remained_slc_commands(self) -> None:
|
|
1373
1397
|
if not self._remained_slc_setup_features:
|
|
1374
|
-
self.emit(
|
|
1398
|
+
self.emit(self.EVENT_SLC_COMPLETE)
|
|
1375
1399
|
|
|
1376
1400
|
def _on_brsf(self, hf_features: bytes) -> None:
|
|
1377
1401
|
self.supported_hf_features = int(hf_features)
|
|
@@ -1390,17 +1414,17 @@ class AgProtocol(utils.EventEmitter):
|
|
|
1390
1414
|
|
|
1391
1415
|
def _on_bac(self, *args) -> None:
|
|
1392
1416
|
self.supported_audio_codecs = [AudioCodec(int(value)) for value in args]
|
|
1393
|
-
self.emit(
|
|
1417
|
+
self.emit(self.EVENT_SUPPORTED_AUDIO_CODECS, self.supported_audio_codecs)
|
|
1394
1418
|
self.send_ok()
|
|
1395
1419
|
|
|
1396
1420
|
def _on_bcs(self, codec: bytes) -> None:
|
|
1397
1421
|
self.active_codec = AudioCodec(int(codec))
|
|
1398
1422
|
self.send_ok()
|
|
1399
|
-
self.emit(
|
|
1423
|
+
self.emit(self.EVENT_CODEC_NEGOTIATION, self.active_codec)
|
|
1400
1424
|
|
|
1401
1425
|
def _on_bvra(self, vrec: bytes) -> None:
|
|
1402
1426
|
self.send_ok()
|
|
1403
|
-
self.emit(
|
|
1427
|
+
self.emit(self.EVENT_VOICE_RECOGNITION, VoiceRecognitionState(int(vrec)))
|
|
1404
1428
|
|
|
1405
1429
|
def _on_chld(self, operation_code: bytes) -> None:
|
|
1406
1430
|
call_index: Optional[int] = None
|
|
@@ -1427,7 +1451,7 @@ class AgProtocol(utils.EventEmitter):
|
|
|
1427
1451
|
# Real three-way calls have more complicated situations, but this is not a popular issue - let users to handle the remaining :)
|
|
1428
1452
|
|
|
1429
1453
|
self.send_ok()
|
|
1430
|
-
self.emit(
|
|
1454
|
+
self.emit(self.EVENT_CALL_HOLD, operation, call_index)
|
|
1431
1455
|
|
|
1432
1456
|
def _on_chld_test(self) -> None:
|
|
1433
1457
|
if not self.supports_ag_feature(AgFeature.THREE_WAY_CALLING):
|
|
@@ -1553,7 +1577,7 @@ class AgProtocol(utils.EventEmitter):
|
|
|
1553
1577
|
return
|
|
1554
1578
|
|
|
1555
1579
|
self.hf_indicators[index].current_status = int(value_bytes)
|
|
1556
|
-
self.emit(
|
|
1580
|
+
self.emit(self.EVENT_HF_INDICATOR, self.hf_indicators[index])
|
|
1557
1581
|
self.send_ok()
|
|
1558
1582
|
|
|
1559
1583
|
def _on_bia(self, *args) -> None:
|
|
@@ -1562,21 +1586,21 @@ class AgProtocol(utils.EventEmitter):
|
|
|
1562
1586
|
self.send_ok()
|
|
1563
1587
|
|
|
1564
1588
|
def _on_bcc(self) -> None:
|
|
1565
|
-
self.emit(
|
|
1589
|
+
self.emit(self.EVENT_CODEC_CONNECTION_REQUEST)
|
|
1566
1590
|
self.send_ok()
|
|
1567
1591
|
|
|
1568
1592
|
def _on_a(self) -> None:
|
|
1569
1593
|
"""ATA handler."""
|
|
1570
|
-
self.emit(
|
|
1594
|
+
self.emit(self.EVENT_ANSWER)
|
|
1571
1595
|
self.send_ok()
|
|
1572
1596
|
|
|
1573
1597
|
def _on_d(self, number: bytes) -> None:
|
|
1574
1598
|
"""ATD handler."""
|
|
1575
|
-
self.emit(
|
|
1599
|
+
self.emit(self.EVENT_DIAL, number.decode())
|
|
1576
1600
|
self.send_ok()
|
|
1577
1601
|
|
|
1578
1602
|
def _on_chup(self) -> None:
|
|
1579
|
-
self.emit(
|
|
1603
|
+
self.emit(self.EVENT_HANG_UP)
|
|
1580
1604
|
self.send_ok()
|
|
1581
1605
|
|
|
1582
1606
|
def _on_clcc(self) -> None:
|
|
@@ -1602,11 +1626,11 @@ class AgProtocol(utils.EventEmitter):
|
|
|
1602
1626
|
self.send_ok()
|
|
1603
1627
|
|
|
1604
1628
|
def _on_vgs(self, level: bytes) -> None:
|
|
1605
|
-
self.emit(
|
|
1629
|
+
self.emit(self.EVENT_SPEAKER_VOLUME, int(level))
|
|
1606
1630
|
self.send_ok()
|
|
1607
1631
|
|
|
1608
1632
|
def _on_vgm(self, level: bytes) -> None:
|
|
1609
|
-
self.emit(
|
|
1633
|
+
self.emit(self.EVENT_MICROPHONE_VOLUME, int(level))
|
|
1610
1634
|
self.send_ok()
|
|
1611
1635
|
|
|
1612
1636
|
|
bumble/hid.py
CHANGED
|
@@ -201,6 +201,13 @@ class HID(ABC, utils.EventEmitter):
|
|
|
201
201
|
l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None
|
|
202
202
|
connection: Optional[device.Connection] = None
|
|
203
203
|
|
|
204
|
+
EVENT_INTERRUPT_DATA = "interrupt_data"
|
|
205
|
+
EVENT_CONTROL_DATA = "control_data"
|
|
206
|
+
EVENT_SUSPEND = "suspend"
|
|
207
|
+
EVENT_EXIT_SUSPEND = "exit_suspend"
|
|
208
|
+
EVENT_VIRTUAL_CABLE_UNPLUG = "virtual_cable_unplug"
|
|
209
|
+
EVENT_HANDSHAKE = "handshake"
|
|
210
|
+
|
|
204
211
|
class Role(enum.IntEnum):
|
|
205
212
|
HOST = 0x00
|
|
206
213
|
DEVICE = 0x01
|
|
@@ -215,7 +222,7 @@ class HID(ABC, utils.EventEmitter):
|
|
|
215
222
|
device.register_l2cap_server(HID_CONTROL_PSM, self.on_l2cap_connection)
|
|
216
223
|
device.register_l2cap_server(HID_INTERRUPT_PSM, self.on_l2cap_connection)
|
|
217
224
|
|
|
218
|
-
device.on(
|
|
225
|
+
device.on(device.EVENT_CONNECTION, self.on_device_connection)
|
|
219
226
|
|
|
220
227
|
async def connect_control_channel(self) -> None:
|
|
221
228
|
# Create a new L2CAP connection - control channel
|
|
@@ -258,15 +265,20 @@ class HID(ABC, utils.EventEmitter):
|
|
|
258
265
|
def on_device_connection(self, connection: device.Connection) -> None:
|
|
259
266
|
self.connection = connection
|
|
260
267
|
self.remote_device_bd_address = connection.peer_address
|
|
261
|
-
connection.on(
|
|
268
|
+
connection.on(connection.EVENT_DISCONNECTION, self.on_device_disconnection)
|
|
262
269
|
|
|
263
270
|
def on_device_disconnection(self, reason: int) -> None:
|
|
264
271
|
self.connection = None
|
|
265
272
|
|
|
266
273
|
def on_l2cap_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
|
267
274
|
logger.debug(f'+++ New L2CAP connection: {l2cap_channel}')
|
|
268
|
-
l2cap_channel.on(
|
|
269
|
-
|
|
275
|
+
l2cap_channel.on(
|
|
276
|
+
l2cap_channel.EVENT_OPEN, lambda: self.on_l2cap_channel_open(l2cap_channel)
|
|
277
|
+
)
|
|
278
|
+
l2cap_channel.on(
|
|
279
|
+
l2cap_channel.EVENT_CLOSE,
|
|
280
|
+
lambda: self.on_l2cap_channel_close(l2cap_channel),
|
|
281
|
+
)
|
|
270
282
|
|
|
271
283
|
def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
|
272
284
|
if l2cap_channel.psm == HID_CONTROL_PSM:
|
|
@@ -290,7 +302,7 @@ class HID(ABC, utils.EventEmitter):
|
|
|
290
302
|
|
|
291
303
|
def on_intr_pdu(self, pdu: bytes) -> None:
|
|
292
304
|
logger.debug(f'<<< HID INTERRUPT PDU: {pdu.hex()}')
|
|
293
|
-
self.emit(
|
|
305
|
+
self.emit(self.EVENT_INTERRUPT_DATA, pdu)
|
|
294
306
|
|
|
295
307
|
def send_pdu_on_ctrl(self, msg: bytes) -> None:
|
|
296
308
|
assert self.l2cap_ctrl_channel
|
|
@@ -363,17 +375,17 @@ class Device(HID):
|
|
|
363
375
|
self.handle_set_protocol(pdu)
|
|
364
376
|
elif message_type == Message.MessageType.DATA:
|
|
365
377
|
logger.debug('<<< HID CONTROL DATA')
|
|
366
|
-
self.emit(
|
|
378
|
+
self.emit(self.EVENT_CONTROL_DATA, pdu)
|
|
367
379
|
elif message_type == Message.MessageType.CONTROL:
|
|
368
380
|
if param == Message.ControlCommand.SUSPEND:
|
|
369
381
|
logger.debug('<<< HID SUSPEND')
|
|
370
|
-
self.emit(
|
|
382
|
+
self.emit(self.EVENT_SUSPEND)
|
|
371
383
|
elif param == Message.ControlCommand.EXIT_SUSPEND:
|
|
372
384
|
logger.debug('<<< HID EXIT SUSPEND')
|
|
373
|
-
self.emit(
|
|
385
|
+
self.emit(self.EVENT_EXIT_SUSPEND)
|
|
374
386
|
elif param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
|
|
375
387
|
logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
|
|
376
|
-
self.emit(
|
|
388
|
+
self.emit(self.EVENT_VIRTUAL_CABLE_UNPLUG)
|
|
377
389
|
else:
|
|
378
390
|
logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
|
|
379
391
|
else:
|
|
@@ -538,14 +550,14 @@ class Host(HID):
|
|
|
538
550
|
message_type = pdu[0] >> 4
|
|
539
551
|
if message_type == Message.MessageType.HANDSHAKE:
|
|
540
552
|
logger.debug(f'<<< HID HANDSHAKE: {Message.Handshake(param).name}')
|
|
541
|
-
self.emit(
|
|
553
|
+
self.emit(self.EVENT_HANDSHAKE, Message.Handshake(param))
|
|
542
554
|
elif message_type == Message.MessageType.DATA:
|
|
543
555
|
logger.debug('<<< HID CONTROL DATA')
|
|
544
|
-
self.emit(
|
|
556
|
+
self.emit(self.EVENT_CONTROL_DATA, pdu)
|
|
545
557
|
elif message_type == Message.MessageType.CONTROL:
|
|
546
558
|
if param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
|
|
547
559
|
logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
|
|
548
|
-
self.emit(
|
|
560
|
+
self.emit(self.EVENT_VIRTUAL_CABLE_UNPLUG)
|
|
549
561
|
else:
|
|
550
562
|
logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
|
|
551
563
|
else:
|
bumble/host.py
CHANGED
|
@@ -435,6 +435,14 @@ class Host(utils.EventEmitter):
|
|
|
435
435
|
)
|
|
436
436
|
)
|
|
437
437
|
)
|
|
438
|
+
if self.supports_command(hci.HCI_SET_EVENT_MASK_PAGE_2_COMMAND):
|
|
439
|
+
await self.send_command(
|
|
440
|
+
hci.HCI_Set_Event_Mask_Page_2_Command(
|
|
441
|
+
event_mask_page_2=hci.HCI_Set_Event_Mask_Page_2_Command.mask(
|
|
442
|
+
[hci.HCI_ENCRYPTION_CHANGE_V2_EVENT]
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
)
|
|
438
446
|
|
|
439
447
|
if (
|
|
440
448
|
self.local_version is not None
|
|
@@ -456,6 +464,7 @@ class Host(utils.EventEmitter):
|
|
|
456
464
|
hci.HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT,
|
|
457
465
|
hci.HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT,
|
|
458
466
|
hci.HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT,
|
|
467
|
+
hci.HCI_LE_ENHANCED_CONNECTION_COMPLETE_V2_EVENT,
|
|
459
468
|
hci.HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT,
|
|
460
469
|
hci.HCI_LE_PHY_UPDATE_COMPLETE_EVENT,
|
|
461
470
|
hci.HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT,
|
|
@@ -1383,6 +1392,21 @@ class Host(utils.EventEmitter):
|
|
|
1383
1392
|
'connection_encryption_change',
|
|
1384
1393
|
event.connection_handle,
|
|
1385
1394
|
event.encryption_enabled,
|
|
1395
|
+
0,
|
|
1396
|
+
)
|
|
1397
|
+
else:
|
|
1398
|
+
self.emit(
|
|
1399
|
+
'connection_encryption_failure', event.connection_handle, event.status
|
|
1400
|
+
)
|
|
1401
|
+
|
|
1402
|
+
def on_hci_encryption_change_v2_event(self, event):
|
|
1403
|
+
# Notify the client
|
|
1404
|
+
if event.status == hci.HCI_SUCCESS:
|
|
1405
|
+
self.emit(
|
|
1406
|
+
'connection_encryption_change',
|
|
1407
|
+
event.connection_handle,
|
|
1408
|
+
event.encryption_enabled,
|
|
1409
|
+
event.encryption_key_size,
|
|
1386
1410
|
)
|
|
1387
1411
|
else:
|
|
1388
1412
|
self.emit(
|