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/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
@@ -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
- 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)
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.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
+ )