bumble 0.0.222__py3-none-any.whl → 0.0.223__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/usb_probe.py +15 -2
- bumble/att.py +97 -32
- bumble/avctp.py +1 -1
- bumble/avdtp.py +3 -3
- bumble/device.py +2 -5
- bumble/drivers/rtk.py +26 -3
- bumble/gatt.py +2 -2
- bumble/gatt_client.py +5 -4
- bumble/gatt_server.py +100 -1
- bumble/hci.py +51 -50
- bumble/hid.py +2 -2
- bumble/host.py +10 -0
- bumble/l2cap.py +3 -1
- bumble/pandora/l2cap.py +1 -1
- bumble/profiles/battery_service.py +25 -34
- bumble/profiles/heart_rate_service.py +130 -121
- bumble/rfcomm.py +1 -1
- bumble/sdp.py +2 -2
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/METADATA +1 -1
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/RECORD +25 -25
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/WHEEL +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.223.dist-info}/top_level.txt +0 -0
bumble/hci.py
CHANGED
|
@@ -2090,9 +2090,9 @@ class Address:
|
|
|
2090
2090
|
RANDOM_IDENTITY_ADDRESS = AddressType.RANDOM_IDENTITY
|
|
2091
2091
|
|
|
2092
2092
|
# Type declarations
|
|
2093
|
-
NIL: Address
|
|
2094
|
-
ANY: Address
|
|
2095
|
-
ANY_RANDOM: Address
|
|
2093
|
+
NIL: ClassVar[Address]
|
|
2094
|
+
ANY: ClassVar[Address]
|
|
2095
|
+
ANY_RANDOM: ClassVar[Address]
|
|
2096
2096
|
|
|
2097
2097
|
# pylint: disable-next=unnecessary-lambda
|
|
2098
2098
|
ADDRESS_TYPE_SPEC = {'size': 1, 'mapper': lambda x: Address.address_type_name(x)}
|
|
@@ -2204,38 +2204,38 @@ class Address:
|
|
|
2204
2204
|
|
|
2205
2205
|
self.address_type = address_type
|
|
2206
2206
|
|
|
2207
|
-
def clone(self):
|
|
2207
|
+
def clone(self) -> Address:
|
|
2208
2208
|
return Address(self.address_bytes, self.address_type)
|
|
2209
2209
|
|
|
2210
2210
|
@property
|
|
2211
|
-
def is_public(self):
|
|
2211
|
+
def is_public(self) -> bool:
|
|
2212
2212
|
return self.address_type in (
|
|
2213
2213
|
self.PUBLIC_DEVICE_ADDRESS,
|
|
2214
2214
|
self.PUBLIC_IDENTITY_ADDRESS,
|
|
2215
2215
|
)
|
|
2216
2216
|
|
|
2217
2217
|
@property
|
|
2218
|
-
def is_random(self):
|
|
2218
|
+
def is_random(self) -> bool:
|
|
2219
2219
|
return not self.is_public
|
|
2220
2220
|
|
|
2221
2221
|
@property
|
|
2222
|
-
def is_resolved(self):
|
|
2222
|
+
def is_resolved(self) -> bool:
|
|
2223
2223
|
return self.address_type in (
|
|
2224
2224
|
self.PUBLIC_IDENTITY_ADDRESS,
|
|
2225
2225
|
self.RANDOM_IDENTITY_ADDRESS,
|
|
2226
2226
|
)
|
|
2227
2227
|
|
|
2228
2228
|
@property
|
|
2229
|
-
def is_resolvable(self):
|
|
2229
|
+
def is_resolvable(self) -> bool:
|
|
2230
2230
|
return self.address_type == self.RANDOM_DEVICE_ADDRESS and (
|
|
2231
2231
|
self.address_bytes[5] >> 6 == 1
|
|
2232
2232
|
)
|
|
2233
2233
|
|
|
2234
2234
|
@property
|
|
2235
|
-
def is_static(self):
|
|
2235
|
+
def is_static(self) -> bool:
|
|
2236
2236
|
return self.is_random and (self.address_bytes[5] >> 6 == 3)
|
|
2237
2237
|
|
|
2238
|
-
def to_string(self, with_type_qualifier=True):
|
|
2238
|
+
def to_string(self, with_type_qualifier: bool = True) -> str:
|
|
2239
2239
|
'''
|
|
2240
2240
|
String representation of the address, MSB first, with an optional type
|
|
2241
2241
|
qualifier.
|
|
@@ -2245,23 +2245,23 @@ class Address:
|
|
|
2245
2245
|
return result
|
|
2246
2246
|
return result + '/P'
|
|
2247
2247
|
|
|
2248
|
-
def __bytes__(self):
|
|
2248
|
+
def __bytes__(self) -> bytes:
|
|
2249
2249
|
return self.address_bytes
|
|
2250
2250
|
|
|
2251
|
-
def __hash__(self):
|
|
2251
|
+
def __hash__(self) -> int:
|
|
2252
2252
|
return hash(self.address_bytes)
|
|
2253
2253
|
|
|
2254
|
-
def __eq__(self, other):
|
|
2254
|
+
def __eq__(self, other: Any) -> bool:
|
|
2255
2255
|
return (
|
|
2256
2256
|
isinstance(other, Address)
|
|
2257
2257
|
and self.address_bytes == other.address_bytes
|
|
2258
2258
|
and self.is_public == other.is_public
|
|
2259
2259
|
)
|
|
2260
2260
|
|
|
2261
|
-
def __str__(self):
|
|
2261
|
+
def __str__(self) -> str:
|
|
2262
2262
|
return self.to_string()
|
|
2263
2263
|
|
|
2264
|
-
def __repr__(self):
|
|
2264
|
+
def __repr__(self) -> str:
|
|
2265
2265
|
return f'Address({self.to_string(False)}/{self.address_type_name(self.address_type)})'
|
|
2266
2266
|
|
|
2267
2267
|
|
|
@@ -2300,10 +2300,10 @@ class HCI_Packet:
|
|
|
2300
2300
|
Abstract Base class for HCI packets
|
|
2301
2301
|
'''
|
|
2302
2302
|
|
|
2303
|
-
hci_packet_type:
|
|
2303
|
+
hci_packet_type: int
|
|
2304
2304
|
|
|
2305
|
-
@
|
|
2306
|
-
def from_bytes(packet: bytes) -> HCI_Packet:
|
|
2305
|
+
@classmethod
|
|
2306
|
+
def from_bytes(cls, packet: bytes) -> HCI_Packet:
|
|
2307
2307
|
packet_type = packet[0]
|
|
2308
2308
|
|
|
2309
2309
|
if packet_type == HCI_COMMAND_PACKET:
|
|
@@ -2323,7 +2323,7 @@ class HCI_Packet:
|
|
|
2323
2323
|
|
|
2324
2324
|
return HCI_CustomPacket(packet)
|
|
2325
2325
|
|
|
2326
|
-
def __init__(self, name):
|
|
2326
|
+
def __init__(self, name: str) -> None:
|
|
2327
2327
|
self.name = name
|
|
2328
2328
|
|
|
2329
2329
|
def __bytes__(self) -> bytes:
|
|
@@ -2335,7 +2335,7 @@ class HCI_Packet:
|
|
|
2335
2335
|
|
|
2336
2336
|
# -----------------------------------------------------------------------------
|
|
2337
2337
|
class HCI_CustomPacket(HCI_Packet):
|
|
2338
|
-
def __init__(self, payload):
|
|
2338
|
+
def __init__(self, payload: bytes) -> None:
|
|
2339
2339
|
super().__init__('HCI_CUSTOM_PACKET')
|
|
2340
2340
|
self.hci_packet_type = payload[0]
|
|
2341
2341
|
self.payload = payload
|
|
@@ -7452,6 +7452,7 @@ class HCI_Vendor_Event(HCI_Event):
|
|
|
7452
7452
|
|
|
7453
7453
|
|
|
7454
7454
|
# -----------------------------------------------------------------------------
|
|
7455
|
+
@dataclasses.dataclass
|
|
7455
7456
|
class HCI_AclDataPacket(HCI_Packet):
|
|
7456
7457
|
'''
|
|
7457
7458
|
See Bluetooth spec @ 5.4.2 HCI ACL Data Packets
|
|
@@ -7459,8 +7460,14 @@ class HCI_AclDataPacket(HCI_Packet):
|
|
|
7459
7460
|
|
|
7460
7461
|
hci_packet_type = HCI_ACL_DATA_PACKET
|
|
7461
7462
|
|
|
7462
|
-
|
|
7463
|
-
|
|
7463
|
+
connection_handle: int
|
|
7464
|
+
pb_flag: int
|
|
7465
|
+
bc_flag: int
|
|
7466
|
+
data_total_length: int
|
|
7467
|
+
data: bytes
|
|
7468
|
+
|
|
7469
|
+
@classmethod
|
|
7470
|
+
def from_bytes(cls, packet: bytes) -> HCI_AclDataPacket:
|
|
7464
7471
|
# Read the header
|
|
7465
7472
|
h, data_total_length = struct.unpack_from('<HH', packet, 1)
|
|
7466
7473
|
connection_handle = h & 0xFFF
|
|
@@ -7469,25 +7476,22 @@ class HCI_AclDataPacket(HCI_Packet):
|
|
|
7469
7476
|
data = packet[5:]
|
|
7470
7477
|
if len(data) != data_total_length:
|
|
7471
7478
|
raise InvalidPacketError('invalid packet length')
|
|
7472
|
-
return
|
|
7473
|
-
connection_handle,
|
|
7479
|
+
return cls(
|
|
7480
|
+
connection_handle=connection_handle,
|
|
7481
|
+
pb_flag=pb_flag,
|
|
7482
|
+
bc_flag=bc_flag,
|
|
7483
|
+
data_total_length=data_total_length,
|
|
7484
|
+
data=data,
|
|
7474
7485
|
)
|
|
7475
7486
|
|
|
7476
|
-
def __bytes__(self):
|
|
7487
|
+
def __bytes__(self) -> bytes:
|
|
7477
7488
|
h = (self.pb_flag << 12) | (self.bc_flag << 14) | self.connection_handle
|
|
7478
7489
|
return (
|
|
7479
7490
|
struct.pack('<BHH', HCI_ACL_DATA_PACKET, h, self.data_total_length)
|
|
7480
7491
|
+ self.data
|
|
7481
7492
|
)
|
|
7482
7493
|
|
|
7483
|
-
def
|
|
7484
|
-
self.connection_handle = connection_handle
|
|
7485
|
-
self.pb_flag = pb_flag
|
|
7486
|
-
self.bc_flag = bc_flag
|
|
7487
|
-
self.data_total_length = data_total_length
|
|
7488
|
-
self.data = data
|
|
7489
|
-
|
|
7490
|
-
def __str__(self):
|
|
7494
|
+
def __str__(self) -> str:
|
|
7491
7495
|
return (
|
|
7492
7496
|
f'{color("ACL", "blue")}: '
|
|
7493
7497
|
f'handle=0x{self.connection_handle:04x}, '
|
|
@@ -7498,6 +7502,7 @@ class HCI_AclDataPacket(HCI_Packet):
|
|
|
7498
7502
|
|
|
7499
7503
|
|
|
7500
7504
|
# -----------------------------------------------------------------------------
|
|
7505
|
+
@dataclasses.dataclass
|
|
7501
7506
|
class HCI_SynchronousDataPacket(HCI_Packet):
|
|
7502
7507
|
'''
|
|
7503
7508
|
See Bluetooth spec @ 5.4.3 HCI SCO Data Packets
|
|
@@ -7505,8 +7510,13 @@ class HCI_SynchronousDataPacket(HCI_Packet):
|
|
|
7505
7510
|
|
|
7506
7511
|
hci_packet_type = HCI_SYNCHRONOUS_DATA_PACKET
|
|
7507
7512
|
|
|
7508
|
-
|
|
7509
|
-
|
|
7513
|
+
connection_handle: int
|
|
7514
|
+
packet_status: int
|
|
7515
|
+
data_total_length: int
|
|
7516
|
+
data: bytes
|
|
7517
|
+
|
|
7518
|
+
@classmethod
|
|
7519
|
+
def from_bytes(cls, packet: bytes) -> HCI_SynchronousDataPacket:
|
|
7510
7520
|
# Read the header
|
|
7511
7521
|
h, data_total_length = struct.unpack_from('<HB', packet, 1)
|
|
7512
7522
|
connection_handle = h & 0xFFF
|
|
@@ -7516,8 +7526,11 @@ class HCI_SynchronousDataPacket(HCI_Packet):
|
|
|
7516
7526
|
raise InvalidPacketError(
|
|
7517
7527
|
f'invalid packet length {len(data)} != {data_total_length}'
|
|
7518
7528
|
)
|
|
7519
|
-
return
|
|
7520
|
-
connection_handle,
|
|
7529
|
+
return cls(
|
|
7530
|
+
connection_handle=connection_handle,
|
|
7531
|
+
packet_status=packet_status,
|
|
7532
|
+
data_total_length=data_total_length,
|
|
7533
|
+
data=data,
|
|
7521
7534
|
)
|
|
7522
7535
|
|
|
7523
7536
|
def __bytes__(self) -> bytes:
|
|
@@ -7527,18 +7540,6 @@ class HCI_SynchronousDataPacket(HCI_Packet):
|
|
|
7527
7540
|
+ self.data
|
|
7528
7541
|
)
|
|
7529
7542
|
|
|
7530
|
-
def __init__(
|
|
7531
|
-
self,
|
|
7532
|
-
connection_handle: int,
|
|
7533
|
-
packet_status: int,
|
|
7534
|
-
data_total_length: int,
|
|
7535
|
-
data: bytes,
|
|
7536
|
-
) -> None:
|
|
7537
|
-
self.connection_handle = connection_handle
|
|
7538
|
-
self.packet_status = packet_status
|
|
7539
|
-
self.data_total_length = data_total_length
|
|
7540
|
-
self.data = data
|
|
7541
|
-
|
|
7542
7543
|
def __str__(self) -> str:
|
|
7543
7544
|
return (
|
|
7544
7545
|
f'{color("SCO", "blue")}: '
|
|
@@ -7556,7 +7557,7 @@ class HCI_IsoDataPacket(HCI_Packet):
|
|
|
7556
7557
|
See Bluetooth spec @ 5.4.5 HCI ISO Data Packets
|
|
7557
7558
|
'''
|
|
7558
7559
|
|
|
7559
|
-
hci_packet_type
|
|
7560
|
+
hci_packet_type = HCI_ISO_DATA_PACKET
|
|
7560
7561
|
|
|
7561
7562
|
connection_handle: int
|
|
7562
7563
|
data_total_length: int
|
bumble/hid.py
CHANGED
|
@@ -312,11 +312,11 @@ class HID(ABC, utils.EventEmitter):
|
|
|
312
312
|
|
|
313
313
|
def send_pdu_on_ctrl(self, msg: bytes) -> None:
|
|
314
314
|
assert self.l2cap_ctrl_channel
|
|
315
|
-
self.l2cap_ctrl_channel.
|
|
315
|
+
self.l2cap_ctrl_channel.write(msg)
|
|
316
316
|
|
|
317
317
|
def send_pdu_on_intr(self, msg: bytes) -> None:
|
|
318
318
|
assert self.l2cap_intr_channel
|
|
319
|
-
self.l2cap_intr_channel.
|
|
319
|
+
self.l2cap_intr_channel.write(msg)
|
|
320
320
|
|
|
321
321
|
def send_data(self, data: bytes) -> None:
|
|
322
322
|
if self.role == HID.Role.HOST:
|
bumble/host.py
CHANGED
|
@@ -732,6 +732,16 @@ class Host(utils.EventEmitter):
|
|
|
732
732
|
)
|
|
733
733
|
packet_queue.enqueue(acl_packet, connection_handle)
|
|
734
734
|
|
|
735
|
+
def send_sco_sdu(self, connection_handle: int, sdu: bytes) -> None:
|
|
736
|
+
self.send_hci_packet(
|
|
737
|
+
hci.HCI_SynchronousDataPacket(
|
|
738
|
+
connection_handle=connection_handle,
|
|
739
|
+
packet_status=0,
|
|
740
|
+
data_total_length=len(sdu),
|
|
741
|
+
data=sdu,
|
|
742
|
+
)
|
|
743
|
+
)
|
|
744
|
+
|
|
735
745
|
def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
|
|
736
746
|
self.send_acl_sdu(connection_handle, bytes(L2CAP_PDU(cid, pdu)))
|
|
737
747
|
|
bumble/l2cap.py
CHANGED
|
@@ -1647,7 +1647,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1647
1647
|
self.connection_result = None
|
|
1648
1648
|
self.disconnection_result = None
|
|
1649
1649
|
self.drained = asyncio.Event()
|
|
1650
|
-
|
|
1650
|
+
# Core Specification Vol 3, Part G, 5.3.1 ATT_MTU
|
|
1651
|
+
# ATT_MTU shall be set to the minimum of the MTU field values of the two devices.
|
|
1652
|
+
self.att_mtu = min(mtu, peer_mtu)
|
|
1651
1653
|
|
|
1652
1654
|
self.drained.set()
|
|
1653
1655
|
|
bumble/pandora/l2cap.py
CHANGED
|
@@ -278,7 +278,7 @@ class L2CAPService(L2CAPServicer):
|
|
|
278
278
|
if not l2cap_channel:
|
|
279
279
|
return SendResponse(error=COMMAND_NOT_UNDERSTOOD)
|
|
280
280
|
if isinstance(l2cap_channel, ClassicChannel):
|
|
281
|
-
l2cap_channel.
|
|
281
|
+
l2cap_channel.write(request.data)
|
|
282
282
|
else:
|
|
283
283
|
l2cap_channel.write(request.data)
|
|
284
284
|
return SendResponse(success=empty_pb2.Empty())
|
|
@@ -16,35 +16,28 @@
|
|
|
16
16
|
# -----------------------------------------------------------------------------
|
|
17
17
|
# Imports
|
|
18
18
|
# -----------------------------------------------------------------------------
|
|
19
|
+
from collections.abc import Callable
|
|
19
20
|
|
|
20
|
-
from bumble
|
|
21
|
-
GATT_BATTERY_LEVEL_CHARACTERISTIC,
|
|
22
|
-
GATT_BATTERY_SERVICE,
|
|
23
|
-
Characteristic,
|
|
24
|
-
CharacteristicValue,
|
|
25
|
-
TemplateService,
|
|
26
|
-
)
|
|
27
|
-
from bumble.gatt_adapters import (
|
|
28
|
-
PackedCharacteristicAdapter,
|
|
29
|
-
PackedCharacteristicProxyAdapter,
|
|
30
|
-
)
|
|
31
|
-
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy
|
|
21
|
+
from bumble import device, gatt, gatt_adapters, gatt_client
|
|
32
22
|
|
|
33
23
|
|
|
34
24
|
# -----------------------------------------------------------------------------
|
|
35
|
-
class BatteryService(TemplateService):
|
|
36
|
-
UUID = GATT_BATTERY_SERVICE
|
|
25
|
+
class BatteryService(gatt.TemplateService):
|
|
26
|
+
UUID = gatt.GATT_BATTERY_SERVICE
|
|
37
27
|
BATTERY_LEVEL_FORMAT = 'B'
|
|
38
28
|
|
|
39
|
-
battery_level_characteristic: Characteristic[int]
|
|
40
|
-
|
|
41
|
-
def __init__(self, read_battery_level):
|
|
42
|
-
self.battery_level_characteristic = PackedCharacteristicAdapter(
|
|
43
|
-
Characteristic(
|
|
44
|
-
GATT_BATTERY_LEVEL_CHARACTERISTIC,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
29
|
+
battery_level_characteristic: gatt.Characteristic[int]
|
|
30
|
+
|
|
31
|
+
def __init__(self, read_battery_level: Callable[[device.Connection], int]) -> None:
|
|
32
|
+
self.battery_level_characteristic = gatt_adapters.PackedCharacteristicAdapter(
|
|
33
|
+
gatt.Characteristic(
|
|
34
|
+
gatt.GATT_BATTERY_LEVEL_CHARACTERISTIC,
|
|
35
|
+
properties=(
|
|
36
|
+
gatt.Characteristic.Properties.READ
|
|
37
|
+
| gatt.Characteristic.Properties.NOTIFY
|
|
38
|
+
),
|
|
39
|
+
permissions=gatt.Characteristic.READABLE,
|
|
40
|
+
value=gatt.CharacteristicValue(read=read_battery_level),
|
|
48
41
|
),
|
|
49
42
|
pack_format=BatteryService.BATTERY_LEVEL_FORMAT,
|
|
50
43
|
)
|
|
@@ -52,19 +45,17 @@ class BatteryService(TemplateService):
|
|
|
52
45
|
|
|
53
46
|
|
|
54
47
|
# -----------------------------------------------------------------------------
|
|
55
|
-
class BatteryServiceProxy(ProfileServiceProxy):
|
|
48
|
+
class BatteryServiceProxy(gatt_client.ProfileServiceProxy):
|
|
56
49
|
SERVICE_CLASS = BatteryService
|
|
57
50
|
|
|
58
|
-
battery_level: CharacteristicProxy[int]
|
|
51
|
+
battery_level: gatt_client.CharacteristicProxy[int]
|
|
59
52
|
|
|
60
|
-
def __init__(self, service_proxy):
|
|
53
|
+
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
61
54
|
self.service_proxy = service_proxy
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
else:
|
|
70
|
-
self.battery_level = None
|
|
56
|
+
self.battery_level = gatt_adapters.PackedCharacteristicProxyAdapter(
|
|
57
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
58
|
+
gatt.GATT_BATTERY_LEVEL_CHARACTERISTIC
|
|
59
|
+
),
|
|
60
|
+
pack_format=BatteryService.BATTERY_LEVEL_FORMAT,
|
|
61
|
+
)
|