bumble 0.0.170__py3-none-any.whl → 0.0.172__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 CHANGED
@@ -1,4 +1,8 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
- __version__ = version = '0.0.170' # type: str
4
- __version_tuple__ = version_tuple = (0, 0, 170) # type: tuple[int | str, ...]
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple
6
+
7
+ __version__ = version = '0.0.172' # type: str
8
+ __version_tuple__ = version_tuple = (0, 0, 172) # type: Tuple[int | str, ...]
bumble/apps/console.py CHANGED
@@ -1172,7 +1172,7 @@ class ScanResult:
1172
1172
  name = ''
1173
1173
 
1174
1174
  # Remove any '/P' qualifier suffix from the address string
1175
- address_str = str(self.address).replace('/P', '')
1175
+ address_str = self.address.to_string(with_type_qualifier=False)
1176
1176
 
1177
1177
  # RSSI bar
1178
1178
  bar_string = rssi_bar(self.rssi)
@@ -63,7 +63,8 @@ async def get_classic_info(host):
63
63
  if command_succeeded(response):
64
64
  print()
65
65
  print(
66
- color('Classic Address:', 'yellow'), response.return_parameters.bd_addr
66
+ color('Classic Address:', 'yellow'),
67
+ response.return_parameters.bd_addr.to_string(False),
67
68
  )
68
69
 
69
70
  if host.supports_command(HCI_READ_LOCAL_NAME_COMMAND):
@@ -195,7 +195,7 @@ class WebSocketOutput(QueuedOutput):
195
195
  except HCI_StatusError:
196
196
  pass
197
197
  peer_name = '' if connection.peer_name is None else connection.peer_name
198
- peer_address = str(connection.peer_address).replace('/P', '')
198
+ peer_address = connection.peer_address.to_string(False)
199
199
  await self.send_message(
200
200
  'connection',
201
201
  peer_address=peer_address,
@@ -376,7 +376,7 @@ class UiServer:
376
376
  if connection := self.speaker().connection:
377
377
  await self.send_message(
378
378
  'connection',
379
- peer_address=str(connection.peer_address).replace('/P', ''),
379
+ peer_address=connection.peer_address.to_string(False),
380
380
  peer_name=connection.peer_name,
381
381
  )
382
382
 
bumble/att.py CHANGED
@@ -23,13 +23,14 @@
23
23
  # Imports
24
24
  # -----------------------------------------------------------------------------
25
25
  from __future__ import annotations
26
+ import enum
26
27
  import functools
27
28
  import struct
28
29
  from pyee import EventEmitter
29
- from typing import Dict, Type, TYPE_CHECKING
30
+ from typing import Dict, Type, List, Protocol, Union, Optional, Any, TYPE_CHECKING
30
31
 
31
- from bumble.core import UUID, name_or_number, get_dict_key_by_value, ProtocolError
32
- from bumble.hci import HCI_Object, key_with_value, HCI_Constant
32
+ from bumble.core import UUID, name_or_number, ProtocolError
33
+ from bumble.hci import HCI_Object, key_with_value
33
34
  from bumble.colors import color
34
35
 
35
36
  if TYPE_CHECKING:
@@ -182,6 +183,7 @@ UUID_2_FIELD_SPEC = lambda x, y: UUID.parse_uuid_2(x, y) # noqa: E731
182
183
  # pylint: enable=line-too-long
183
184
  # pylint: disable=invalid-name
184
185
 
186
+
185
187
  # -----------------------------------------------------------------------------
186
188
  # Exceptions
187
189
  # -----------------------------------------------------------------------------
@@ -209,7 +211,7 @@ class ATT_PDU:
209
211
 
210
212
  pdu_classes: Dict[int, Type[ATT_PDU]] = {}
211
213
  op_code = 0
212
- name = None
214
+ name: str
213
215
 
214
216
  @staticmethod
215
217
  def from_bytes(pdu):
@@ -720,47 +722,67 @@ class ATT_Handle_Value_Confirmation(ATT_PDU):
720
722
 
721
723
 
722
724
  # -----------------------------------------------------------------------------
723
- class Attribute(EventEmitter):
724
- # Permission flags
725
- READABLE = 0x01
726
- WRITEABLE = 0x02
727
- READ_REQUIRES_ENCRYPTION = 0x04
728
- WRITE_REQUIRES_ENCRYPTION = 0x08
729
- READ_REQUIRES_AUTHENTICATION = 0x10
730
- WRITE_REQUIRES_AUTHENTICATION = 0x20
731
- READ_REQUIRES_AUTHORIZATION = 0x40
732
- WRITE_REQUIRES_AUTHORIZATION = 0x80
733
-
734
- PERMISSION_NAMES = {
735
- READABLE: 'READABLE',
736
- WRITEABLE: 'WRITEABLE',
737
- READ_REQUIRES_ENCRYPTION: 'READ_REQUIRES_ENCRYPTION',
738
- WRITE_REQUIRES_ENCRYPTION: 'WRITE_REQUIRES_ENCRYPTION',
739
- READ_REQUIRES_AUTHENTICATION: 'READ_REQUIRES_AUTHENTICATION',
740
- WRITE_REQUIRES_AUTHENTICATION: 'WRITE_REQUIRES_AUTHENTICATION',
741
- READ_REQUIRES_AUTHORIZATION: 'READ_REQUIRES_AUTHORIZATION',
742
- WRITE_REQUIRES_AUTHORIZATION: 'WRITE_REQUIRES_AUTHORIZATION',
743
- }
725
+ class ConnectionValue(Protocol):
726
+ def read(self, connection) -> bytes:
727
+ ...
744
728
 
745
- @staticmethod
746
- def string_to_permissions(permissions_str: str):
747
- try:
748
- return functools.reduce(
749
- lambda x, y: x | get_dict_key_by_value(Attribute.PERMISSION_NAMES, y),
750
- permissions_str.split(","),
751
- 0,
752
- )
753
- except TypeError as exc:
754
- raise TypeError(
755
- f"Attribute::permissions error:\nExpected a string containing any of the keys, separated by commas: {','.join(Attribute.PERMISSION_NAMES.values())}\nGot: {permissions_str}"
756
- ) from exc
729
+ def write(self, connection, value: bytes) -> None:
730
+ ...
757
731
 
758
- def __init__(self, attribute_type, permissions, value=b''):
732
+
733
+ # -----------------------------------------------------------------------------
734
+ class Attribute(EventEmitter):
735
+ class Permissions(enum.IntFlag):
736
+ READABLE = 0x01
737
+ WRITEABLE = 0x02
738
+ READ_REQUIRES_ENCRYPTION = 0x04
739
+ WRITE_REQUIRES_ENCRYPTION = 0x08
740
+ READ_REQUIRES_AUTHENTICATION = 0x10
741
+ WRITE_REQUIRES_AUTHENTICATION = 0x20
742
+ READ_REQUIRES_AUTHORIZATION = 0x40
743
+ WRITE_REQUIRES_AUTHORIZATION = 0x80
744
+
745
+ @classmethod
746
+ def from_string(cls, permissions_str: str) -> Attribute.Permissions:
747
+ try:
748
+ return functools.reduce(
749
+ lambda x, y: x | Attribute.Permissions[y],
750
+ permissions_str.replace('|', ',').split(","),
751
+ Attribute.Permissions(0),
752
+ )
753
+ except TypeError as exc:
754
+ # The check for `p.name is not None` here is needed because for InFlag
755
+ # enums, the .name property can be None, when the enum value is 0,
756
+ # so the type hint for .name is Optional[str].
757
+ enum_list: List[str] = [p.name for p in cls if p.name is not None]
758
+ enum_list_str = ",".join(enum_list)
759
+ raise TypeError(
760
+ f"Attribute::permissions error:\nExpected a string containing any of the keys, separated by commas: {enum_list_str }\nGot: {permissions_str}"
761
+ ) from exc
762
+
763
+ # Permission flags(legacy-use only)
764
+ READABLE = Permissions.READABLE
765
+ WRITEABLE = Permissions.WRITEABLE
766
+ READ_REQUIRES_ENCRYPTION = Permissions.READ_REQUIRES_ENCRYPTION
767
+ WRITE_REQUIRES_ENCRYPTION = Permissions.WRITE_REQUIRES_ENCRYPTION
768
+ READ_REQUIRES_AUTHENTICATION = Permissions.READ_REQUIRES_AUTHENTICATION
769
+ WRITE_REQUIRES_AUTHENTICATION = Permissions.WRITE_REQUIRES_AUTHENTICATION
770
+ READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION
771
+ WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION
772
+
773
+ value: Union[str, bytes, ConnectionValue]
774
+
775
+ def __init__(
776
+ self,
777
+ attribute_type: Union[str, bytes, UUID],
778
+ permissions: Union[str, Attribute.Permissions],
779
+ value: Union[str, bytes, ConnectionValue] = b'',
780
+ ) -> None:
759
781
  EventEmitter.__init__(self)
760
782
  self.handle = 0
761
783
  self.end_group_handle = 0
762
784
  if isinstance(permissions, str):
763
- self.permissions = self.string_to_permissions(permissions)
785
+ self.permissions = Attribute.Permissions.from_string(permissions)
764
786
  else:
765
787
  self.permissions = permissions
766
788
 
@@ -778,22 +800,26 @@ class Attribute(EventEmitter):
778
800
  else:
779
801
  self.value = value
780
802
 
781
- def encode_value(self, value):
803
+ def encode_value(self, value: Any) -> bytes:
782
804
  return value
783
805
 
784
- def decode_value(self, value_bytes):
806
+ def decode_value(self, value_bytes: bytes) -> Any:
785
807
  return value_bytes
786
808
 
787
- def read_value(self, connection: Connection):
809
+ def read_value(self, connection: Optional[Connection]) -> bytes:
788
810
  if (
789
- self.permissions & self.READ_REQUIRES_ENCRYPTION
790
- ) and not connection.encryption:
811
+ (self.permissions & self.READ_REQUIRES_ENCRYPTION)
812
+ and connection is not None
813
+ and not connection.encryption
814
+ ):
791
815
  raise ATT_Error(
792
816
  error_code=ATT_INSUFFICIENT_ENCRYPTION_ERROR, att_handle=self.handle
793
817
  )
794
818
  if (
795
- self.permissions & self.READ_REQUIRES_AUTHENTICATION
796
- ) and not connection.authenticated:
819
+ (self.permissions & self.READ_REQUIRES_AUTHENTICATION)
820
+ and connection is not None
821
+ and not connection.authenticated
822
+ ):
797
823
  raise ATT_Error(
798
824
  error_code=ATT_INSUFFICIENT_AUTHENTICATION_ERROR, att_handle=self.handle
799
825
  )
@@ -803,9 +829,9 @@ class Attribute(EventEmitter):
803
829
  error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
804
830
  )
805
831
 
806
- if read := getattr(self.value, 'read', None):
832
+ if hasattr(self.value, 'read'):
807
833
  try:
808
- value = read(connection) # pylint: disable=not-callable
834
+ value = self.value.read(connection)
809
835
  except ATT_Error as error:
810
836
  raise ATT_Error(
811
837
  error_code=error.error_code, att_handle=self.handle
@@ -815,7 +841,7 @@ class Attribute(EventEmitter):
815
841
 
816
842
  return self.encode_value(value)
817
843
 
818
- def write_value(self, connection: Connection, value_bytes):
844
+ def write_value(self, connection: Connection, value_bytes: bytes) -> None:
819
845
  if (
820
846
  self.permissions & self.WRITE_REQUIRES_ENCRYPTION
821
847
  ) and not connection.encryption:
@@ -836,9 +862,9 @@ class Attribute(EventEmitter):
836
862
 
837
863
  value = self.decode_value(value_bytes)
838
864
 
839
- if write := getattr(self.value, 'write', None):
865
+ if hasattr(self.value, 'write'):
840
866
  try:
841
- write(connection, value) # pylint: disable=not-callable
867
+ self.value.write(connection, value) # pylint: disable=not-callable
842
868
  except ATT_Error as error:
843
869
  raise ATT_Error(
844
870
  error_code=error.error_code, att_handle=self.handle
bumble/device.py CHANGED
@@ -1186,8 +1186,8 @@ class Device(CompositeEventEmitter):
1186
1186
  def create_l2cap_registrar(self, psm):
1187
1187
  return lambda handler: self.register_l2cap_server(psm, handler)
1188
1188
 
1189
- def register_l2cap_server(self, psm, server):
1190
- self.l2cap_channel_manager.register_server(psm, server)
1189
+ def register_l2cap_server(self, psm, server) -> int:
1190
+ return self.l2cap_channel_manager.register_server(psm, server)
1191
1191
 
1192
1192
  def register_l2cap_channel_server(
1193
1193
  self,
@@ -2758,7 +2758,9 @@ class Device(CompositeEventEmitter):
2758
2758
  self.abort_on(
2759
2759
  'flush',
2760
2760
  self.start_advertising(
2761
- advertising_type=self.advertising_type, auto_restart=True
2761
+ advertising_type=self.advertising_type,
2762
+ own_address_type=self.advertising_own_address_type,
2763
+ auto_restart=True,
2762
2764
  ),
2763
2765
  )
2764
2766
 
bumble/gatt.py CHANGED
@@ -28,7 +28,7 @@ import enum
28
28
  import functools
29
29
  import logging
30
30
  import struct
31
- from typing import Optional, Sequence, List
31
+ from typing import Optional, Sequence, Iterable, List, Union
32
32
 
33
33
  from .colors import color
34
34
  from .core import UUID, get_dict_key_by_value
@@ -187,7 +187,7 @@ GATT_CENTRAL_ADDRESS_RESOLUTION__CHARACTERISTIC = UUID.from_16_bi
187
187
  # -----------------------------------------------------------------------------
188
188
 
189
189
 
190
- def show_services(services):
190
+ def show_services(services: Iterable[Service]) -> None:
191
191
  for service in services:
192
192
  print(color(str(service), 'cyan'))
193
193
 
@@ -210,11 +210,11 @@ class Service(Attribute):
210
210
 
211
211
  def __init__(
212
212
  self,
213
- uuid,
213
+ uuid: Union[str, UUID],
214
214
  characteristics: List[Characteristic],
215
215
  primary=True,
216
216
  included_services: List[Service] = [],
217
- ):
217
+ ) -> None:
218
218
  # Convert the uuid to a UUID object if it isn't already
219
219
  if isinstance(uuid, str):
220
220
  uuid = UUID(uuid)
@@ -239,7 +239,7 @@ class Service(Attribute):
239
239
  """
240
240
  return None
241
241
 
242
- def __str__(self):
242
+ def __str__(self) -> str:
243
243
  return (
244
244
  f'Service(handle=0x{self.handle:04X}, '
245
245
  f'end=0x{self.end_group_handle:04X}, '
@@ -255,9 +255,11 @@ class TemplateService(Service):
255
255
  to expose their UUID as a class property
256
256
  '''
257
257
 
258
- UUID: Optional[UUID] = None
258
+ UUID: UUID
259
259
 
260
- def __init__(self, characteristics, primary=True):
260
+ def __init__(
261
+ self, characteristics: List[Characteristic], primary: bool = True
262
+ ) -> None:
261
263
  super().__init__(self.UUID, characteristics, primary)
262
264
 
263
265
 
@@ -269,7 +271,7 @@ class IncludedServiceDeclaration(Attribute):
269
271
 
270
272
  service: Service
271
273
 
272
- def __init__(self, service):
274
+ def __init__(self, service: Service) -> None:
273
275
  declaration_bytes = struct.pack(
274
276
  '<HH2s', service.handle, service.end_group_handle, service.uuid.to_bytes()
275
277
  )
@@ -278,7 +280,7 @@ class IncludedServiceDeclaration(Attribute):
278
280
  )
279
281
  self.service = service
280
282
 
281
- def __str__(self):
283
+ def __str__(self) -> str:
282
284
  return (
283
285
  f'IncludedServiceDefinition(handle=0x{self.handle:04X}, '
284
286
  f'group_starting_handle=0x{self.service.handle:04X}, '
@@ -326,7 +328,7 @@ class Characteristic(Attribute):
326
328
  f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by , or |: {enum_list_str}\nGot: {properties_str}"
327
329
  )
328
330
 
329
- def __str__(self):
331
+ def __str__(self) -> str:
330
332
  # NOTE: we override this method to offer a consistent result between python
331
333
  # versions: the value returned by IntFlag.__str__() changed in version 11.
332
334
  return '|'.join(
@@ -348,10 +350,10 @@ class Characteristic(Attribute):
348
350
 
349
351
  def __init__(
350
352
  self,
351
- uuid,
353
+ uuid: Union[str, bytes, UUID],
352
354
  properties: Characteristic.Properties,
353
- permissions,
354
- value=b'',
355
+ permissions: Union[str, Attribute.Permissions],
356
+ value: Union[str, bytes, CharacteristicValue] = b'',
355
357
  descriptors: Sequence[Descriptor] = (),
356
358
  ):
357
359
  super().__init__(uuid, permissions, value)
@@ -369,7 +371,7 @@ class Characteristic(Attribute):
369
371
  def has_properties(self, properties: Characteristic.Properties) -> bool:
370
372
  return self.properties & properties == properties
371
373
 
372
- def __str__(self):
374
+ def __str__(self) -> str:
373
375
  return (
374
376
  f'Characteristic(handle=0x{self.handle:04X}, '
375
377
  f'end=0x{self.end_group_handle:04X}, '
@@ -386,7 +388,7 @@ class CharacteristicDeclaration(Attribute):
386
388
 
387
389
  characteristic: Characteristic
388
390
 
389
- def __init__(self, characteristic, value_handle):
391
+ def __init__(self, characteristic: Characteristic, value_handle: int) -> None:
390
392
  declaration_bytes = (
391
393
  struct.pack('<BH', characteristic.properties, value_handle)
392
394
  + characteristic.uuid.to_pdu_bytes()
@@ -397,7 +399,7 @@ class CharacteristicDeclaration(Attribute):
397
399
  self.value_handle = value_handle
398
400
  self.characteristic = characteristic
399
401
 
400
- def __str__(self):
402
+ def __str__(self) -> str:
401
403
  return (
402
404
  f'CharacteristicDeclaration(handle=0x{self.handle:04X}, '
403
405
  f'value_handle=0x{self.value_handle:04X}, '
@@ -520,7 +522,7 @@ class CharacteristicAdapter:
520
522
 
521
523
  return self.wrapped_characteristic.unsubscribe(subscriber)
522
524
 
523
- def __str__(self):
525
+ def __str__(self) -> str:
524
526
  wrapped = str(self.wrapped_characteristic)
525
527
  return f'{self.__class__.__name__}({wrapped})'
526
528
 
@@ -600,10 +602,10 @@ class UTF8CharacteristicAdapter(CharacteristicAdapter):
600
602
  Adapter that converts strings to/from bytes using UTF-8 encoding
601
603
  '''
602
604
 
603
- def encode_value(self, value):
605
+ def encode_value(self, value: str) -> bytes:
604
606
  return value.encode('utf-8')
605
607
 
606
- def decode_value(self, value):
608
+ def decode_value(self, value: bytes) -> str:
607
609
  return value.decode('utf-8')
608
610
 
609
611
 
@@ -613,7 +615,7 @@ class Descriptor(Attribute):
613
615
  See Vol 3, Part G - 3.3.3 Characteristic Descriptor Declarations
614
616
  '''
615
617
 
616
- def __str__(self):
618
+ def __str__(self) -> str:
617
619
  return (
618
620
  f'Descriptor(handle=0x{self.handle:04X}, '
619
621
  f'type={self.type}, '