bumble 0.0.207__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 +9 -4
- bumble/apps/auracast.py +29 -35
- bumble/apps/bench.py +13 -10
- bumble/apps/console.py +19 -12
- bumble/apps/gg_bridge.py +1 -1
- bumble/att.py +61 -39
- bumble/controller.py +7 -8
- bumble/core.py +306 -159
- bumble/device.py +127 -82
- bumble/gatt.py +25 -228
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +38 -31
- bumble/gatt_server.py +5 -5
- bumble/hci.py +76 -71
- bumble/host.py +19 -8
- bumble/l2cap.py +2 -2
- bumble/link.py +2 -2
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +19 -23
- bumble/pandora/security.py +2 -3
- bumble/pandora/utils.py +2 -2
- bumble/profiles/aics.py +33 -23
- bumble/profiles/ancs.py +514 -0
- bumble/profiles/ascs.py +2 -1
- bumble/profiles/asha.py +11 -9
- bumble/profiles/bass.py +8 -5
- bumble/profiles/battery_service.py +13 -3
- bumble/profiles/device_information_service.py +16 -14
- bumble/profiles/gap.py +12 -8
- bumble/profiles/gatt_service.py +1 -0
- bumble/profiles/gmap.py +16 -11
- bumble/profiles/hap.py +8 -6
- bumble/profiles/heart_rate_service.py +20 -4
- bumble/profiles/mcp.py +11 -9
- bumble/profiles/pacs.py +37 -24
- bumble/profiles/tmap.py +6 -4
- bumble/profiles/vcs.py +6 -5
- bumble/profiles/vocs.py +49 -41
- bumble/smp.py +3 -3
- bumble/transport/usb.py +1 -3
- bumble/utils.py +10 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/METADATA +3 -3
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/RECORD +47 -45
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/WHEEL +1 -1
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/LICENSE +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/top_level.txt +0 -0
bumble/gatt_client.py
CHANGED
|
@@ -29,16 +29,18 @@ import logging
|
|
|
29
29
|
import struct
|
|
30
30
|
from datetime import datetime
|
|
31
31
|
from typing import (
|
|
32
|
+
Any,
|
|
33
|
+
Callable,
|
|
34
|
+
Dict,
|
|
35
|
+
Generic,
|
|
36
|
+
Iterable,
|
|
32
37
|
List,
|
|
33
38
|
Optional,
|
|
34
|
-
|
|
39
|
+
Set,
|
|
35
40
|
Tuple,
|
|
36
|
-
Callable,
|
|
37
41
|
Union,
|
|
38
|
-
Any,
|
|
39
|
-
Iterable,
|
|
40
42
|
Type,
|
|
41
|
-
|
|
43
|
+
TypeVar,
|
|
42
44
|
TYPE_CHECKING,
|
|
43
45
|
)
|
|
44
46
|
|
|
@@ -82,9 +84,14 @@ from .gatt import (
|
|
|
82
84
|
TemplateService,
|
|
83
85
|
)
|
|
84
86
|
|
|
87
|
+
# -----------------------------------------------------------------------------
|
|
88
|
+
# Typing
|
|
89
|
+
# -----------------------------------------------------------------------------
|
|
85
90
|
if TYPE_CHECKING:
|
|
86
91
|
from bumble.device import Connection
|
|
87
92
|
|
|
93
|
+
_T = TypeVar('_T')
|
|
94
|
+
|
|
88
95
|
# -----------------------------------------------------------------------------
|
|
89
96
|
# Logging
|
|
90
97
|
# -----------------------------------------------------------------------------
|
|
@@ -110,7 +117,7 @@ def show_services(services: Iterable[ServiceProxy]) -> None:
|
|
|
110
117
|
# -----------------------------------------------------------------------------
|
|
111
118
|
# Proxies
|
|
112
119
|
# -----------------------------------------------------------------------------
|
|
113
|
-
class AttributeProxy(EventEmitter):
|
|
120
|
+
class AttributeProxy(EventEmitter, Generic[_T]):
|
|
114
121
|
def __init__(
|
|
115
122
|
self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID
|
|
116
123
|
) -> None:
|
|
@@ -120,21 +127,21 @@ class AttributeProxy(EventEmitter):
|
|
|
120
127
|
self.end_group_handle = end_group_handle
|
|
121
128
|
self.type = attribute_type
|
|
122
129
|
|
|
123
|
-
async def read_value(self, no_long_read: bool = False) ->
|
|
130
|
+
async def read_value(self, no_long_read: bool = False) -> _T:
|
|
124
131
|
return self.decode_value(
|
|
125
132
|
await self.client.read_value(self.handle, no_long_read)
|
|
126
133
|
)
|
|
127
134
|
|
|
128
|
-
async def write_value(self, value, with_response=False):
|
|
135
|
+
async def write_value(self, value: _T, with_response=False):
|
|
129
136
|
return await self.client.write_value(
|
|
130
137
|
self.handle, self.encode_value(value), with_response
|
|
131
138
|
)
|
|
132
139
|
|
|
133
|
-
def encode_value(self, value:
|
|
134
|
-
return value
|
|
140
|
+
def encode_value(self, value: _T) -> bytes:
|
|
141
|
+
return value # type: ignore
|
|
135
142
|
|
|
136
|
-
def decode_value(self,
|
|
137
|
-
return
|
|
143
|
+
def decode_value(self, value: bytes) -> _T:
|
|
144
|
+
return value # type: ignore
|
|
138
145
|
|
|
139
146
|
def __str__(self) -> str:
|
|
140
147
|
return f'Attribute(handle=0x{self.handle:04X}, type={self.type})'
|
|
@@ -184,19 +191,19 @@ class ServiceProxy(AttributeProxy):
|
|
|
184
191
|
return f'Service(handle=0x{self.handle:04X}, uuid={self.uuid})'
|
|
185
192
|
|
|
186
193
|
|
|
187
|
-
class CharacteristicProxy(AttributeProxy):
|
|
194
|
+
class CharacteristicProxy(AttributeProxy[_T]):
|
|
188
195
|
properties: Characteristic.Properties
|
|
189
196
|
descriptors: List[DescriptorProxy]
|
|
190
|
-
subscribers: Dict[Any, Callable[[
|
|
197
|
+
subscribers: Dict[Any, Callable[[_T], Any]]
|
|
191
198
|
|
|
192
199
|
def __init__(
|
|
193
200
|
self,
|
|
194
|
-
client,
|
|
195
|
-
handle,
|
|
196
|
-
end_group_handle,
|
|
197
|
-
uuid,
|
|
201
|
+
client: Client,
|
|
202
|
+
handle: int,
|
|
203
|
+
end_group_handle: int,
|
|
204
|
+
uuid: UUID,
|
|
198
205
|
properties: int,
|
|
199
|
-
):
|
|
206
|
+
) -> None:
|
|
200
207
|
super().__init__(client, handle, end_group_handle, uuid)
|
|
201
208
|
self.uuid = uuid
|
|
202
209
|
self.properties = Characteristic.Properties(properties)
|
|
@@ -204,21 +211,21 @@ class CharacteristicProxy(AttributeProxy):
|
|
|
204
211
|
self.descriptors_discovered = False
|
|
205
212
|
self.subscribers = {} # Map from subscriber to proxy subscriber
|
|
206
213
|
|
|
207
|
-
def get_descriptor(self, descriptor_type):
|
|
214
|
+
def get_descriptor(self, descriptor_type: UUID) -> Optional[DescriptorProxy]:
|
|
208
215
|
for descriptor in self.descriptors:
|
|
209
216
|
if descriptor.type == descriptor_type:
|
|
210
217
|
return descriptor
|
|
211
218
|
|
|
212
219
|
return None
|
|
213
220
|
|
|
214
|
-
async def discover_descriptors(self):
|
|
221
|
+
async def discover_descriptors(self) -> list[DescriptorProxy]:
|
|
215
222
|
return await self.client.discover_descriptors(self)
|
|
216
223
|
|
|
217
224
|
async def subscribe(
|
|
218
225
|
self,
|
|
219
|
-
subscriber: Optional[Callable[[
|
|
226
|
+
subscriber: Optional[Callable[[_T], Any]] = None,
|
|
220
227
|
prefer_notify: bool = True,
|
|
221
|
-
):
|
|
228
|
+
) -> None:
|
|
222
229
|
if subscriber is not None:
|
|
223
230
|
if subscriber in self.subscribers:
|
|
224
231
|
# We already have a proxy subscriber
|
|
@@ -233,13 +240,13 @@ class CharacteristicProxy(AttributeProxy):
|
|
|
233
240
|
self.subscribers[subscriber] = on_change
|
|
234
241
|
subscriber = on_change
|
|
235
242
|
|
|
236
|
-
|
|
243
|
+
await self.client.subscribe(self, subscriber, prefer_notify)
|
|
237
244
|
|
|
238
|
-
async def unsubscribe(self, subscriber=None, force=False):
|
|
245
|
+
async def unsubscribe(self, subscriber=None, force=False) -> None:
|
|
239
246
|
if subscriber in self.subscribers:
|
|
240
247
|
subscriber = self.subscribers.pop(subscriber)
|
|
241
248
|
|
|
242
|
-
|
|
249
|
+
await self.client.unsubscribe(self, subscriber, force)
|
|
243
250
|
|
|
244
251
|
def __str__(self) -> str:
|
|
245
252
|
return (
|
|
@@ -250,7 +257,7 @@ class CharacteristicProxy(AttributeProxy):
|
|
|
250
257
|
|
|
251
258
|
|
|
252
259
|
class DescriptorProxy(AttributeProxy):
|
|
253
|
-
def __init__(self, client, handle, descriptor_type):
|
|
260
|
+
def __init__(self, client: Client, handle: int, descriptor_type: UUID) -> None:
|
|
254
261
|
super().__init__(client, handle, 0, descriptor_type)
|
|
255
262
|
|
|
256
263
|
def __str__(self) -> str:
|
|
@@ -679,7 +686,7 @@ class Client:
|
|
|
679
686
|
|
|
680
687
|
properties, handle = struct.unpack_from('<BH', attribute_value)
|
|
681
688
|
characteristic_uuid = UUID.from_bytes(attribute_value[3:])
|
|
682
|
-
characteristic = CharacteristicProxy(
|
|
689
|
+
characteristic: CharacteristicProxy = CharacteristicProxy(
|
|
683
690
|
self, handle, 0, characteristic_uuid, properties
|
|
684
691
|
)
|
|
685
692
|
|
|
@@ -805,7 +812,7 @@ class Client:
|
|
|
805
812
|
logger.warning(f'bogus handle value: {attribute_handle}')
|
|
806
813
|
return []
|
|
807
814
|
|
|
808
|
-
attribute = AttributeProxy(
|
|
815
|
+
attribute: AttributeProxy = AttributeProxy(
|
|
809
816
|
self, attribute_handle, 0, UUID.from_bytes(attribute_uuid)
|
|
810
817
|
)
|
|
811
818
|
attributes.append(attribute)
|
|
@@ -818,7 +825,7 @@ class Client:
|
|
|
818
825
|
async def subscribe(
|
|
819
826
|
self,
|
|
820
827
|
characteristic: CharacteristicProxy,
|
|
821
|
-
subscriber: Optional[Callable[[
|
|
828
|
+
subscriber: Optional[Callable[[Any], Any]] = None,
|
|
822
829
|
prefer_notify: bool = True,
|
|
823
830
|
) -> None:
|
|
824
831
|
# If we haven't already discovered the descriptors for this characteristic,
|
|
@@ -868,7 +875,7 @@ class Client:
|
|
|
868
875
|
async def unsubscribe(
|
|
869
876
|
self,
|
|
870
877
|
characteristic: CharacteristicProxy,
|
|
871
|
-
subscriber: Optional[Callable[[
|
|
878
|
+
subscriber: Optional[Callable[[Any], Any]] = None,
|
|
872
879
|
force: bool = False,
|
|
873
880
|
) -> None:
|
|
874
881
|
'''
|
bumble/gatt_server.py
CHANGED
|
@@ -36,7 +36,6 @@ from typing import (
|
|
|
36
36
|
Tuple,
|
|
37
37
|
TypeVar,
|
|
38
38
|
Type,
|
|
39
|
-
Union,
|
|
40
39
|
TYPE_CHECKING,
|
|
41
40
|
)
|
|
42
41
|
from pyee import EventEmitter
|
|
@@ -78,7 +77,6 @@ from bumble.gatt import (
|
|
|
78
77
|
GATT_REQUEST_TIMEOUT,
|
|
79
78
|
GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
|
|
80
79
|
Characteristic,
|
|
81
|
-
CharacteristicAdapter,
|
|
82
80
|
CharacteristicDeclaration,
|
|
83
81
|
CharacteristicValue,
|
|
84
82
|
IncludedServiceDeclaration,
|
|
@@ -469,7 +467,7 @@ class Server(EventEmitter):
|
|
|
469
467
|
finally:
|
|
470
468
|
self.pending_confirmations[connection.handle] = None
|
|
471
469
|
|
|
472
|
-
async def
|
|
470
|
+
async def _notify_or_indicate_subscribers(
|
|
473
471
|
self,
|
|
474
472
|
indicate: bool,
|
|
475
473
|
attribute: Attribute,
|
|
@@ -503,7 +501,9 @@ class Server(EventEmitter):
|
|
|
503
501
|
value: Optional[bytes] = None,
|
|
504
502
|
force: bool = False,
|
|
505
503
|
):
|
|
506
|
-
return await self.
|
|
504
|
+
return await self._notify_or_indicate_subscribers(
|
|
505
|
+
False, attribute, value, force
|
|
506
|
+
)
|
|
507
507
|
|
|
508
508
|
async def indicate_subscribers(
|
|
509
509
|
self,
|
|
@@ -511,7 +511,7 @@ class Server(EventEmitter):
|
|
|
511
511
|
value: Optional[bytes] = None,
|
|
512
512
|
force: bool = False,
|
|
513
513
|
):
|
|
514
|
-
return await self.
|
|
514
|
+
return await self._notify_or_indicate_subscribers(True, attribute, value, force)
|
|
515
515
|
|
|
516
516
|
def on_disconnection(self, connection: Connection) -> None:
|
|
517
517
|
if connection.handle in self.subscribers:
|
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
|
|
@@ -235,7 +240,7 @@ class Host(AbortableEventEmitter):
|
|
|
235
240
|
cis_links: Dict[int, IsoLink]
|
|
236
241
|
bis_links: Dict[int, IsoLink]
|
|
237
242
|
sco_links: Dict[int, ScoLink]
|
|
238
|
-
bigs: dict[int, set[int]]
|
|
243
|
+
bigs: dict[int, set[int]]
|
|
239
244
|
acl_packet_queue: Optional[DataPacketQueue] = None
|
|
240
245
|
le_acl_packet_queue: Optional[DataPacketQueue] = None
|
|
241
246
|
iso_packet_queue: Optional[DataPacketQueue] = None
|
|
@@ -259,6 +264,7 @@ class Host(AbortableEventEmitter):
|
|
|
259
264
|
self.cis_links = {} # CIS links, by connection handle
|
|
260
265
|
self.bis_links = {} # BIS links, by connection handle
|
|
261
266
|
self.sco_links = {} # SCO links, by connection handle
|
|
267
|
+
self.bigs = {} # BIG Handle to BIS Handles
|
|
262
268
|
self.pending_command = None
|
|
263
269
|
self.pending_response: Optional[asyncio.Future[Any]] = None
|
|
264
270
|
self.number_of_supported_advertising_sets = 0
|
|
@@ -978,7 +984,7 @@ class Host(AbortableEventEmitter):
|
|
|
978
984
|
event.peer_address,
|
|
979
985
|
getattr(event, 'local_resolvable_private_address', None),
|
|
980
986
|
getattr(event, 'peer_resolvable_private_address', None),
|
|
981
|
-
event.role,
|
|
987
|
+
hci.Role(event.role),
|
|
982
988
|
connection_parameters,
|
|
983
989
|
)
|
|
984
990
|
else:
|
|
@@ -1061,8 +1067,10 @@ class Host(AbortableEventEmitter):
|
|
|
1061
1067
|
)
|
|
1062
1068
|
|
|
1063
1069
|
# Flush the data queues
|
|
1064
|
-
self.acl_packet_queue
|
|
1065
|
-
|
|
1070
|
+
if self.acl_packet_queue:
|
|
1071
|
+
self.acl_packet_queue.flush(handle)
|
|
1072
|
+
if self.le_acl_packet_queue:
|
|
1073
|
+
self.le_acl_packet_queue.flush(handle)
|
|
1066
1074
|
if self.iso_packet_queue:
|
|
1067
1075
|
self.iso_packet_queue.flush(handle)
|
|
1068
1076
|
else:
|
|
@@ -1098,8 +1106,11 @@ class Host(AbortableEventEmitter):
|
|
|
1098
1106
|
|
|
1099
1107
|
# Notify the client
|
|
1100
1108
|
if event.status == hci.HCI_SUCCESS:
|
|
1101
|
-
|
|
1102
|
-
|
|
1109
|
+
self.emit(
|
|
1110
|
+
'connection_phy_update',
|
|
1111
|
+
connection.handle,
|
|
1112
|
+
ConnectionPHY(event.tx_phy, event.rx_phy),
|
|
1113
|
+
)
|
|
1103
1114
|
else:
|
|
1104
1115
|
self.emit('connection_phy_update_failure', connection.handle, event.status)
|
|
1105
1116
|
|
|
@@ -1331,7 +1342,7 @@ class Host(AbortableEventEmitter):
|
|
|
1331
1342
|
f'role change for {event.bd_addr}: '
|
|
1332
1343
|
f'{hci.HCI_Constant.role_name(event.new_role)}'
|
|
1333
1344
|
)
|
|
1334
|
-
self.emit('role_change', event.bd_addr, event.new_role)
|
|
1345
|
+
self.emit('role_change', event.bd_addr, hci.Role(event.new_role))
|
|
1335
1346
|
else:
|
|
1336
1347
|
logger.debug(
|
|
1337
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
|
)
|
bumble/pairing.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2021-
|
|
1
|
+
# Copyright 2021-2025 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -76,18 +76,18 @@ class OobData:
|
|
|
76
76
|
return instance
|
|
77
77
|
|
|
78
78
|
def to_ad(self) -> AdvertisingData:
|
|
79
|
-
ad_structures = []
|
|
79
|
+
ad_structures: list[tuple[int, bytes]] = []
|
|
80
80
|
if self.address is not None:
|
|
81
81
|
ad_structures.append(
|
|
82
|
-
(AdvertisingData.LE_BLUETOOTH_DEVICE_ADDRESS, bytes(self.address))
|
|
82
|
+
(AdvertisingData.Type.LE_BLUETOOTH_DEVICE_ADDRESS, bytes(self.address))
|
|
83
83
|
)
|
|
84
84
|
if self.role is not None:
|
|
85
|
-
ad_structures.append((AdvertisingData.LE_ROLE, bytes([self.role])))
|
|
85
|
+
ad_structures.append((AdvertisingData.Type.LE_ROLE, bytes([self.role])))
|
|
86
86
|
if self.shared_data is not None:
|
|
87
87
|
ad_structures.extend(self.shared_data.to_ad().ad_structures)
|
|
88
88
|
if self.legacy_context is not None:
|
|
89
89
|
ad_structures.append(
|
|
90
|
-
(AdvertisingData.SECURITY_MANAGER_TK_VALUE, self.legacy_context.tk)
|
|
90
|
+
(AdvertisingData.Type.SECURITY_MANAGER_TK_VALUE, self.legacy_context.tk)
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
return AdvertisingData(ad_structures)
|