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/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: ClassVar[int]
2303
+ hci_packet_type: int
2304
2304
 
2305
- @staticmethod
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
- @staticmethod
7463
- def from_bytes(packet: bytes) -> HCI_AclDataPacket:
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 HCI_AclDataPacket(
7473
- connection_handle, pb_flag, bc_flag, data_total_length, data
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 __init__(self, connection_handle, pb_flag, bc_flag, data_total_length, data):
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
- @staticmethod
7509
- def from_bytes(packet: bytes) -> HCI_SynchronousDataPacket:
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 HCI_SynchronousDataPacket(
7520
- connection_handle, packet_status, data_total_length, data
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: ClassVar[int] = HCI_ISO_DATA_PACKET
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.send_pdu(msg)
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.send_pdu(msg)
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 = 0
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] & 0b001111111),
320
- segmentation_and_reassembly=(data[1] >> 6) & 0b11,
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.segmentation_and_reassembly << 6),
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, req_seq=self.req_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
- _expected_ack_seq: int = 0
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.channel.send_pdu(
976
- SupervisoryEnhancedControlField(
977
- supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
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) > self.peer_mps:
991
- raise InvalidArgumentError(
992
- f'SDU size({len(sdu)}) exceeds channel MPS {self.peer_mps}'
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
- pdu = self._PendingPdu(payload=sdu, tx_seq=self._get_next_tx_seq())
995
- self._pending_pdus.append(pdu)
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._next_seq_num:
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._next_seq_num = (self._next_seq_num + 1) % self.MAX_SEQ_NUM
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
- ack_frame = SupervisoryEnhancedControlField(
1009
- supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
1010
- req_seq=self._next_seq_num,
1011
- )
1012
- self.channel.send_pdu(ack_frame)
1013
- self.channel.on_sdu(pdu[2:])
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.channel.send_pdu(
1026
- SupervisoryEnhancedControlField(
1027
- supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
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 or self._monitor_handle:
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
- for pdu in self._pending_pdus:
1041
- if self._num_unacked_frames >= self.peer_tx_window_size:
1042
- return
1043
- self._send_pdu(pdu)
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 _send_pdu(self, pdu: _PendingPdu) -> None:
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._num_frames_between(self._expected_ack_seq, new_seq)
1060
- if num_frames_acked > self._num_unacked_frames:
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._num_unacked_frames,
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._pending_pdus[:num_frames_acked]
1072
- self._expected_ack_seq = new_seq
1126
+ del self._tx_window[:num_frames_acked]
1127
+ self._last_acked_tx_seq = new_seq
1073
1128
  if (
1074
- self._expected_ack_seq == self._next_tx_seq
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
- self.att_mtu = 0 # Filled by GATT client or server later.
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.send_pdu(request.data)
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.gatt import (
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
- Characteristic.Properties.READ | Characteristic.Properties.NOTIFY,
46
- Characteristic.READABLE,
47
- CharacteristicValue(read=read_battery_level),
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] | None
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
- if characteristics := service_proxy.get_characteristics_by_uuid(
64
- GATT_BATTERY_LEVEL_CHARACTERISTIC
65
- ):
66
- self.battery_level = PackedCharacteristicProxyAdapter(
67
- characteristics[0], pack_format=BatteryService.BATTERY_LEVEL_FORMAT
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
+ )