bumble 0.0.212__py3-none-any.whl → 0.0.213__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.
Files changed (86) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +6 -0
  3. bumble/apps/README.md +0 -3
  4. bumble/apps/auracast.py +11 -9
  5. bumble/apps/bench.py +480 -31
  6. bumble/apps/console.py +3 -3
  7. bumble/apps/controller_info.py +47 -10
  8. bumble/apps/controller_loopback.py +7 -3
  9. bumble/apps/controllers.py +2 -2
  10. bumble/apps/device_info.py +2 -2
  11. bumble/apps/gatt_dump.py +2 -2
  12. bumble/apps/gg_bridge.py +2 -2
  13. bumble/apps/hci_bridge.py +2 -2
  14. bumble/apps/l2cap_bridge.py +2 -2
  15. bumble/apps/lea_unicast/app.py +6 -1
  16. bumble/apps/pair.py +19 -11
  17. bumble/apps/pandora_server.py +2 -2
  18. bumble/apps/rfcomm_bridge.py +1 -1
  19. bumble/apps/scan.py +2 -2
  20. bumble/apps/show.py +4 -2
  21. bumble/apps/speaker/speaker.html +1 -0
  22. bumble/apps/speaker/speaker.js +113 -62
  23. bumble/apps/speaker/speaker.py +126 -18
  24. bumble/at.py +4 -4
  25. bumble/att.py +2 -6
  26. bumble/avc.py +7 -7
  27. bumble/avctp.py +3 -3
  28. bumble/avdtp.py +16 -20
  29. bumble/avrcp.py +41 -53
  30. bumble/colors.py +2 -2
  31. bumble/controller.py +84 -23
  32. bumble/device.py +348 -182
  33. bumble/drivers/__init__.py +2 -2
  34. bumble/drivers/common.py +0 -2
  35. bumble/drivers/intel.py +37 -40
  36. bumble/drivers/rtk.py +28 -35
  37. bumble/gatt.py +4 -4
  38. bumble/gatt_adapters.py +4 -5
  39. bumble/gatt_client.py +26 -31
  40. bumble/gatt_server.py +7 -11
  41. bumble/hci.py +2601 -2909
  42. bumble/helpers.py +4 -5
  43. bumble/hfp.py +32 -37
  44. bumble/host.py +94 -35
  45. bumble/keys.py +5 -5
  46. bumble/l2cap.py +310 -394
  47. bumble/link.py +6 -270
  48. bumble/pairing.py +23 -20
  49. bumble/pandora/__init__.py +1 -1
  50. bumble/pandora/config.py +2 -2
  51. bumble/pandora/device.py +6 -6
  52. bumble/pandora/host.py +27 -28
  53. bumble/pandora/l2cap.py +2 -2
  54. bumble/pandora/security.py +6 -6
  55. bumble/pandora/utils.py +3 -3
  56. bumble/profiles/ascs.py +132 -131
  57. bumble/profiles/asha.py +2 -2
  58. bumble/profiles/bap.py +3 -4
  59. bumble/profiles/csip.py +2 -2
  60. bumble/profiles/device_information_service.py +2 -2
  61. bumble/profiles/gap.py +2 -2
  62. bumble/profiles/hap.py +34 -33
  63. bumble/profiles/le_audio.py +4 -4
  64. bumble/profiles/mcp.py +4 -4
  65. bumble/profiles/vcs.py +3 -5
  66. bumble/rfcomm.py +10 -10
  67. bumble/rtp.py +1 -2
  68. bumble/sdp.py +2 -2
  69. bumble/smp.py +57 -61
  70. bumble/tools/rtk_util.py +2 -2
  71. bumble/transport/__init__.py +2 -16
  72. bumble/transport/android_netsim.py +5 -5
  73. bumble/transport/common.py +4 -4
  74. bumble/transport/pyusb.py +2 -2
  75. bumble/utils.py +2 -5
  76. bumble/vendor/android/hci.py +118 -200
  77. bumble/vendor/zephyr/hci.py +32 -27
  78. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/METADATA +2 -2
  79. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/RECORD +83 -86
  80. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
  81. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
  82. bumble/apps/link_relay/__init__.py +0 -0
  83. bumble/apps/link_relay/link_relay.py +0 -289
  84. bumble/apps/link_relay/logging.yml +0 -21
  85. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
  86. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/top_level.txt +0 -0
bumble/pandora/l2cap.py CHANGED
@@ -51,7 +51,7 @@ from pandora.l2cap_pb2 import ( # pytype: disable=pyi-error
51
51
  WaitDisconnectionRequest,
52
52
  WaitDisconnectionResponse,
53
53
  )
54
- from typing import AsyncGenerator, Dict, Optional, Union
54
+ from typing import AsyncGenerator, Optional, Union
55
55
  from dataclasses import dataclass
56
56
 
57
57
  L2capChannel = Union[ClassicChannel, LeCreditBasedChannel]
@@ -70,7 +70,7 @@ class L2CAPService(L2CAPServicer):
70
70
  )
71
71
  self.device = device
72
72
  self.config = config
73
- self.channels: Dict[bytes, ChannelContext] = {}
73
+ self.channels: dict[bytes, ChannelContext] = {}
74
74
 
75
75
  def register_event(self, l2cap_channel: L2capChannel) -> ChannelContext:
76
76
  close_future = asyncio.get_running_loop().create_future()
@@ -57,7 +57,7 @@ from pandora.security_pb2 import (
57
57
  WaitSecurityRequest,
58
58
  WaitSecurityResponse,
59
59
  )
60
- from typing import Any, AsyncGenerator, AsyncIterator, Callable, Dict, Optional, Union
60
+ from typing import Any, AsyncGenerator, AsyncIterator, Callable, Optional, Union
61
61
 
62
62
 
63
63
  class PairingDelegate(BasePairingDelegate):
@@ -244,16 +244,16 @@ class SecurityService(SecurityServicer):
244
244
  and connection.authenticated
245
245
  and link_key_type
246
246
  in (
247
- hci.HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_192_TYPE,
248
- hci.HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256_TYPE,
247
+ hci.LinkKeyType.AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_192,
248
+ hci.LinkKeyType.AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256,
249
249
  )
250
250
  )
251
251
  if level == LEVEL4:
252
252
  return (
253
- connection.encryption == hci.HCI_Encryption_Change_Event.AES_CCM
253
+ connection.encryption == hci.HCI_Encryption_Change_Event.Enabled.AES_CCM
254
254
  and connection.authenticated
255
255
  and link_key_type
256
- == hci.HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256_TYPE
256
+ == hci.LinkKeyType.AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256
257
257
  )
258
258
  raise InvalidArgumentError(f"Unexpected level {level}")
259
259
 
@@ -457,7 +457,7 @@ class SecurityService(SecurityServicer):
457
457
  if self.need_pairing(connection, level):
458
458
  pair_task = asyncio.create_task(connection.pair())
459
459
 
460
- listeners: Dict[str, Callable[..., Union[None, Awaitable[None]]]] = {
460
+ listeners: dict[str, Callable[..., Union[None, Awaitable[None]]]] = {
461
461
  'disconnection': set_failure('connection_died'),
462
462
  'pairing_failure': set_failure('pairing_failure'),
463
463
  'connection_authentication_failure': set_failure('authentication_failure'),
bumble/pandora/utils.py CHANGED
@@ -22,9 +22,9 @@ import logging
22
22
  from bumble.device import Device
23
23
  from bumble.hci import Address, AddressType
24
24
  from google.protobuf.message import Message # pytype: disable=pyi-error
25
- from typing import Any, Dict, Generator, MutableMapping, Optional, Tuple
25
+ from typing import Any, Generator, MutableMapping, Optional
26
26
 
27
- ADDRESS_TYPES: Dict[str, AddressType] = {
27
+ ADDRESS_TYPES: dict[str, AddressType] = {
28
28
  "public": Address.PUBLIC_DEVICE_ADDRESS,
29
29
  "random": Address.RANDOM_DEVICE_ADDRESS,
30
30
  "public_identity": Address.PUBLIC_IDENTITY_ADDRESS,
@@ -43,7 +43,7 @@ class BumbleServerLoggerAdapter(logging.LoggerAdapter): # type: ignore
43
43
 
44
44
  def process(
45
45
  self, msg: str, kwargs: MutableMapping[str, Any]
46
- ) -> Tuple[str, MutableMapping[str, Any]]:
46
+ ) -> tuple[str, MutableMapping[str, Any]]:
47
47
  assert self.extra
48
48
  service_name = self.extra['service_name']
49
49
  assert isinstance(service_name, str)
bumble/profiles/ascs.py CHANGED
@@ -18,10 +18,13 @@
18
18
  # -----------------------------------------------------------------------------
19
19
  from __future__ import annotations
20
20
 
21
+ from dataclasses import dataclass, field
21
22
  import enum
23
+ import functools
22
24
  import logging
23
25
  import struct
24
- from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
26
+ from typing import Any, Optional, Union, TypeVar
27
+ from collections.abc import Sequence
25
28
 
26
29
  from bumble import utils
27
30
  from bumble import colors
@@ -48,11 +51,11 @@ class ASE_Operation:
48
51
  See Audio Stream Control Service - 5 ASE Control operations.
49
52
  '''
50
53
 
51
- classes: Dict[int, Type[ASE_Operation]] = {}
52
- op_code: int
54
+ classes: dict[int, type[ASE_Operation]] = {}
55
+ op_code: Opcode
53
56
  name: str
54
57
  fields: Optional[Sequence[Any]] = None
55
- ase_id: List[int]
58
+ ase_id: Sequence[int]
56
59
 
57
60
  class Opcode(enum.IntEnum):
58
61
  # fmt: off
@@ -65,51 +68,30 @@ class ASE_Operation:
65
68
  UPDATE_METADATA = 0x07
66
69
  RELEASE = 0x08
67
70
 
68
- @staticmethod
69
- def from_bytes(pdu: bytes) -> ASE_Operation:
71
+ @classmethod
72
+ def from_bytes(cls, pdu: bytes) -> ASE_Operation:
70
73
  op_code = pdu[0]
71
74
 
72
- cls = ASE_Operation.classes.get(op_code)
73
- if cls is None:
74
- instance = ASE_Operation(pdu)
75
- instance.name = ASE_Operation.Opcode(op_code).name
76
- instance.op_code = op_code
77
- return instance
78
- self = cls.__new__(cls)
79
- ASE_Operation.__init__(self, pdu)
80
- if self.fields is not None:
81
- self.init_from_bytes(pdu, 1)
82
- return self
83
-
84
- @staticmethod
85
- def subclass(fields):
86
- def inner(cls: Type[ASE_Operation]):
87
- try:
88
- operation = ASE_Operation.Opcode[cls.__name__[4:].upper()]
89
- cls.name = operation.name
90
- cls.op_code = operation
91
- except:
92
- raise KeyError(f'PDU name {cls.name} not found in Ase_Operation.Opcode')
93
- cls.fields = fields
94
-
95
- # Register a factory for this class
96
- ASE_Operation.classes[cls.op_code] = cls
97
-
98
- return cls
99
-
100
- return inner
101
-
102
- def __init__(self, pdu: Optional[bytes] = None, **kwargs) -> None:
103
- if self.fields is not None and kwargs:
104
- hci.HCI_Object.init_from_fields(self, self.fields, kwargs)
105
- if pdu is None:
106
- pdu = bytes([self.op_code]) + hci.HCI_Object.dict_to_bytes(
107
- kwargs, self.fields
108
- )
109
- self.pdu = pdu
75
+ clazz = ASE_Operation.classes[op_code]
76
+ return clazz(
77
+ **hci.HCI_Object.dict_from_bytes(pdu, offset=1, fields=clazz.fields)
78
+ )
110
79
 
111
- def init_from_bytes(self, pdu: bytes, offset: int):
112
- return hci.HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
80
+ _OP = TypeVar("_OP", bound="ASE_Operation")
81
+
82
+ @classmethod
83
+ def subclass(cls, clazz: type[_OP]) -> type[_OP]:
84
+ clazz.name = f"ASE_{clazz.op_code.name.upper()}"
85
+ clazz.fields = hci.HCI_Object.fields_from_dataclass(clazz)
86
+ # Register a factory for this class
87
+ ASE_Operation.classes[clazz.op_code] = clazz
88
+ return clazz
89
+
90
+ @functools.cached_property
91
+ def pdu(self) -> bytes:
92
+ return bytes([self.op_code]) + hci.HCI_Object.dict_to_bytes(
93
+ self.__dict__, self.fields
94
+ )
113
95
 
114
96
  def __bytes__(self) -> bytes:
115
97
  return self.pdu
@@ -124,105 +106,128 @@ class ASE_Operation:
124
106
  return result
125
107
 
126
108
 
127
- @ASE_Operation.subclass(
128
- [
129
- [
130
- ('ase_id', 1),
131
- ('target_latency', 1),
132
- ('target_phy', 1),
133
- ('codec_id', hci.CodingFormat.parse_from_bytes),
134
- ('codec_specific_configuration', 'v'),
135
- ],
136
- ]
137
- )
109
+ @ASE_Operation.subclass
110
+ @dataclass
138
111
  class ASE_Config_Codec(ASE_Operation):
139
112
  '''
140
113
  See Audio Stream Control Service 5.1 - Config Codec Operation
141
114
  '''
142
115
 
143
- target_latency: List[int]
144
- target_phy: List[int]
145
- codec_id: List[hci.CodingFormat]
146
- codec_specific_configuration: List[bytes]
147
-
148
-
149
- @ASE_Operation.subclass(
150
- [
151
- [
152
- ('ase_id', 1),
153
- ('cig_id', 1),
154
- ('cis_id', 1),
155
- ('sdu_interval', 3),
156
- ('framing', 1),
157
- ('phy', 1),
158
- ('max_sdu', 2),
159
- ('retransmission_number', 1),
160
- ('max_transport_latency', 2),
161
- ('presentation_delay', 3),
162
- ],
163
- ]
164
- )
116
+ op_code = ASE_Operation.Opcode.CONFIG_CODEC
117
+
118
+ ase_id: Sequence[int] = field(metadata=hci.metadata(1, list_begin=True))
119
+ target_latency: Sequence[int] = field(metadata=hci.metadata(1))
120
+ target_phy: Sequence[int] = field(metadata=hci.metadata(1))
121
+ codec_id: Sequence[hci.CodingFormat] = field(
122
+ metadata=hci.metadata(hci.CodingFormat.parse_from_bytes)
123
+ )
124
+ codec_specific_configuration: Sequence[bytes] = field(
125
+ metadata=hci.metadata('v', list_end=True)
126
+ )
127
+
128
+
129
+ @ASE_Operation.subclass
130
+ @dataclass
165
131
  class ASE_Config_QOS(ASE_Operation):
166
132
  '''
167
133
  See Audio Stream Control Service 5.2 - Config Qos Operation
168
134
  '''
169
135
 
170
- cig_id: List[int]
171
- cis_id: List[int]
172
- sdu_interval: List[int]
173
- framing: List[int]
174
- phy: List[int]
175
- max_sdu: List[int]
176
- retransmission_number: List[int]
177
- max_transport_latency: List[int]
178
- presentation_delay: List[int]
136
+ op_code = ASE_Operation.Opcode.CONFIG_QOS
137
+
138
+ ase_id: Sequence[int] = field(metadata=hci.metadata(1, list_begin=True))
139
+ cig_id: Sequence[int] = field(metadata=hci.metadata(1))
140
+ cis_id: Sequence[int] = field(metadata=hci.metadata(1))
141
+ sdu_interval: Sequence[int] = field(metadata=hci.metadata(3))
142
+ framing: Sequence[int] = field(metadata=hci.metadata(1))
143
+ phy: Sequence[int] = field(metadata=hci.metadata(1))
144
+ max_sdu: Sequence[int] = field(metadata=hci.metadata(2))
145
+ retransmission_number: Sequence[int] = field(metadata=hci.metadata(1))
146
+ max_transport_latency: Sequence[int] = field(metadata=hci.metadata(2))
147
+ presentation_delay: Sequence[int] = field(metadata=hci.metadata(3, list_end=True))
179
148
 
180
149
 
181
- @ASE_Operation.subclass([[('ase_id', 1), ('metadata', 'v')]])
150
+ @ASE_Operation.subclass
151
+ @dataclass
182
152
  class ASE_Enable(ASE_Operation):
183
153
  '''
184
154
  See Audio Stream Control Service 5.3 - Enable Operation
185
155
  '''
186
156
 
187
- metadata: bytes
157
+ op_code = ASE_Operation.Opcode.ENABLE
188
158
 
159
+ ase_id: Sequence[int] = field(metadata=hci.metadata(1, list_begin=True))
160
+ metadata: Sequence[bytes] = field(metadata=hci.metadata('v', list_end=True))
189
161
 
190
- @ASE_Operation.subclass([[('ase_id', 1)]])
162
+
163
+ @ASE_Operation.subclass
164
+ @dataclass
191
165
  class ASE_Receiver_Start_Ready(ASE_Operation):
192
166
  '''
193
167
  See Audio Stream Control Service 5.4 - Receiver Start Ready Operation
194
168
  '''
195
169
 
170
+ op_code = ASE_Operation.Opcode.RECEIVER_START_READY
171
+
172
+ ase_id: Sequence[int] = field(
173
+ metadata=hci.metadata(1, list_begin=True, list_end=True)
174
+ )
196
175
 
197
- @ASE_Operation.subclass([[('ase_id', 1)]])
176
+
177
+ @ASE_Operation.subclass
178
+ @dataclass
198
179
  class ASE_Disable(ASE_Operation):
199
180
  '''
200
181
  See Audio Stream Control Service 5.5 - Disable Operation
201
182
  '''
202
183
 
184
+ op_code = ASE_Operation.Opcode.DISABLE
185
+
186
+ ase_id: Sequence[int] = field(
187
+ metadata=hci.metadata(1, list_begin=True, list_end=True)
188
+ )
189
+
203
190
 
204
- @ASE_Operation.subclass([[('ase_id', 1)]])
191
+ @ASE_Operation.subclass
192
+ @dataclass
205
193
  class ASE_Receiver_Stop_Ready(ASE_Operation):
206
194
  '''
207
195
  See Audio Stream Control Service 5.6 - Receiver Stop Ready Operation
208
196
  '''
209
197
 
198
+ op_code = ASE_Operation.Opcode.RECEIVER_STOP_READY
210
199
 
211
- @ASE_Operation.subclass([[('ase_id', 1), ('metadata', 'v')]])
200
+ ase_id: Sequence[int] = field(
201
+ metadata=hci.metadata(1, list_begin=True, list_end=True)
202
+ )
203
+
204
+
205
+ @ASE_Operation.subclass
206
+ @dataclass
212
207
  class ASE_Update_Metadata(ASE_Operation):
213
208
  '''
214
209
  See Audio Stream Control Service 5.7 - Update Metadata Operation
215
210
  '''
216
211
 
217
- metadata: List[bytes]
212
+ op_code = ASE_Operation.Opcode.UPDATE_METADATA
218
213
 
214
+ ase_id: Sequence[int] = field(metadata=hci.metadata(1, list_begin=True))
215
+ metadata: Sequence[bytes] = field(metadata=hci.metadata('v', list_end=True))
219
216
 
220
- @ASE_Operation.subclass([[('ase_id', 1)]])
217
+
218
+ @ASE_Operation.subclass
219
+ @dataclass
221
220
  class ASE_Release(ASE_Operation):
222
221
  '''
223
222
  See Audio Stream Control Service 5.8 - Release Operation
224
223
  '''
225
224
 
225
+ op_code = ASE_Operation.Opcode.RELEASE
226
+
227
+ ase_id: Sequence[int] = field(
228
+ metadata=hci.metadata(1, list_begin=True, list_end=True)
229
+ )
230
+
226
231
 
227
232
  class AseResponseCode(enum.IntEnum):
228
233
  # fmt: off
@@ -338,22 +343,16 @@ class AseStateMachine(gatt.Characteristic):
338
343
  self.service.device.EVENT_CIS_ESTABLISHMENT, self.on_cis_establishment
339
344
  )
340
345
 
341
- def on_cis_request(
342
- self,
343
- acl_connection: device.Connection,
344
- cis_handle: int,
345
- cig_id: int,
346
- cis_id: int,
347
- ) -> None:
346
+ def on_cis_request(self, cis_link: device.CisLink) -> None:
348
347
  if (
349
- cig_id == self.cig_id
350
- and cis_id == self.cis_id
348
+ cis_link.cig_id == self.cig_id
349
+ and cis_link.cis_id == self.cis_id
351
350
  and self.state == self.State.ENABLING
352
351
  ):
353
352
  utils.cancel_on_event(
354
- acl_connection,
353
+ cis_link.acl_connection,
355
354
  'flush',
356
- self.service.device.accept_cis_request(cis_handle),
355
+ self.service.device.accept_cis_request(cis_link),
357
356
  )
358
357
 
359
358
  def on_cis_establishment(self, cis_link: device.CisLink) -> None:
@@ -384,7 +383,7 @@ class AseStateMachine(gatt.Characteristic):
384
383
  target_phy: int,
385
384
  codec_id: hci.CodingFormat,
386
385
  codec_specific_configuration: bytes,
387
- ) -> Tuple[AseResponseCode, AseReasonCode]:
386
+ ) -> tuple[AseResponseCode, AseReasonCode]:
388
387
  if self.state not in (
389
388
  self.State.IDLE,
390
389
  self.State.CODEC_CONFIGURED,
@@ -420,7 +419,7 @@ class AseStateMachine(gatt.Characteristic):
420
419
  retransmission_number: int,
421
420
  max_transport_latency: int,
422
421
  presentation_delay: int,
423
- ) -> Tuple[AseResponseCode, AseReasonCode]:
422
+ ) -> tuple[AseResponseCode, AseReasonCode]:
424
423
  if self.state not in (
425
424
  AseStateMachine.State.CODEC_CONFIGURED,
426
425
  AseStateMachine.State.QOS_CONFIGURED,
@@ -444,7 +443,7 @@ class AseStateMachine(gatt.Characteristic):
444
443
 
445
444
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
446
445
 
447
- def on_enable(self, metadata: bytes) -> Tuple[AseResponseCode, AseReasonCode]:
446
+ def on_enable(self, metadata: bytes) -> tuple[AseResponseCode, AseReasonCode]:
448
447
  if self.state != AseStateMachine.State.QOS_CONFIGURED:
449
448
  return (
450
449
  AseResponseCode.INVALID_ASE_STATE_MACHINE_TRANSITION,
@@ -456,7 +455,7 @@ class AseStateMachine(gatt.Characteristic):
456
455
 
457
456
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
458
457
 
459
- def on_receiver_start_ready(self) -> Tuple[AseResponseCode, AseReasonCode]:
458
+ def on_receiver_start_ready(self) -> tuple[AseResponseCode, AseReasonCode]:
460
459
  if self.state != AseStateMachine.State.ENABLING:
461
460
  return (
462
461
  AseResponseCode.INVALID_ASE_STATE_MACHINE_TRANSITION,
@@ -465,7 +464,7 @@ class AseStateMachine(gatt.Characteristic):
465
464
  self.state = self.State.STREAMING
466
465
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
467
466
 
468
- def on_disable(self) -> Tuple[AseResponseCode, AseReasonCode]:
467
+ def on_disable(self) -> tuple[AseResponseCode, AseReasonCode]:
469
468
  if self.state not in (
470
469
  AseStateMachine.State.ENABLING,
471
470
  AseStateMachine.State.STREAMING,
@@ -480,7 +479,7 @@ class AseStateMachine(gatt.Characteristic):
480
479
  self.state = self.State.DISABLING
481
480
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
482
481
 
483
- def on_receiver_stop_ready(self) -> Tuple[AseResponseCode, AseReasonCode]:
482
+ def on_receiver_stop_ready(self) -> tuple[AseResponseCode, AseReasonCode]:
484
483
  if (
485
484
  self.role != AudioRole.SOURCE
486
485
  or self.state != AseStateMachine.State.DISABLING
@@ -494,7 +493,7 @@ class AseStateMachine(gatt.Characteristic):
494
493
 
495
494
  def on_update_metadata(
496
495
  self, metadata: bytes
497
- ) -> Tuple[AseResponseCode, AseReasonCode]:
496
+ ) -> tuple[AseResponseCode, AseReasonCode]:
498
497
  if self.state not in (
499
498
  AseStateMachine.State.ENABLING,
500
499
  AseStateMachine.State.STREAMING,
@@ -506,7 +505,7 @@ class AseStateMachine(gatt.Characteristic):
506
505
  self.metadata = le_audio.Metadata.from_bytes(metadata)
507
506
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
508
507
 
509
- def on_release(self) -> Tuple[AseResponseCode, AseReasonCode]:
508
+ def on_release(self) -> tuple[AseResponseCode, AseReasonCode]:
510
509
  if self.state == AseStateMachine.State.IDLE:
511
510
  return (
512
511
  AseResponseCode.INVALID_ASE_STATE_MACHINE_TRANSITION,
@@ -516,7 +515,7 @@ class AseStateMachine(gatt.Characteristic):
516
515
 
517
516
  async def remove_cis_async():
518
517
  if self.cis_link:
519
- await self.cis_link.remove_data_path(self.role)
518
+ await self.cis_link.remove_data_path([self.role])
520
519
  self.state = self.State.IDLE
521
520
  await self.service.device.notify_subscribers(self, self.value)
522
521
 
@@ -604,7 +603,7 @@ class AseStateMachine(gatt.Characteristic):
604
603
  class AudioStreamControlService(gatt.TemplateService):
605
604
  UUID = gatt.GATT_AUDIO_STREAM_CONTROL_SERVICE
606
605
 
607
- ase_state_machines: Dict[int, AseStateMachine]
606
+ ase_state_machines: dict[int, AseStateMachine]
608
607
  ase_control_point: gatt.Characteristic[bytes]
609
608
  _active_client: Optional[device.Connection] = None
610
609
 
@@ -649,7 +648,9 @@ class AudioStreamControlService(gatt.TemplateService):
649
648
  ase.state = AseStateMachine.State.IDLE
650
649
  self._active_client = None
651
650
 
652
- def on_write_ase_control_point(self, connection, data):
651
+ def on_write_ase_control_point(
652
+ self, connection: device.Connection, data: bytes
653
+ ) -> None:
653
654
  if not self._active_client and connection:
654
655
  self._active_client = connection
655
656
  connection.once('disconnection', self._on_client_disconnected)
@@ -658,7 +659,7 @@ class AudioStreamControlService(gatt.TemplateService):
658
659
  responses = []
659
660
  logger.debug(f'*** ASCS Write {operation} ***')
660
661
 
661
- if operation.op_code == ASE_Operation.Opcode.CONFIG_CODEC:
662
+ if isinstance(operation, ASE_Config_Codec):
662
663
  for ase_id, *args in zip(
663
664
  operation.ase_id,
664
665
  operation.target_latency,
@@ -667,7 +668,7 @@ class AudioStreamControlService(gatt.TemplateService):
667
668
  operation.codec_specific_configuration,
668
669
  ):
669
670
  responses.append(self.on_operation(operation.op_code, ase_id, args))
670
- elif operation.op_code == ASE_Operation.Opcode.CONFIG_QOS:
671
+ elif isinstance(operation, ASE_Config_QOS):
671
672
  for ase_id, *args in zip(
672
673
  operation.ase_id,
673
674
  operation.cig_id,
@@ -681,20 +682,20 @@ class AudioStreamControlService(gatt.TemplateService):
681
682
  operation.presentation_delay,
682
683
  ):
683
684
  responses.append(self.on_operation(operation.op_code, ase_id, args))
684
- elif operation.op_code in (
685
- ASE_Operation.Opcode.ENABLE,
686
- ASE_Operation.Opcode.UPDATE_METADATA,
687
- ):
685
+ elif isinstance(operation, (ASE_Enable, ASE_Update_Metadata)):
688
686
  for ase_id, *args in zip(
689
687
  operation.ase_id,
690
688
  operation.metadata,
691
689
  ):
692
690
  responses.append(self.on_operation(operation.op_code, ase_id, args))
693
- elif operation.op_code in (
694
- ASE_Operation.Opcode.RECEIVER_START_READY,
695
- ASE_Operation.Opcode.DISABLE,
696
- ASE_Operation.Opcode.RECEIVER_STOP_READY,
697
- ASE_Operation.Opcode.RELEASE,
691
+ elif isinstance(
692
+ operation,
693
+ (
694
+ ASE_Receiver_Start_Ready,
695
+ ASE_Disable,
696
+ ASE_Receiver_Stop_Ready,
697
+ ASE_Release,
698
+ ),
698
699
  ):
699
700
  for ase_id in operation.ase_id:
700
701
  responses.append(self.on_operation(operation.op_code, ase_id, []))
@@ -723,8 +724,8 @@ class AudioStreamControlService(gatt.TemplateService):
723
724
  class AudioStreamControlServiceProxy(gatt_client.ProfileServiceProxy):
724
725
  SERVICE_CLASS = AudioStreamControlService
725
726
 
726
- sink_ase: List[gatt_client.CharacteristicProxy[bytes]]
727
- source_ase: List[gatt_client.CharacteristicProxy[bytes]]
727
+ sink_ase: list[gatt_client.CharacteristicProxy[bytes]]
728
+ source_ase: list[gatt_client.CharacteristicProxy[bytes]]
728
729
  ase_control_point: gatt_client.CharacteristicProxy[bytes]
729
730
 
730
731
  def __init__(self, service_proxy: gatt_client.ServiceProxy):
bumble/profiles/asha.py CHANGED
@@ -19,7 +19,7 @@
19
19
  import enum
20
20
  import struct
21
21
  import logging
22
- from typing import List, Optional, Callable, Union, Any
22
+ from typing import Optional, Callable, Union, Any
23
23
 
24
24
  from bumble import l2cap
25
25
  from bumble import utils
@@ -103,7 +103,7 @@ class AshaService(gatt.TemplateService):
103
103
  def __init__(
104
104
  self,
105
105
  capability: int,
106
- hisyncid: Union[List[int], bytes],
106
+ hisyncid: Union[list[int], bytes],
107
107
  device: Device,
108
108
  psm: int = 0,
109
109
  audio_sink: Optional[Callable[[bytes], Any]] = None,
bumble/profiles/bap.py CHANGED
@@ -24,7 +24,6 @@ import enum
24
24
  import struct
25
25
  import functools
26
26
  import logging
27
- from typing import List
28
27
  from typing_extensions import Self
29
28
 
30
29
  from bumble import core
@@ -282,7 +281,7 @@ class UnicastServerAdvertisingData:
282
281
  # -----------------------------------------------------------------------------
283
282
 
284
283
 
285
- def bits_to_channel_counts(data: int) -> List[int]:
284
+ def bits_to_channel_counts(data: int) -> list[int]:
286
285
  pos = 0
287
286
  counts = []
288
287
  while data != 0:
@@ -527,7 +526,7 @@ class BasicAudioAnnouncement:
527
526
  codec_id: hci.CodingFormat
528
527
  codec_specific_configuration: CodecSpecificConfiguration
529
528
  metadata: le_audio.Metadata
530
- bis: List[BasicAudioAnnouncement.BIS]
529
+ bis: list[BasicAudioAnnouncement.BIS]
531
530
 
532
531
  def __bytes__(self) -> bytes:
533
532
  metadata_bytes = bytes(self.metadata)
@@ -545,7 +544,7 @@ class BasicAudioAnnouncement:
545
544
  )
546
545
 
547
546
  presentation_delay: int
548
- subgroups: List[BasicAudioAnnouncement.Subgroup]
547
+ subgroups: list[BasicAudioAnnouncement.Subgroup]
549
548
 
550
549
  @classmethod
551
550
  def from_bytes(cls, data: bytes) -> Self:
bumble/profiles/csip.py CHANGED
@@ -19,7 +19,7 @@
19
19
  from __future__ import annotations
20
20
  import enum
21
21
  import struct
22
- from typing import Optional, Tuple
22
+ from typing import Optional
23
23
 
24
24
  from bumble import core
25
25
  from bumble import crypto
@@ -228,7 +228,7 @@ class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy):
228
228
  ):
229
229
  self.set_member_rank = characteristics[0]
230
230
 
231
- async def read_set_identity_resolving_key(self) -> Tuple[SirkType, bytes]:
231
+ async def read_set_identity_resolving_key(self) -> tuple[SirkType, bytes]:
232
232
  '''Reads SIRK and decrypts if encrypted.'''
233
233
  response = await self.set_identity_resolving_key.read_value()
234
234
  if len(response) != SET_IDENTITY_RESOLVING_KEY_LENGTH + 1:
@@ -17,7 +17,7 @@
17
17
  # Imports
18
18
  # -----------------------------------------------------------------------------
19
19
  import struct
20
- from typing import Optional, Tuple
20
+ from typing import Optional
21
21
 
22
22
  from bumble.gatt import (
23
23
  GATT_DEVICE_INFORMATION_SERVICE,
@@ -60,7 +60,7 @@ class DeviceInformationService(TemplateService):
60
60
  hardware_revision: Optional[str] = None,
61
61
  firmware_revision: Optional[str] = None,
62
62
  software_revision: Optional[str] = None,
63
- system_id: Optional[Tuple[int, int]] = None, # (OUI, Manufacturer ID)
63
+ system_id: Optional[tuple[int, int]] = None, # (OUI, Manufacturer ID)
64
64
  ieee_regulatory_certification_data_list: Optional[bytes] = None,
65
65
  # TODO: pnp_id
66
66
  ):
bumble/profiles/gap.py CHANGED
@@ -19,7 +19,7 @@
19
19
  # -----------------------------------------------------------------------------
20
20
  import logging
21
21
  import struct
22
- from typing import Optional, Tuple, Union
22
+ from typing import Optional, Union
23
23
 
24
24
  from bumble.core import Appearance
25
25
  from bumble.gatt import (
@@ -54,7 +54,7 @@ class GenericAccessService(TemplateService):
54
54
  appearance_characteristic: Characteristic[bytes]
55
55
 
56
56
  def __init__(
57
- self, device_name: str, appearance: Union[Appearance, Tuple[int, int], int] = 0
57
+ self, device_name: str, appearance: Union[Appearance, tuple[int, int], int] = 0
58
58
  ):
59
59
  if isinstance(appearance, int):
60
60
  appearance_int = appearance