bumble 0.0.221__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 +120 -63
- 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.221.dist-info → bumble-0.0.223.dist-info}/METADATA +1 -1
- {bumble-0.0.221.dist-info → bumble-0.0.223.dist-info}/RECORD +25 -25
- {bumble-0.0.221.dist-info → bumble-0.0.223.dist-info}/WHEEL +0 -0
- {bumble-0.0.221.dist-info → bumble-0.0.223.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.221.dist-info → bumble-0.0.223.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.221.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
|
@@ -20,6 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
import asyncio
|
|
21
21
|
import dataclasses
|
|
22
22
|
import enum
|
|
23
|
+
import itertools
|
|
23
24
|
import logging
|
|
24
25
|
import struct
|
|
25
26
|
from collections import deque
|
|
@@ -302,11 +303,9 @@ class EnhancedControlField(ControlField):
|
|
|
302
303
|
|
|
303
304
|
@dataclasses.dataclass
|
|
304
305
|
class InformationEnhancedControlField(EnhancedControlField):
|
|
305
|
-
tx_seq: int
|
|
306
|
+
tx_seq: int
|
|
307
|
+
sar: int
|
|
306
308
|
req_seq: int = 0
|
|
307
|
-
segmentation_and_reassembly: int = (
|
|
308
|
-
EnhancedControlField.SegmentationAndReassembly.UNSEGMENTED
|
|
309
|
-
)
|
|
310
309
|
final: int = 1
|
|
311
310
|
|
|
312
311
|
frame_type = EnhancedControlField.FieldType.I_FRAME
|
|
@@ -316,15 +315,15 @@ class InformationEnhancedControlField(EnhancedControlField):
|
|
|
316
315
|
return cls(
|
|
317
316
|
tx_seq=(data[0] >> 1) & 0b0111111,
|
|
318
317
|
final=(data[0] >> 7) & 0b1,
|
|
319
|
-
req_seq=(data[1] &
|
|
320
|
-
|
|
318
|
+
req_seq=(data[1] & 0b00111111),
|
|
319
|
+
sar=(data[1] >> 6) & 0b11,
|
|
321
320
|
)
|
|
322
321
|
|
|
323
322
|
def __bytes__(self) -> bytes:
|
|
324
323
|
return bytes(
|
|
325
324
|
[
|
|
326
325
|
self.frame_type | (self.tx_seq << 1) | (self.final << 7),
|
|
327
|
-
self.req_seq | (self.
|
|
326
|
+
self.req_seq | (self.sar << 6),
|
|
328
327
|
]
|
|
329
328
|
)
|
|
330
329
|
|
|
@@ -889,27 +888,38 @@ class EnhancedRetransmissionProcessor(Processor):
|
|
|
889
888
|
class _PendingPdu:
|
|
890
889
|
payload: bytes
|
|
891
890
|
tx_seq: int
|
|
891
|
+
sar: InformationEnhancedControlField.SegmentationAndReassembly
|
|
892
|
+
sdu_length: int = 0
|
|
892
893
|
req_seq: int = 0
|
|
893
894
|
|
|
894
895
|
def __bytes__(self) -> bytes:
|
|
895
896
|
return (
|
|
896
897
|
bytes(
|
|
897
898
|
InformationEnhancedControlField(
|
|
898
|
-
tx_seq=self.tx_seq,
|
|
899
|
+
tx_seq=self.tx_seq,
|
|
900
|
+
req_seq=self.req_seq,
|
|
901
|
+
sar=self.sar,
|
|
899
902
|
)
|
|
900
903
|
)
|
|
904
|
+
+ (
|
|
905
|
+
struct.pack('<H', self.sdu_length)
|
|
906
|
+
if self.sar
|
|
907
|
+
== InformationEnhancedControlField.SegmentationAndReassembly.START
|
|
908
|
+
else b''
|
|
909
|
+
)
|
|
901
910
|
+ self.payload
|
|
902
911
|
)
|
|
903
912
|
|
|
904
|
-
|
|
913
|
+
_last_acked_tx_seq: int = 0
|
|
914
|
+
_last_acked_rx_seq: int = 0
|
|
905
915
|
_next_tx_seq: int = 0
|
|
906
|
-
_last_tx_seq: int = 0
|
|
907
916
|
_req_seq_num: int = 0
|
|
908
|
-
_next_seq_num: int = 0
|
|
909
917
|
_remote_is_busy: bool = False
|
|
918
|
+
_in_sdu: bytes = b''
|
|
910
919
|
|
|
911
920
|
_num_receiver_ready_polls_sent: int = 0
|
|
912
921
|
_pending_pdus: list[_PendingPdu]
|
|
922
|
+
_tx_window: list[_PendingPdu]
|
|
913
923
|
_monitor_handle: asyncio.TimerHandle | None = None
|
|
914
924
|
_receiver_ready_poll_handle: asyncio.TimerHandle | None = None
|
|
915
925
|
|
|
@@ -917,12 +927,6 @@ class EnhancedRetransmissionProcessor(Processor):
|
|
|
917
927
|
monitor_timeout: float
|
|
918
928
|
retransmission_timeout: float
|
|
919
929
|
|
|
920
|
-
@classmethod
|
|
921
|
-
def _num_frames_between(cls, low: int, high: int) -> int:
|
|
922
|
-
if high < low:
|
|
923
|
-
high += cls.MAX_SEQ_NUM
|
|
924
|
-
return high - low
|
|
925
|
-
|
|
926
930
|
def __init__(
|
|
927
931
|
self,
|
|
928
932
|
channel: ClassicChannel,
|
|
@@ -935,6 +939,7 @@ class EnhancedRetransmissionProcessor(Processor):
|
|
|
935
939
|
self.peer_mps = peer_mps
|
|
936
940
|
self.peer_tx_window_size = peer_tx_window_size
|
|
937
941
|
self._pending_pdus = []
|
|
942
|
+
self._tx_window = []
|
|
938
943
|
self.monitor_timeout = spec.monitor_timeout
|
|
939
944
|
self.channel = channel
|
|
940
945
|
self.retransmission_timeout = spec.retransmission_timeout
|
|
@@ -972,12 +977,9 @@ class EnhancedRetransmissionProcessor(Processor):
|
|
|
972
977
|
|
|
973
978
|
def _send_receiver_ready_poll(self) -> None:
|
|
974
979
|
self._num_receiver_ready_polls_sent += 1
|
|
975
|
-
self.
|
|
976
|
-
SupervisoryEnhancedControlField
|
|
977
|
-
|
|
978
|
-
final=1,
|
|
979
|
-
req_seq=self._next_seq_num,
|
|
980
|
-
)
|
|
980
|
+
self._send_s_frame(
|
|
981
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
982
|
+
final=1,
|
|
981
983
|
)
|
|
982
984
|
|
|
983
985
|
def _get_next_tx_seq(self) -> int:
|
|
@@ -987,12 +989,35 @@ class EnhancedRetransmissionProcessor(Processor):
|
|
|
987
989
|
|
|
988
990
|
@override
|
|
989
991
|
def send_sdu(self, sdu: bytes) -> None:
|
|
990
|
-
if len(sdu)
|
|
991
|
-
|
|
992
|
-
|
|
992
|
+
if len(sdu) <= self.peer_mps:
|
|
993
|
+
pdu = self._PendingPdu(
|
|
994
|
+
payload=sdu,
|
|
995
|
+
tx_seq=self._get_next_tx_seq(),
|
|
996
|
+
req_seq=self._req_seq_num,
|
|
997
|
+
sar=InformationEnhancedControlField.SegmentationAndReassembly.UNSEGMENTED,
|
|
993
998
|
)
|
|
994
|
-
|
|
995
|
-
|
|
999
|
+
self._pending_pdus.append(pdu)
|
|
1000
|
+
else:
|
|
1001
|
+
for offset in range(0, len(sdu), self.peer_mps):
|
|
1002
|
+
payload = sdu[offset : offset + self.peer_mps]
|
|
1003
|
+
if offset == 0:
|
|
1004
|
+
sar = (
|
|
1005
|
+
InformationEnhancedControlField.SegmentationAndReassembly.START
|
|
1006
|
+
)
|
|
1007
|
+
elif offset + len(payload) >= len(sdu):
|
|
1008
|
+
sar = InformationEnhancedControlField.SegmentationAndReassembly.END
|
|
1009
|
+
else:
|
|
1010
|
+
sar = (
|
|
1011
|
+
InformationEnhancedControlField.SegmentationAndReassembly.CONTINUATION
|
|
1012
|
+
)
|
|
1013
|
+
pdu = self._PendingPdu(
|
|
1014
|
+
payload=payload,
|
|
1015
|
+
tx_seq=self._get_next_tx_seq(),
|
|
1016
|
+
req_seq=self._req_seq_num,
|
|
1017
|
+
sar=sar,
|
|
1018
|
+
sdu_length=len(sdu),
|
|
1019
|
+
)
|
|
1020
|
+
self._pending_pdus.append(pdu)
|
|
996
1021
|
self._process_output()
|
|
997
1022
|
|
|
998
1023
|
@override
|
|
@@ -1000,17 +1025,37 @@ class EnhancedRetransmissionProcessor(Processor):
|
|
|
1000
1025
|
control_field = EnhancedControlField.from_bytes(pdu)
|
|
1001
1026
|
self._update_ack_seq(control_field.req_seq, control_field.final != 0)
|
|
1002
1027
|
if isinstance(control_field, InformationEnhancedControlField):
|
|
1003
|
-
if control_field.tx_seq != self.
|
|
1028
|
+
if control_field.tx_seq != self._req_seq_num:
|
|
1029
|
+
logger.error(
|
|
1030
|
+
"tx_seq != self._req_seq_num, tx_seq: %d, self._req_seq_num: %d",
|
|
1031
|
+
control_field.tx_seq,
|
|
1032
|
+
self._req_seq_num,
|
|
1033
|
+
)
|
|
1004
1034
|
return
|
|
1005
|
-
self.
|
|
1006
|
-
self._req_seq_num = self._next_seq_num
|
|
1035
|
+
self._req_seq_num = (control_field.tx_seq + 1) % self.MAX_SEQ_NUM
|
|
1007
1036
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
)
|
|
1012
|
-
|
|
1013
|
-
|
|
1037
|
+
if (
|
|
1038
|
+
control_field.sar
|
|
1039
|
+
== InformationEnhancedControlField.SegmentationAndReassembly.START
|
|
1040
|
+
):
|
|
1041
|
+
# Drop Control Field(2) + SDU Length(2)
|
|
1042
|
+
self._in_sdu += pdu[4:]
|
|
1043
|
+
else:
|
|
1044
|
+
# Drop Control Field(2)
|
|
1045
|
+
self._in_sdu += pdu[2:]
|
|
1046
|
+
if control_field.sar in (
|
|
1047
|
+
InformationEnhancedControlField.SegmentationAndReassembly.END,
|
|
1048
|
+
InformationEnhancedControlField.SegmentationAndReassembly.UNSEGMENTED,
|
|
1049
|
+
):
|
|
1050
|
+
self.channel.on_sdu(self._in_sdu)
|
|
1051
|
+
self._in_sdu = b''
|
|
1052
|
+
|
|
1053
|
+
# If sink doesn't trigger any I-frame, ack this frame.
|
|
1054
|
+
if self._req_seq_num != self._last_acked_rx_seq:
|
|
1055
|
+
self._send_s_frame(
|
|
1056
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
1057
|
+
final=0,
|
|
1058
|
+
)
|
|
1014
1059
|
elif isinstance(control_field, SupervisoryEnhancedControlField):
|
|
1015
1060
|
self._remote_is_busy = (
|
|
1016
1061
|
control_field.supervision_function
|
|
@@ -1022,56 +1067,66 @@ class EnhancedRetransmissionProcessor(Processor):
|
|
|
1022
1067
|
SupervisoryEnhancedControlField.SupervisoryFunction.RNR,
|
|
1023
1068
|
):
|
|
1024
1069
|
if control_field.poll:
|
|
1025
|
-
self.
|
|
1026
|
-
SupervisoryEnhancedControlField
|
|
1027
|
-
|
|
1028
|
-
final=1,
|
|
1029
|
-
req_seq=self._next_seq_num,
|
|
1030
|
-
)
|
|
1070
|
+
self._send_s_frame(
|
|
1071
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
1072
|
+
final=1,
|
|
1031
1073
|
)
|
|
1032
1074
|
else:
|
|
1033
1075
|
# TODO: Handle Retransmission.
|
|
1034
1076
|
pass
|
|
1035
1077
|
|
|
1036
1078
|
def _process_output(self) -> None:
|
|
1037
|
-
if self._remote_is_busy
|
|
1079
|
+
if self._remote_is_busy:
|
|
1080
|
+
logger.debug("Remote is busy")
|
|
1081
|
+
return
|
|
1082
|
+
if self._monitor_handle:
|
|
1083
|
+
logger.debug("Monitor handle is not None")
|
|
1038
1084
|
return
|
|
1039
1085
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
self._last_tx_seq = pdu.tx_seq
|
|
1045
|
-
|
|
1046
|
-
@property
|
|
1047
|
-
def _num_unacked_frames(self) -> int:
|
|
1048
|
-
if not self._pending_pdus:
|
|
1049
|
-
return 0
|
|
1050
|
-
return self._num_frames_between(self._expected_ack_seq, self._last_tx_seq + 1)
|
|
1086
|
+
pdu_to_send = self.peer_tx_window_size - len(self._tx_window)
|
|
1087
|
+
for pdu in itertools.islice(self._pending_pdus, pdu_to_send):
|
|
1088
|
+
self._send_i_frame(pdu)
|
|
1089
|
+
self._pending_pdus = self._pending_pdus[pdu_to_send:]
|
|
1051
1090
|
|
|
1052
|
-
def
|
|
1091
|
+
def _send_i_frame(self, pdu: _PendingPdu) -> None:
|
|
1053
1092
|
pdu.req_seq = self._req_seq_num
|
|
1054
1093
|
|
|
1055
1094
|
self._start_receiver_ready_poll()
|
|
1095
|
+
self._tx_window.append(pdu)
|
|
1056
1096
|
self.channel.send_pdu(bytes(pdu))
|
|
1097
|
+
self._last_acked_rx_seq = self._req_seq_num
|
|
1098
|
+
|
|
1099
|
+
def _send_s_frame(
|
|
1100
|
+
self,
|
|
1101
|
+
supervision_function: SupervisoryEnhancedControlField.SupervisoryFunction,
|
|
1102
|
+
final: int,
|
|
1103
|
+
) -> None:
|
|
1104
|
+
self.channel.send_pdu(
|
|
1105
|
+
SupervisoryEnhancedControlField(
|
|
1106
|
+
supervision_function=supervision_function,
|
|
1107
|
+
final=final,
|
|
1108
|
+
req_seq=self._req_seq_num,
|
|
1109
|
+
)
|
|
1110
|
+
)
|
|
1111
|
+
self._last_acked_rx_seq = self._req_seq_num
|
|
1057
1112
|
|
|
1058
1113
|
def _update_ack_seq(self, new_seq: int, is_poll_response: bool) -> None:
|
|
1059
|
-
num_frames_acked = self.
|
|
1060
|
-
if num_frames_acked > self.
|
|
1114
|
+
num_frames_acked = (new_seq - self._last_acked_tx_seq) % self.MAX_SEQ_NUM
|
|
1115
|
+
if num_frames_acked > len(self._tx_window):
|
|
1061
1116
|
logger.error(
|
|
1062
1117
|
"Received acknowledgment for %d frames but only %d frames are pending",
|
|
1063
1118
|
num_frames_acked,
|
|
1064
|
-
self.
|
|
1119
|
+
len(self._tx_window),
|
|
1065
1120
|
)
|
|
1066
1121
|
return
|
|
1067
1122
|
if is_poll_response and self._monitor_handle:
|
|
1068
1123
|
self._monitor_handle.cancel()
|
|
1069
1124
|
self._monitor_handle = None
|
|
1070
1125
|
|
|
1071
|
-
del self.
|
|
1072
|
-
self.
|
|
1126
|
+
del self._tx_window[:num_frames_acked]
|
|
1127
|
+
self._last_acked_tx_seq = new_seq
|
|
1073
1128
|
if (
|
|
1074
|
-
self.
|
|
1129
|
+
self._last_acked_tx_seq == self._next_tx_seq
|
|
1075
1130
|
and self._receiver_ready_poll_handle
|
|
1076
1131
|
):
|
|
1077
1132
|
self._receiver_ready_poll_handle.cancel()
|
|
@@ -1592,7 +1647,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1592
1647
|
self.connection_result = None
|
|
1593
1648
|
self.disconnection_result = None
|
|
1594
1649
|
self.drained = asyncio.Event()
|
|
1595
|
-
|
|
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)
|
|
1596
1653
|
|
|
1597
1654
|
self.drained.set()
|
|
1598
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
|
+
)
|