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.
- bumble/_version.py +2 -2
- bumble/a2dp.py +6 -0
- bumble/apps/README.md +0 -3
- bumble/apps/auracast.py +14 -11
- bumble/apps/bench.py +482 -37
- bumble/apps/console.py +3 -3
- bumble/apps/controller_info.py +44 -12
- bumble/apps/controller_loopback.py +7 -7
- bumble/apps/controllers.py +4 -5
- bumble/apps/device_info.py +4 -5
- bumble/apps/gatt_dump.py +5 -5
- bumble/apps/gg_bridge.py +5 -5
- bumble/apps/hci_bridge.py +5 -4
- bumble/apps/l2cap_bridge.py +5 -5
- bumble/apps/lea_unicast/app.py +8 -3
- bumble/apps/pair.py +19 -11
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +2 -3
- bumble/apps/rfcomm_bridge.py +3 -4
- bumble/apps/scan.py +4 -5
- bumble/apps/show.py +6 -4
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +123 -19
- bumble/apps/unbond.py +2 -3
- bumble/apps/usb_probe.py +2 -3
- bumble/at.py +4 -4
- bumble/att.py +2 -6
- bumble/avc.py +7 -7
- bumble/avctp.py +3 -3
- bumble/avdtp.py +16 -20
- bumble/avrcp.py +42 -54
- bumble/colors.py +2 -2
- bumble/controller.py +174 -45
- bumble/device.py +398 -182
- bumble/drivers/__init__.py +2 -2
- bumble/drivers/common.py +0 -2
- bumble/drivers/intel.py +37 -40
- bumble/drivers/rtk.py +28 -35
- bumble/gatt.py +4 -4
- bumble/gatt_adapters.py +4 -5
- bumble/gatt_client.py +26 -31
- bumble/gatt_server.py +7 -11
- bumble/hci.py +2648 -2909
- bumble/helpers.py +4 -5
- bumble/hfp.py +32 -37
- bumble/host.py +104 -35
- bumble/keys.py +5 -5
- bumble/l2cap.py +312 -409
- bumble/link.py +16 -280
- bumble/logging.py +65 -0
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/config.py +2 -2
- bumble/pandora/device.py +6 -6
- bumble/pandora/host.py +27 -28
- bumble/pandora/l2cap.py +2 -2
- bumble/pandora/security.py +6 -6
- bumble/pandora/utils.py +3 -3
- bumble/profiles/ams.py +404 -0
- bumble/profiles/ascs.py +142 -131
- bumble/profiles/asha.py +2 -2
- bumble/profiles/bap.py +3 -4
- bumble/profiles/csip.py +2 -2
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +2 -2
- bumble/profiles/hap.py +34 -33
- bumble/profiles/le_audio.py +4 -4
- bumble/profiles/mcp.py +4 -4
- bumble/profiles/vcs.py +3 -5
- bumble/rfcomm.py +10 -10
- bumble/rtp.py +1 -2
- bumble/sdp.py +2 -2
- bumble/smp.py +62 -63
- bumble/tools/intel_util.py +3 -2
- bumble/tools/rtk_util.py +6 -5
- bumble/transport/__init__.py +2 -16
- bumble/transport/android_netsim.py +5 -5
- bumble/transport/common.py +4 -4
- bumble/transport/pyusb.py +2 -2
- bumble/utils.py +2 -5
- bumble/vendor/android/hci.py +118 -200
- bumble/vendor/zephyr/hci.py +32 -27
- {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/METADATA +4 -3
- {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/RECORD +89 -90
- {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/WHEEL +1 -1
- {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/entry_points.txt +0 -1
- bumble/apps/link_relay/__init__.py +0 -0
- bumble/apps/link_relay/link_relay.py +0 -289
- bumble/apps/link_relay/logging.yml +0 -21
- {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/licenses/LICENSE +0 -0
- {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,
|
|
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:
|
|
52
|
-
op_code:
|
|
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:
|
|
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
|
-
@
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
) ->
|
|
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
|
-
) ->
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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
|
-
) ->
|
|
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) ->
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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:
|
|
727
|
-
source_ase:
|
|
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
|
|
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[
|
|
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) ->
|
|
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:
|
|
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:
|
|
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
|
|
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) ->
|
|
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
|
|
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[
|
|
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,
|
|
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,
|
|
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
|