bumble 0.0.223__py3-none-any.whl → 0.0.224__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/apps/controller_info.py +90 -114
- bumble/apps/controller_loopback.py +11 -9
- bumble/apps/gg_bridge.py +1 -1
- bumble/apps/hci_bridge.py +3 -1
- bumble/apps/l2cap_bridge.py +1 -1
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +10 -4
- bumble/apps/speaker/speaker.py +1 -1
- bumble/avrcp.py +366 -190
- bumble/bridge.py +10 -2
- bumble/controller.py +14 -1
- bumble/core.py +1 -1
- bumble/device.py +998 -573
- bumble/drivers/intel.py +45 -39
- bumble/drivers/rtk.py +76 -40
- bumble/hci.py +1318 -796
- bumble/host.py +329 -157
- bumble/l2cap.py +10 -5
- bumble/smp.py +8 -3
- bumble/snoop.py +111 -1
- bumble/transport/android_netsim.py +1 -1
- bumble/vendor/android/hci.py +108 -86
- bumble/vendor/zephyr/hci.py +24 -18
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/METADATA +4 -3
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/RECORD +30 -30
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/WHEEL +1 -1
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/top_level.txt +0 -0
bumble/avrcp.py
CHANGED
|
@@ -26,7 +26,7 @@ from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequen
|
|
|
26
26
|
from dataclasses import dataclass, field
|
|
27
27
|
from typing import ClassVar, SupportsBytes, TypeVar
|
|
28
28
|
|
|
29
|
-
from bumble import avc, avctp, core, hci, l2cap, utils
|
|
29
|
+
from bumble import avc, avctp, core, hci, l2cap, sdp, utils
|
|
30
30
|
from bumble.colors import color
|
|
31
31
|
from bumble.device import Connection, Device
|
|
32
32
|
from bumble.sdp import (
|
|
@@ -55,13 +55,15 @@ AVRCP_PID = 0x110E
|
|
|
55
55
|
AVRCP_BLUETOOTH_SIG_COMPANY_ID = 0x001958
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
_UINT64_BE_METADATA =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
_UINT64_BE_METADATA = hci.metadata(
|
|
59
|
+
{
|
|
60
|
+
'parser': lambda data, offset: (
|
|
61
|
+
offset + 8,
|
|
62
|
+
int.from_bytes(data[offset : offset + 8], byteorder='big'),
|
|
63
|
+
),
|
|
64
|
+
'serializer': lambda x: x.to_bytes(8, byteorder='big'),
|
|
65
|
+
}
|
|
66
|
+
)
|
|
65
67
|
|
|
66
68
|
|
|
67
69
|
class PduId(utils.OpenIntEnum):
|
|
@@ -92,7 +94,7 @@ class PduId(utils.OpenIntEnum):
|
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
class CharacterSetId(hci.SpecableEnum):
|
|
95
|
-
UTF_8 =
|
|
97
|
+
UTF_8 = 0x6A
|
|
96
98
|
|
|
97
99
|
|
|
98
100
|
class MediaAttributeId(hci.SpecableEnum):
|
|
@@ -192,82 +194,43 @@ class TargetFeatures(enum.IntFlag):
|
|
|
192
194
|
|
|
193
195
|
|
|
194
196
|
# -----------------------------------------------------------------------------
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
avctp_version_int = avctp_version[0] << 8 | avctp_version[1]
|
|
202
|
-
avrcp_version_int = avrcp_version[0] << 8 | avrcp_version[1]
|
|
197
|
+
@dataclass
|
|
198
|
+
class ControllerServiceSdpRecord:
|
|
199
|
+
service_record_handle: int
|
|
200
|
+
avctp_version: tuple[int, int] = (1, 4)
|
|
201
|
+
avrcp_version: tuple[int, int] = (1, 6)
|
|
202
|
+
supported_features: int | ControllerFeatures = ControllerFeatures(1)
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
),
|
|
213
|
-
ServiceAttribute(
|
|
214
|
-
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
|
215
|
-
DataElement.sequence(
|
|
216
|
-
[
|
|
217
|
-
DataElement.uuid(core.BT_AV_REMOTE_CONTROL_SERVICE),
|
|
218
|
-
DataElement.uuid(core.BT_AV_REMOTE_CONTROL_CONTROLLER_SERVICE),
|
|
219
|
-
]
|
|
204
|
+
def to_service_attributes(self) -> list[ServiceAttribute]:
|
|
205
|
+
avctp_version_int = self.avctp_version[0] << 8 | self.avctp_version[1]
|
|
206
|
+
avrcp_version_int = self.avrcp_version[0] << 8 | self.avrcp_version[1]
|
|
207
|
+
|
|
208
|
+
attributes = [
|
|
209
|
+
ServiceAttribute(
|
|
210
|
+
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
|
211
|
+
DataElement.unsigned_integer_32(self.service_record_handle),
|
|
220
212
|
),
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
DataElement.sequence(
|
|
225
|
-
[
|
|
226
|
-
DataElement.sequence(
|
|
227
|
-
[
|
|
228
|
-
DataElement.uuid(core.BT_L2CAP_PROTOCOL_ID),
|
|
229
|
-
DataElement.unsigned_integer_16(avctp.AVCTP_PSM),
|
|
230
|
-
]
|
|
231
|
-
),
|
|
232
|
-
DataElement.sequence(
|
|
233
|
-
[
|
|
234
|
-
DataElement.uuid(core.BT_AVCTP_PROTOCOL_ID),
|
|
235
|
-
DataElement.unsigned_integer_16(avctp_version_int),
|
|
236
|
-
]
|
|
237
|
-
),
|
|
238
|
-
]
|
|
213
|
+
ServiceAttribute(
|
|
214
|
+
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
|
215
|
+
DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]),
|
|
239
216
|
),
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
DataElement.unsigned_integer_16(avrcp_version_int),
|
|
249
|
-
]
|
|
250
|
-
),
|
|
251
|
-
]
|
|
217
|
+
ServiceAttribute(
|
|
218
|
+
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
|
219
|
+
DataElement.sequence(
|
|
220
|
+
[
|
|
221
|
+
DataElement.uuid(core.BT_AV_REMOTE_CONTROL_SERVICE),
|
|
222
|
+
DataElement.uuid(core.BT_AV_REMOTE_CONTROL_CONTROLLER_SERVICE),
|
|
223
|
+
]
|
|
224
|
+
),
|
|
252
225
|
),
|
|
253
|
-
),
|
|
254
|
-
ServiceAttribute(
|
|
255
|
-
SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID,
|
|
256
|
-
DataElement.unsigned_integer_16(supported_features),
|
|
257
|
-
),
|
|
258
|
-
]
|
|
259
|
-
if supported_features & ControllerFeatures.SUPPORTS_BROWSING:
|
|
260
|
-
attributes.append(
|
|
261
226
|
ServiceAttribute(
|
|
262
|
-
|
|
227
|
+
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
263
228
|
DataElement.sequence(
|
|
264
229
|
[
|
|
265
230
|
DataElement.sequence(
|
|
266
231
|
[
|
|
267
232
|
DataElement.uuid(core.BT_L2CAP_PROTOCOL_ID),
|
|
268
|
-
DataElement.unsigned_integer_16(
|
|
269
|
-
avctp.AVCTP_BROWSING_PSM
|
|
270
|
-
),
|
|
233
|
+
DataElement.unsigned_integer_16(avctp.AVCTP_PSM),
|
|
271
234
|
]
|
|
272
235
|
),
|
|
273
236
|
DataElement.sequence(
|
|
@@ -279,87 +242,130 @@ def make_controller_service_sdp_records(
|
|
|
279
242
|
]
|
|
280
243
|
),
|
|
281
244
|
),
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
avctp_version_int = avctp_version[0] << 8 | avctp_version[1]
|
|
295
|
-
avrcp_version_int = avrcp_version[0] << 8 | avrcp_version[1]
|
|
296
|
-
|
|
297
|
-
attributes = [
|
|
298
|
-
ServiceAttribute(
|
|
299
|
-
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
|
300
|
-
DataElement.unsigned_integer_32(service_record_handle),
|
|
301
|
-
),
|
|
302
|
-
ServiceAttribute(
|
|
303
|
-
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
|
304
|
-
DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]),
|
|
305
|
-
),
|
|
306
|
-
ServiceAttribute(
|
|
307
|
-
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
|
308
|
-
DataElement.sequence(
|
|
309
|
-
[
|
|
310
|
-
DataElement.uuid(core.BT_AV_REMOTE_CONTROL_TARGET_SERVICE),
|
|
311
|
-
]
|
|
245
|
+
ServiceAttribute(
|
|
246
|
+
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
247
|
+
DataElement.sequence(
|
|
248
|
+
[
|
|
249
|
+
DataElement.sequence(
|
|
250
|
+
[
|
|
251
|
+
DataElement.uuid(core.BT_AV_REMOTE_CONTROL_SERVICE),
|
|
252
|
+
DataElement.unsigned_integer_16(avrcp_version_int),
|
|
253
|
+
]
|
|
254
|
+
),
|
|
255
|
+
]
|
|
256
|
+
),
|
|
312
257
|
),
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
DataElement.sequence(
|
|
317
|
-
[
|
|
318
|
-
DataElement.sequence(
|
|
319
|
-
[
|
|
320
|
-
DataElement.uuid(core.BT_L2CAP_PROTOCOL_ID),
|
|
321
|
-
DataElement.unsigned_integer_16(avctp.AVCTP_PSM),
|
|
322
|
-
]
|
|
323
|
-
),
|
|
324
|
-
DataElement.sequence(
|
|
325
|
-
[
|
|
326
|
-
DataElement.uuid(core.BT_AVCTP_PROTOCOL_ID),
|
|
327
|
-
DataElement.unsigned_integer_16(avctp_version_int),
|
|
328
|
-
]
|
|
329
|
-
),
|
|
330
|
-
]
|
|
258
|
+
ServiceAttribute(
|
|
259
|
+
SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID,
|
|
260
|
+
DataElement.unsigned_integer_16(self.supported_features),
|
|
331
261
|
),
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
262
|
+
]
|
|
263
|
+
if self.supported_features & ControllerFeatures.SUPPORTS_BROWSING:
|
|
264
|
+
attributes.append(
|
|
265
|
+
ServiceAttribute(
|
|
266
|
+
SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
337
267
|
DataElement.sequence(
|
|
338
268
|
[
|
|
339
|
-
DataElement.
|
|
340
|
-
|
|
269
|
+
DataElement.sequence(
|
|
270
|
+
[
|
|
271
|
+
DataElement.uuid(core.BT_L2CAP_PROTOCOL_ID),
|
|
272
|
+
DataElement.unsigned_integer_16(
|
|
273
|
+
avctp.AVCTP_BROWSING_PSM
|
|
274
|
+
),
|
|
275
|
+
]
|
|
276
|
+
),
|
|
277
|
+
DataElement.sequence(
|
|
278
|
+
[
|
|
279
|
+
DataElement.uuid(core.BT_AVCTP_PROTOCOL_ID),
|
|
280
|
+
DataElement.unsigned_integer_16(avctp_version_int),
|
|
281
|
+
]
|
|
282
|
+
),
|
|
341
283
|
]
|
|
342
284
|
),
|
|
343
|
-
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
return attributes
|
|
288
|
+
|
|
289
|
+
@classmethod
|
|
290
|
+
async def find(cls, connection: Connection) -> list[ControllerServiceSdpRecord]:
|
|
291
|
+
async with sdp.Client(connection) as sdp_client:
|
|
292
|
+
search_result = await sdp_client.search_attributes(
|
|
293
|
+
uuids=[core.BT_AV_REMOTE_CONTROL_CONTROLLER_SERVICE],
|
|
294
|
+
attribute_ids=[
|
|
295
|
+
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
|
296
|
+
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
297
|
+
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
298
|
+
SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID,
|
|
299
|
+
],
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
records: list[ControllerServiceSdpRecord] = []
|
|
303
|
+
for attribute_lists in search_result:
|
|
304
|
+
record = cls(0)
|
|
305
|
+
for attribute in attribute_lists:
|
|
306
|
+
if attribute.id == SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID:
|
|
307
|
+
record.service_record_handle = attribute.value.value
|
|
308
|
+
elif attribute.id == SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
|
309
|
+
# [[L2CAP, PSM], [AVCTP, version]]
|
|
310
|
+
record.avctp_version = (
|
|
311
|
+
attribute.value.value[1].value[1].value >> 8,
|
|
312
|
+
attribute.value.value[1].value[1].value & 0xFF,
|
|
313
|
+
)
|
|
314
|
+
elif (
|
|
315
|
+
attribute.id
|
|
316
|
+
== SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID
|
|
317
|
+
):
|
|
318
|
+
# [[AV_REMOTE_CONTROL, version]]
|
|
319
|
+
record.avrcp_version = (
|
|
320
|
+
attribute.value.value[0].value[1].value >> 8,
|
|
321
|
+
attribute.value.value[0].value[1].value & 0xFF,
|
|
322
|
+
)
|
|
323
|
+
elif attribute.id == SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID:
|
|
324
|
+
record.supported_features = ControllerFeatures(
|
|
325
|
+
attribute.value.value
|
|
326
|
+
)
|
|
327
|
+
records.append(record)
|
|
328
|
+
return records
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# -----------------------------------------------------------------------------
|
|
332
|
+
@dataclass
|
|
333
|
+
class TargetServiceSdpRecord:
|
|
334
|
+
service_record_handle: int
|
|
335
|
+
avctp_version: tuple[int, int] = (1, 4)
|
|
336
|
+
avrcp_version: tuple[int, int] = (1, 6)
|
|
337
|
+
supported_features: int | TargetFeatures = TargetFeatures(0x23)
|
|
338
|
+
|
|
339
|
+
def to_service_attributes(self) -> list[ServiceAttribute]:
|
|
340
|
+
# TODO: support a way to compute the supported features from a feature list
|
|
341
|
+
avctp_version_int = self.avctp_version[0] << 8 | self.avctp_version[1]
|
|
342
|
+
avrcp_version_int = self.avrcp_version[0] << 8 | self.avrcp_version[1]
|
|
343
|
+
|
|
344
|
+
attributes = [
|
|
345
|
+
ServiceAttribute(
|
|
346
|
+
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
|
347
|
+
DataElement.unsigned_integer_32(self.service_record_handle),
|
|
348
|
+
),
|
|
349
|
+
ServiceAttribute(
|
|
350
|
+
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
|
351
|
+
DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]),
|
|
352
|
+
),
|
|
353
|
+
ServiceAttribute(
|
|
354
|
+
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
|
355
|
+
DataElement.sequence(
|
|
356
|
+
[
|
|
357
|
+
DataElement.uuid(core.BT_AV_REMOTE_CONTROL_TARGET_SERVICE),
|
|
358
|
+
]
|
|
359
|
+
),
|
|
344
360
|
),
|
|
345
|
-
),
|
|
346
|
-
ServiceAttribute(
|
|
347
|
-
SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID,
|
|
348
|
-
DataElement.unsigned_integer_16(supported_features),
|
|
349
|
-
),
|
|
350
|
-
]
|
|
351
|
-
if supported_features & TargetFeatures.SUPPORTS_BROWSING:
|
|
352
|
-
attributes.append(
|
|
353
361
|
ServiceAttribute(
|
|
354
|
-
|
|
362
|
+
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
355
363
|
DataElement.sequence(
|
|
356
364
|
[
|
|
357
365
|
DataElement.sequence(
|
|
358
366
|
[
|
|
359
367
|
DataElement.uuid(core.BT_L2CAP_PROTOCOL_ID),
|
|
360
|
-
DataElement.unsigned_integer_16(
|
|
361
|
-
avctp.AVCTP_BROWSING_PSM
|
|
362
|
-
),
|
|
368
|
+
DataElement.unsigned_integer_16(avctp.AVCTP_PSM),
|
|
363
369
|
]
|
|
364
370
|
),
|
|
365
371
|
DataElement.sequence(
|
|
@@ -371,8 +377,90 @@ def make_target_service_sdp_records(
|
|
|
371
377
|
]
|
|
372
378
|
),
|
|
373
379
|
),
|
|
374
|
-
|
|
375
|
-
|
|
380
|
+
ServiceAttribute(
|
|
381
|
+
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
382
|
+
DataElement.sequence(
|
|
383
|
+
[
|
|
384
|
+
DataElement.sequence(
|
|
385
|
+
[
|
|
386
|
+
DataElement.uuid(core.BT_AV_REMOTE_CONTROL_SERVICE),
|
|
387
|
+
DataElement.unsigned_integer_16(avrcp_version_int),
|
|
388
|
+
]
|
|
389
|
+
),
|
|
390
|
+
]
|
|
391
|
+
),
|
|
392
|
+
),
|
|
393
|
+
ServiceAttribute(
|
|
394
|
+
SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID,
|
|
395
|
+
DataElement.unsigned_integer_16(self.supported_features),
|
|
396
|
+
),
|
|
397
|
+
]
|
|
398
|
+
if self.supported_features & TargetFeatures.SUPPORTS_BROWSING:
|
|
399
|
+
attributes.append(
|
|
400
|
+
ServiceAttribute(
|
|
401
|
+
SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
402
|
+
DataElement.sequence(
|
|
403
|
+
[
|
|
404
|
+
DataElement.sequence(
|
|
405
|
+
[
|
|
406
|
+
DataElement.uuid(core.BT_L2CAP_PROTOCOL_ID),
|
|
407
|
+
DataElement.unsigned_integer_16(
|
|
408
|
+
avctp.AVCTP_BROWSING_PSM
|
|
409
|
+
),
|
|
410
|
+
]
|
|
411
|
+
),
|
|
412
|
+
DataElement.sequence(
|
|
413
|
+
[
|
|
414
|
+
DataElement.uuid(core.BT_AVCTP_PROTOCOL_ID),
|
|
415
|
+
DataElement.unsigned_integer_16(avctp_version_int),
|
|
416
|
+
]
|
|
417
|
+
),
|
|
418
|
+
]
|
|
419
|
+
),
|
|
420
|
+
),
|
|
421
|
+
)
|
|
422
|
+
return attributes
|
|
423
|
+
|
|
424
|
+
@classmethod
|
|
425
|
+
async def find(cls, connection: Connection) -> list[TargetServiceSdpRecord]:
|
|
426
|
+
async with sdp.Client(connection) as sdp_client:
|
|
427
|
+
search_result = await sdp_client.search_attributes(
|
|
428
|
+
uuids=[core.BT_AV_REMOTE_CONTROL_TARGET_SERVICE],
|
|
429
|
+
attribute_ids=[
|
|
430
|
+
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
|
431
|
+
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
432
|
+
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
433
|
+
SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID,
|
|
434
|
+
],
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
records: list[TargetServiceSdpRecord] = []
|
|
438
|
+
for attribute_lists in search_result:
|
|
439
|
+
record = cls(0)
|
|
440
|
+
for attribute in attribute_lists:
|
|
441
|
+
if attribute.id == SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID:
|
|
442
|
+
record.service_record_handle = attribute.value.value
|
|
443
|
+
elif attribute.id == SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
|
444
|
+
# [[L2CAP, PSM], [AVCTP, version]]
|
|
445
|
+
record.avctp_version = (
|
|
446
|
+
attribute.value.value[1].value[1].value >> 8,
|
|
447
|
+
attribute.value.value[1].value[1].value & 0xFF,
|
|
448
|
+
)
|
|
449
|
+
elif (
|
|
450
|
+
attribute.id
|
|
451
|
+
== SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID
|
|
452
|
+
):
|
|
453
|
+
# [[AV_REMOTE_CONTROL, version]]
|
|
454
|
+
record.avrcp_version = (
|
|
455
|
+
attribute.value.value[0].value[1].value >> 8,
|
|
456
|
+
attribute.value.value[0].value[1].value & 0xFF,
|
|
457
|
+
)
|
|
458
|
+
elif attribute.id == SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID:
|
|
459
|
+
record.supported_features = TargetFeatures(
|
|
460
|
+
attribute.value.value
|
|
461
|
+
)
|
|
462
|
+
records.append(record)
|
|
463
|
+
return records
|
|
376
464
|
|
|
377
465
|
|
|
378
466
|
# -----------------------------------------------------------------------------
|
|
@@ -491,14 +579,12 @@ class BrowseableItem:
|
|
|
491
579
|
**hci.HCI_Object.dict_from_bytes(data, offset + 3, subclass.fields)
|
|
492
580
|
)
|
|
493
581
|
instance._payload = data[3:]
|
|
494
|
-
return offset + length, instance
|
|
582
|
+
return offset + length + 3, instance
|
|
495
583
|
|
|
496
584
|
def __bytes__(self) -> bytes:
|
|
497
585
|
if self._payload is None:
|
|
498
586
|
self._payload = hci.HCI_Object.dict_to_bytes(self.__dict__, self.fields)
|
|
499
|
-
return (
|
|
500
|
-
struct.pack('>BH', self.item_type, len(self._payload) + 3) + self._payload
|
|
501
|
-
)
|
|
587
|
+
return struct.pack('>BH', self.item_type, len(self._payload)) + self._payload
|
|
502
588
|
|
|
503
589
|
_Item = TypeVar('_Item', bound='BrowseableItem')
|
|
504
590
|
|
|
@@ -601,11 +687,11 @@ class MediaPlayerItem(BrowseableItem):
|
|
|
601
687
|
metadata=MajorPlayerType.type_metadata(1)
|
|
602
688
|
)
|
|
603
689
|
player_sub_type: PlayerSubType = field(
|
|
604
|
-
metadata=PlayerSubType.type_metadata(4, byteorder='
|
|
690
|
+
metadata=PlayerSubType.type_metadata(4, byteorder='little')
|
|
605
691
|
)
|
|
606
692
|
play_status: PlayStatus = field(metadata=PlayStatus.type_metadata(1))
|
|
607
693
|
feature_bitmask: Features = field(
|
|
608
|
-
metadata=Features.type_metadata(16, byteorder='
|
|
694
|
+
metadata=Features.type_metadata(16, byteorder='little')
|
|
609
695
|
)
|
|
610
696
|
character_set_id: CharacterSetId = field(
|
|
611
697
|
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
@@ -634,7 +720,7 @@ class FolderItem(BrowseableItem):
|
|
|
634
720
|
|
|
635
721
|
folder_uid: int = field(metadata=_UINT64_BE_METADATA)
|
|
636
722
|
folder_type: FolderType = field(metadata=FolderType.type_metadata(1))
|
|
637
|
-
is_playable:
|
|
723
|
+
is_playable: Playable = field(metadata=Playable.type_metadata(1))
|
|
638
724
|
character_set_id: CharacterSetId = field(
|
|
639
725
|
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
640
726
|
)
|
|
@@ -876,7 +962,7 @@ class GetPlayStatusCommand(Command):
|
|
|
876
962
|
class GetElementAttributesCommand(Command):
|
|
877
963
|
pdu_id = PduId.GET_ELEMENT_ATTRIBUTES
|
|
878
964
|
|
|
879
|
-
identifier: int = field(metadata=
|
|
965
|
+
identifier: int = field(metadata=_UINT64_BE_METADATA)
|
|
880
966
|
attribute_ids: Sequence[MediaAttributeId] = field(
|
|
881
967
|
metadata=MediaAttributeId.type_metadata(
|
|
882
968
|
4, list_begin=True, list_end=True, byteorder='big'
|
|
@@ -951,7 +1037,7 @@ class ChangePathCommand(Command):
|
|
|
951
1037
|
|
|
952
1038
|
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
953
1039
|
direction: Direction = field(metadata=Direction.type_metadata(1))
|
|
954
|
-
folder_uid: int = field(metadata=
|
|
1040
|
+
folder_uid: int = field(metadata=_UINT64_BE_METADATA)
|
|
955
1041
|
|
|
956
1042
|
|
|
957
1043
|
# -----------------------------------------------------------------------------
|
|
@@ -961,7 +1047,7 @@ class GetItemAttributesCommand(Command):
|
|
|
961
1047
|
pdu_id = PduId.GET_ITEM_ATTRIBUTES
|
|
962
1048
|
|
|
963
1049
|
scope: Scope = field(metadata=Scope.type_metadata(1))
|
|
964
|
-
uid: int = field(metadata=
|
|
1050
|
+
uid: int = field(metadata=_UINT64_BE_METADATA)
|
|
965
1051
|
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
966
1052
|
start_item: int = field(metadata=hci.metadata('>4'))
|
|
967
1053
|
end_item: int = field(metadata=hci.metadata('>4'))
|
|
@@ -999,7 +1085,7 @@ class PlayItemCommand(Command):
|
|
|
999
1085
|
pdu_id = PduId.PLAY_ITEM
|
|
1000
1086
|
|
|
1001
1087
|
scope: Scope = field(metadata=Scope.type_metadata(1))
|
|
1002
|
-
uid: int = field(metadata=
|
|
1088
|
+
uid: int = field(metadata=_UINT64_BE_METADATA)
|
|
1003
1089
|
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
1004
1090
|
|
|
1005
1091
|
|
|
@@ -1010,7 +1096,7 @@ class AddToNowPlayingCommand(Command):
|
|
|
1010
1096
|
pdu_id = PduId.ADD_TO_NOW_PLAYING
|
|
1011
1097
|
|
|
1012
1098
|
scope: Scope = field(metadata=Scope.type_metadata(1))
|
|
1013
|
-
uid: int = field(metadata=
|
|
1099
|
+
uid: int = field(metadata=_UINT64_BE_METADATA)
|
|
1014
1100
|
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
1015
1101
|
|
|
1016
1102
|
|
|
@@ -1204,6 +1290,10 @@ class InformBatteryStatusOfCtResponse(Response):
|
|
|
1204
1290
|
@dataclass
|
|
1205
1291
|
class GetPlayStatusResponse(Response):
|
|
1206
1292
|
pdu_id = PduId.GET_PLAY_STATUS
|
|
1293
|
+
|
|
1294
|
+
# TG doesn't support Song Length or Position.
|
|
1295
|
+
UNAVAILABLE = 0xFFFFFFFF
|
|
1296
|
+
|
|
1207
1297
|
song_length: int = field(metadata=hci.metadata(">4"))
|
|
1208
1298
|
song_position: int = field(metadata=hci.metadata(">4"))
|
|
1209
1299
|
play_status: PlayStatus = field(metadata=PlayStatus.type_metadata(1))
|
|
@@ -1521,16 +1611,33 @@ class Delegate:
|
|
|
1521
1611
|
def __init__(self, status_code: StatusCode) -> None:
|
|
1522
1612
|
self.status_code = status_code
|
|
1523
1613
|
|
|
1614
|
+
class AvcError(Exception):
|
|
1615
|
+
"""The delegate AVC method failed, with a specified status code."""
|
|
1616
|
+
|
|
1617
|
+
def __init__(self, status_code: avc.ResponseFrame.ResponseCode) -> None:
|
|
1618
|
+
self.status_code = status_code
|
|
1619
|
+
|
|
1524
1620
|
supported_events: list[EventId]
|
|
1621
|
+
supported_company_ids: list[int]
|
|
1525
1622
|
volume: int
|
|
1623
|
+
playback_status: PlayStatus
|
|
1526
1624
|
|
|
1527
|
-
def __init__(
|
|
1625
|
+
def __init__(
|
|
1626
|
+
self,
|
|
1627
|
+
supported_events: Iterable[EventId] = (),
|
|
1628
|
+
supported_company_ids: Iterable[int] = (AVRCP_BLUETOOTH_SIG_COMPANY_ID,),
|
|
1629
|
+
) -> None:
|
|
1630
|
+
self.supported_company_ids = list(supported_company_ids)
|
|
1528
1631
|
self.supported_events = list(supported_events)
|
|
1529
1632
|
self.volume = 0
|
|
1633
|
+
self.playback_status = PlayStatus.STOPPED
|
|
1530
1634
|
|
|
1531
1635
|
async def get_supported_events(self) -> list[EventId]:
|
|
1532
1636
|
return self.supported_events
|
|
1533
1637
|
|
|
1638
|
+
async def get_supported_company_ids(self) -> list[int]:
|
|
1639
|
+
return self.supported_company_ids
|
|
1640
|
+
|
|
1534
1641
|
async def set_absolute_volume(self, volume: int) -> None:
|
|
1535
1642
|
"""
|
|
1536
1643
|
Set the absolute volume.
|
|
@@ -1543,6 +1650,19 @@ class Delegate:
|
|
|
1543
1650
|
async def get_absolute_volume(self) -> int:
|
|
1544
1651
|
return self.volume
|
|
1545
1652
|
|
|
1653
|
+
async def on_key_event(
|
|
1654
|
+
self,
|
|
1655
|
+
key: avc.PassThroughFrame.OperationId,
|
|
1656
|
+
pressed: bool,
|
|
1657
|
+
data: bytes,
|
|
1658
|
+
) -> None:
|
|
1659
|
+
logger.debug(
|
|
1660
|
+
"@@@ on_key_event: key=%s, pressed=%s, data=%s", key, pressed, data.hex()
|
|
1661
|
+
)
|
|
1662
|
+
|
|
1663
|
+
async def get_playback_status(self) -> PlayStatus:
|
|
1664
|
+
return self.playback_status
|
|
1665
|
+
|
|
1546
1666
|
# TODO add other delegate methods
|
|
1547
1667
|
|
|
1548
1668
|
|
|
@@ -1756,6 +1876,19 @@ class Protocol(utils.EventEmitter):
|
|
|
1756
1876
|
if isinstance(capability, EventId)
|
|
1757
1877
|
)
|
|
1758
1878
|
|
|
1879
|
+
async def get_supported_company_ids(self) -> list[int]:
|
|
1880
|
+
"""Get the list of events supported by the connected peer."""
|
|
1881
|
+
response_context = await self.send_avrcp_command(
|
|
1882
|
+
avc.CommandFrame.CommandType.STATUS,
|
|
1883
|
+
GetCapabilitiesCommand(GetCapabilitiesCommand.CapabilityId.COMPANY_ID),
|
|
1884
|
+
)
|
|
1885
|
+
response = self._check_response(response_context, GetCapabilitiesResponse)
|
|
1886
|
+
return list(
|
|
1887
|
+
int.from_bytes(capability, 'big')
|
|
1888
|
+
for capability in response.capabilities
|
|
1889
|
+
if isinstance(capability, bytes)
|
|
1890
|
+
)
|
|
1891
|
+
|
|
1759
1892
|
async def get_play_status(self) -> SongAndPlayStatus:
|
|
1760
1893
|
"""Get the play status of the connected peer."""
|
|
1761
1894
|
response_context = await self.send_avrcp_command(
|
|
@@ -2052,16 +2185,28 @@ class Protocol(utils.EventEmitter):
|
|
|
2052
2185
|
return
|
|
2053
2186
|
|
|
2054
2187
|
if isinstance(command, avc.PassThroughCommandFrame):
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2188
|
+
|
|
2189
|
+
async def dispatch_key_event() -> None:
|
|
2190
|
+
try:
|
|
2191
|
+
await self.delegate.on_key_event(
|
|
2192
|
+
command.operation_id,
|
|
2193
|
+
command.state_flag == avc.PassThroughFrame.StateFlag.PRESSED,
|
|
2194
|
+
command.operation_data,
|
|
2195
|
+
)
|
|
2196
|
+
response_code = avc.ResponseFrame.ResponseCode.ACCEPTED
|
|
2197
|
+
except Delegate.AvcError as error:
|
|
2198
|
+
logger.exception("delegate method raised exception")
|
|
2199
|
+
response_code = error.status_code
|
|
2200
|
+
except Exception:
|
|
2201
|
+
logger.exception("delegate method raised exception")
|
|
2202
|
+
response_code = avc.ResponseFrame.ResponseCode.REJECTED
|
|
2203
|
+
self.send_passthrough_response(
|
|
2204
|
+
transaction_label=transaction_label,
|
|
2205
|
+
command=command,
|
|
2206
|
+
response_code=response_code,
|
|
2207
|
+
)
|
|
2208
|
+
|
|
2209
|
+
utils.AsyncRunner.spawn(dispatch_key_event())
|
|
2065
2210
|
return
|
|
2066
2211
|
|
|
2067
2212
|
# TODO handle other types
|
|
@@ -2141,6 +2286,8 @@ class Protocol(utils.EventEmitter):
|
|
|
2141
2286
|
self._on_set_absolute_volume_command(transaction_label, command)
|
|
2142
2287
|
elif isinstance(command, RegisterNotificationCommand):
|
|
2143
2288
|
self._on_register_notification_command(transaction_label, command)
|
|
2289
|
+
elif isinstance(command, GetPlayStatusCommand):
|
|
2290
|
+
self._on_get_play_status_command(transaction_label, command)
|
|
2144
2291
|
else:
|
|
2145
2292
|
# Not supported.
|
|
2146
2293
|
# TODO: check that this is the right way to respond in this case.
|
|
@@ -2364,17 +2511,27 @@ class Protocol(utils.EventEmitter):
|
|
|
2364
2511
|
logger.debug(f"<<< AVRCP command PDU: {command}")
|
|
2365
2512
|
|
|
2366
2513
|
async def get_supported_events() -> None:
|
|
2514
|
+
capabilities: Sequence[bytes | SupportsBytes]
|
|
2367
2515
|
if (
|
|
2368
2516
|
command.capability_id
|
|
2369
|
-
|
|
2517
|
+
== GetCapabilitiesCommand.CapabilityId.EVENTS_SUPPORTED
|
|
2370
2518
|
):
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2519
|
+
capabilities = await self.delegate.get_supported_events()
|
|
2520
|
+
elif (
|
|
2521
|
+
command.capability_id == GetCapabilitiesCommand.CapabilityId.COMPANY_ID
|
|
2522
|
+
):
|
|
2523
|
+
company_ids = await self.delegate.get_supported_company_ids()
|
|
2524
|
+
capabilities = [
|
|
2525
|
+
company_id.to_bytes(3, 'big') for company_id in company_ids
|
|
2526
|
+
]
|
|
2527
|
+
else:
|
|
2528
|
+
raise core.InvalidArgumentError(
|
|
2529
|
+
f"Unsupported capability: {command.capability_id}"
|
|
2530
|
+
)
|
|
2374
2531
|
self.send_avrcp_response(
|
|
2375
2532
|
transaction_label,
|
|
2376
2533
|
avc.ResponseFrame.ResponseCode.IMPLEMENTED_OR_STABLE,
|
|
2377
|
-
GetCapabilitiesResponse(command.capability_id,
|
|
2534
|
+
GetCapabilitiesResponse(command.capability_id, capabilities),
|
|
2378
2535
|
)
|
|
2379
2536
|
|
|
2380
2537
|
self._delegate_command(transaction_label, command, get_supported_events())
|
|
@@ -2395,6 +2552,26 @@ class Protocol(utils.EventEmitter):
|
|
|
2395
2552
|
|
|
2396
2553
|
self._delegate_command(transaction_label, command, set_absolute_volume())
|
|
2397
2554
|
|
|
2555
|
+
def _on_get_play_status_command(
|
|
2556
|
+
self, transaction_label: int, command: GetPlayStatusCommand
|
|
2557
|
+
) -> None:
|
|
2558
|
+
logger.debug("<<< AVRCP command PDU: %s", command)
|
|
2559
|
+
|
|
2560
|
+
async def get_playback_status() -> None:
|
|
2561
|
+
play_status: PlayStatus = await self.delegate.get_playback_status()
|
|
2562
|
+
self.send_avrcp_response(
|
|
2563
|
+
transaction_label,
|
|
2564
|
+
avc.ResponseFrame.ResponseCode.IMPLEMENTED_OR_STABLE,
|
|
2565
|
+
GetPlayStatusResponse(
|
|
2566
|
+
# TODO: Delegate this.
|
|
2567
|
+
song_length=GetPlayStatusResponse.UNAVAILABLE,
|
|
2568
|
+
song_position=GetPlayStatusResponse.UNAVAILABLE,
|
|
2569
|
+
play_status=play_status,
|
|
2570
|
+
),
|
|
2571
|
+
)
|
|
2572
|
+
|
|
2573
|
+
self._delegate_command(transaction_label, command, get_playback_status())
|
|
2574
|
+
|
|
2398
2575
|
def _on_register_notification_command(
|
|
2399
2576
|
self, transaction_label: int, command: RegisterNotificationCommand
|
|
2400
2577
|
) -> None:
|
|
@@ -2410,28 +2587,27 @@ class Protocol(utils.EventEmitter):
|
|
|
2410
2587
|
)
|
|
2411
2588
|
return
|
|
2412
2589
|
|
|
2590
|
+
response: Response
|
|
2413
2591
|
if command.event_id == EventId.VOLUME_CHANGED:
|
|
2414
2592
|
volume = await self.delegate.get_absolute_volume()
|
|
2415
2593
|
response = RegisterNotificationResponse(VolumeChangedEvent(volume))
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
avc.ResponseFrame.ResponseCode.INTERIM,
|
|
2419
|
-
response,
|
|
2420
|
-
)
|
|
2421
|
-
self._register_notification_listener(transaction_label, command)
|
|
2422
|
-
return
|
|
2423
|
-
|
|
2424
|
-
if command.event_id == EventId.PLAYBACK_STATUS_CHANGED:
|
|
2425
|
-
# TODO: testing only, use delegate
|
|
2594
|
+
elif command.event_id == EventId.PLAYBACK_STATUS_CHANGED:
|
|
2595
|
+
playback_status = await self.delegate.get_playback_status()
|
|
2426
2596
|
response = RegisterNotificationResponse(
|
|
2427
|
-
PlaybackStatusChangedEvent(play_status=
|
|
2428
|
-
)
|
|
2429
|
-
self.send_avrcp_response(
|
|
2430
|
-
transaction_label,
|
|
2431
|
-
avc.ResponseFrame.ResponseCode.INTERIM,
|
|
2432
|
-
response,
|
|
2597
|
+
PlaybackStatusChangedEvent(play_status=playback_status)
|
|
2433
2598
|
)
|
|
2434
|
-
|
|
2599
|
+
elif command.event_id == EventId.NOW_PLAYING_CONTENT_CHANGED:
|
|
2600
|
+
playback_status = await self.delegate.get_playback_status()
|
|
2601
|
+
response = RegisterNotificationResponse(NowPlayingContentChangedEvent())
|
|
2602
|
+
else:
|
|
2603
|
+
logger.warning("Event supported but not handled %s", command.event_id)
|
|
2435
2604
|
return
|
|
2436
2605
|
|
|
2606
|
+
self.send_avrcp_response(
|
|
2607
|
+
transaction_label,
|
|
2608
|
+
avc.ResponseFrame.ResponseCode.INTERIM,
|
|
2609
|
+
response,
|
|
2610
|
+
)
|
|
2611
|
+
self._register_notification_listener(transaction_label, command)
|
|
2612
|
+
|
|
2437
2613
|
self._delegate_command(transaction_label, command, register_notification())
|