bumble 0.0.207__py3-none-any.whl → 0.0.208__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,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '0.0.207'
16
- __version_tuple__ = version_tuple = (0, 0, 207)
20
+ __version__ = version = '0.0.208'
21
+ __version_tuple__ = version_tuple = (0, 0, 208)
bumble/apps/auracast.py CHANGED
@@ -27,7 +27,6 @@ import logging
27
27
  import os
28
28
  import struct
29
29
  from typing import (
30
- cast,
31
30
  Any,
32
31
  AsyncGenerator,
33
32
  Coroutine,
@@ -127,11 +126,9 @@ class BroadcastScanner(pyee.EventEmitter):
127
126
  def update(self, advertisement: bumble.device.Advertisement) -> None:
128
127
  self.rssi = advertisement.rssi
129
128
  for service_data in advertisement.data.get_all(
130
- core.AdvertisingData.SERVICE_DATA
129
+ core.AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID
131
130
  ):
132
- assert isinstance(service_data, tuple)
133
131
  service_uuid, data = service_data
134
- assert isinstance(data, bytes)
135
132
 
136
133
  if service_uuid == gatt.GATT_PUBLIC_BROADCAST_ANNOUNCEMENT_SERVICE:
137
134
  self.public_broadcast_announcement = (
@@ -145,16 +142,14 @@ class BroadcastScanner(pyee.EventEmitter):
145
142
  )
146
143
  continue
147
144
 
148
- self.appearance = advertisement.data.get( # type: ignore[assignment]
149
- core.AdvertisingData.APPEARANCE
145
+ self.appearance = advertisement.data.get(
146
+ core.AdvertisingData.Type.APPEARANCE
150
147
  )
151
148
 
152
149
  if manufacturer_data := advertisement.data.get(
153
- core.AdvertisingData.MANUFACTURER_SPECIFIC_DATA
150
+ core.AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA
154
151
  ):
155
- assert isinstance(manufacturer_data, tuple)
156
- company_id = cast(int, manufacturer_data[0])
157
- data = cast(bytes, manufacturer_data[1])
152
+ company_id, data = manufacturer_data
158
153
  self.manufacturer_data = (
159
154
  company_ids.COMPANY_IDENTIFIERS.get(
160
155
  company_id, f'0x{company_id:04X}'
@@ -271,11 +266,9 @@ class BroadcastScanner(pyee.EventEmitter):
271
266
  return
272
267
 
273
268
  for service_data in advertisement.data.get_all(
274
- core.AdvertisingData.SERVICE_DATA
269
+ core.AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID
275
270
  ):
276
- assert isinstance(service_data, tuple)
277
271
  service_uuid, data = service_data
278
- assert isinstance(data, bytes)
279
272
 
280
273
  if service_uuid == gatt.GATT_BASIC_AUDIO_ANNOUNCEMENT_SERVICE:
281
274
  self.basic_audio_announcement = (
@@ -316,24 +309,23 @@ class BroadcastScanner(pyee.EventEmitter):
316
309
  def on_advertisement(self, advertisement: bumble.device.Advertisement) -> None:
317
310
  if not (
318
311
  ads := advertisement.data.get_all(
319
- core.AdvertisingData.SERVICE_DATA_16_BIT_UUID
312
+ core.AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID
320
313
  )
321
314
  ) or not (
322
315
  broadcast_audio_announcement := next(
323
316
  (
324
317
  ad
325
318
  for ad in ads
326
- if isinstance(ad, tuple)
327
- and ad[0] == gatt.GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE
319
+ if ad[0] == gatt.GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE
328
320
  ),
329
321
  None,
330
322
  )
331
323
  ):
332
324
  return
333
325
 
334
- broadcast_name = advertisement.data.get(core.AdvertisingData.BROADCAST_NAME)
335
- assert isinstance(broadcast_name, str) or broadcast_name is None
336
- assert isinstance(broadcast_audio_announcement[1], bytes)
326
+ broadcast_name = advertisement.data.get_all(
327
+ core.AdvertisingData.Type.BROADCAST_NAME
328
+ )
337
329
 
338
330
  if broadcast := self.broadcasts.get(advertisement.address):
339
331
  broadcast.update(advertisement)
@@ -341,7 +333,7 @@ class BroadcastScanner(pyee.EventEmitter):
341
333
 
342
334
  bumble.utils.AsyncRunner.spawn(
343
335
  self.on_new_broadcast(
344
- broadcast_name,
336
+ broadcast_name[0] if broadcast_name else None,
345
337
  advertisement,
346
338
  bap.BroadcastAudioAnnouncement.from_bytes(
347
339
  broadcast_audio_announcement[1]
@@ -522,14 +514,19 @@ async def run_assist(
522
514
  return
523
515
 
524
516
  # Subscribe to and read the broadcast receive state characteristics
517
+ def on_broadcast_receive_state_update(
518
+ value: bass.BroadcastReceiveState, index: int
519
+ ) -> None:
520
+ print(
521
+ f"{color(f'Broadcast Receive State Update [{index}]:', 'green')} {value}"
522
+ )
523
+
525
524
  for i, broadcast_receive_state in enumerate(
526
525
  bass_client.broadcast_receive_states
527
526
  ):
528
527
  try:
529
528
  await broadcast_receive_state.subscribe(
530
- lambda value, i=i: print(
531
- f"{color(f'Broadcast Receive State Update [{i}]:', 'green')} {value}"
532
- )
529
+ functools.partial(on_broadcast_receive_state_update, index=i)
533
530
  )
534
531
  except core.ProtocolError as error:
535
532
  print(
@@ -790,16 +787,8 @@ async def run_receive(
790
787
  for i, bis_link in enumerate(big_sync.bis_links):
791
788
  print(f'Setup ISO for BIS {bis_link.handle}')
792
789
  bis_link.sink = functools.partial(sink, lc3_queues[i])
793
- await device.send_command(
794
- hci.HCI_LE_Setup_ISO_Data_Path_Command(
795
- connection_handle=bis_link.handle,
796
- data_path_direction=hci.HCI_LE_Setup_ISO_Data_Path_Command.Direction.CONTROLLER_TO_HOST,
797
- data_path_id=0,
798
- codec_id=hci.CodingFormat(codec_id=hci.CodecID.TRANSPARENT),
799
- controller_delay=0,
800
- codec_configuration=b'',
801
- ),
802
- check_result=True,
790
+ await bis_link.setup_data_path(
791
+ direction=bis_link.Direction.CONTROLLER_TO_HOST
803
792
  )
804
793
 
805
794
  terminated = asyncio.Event()
@@ -959,10 +948,15 @@ async def run_transmit(
959
948
  ),
960
949
  ),
961
950
  )
951
+ for bis_link in big.bis_links:
952
+ print(f'Setup ISO for BIS {bis_link.handle}')
953
+ await bis_link.setup_data_path(
954
+ direction=bis_link.Direction.HOST_TO_CONTROLLER
955
+ )
962
956
 
963
957
  iso_queues = [
964
- bumble.device.IsoPacketStream(big.bis_links[0], 64),
965
- bumble.device.IsoPacketStream(big.bis_links[1], 64),
958
+ bumble.device.IsoPacketStream(bis_link, 64)
959
+ for bis_link in big.bis_links
966
960
  ]
967
961
 
968
962
  def on_flow():
bumble/apps/bench.py CHANGED
@@ -104,15 +104,16 @@ def le_phy_name(phy_id):
104
104
  )
105
105
 
106
106
 
107
+ def print_connection_phy(phy):
108
+ logging.info(
109
+ color('@@@ PHY: ', 'yellow') + f'TX:{le_phy_name(phy.tx_phy)}/'
110
+ f'RX:{le_phy_name(phy.rx_phy)}'
111
+ )
112
+
113
+
107
114
  def print_connection(connection):
108
115
  params = []
109
116
  if connection.transport == BT_LE_TRANSPORT:
110
- params.append(
111
- 'PHY='
112
- f'TX:{le_phy_name(connection.phy.tx_phy)}/'
113
- f'RX:{le_phy_name(connection.phy.rx_phy)}'
114
- )
115
-
116
117
  params.append(
117
118
  'DL=('
118
119
  f'TX:{connection.data_length[0]}/{connection.data_length[1]},'
@@ -1288,6 +1289,8 @@ class Central(Connection.Listener):
1288
1289
  logging.info(color('### Connected', 'cyan'))
1289
1290
  self.connection.listener = self
1290
1291
  print_connection(self.connection)
1292
+ phy = await self.connection.get_phy()
1293
+ print_connection_phy(phy)
1291
1294
 
1292
1295
  # Switch roles if needed.
1293
1296
  if self.role_switch:
@@ -1345,8 +1348,8 @@ class Central(Connection.Listener):
1345
1348
  def on_connection_parameters_update(self):
1346
1349
  print_connection(self.connection)
1347
1350
 
1348
- def on_connection_phy_update(self):
1349
- print_connection(self.connection)
1351
+ def on_connection_phy_update(self, phy):
1352
+ print_connection_phy(phy)
1350
1353
 
1351
1354
  def on_connection_att_mtu_update(self):
1352
1355
  print_connection(self.connection)
@@ -1472,8 +1475,8 @@ class Peripheral(Device.Listener, Connection.Listener):
1472
1475
  def on_connection_parameters_update(self):
1473
1476
  print_connection(self.connection)
1474
1477
 
1475
- def on_connection_phy_update(self):
1476
- print_connection(self.connection)
1478
+ def on_connection_phy_update(self, phy):
1479
+ print_connection_phy(phy)
1477
1480
 
1478
1481
  def on_connection_att_mtu_update(self):
1479
1482
  print_connection(self.connection)
bumble/apps/console.py CHANGED
@@ -22,7 +22,6 @@
22
22
  import asyncio
23
23
  import logging
24
24
  import os
25
- import random
26
25
  import re
27
26
  import humanize
28
27
  from typing import Optional, Union
@@ -57,7 +56,13 @@ from bumble import __version__
57
56
  import bumble.core
58
57
  from bumble import colors
59
58
  from bumble.core import UUID, AdvertisingData, BT_LE_TRANSPORT
60
- from bumble.device import ConnectionParametersPreferences, Device, Connection, Peer
59
+ from bumble.device import (
60
+ ConnectionParametersPreferences,
61
+ ConnectionPHY,
62
+ Device,
63
+ Connection,
64
+ Peer,
65
+ )
61
66
  from bumble.utils import AsyncRunner
62
67
  from bumble.transport import open_transport_or_link
63
68
  from bumble.gatt import Characteristic, Service, CharacteristicDeclaration, Descriptor
@@ -125,6 +130,7 @@ def parse_phys(phys):
125
130
  # -----------------------------------------------------------------------------
126
131
  class ConsoleApp:
127
132
  connected_peer: Optional[Peer]
133
+ connection_phy: Optional[ConnectionPHY]
128
134
 
129
135
  def __init__(self):
130
136
  self.known_addresses = set()
@@ -132,6 +138,7 @@ class ConsoleApp:
132
138
  self.known_local_attributes = []
133
139
  self.device = None
134
140
  self.connected_peer = None
141
+ self.connection_phy = None
135
142
  self.top_tab = 'device'
136
143
  self.monitor_rssi = False
137
144
  self.connection_rssi = None
@@ -332,10 +339,10 @@ class ConsoleApp:
332
339
  f'{connection.parameters.peripheral_latency}/'
333
340
  f'{connection.parameters.supervision_timeout}'
334
341
  )
335
- if connection.transport == BT_LE_TRANSPORT:
342
+ if self.connection_phy is not None:
336
343
  phy_state = (
337
- f' RX={le_phy_name(connection.phy.rx_phy)}/'
338
- f'TX={le_phy_name(connection.phy.tx_phy)}'
344
+ f' RX={le_phy_name(self.connection_phy.rx_phy)}/'
345
+ f'TX={le_phy_name(self.connection_phy.tx_phy)}'
339
346
  )
340
347
  else:
341
348
  phy_state = ''
@@ -654,11 +661,12 @@ class ConsoleApp:
654
661
  self.append_to_output('connecting...')
655
662
 
656
663
  try:
657
- await self.device.connect(
664
+ connection = await self.device.connect(
658
665
  params[0],
659
666
  connection_parameters_preferences=connection_parameters_preferences,
660
667
  timeout=DEFAULT_CONNECTION_TIMEOUT,
661
668
  )
669
+ self.connection_phy = await connection.get_phy()
662
670
  self.top_tab = 'services'
663
671
  except bumble.core.TimeoutError:
664
672
  self.show_error('connection timed out')
@@ -838,8 +846,8 @@ class ConsoleApp:
838
846
 
839
847
  phy = await self.connected_peer.connection.get_phy()
840
848
  self.append_to_output(
841
- f'PHY: RX={HCI_Constant.le_phy_name(phy[0])}, '
842
- f'TX={HCI_Constant.le_phy_name(phy[1])}'
849
+ f'PHY: RX={HCI_Constant.le_phy_name(phy.rx_phy)}, '
850
+ f'TX={HCI_Constant.le_phy_name(phy.tx_phy)}'
843
851
  )
844
852
 
845
853
  async def do_request_mtu(self, params):
@@ -1076,10 +1084,9 @@ class DeviceListener(Device.Listener, Connection.Listener):
1076
1084
  f'{self.app.connected_peer.connection.parameters}'
1077
1085
  )
1078
1086
 
1079
- def on_connection_phy_update(self):
1080
- self.app.append_to_output(
1081
- f'connection phy update: {self.app.connected_peer.connection.phy}'
1082
- )
1087
+ def on_connection_phy_update(self, phy):
1088
+ self.app.connection_phy = phy
1089
+ self.app.append_to_output(f'connection phy update: {phy}')
1083
1090
 
1084
1091
  def on_connection_att_mtu_update(self):
1085
1092
  self.app.append_to_output(
bumble/apps/gg_bridge.py CHANGED
@@ -234,7 +234,7 @@ class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
234
234
  Characteristic.WRITEABLE,
235
235
  CharacteristicValue(write=self.on_rx_write),
236
236
  )
237
- self.tx_characteristic = Characteristic(
237
+ self.tx_characteristic: Characteristic[bytes] = Characteristic(
238
238
  GG_GATTLINK_TX_CHARACTERISTIC_UUID,
239
239
  Characteristic.Properties.NOTIFY,
240
240
  Characteristic.READABLE,
bumble/att.py CHANGED
@@ -29,13 +29,14 @@ import functools
29
29
  import inspect
30
30
  import struct
31
31
  from typing import (
32
- Any,
33
32
  Awaitable,
34
33
  Callable,
34
+ Generic,
35
35
  Dict,
36
36
  List,
37
37
  Optional,
38
38
  Type,
39
+ TypeVar,
39
40
  Union,
40
41
  TYPE_CHECKING,
41
42
  )
@@ -43,13 +44,18 @@ from typing import (
43
44
  from pyee import EventEmitter
44
45
 
45
46
  from bumble import utils
46
- from bumble.core import UUID, name_or_number, ProtocolError
47
+ from bumble.core import UUID, name_or_number, InvalidOperationError, ProtocolError
47
48
  from bumble.hci import HCI_Object, key_with_value
48
49
  from bumble.colors import color
49
50
 
51
+ # -----------------------------------------------------------------------------
52
+ # Typing
53
+ # -----------------------------------------------------------------------------
50
54
  if TYPE_CHECKING:
51
55
  from bumble.device import Connection
52
56
 
57
+ _T = TypeVar('_T')
58
+
53
59
  # -----------------------------------------------------------------------------
54
60
  # Constants
55
61
  # -----------------------------------------------------------------------------
@@ -748,7 +754,7 @@ class ATT_Handle_Value_Confirmation(ATT_PDU):
748
754
 
749
755
 
750
756
  # -----------------------------------------------------------------------------
751
- class AttributeValue:
757
+ class AttributeValue(Generic[_T]):
752
758
  '''
753
759
  Attribute value where reading and/or writing is delegated to functions
754
760
  passed as arguments to the constructor.
@@ -757,33 +763,34 @@ class AttributeValue:
757
763
  def __init__(
758
764
  self,
759
765
  read: Union[
760
- Callable[[Optional[Connection]], Any],
761
- Callable[[Optional[Connection]], Awaitable[Any]],
766
+ Callable[[Optional[Connection]], _T],
767
+ Callable[[Optional[Connection]], Awaitable[_T]],
762
768
  None,
763
769
  ] = None,
764
770
  write: Union[
765
- Callable[[Optional[Connection], Any], None],
766
- Callable[[Optional[Connection], Any], Awaitable[None]],
771
+ Callable[[Optional[Connection], _T], None],
772
+ Callable[[Optional[Connection], _T], Awaitable[None]],
767
773
  None,
768
774
  ] = None,
769
775
  ):
770
776
  self._read = read
771
777
  self._write = write
772
778
 
773
- def read(self, connection: Optional[Connection]) -> Union[bytes, Awaitable[bytes]]:
774
- return self._read(connection) if self._read else b''
779
+ def read(self, connection: Optional[Connection]) -> Union[_T, Awaitable[_T]]:
780
+ if self._read is None:
781
+ raise InvalidOperationError('AttributeValue has no read function')
782
+ return self._read(connection)
775
783
 
776
784
  def write(
777
- self, connection: Optional[Connection], value: bytes
785
+ self, connection: Optional[Connection], value: _T
778
786
  ) -> Union[Awaitable[None], None]:
779
- if self._write:
780
- return self._write(connection, value)
781
-
782
- return None
787
+ if self._write is None:
788
+ raise InvalidOperationError('AttributeValue has no write function')
789
+ return self._write(connection, value)
783
790
 
784
791
 
785
792
  # -----------------------------------------------------------------------------
786
- class Attribute(EventEmitter):
793
+ class Attribute(EventEmitter, Generic[_T]):
787
794
  class Permissions(enum.IntFlag):
788
795
  READABLE = 0x01
789
796
  WRITEABLE = 0x02
@@ -822,13 +829,13 @@ class Attribute(EventEmitter):
822
829
  READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION
823
830
  WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION
824
831
 
825
- value: Any
832
+ value: Union[AttributeValue[_T], _T, None]
826
833
 
827
834
  def __init__(
828
835
  self,
829
836
  attribute_type: Union[str, bytes, UUID],
830
837
  permissions: Union[str, Attribute.Permissions],
831
- value: Any = b'',
838
+ value: Union[AttributeValue[_T], _T, None] = None,
832
839
  ) -> None:
833
840
  EventEmitter.__init__(self)
834
841
  self.handle = 0
@@ -848,11 +855,11 @@ class Attribute(EventEmitter):
848
855
 
849
856
  self.value = value
850
857
 
851
- def encode_value(self, value: Any) -> bytes:
852
- return value
858
+ def encode_value(self, value: _T) -> bytes:
859
+ return value # type: ignore
853
860
 
854
- def decode_value(self, value_bytes: bytes) -> Any:
855
- return value_bytes
861
+ def decode_value(self, value: bytes) -> _T:
862
+ return value # type: ignore
856
863
 
857
864
  async def read_value(self, connection: Optional[Connection]) -> bytes:
858
865
  if (
@@ -877,11 +884,14 @@ class Attribute(EventEmitter):
877
884
  error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
878
885
  )
879
886
 
880
- if hasattr(self.value, 'read'):
887
+ value: Union[_T, None]
888
+ if isinstance(self.value, AttributeValue):
881
889
  try:
882
- value = self.value.read(connection)
883
- if inspect.isawaitable(value):
884
- value = await value
890
+ read_value = self.value.read(connection)
891
+ if inspect.isawaitable(read_value):
892
+ value = await read_value
893
+ else:
894
+ value = read_value
885
895
  except ATT_Error as error:
886
896
  raise ATT_Error(
887
897
  error_code=error.error_code, att_handle=self.handle
@@ -889,20 +899,24 @@ class Attribute(EventEmitter):
889
899
  else:
890
900
  value = self.value
891
901
 
892
- self.emit('read', connection, value)
902
+ self.emit('read', connection, b'' if value is None else value)
893
903
 
894
- return self.encode_value(value)
904
+ return b'' if value is None else self.encode_value(value)
895
905
 
896
- async def write_value(self, connection: Connection, value_bytes: bytes) -> None:
906
+ async def write_value(self, connection: Optional[Connection], value: bytes) -> None:
897
907
  if (
898
- self.permissions & self.WRITE_REQUIRES_ENCRYPTION
899
- ) and not connection.encryption:
908
+ (self.permissions & self.WRITE_REQUIRES_ENCRYPTION)
909
+ and connection is not None
910
+ and not connection.encryption
911
+ ):
900
912
  raise ATT_Error(
901
913
  error_code=ATT_INSUFFICIENT_ENCRYPTION_ERROR, att_handle=self.handle
902
914
  )
903
915
  if (
904
- self.permissions & self.WRITE_REQUIRES_AUTHENTICATION
905
- ) and not connection.authenticated:
916
+ (self.permissions & self.WRITE_REQUIRES_AUTHENTICATION)
917
+ and connection is not None
918
+ and not connection.authenticated
919
+ ):
906
920
  raise ATT_Error(
907
921
  error_code=ATT_INSUFFICIENT_AUTHENTICATION_ERROR, att_handle=self.handle
908
922
  )
@@ -912,11 +926,11 @@ class Attribute(EventEmitter):
912
926
  error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
913
927
  )
914
928
 
915
- value = self.decode_value(value_bytes)
929
+ decoded_value = self.decode_value(value)
916
930
 
917
- if hasattr(self.value, 'write'):
931
+ if isinstance(self.value, AttributeValue):
918
932
  try:
919
- result = self.value.write(connection, value)
933
+ result = self.value.write(connection, decoded_value)
920
934
  if inspect.isawaitable(result):
921
935
  await result
922
936
  except ATT_Error as error:
@@ -924,9 +938,9 @@ class Attribute(EventEmitter):
924
938
  error_code=error.error_code, att_handle=self.handle
925
939
  ) from error
926
940
  else:
927
- self.value = value
941
+ self.value = decoded_value
928
942
 
929
- self.emit('write', connection, value)
943
+ self.emit('write', connection, decoded_value)
930
944
 
931
945
  def __repr__(self):
932
946
  if isinstance(self.value, bytes):