bumble 0.0.208__py3-none-any.whl → 0.0.209__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/att.py +10 -2
- bumble/controller.py +7 -8
- bumble/core.py +5 -4
- bumble/device.py +25 -26
- bumble/gatt.py +16 -0
- bumble/hci.py +76 -71
- bumble/host.py +8 -3
- bumble/l2cap.py +2 -2
- bumble/link.py +2 -2
- bumble/pandora/host.py +12 -11
- bumble/pandora/security.py +2 -3
- bumble/pandora/utils.py +2 -2
- bumble/profiles/ancs.py +514 -0
- bumble/smp.py +3 -3
- bumble/transport/usb.py +1 -3
- {bumble-0.0.208.dist-info → bumble-0.0.209.dist-info}/METADATA +1 -1
- {bumble-0.0.208.dist-info → bumble-0.0.209.dist-info}/RECORD +22 -21
- {bumble-0.0.208.dist-info → bumble-0.0.209.dist-info}/WHEEL +1 -1
- {bumble-0.0.208.dist-info → bumble-0.0.209.dist-info}/LICENSE +0 -0
- {bumble-0.0.208.dist-info → bumble-0.0.209.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.208.dist-info → bumble-0.0.209.dist-info}/top_level.txt +0 -0
bumble/_version.py
CHANGED
bumble/att.py
CHANGED
|
@@ -223,7 +223,12 @@ UUID_2_FIELD_SPEC = lambda x, y: UUID.parse_uuid_2(x, y) # noqa: E731
|
|
|
223
223
|
# Exceptions
|
|
224
224
|
# -----------------------------------------------------------------------------
|
|
225
225
|
class ATT_Error(ProtocolError):
|
|
226
|
-
|
|
226
|
+
error_code: int
|
|
227
|
+
att_handle: int
|
|
228
|
+
|
|
229
|
+
def __init__(
|
|
230
|
+
self, error_code: int, att_handle: int = 0x0000, message: str = ''
|
|
231
|
+
) -> None:
|
|
227
232
|
super().__init__(
|
|
228
233
|
error_code,
|
|
229
234
|
error_namespace='att',
|
|
@@ -233,7 +238,10 @@ class ATT_Error(ProtocolError):
|
|
|
233
238
|
self.message = message
|
|
234
239
|
|
|
235
240
|
def __str__(self):
|
|
236
|
-
return
|
|
241
|
+
return (
|
|
242
|
+
f'ATT_Error(error={self.error_name}, '
|
|
243
|
+
f'handle={self.att_handle:04X}): {self.message}'
|
|
244
|
+
)
|
|
237
245
|
|
|
238
246
|
|
|
239
247
|
# -----------------------------------------------------------------------------
|
bumble/controller.py
CHANGED
|
@@ -25,8 +25,6 @@ import random
|
|
|
25
25
|
import struct
|
|
26
26
|
from bumble.colors import color
|
|
27
27
|
from bumble.core import (
|
|
28
|
-
BT_CENTRAL_ROLE,
|
|
29
|
-
BT_PERIPHERAL_ROLE,
|
|
30
28
|
BT_LE_TRANSPORT,
|
|
31
29
|
BT_BR_EDR_TRANSPORT,
|
|
32
30
|
)
|
|
@@ -47,6 +45,7 @@ from bumble.hci import (
|
|
|
47
45
|
HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
48
46
|
HCI_VERSION_BLUETOOTH_CORE_5_0,
|
|
49
47
|
Address,
|
|
48
|
+
Role,
|
|
50
49
|
HCI_AclDataPacket,
|
|
51
50
|
HCI_AclDataPacketAssembler,
|
|
52
51
|
HCI_Command_Complete_Event,
|
|
@@ -98,7 +97,7 @@ class CisLink:
|
|
|
98
97
|
class Connection:
|
|
99
98
|
controller: Controller
|
|
100
99
|
handle: int
|
|
101
|
-
role:
|
|
100
|
+
role: Role
|
|
102
101
|
peer_address: Address
|
|
103
102
|
link: Any
|
|
104
103
|
transport: int
|
|
@@ -390,7 +389,7 @@ class Controller:
|
|
|
390
389
|
connection = Connection(
|
|
391
390
|
controller=self,
|
|
392
391
|
handle=connection_handle,
|
|
393
|
-
role=
|
|
392
|
+
role=Role.PERIPHERAL,
|
|
394
393
|
peer_address=peer_address,
|
|
395
394
|
link=self.link,
|
|
396
395
|
transport=BT_LE_TRANSPORT,
|
|
@@ -450,7 +449,7 @@ class Controller:
|
|
|
450
449
|
connection = Connection(
|
|
451
450
|
controller=self,
|
|
452
451
|
handle=connection_handle,
|
|
453
|
-
role=
|
|
452
|
+
role=Role.CENTRAL,
|
|
454
453
|
peer_address=peer_address,
|
|
455
454
|
link=self.link,
|
|
456
455
|
transport=BT_LE_TRANSPORT,
|
|
@@ -469,7 +468,7 @@ class Controller:
|
|
|
469
468
|
HCI_LE_Connection_Complete_Event(
|
|
470
469
|
status=status,
|
|
471
470
|
connection_handle=connection.handle if connection else 0,
|
|
472
|
-
role=
|
|
471
|
+
role=Role.CENTRAL,
|
|
473
472
|
peer_address_type=le_create_connection_command.peer_address_type,
|
|
474
473
|
peer_address=le_create_connection_command.peer_address,
|
|
475
474
|
connection_interval=le_create_connection_command.connection_interval_min,
|
|
@@ -693,7 +692,7 @@ class Controller:
|
|
|
693
692
|
controller=self,
|
|
694
693
|
handle=connection_handle,
|
|
695
694
|
# Role doesn't matter in Classic because they are managed by HCI_Role_Change and HCI_Role_Discovery
|
|
696
|
-
role=
|
|
695
|
+
role=Role.CENTRAL,
|
|
697
696
|
peer_address=peer_address,
|
|
698
697
|
link=self.link,
|
|
699
698
|
transport=BT_BR_EDR_TRANSPORT,
|
|
@@ -761,7 +760,7 @@ class Controller:
|
|
|
761
760
|
controller=self,
|
|
762
761
|
handle=connection_handle,
|
|
763
762
|
# Role doesn't matter in SCO.
|
|
764
|
-
role=
|
|
763
|
+
role=Role.CENTRAL,
|
|
765
764
|
peer_address=peer_address,
|
|
766
765
|
link=self.link,
|
|
767
766
|
transport=BT_BR_EDR_TRANSPORT,
|
bumble/core.py
CHANGED
|
@@ -31,11 +31,12 @@ from bumble.utils import OpenIntEnum
|
|
|
31
31
|
# -----------------------------------------------------------------------------
|
|
32
32
|
# fmt: off
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
class PhysicalTransport(enum.IntEnum):
|
|
35
|
+
BR_EDR = 0
|
|
36
|
+
LE = 1
|
|
36
37
|
|
|
37
|
-
BT_BR_EDR_TRANSPORT =
|
|
38
|
-
BT_LE_TRANSPORT =
|
|
38
|
+
BT_BR_EDR_TRANSPORT = PhysicalTransport.BR_EDR
|
|
39
|
+
BT_LE_TRANSPORT = PhysicalTransport.LE
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
# fmt: on
|
bumble/device.py
CHANGED
|
@@ -58,9 +58,7 @@ from .host import DataPacketQueue, Host
|
|
|
58
58
|
from .profiles.gap import GenericAccessService
|
|
59
59
|
from .core import (
|
|
60
60
|
BT_BR_EDR_TRANSPORT,
|
|
61
|
-
BT_CENTRAL_ROLE,
|
|
62
61
|
BT_LE_TRANSPORT,
|
|
63
|
-
BT_PERIPHERAL_ROLE,
|
|
64
62
|
AdvertisingData,
|
|
65
63
|
BaseBumbleError,
|
|
66
64
|
ConnectionParameterUpdateError,
|
|
@@ -1555,13 +1553,13 @@ class IsoPacketStream:
|
|
|
1555
1553
|
class Connection(CompositeEventEmitter):
|
|
1556
1554
|
device: Device
|
|
1557
1555
|
handle: int
|
|
1558
|
-
transport:
|
|
1556
|
+
transport: core.PhysicalTransport
|
|
1559
1557
|
self_address: hci.Address
|
|
1560
1558
|
self_resolvable_address: Optional[hci.Address]
|
|
1561
1559
|
peer_address: hci.Address
|
|
1562
1560
|
peer_resolvable_address: Optional[hci.Address]
|
|
1563
1561
|
peer_le_features: Optional[hci.LeFeatureMask]
|
|
1564
|
-
role:
|
|
1562
|
+
role: hci.Role
|
|
1565
1563
|
encryption: int
|
|
1566
1564
|
authenticated: bool
|
|
1567
1565
|
sc: bool
|
|
@@ -1674,9 +1672,9 @@ class Connection(CompositeEventEmitter):
|
|
|
1674
1672
|
def role_name(self):
|
|
1675
1673
|
if self.role is None:
|
|
1676
1674
|
return 'NOT-SET'
|
|
1677
|
-
if self.role ==
|
|
1675
|
+
if self.role == hci.Role.CENTRAL:
|
|
1678
1676
|
return 'CENTRAL'
|
|
1679
|
-
if self.role ==
|
|
1677
|
+
if self.role == hci.Role.PERIPHERAL:
|
|
1680
1678
|
return 'PERIPHERAL'
|
|
1681
1679
|
return f'UNKNOWN[{self.role}]'
|
|
1682
1680
|
|
|
@@ -1734,7 +1732,7 @@ class Connection(CompositeEventEmitter):
|
|
|
1734
1732
|
async def encrypt(self, enable: bool = True) -> None:
|
|
1735
1733
|
return await self.device.encrypt(self, enable)
|
|
1736
1734
|
|
|
1737
|
-
async def switch_role(self, role:
|
|
1735
|
+
async def switch_role(self, role: hci.Role) -> None:
|
|
1738
1736
|
return await self.device.switch_role(self, role)
|
|
1739
1737
|
|
|
1740
1738
|
async def sustain(self, timeout: Optional[float] = None) -> None:
|
|
@@ -2713,7 +2711,7 @@ class Device(CompositeEventEmitter):
|
|
|
2713
2711
|
if phy == hci.HCI_LE_1M_PHY:
|
|
2714
2712
|
return True
|
|
2715
2713
|
|
|
2716
|
-
feature_map = {
|
|
2714
|
+
feature_map: dict[int, hci.LeFeatureMask] = {
|
|
2717
2715
|
hci.HCI_LE_2M_PHY: hci.LeFeatureMask.LE_2M_PHY,
|
|
2718
2716
|
hci.HCI_LE_CODED_PHY: hci.LeFeatureMask.LE_CODED_PHY,
|
|
2719
2717
|
}
|
|
@@ -2734,7 +2732,7 @@ class Device(CompositeEventEmitter):
|
|
|
2734
2732
|
self,
|
|
2735
2733
|
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
|
2736
2734
|
target: Optional[hci.Address] = None,
|
|
2737
|
-
own_address_type:
|
|
2735
|
+
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
2738
2736
|
auto_restart: bool = False,
|
|
2739
2737
|
advertising_data: Optional[bytes] = None,
|
|
2740
2738
|
scan_response_data: Optional[bytes] = None,
|
|
@@ -3015,7 +3013,7 @@ class Device(CompositeEventEmitter):
|
|
|
3015
3013
|
active: bool = True,
|
|
3016
3014
|
scan_interval: float = DEVICE_DEFAULT_SCAN_INTERVAL, # Scan interval in ms
|
|
3017
3015
|
scan_window: float = DEVICE_DEFAULT_SCAN_WINDOW, # Scan window in ms
|
|
3018
|
-
own_address_type:
|
|
3016
|
+
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
3019
3017
|
filter_duplicates: bool = False,
|
|
3020
3018
|
scanning_phys: Sequence[int] = (hci.HCI_LE_1M_PHY, hci.HCI_LE_CODED_PHY),
|
|
3021
3019
|
) -> None:
|
|
@@ -3091,7 +3089,7 @@ class Device(CompositeEventEmitter):
|
|
|
3091
3089
|
# pylint: disable=line-too-long
|
|
3092
3090
|
hci.HCI_LE_Set_Scan_Parameters_Command(
|
|
3093
3091
|
le_scan_type=scan_type,
|
|
3094
|
-
le_scan_interval=int(
|
|
3092
|
+
le_scan_interval=int(scan_interval / 0.625),
|
|
3095
3093
|
le_scan_window=int(scan_window / 0.625),
|
|
3096
3094
|
own_address_type=own_address_type,
|
|
3097
3095
|
scanning_filter_policy=hci.HCI_LE_Set_Scan_Parameters_Command.BASIC_UNFILTERED_POLICY,
|
|
@@ -3381,11 +3379,11 @@ class Device(CompositeEventEmitter):
|
|
|
3381
3379
|
async def connect(
|
|
3382
3380
|
self,
|
|
3383
3381
|
peer_address: Union[hci.Address, str],
|
|
3384
|
-
transport:
|
|
3382
|
+
transport: core.PhysicalTransport = BT_LE_TRANSPORT,
|
|
3385
3383
|
connection_parameters_preferences: Optional[
|
|
3386
|
-
|
|
3384
|
+
dict[hci.Phy, ConnectionParametersPreferences]
|
|
3387
3385
|
] = None,
|
|
3388
|
-
own_address_type:
|
|
3386
|
+
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
3389
3387
|
timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3390
3388
|
always_resolve: bool = False,
|
|
3391
3389
|
) -> Connection:
|
|
@@ -3433,6 +3431,7 @@ class Device(CompositeEventEmitter):
|
|
|
3433
3431
|
# Check parameters
|
|
3434
3432
|
if transport not in (BT_LE_TRANSPORT, BT_BR_EDR_TRANSPORT):
|
|
3435
3433
|
raise InvalidArgumentError('invalid transport')
|
|
3434
|
+
transport = core.PhysicalTransport(transport)
|
|
3436
3435
|
|
|
3437
3436
|
# Adjust the transport automatically if we need to
|
|
3438
3437
|
if transport == BT_LE_TRANSPORT and not self.le_enabled:
|
|
@@ -3628,7 +3627,7 @@ class Device(CompositeEventEmitter):
|
|
|
3628
3627
|
else:
|
|
3629
3628
|
# Save pending connection
|
|
3630
3629
|
self.pending_connections[peer_address] = Connection.incomplete(
|
|
3631
|
-
self, peer_address,
|
|
3630
|
+
self, peer_address, hci.Role.CENTRAL
|
|
3632
3631
|
)
|
|
3633
3632
|
|
|
3634
3633
|
# TODO: allow passing other settings
|
|
@@ -3683,7 +3682,7 @@ class Device(CompositeEventEmitter):
|
|
|
3683
3682
|
async def accept(
|
|
3684
3683
|
self,
|
|
3685
3684
|
peer_address: Union[hci.Address, str] = hci.Address.ANY,
|
|
3686
|
-
role:
|
|
3685
|
+
role: hci.Role = hci.Role.PERIPHERAL,
|
|
3687
3686
|
timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3688
3687
|
) -> Connection:
|
|
3689
3688
|
'''
|
|
@@ -3769,12 +3768,12 @@ class Device(CompositeEventEmitter):
|
|
|
3769
3768
|
self.on('connection', on_connection)
|
|
3770
3769
|
self.on('connection_failure', on_connection_failure)
|
|
3771
3770
|
|
|
3772
|
-
# Save pending connection, with the Peripheral role.
|
|
3771
|
+
# Save pending connection, with the Peripheral hci.role.
|
|
3773
3772
|
# Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
|
|
3774
3773
|
# command, this connection is still considered Peripheral until an eventual
|
|
3775
3774
|
# role change event.
|
|
3776
3775
|
self.pending_connections[peer_address] = Connection.incomplete(
|
|
3777
|
-
self, peer_address,
|
|
3776
|
+
self, peer_address, hci.Role.PERIPHERAL
|
|
3778
3777
|
)
|
|
3779
3778
|
|
|
3780
3779
|
try:
|
|
@@ -3903,7 +3902,7 @@ class Device(CompositeEventEmitter):
|
|
|
3903
3902
|
'''
|
|
3904
3903
|
|
|
3905
3904
|
if use_l2cap:
|
|
3906
|
-
if connection.role !=
|
|
3905
|
+
if connection.role != hci.Role.PERIPHERAL:
|
|
3907
3906
|
raise InvalidStateError(
|
|
3908
3907
|
'only peripheral can update connection parameters with l2cap'
|
|
3909
3908
|
)
|
|
@@ -4148,10 +4147,10 @@ class Device(CompositeEventEmitter):
|
|
|
4148
4147
|
if keys.ltk:
|
|
4149
4148
|
return keys.ltk.value
|
|
4150
4149
|
|
|
4151
|
-
if connection.role ==
|
|
4150
|
+
if connection.role == hci.Role.CENTRAL and keys.ltk_central:
|
|
4152
4151
|
return keys.ltk_central.value
|
|
4153
4152
|
|
|
4154
|
-
if connection.role ==
|
|
4153
|
+
if connection.role == hci.Role.PERIPHERAL and keys.ltk_peripheral:
|
|
4155
4154
|
return keys.ltk_peripheral.value
|
|
4156
4155
|
return None
|
|
4157
4156
|
|
|
@@ -4303,7 +4302,7 @@ class Device(CompositeEventEmitter):
|
|
|
4303
4302
|
self.emit('key_store_update')
|
|
4304
4303
|
|
|
4305
4304
|
# [Classic only]
|
|
4306
|
-
async def switch_role(self, connection: Connection, role:
|
|
4305
|
+
async def switch_role(self, connection: Connection, role: hci.Role):
|
|
4307
4306
|
pending_role_change = asyncio.get_running_loop().create_future()
|
|
4308
4307
|
|
|
4309
4308
|
def on_role_change(new_role):
|
|
@@ -5178,11 +5177,11 @@ class Device(CompositeEventEmitter):
|
|
|
5178
5177
|
def on_connection(
|
|
5179
5178
|
self,
|
|
5180
5179
|
connection_handle: int,
|
|
5181
|
-
transport:
|
|
5180
|
+
transport: core.PhysicalTransport,
|
|
5182
5181
|
peer_address: hci.Address,
|
|
5183
5182
|
self_resolvable_address: Optional[hci.Address],
|
|
5184
5183
|
peer_resolvable_address: Optional[hci.Address],
|
|
5185
|
-
role:
|
|
5184
|
+
role: hci.Role,
|
|
5186
5185
|
connection_parameters: ConnectionParameters,
|
|
5187
5186
|
) -> None:
|
|
5188
5187
|
# Convert all-zeros addresses into None.
|
|
@@ -5225,7 +5224,7 @@ class Device(CompositeEventEmitter):
|
|
|
5225
5224
|
peer_address = resolved_address
|
|
5226
5225
|
|
|
5227
5226
|
self_address = None
|
|
5228
|
-
own_address_type: Optional[
|
|
5227
|
+
own_address_type: Optional[hci.OwnAddressType] = None
|
|
5229
5228
|
if role == hci.HCI_CENTRAL_ROLE:
|
|
5230
5229
|
own_address_type = self.connect_own_address_type
|
|
5231
5230
|
assert own_address_type is not None
|
|
@@ -5353,7 +5352,7 @@ class Device(CompositeEventEmitter):
|
|
|
5353
5352
|
elif self.classic_accept_any:
|
|
5354
5353
|
# Save pending connection
|
|
5355
5354
|
self.pending_connections[bd_addr] = Connection.incomplete(
|
|
5356
|
-
self, bd_addr,
|
|
5355
|
+
self, bd_addr, hci.Role.PERIPHERAL
|
|
5357
5356
|
)
|
|
5358
5357
|
|
|
5359
5358
|
self.host.send_command_sync(
|
bumble/gatt.py
CHANGED
|
@@ -286,6 +286,22 @@ GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC = UUID('38663f1a-e711-4cac-b641-32
|
|
|
286
286
|
GATT_ASHA_VOLUME_CHARACTERISTIC = UUID('00e4ca9e-ab14-41e4-8823-f9e70c7e91df', 'Volume')
|
|
287
287
|
GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC = UUID('2d410339-82b6-42aa-b34e-e2e01df8cc1a', 'LE_PSM_OUT')
|
|
288
288
|
|
|
289
|
+
# Apple Notification Center Service
|
|
290
|
+
GATT_ANCS_SERVICE = UUID('7905F431-B5CE-4E99-A40F-4B1E122D00D0', 'Apple Notification Center')
|
|
291
|
+
GATT_ANCS_NOTIFICATION_SOURCE_CHARACTERISTIC = UUID('9FBF120D-6301-42D9-8C58-25E699A21DBD', 'Notification Source')
|
|
292
|
+
GATT_ANCS_CONTROL_POINT_CHARACTERISTIC = UUID('69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9', 'Control Point')
|
|
293
|
+
GATT_ANCS_DATA_SOURCE_CHARACTERISTIC = UUID('22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB', 'Data Source')
|
|
294
|
+
|
|
295
|
+
# Apple Media Service
|
|
296
|
+
GATT_AMS_SERVICE = UUID('89D3502B-0F36-433A-8EF4-C502AD55F8DC', 'Apple Media')
|
|
297
|
+
GATT_AMS_REMOTE_COMMAND_CHARACTERISTIC = UUID('9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2', 'Remote Command')
|
|
298
|
+
GATT_AMS_ENTITY_UPDATE_CHARACTERISTIC = UUID('2F7CABCE-808D-411F-9A0C-BB92BA96C102', 'Entity Update')
|
|
299
|
+
GATT_AMS_ENTITY_ATTRIBUTE_CHARACTERISTIC = UUID('C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7', 'Entity Attribute')
|
|
300
|
+
|
|
301
|
+
# Misc Apple Services
|
|
302
|
+
GATT_APPLE_CONTINUITY_SERVICE = UUID('D0611E78-BBB4-4591-A5F8-487910AE4366', 'Apple Continuity')
|
|
303
|
+
GATT_APPLE_NEARBY_SERVICE = UUID('9FA480E0-4967-4542-9390-D343DC5D04AE', 'Apple Nearby')
|
|
304
|
+
|
|
289
305
|
# Misc
|
|
290
306
|
GATT_DEVICE_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2A00, 'Device Name')
|
|
291
307
|
GATT_APPEARANCE_CHARACTERISTIC = UUID.from_16_bits(0x2A01, 'Appearance')
|
bumble/hci.py
CHANGED
|
@@ -24,6 +24,7 @@ import logging
|
|
|
24
24
|
import secrets
|
|
25
25
|
import struct
|
|
26
26
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union, ClassVar
|
|
27
|
+
from typing_extensions import Self
|
|
27
28
|
|
|
28
29
|
from bumble import crypto
|
|
29
30
|
from bumble.colors import color
|
|
@@ -34,6 +35,7 @@ from bumble.core import (
|
|
|
34
35
|
InvalidArgumentError,
|
|
35
36
|
InvalidPacketError,
|
|
36
37
|
ProtocolError,
|
|
38
|
+
PhysicalTransport,
|
|
37
39
|
bit_flags_to_strings,
|
|
38
40
|
name_or_number,
|
|
39
41
|
padded_bytes,
|
|
@@ -94,7 +96,7 @@ def map_class_of_device(class_of_device):
|
|
|
94
96
|
)
|
|
95
97
|
|
|
96
98
|
|
|
97
|
-
def phy_list_to_bits(phys: Optional[Iterable[
|
|
99
|
+
def phy_list_to_bits(phys: Optional[Iterable[Phy]]) -> int:
|
|
98
100
|
if phys is None:
|
|
99
101
|
return 0
|
|
100
102
|
|
|
@@ -700,30 +702,22 @@ HCI_ERROR_NAMES[HCI_SUCCESS] = 'HCI_SUCCESS'
|
|
|
700
702
|
HCI_COMMAND_STATUS_PENDING = 0
|
|
701
703
|
|
|
702
704
|
|
|
705
|
+
class Phy(enum.IntEnum):
|
|
706
|
+
LE_1M = 1
|
|
707
|
+
LE_2M = 2
|
|
708
|
+
LE_CODED = 3
|
|
709
|
+
|
|
710
|
+
|
|
703
711
|
# ACL
|
|
704
712
|
HCI_ACL_PB_FIRST_NON_FLUSHABLE = 0
|
|
705
713
|
HCI_ACL_PB_CONTINUATION = 1
|
|
706
714
|
HCI_ACL_PB_FIRST_FLUSHABLE = 2
|
|
707
715
|
HCI_ACK_PB_COMPLETE_L2CAP = 3
|
|
708
716
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
HCI_ROLE_NAMES = {
|
|
714
|
-
HCI_CENTRAL_ROLE: 'CENTRAL',
|
|
715
|
-
HCI_PERIPHERAL_ROLE: 'PERIPHERAL'
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
# LE PHY Types
|
|
719
|
-
HCI_LE_1M_PHY = 1
|
|
720
|
-
HCI_LE_2M_PHY = 2
|
|
721
|
-
HCI_LE_CODED_PHY = 3
|
|
722
|
-
|
|
723
|
-
HCI_LE_PHY_NAMES = {
|
|
724
|
-
HCI_LE_1M_PHY: 'LE 1M',
|
|
725
|
-
HCI_LE_2M_PHY: 'LE 2M',
|
|
726
|
-
HCI_LE_CODED_PHY: 'LE Coded'
|
|
717
|
+
HCI_LE_PHY_NAMES: dict[int,str] = {
|
|
718
|
+
Phy.LE_1M: 'LE 1M',
|
|
719
|
+
Phy.LE_2M: 'LE 2M',
|
|
720
|
+
Phy.LE_CODED: 'LE Coded'
|
|
727
721
|
}
|
|
728
722
|
|
|
729
723
|
HCI_LE_1M_PHY_BIT = 0
|
|
@@ -732,19 +726,13 @@ HCI_LE_CODED_PHY_BIT = 2
|
|
|
732
726
|
|
|
733
727
|
HCI_LE_PHY_BIT_NAMES = ['LE_1M_PHY', 'LE_2M_PHY', 'LE_CODED_PHY']
|
|
734
728
|
|
|
735
|
-
HCI_LE_PHY_TYPE_TO_BIT = {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
729
|
+
HCI_LE_PHY_TYPE_TO_BIT: dict[Phy, int] = {
|
|
730
|
+
Phy.LE_1M: HCI_LE_1M_PHY_BIT,
|
|
731
|
+
Phy.LE_2M: HCI_LE_2M_PHY_BIT,
|
|
732
|
+
Phy.LE_CODED: HCI_LE_CODED_PHY_BIT,
|
|
739
733
|
}
|
|
740
734
|
|
|
741
735
|
|
|
742
|
-
class Phy(enum.IntEnum):
|
|
743
|
-
LE_1M = HCI_LE_1M_PHY
|
|
744
|
-
LE_2M = HCI_LE_2M_PHY
|
|
745
|
-
LE_CODED = HCI_LE_CODED_PHY
|
|
746
|
-
|
|
747
|
-
|
|
748
736
|
class PhyBit(enum.IntFlag):
|
|
749
737
|
LE_1M = 1 << HCI_LE_1M_PHY_BIT
|
|
750
738
|
LE_2M = 1 << HCI_LE_2M_PHY_BIT
|
|
@@ -811,6 +799,19 @@ class CsSubeventAbortReason(OpenIntEnum):
|
|
|
811
799
|
SCHEDULING_CONFLICT_OR_LIMITED_RESOURCES = 0x03
|
|
812
800
|
UNSPECIFIED = 0x0F
|
|
813
801
|
|
|
802
|
+
class Role(enum.IntEnum):
|
|
803
|
+
CENTRAL = 0
|
|
804
|
+
PERIPHERAL = 1
|
|
805
|
+
|
|
806
|
+
# For Backward Compatibility.
|
|
807
|
+
HCI_CENTRAL_ROLE = Role.CENTRAL
|
|
808
|
+
HCI_PERIPHERAL_ROLE = Role.PERIPHERAL
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
HCI_LE_1M_PHY = Phy.LE_1M
|
|
812
|
+
HCI_LE_2M_PHY = Phy.LE_2M
|
|
813
|
+
HCI_LE_CODED_PHY = Phy.LE_CODED
|
|
814
|
+
|
|
814
815
|
|
|
815
816
|
# Connection Parameters
|
|
816
817
|
HCI_CONNECTION_INTERVAL_MS_PER_UNIT = 1.25
|
|
@@ -889,10 +890,15 @@ HCI_LINK_TYPE_NAMES = {
|
|
|
889
890
|
}
|
|
890
891
|
|
|
891
892
|
# Address types
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
893
|
+
class AddressType(OpenIntEnum):
|
|
894
|
+
PUBLIC_DEVICE = 0x00
|
|
895
|
+
RANDOM_DEVICE = 0x01
|
|
896
|
+
PUBLIC_IDENTITY = 0x02
|
|
897
|
+
RANDOM_IDENTITY = 0x03
|
|
898
|
+
# (Directed Only) Address is RPA, but controller cannot resolve.
|
|
899
|
+
UNABLE_TO_RESOLVE = 0xFE
|
|
900
|
+
# (Extended Only) No address.
|
|
901
|
+
ANONYMOUS = 0xFF
|
|
896
902
|
|
|
897
903
|
# Supported Commands Masks
|
|
898
904
|
# See Bluetooth spec @ 6.27 SUPPORTED COMMANDS
|
|
@@ -1582,8 +1588,8 @@ class HCI_Constant:
|
|
|
1582
1588
|
return HCI_ERROR_NAMES.get(status, f'0x{status:02X}')
|
|
1583
1589
|
|
|
1584
1590
|
@staticmethod
|
|
1585
|
-
def role_name(role):
|
|
1586
|
-
return
|
|
1591
|
+
def role_name(role: int) -> str:
|
|
1592
|
+
return Role(role).name
|
|
1587
1593
|
|
|
1588
1594
|
@staticmethod
|
|
1589
1595
|
def le_phy_name(phy):
|
|
@@ -1949,17 +1955,10 @@ class Address:
|
|
|
1949
1955
|
address[0] is the LSB of the address, address[5] is the MSB.
|
|
1950
1956
|
'''
|
|
1951
1957
|
|
|
1952
|
-
PUBLIC_DEVICE_ADDRESS =
|
|
1953
|
-
RANDOM_DEVICE_ADDRESS =
|
|
1954
|
-
PUBLIC_IDENTITY_ADDRESS =
|
|
1955
|
-
RANDOM_IDENTITY_ADDRESS =
|
|
1956
|
-
|
|
1957
|
-
ADDRESS_TYPE_NAMES = {
|
|
1958
|
-
PUBLIC_DEVICE_ADDRESS: 'PUBLIC_DEVICE_ADDRESS',
|
|
1959
|
-
RANDOM_DEVICE_ADDRESS: 'RANDOM_DEVICE_ADDRESS',
|
|
1960
|
-
PUBLIC_IDENTITY_ADDRESS: 'PUBLIC_IDENTITY_ADDRESS',
|
|
1961
|
-
RANDOM_IDENTITY_ADDRESS: 'RANDOM_IDENTITY_ADDRESS',
|
|
1962
|
-
}
|
|
1958
|
+
PUBLIC_DEVICE_ADDRESS = AddressType.PUBLIC_DEVICE
|
|
1959
|
+
RANDOM_DEVICE_ADDRESS = AddressType.RANDOM_DEVICE
|
|
1960
|
+
PUBLIC_IDENTITY_ADDRESS = AddressType.PUBLIC_IDENTITY
|
|
1961
|
+
RANDOM_IDENTITY_ADDRESS = AddressType.RANDOM_IDENTITY
|
|
1963
1962
|
|
|
1964
1963
|
# Type declarations
|
|
1965
1964
|
NIL: Address
|
|
@@ -1969,40 +1968,44 @@ class Address:
|
|
|
1969
1968
|
# pylint: disable-next=unnecessary-lambda
|
|
1970
1969
|
ADDRESS_TYPE_SPEC = {'size': 1, 'mapper': lambda x: Address.address_type_name(x)}
|
|
1971
1970
|
|
|
1972
|
-
@
|
|
1973
|
-
def address_type_name(address_type):
|
|
1974
|
-
return
|
|
1971
|
+
@classmethod
|
|
1972
|
+
def address_type_name(cls: type[Self], address_type: int) -> str:
|
|
1973
|
+
return AddressType(address_type).name
|
|
1975
1974
|
|
|
1976
|
-
@
|
|
1977
|
-
def from_string_for_transport(
|
|
1975
|
+
@classmethod
|
|
1976
|
+
def from_string_for_transport(
|
|
1977
|
+
cls: type[Self], string: str, transport: PhysicalTransport
|
|
1978
|
+
) -> Self:
|
|
1978
1979
|
if transport == BT_BR_EDR_TRANSPORT:
|
|
1979
1980
|
address_type = Address.PUBLIC_DEVICE_ADDRESS
|
|
1980
1981
|
else:
|
|
1981
1982
|
address_type = Address.RANDOM_DEVICE_ADDRESS
|
|
1982
|
-
return
|
|
1983
|
+
return cls(string, address_type)
|
|
1983
1984
|
|
|
1984
|
-
@
|
|
1985
|
-
def parse_address(data, offset):
|
|
1985
|
+
@classmethod
|
|
1986
|
+
def parse_address(cls: type[Self], data: bytes, offset: int) -> tuple[int, Self]:
|
|
1986
1987
|
# Fix the type to a default value. This is used for parsing type-less Classic
|
|
1987
1988
|
# addresses
|
|
1988
|
-
return
|
|
1989
|
-
data, offset, Address.PUBLIC_DEVICE_ADDRESS
|
|
1990
|
-
)
|
|
1989
|
+
return cls.parse_address_with_type(data, offset, Address.PUBLIC_DEVICE_ADDRESS)
|
|
1991
1990
|
|
|
1992
|
-
@
|
|
1993
|
-
def parse_random_address(
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
)
|
|
1991
|
+
@classmethod
|
|
1992
|
+
def parse_random_address(
|
|
1993
|
+
cls: type[Self], data: bytes, offset: int
|
|
1994
|
+
) -> tuple[int, Self]:
|
|
1995
|
+
return cls.parse_address_with_type(data, offset, Address.RANDOM_DEVICE_ADDRESS)
|
|
1997
1996
|
|
|
1998
|
-
@
|
|
1999
|
-
def parse_address_with_type(
|
|
2000
|
-
|
|
1997
|
+
@classmethod
|
|
1998
|
+
def parse_address_with_type(
|
|
1999
|
+
cls: type[Self], data: bytes, offset: int, address_type: AddressType
|
|
2000
|
+
) -> tuple[int, Self]:
|
|
2001
|
+
return offset + 6, cls(data[offset : offset + 6], address_type)
|
|
2001
2002
|
|
|
2002
|
-
@
|
|
2003
|
-
def parse_address_preceded_by_type(
|
|
2004
|
-
|
|
2005
|
-
|
|
2003
|
+
@classmethod
|
|
2004
|
+
def parse_address_preceded_by_type(
|
|
2005
|
+
cls: type[Self], data: bytes, offset: int
|
|
2006
|
+
) -> tuple[int, Self]:
|
|
2007
|
+
address_type = AddressType(data[offset - 1])
|
|
2008
|
+
return cls.parse_address_with_type(data, offset, address_type)
|
|
2006
2009
|
|
|
2007
2010
|
@classmethod
|
|
2008
2011
|
def generate_static_address(cls) -> Address:
|
|
@@ -2042,8 +2045,10 @@ class Address:
|
|
|
2042
2045
|
)
|
|
2043
2046
|
|
|
2044
2047
|
def __init__(
|
|
2045
|
-
self,
|
|
2046
|
-
|
|
2048
|
+
self,
|
|
2049
|
+
address: Union[bytes, str],
|
|
2050
|
+
address_type: AddressType = RANDOM_DEVICE_ADDRESS,
|
|
2051
|
+
) -> None:
|
|
2047
2052
|
'''
|
|
2048
2053
|
Initialize an instance. `address` may be a byte array in little-endian
|
|
2049
2054
|
format, or a hex string in big-endian format (with optional ':'
|
bumble/host.py
CHANGED
|
@@ -44,6 +44,7 @@ from bumble import hci
|
|
|
44
44
|
from bumble.core import (
|
|
45
45
|
BT_BR_EDR_TRANSPORT,
|
|
46
46
|
BT_LE_TRANSPORT,
|
|
47
|
+
PhysicalTransport,
|
|
47
48
|
ConnectionPHY,
|
|
48
49
|
ConnectionParameters,
|
|
49
50
|
)
|
|
@@ -186,7 +187,11 @@ class DataPacketQueue(pyee.EventEmitter):
|
|
|
186
187
|
# -----------------------------------------------------------------------------
|
|
187
188
|
class Connection:
|
|
188
189
|
def __init__(
|
|
189
|
-
self,
|
|
190
|
+
self,
|
|
191
|
+
host: Host,
|
|
192
|
+
handle: int,
|
|
193
|
+
peer_address: hci.Address,
|
|
194
|
+
transport: PhysicalTransport,
|
|
190
195
|
):
|
|
191
196
|
self.host = host
|
|
192
197
|
self.handle = handle
|
|
@@ -979,7 +984,7 @@ class Host(AbortableEventEmitter):
|
|
|
979
984
|
event.peer_address,
|
|
980
985
|
getattr(event, 'local_resolvable_private_address', None),
|
|
981
986
|
getattr(event, 'peer_resolvable_private_address', None),
|
|
982
|
-
event.role,
|
|
987
|
+
hci.Role(event.role),
|
|
983
988
|
connection_parameters,
|
|
984
989
|
)
|
|
985
990
|
else:
|
|
@@ -1337,7 +1342,7 @@ class Host(AbortableEventEmitter):
|
|
|
1337
1342
|
f'role change for {event.bd_addr}: '
|
|
1338
1343
|
f'{hci.HCI_Constant.role_name(event.new_role)}'
|
|
1339
1344
|
)
|
|
1340
|
-
self.emit('role_change', event.bd_addr, event.new_role)
|
|
1345
|
+
self.emit('role_change', event.bd_addr, hci.Role(event.new_role))
|
|
1341
1346
|
else:
|
|
1342
1347
|
logger.debug(
|
|
1343
1348
|
f'role change for {event.bd_addr} failed: '
|
bumble/l2cap.py
CHANGED
|
@@ -42,7 +42,6 @@ from typing import (
|
|
|
42
42
|
from .utils import deprecated
|
|
43
43
|
from .colors import color
|
|
44
44
|
from .core import (
|
|
45
|
-
BT_CENTRAL_ROLE,
|
|
46
45
|
InvalidStateError,
|
|
47
46
|
InvalidArgumentError,
|
|
48
47
|
InvalidPacketError,
|
|
@@ -52,6 +51,7 @@ from .core import (
|
|
|
52
51
|
from .hci import (
|
|
53
52
|
HCI_LE_Connection_Update_Command,
|
|
54
53
|
HCI_Object,
|
|
54
|
+
Role,
|
|
55
55
|
key_with_value,
|
|
56
56
|
name_or_number,
|
|
57
57
|
)
|
|
@@ -1908,7 +1908,7 @@ class ChannelManager:
|
|
|
1908
1908
|
def on_l2cap_connection_parameter_update_request(
|
|
1909
1909
|
self, connection: Connection, cid: int, request
|
|
1910
1910
|
):
|
|
1911
|
-
if connection.role ==
|
|
1911
|
+
if connection.role == Role.CENTRAL:
|
|
1912
1912
|
self.send_control_frame(
|
|
1913
1913
|
connection,
|
|
1914
1914
|
cid,
|
bumble/link.py
CHANGED
|
@@ -20,7 +20,6 @@ import asyncio
|
|
|
20
20
|
from functools import partial
|
|
21
21
|
|
|
22
22
|
from bumble.core import (
|
|
23
|
-
BT_PERIPHERAL_ROLE,
|
|
24
23
|
BT_BR_EDR_TRANSPORT,
|
|
25
24
|
BT_LE_TRANSPORT,
|
|
26
25
|
InvalidStateError,
|
|
@@ -28,6 +27,7 @@ from bumble.core import (
|
|
|
28
27
|
from bumble.colors import color
|
|
29
28
|
from bumble.hci import (
|
|
30
29
|
Address,
|
|
30
|
+
Role,
|
|
31
31
|
HCI_SUCCESS,
|
|
32
32
|
HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR,
|
|
33
33
|
HCI_CONNECTION_TIMEOUT_ERROR,
|
|
@@ -292,7 +292,7 @@ class LocalLink:
|
|
|
292
292
|
return
|
|
293
293
|
|
|
294
294
|
async def task():
|
|
295
|
-
if responder_role !=
|
|
295
|
+
if responder_role != Role.PERIPHERAL:
|
|
296
296
|
initiator_controller.on_classic_role_change(
|
|
297
297
|
responder_controller.public_address, int(not (responder_role))
|
|
298
298
|
)
|