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.
- bumble/_version.py +2 -2
- bumble/a2dp.py +6 -0
- bumble/apps/README.md +0 -3
- bumble/apps/auracast.py +11 -9
- bumble/apps/bench.py +480 -31
- bumble/apps/console.py +3 -3
- bumble/apps/controller_info.py +47 -10
- bumble/apps/controller_loopback.py +7 -3
- bumble/apps/controllers.py +2 -2
- bumble/apps/device_info.py +2 -2
- bumble/apps/gatt_dump.py +2 -2
- bumble/apps/gg_bridge.py +2 -2
- bumble/apps/hci_bridge.py +2 -2
- bumble/apps/l2cap_bridge.py +2 -2
- bumble/apps/lea_unicast/app.py +6 -1
- bumble/apps/pair.py +19 -11
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +2 -2
- bumble/apps/show.py +4 -2
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +126 -18
- 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 +41 -53
- bumble/colors.py +2 -2
- bumble/controller.py +84 -23
- bumble/device.py +348 -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 +2601 -2909
- bumble/helpers.py +4 -5
- bumble/hfp.py +32 -37
- bumble/host.py +94 -35
- bumble/keys.py +5 -5
- bumble/l2cap.py +310 -394
- bumble/link.py +6 -270
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +1 -1
- 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/ascs.py +132 -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 +57 -61
- bumble/tools/rtk_util.py +2 -2
- 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.213.dist-info}/METADATA +2 -2
- {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/RECORD +83 -86
- {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
- {bumble-0.0.212.dist-info → bumble-0.0.213.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.213.dist-info}/licenses/LICENSE +0 -0
- {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,
|
|
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:
|
|
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()
|
bumble/pandora/security.py
CHANGED
|
@@ -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,
|
|
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.
|
|
248
|
-
hci.
|
|
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.
|
|
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:
|
|
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,
|
|
25
|
+
from typing import Any, Generator, MutableMapping, Optional
|
|
26
26
|
|
|
27
|
-
ADDRESS_TYPES:
|
|
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
|
-
) ->
|
|
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,
|
|
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
|
-
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
@@ -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) ->
|
|
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) ->
|
|
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) ->
|
|
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
|
-
) ->
|
|
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) ->
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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:
|
|
727
|
-
source_ase:
|
|
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
|
|
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
|