bumble 0.0.198__py3-none-any.whl → 0.0.200__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 +502 -202
- bumble/apps/controller_info.py +60 -0
- bumble/apps/pair.py +32 -5
- bumble/apps/player/player.py +608 -0
- bumble/apps/speaker/speaker.py +25 -27
- bumble/att.py +57 -41
- bumble/avc.py +1 -2
- bumble/avdtp.py +56 -99
- bumble/avrcp.py +48 -29
- bumble/codecs.py +214 -68
- bumble/decoder.py +14 -10
- bumble/device.py +19 -11
- bumble/drivers/rtk.py +19 -5
- bumble/gatt.py +24 -19
- bumble/gatt_client.py +5 -25
- bumble/gatt_server.py +14 -6
- bumble/hci.py +298 -7
- bumble/hfp.py +52 -48
- bumble/host.py +28 -6
- bumble/pandora/__init__.py +3 -0
- bumble/pandora/l2cap.py +310 -0
- bumble/profiles/aics.py +520 -0
- bumble/profiles/asha.py +295 -0
- bumble/profiles/hap.py +674 -0
- bumble/profiles/vcp.py +5 -3
- bumble/rtp.py +110 -0
- bumble/smp.py +23 -4
- bumble/transport/android_netsim.py +3 -0
- bumble/transport/pyusb.py +20 -2
- {bumble-0.0.198.dist-info → bumble-0.0.200.dist-info}/METADATA +2 -2
- {bumble-0.0.198.dist-info → bumble-0.0.200.dist-info}/RECORD +36 -31
- {bumble-0.0.198.dist-info → bumble-0.0.200.dist-info}/WHEEL +1 -1
- {bumble-0.0.198.dist-info → bumble-0.0.200.dist-info}/entry_points.txt +1 -0
- bumble/profiles/asha_service.py +0 -193
- {bumble-0.0.198.dist-info → bumble-0.0.200.dist-info}/LICENSE +0 -0
- {bumble-0.0.198.dist-info → bumble-0.0.200.dist-info}/top_level.txt +0 -0
bumble/att.py
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
# Imports
|
|
24
24
|
# -----------------------------------------------------------------------------
|
|
25
25
|
from __future__ import annotations
|
|
26
|
+
|
|
26
27
|
import enum
|
|
27
28
|
import functools
|
|
28
29
|
import inspect
|
|
@@ -41,6 +42,7 @@ from typing import (
|
|
|
41
42
|
|
|
42
43
|
from pyee import EventEmitter
|
|
43
44
|
|
|
45
|
+
from bumble import utils
|
|
44
46
|
from bumble.core import UUID, name_or_number, ProtocolError
|
|
45
47
|
from bumble.hci import HCI_Object, key_with_value
|
|
46
48
|
from bumble.colors import color
|
|
@@ -145,43 +147,57 @@ ATT_RESPONSES = [
|
|
|
145
147
|
ATT_EXECUTE_WRITE_RESPONSE
|
|
146
148
|
]
|
|
147
149
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
150
|
+
class ErrorCode(utils.OpenIntEnum):
|
|
151
|
+
'''
|
|
152
|
+
See
|
|
153
|
+
|
|
154
|
+
* Bluetooth spec @ Vol 3, Part F - 3.4.1.1 Error Response
|
|
155
|
+
* Core Specification Supplement: Common Profile And Service Error Codes
|
|
156
|
+
'''
|
|
157
|
+
INVALID_HANDLE = 0x01
|
|
158
|
+
READ_NOT_PERMITTED = 0x02
|
|
159
|
+
WRITE_NOT_PERMITTED = 0x03
|
|
160
|
+
INVALID_PDU = 0x04
|
|
161
|
+
INSUFFICIENT_AUTHENTICATION = 0x05
|
|
162
|
+
REQUEST_NOT_SUPPORTED = 0x06
|
|
163
|
+
INVALID_OFFSET = 0x07
|
|
164
|
+
INSUFFICIENT_AUTHORIZATION = 0x08
|
|
165
|
+
PREPARE_QUEUE_FULL = 0x09
|
|
166
|
+
ATTRIBUTE_NOT_FOUND = 0x0A
|
|
167
|
+
ATTRIBUTE_NOT_LONG = 0x0B
|
|
168
|
+
INSUFFICIENT_ENCRYPTION_KEY_SIZE = 0x0C
|
|
169
|
+
INVALID_ATTRIBUTE_LENGTH = 0x0D
|
|
170
|
+
UNLIKELY_ERROR = 0x0E
|
|
171
|
+
INSUFFICIENT_ENCRYPTION = 0x0F
|
|
172
|
+
UNSUPPORTED_GROUP_TYPE = 0x10
|
|
173
|
+
INSUFFICIENT_RESOURCES = 0x11
|
|
174
|
+
DATABASE_OUT_OF_SYNC = 0x12
|
|
175
|
+
VALUE_NOT_ALLOWED = 0x13
|
|
176
|
+
# 0x80 – 0x9F: Application Error
|
|
177
|
+
# 0xE0 – 0xFF: Common Profile and Service Error Codes
|
|
178
|
+
WRITE_REQUEST_REJECTED = 0xFC
|
|
179
|
+
CCCD_IMPROPERLY_CONFIGURED = 0xFD
|
|
180
|
+
PROCEDURE_ALREADY_IN_PROGRESS = 0xFE
|
|
181
|
+
OUT_OF_RANGE = 0xFF
|
|
182
|
+
|
|
183
|
+
# Backward Compatible Constants
|
|
184
|
+
ATT_INVALID_HANDLE_ERROR = ErrorCode.INVALID_HANDLE
|
|
185
|
+
ATT_READ_NOT_PERMITTED_ERROR = ErrorCode.READ_NOT_PERMITTED
|
|
186
|
+
ATT_WRITE_NOT_PERMITTED_ERROR = ErrorCode.WRITE_NOT_PERMITTED
|
|
187
|
+
ATT_INVALID_PDU_ERROR = ErrorCode.INVALID_PDU
|
|
188
|
+
ATT_INSUFFICIENT_AUTHENTICATION_ERROR = ErrorCode.INSUFFICIENT_AUTHENTICATION
|
|
189
|
+
ATT_REQUEST_NOT_SUPPORTED_ERROR = ErrorCode.REQUEST_NOT_SUPPORTED
|
|
190
|
+
ATT_INVALID_OFFSET_ERROR = ErrorCode.INVALID_OFFSET
|
|
191
|
+
ATT_INSUFFICIENT_AUTHORIZATION_ERROR = ErrorCode.INSUFFICIENT_AUTHORIZATION
|
|
192
|
+
ATT_PREPARE_QUEUE_FULL_ERROR = ErrorCode.PREPARE_QUEUE_FULL
|
|
193
|
+
ATT_ATTRIBUTE_NOT_FOUND_ERROR = ErrorCode.ATTRIBUTE_NOT_FOUND
|
|
194
|
+
ATT_ATTRIBUTE_NOT_LONG_ERROR = ErrorCode.ATTRIBUTE_NOT_LONG
|
|
195
|
+
ATT_INSUFFICIENT_ENCRYPTION_KEY_SIZE_ERROR = ErrorCode.INSUFFICIENT_ENCRYPTION_KEY_SIZE
|
|
196
|
+
ATT_INVALID_ATTRIBUTE_LENGTH_ERROR = ErrorCode.INVALID_ATTRIBUTE_LENGTH
|
|
197
|
+
ATT_UNLIKELY_ERROR_ERROR = ErrorCode.UNLIKELY_ERROR
|
|
198
|
+
ATT_INSUFFICIENT_ENCRYPTION_ERROR = ErrorCode.INSUFFICIENT_ENCRYPTION
|
|
199
|
+
ATT_UNSUPPORTED_GROUP_TYPE_ERROR = ErrorCode.UNSUPPORTED_GROUP_TYPE
|
|
200
|
+
ATT_INSUFFICIENT_RESOURCES_ERROR = ErrorCode.INSUFFICIENT_RESOURCES
|
|
185
201
|
|
|
186
202
|
ATT_DEFAULT_MTU = 23
|
|
187
203
|
|
|
@@ -245,9 +261,9 @@ class ATT_PDU:
|
|
|
245
261
|
def pdu_name(op_code):
|
|
246
262
|
return name_or_number(ATT_PDU_NAMES, op_code, 2)
|
|
247
263
|
|
|
248
|
-
@
|
|
249
|
-
def error_name(error_code):
|
|
250
|
-
return
|
|
264
|
+
@classmethod
|
|
265
|
+
def error_name(cls, error_code: int) -> str:
|
|
266
|
+
return ErrorCode(error_code).name
|
|
251
267
|
|
|
252
268
|
@staticmethod
|
|
253
269
|
def subclass(fields):
|
|
@@ -795,7 +811,7 @@ class Attribute(EventEmitter):
|
|
|
795
811
|
enum_list: List[str] = [p.name for p in cls if p.name is not None]
|
|
796
812
|
enum_list_str = ",".join(enum_list)
|
|
797
813
|
raise TypeError(
|
|
798
|
-
f"Attribute::permissions error:\nExpected a string containing any of the keys, separated by commas: {enum_list_str
|
|
814
|
+
f"Attribute::permissions error:\nExpected a string containing any of the keys, separated by commas: {enum_list_str}\nGot: {permissions_str}"
|
|
799
815
|
) from exc
|
|
800
816
|
|
|
801
817
|
# Permission flags(legacy-use only)
|
bumble/avc.py
CHANGED
|
@@ -119,7 +119,7 @@ class Frame:
|
|
|
119
119
|
# Not supported
|
|
120
120
|
raise NotImplementedError("extended subunit types not supported")
|
|
121
121
|
|
|
122
|
-
if subunit_id < 5:
|
|
122
|
+
if subunit_id < 5 or subunit_id == 7:
|
|
123
123
|
opcode_offset = 2
|
|
124
124
|
elif subunit_id == 5:
|
|
125
125
|
# Extended to the next byte
|
|
@@ -132,7 +132,6 @@ class Frame:
|
|
|
132
132
|
else:
|
|
133
133
|
subunit_id = 5 + extension
|
|
134
134
|
opcode_offset = 3
|
|
135
|
-
|
|
136
135
|
elif subunit_id == 6:
|
|
137
136
|
raise core.InvalidPacketError("reserved subunit ID")
|
|
138
137
|
|
bumble/avdtp.py
CHANGED
|
@@ -17,12 +17,10 @@
|
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
import asyncio
|
|
20
|
-
import struct
|
|
21
20
|
import time
|
|
22
21
|
import logging
|
|
23
22
|
import enum
|
|
24
23
|
import warnings
|
|
25
|
-
from pyee import EventEmitter
|
|
26
24
|
from typing import (
|
|
27
25
|
Any,
|
|
28
26
|
Awaitable,
|
|
@@ -39,6 +37,8 @@ from typing import (
|
|
|
39
37
|
cast,
|
|
40
38
|
)
|
|
41
39
|
|
|
40
|
+
from pyee import EventEmitter
|
|
41
|
+
|
|
42
42
|
from .core import (
|
|
43
43
|
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
|
|
44
44
|
InvalidStateError,
|
|
@@ -51,13 +51,16 @@ from .a2dp import (
|
|
|
51
51
|
A2DP_MPEG_2_4_AAC_CODEC_TYPE,
|
|
52
52
|
A2DP_NON_A2DP_CODEC_TYPE,
|
|
53
53
|
A2DP_SBC_CODEC_TYPE,
|
|
54
|
+
A2DP_VENDOR_MEDIA_CODEC_INFORMATION_CLASSES,
|
|
54
55
|
AacMediaCodecInformation,
|
|
55
56
|
SbcMediaCodecInformation,
|
|
56
57
|
VendorSpecificMediaCodecInformation,
|
|
57
58
|
)
|
|
59
|
+
from .rtp import MediaPacket
|
|
58
60
|
from . import sdp, device, l2cap
|
|
59
61
|
from .colors import color
|
|
60
62
|
|
|
63
|
+
|
|
61
64
|
# -----------------------------------------------------------------------------
|
|
62
65
|
# Logging
|
|
63
66
|
# -----------------------------------------------------------------------------
|
|
@@ -278,95 +281,6 @@ class RealtimeClock:
|
|
|
278
281
|
await asyncio.sleep(duration)
|
|
279
282
|
|
|
280
283
|
|
|
281
|
-
# -----------------------------------------------------------------------------
|
|
282
|
-
class MediaPacket:
|
|
283
|
-
@staticmethod
|
|
284
|
-
def from_bytes(data: bytes) -> MediaPacket:
|
|
285
|
-
version = (data[0] >> 6) & 0x03
|
|
286
|
-
padding = (data[0] >> 5) & 0x01
|
|
287
|
-
extension = (data[0] >> 4) & 0x01
|
|
288
|
-
csrc_count = data[0] & 0x0F
|
|
289
|
-
marker = (data[1] >> 7) & 0x01
|
|
290
|
-
payload_type = data[1] & 0x7F
|
|
291
|
-
sequence_number = struct.unpack_from('>H', data, 2)[0]
|
|
292
|
-
timestamp = struct.unpack_from('>I', data, 4)[0]
|
|
293
|
-
ssrc = struct.unpack_from('>I', data, 8)[0]
|
|
294
|
-
csrc_list = [
|
|
295
|
-
struct.unpack_from('>I', data, 12 + i)[0] for i in range(csrc_count)
|
|
296
|
-
]
|
|
297
|
-
payload = data[12 + csrc_count * 4 :]
|
|
298
|
-
|
|
299
|
-
return MediaPacket(
|
|
300
|
-
version,
|
|
301
|
-
padding,
|
|
302
|
-
extension,
|
|
303
|
-
marker,
|
|
304
|
-
sequence_number,
|
|
305
|
-
timestamp,
|
|
306
|
-
ssrc,
|
|
307
|
-
csrc_list,
|
|
308
|
-
payload_type,
|
|
309
|
-
payload,
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
def __init__(
|
|
313
|
-
self,
|
|
314
|
-
version: int,
|
|
315
|
-
padding: int,
|
|
316
|
-
extension: int,
|
|
317
|
-
marker: int,
|
|
318
|
-
sequence_number: int,
|
|
319
|
-
timestamp: int,
|
|
320
|
-
ssrc: int,
|
|
321
|
-
csrc_list: List[int],
|
|
322
|
-
payload_type: int,
|
|
323
|
-
payload: bytes,
|
|
324
|
-
) -> None:
|
|
325
|
-
self.version = version
|
|
326
|
-
self.padding = padding
|
|
327
|
-
self.extension = extension
|
|
328
|
-
self.marker = marker
|
|
329
|
-
self.sequence_number = sequence_number & 0xFFFF
|
|
330
|
-
self.timestamp = timestamp & 0xFFFFFFFF
|
|
331
|
-
self.ssrc = ssrc
|
|
332
|
-
self.csrc_list = csrc_list
|
|
333
|
-
self.payload_type = payload_type
|
|
334
|
-
self.payload = payload
|
|
335
|
-
|
|
336
|
-
def __bytes__(self) -> bytes:
|
|
337
|
-
header = bytes(
|
|
338
|
-
[
|
|
339
|
-
self.version << 6
|
|
340
|
-
| self.padding << 5
|
|
341
|
-
| self.extension << 4
|
|
342
|
-
| len(self.csrc_list),
|
|
343
|
-
self.marker << 7 | self.payload_type,
|
|
344
|
-
]
|
|
345
|
-
) + struct.pack(
|
|
346
|
-
'>HII',
|
|
347
|
-
self.sequence_number,
|
|
348
|
-
self.timestamp,
|
|
349
|
-
self.ssrc,
|
|
350
|
-
)
|
|
351
|
-
for csrc in self.csrc_list:
|
|
352
|
-
header += struct.pack('>I', csrc)
|
|
353
|
-
return header + self.payload
|
|
354
|
-
|
|
355
|
-
def __str__(self) -> str:
|
|
356
|
-
return (
|
|
357
|
-
f'RTP(v={self.version},'
|
|
358
|
-
f'p={self.padding},'
|
|
359
|
-
f'x={self.extension},'
|
|
360
|
-
f'm={self.marker},'
|
|
361
|
-
f'pt={self.payload_type},'
|
|
362
|
-
f'sn={self.sequence_number},'
|
|
363
|
-
f'ts={self.timestamp},'
|
|
364
|
-
f'ssrc={self.ssrc},'
|
|
365
|
-
f'csrcs={self.csrc_list},'
|
|
366
|
-
f'payload_size={len(self.payload)})'
|
|
367
|
-
)
|
|
368
|
-
|
|
369
|
-
|
|
370
284
|
# -----------------------------------------------------------------------------
|
|
371
285
|
class MediaPacketPump:
|
|
372
286
|
pump_task: Optional[asyncio.Task]
|
|
@@ -377,6 +291,7 @@ class MediaPacketPump:
|
|
|
377
291
|
self.packets = packets
|
|
378
292
|
self.clock = clock
|
|
379
293
|
self.pump_task = None
|
|
294
|
+
self.completed = asyncio.Event()
|
|
380
295
|
|
|
381
296
|
async def start(self, rtp_channel: l2cap.ClassicChannel) -> None:
|
|
382
297
|
async def pump_packets():
|
|
@@ -406,6 +321,8 @@ class MediaPacketPump:
|
|
|
406
321
|
)
|
|
407
322
|
except asyncio.exceptions.CancelledError:
|
|
408
323
|
logger.debug('pump canceled')
|
|
324
|
+
finally:
|
|
325
|
+
self.completed.set()
|
|
409
326
|
|
|
410
327
|
# Pump packets
|
|
411
328
|
self.pump_task = asyncio.create_task(pump_packets())
|
|
@@ -417,6 +334,9 @@ class MediaPacketPump:
|
|
|
417
334
|
await self.pump_task
|
|
418
335
|
self.pump_task = None
|
|
419
336
|
|
|
337
|
+
async def wait_for_completion(self) -> None:
|
|
338
|
+
await self.completed.wait()
|
|
339
|
+
|
|
420
340
|
|
|
421
341
|
# -----------------------------------------------------------------------------
|
|
422
342
|
class MessageAssembler:
|
|
@@ -580,10 +500,10 @@ class ServiceCapabilities:
|
|
|
580
500
|
self.service_category = service_category
|
|
581
501
|
self.service_capabilities_bytes = service_capabilities_bytes
|
|
582
502
|
|
|
583
|
-
def to_string(self, details: List[str] =
|
|
503
|
+
def to_string(self, details: Optional[List[str]] = None) -> str:
|
|
584
504
|
attributes = ','.join(
|
|
585
505
|
[name_or_number(AVDTP_SERVICE_CATEGORY_NAMES, self.service_category)]
|
|
586
|
-
+ details
|
|
506
|
+
+ (details or [])
|
|
587
507
|
)
|
|
588
508
|
return f'ServiceCapabilities({attributes})'
|
|
589
509
|
|
|
@@ -615,11 +535,25 @@ class MediaCodecCapabilities(ServiceCapabilities):
|
|
|
615
535
|
self.media_codec_information
|
|
616
536
|
)
|
|
617
537
|
elif self.media_codec_type == A2DP_NON_A2DP_CODEC_TYPE:
|
|
618
|
-
|
|
538
|
+
vendor_media_codec_information = (
|
|
619
539
|
VendorSpecificMediaCodecInformation.from_bytes(
|
|
620
540
|
self.media_codec_information
|
|
621
541
|
)
|
|
622
542
|
)
|
|
543
|
+
if (
|
|
544
|
+
vendor_class_map := A2DP_VENDOR_MEDIA_CODEC_INFORMATION_CLASSES.get(
|
|
545
|
+
vendor_media_codec_information.vendor_id
|
|
546
|
+
)
|
|
547
|
+
) and (
|
|
548
|
+
media_codec_information_class := vendor_class_map.get(
|
|
549
|
+
vendor_media_codec_information.codec_id
|
|
550
|
+
)
|
|
551
|
+
):
|
|
552
|
+
self.media_codec_information = media_codec_information_class.from_bytes(
|
|
553
|
+
vendor_media_codec_information.value
|
|
554
|
+
)
|
|
555
|
+
else:
|
|
556
|
+
self.media_codec_information = vendor_media_codec_information
|
|
623
557
|
|
|
624
558
|
def __init__(
|
|
625
559
|
self,
|
|
@@ -1316,10 +1250,20 @@ class Protocol(EventEmitter):
|
|
|
1316
1250
|
return None
|
|
1317
1251
|
|
|
1318
1252
|
def add_source(
|
|
1319
|
-
self,
|
|
1253
|
+
self,
|
|
1254
|
+
codec_capabilities: MediaCodecCapabilities,
|
|
1255
|
+
packet_pump: MediaPacketPump,
|
|
1256
|
+
delay_reporting: bool = False,
|
|
1320
1257
|
) -> LocalSource:
|
|
1321
1258
|
seid = len(self.local_endpoints) + 1
|
|
1322
|
-
|
|
1259
|
+
service_capabilities = (
|
|
1260
|
+
[ServiceCapabilities(AVDTP_DELAY_REPORTING_SERVICE_CATEGORY)]
|
|
1261
|
+
if delay_reporting
|
|
1262
|
+
else []
|
|
1263
|
+
)
|
|
1264
|
+
source = LocalSource(
|
|
1265
|
+
self, seid, codec_capabilities, service_capabilities, packet_pump
|
|
1266
|
+
)
|
|
1323
1267
|
self.local_endpoints.append(source)
|
|
1324
1268
|
|
|
1325
1269
|
return source
|
|
@@ -1372,7 +1316,7 @@ class Protocol(EventEmitter):
|
|
|
1372
1316
|
return self.remote_endpoints.values()
|
|
1373
1317
|
|
|
1374
1318
|
def find_remote_sink_by_codec(
|
|
1375
|
-
self, media_type: int, codec_type: int
|
|
1319
|
+
self, media_type: int, codec_type: int, vendor_id: int = 0, codec_id: int = 0
|
|
1376
1320
|
) -> Optional[DiscoveredStreamEndPoint]:
|
|
1377
1321
|
for endpoint in self.remote_endpoints.values():
|
|
1378
1322
|
if (
|
|
@@ -1397,7 +1341,19 @@ class Protocol(EventEmitter):
|
|
|
1397
1341
|
codec_capabilities.media_type == AVDTP_AUDIO_MEDIA_TYPE
|
|
1398
1342
|
and codec_capabilities.media_codec_type == codec_type
|
|
1399
1343
|
):
|
|
1400
|
-
|
|
1344
|
+
if isinstance(
|
|
1345
|
+
codec_capabilities.media_codec_information,
|
|
1346
|
+
VendorSpecificMediaCodecInformation,
|
|
1347
|
+
):
|
|
1348
|
+
if (
|
|
1349
|
+
codec_capabilities.media_codec_information.vendor_id
|
|
1350
|
+
== vendor_id
|
|
1351
|
+
and codec_capabilities.media_codec_information.codec_id
|
|
1352
|
+
== codec_id
|
|
1353
|
+
):
|
|
1354
|
+
has_codec = True
|
|
1355
|
+
else:
|
|
1356
|
+
has_codec = True
|
|
1401
1357
|
if has_media_transport and has_codec:
|
|
1402
1358
|
return endpoint
|
|
1403
1359
|
|
|
@@ -2180,12 +2136,13 @@ class LocalSource(LocalStreamEndPoint):
|
|
|
2180
2136
|
protocol: Protocol,
|
|
2181
2137
|
seid: int,
|
|
2182
2138
|
codec_capabilities: MediaCodecCapabilities,
|
|
2139
|
+
other_capabilitiles: Iterable[ServiceCapabilities],
|
|
2183
2140
|
packet_pump: MediaPacketPump,
|
|
2184
2141
|
) -> None:
|
|
2185
2142
|
capabilities = [
|
|
2186
2143
|
ServiceCapabilities(AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY),
|
|
2187
2144
|
codec_capabilities,
|
|
2188
|
-
]
|
|
2145
|
+
] + list(other_capabilitiles)
|
|
2189
2146
|
super().__init__(
|
|
2190
2147
|
protocol,
|
|
2191
2148
|
seid,
|
bumble/avrcp.py
CHANGED
|
@@ -1491,10 +1491,14 @@ class Protocol(pyee.EventEmitter):
|
|
|
1491
1491
|
f"<<< AVCTP Command, transaction_label={transaction_label}: " f"{command}"
|
|
1492
1492
|
)
|
|
1493
1493
|
|
|
1494
|
-
# Only the PANEL subunit
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1494
|
+
# Only addressing the unit, or the PANEL subunit with subunit ID 0 is supported
|
|
1495
|
+
# in this profile.
|
|
1496
|
+
if not (
|
|
1497
|
+
command.subunit_type == avc.Frame.SubunitType.UNIT
|
|
1498
|
+
and command.subunit_id == 7
|
|
1499
|
+
) and not (
|
|
1500
|
+
command.subunit_type == avc.Frame.SubunitType.PANEL
|
|
1501
|
+
and command.subunit_id == 0
|
|
1498
1502
|
):
|
|
1499
1503
|
logger.debug("subunit not supported")
|
|
1500
1504
|
self.send_not_implemented_response(transaction_label, command)
|
|
@@ -1528,8 +1532,8 @@ class Protocol(pyee.EventEmitter):
|
|
|
1528
1532
|
# TODO: delegate
|
|
1529
1533
|
response = avc.PassThroughResponseFrame(
|
|
1530
1534
|
avc.ResponseFrame.ResponseCode.ACCEPTED,
|
|
1531
|
-
|
|
1532
|
-
|
|
1535
|
+
command.subunit_type,
|
|
1536
|
+
command.subunit_id,
|
|
1533
1537
|
command.state_flag,
|
|
1534
1538
|
command.operation_id,
|
|
1535
1539
|
command.operation_data,
|
|
@@ -1846,6 +1850,15 @@ class Protocol(pyee.EventEmitter):
|
|
|
1846
1850
|
RejectedResponse(pdu_id, status_code),
|
|
1847
1851
|
)
|
|
1848
1852
|
|
|
1853
|
+
def send_not_implemented_avrcp_response(
|
|
1854
|
+
self, transaction_label: int, pdu_id: Protocol.PduId
|
|
1855
|
+
) -> None:
|
|
1856
|
+
self.send_avrcp_response(
|
|
1857
|
+
transaction_label,
|
|
1858
|
+
avc.ResponseFrame.ResponseCode.NOT_IMPLEMENTED,
|
|
1859
|
+
NotImplementedResponse(pdu_id, b''),
|
|
1860
|
+
)
|
|
1861
|
+
|
|
1849
1862
|
def _on_get_capabilities_command(
|
|
1850
1863
|
self, transaction_label: int, command: GetCapabilitiesCommand
|
|
1851
1864
|
) -> None:
|
|
@@ -1891,29 +1904,35 @@ class Protocol(pyee.EventEmitter):
|
|
|
1891
1904
|
async def register_notification():
|
|
1892
1905
|
# Check if the event is supported.
|
|
1893
1906
|
supported_events = await self.delegate.get_supported_events()
|
|
1894
|
-
if command.event_id in supported_events:
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
avc.ResponseFrame.ResponseCode.INTERIM,
|
|
1901
|
-
response,
|
|
1902
|
-
)
|
|
1903
|
-
self._register_notification_listener(transaction_label, command)
|
|
1904
|
-
return
|
|
1907
|
+
if command.event_id not in supported_events:
|
|
1908
|
+
logger.debug("event not supported")
|
|
1909
|
+
self.send_not_implemented_avrcp_response(
|
|
1910
|
+
transaction_label, self.PduId.REGISTER_NOTIFICATION
|
|
1911
|
+
)
|
|
1912
|
+
return
|
|
1905
1913
|
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1914
|
+
if command.event_id == EventId.VOLUME_CHANGED:
|
|
1915
|
+
volume = await self.delegate.get_absolute_volume()
|
|
1916
|
+
response = RegisterNotificationResponse(VolumeChangedEvent(volume))
|
|
1917
|
+
self.send_avrcp_response(
|
|
1918
|
+
transaction_label,
|
|
1919
|
+
avc.ResponseFrame.ResponseCode.INTERIM,
|
|
1920
|
+
response,
|
|
1921
|
+
)
|
|
1922
|
+
self._register_notification_listener(transaction_label, command)
|
|
1923
|
+
return
|
|
1924
|
+
|
|
1925
|
+
if command.event_id == EventId.PLAYBACK_STATUS_CHANGED:
|
|
1926
|
+
# TODO: testing only, use delegate
|
|
1927
|
+
response = RegisterNotificationResponse(
|
|
1928
|
+
PlaybackStatusChangedEvent(play_status=PlayStatus.PLAYING)
|
|
1929
|
+
)
|
|
1930
|
+
self.send_avrcp_response(
|
|
1931
|
+
transaction_label,
|
|
1932
|
+
avc.ResponseFrame.ResponseCode.INTERIM,
|
|
1933
|
+
response,
|
|
1934
|
+
)
|
|
1935
|
+
self._register_notification_listener(transaction_label, command)
|
|
1936
|
+
return
|
|
1918
1937
|
|
|
1919
1938
|
self._delegate_command(transaction_label, command, register_notification())
|