bumble 0.0.212__py3-none-any.whl → 0.0.214__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 (92) 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 +14 -11
  5. bumble/apps/bench.py +482 -37
  6. bumble/apps/console.py +3 -3
  7. bumble/apps/controller_info.py +44 -12
  8. bumble/apps/controller_loopback.py +7 -7
  9. bumble/apps/controllers.py +4 -5
  10. bumble/apps/device_info.py +4 -5
  11. bumble/apps/gatt_dump.py +5 -5
  12. bumble/apps/gg_bridge.py +5 -5
  13. bumble/apps/hci_bridge.py +5 -4
  14. bumble/apps/l2cap_bridge.py +5 -5
  15. bumble/apps/lea_unicast/app.py +8 -3
  16. bumble/apps/pair.py +19 -11
  17. bumble/apps/pandora_server.py +2 -2
  18. bumble/apps/player/player.py +2 -3
  19. bumble/apps/rfcomm_bridge.py +3 -4
  20. bumble/apps/scan.py +4 -5
  21. bumble/apps/show.py +6 -4
  22. bumble/apps/speaker/speaker.html +1 -0
  23. bumble/apps/speaker/speaker.js +113 -62
  24. bumble/apps/speaker/speaker.py +123 -19
  25. bumble/apps/unbond.py +2 -3
  26. bumble/apps/usb_probe.py +2 -3
  27. bumble/at.py +4 -4
  28. bumble/att.py +2 -6
  29. bumble/avc.py +7 -7
  30. bumble/avctp.py +3 -3
  31. bumble/avdtp.py +16 -20
  32. bumble/avrcp.py +42 -54
  33. bumble/colors.py +2 -2
  34. bumble/controller.py +174 -45
  35. bumble/device.py +398 -182
  36. bumble/drivers/__init__.py +2 -2
  37. bumble/drivers/common.py +0 -2
  38. bumble/drivers/intel.py +37 -40
  39. bumble/drivers/rtk.py +28 -35
  40. bumble/gatt.py +4 -4
  41. bumble/gatt_adapters.py +4 -5
  42. bumble/gatt_client.py +26 -31
  43. bumble/gatt_server.py +7 -11
  44. bumble/hci.py +2648 -2909
  45. bumble/helpers.py +4 -5
  46. bumble/hfp.py +32 -37
  47. bumble/host.py +104 -35
  48. bumble/keys.py +5 -5
  49. bumble/l2cap.py +312 -409
  50. bumble/link.py +16 -280
  51. bumble/logging.py +65 -0
  52. bumble/pairing.py +23 -20
  53. bumble/pandora/__init__.py +2 -2
  54. bumble/pandora/config.py +2 -2
  55. bumble/pandora/device.py +6 -6
  56. bumble/pandora/host.py +27 -28
  57. bumble/pandora/l2cap.py +2 -2
  58. bumble/pandora/security.py +6 -6
  59. bumble/pandora/utils.py +3 -3
  60. bumble/profiles/ams.py +404 -0
  61. bumble/profiles/ascs.py +142 -131
  62. bumble/profiles/asha.py +2 -2
  63. bumble/profiles/bap.py +3 -4
  64. bumble/profiles/csip.py +2 -2
  65. bumble/profiles/device_information_service.py +2 -2
  66. bumble/profiles/gap.py +2 -2
  67. bumble/profiles/hap.py +34 -33
  68. bumble/profiles/le_audio.py +4 -4
  69. bumble/profiles/mcp.py +4 -4
  70. bumble/profiles/vcs.py +3 -5
  71. bumble/rfcomm.py +10 -10
  72. bumble/rtp.py +1 -2
  73. bumble/sdp.py +2 -2
  74. bumble/smp.py +62 -63
  75. bumble/tools/intel_util.py +3 -2
  76. bumble/tools/rtk_util.py +6 -5
  77. bumble/transport/__init__.py +2 -16
  78. bumble/transport/android_netsim.py +5 -5
  79. bumble/transport/common.py +4 -4
  80. bumble/transport/pyusb.py +2 -2
  81. bumble/utils.py +2 -5
  82. bumble/vendor/android/hci.py +118 -200
  83. bumble/vendor/zephyr/hci.py +32 -27
  84. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/METADATA +4 -3
  85. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/RECORD +89 -90
  86. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/WHEEL +1 -1
  87. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/entry_points.txt +0 -1
  88. bumble/apps/link_relay/__init__.py +0 -0
  89. bumble/apps/link_relay/link_relay.py +0 -289
  90. bumble/apps/link_relay/logging.yml +0 -21
  91. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/licenses/LICENSE +0 -0
  92. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/top_level.txt +0 -0
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
+ )
79
+
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
110
89
 
111
- def init_from_bytes(self, pdu: bytes, offset: int):
112
- return hci.HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
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
179
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))
180
148
 
181
- @ASE_Operation.subclass([[('ase_id', 1), ('metadata', 'v')]])
149
+
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
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))
188
161
 
189
162
 
190
- @ASE_Operation.subclass([[('ase_id', 1)]])
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
+ )
175
+
196
176
 
197
- @ASE_Operation.subclass([[('ase_id', 1)]])
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
203
185
 
204
- @ASE_Operation.subclass([[('ase_id', 1)]])
186
+ ase_id: Sequence[int] = field(
187
+ metadata=hci.metadata(1, list_begin=True, list_end=True)
188
+ )
189
+
190
+
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
199
+
200
+ ase_id: Sequence[int] = field(
201
+ metadata=hci.metadata(1, list_begin=True, list_end=True)
202
+ )
210
203
 
211
- @ASE_Operation.subclass([[('ase_id', 1), ('metadata', 'v')]])
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
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))
218
216
 
219
217
 
220
- @ASE_Operation.subclass([[('ase_id', 1)]])
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,
@@ -453,10 +452,20 @@ class AseStateMachine(gatt.Characteristic):
453
452
 
454
453
  self.metadata = le_audio.Metadata.from_bytes(metadata)
455
454
  self.state = self.State.ENABLING
455
+ # CIS could be established before enable.
456
+ if cis_link := next(
457
+ (
458
+ cis_link
459
+ for cis_link in self.service.device.cis_links.values()
460
+ if cis_link.cig_id == self.cig_id and cis_link.cis_id == self.cis_id
461
+ ),
462
+ None,
463
+ ):
464
+ self.on_cis_establishment(cis_link)
456
465
 
457
466
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
458
467
 
459
- def on_receiver_start_ready(self) -> Tuple[AseResponseCode, AseReasonCode]:
468
+ def on_receiver_start_ready(self) -> tuple[AseResponseCode, AseReasonCode]:
460
469
  if self.state != AseStateMachine.State.ENABLING:
461
470
  return (
462
471
  AseResponseCode.INVALID_ASE_STATE_MACHINE_TRANSITION,
@@ -465,7 +474,7 @@ class AseStateMachine(gatt.Characteristic):
465
474
  self.state = self.State.STREAMING
466
475
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
467
476
 
468
- def on_disable(self) -> Tuple[AseResponseCode, AseReasonCode]:
477
+ def on_disable(self) -> tuple[AseResponseCode, AseReasonCode]:
469
478
  if self.state not in (
470
479
  AseStateMachine.State.ENABLING,
471
480
  AseStateMachine.State.STREAMING,
@@ -480,7 +489,7 @@ class AseStateMachine(gatt.Characteristic):
480
489
  self.state = self.State.DISABLING
481
490
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
482
491
 
483
- def on_receiver_stop_ready(self) -> Tuple[AseResponseCode, AseReasonCode]:
492
+ def on_receiver_stop_ready(self) -> tuple[AseResponseCode, AseReasonCode]:
484
493
  if (
485
494
  self.role != AudioRole.SOURCE
486
495
  or self.state != AseStateMachine.State.DISABLING
@@ -494,7 +503,7 @@ class AseStateMachine(gatt.Characteristic):
494
503
 
495
504
  def on_update_metadata(
496
505
  self, metadata: bytes
497
- ) -> Tuple[AseResponseCode, AseReasonCode]:
506
+ ) -> tuple[AseResponseCode, AseReasonCode]:
498
507
  if self.state not in (
499
508
  AseStateMachine.State.ENABLING,
500
509
  AseStateMachine.State.STREAMING,
@@ -506,7 +515,7 @@ class AseStateMachine(gatt.Characteristic):
506
515
  self.metadata = le_audio.Metadata.from_bytes(metadata)
507
516
  return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
508
517
 
509
- def on_release(self) -> Tuple[AseResponseCode, AseReasonCode]:
518
+ def on_release(self) -> tuple[AseResponseCode, AseReasonCode]:
510
519
  if self.state == AseStateMachine.State.IDLE:
511
520
  return (
512
521
  AseResponseCode.INVALID_ASE_STATE_MACHINE_TRANSITION,
@@ -516,7 +525,7 @@ class AseStateMachine(gatt.Characteristic):
516
525
 
517
526
  async def remove_cis_async():
518
527
  if self.cis_link:
519
- await self.cis_link.remove_data_path(self.role)
528
+ await self.cis_link.remove_data_path([self.role])
520
529
  self.state = self.State.IDLE
521
530
  await self.service.device.notify_subscribers(self, self.value)
522
531
 
@@ -604,7 +613,7 @@ class AseStateMachine(gatt.Characteristic):
604
613
  class AudioStreamControlService(gatt.TemplateService):
605
614
  UUID = gatt.GATT_AUDIO_STREAM_CONTROL_SERVICE
606
615
 
607
- ase_state_machines: Dict[int, AseStateMachine]
616
+ ase_state_machines: dict[int, AseStateMachine]
608
617
  ase_control_point: gatt.Characteristic[bytes]
609
618
  _active_client: Optional[device.Connection] = None
610
619
 
@@ -649,7 +658,9 @@ class AudioStreamControlService(gatt.TemplateService):
649
658
  ase.state = AseStateMachine.State.IDLE
650
659
  self._active_client = None
651
660
 
652
- def on_write_ase_control_point(self, connection, data):
661
+ def on_write_ase_control_point(
662
+ self, connection: device.Connection, data: bytes
663
+ ) -> None:
653
664
  if not self._active_client and connection:
654
665
  self._active_client = connection
655
666
  connection.once('disconnection', self._on_client_disconnected)
@@ -658,7 +669,7 @@ class AudioStreamControlService(gatt.TemplateService):
658
669
  responses = []
659
670
  logger.debug(f'*** ASCS Write {operation} ***')
660
671
 
661
- if operation.op_code == ASE_Operation.Opcode.CONFIG_CODEC:
672
+ if isinstance(operation, ASE_Config_Codec):
662
673
  for ase_id, *args in zip(
663
674
  operation.ase_id,
664
675
  operation.target_latency,
@@ -667,7 +678,7 @@ class AudioStreamControlService(gatt.TemplateService):
667
678
  operation.codec_specific_configuration,
668
679
  ):
669
680
  responses.append(self.on_operation(operation.op_code, ase_id, args))
670
- elif operation.op_code == ASE_Operation.Opcode.CONFIG_QOS:
681
+ elif isinstance(operation, ASE_Config_QOS):
671
682
  for ase_id, *args in zip(
672
683
  operation.ase_id,
673
684
  operation.cig_id,
@@ -681,20 +692,20 @@ class AudioStreamControlService(gatt.TemplateService):
681
692
  operation.presentation_delay,
682
693
  ):
683
694
  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
- ):
695
+ elif isinstance(operation, (ASE_Enable, ASE_Update_Metadata)):
688
696
  for ase_id, *args in zip(
689
697
  operation.ase_id,
690
698
  operation.metadata,
691
699
  ):
692
700
  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,
701
+ elif isinstance(
702
+ operation,
703
+ (
704
+ ASE_Receiver_Start_Ready,
705
+ ASE_Disable,
706
+ ASE_Receiver_Stop_Ready,
707
+ ASE_Release,
708
+ ),
698
709
  ):
699
710
  for ase_id in operation.ase_id:
700
711
  responses.append(self.on_operation(operation.op_code, ase_id, []))
@@ -723,8 +734,8 @@ class AudioStreamControlService(gatt.TemplateService):
723
734
  class AudioStreamControlServiceProxy(gatt_client.ProfileServiceProxy):
724
735
  SERVICE_CLASS = AudioStreamControlService
725
736
 
726
- sink_ase: List[gatt_client.CharacteristicProxy[bytes]]
727
- source_ase: List[gatt_client.CharacteristicProxy[bytes]]
737
+ sink_ase: list[gatt_client.CharacteristicProxy[bytes]]
738
+ source_ase: list[gatt_client.CharacteristicProxy[bytes]]
728
739
  ase_control_point: gatt_client.CharacteristicProxy[bytes]
729
740
 
730
741
  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