bumble 0.0.213__py3-none-any.whl → 0.0.215__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 +16 -3
- bumble/a2dp.py +15 -16
- bumble/apps/auracast.py +14 -38
- bumble/apps/bench.py +10 -15
- bumble/apps/ble_rpa_tool.py +1 -0
- bumble/apps/console.py +22 -25
- bumble/apps/controller_info.py +20 -25
- bumble/apps/controller_loopback.py +6 -10
- bumble/apps/controllers.py +2 -3
- bumble/apps/device_info.py +4 -5
- bumble/apps/gatt_dump.py +3 -3
- bumble/apps/gg_bridge.py +7 -8
- bumble/apps/hci_bridge.py +4 -3
- bumble/apps/l2cap_bridge.py +5 -5
- bumble/apps/lea_unicast/app.py +16 -26
- bumble/apps/pair.py +30 -43
- bumble/apps/pandora_server.py +5 -4
- bumble/apps/player/player.py +20 -24
- bumble/apps/rfcomm_bridge.py +4 -10
- bumble/apps/scan.py +17 -8
- bumble/apps/show.py +4 -5
- bumble/apps/speaker/speaker.py +23 -27
- bumble/apps/unbond.py +3 -3
- bumble/apps/usb_probe.py +2 -4
- bumble/att.py +241 -246
- bumble/audio/io.py +5 -9
- bumble/avc.py +2 -2
- bumble/avctp.py +6 -7
- bumble/avdtp.py +19 -22
- bumble/avrcp.py +1097 -589
- bumble/codecs.py +2 -0
- bumble/controller.py +142 -35
- bumble/core.py +567 -248
- bumble/crypto/__init__.py +2 -2
- bumble/crypto/builtin.py +1 -1
- bumble/crypto/cryptography.py +2 -4
- bumble/data_types.py +1025 -0
- bumble/device.py +319 -267
- bumble/drivers/__init__.py +3 -2
- bumble/drivers/intel.py +3 -4
- bumble/drivers/rtk.py +26 -9
- bumble/gap.py +4 -4
- bumble/gatt.py +3 -2
- bumble/gatt_adapters.py +3 -11
- bumble/gatt_client.py +69 -81
- bumble/gatt_server.py +124 -124
- bumble/hci.py +114 -18
- bumble/helpers.py +19 -26
- bumble/hfp.py +10 -21
- bumble/hid.py +22 -16
- bumble/host.py +191 -103
- bumble/keys.py +5 -3
- bumble/l2cap.py +138 -104
- bumble/link.py +18 -19
- bumble/logging.py +65 -0
- bumble/pairing.py +7 -6
- bumble/pandora/__init__.py +9 -8
- bumble/pandora/config.py +3 -1
- bumble/pandora/device.py +3 -2
- bumble/pandora/host.py +38 -36
- bumble/pandora/l2cap.py +22 -21
- bumble/pandora/security.py +15 -15
- bumble/pandora/utils.py +5 -3
- bumble/profiles/aics.py +11 -11
- bumble/profiles/ams.py +403 -0
- bumble/profiles/ancs.py +6 -7
- bumble/profiles/ascs.py +14 -9
- bumble/profiles/asha.py +8 -12
- bumble/profiles/bap.py +11 -23
- bumble/profiles/bass.py +2 -7
- bumble/profiles/battery_service.py +3 -4
- bumble/profiles/cap.py +1 -2
- bumble/profiles/csip.py +2 -6
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +4 -4
- bumble/profiles/gatt_service.py +1 -4
- bumble/profiles/gmap.py +5 -5
- bumble/profiles/hap.py +62 -59
- bumble/profiles/heart_rate_service.py +5 -4
- bumble/profiles/le_audio.py +3 -1
- bumble/profiles/mcp.py +3 -7
- bumble/profiles/pacs.py +3 -6
- bumble/profiles/pbp.py +2 -0
- bumble/profiles/tmap.py +2 -3
- bumble/profiles/vcs.py +2 -8
- bumble/profiles/vocs.py +8 -8
- bumble/rfcomm.py +11 -14
- bumble/rtp.py +1 -0
- bumble/sdp.py +10 -8
- bumble/smp.py +151 -159
- bumble/snoop.py +5 -5
- bumble/tools/generate_company_id_list.py +1 -0
- bumble/tools/intel_fw_download.py +3 -3
- bumble/tools/intel_util.py +5 -4
- bumble/tools/rtk_fw_download.py +6 -3
- bumble/tools/rtk_util.py +26 -8
- bumble/transport/__init__.py +19 -15
- bumble/transport/android_emulator.py +8 -13
- bumble/transport/android_netsim.py +19 -18
- bumble/transport/common.py +12 -15
- bumble/transport/file.py +1 -1
- bumble/transport/hci_socket.py +4 -6
- bumble/transport/pty.py +5 -6
- bumble/transport/pyusb.py +7 -10
- bumble/transport/serial.py +2 -1
- bumble/transport/tcp_client.py +2 -2
- bumble/transport/tcp_server.py +11 -14
- bumble/transport/udp.py +3 -3
- bumble/transport/unix.py +67 -1
- bumble/transport/usb.py +6 -6
- bumble/transport/vhci.py +0 -1
- bumble/transport/ws_client.py +2 -1
- bumble/transport/ws_server.py +3 -2
- bumble/utils.py +20 -5
- bumble/vendor/android/hci.py +1 -2
- bumble/vendor/zephyr/hci.py +0 -1
- {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/METADATA +4 -2
- bumble-0.0.215.dist-info/RECORD +183 -0
- bumble-0.0.213.dist-info/RECORD +0 -180
- {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/WHEEL +0 -0
- {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/top_level.txt +0 -0
bumble/avrcp.py
CHANGED
|
@@ -16,16 +16,18 @@
|
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
from __future__ import annotations
|
|
19
|
+
|
|
19
20
|
import asyncio
|
|
20
|
-
from dataclasses import dataclass
|
|
21
21
|
import enum
|
|
22
|
+
import functools
|
|
22
23
|
import logging
|
|
23
24
|
import struct
|
|
25
|
+
from dataclasses import dataclass, field
|
|
24
26
|
from typing import (
|
|
25
27
|
AsyncIterator,
|
|
26
28
|
Awaitable,
|
|
27
29
|
Callable,
|
|
28
|
-
|
|
30
|
+
ClassVar,
|
|
29
31
|
Iterable,
|
|
30
32
|
List,
|
|
31
33
|
Optional,
|
|
@@ -33,28 +35,24 @@ from typing import (
|
|
|
33
35
|
SupportsBytes,
|
|
34
36
|
TypeVar,
|
|
35
37
|
Union,
|
|
38
|
+
cast,
|
|
36
39
|
)
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
from bumble import avc, avctp, core, hci, l2cap, utils
|
|
39
42
|
from bumble.colors import color
|
|
40
|
-
from bumble.device import
|
|
43
|
+
from bumble.device import Connection, Device
|
|
41
44
|
from bumble.sdp import (
|
|
42
|
-
|
|
45
|
+
SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
46
|
+
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
43
47
|
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
|
44
|
-
SDP_PUBLIC_BROWSE_ROOT,
|
|
45
48
|
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
49
|
+
SDP_PUBLIC_BROWSE_ROOT,
|
|
46
50
|
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
|
47
|
-
|
|
51
|
+
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
|
48
52
|
SDP_SUPPORTED_FEATURES_ATTRIBUTE_ID,
|
|
49
53
|
DataElement,
|
|
50
54
|
ServiceAttribute,
|
|
51
55
|
)
|
|
52
|
-
from bumble import utils
|
|
53
|
-
from bumble import core
|
|
54
|
-
from bumble import l2cap
|
|
55
|
-
from bumble import avc
|
|
56
|
-
from bumble import avctp
|
|
57
|
-
|
|
58
56
|
|
|
59
57
|
# -----------------------------------------------------------------------------
|
|
60
58
|
# Logging
|
|
@@ -69,18 +67,153 @@ AVRCP_PID = 0x110E
|
|
|
69
67
|
AVRCP_BLUETOOTH_SIG_COMPANY_ID = 0x001958
|
|
70
68
|
|
|
71
69
|
|
|
70
|
+
_UINT64_BE_METADATA = {
|
|
71
|
+
'parser': lambda data, offset: (
|
|
72
|
+
offset + 8,
|
|
73
|
+
int.from_bytes(data[offset : offset + 8], byteorder='big'),
|
|
74
|
+
),
|
|
75
|
+
'serializer': lambda x: x.to_bytes(8, byteorder='big'),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class PduId(utils.OpenIntEnum):
|
|
80
|
+
GET_CAPABILITIES = 0x10
|
|
81
|
+
LIST_PLAYER_APPLICATION_SETTING_ATTRIBUTES = 0x11
|
|
82
|
+
LIST_PLAYER_APPLICATION_SETTING_VALUES = 0x12
|
|
83
|
+
GET_CURRENT_PLAYER_APPLICATION_SETTING_VALUE = 0x13
|
|
84
|
+
SET_PLAYER_APPLICATION_SETTING_VALUE = 0x14
|
|
85
|
+
GET_PLAYER_APPLICATION_SETTING_ATTRIBUTE_TEXT = 0x15
|
|
86
|
+
GET_PLAYER_APPLICATION_SETTING_VALUE_TEXT = 0x16
|
|
87
|
+
INFORM_DISPLAYABLE_CHARACTER_SET = 0x17
|
|
88
|
+
INFORM_BATTERY_STATUS_OF_CT = 0x18
|
|
89
|
+
GET_ELEMENT_ATTRIBUTES = 0x20
|
|
90
|
+
GET_PLAY_STATUS = 0x30
|
|
91
|
+
REGISTER_NOTIFICATION = 0x31
|
|
92
|
+
REQUEST_CONTINUING_RESPONSE = 0x40
|
|
93
|
+
ABORT_CONTINUING_RESPONSE = 0x41
|
|
94
|
+
SET_ABSOLUTE_VOLUME = 0x50
|
|
95
|
+
SET_ADDRESSED_PLAYER = 0x60
|
|
96
|
+
SET_BROWSED_PLAYER = 0x70
|
|
97
|
+
GET_FOLDER_ITEMS = 0x71
|
|
98
|
+
CHANGE_PATH = 0x72
|
|
99
|
+
GET_ITEM_ATTRIBUTES = 0x73
|
|
100
|
+
PLAY_ITEM = 0x74
|
|
101
|
+
GET_TOTAL_NUMBER_OF_ITEMS = 0x75
|
|
102
|
+
SEARCH = 0x80
|
|
103
|
+
ADD_TO_NOW_PLAYING = 0x90
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class CharacterSetId(hci.SpecableEnum):
|
|
107
|
+
UTF_8 = 0x06
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class MediaAttributeId(hci.SpecableEnum):
|
|
111
|
+
TITLE = 0x01
|
|
112
|
+
ARTIST_NAME = 0x02
|
|
113
|
+
ALBUM_NAME = 0x03
|
|
114
|
+
TRACK_NUMBER = 0x04
|
|
115
|
+
TOTAL_NUMBER_OF_TRACKS = 0x05
|
|
116
|
+
GENRE = 0x06
|
|
117
|
+
PLAYING_TIME = 0x07
|
|
118
|
+
DEFAULT_COVER_ART = 0x08
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class PlayStatus(hci.SpecableEnum):
|
|
122
|
+
STOPPED = 0x00
|
|
123
|
+
PLAYING = 0x01
|
|
124
|
+
PAUSED = 0x02
|
|
125
|
+
FWD_SEEK = 0x03
|
|
126
|
+
REV_SEEK = 0x04
|
|
127
|
+
ERROR = 0xFF
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class EventId(hci.SpecableEnum):
|
|
131
|
+
PLAYBACK_STATUS_CHANGED = 0x01
|
|
132
|
+
TRACK_CHANGED = 0x02
|
|
133
|
+
TRACK_REACHED_END = 0x03
|
|
134
|
+
TRACK_REACHED_START = 0x04
|
|
135
|
+
PLAYBACK_POS_CHANGED = 0x05
|
|
136
|
+
BATT_STATUS_CHANGED = 0x06
|
|
137
|
+
SYSTEM_STATUS_CHANGED = 0x07
|
|
138
|
+
PLAYER_APPLICATION_SETTING_CHANGED = 0x08
|
|
139
|
+
NOW_PLAYING_CONTENT_CHANGED = 0x09
|
|
140
|
+
AVAILABLE_PLAYERS_CHANGED = 0x0A
|
|
141
|
+
ADDRESSED_PLAYER_CHANGED = 0x0B
|
|
142
|
+
UIDS_CHANGED = 0x0C
|
|
143
|
+
VOLUME_CHANGED = 0x0D
|
|
144
|
+
|
|
145
|
+
def __bytes__(self) -> bytes:
|
|
146
|
+
return bytes([int(self)])
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class StatusCode(hci.SpecableEnum):
|
|
150
|
+
INVALID_COMMAND = 0x00
|
|
151
|
+
INVALID_PARAMETER = 0x01
|
|
152
|
+
PARAMETER_CONTENT_ERROR = 0x02
|
|
153
|
+
INTERNAL_ERROR = 0x03
|
|
154
|
+
OPERATION_COMPLETED = 0x04
|
|
155
|
+
UID_CHANGED = 0x05
|
|
156
|
+
INVALID_DIRECTION = 0x07
|
|
157
|
+
NOT_A_DIRECTORY = 0x08
|
|
158
|
+
DOES_NOT_EXIST = 0x09
|
|
159
|
+
INVALID_SCOPE = 0x0A
|
|
160
|
+
RANGE_OUT_OF_BOUNDS = 0x0B
|
|
161
|
+
FOLDER_ITEM_IS_NOT_PLAYABLE = 0x0C
|
|
162
|
+
MEDIA_IN_USE = 0x0D
|
|
163
|
+
NOW_PLAYING_LIST_FULL = 0x0E
|
|
164
|
+
SEARCH_NOT_SUPPORTED = 0x0F
|
|
165
|
+
SEARCH_IN_PROGRESS = 0x10
|
|
166
|
+
INVALID_PLAYER_ID = 0x11
|
|
167
|
+
PLAYER_NOT_BROWSABLE = 0x12
|
|
168
|
+
PLAYER_NOT_ADDRESSED = 0x13
|
|
169
|
+
NO_VALID_SEARCH_RESULTS = 0x14
|
|
170
|
+
NO_AVAILABLE_PLAYERS = 0x15
|
|
171
|
+
ADDRESSED_PLAYER_CHANGED = 0x16
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class Scope(hci.SpecableEnum):
|
|
175
|
+
MEDIA_PLAYER_LIST = 0x00
|
|
176
|
+
MEDIA_PLAYER_VIRTUAL_FILESYSTEM = 0x01
|
|
177
|
+
SEARCH = 0x02
|
|
178
|
+
NOW_PLAYING = 0x03
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ControllerFeatures(enum.IntFlag):
|
|
182
|
+
# fmt: off
|
|
183
|
+
CATEGORY_1 = 1 << 0
|
|
184
|
+
CATEGORY_2 = 1 << 1
|
|
185
|
+
CATEGORY_3 = 1 << 2
|
|
186
|
+
CATEGORY_4 = 1 << 3
|
|
187
|
+
SUPPORTS_BROWSING = 1 << 6
|
|
188
|
+
SUPPORTS_COVER_ART_GET_IMAGE_PROPERTIES_FEATURE = 1 << 7
|
|
189
|
+
SUPPORTS_COVER_ART_GET_IMAGE_FEATURE = 1 << 8
|
|
190
|
+
SUPPORTS_COVER_ART_GET_LINKED_THUMBNAIL_FEATURE = 1 << 9
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class TargetFeatures(enum.IntFlag):
|
|
194
|
+
# fmt: off
|
|
195
|
+
CATEGORY_1 = 1 << 0
|
|
196
|
+
CATEGORY_2 = 1 << 1
|
|
197
|
+
CATEGORY_3 = 1 << 2
|
|
198
|
+
CATEGORY_4 = 1 << 3
|
|
199
|
+
PLAYER_APPLICATION_SETTINGS = 1 << 4
|
|
200
|
+
GROUP_NAVIGATION = 1 << 5
|
|
201
|
+
SUPPORTS_BROWSING = 1 << 6
|
|
202
|
+
SUPPORTS_MULTIPLE_MEDIA_PLAYER_APPLICATIONS = 1 << 7
|
|
203
|
+
SUPPORTS_COVER_ART = 1 << 8
|
|
204
|
+
|
|
205
|
+
|
|
72
206
|
# -----------------------------------------------------------------------------
|
|
73
207
|
def make_controller_service_sdp_records(
|
|
74
208
|
service_record_handle: int,
|
|
75
209
|
avctp_version: tuple[int, int] = (1, 4),
|
|
76
210
|
avrcp_version: tuple[int, int] = (1, 6),
|
|
77
|
-
supported_features: int = 1,
|
|
211
|
+
supported_features: Union[int, ControllerFeatures] = 1,
|
|
78
212
|
) -> list[ServiceAttribute]:
|
|
79
|
-
# TODO: support a way to compute the supported features from a feature list
|
|
80
213
|
avctp_version_int = avctp_version[0] << 8 | avctp_version[1]
|
|
81
214
|
avrcp_version_int = avrcp_version[0] << 8 | avrcp_version[1]
|
|
82
215
|
|
|
83
|
-
|
|
216
|
+
attributes = [
|
|
84
217
|
ServiceAttribute(
|
|
85
218
|
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
|
86
219
|
DataElement.unsigned_integer_32(service_record_handle),
|
|
@@ -135,6 +268,31 @@ def make_controller_service_sdp_records(
|
|
|
135
268
|
DataElement.unsigned_integer_16(supported_features),
|
|
136
269
|
),
|
|
137
270
|
]
|
|
271
|
+
if supported_features & ControllerFeatures.SUPPORTS_BROWSING:
|
|
272
|
+
attributes.append(
|
|
273
|
+
ServiceAttribute(
|
|
274
|
+
SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
275
|
+
DataElement.sequence(
|
|
276
|
+
[
|
|
277
|
+
DataElement.sequence(
|
|
278
|
+
[
|
|
279
|
+
DataElement.uuid(core.BT_L2CAP_PROTOCOL_ID),
|
|
280
|
+
DataElement.unsigned_integer_16(
|
|
281
|
+
avctp.AVCTP_BROWSING_PSM
|
|
282
|
+
),
|
|
283
|
+
]
|
|
284
|
+
),
|
|
285
|
+
DataElement.sequence(
|
|
286
|
+
[
|
|
287
|
+
DataElement.uuid(core.BT_AVCTP_PROTOCOL_ID),
|
|
288
|
+
DataElement.unsigned_integer_16(avctp_version_int),
|
|
289
|
+
]
|
|
290
|
+
),
|
|
291
|
+
]
|
|
292
|
+
),
|
|
293
|
+
),
|
|
294
|
+
)
|
|
295
|
+
return attributes
|
|
138
296
|
|
|
139
297
|
|
|
140
298
|
# -----------------------------------------------------------------------------
|
|
@@ -142,13 +300,13 @@ def make_target_service_sdp_records(
|
|
|
142
300
|
service_record_handle: int,
|
|
143
301
|
avctp_version: tuple[int, int] = (1, 4),
|
|
144
302
|
avrcp_version: tuple[int, int] = (1, 6),
|
|
145
|
-
supported_features: int = 0x23,
|
|
303
|
+
supported_features: Union[int, TargetFeatures] = 0x23,
|
|
146
304
|
) -> list[ServiceAttribute]:
|
|
147
305
|
# TODO: support a way to compute the supported features from a feature list
|
|
148
306
|
avctp_version_int = avctp_version[0] << 8 | avctp_version[1]
|
|
149
307
|
avrcp_version_int = avrcp_version[0] << 8 | avrcp_version[1]
|
|
150
308
|
|
|
151
|
-
|
|
309
|
+
attributes = [
|
|
152
310
|
ServiceAttribute(
|
|
153
311
|
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
|
154
312
|
DataElement.unsigned_integer_32(service_record_handle),
|
|
@@ -202,17 +360,320 @@ def make_target_service_sdp_records(
|
|
|
202
360
|
DataElement.unsigned_integer_16(supported_features),
|
|
203
361
|
),
|
|
204
362
|
]
|
|
363
|
+
if supported_features & TargetFeatures.SUPPORTS_BROWSING:
|
|
364
|
+
attributes.append(
|
|
365
|
+
ServiceAttribute(
|
|
366
|
+
SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
|
367
|
+
DataElement.sequence(
|
|
368
|
+
[
|
|
369
|
+
DataElement.sequence(
|
|
370
|
+
[
|
|
371
|
+
DataElement.uuid(core.BT_L2CAP_PROTOCOL_ID),
|
|
372
|
+
DataElement.unsigned_integer_16(
|
|
373
|
+
avctp.AVCTP_BROWSING_PSM
|
|
374
|
+
),
|
|
375
|
+
]
|
|
376
|
+
),
|
|
377
|
+
DataElement.sequence(
|
|
378
|
+
[
|
|
379
|
+
DataElement.uuid(core.BT_AVCTP_PROTOCOL_ID),
|
|
380
|
+
DataElement.unsigned_integer_16(avctp_version_int),
|
|
381
|
+
]
|
|
382
|
+
),
|
|
383
|
+
]
|
|
384
|
+
),
|
|
385
|
+
),
|
|
386
|
+
)
|
|
387
|
+
return attributes
|
|
205
388
|
|
|
206
389
|
|
|
207
390
|
# -----------------------------------------------------------------------------
|
|
208
|
-
def
|
|
391
|
+
def _parse_string(data: bytes, offset: int, length_size: int) -> tuple[int, str]:
|
|
392
|
+
length = int.from_bytes(
|
|
393
|
+
data[offset : offset + length_size], byteorder='big', signed=False
|
|
394
|
+
)
|
|
395
|
+
offset += length_size
|
|
396
|
+
encoded = data[offset : offset + length]
|
|
209
397
|
try:
|
|
210
|
-
|
|
211
|
-
return value.decode("utf-8")
|
|
212
|
-
return value.decode("ascii")
|
|
398
|
+
decoded = encoded.decode("utf-8")
|
|
213
399
|
except UnicodeDecodeError:
|
|
214
|
-
|
|
215
|
-
|
|
400
|
+
# This can decode anything.
|
|
401
|
+
decoded = encoded.decode("latin1")
|
|
402
|
+
return offset + length, decoded
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# -----------------------------------------------------------------------------
|
|
406
|
+
def _serialize_string(value: str, length_size: int) -> bytes:
|
|
407
|
+
encoded = value.encode("utf-8")
|
|
408
|
+
return len(encoded).to_bytes(length_size, byteorder='big', signed=False) + encoded
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# -----------------------------------------------------------------------------
|
|
412
|
+
def _string_spec(length_size: int):
|
|
413
|
+
return {
|
|
414
|
+
'parser': functools.partial(_parse_string, length_size=length_size),
|
|
415
|
+
'serializer': functools.partial(_serialize_string, length_size=length_size),
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# -----------------------------------------------------------------------------
|
|
420
|
+
@dataclass
|
|
421
|
+
class MediaAttribute(hci.HCI_Dataclass_Object):
|
|
422
|
+
attribute_id: MediaAttributeId = field(
|
|
423
|
+
metadata=MediaAttributeId.type_metadata(4, byteorder='big')
|
|
424
|
+
)
|
|
425
|
+
character_set_id: CharacterSetId = field(
|
|
426
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
427
|
+
)
|
|
428
|
+
attribute_value: str = field(metadata=hci.metadata(_string_spec(2)))
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# -----------------------------------------------------------------------------
|
|
432
|
+
@dataclass
|
|
433
|
+
class SongAndPlayStatus:
|
|
434
|
+
song_length: int
|
|
435
|
+
song_position: int
|
|
436
|
+
play_status: PlayStatus
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# -----------------------------------------------------------------------------
|
|
440
|
+
class ApplicationSetting:
|
|
441
|
+
class AttributeId(hci.SpecableEnum):
|
|
442
|
+
EQUALIZER_ON_OFF = 0x01
|
|
443
|
+
REPEAT_MODE = 0x02
|
|
444
|
+
SHUFFLE_ON_OFF = 0x03
|
|
445
|
+
SCAN_ON_OFF = 0x04
|
|
446
|
+
|
|
447
|
+
class EqualizerOnOffStatus(hci.SpecableEnum):
|
|
448
|
+
OFF = 0x01
|
|
449
|
+
ON = 0x02
|
|
450
|
+
|
|
451
|
+
class RepeatModeStatus(hci.SpecableEnum):
|
|
452
|
+
OFF = 0x01
|
|
453
|
+
SINGLE_TRACK_REPEAT = 0x02
|
|
454
|
+
ALL_TRACK_REPEAT = 0x03
|
|
455
|
+
GROUP_REPEAT = 0x04
|
|
456
|
+
|
|
457
|
+
class ShuffleOnOffStatus(hci.SpecableEnum):
|
|
458
|
+
OFF = 0x01
|
|
459
|
+
ALL_TRACKS_SHUFFLE = 0x02
|
|
460
|
+
GROUP_SHUFFLE = 0x03
|
|
461
|
+
|
|
462
|
+
class ScanOnOffStatus(hci.SpecableEnum):
|
|
463
|
+
OFF = 0x01
|
|
464
|
+
ALL_TRACKS_SCAN = 0x02
|
|
465
|
+
GROUP_SCAN = 0x03
|
|
466
|
+
|
|
467
|
+
class GenericValue(hci.SpecableEnum):
|
|
468
|
+
pass
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# -----------------------------------------------------------------------------
|
|
472
|
+
@dataclass
|
|
473
|
+
class AttributeValueEntry(hci.HCI_Dataclass_Object):
|
|
474
|
+
attribute_id: MediaAttributeId = field(
|
|
475
|
+
metadata=MediaAttributeId.type_metadata(4, byteorder='big')
|
|
476
|
+
)
|
|
477
|
+
character_set_id: CharacterSetId = field(
|
|
478
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
479
|
+
)
|
|
480
|
+
attribute_value: str = field(metadata=hci.metadata(_string_spec(2)))
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# -----------------------------------------------------------------------------
|
|
484
|
+
class BrowseableItem:
|
|
485
|
+
"""6.10.2 Browseable items."""
|
|
486
|
+
|
|
487
|
+
class Type(hci.SpecableEnum):
|
|
488
|
+
MEDIA_PLAYER = 0x01
|
|
489
|
+
FOLDER = 0x02
|
|
490
|
+
MEDIA_ELEMENT = 0x03
|
|
491
|
+
|
|
492
|
+
item_type: ClassVar[Type]
|
|
493
|
+
_payload: Optional[bytes] = None
|
|
494
|
+
|
|
495
|
+
subclasses: ClassVar[dict[Type, type[BrowseableItem]]] = {}
|
|
496
|
+
fields: ClassVar[hci.Fields] = ()
|
|
497
|
+
|
|
498
|
+
@classmethod
|
|
499
|
+
def parse_from_bytes(cls, data: bytes, offset: int) -> tuple[int, BrowseableItem]:
|
|
500
|
+
item_type, length = struct.unpack_from('>BH', data, offset)
|
|
501
|
+
subclass = cls.subclasses[BrowseableItem.Type(item_type)]
|
|
502
|
+
instance = subclass(
|
|
503
|
+
**hci.HCI_Object.dict_from_bytes(data, offset + 3, subclass.fields)
|
|
504
|
+
)
|
|
505
|
+
instance._payload = data[3:]
|
|
506
|
+
return offset + length, instance
|
|
507
|
+
|
|
508
|
+
def __bytes__(self) -> bytes:
|
|
509
|
+
if self._payload is None:
|
|
510
|
+
self._payload = hci.HCI_Object.dict_to_bytes(self.__dict__, self.fields)
|
|
511
|
+
return (
|
|
512
|
+
struct.pack('>BH', self.item_type, len(self._payload) + 3) + self._payload
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
_Item = TypeVar('_Item', bound='BrowseableItem')
|
|
516
|
+
|
|
517
|
+
@classmethod
|
|
518
|
+
def item(cls, subclass: type[_Item]) -> type[_Item]:
|
|
519
|
+
cls.subclasses[subclass.item_type] = subclass
|
|
520
|
+
subclass.fields = hci.HCI_Object.fields_from_dataclass(subclass)
|
|
521
|
+
return subclass
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
# -----------------------------------------------------------------------------
|
|
525
|
+
@BrowseableItem.item
|
|
526
|
+
@dataclass
|
|
527
|
+
class MediaPlayerItem(BrowseableItem):
|
|
528
|
+
item_type = BrowseableItem.Type.MEDIA_PLAYER
|
|
529
|
+
|
|
530
|
+
class MajorPlayerType(hci.SpecableFlag):
|
|
531
|
+
AUDIO = 0x01
|
|
532
|
+
VIDEO = 0x02
|
|
533
|
+
BROADCASTING_AUDIO = 0x04
|
|
534
|
+
BROADCASTING_VIDEO = 0x08
|
|
535
|
+
|
|
536
|
+
class PlayerSubType(hci.SpecableFlag):
|
|
537
|
+
AUDIO_BOOK = 0x01
|
|
538
|
+
PODCAST = 0x02
|
|
539
|
+
|
|
540
|
+
class Features(hci.SpecableFlag):
|
|
541
|
+
SELECT = 1 << 0
|
|
542
|
+
UP = 1 << 1
|
|
543
|
+
DOWN = 1 << 2
|
|
544
|
+
LEFT = 1 << 3
|
|
545
|
+
RIGHT = 1 << 4
|
|
546
|
+
RIGHT_UP = 1 << 5
|
|
547
|
+
RIGHT_DOWN = 1 << 6
|
|
548
|
+
LEFT_UP = 1 << 7
|
|
549
|
+
LEFT_DOWN = 1 << 8
|
|
550
|
+
ROOT_MENU = 1 << 9
|
|
551
|
+
SETUP_MENU = 1 << 10
|
|
552
|
+
CONTENTS_MENU = 1 << 11
|
|
553
|
+
FAVORITE_MENU = 1 << 12
|
|
554
|
+
EXIT = 1 << 13
|
|
555
|
+
NUM_0 = 1 << 14
|
|
556
|
+
NUM_1 = 1 << 15
|
|
557
|
+
NUM_2 = 1 << 16
|
|
558
|
+
NUM_3 = 1 << 17
|
|
559
|
+
NUM_4 = 1 << 18
|
|
560
|
+
NUM_5 = 1 << 19
|
|
561
|
+
NUM_6 = 1 << 20
|
|
562
|
+
NUM_7 = 1 << 21
|
|
563
|
+
NUM_8 = 1 << 22
|
|
564
|
+
NUM_9 = 1 << 23
|
|
565
|
+
DOT = 1 << 24
|
|
566
|
+
ENTER = 1 << 25
|
|
567
|
+
CLEAR = 1 << 26
|
|
568
|
+
CHANNEL_UP = 1 << 27
|
|
569
|
+
CHANNEL_DOWN = 1 << 28
|
|
570
|
+
PREVIOUS_CHANNEL = 1 << 29
|
|
571
|
+
SOUND_SELECT = 1 << 30
|
|
572
|
+
INPUT_SELECT = 1 << 31
|
|
573
|
+
DISPLAY_INFORMATION = 1 << 32
|
|
574
|
+
HELP = 1 << 33
|
|
575
|
+
PAGE_UP = 1 << 34
|
|
576
|
+
PAGE_DOWN = 1 << 35
|
|
577
|
+
POWER = 1 << 36
|
|
578
|
+
VOLUME_UP = 1 << 37
|
|
579
|
+
VOLUME_DOWN = 1 << 38
|
|
580
|
+
MUTE = 1 << 39
|
|
581
|
+
PLAY = 1 << 40
|
|
582
|
+
STOP = 1 << 41
|
|
583
|
+
PAUSE = 1 << 42
|
|
584
|
+
RECORD = 1 << 43
|
|
585
|
+
REWIND = 1 << 44
|
|
586
|
+
FAST_FORWARD = 1 << 45
|
|
587
|
+
EJECT = 1 << 46
|
|
588
|
+
FORWARD = 1 << 47
|
|
589
|
+
BACKWARD = 1 << 48
|
|
590
|
+
ANGLE = 1 << 49
|
|
591
|
+
SUBPICTURE = 1 << 50
|
|
592
|
+
F1 = 1 << 51
|
|
593
|
+
F2 = 1 << 52
|
|
594
|
+
F3 = 1 << 53
|
|
595
|
+
F4 = 1 << 54
|
|
596
|
+
F5 = 1 << 55
|
|
597
|
+
VENDOR_UNIQUE = 1 << 56
|
|
598
|
+
BASIC_GROUP_NAVIGATION = 1 << 57
|
|
599
|
+
ADVANCED_CONTROL_PLAYER = 1 << 58
|
|
600
|
+
BROWSING = 1 << 59
|
|
601
|
+
SEARCHING = 1 << 60
|
|
602
|
+
ADD_TO_NOW_PLAYING = 1 << 61
|
|
603
|
+
UI_DS_UNIQUE_IN_PLAYER_BROWSE_TREE = 1 << 62
|
|
604
|
+
ONLY_BROWSABLE_WHEN_ADDRESSED = 1 << 63
|
|
605
|
+
ONLY_SEARCHABLE_WHEN_ADDRESSED = 1 << 64
|
|
606
|
+
NOW_PLAYING = 1 << 65
|
|
607
|
+
UID_PERSISTENCY = 1 << 66
|
|
608
|
+
NUMBER_OF_ITEMS = 1 << 67
|
|
609
|
+
COVER_ART = 1 << 68
|
|
610
|
+
|
|
611
|
+
player_id: int = field(metadata=hci.metadata('>2'))
|
|
612
|
+
major_player_type: MajorPlayerType = field(
|
|
613
|
+
metadata=MajorPlayerType.type_metadata(1)
|
|
614
|
+
)
|
|
615
|
+
player_sub_type: PlayerSubType = field(
|
|
616
|
+
metadata=PlayerSubType.type_metadata(4, byteorder='big')
|
|
617
|
+
)
|
|
618
|
+
play_status: PlayStatus = field(metadata=PlayStatus.type_metadata(1))
|
|
619
|
+
feature_bitmask: Features = field(
|
|
620
|
+
metadata=Features.type_metadata(16, byteorder='big')
|
|
621
|
+
)
|
|
622
|
+
character_set_id: CharacterSetId = field(
|
|
623
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
624
|
+
)
|
|
625
|
+
displayable_name: str = field(metadata=hci.metadata(_string_spec(2)))
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
# -----------------------------------------------------------------------------
|
|
629
|
+
@BrowseableItem.item
|
|
630
|
+
@dataclass
|
|
631
|
+
class FolderItem(BrowseableItem):
|
|
632
|
+
item_type = BrowseableItem.Type.FOLDER
|
|
633
|
+
|
|
634
|
+
class FolderType(hci.SpecableEnum):
|
|
635
|
+
MIXED = 0x00
|
|
636
|
+
TITLES = 0x01
|
|
637
|
+
ALBUMS = 0x02
|
|
638
|
+
ARTISTS = 0x03
|
|
639
|
+
GENRES = 0x04
|
|
640
|
+
PLAYLISTS = 0x05
|
|
641
|
+
YEARS = 0x06
|
|
642
|
+
|
|
643
|
+
class Playable(hci.SpecableEnum):
|
|
644
|
+
NOT_PLAYABLE = 0x00
|
|
645
|
+
PLAYABLE = 0x01
|
|
646
|
+
|
|
647
|
+
folder_uid: int = field(metadata=_UINT64_BE_METADATA)
|
|
648
|
+
folder_type: FolderType = field(metadata=FolderType.type_metadata(1))
|
|
649
|
+
is_playable: FolderType = field(metadata=Playable.type_metadata(1))
|
|
650
|
+
character_set_id: CharacterSetId = field(
|
|
651
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
652
|
+
)
|
|
653
|
+
displayable_name: str = field(metadata=hci.metadata(_string_spec(2)))
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
# -----------------------------------------------------------------------------
|
|
657
|
+
@BrowseableItem.item
|
|
658
|
+
@dataclass
|
|
659
|
+
class MediaElementItem(BrowseableItem):
|
|
660
|
+
item_type = BrowseableItem.Type.MEDIA_ELEMENT
|
|
661
|
+
|
|
662
|
+
class MediaType(hci.SpecableEnum):
|
|
663
|
+
AUDIO = 0x00
|
|
664
|
+
VIDEO = 0x01
|
|
665
|
+
|
|
666
|
+
media_element_uid: int = field(metadata=_UINT64_BE_METADATA)
|
|
667
|
+
media_type: MediaType = field(metadata=MediaType.type_metadata(1))
|
|
668
|
+
character_set_id: CharacterSetId = field(
|
|
669
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
670
|
+
)
|
|
671
|
+
displayable_name: str = field(metadata=hci.metadata(_string_spec(2)))
|
|
672
|
+
attribute_value_entry_list: Sequence[AttributeValueEntry] = field(
|
|
673
|
+
metadata=hci.metadata(
|
|
674
|
+
AttributeValueEntry.parse_from_bytes, list_begin=True, list_end=True
|
|
675
|
+
)
|
|
676
|
+
)
|
|
216
677
|
|
|
217
678
|
|
|
218
679
|
# -----------------------------------------------------------------------------
|
|
@@ -223,10 +684,10 @@ class PduAssembler:
|
|
|
223
684
|
6.3.1 AVRCP specific AV//C commands
|
|
224
685
|
"""
|
|
225
686
|
|
|
226
|
-
pdu_id: Optional[
|
|
687
|
+
pdu_id: Optional[PduId]
|
|
227
688
|
payload: bytes
|
|
228
689
|
|
|
229
|
-
def __init__(self, callback: Callable[[
|
|
690
|
+
def __init__(self, callback: Callable[[PduId, bytes], None]) -> None:
|
|
230
691
|
self.callback = callback
|
|
231
692
|
self.reset()
|
|
232
693
|
|
|
@@ -235,7 +696,7 @@ class PduAssembler:
|
|
|
235
696
|
self.parameter = b''
|
|
236
697
|
|
|
237
698
|
def on_pdu(self, pdu: bytes) -> None:
|
|
238
|
-
pdu_id =
|
|
699
|
+
pdu_id = PduId(pdu[0])
|
|
239
700
|
packet_type = Protocol.PacketType(pdu[1] & 3)
|
|
240
701
|
parameter_length = struct.unpack_from('>H', pdu, 2)[0]
|
|
241
702
|
parameter = pdu[4 : 4 + parameter_length]
|
|
@@ -267,677 +728,794 @@ class PduAssembler:
|
|
|
267
728
|
assert self.pdu_id is not None
|
|
268
729
|
try:
|
|
269
730
|
self.callback(self.pdu_id, self.parameter)
|
|
270
|
-
except Exception
|
|
271
|
-
logger.exception(color(
|
|
731
|
+
except Exception:
|
|
732
|
+
logger.exception(color('!!! exception in callback', 'red'))
|
|
272
733
|
|
|
273
734
|
self.reset()
|
|
274
735
|
|
|
275
736
|
|
|
276
737
|
# -----------------------------------------------------------------------------
|
|
277
|
-
@dataclass
|
|
278
738
|
class Command:
|
|
279
|
-
pdu_id:
|
|
280
|
-
|
|
739
|
+
pdu_id: ClassVar[PduId]
|
|
740
|
+
_payload: Optional[bytes] = None
|
|
281
741
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
)
|
|
286
|
-
return f"Command[{self.pdu_id.name}]({properties_str})"
|
|
742
|
+
_Command = TypeVar('_Command', bound='Command')
|
|
743
|
+
subclasses: ClassVar[dict[int, type[Command]]] = {}
|
|
744
|
+
fields: ClassVar[hci.Fields] = ()
|
|
287
745
|
|
|
288
|
-
|
|
289
|
-
|
|
746
|
+
@classmethod
|
|
747
|
+
def command(cls, subclass: type[_Command]) -> type[_Command]:
|
|
748
|
+
cls.subclasses[subclass.pdu_id] = subclass
|
|
749
|
+
subclass.fields = hci.HCI_Object.fields_from_dataclass(subclass)
|
|
750
|
+
return subclass
|
|
290
751
|
|
|
291
|
-
|
|
292
|
-
|
|
752
|
+
@classmethod
|
|
753
|
+
def from_bytes(cls, pdu_id: int, pdu: bytes) -> Command:
|
|
754
|
+
if not (subclass := cls.subclasses.get(pdu_id)):
|
|
755
|
+
raise core.InvalidPacketError(f"Unimplemented PDU {pdu_id}")
|
|
756
|
+
instance = subclass(**hci.HCI_Object.dict_from_bytes(pdu, 0, subclass.fields))
|
|
757
|
+
instance._payload = pdu[0:]
|
|
758
|
+
return instance
|
|
759
|
+
|
|
760
|
+
def __bytes__(self) -> bytes:
|
|
761
|
+
if self._payload is None:
|
|
762
|
+
self._payload = hci.HCI_Object.dict_to_bytes(self.__dict__, self.fields)
|
|
763
|
+
return self._payload
|
|
293
764
|
|
|
294
765
|
|
|
295
766
|
# -----------------------------------------------------------------------------
|
|
767
|
+
@Command.command
|
|
768
|
+
@dataclass
|
|
296
769
|
class GetCapabilitiesCommand(Command):
|
|
297
|
-
|
|
770
|
+
pdu_id = PduId.GET_CAPABILITIES
|
|
771
|
+
|
|
772
|
+
class CapabilityId(hci.SpecableEnum):
|
|
298
773
|
COMPANY_ID = 0x02
|
|
299
774
|
EVENTS_SUPPORTED = 0x03
|
|
300
775
|
|
|
301
|
-
capability_id: CapabilityId
|
|
776
|
+
capability_id: CapabilityId = field(metadata=CapabilityId.type_metadata(1))
|
|
302
777
|
|
|
303
|
-
@classmethod
|
|
304
|
-
def from_bytes(cls, pdu: bytes) -> GetCapabilitiesCommand:
|
|
305
|
-
return cls(cls.CapabilityId(pdu[0]))
|
|
306
778
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
779
|
+
# -----------------------------------------------------------------------------
|
|
780
|
+
@Command.command
|
|
781
|
+
@dataclass
|
|
782
|
+
class ListPlayerApplicationSettingAttributesCommand(Command):
|
|
783
|
+
pdu_id = PduId.LIST_PLAYER_APPLICATION_SETTING_ATTRIBUTES
|
|
784
|
+
|
|
310
785
|
|
|
311
|
-
|
|
312
|
-
|
|
786
|
+
# -----------------------------------------------------------------------------
|
|
787
|
+
@Command.command
|
|
788
|
+
@dataclass
|
|
789
|
+
class ListPlayerApplicationSettingValuesCommand(Command):
|
|
790
|
+
pdu_id = PduId.LIST_PLAYER_APPLICATION_SETTING_VALUES
|
|
791
|
+
|
|
792
|
+
attribute: ApplicationSetting.AttributeId = field(
|
|
793
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(1)
|
|
794
|
+
)
|
|
313
795
|
|
|
314
796
|
|
|
315
797
|
# -----------------------------------------------------------------------------
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
798
|
+
@Command.command
|
|
799
|
+
@dataclass
|
|
800
|
+
class GetCurrentPlayerApplicationSettingValueCommand(Command):
|
|
801
|
+
pdu_id = PduId.GET_CURRENT_PLAYER_APPLICATION_SETTING_VALUE
|
|
802
|
+
|
|
803
|
+
attribute: Sequence[ApplicationSetting.AttributeId] = field(
|
|
804
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(
|
|
805
|
+
1, list_begin=True, list_end=True
|
|
806
|
+
)
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
# -----------------------------------------------------------------------------
|
|
811
|
+
@Command.command
|
|
812
|
+
@dataclass
|
|
813
|
+
class SetPlayerApplicationSettingValueCommand(Command):
|
|
814
|
+
pdu_id = PduId.SET_PLAYER_APPLICATION_SETTING_VALUE
|
|
815
|
+
|
|
816
|
+
attribute: Sequence[ApplicationSetting.AttributeId] = field(
|
|
817
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(1, list_begin=True)
|
|
818
|
+
)
|
|
819
|
+
value: Sequence[int] = field(metadata=hci.metadata(1, list_end=True))
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
# -----------------------------------------------------------------------------
|
|
823
|
+
@Command.command
|
|
824
|
+
@dataclass
|
|
825
|
+
class GetPlayerApplicationSettingAttributeTextCommand(Command):
|
|
826
|
+
pdu_id = PduId.GET_PLAYER_APPLICATION_SETTING_ATTRIBUTE_TEXT
|
|
827
|
+
|
|
828
|
+
attribute: Sequence[ApplicationSetting.AttributeId] = field(
|
|
829
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(
|
|
830
|
+
1, list_begin=True, list_end=True
|
|
831
|
+
)
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
# -----------------------------------------------------------------------------
|
|
836
|
+
@Command.command
|
|
837
|
+
@dataclass
|
|
838
|
+
class GetPlayerApplicationSettingValueTextCommand(Command):
|
|
839
|
+
pdu_id = PduId.GET_PLAYER_APPLICATION_SETTING_VALUE_TEXT
|
|
320
840
|
|
|
321
|
-
|
|
322
|
-
|
|
841
|
+
attribute: ApplicationSetting.AttributeId = field(
|
|
842
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(1)
|
|
843
|
+
)
|
|
844
|
+
value: Sequence[int] = field(
|
|
845
|
+
metadata=hci.metadata(1, list_begin=True, list_end=True)
|
|
846
|
+
)
|
|
323
847
|
|
|
324
848
|
|
|
325
849
|
# -----------------------------------------------------------------------------
|
|
850
|
+
@Command.command
|
|
851
|
+
@dataclass
|
|
852
|
+
class InformDisplayableCharacterSetCommand(Command):
|
|
853
|
+
pdu_id = PduId.INFORM_DISPLAYABLE_CHARACTER_SET
|
|
854
|
+
|
|
855
|
+
character_set_id: Sequence[CharacterSetId] = field(
|
|
856
|
+
metadata=CharacterSetId.type_metadata(
|
|
857
|
+
2, list_begin=True, list_end=True, byteorder='big'
|
|
858
|
+
)
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
# -----------------------------------------------------------------------------
|
|
863
|
+
@Command.command
|
|
864
|
+
@dataclass
|
|
865
|
+
class InformBatteryStatusOfCtCommand(Command):
|
|
866
|
+
pdu_id = PduId.INFORM_BATTERY_STATUS_OF_CT
|
|
867
|
+
|
|
868
|
+
class BatteryStatus(hci.SpecableEnum):
|
|
869
|
+
NORMAL = 0x00
|
|
870
|
+
WARNING = 0x01
|
|
871
|
+
CRITICAL = 0x02
|
|
872
|
+
EXTERNAL = 0x03
|
|
873
|
+
FULL_CHARGE = 0x04
|
|
874
|
+
|
|
875
|
+
battery_status: BatteryStatus = field(metadata=BatteryStatus.type_metadata(1))
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
# -----------------------------------------------------------------------------
|
|
879
|
+
@Command.command
|
|
880
|
+
@dataclass
|
|
881
|
+
class GetPlayStatusCommand(Command):
|
|
882
|
+
pdu_id = PduId.GET_PLAY_STATUS
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
# -----------------------------------------------------------------------------
|
|
886
|
+
@Command.command
|
|
887
|
+
@dataclass
|
|
326
888
|
class GetElementAttributesCommand(Command):
|
|
327
|
-
|
|
328
|
-
attribute_ids: list[MediaAttributeId]
|
|
889
|
+
pdu_id = PduId.GET_ELEMENT_ATTRIBUTES
|
|
329
890
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
attribute_ids = [MediaAttributeId(pdu[9 + i]) for i in range(num_attributes)]
|
|
335
|
-
return cls(identifier, attribute_ids)
|
|
336
|
-
|
|
337
|
-
def __init__(
|
|
338
|
-
self, identifier: int, attribute_ids: Sequence[MediaAttributeId]
|
|
339
|
-
) -> None:
|
|
340
|
-
parameter = struct.pack(">QB", identifier, len(attribute_ids)) + b''.join(
|
|
341
|
-
[struct.pack(">I", int(attribute_id)) for attribute_id in attribute_ids]
|
|
891
|
+
identifier: int = field(metadata=hci.metadata(_UINT64_BE_METADATA))
|
|
892
|
+
attribute_ids: Sequence[MediaAttributeId] = field(
|
|
893
|
+
metadata=MediaAttributeId.type_metadata(
|
|
894
|
+
4, list_begin=True, list_end=True, byteorder='big'
|
|
342
895
|
)
|
|
343
|
-
|
|
344
|
-
self.identifier = identifier
|
|
345
|
-
self.attribute_ids = list(attribute_ids)
|
|
896
|
+
)
|
|
346
897
|
|
|
347
898
|
|
|
348
899
|
# -----------------------------------------------------------------------------
|
|
900
|
+
@Command.command
|
|
901
|
+
@dataclass
|
|
349
902
|
class SetAbsoluteVolumeCommand(Command):
|
|
903
|
+
pdu_id = PduId.SET_ABSOLUTE_VOLUME
|
|
350
904
|
MAXIMUM_VOLUME = 0x7F
|
|
351
905
|
|
|
352
|
-
volume: int
|
|
906
|
+
volume: int = field(metadata=hci.metadata(1))
|
|
353
907
|
|
|
354
|
-
@classmethod
|
|
355
|
-
def from_bytes(cls, pdu: bytes) -> SetAbsoluteVolumeCommand:
|
|
356
|
-
return cls(pdu[0])
|
|
357
908
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
909
|
+
# -----------------------------------------------------------------------------
|
|
910
|
+
@Command.command
|
|
911
|
+
@dataclass
|
|
912
|
+
class RegisterNotificationCommand(Command):
|
|
913
|
+
pdu_id = PduId.REGISTER_NOTIFICATION
|
|
361
914
|
|
|
362
|
-
|
|
363
|
-
|
|
915
|
+
event_id: EventId = field(metadata=EventId.type_metadata(1))
|
|
916
|
+
playback_interval: int = field(metadata=hci.metadata('>4'))
|
|
364
917
|
|
|
365
918
|
|
|
366
919
|
# -----------------------------------------------------------------------------
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
920
|
+
@Command.command
|
|
921
|
+
@dataclass
|
|
922
|
+
class SetAddressedPlayerCommand(Command):
|
|
923
|
+
pdu_id = PduId.SET_ADDRESSED_PLAYER
|
|
370
924
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
925
|
+
player_id: int = field(metadata=hci.metadata('>2'))
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
# -----------------------------------------------------------------------------
|
|
929
|
+
@Command.command
|
|
930
|
+
@dataclass
|
|
931
|
+
class SetBrowsedPlayerCommand(Command):
|
|
932
|
+
pdu_id = PduId.SET_BROWSED_PLAYER
|
|
933
|
+
|
|
934
|
+
player_id: int = field(metadata=hci.metadata('>2'))
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
# -----------------------------------------------------------------------------
|
|
938
|
+
@Command.command
|
|
939
|
+
@dataclass
|
|
940
|
+
class GetFolderItemsCommand(Command):
|
|
941
|
+
pdu_id = PduId.GET_FOLDER_ITEMS
|
|
942
|
+
|
|
943
|
+
scope: Scope = field(metadata=Scope.type_metadata(1))
|
|
944
|
+
start_item: int = field(metadata=hci.metadata('>4'))
|
|
945
|
+
end_item: int = field(metadata=hci.metadata('>4'))
|
|
946
|
+
# When attributes is empty, all attributes will be requested.
|
|
947
|
+
attributes: Sequence[MediaAttributeId] = field(
|
|
948
|
+
metadata=MediaAttributeId.type_metadata(
|
|
949
|
+
4, list_begin=True, list_end=True, byteorder='big'
|
|
391
950
|
)
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
# -----------------------------------------------------------------------------
|
|
955
|
+
@Command.command
|
|
956
|
+
@dataclass
|
|
957
|
+
class ChangePathCommand(Command):
|
|
958
|
+
pdu_id = PduId.CHANGE_PATH
|
|
959
|
+
|
|
960
|
+
class Direction(hci.SpecableEnum):
|
|
961
|
+
UP = 0
|
|
962
|
+
DOWN = 1
|
|
963
|
+
|
|
964
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
965
|
+
direction: Direction = field(metadata=Direction.type_metadata(1))
|
|
966
|
+
folder_uid: int = field(metadata=hci.metadata(_UINT64_BE_METADATA))
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
# -----------------------------------------------------------------------------
|
|
970
|
+
@Command.command
|
|
971
|
+
@dataclass
|
|
972
|
+
class GetItemAttributesCommand(Command):
|
|
973
|
+
pdu_id = PduId.GET_ITEM_ATTRIBUTES
|
|
974
|
+
|
|
975
|
+
scope: Scope = field(metadata=Scope.type_metadata(1))
|
|
976
|
+
uid: int = field(metadata=hci.metadata(_UINT64_BE_METADATA))
|
|
977
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
978
|
+
start_item: int = field(metadata=hci.metadata('>4'))
|
|
979
|
+
end_item: int = field(metadata=hci.metadata('>4'))
|
|
980
|
+
# When attributes is empty, all attributes will be requested.
|
|
981
|
+
attributes: Sequence[MediaAttributeId] = field(
|
|
982
|
+
metadata=MediaAttributeId.type_metadata(1, list_begin=True, list_end=True)
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
# -----------------------------------------------------------------------------
|
|
987
|
+
@Command.command
|
|
988
|
+
@dataclass
|
|
989
|
+
class GetTotalNumberOfItemsCommand(Command):
|
|
990
|
+
pdu_id = PduId.GET_TOTAL_NUMBER_OF_ITEMS
|
|
991
|
+
|
|
992
|
+
scope: Scope = field(metadata=Scope.type_metadata(1))
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
# -----------------------------------------------------------------------------
|
|
996
|
+
@Command.command
|
|
997
|
+
@dataclass
|
|
998
|
+
class SearchCommand(Command):
|
|
999
|
+
pdu_id = PduId.SEARCH
|
|
1000
|
+
|
|
1001
|
+
character_set_id: CharacterSetId = field(
|
|
1002
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
1003
|
+
)
|
|
1004
|
+
search_string: str = field(metadata=hci.metadata(_string_spec(2)))
|
|
392
1005
|
|
|
393
1006
|
|
|
394
1007
|
# -----------------------------------------------------------------------------
|
|
1008
|
+
@Command.command
|
|
395
1009
|
@dataclass
|
|
1010
|
+
class PlayItemCommand(Command):
|
|
1011
|
+
pdu_id = PduId.PLAY_ITEM
|
|
1012
|
+
|
|
1013
|
+
scope: Scope = field(metadata=Scope.type_metadata(1))
|
|
1014
|
+
uid: int = field(metadata=hci.metadata(_UINT64_BE_METADATA))
|
|
1015
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
# -----------------------------------------------------------------------------
|
|
1019
|
+
@Command.command
|
|
1020
|
+
@dataclass
|
|
1021
|
+
class AddToNowPlayingCommand(Command):
|
|
1022
|
+
pdu_id = PduId.ADD_TO_NOW_PLAYING
|
|
1023
|
+
|
|
1024
|
+
scope: Scope = field(metadata=Scope.type_metadata(1))
|
|
1025
|
+
uid: int = field(metadata=hci.metadata(_UINT64_BE_METADATA))
|
|
1026
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
# -----------------------------------------------------------------------------
|
|
396
1030
|
class Response:
|
|
397
|
-
pdu_id:
|
|
398
|
-
|
|
1031
|
+
pdu_id: PduId
|
|
1032
|
+
_payload: Optional[bytes] = None
|
|
399
1033
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
[f"{name}={value}" for name, value in properties.items()]
|
|
403
|
-
)
|
|
404
|
-
return f"Response[{self.pdu_id.name}]({properties_str})"
|
|
1034
|
+
fields: ClassVar[hci.Fields] = ()
|
|
1035
|
+
subclasses: ClassVar[dict[PduId, type[Response]]] = {}
|
|
405
1036
|
|
|
406
|
-
|
|
407
|
-
return self.to_string({"parameter": self.parameter.hex()})
|
|
1037
|
+
_Response = TypeVar('_Response', bound='Response')
|
|
408
1038
|
|
|
409
|
-
|
|
410
|
-
|
|
1039
|
+
@classmethod
|
|
1040
|
+
def response(cls, subclass: type[_Response]) -> type[_Response]:
|
|
1041
|
+
subclass.fields = hci.HCI_Object.fields_from_dataclass(subclass)
|
|
1042
|
+
if pdu_id := getattr(subclass, 'pdu_id', None):
|
|
1043
|
+
cls.subclasses[pdu_id] = subclass
|
|
1044
|
+
return subclass
|
|
1045
|
+
|
|
1046
|
+
def __bytes__(self) -> bytes:
|
|
1047
|
+
if self._payload is None:
|
|
1048
|
+
self._payload = hci.HCI_Object.dict_to_bytes(self.__dict__, self.fields)
|
|
1049
|
+
return self._payload
|
|
1050
|
+
|
|
1051
|
+
@classmethod
|
|
1052
|
+
def from_bytes(cls, pdu: bytes, pdu_id: PduId) -> Response:
|
|
1053
|
+
if not (subclass := cls.subclasses.get(pdu_id)):
|
|
1054
|
+
raise core.InvalidArgumentError(f"Unimplemented packet {pdu_id.name}")
|
|
1055
|
+
return subclass.from_parameters(pdu)
|
|
1056
|
+
|
|
1057
|
+
@classmethod
|
|
1058
|
+
def from_parameters(cls, parameters: bytes) -> Response:
|
|
1059
|
+
instance = cls(**hci.HCI_Object.dict_from_bytes(parameters, 0, cls.fields))
|
|
1060
|
+
instance._payload = parameters
|
|
1061
|
+
return instance
|
|
411
1062
|
|
|
412
1063
|
|
|
413
1064
|
# -----------------------------------------------------------------------------
|
|
1065
|
+
@Response.response
|
|
1066
|
+
@dataclass
|
|
414
1067
|
class RejectedResponse(Response):
|
|
415
|
-
|
|
1068
|
+
pdu_id: PduId
|
|
1069
|
+
status_code: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
416
1070
|
|
|
417
1071
|
@classmethod
|
|
418
|
-
def from_bytes(cls,
|
|
419
|
-
return cls(pdu_id,
|
|
420
|
-
|
|
421
|
-
def __init__(
|
|
422
|
-
self, pdu_id: Protocol.PduId, status_code: Protocol.StatusCode
|
|
423
|
-
) -> None:
|
|
424
|
-
super().__init__(pdu_id, bytes([int(status_code)]))
|
|
425
|
-
self.status_code = status_code
|
|
426
|
-
|
|
427
|
-
def __str__(self) -> str:
|
|
428
|
-
return self.to_string(
|
|
429
|
-
{
|
|
430
|
-
"status_code": self.status_code.name,
|
|
431
|
-
}
|
|
432
|
-
)
|
|
1072
|
+
def from_bytes(cls, pdu: bytes, pdu_id: PduId) -> Response:
|
|
1073
|
+
return cls(pdu_id=pdu_id, status_code=StatusCode(pdu[0]))
|
|
433
1074
|
|
|
434
1075
|
|
|
435
1076
|
# -----------------------------------------------------------------------------
|
|
1077
|
+
@Response.response
|
|
1078
|
+
@dataclass
|
|
436
1079
|
class NotImplementedResponse(Response):
|
|
1080
|
+
pdu_id: PduId
|
|
1081
|
+
parameters: bytes = field(metadata=hci.metadata('*'))
|
|
1082
|
+
|
|
437
1083
|
@classmethod
|
|
438
|
-
def from_bytes(cls,
|
|
439
|
-
return cls(pdu_id, pdu
|
|
1084
|
+
def from_bytes(cls, pdu: bytes, pdu_id: PduId) -> Response:
|
|
1085
|
+
return cls(pdu_id=pdu_id, parameters=pdu)
|
|
440
1086
|
|
|
441
1087
|
|
|
442
1088
|
# -----------------------------------------------------------------------------
|
|
1089
|
+
@Response.response
|
|
1090
|
+
@dataclass
|
|
443
1091
|
class GetCapabilitiesResponse(Response):
|
|
1092
|
+
pdu_id = PduId.GET_CAPABILITIES
|
|
444
1093
|
capability_id: GetCapabilitiesCommand.CapabilityId
|
|
445
|
-
capabilities:
|
|
1094
|
+
capabilities: Sequence[Union[SupportsBytes, bytes]]
|
|
446
1095
|
|
|
447
1096
|
@classmethod
|
|
448
|
-
def
|
|
449
|
-
if len(
|
|
1097
|
+
def from_parameters(cls, parameters: bytes) -> Response:
|
|
1098
|
+
if len(parameters) < 2:
|
|
450
1099
|
# Possibly a reject response.
|
|
451
1100
|
return cls(GetCapabilitiesCommand.CapabilityId(0), [])
|
|
452
1101
|
|
|
453
1102
|
# Assume that the payloads all follow the same pattern:
|
|
454
1103
|
# <CapabilityID><CapabilityCount><Capability*>
|
|
455
|
-
capability_id = GetCapabilitiesCommand.CapabilityId(
|
|
456
|
-
capability_count =
|
|
1104
|
+
capability_id = GetCapabilitiesCommand.CapabilityId(parameters[0])
|
|
1105
|
+
capability_count = parameters[1]
|
|
457
1106
|
|
|
458
1107
|
capabilities: list[Union[SupportsBytes, bytes]]
|
|
459
1108
|
if capability_id == GetCapabilitiesCommand.CapabilityId.EVENTS_SUPPORTED:
|
|
460
|
-
capabilities = [EventId(
|
|
1109
|
+
capabilities = [EventId(parameters[2 + x]) for x in range(capability_count)]
|
|
461
1110
|
else:
|
|
462
|
-
capability_size = (len(
|
|
1111
|
+
capability_size = (len(parameters) - 2) // capability_count
|
|
463
1112
|
capabilities = [
|
|
464
|
-
|
|
465
|
-
for x in range(2, len(
|
|
1113
|
+
parameters[x : x + capability_size]
|
|
1114
|
+
for x in range(2, len(parameters), capability_size)
|
|
466
1115
|
]
|
|
467
1116
|
|
|
468
1117
|
return cls(capability_id, capabilities)
|
|
469
1118
|
|
|
470
|
-
def
|
|
471
|
-
self,
|
|
472
|
-
|
|
473
|
-
capabilities: Sequence[Union[SupportsBytes, bytes]],
|
|
474
|
-
) -> None:
|
|
475
|
-
super().__init__(
|
|
476
|
-
Protocol.PduId.GET_CAPABILITIES,
|
|
477
|
-
bytes([capability_id, len(capabilities)])
|
|
478
|
-
+ b''.join(bytes(capability) for capability in capabilities),
|
|
479
|
-
)
|
|
480
|
-
self.capability_id = capability_id
|
|
481
|
-
self.capabilities = list(capabilities)
|
|
482
|
-
|
|
483
|
-
def __str__(self) -> str:
|
|
484
|
-
return self.to_string(
|
|
485
|
-
{
|
|
486
|
-
"capability_id": self.capability_id.name,
|
|
487
|
-
"capabilities": str(self.capabilities),
|
|
488
|
-
}
|
|
1119
|
+
def __post_init__(self) -> None:
|
|
1120
|
+
self._payload = bytes([self.capability_id, len(self.capabilities)]) + b''.join(
|
|
1121
|
+
bytes(capability) for capability in self.capabilities
|
|
489
1122
|
)
|
|
490
1123
|
|
|
491
1124
|
|
|
492
1125
|
# -----------------------------------------------------------------------------
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
@classmethod
|
|
499
|
-
def from_bytes(cls, pdu: bytes) -> GetPlayStatusResponse:
|
|
500
|
-
(song_length, song_position) = struct.unpack_from(">II", pdu, 0)
|
|
501
|
-
play_status = PlayStatus(pdu[8])
|
|
502
|
-
|
|
503
|
-
return cls(song_length, song_position, play_status)
|
|
1126
|
+
@Response.response
|
|
1127
|
+
@dataclass
|
|
1128
|
+
class ListPlayerApplicationSettingAttributesResponse(Response):
|
|
1129
|
+
pdu_id = PduId.LIST_PLAYER_APPLICATION_SETTING_ATTRIBUTES
|
|
504
1130
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
song_position: int,
|
|
509
|
-
play_status: PlayStatus,
|
|
510
|
-
) -> None:
|
|
511
|
-
super().__init__(
|
|
512
|
-
Protocol.PduId.GET_PLAY_STATUS,
|
|
513
|
-
struct.pack(">IIB", song_length, song_position, int(play_status)),
|
|
514
|
-
)
|
|
515
|
-
self.song_length = song_length
|
|
516
|
-
self.song_position = song_position
|
|
517
|
-
self.play_status = play_status
|
|
518
|
-
|
|
519
|
-
def __str__(self) -> str:
|
|
520
|
-
return self.to_string(
|
|
521
|
-
{
|
|
522
|
-
"song_length": str(self.song_length),
|
|
523
|
-
"song_position": str(self.song_position),
|
|
524
|
-
"play_status": self.play_status.name,
|
|
525
|
-
}
|
|
1131
|
+
attribute: Sequence[ApplicationSetting.AttributeId] = field(
|
|
1132
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(
|
|
1133
|
+
1, list_begin=True, list_end=True
|
|
526
1134
|
)
|
|
1135
|
+
)
|
|
527
1136
|
|
|
528
1137
|
|
|
529
1138
|
# -----------------------------------------------------------------------------
|
|
530
|
-
|
|
531
|
-
|
|
1139
|
+
@Response.response
|
|
1140
|
+
@dataclass
|
|
1141
|
+
class ListPlayerApplicationSettingValuesResponse(Response):
|
|
1142
|
+
pdu_id = PduId.LIST_PLAYER_APPLICATION_SETTING_VALUES
|
|
532
1143
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
offset = 1
|
|
537
|
-
attributes: list[MediaAttribute] = []
|
|
538
|
-
for _ in range(num_attributes):
|
|
539
|
-
(
|
|
540
|
-
attribute_id_int,
|
|
541
|
-
character_set_id_int,
|
|
542
|
-
attribute_value_length,
|
|
543
|
-
) = struct.unpack_from(">IHH", pdu, offset)
|
|
544
|
-
attribute_value_bytes = pdu[
|
|
545
|
-
offset + 8 : offset + 8 + attribute_value_length
|
|
546
|
-
]
|
|
547
|
-
attribute_id = MediaAttributeId(attribute_id_int)
|
|
548
|
-
character_set_id = CharacterSetId(character_set_id_int)
|
|
549
|
-
attribute_value = _decode_attribute_value(
|
|
550
|
-
attribute_value_bytes, character_set_id
|
|
551
|
-
)
|
|
552
|
-
attributes.append(
|
|
553
|
-
MediaAttribute(attribute_id, character_set_id, attribute_value)
|
|
554
|
-
)
|
|
555
|
-
offset += 8 + attribute_value_length
|
|
556
|
-
|
|
557
|
-
return cls(attributes)
|
|
558
|
-
|
|
559
|
-
def __init__(self, attributes: Sequence[MediaAttribute]) -> None:
|
|
560
|
-
parameter = bytes([len(attributes)])
|
|
561
|
-
for attribute in attributes:
|
|
562
|
-
attribute_value_bytes = attribute.attribute_value.encode("utf-8")
|
|
563
|
-
parameter += (
|
|
564
|
-
struct.pack(
|
|
565
|
-
">IHH",
|
|
566
|
-
int(attribute.attribute_id),
|
|
567
|
-
int(CharacterSetId.UTF_8),
|
|
568
|
-
len(attribute_value_bytes),
|
|
569
|
-
)
|
|
570
|
-
+ attribute_value_bytes
|
|
571
|
-
)
|
|
572
|
-
super().__init__(
|
|
573
|
-
Protocol.PduId.GET_ELEMENT_ATTRIBUTES,
|
|
574
|
-
parameter,
|
|
575
|
-
)
|
|
576
|
-
self.attributes = list(attributes)
|
|
577
|
-
|
|
578
|
-
def __str__(self) -> str:
|
|
579
|
-
attribute_strs = [str(attribute) for attribute in self.attributes]
|
|
580
|
-
return self.to_string(
|
|
581
|
-
{
|
|
582
|
-
"attributes": f"[{', '.join(attribute_strs)}]",
|
|
583
|
-
}
|
|
584
|
-
)
|
|
1144
|
+
value: Sequence[int] = field(
|
|
1145
|
+
metadata=hci.metadata(1, list_begin=True, list_end=True)
|
|
1146
|
+
)
|
|
585
1147
|
|
|
586
1148
|
|
|
587
1149
|
# -----------------------------------------------------------------------------
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
def from_bytes(cls, pdu: bytes) -> SetAbsoluteVolumeResponse:
|
|
593
|
-
return cls(pdu[0])
|
|
594
|
-
|
|
595
|
-
def __init__(self, volume: int) -> None:
|
|
596
|
-
super().__init__(Protocol.PduId.SET_ABSOLUTE_VOLUME, bytes([volume]))
|
|
597
|
-
self.volume = volume
|
|
1150
|
+
@Response.response
|
|
1151
|
+
@dataclass
|
|
1152
|
+
class GetCurrentPlayerApplicationSettingValueResponse(Response):
|
|
1153
|
+
pdu_id = PduId.GET_CURRENT_PLAYER_APPLICATION_SETTING_VALUE
|
|
598
1154
|
|
|
599
|
-
|
|
600
|
-
|
|
1155
|
+
attribute: Sequence[ApplicationSetting.AttributeId] = field(
|
|
1156
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(1, list_begin=True)
|
|
1157
|
+
)
|
|
1158
|
+
value: Sequence[int] = field(metadata=hci.metadata(1, list_end=True))
|
|
601
1159
|
|
|
602
1160
|
|
|
603
1161
|
# -----------------------------------------------------------------------------
|
|
604
|
-
|
|
605
|
-
|
|
1162
|
+
@Response.response
|
|
1163
|
+
@dataclass
|
|
1164
|
+
class SetPlayerApplicationSettingValueResponse(Response):
|
|
1165
|
+
pdu_id = PduId.SET_PLAYER_APPLICATION_SETTING_VALUE
|
|
606
1166
|
|
|
607
|
-
@classmethod
|
|
608
|
-
def from_bytes(cls, pdu: bytes) -> RegisterNotificationResponse:
|
|
609
|
-
return cls(Event.from_bytes(pdu))
|
|
610
1167
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
self.event = event
|
|
1168
|
+
# -----------------------------------------------------------------------------
|
|
1169
|
+
@Response.response
|
|
1170
|
+
@dataclass
|
|
1171
|
+
class GetPlayerApplicationSettingAttributeTextResponse(Response):
|
|
1172
|
+
pdu_id = PduId.GET_PLAYER_APPLICATION_SETTING_ATTRIBUTE_TEXT
|
|
617
1173
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
1174
|
+
attribute: Sequence[ApplicationSetting.AttributeId] = field(
|
|
1175
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(1, list_begin=True)
|
|
1176
|
+
)
|
|
1177
|
+
character_set_id: Sequence[CharacterSetId] = field(
|
|
1178
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
1179
|
+
)
|
|
1180
|
+
attribute_string: Sequence[str] = field(
|
|
1181
|
+
metadata=hci.metadata(_string_spec(1), list_end=True)
|
|
1182
|
+
)
|
|
624
1183
|
|
|
625
1184
|
|
|
626
1185
|
# -----------------------------------------------------------------------------
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
TRACK_REACHED_START = 0x04
|
|
632
|
-
PLAYBACK_POS_CHANGED = 0x05
|
|
633
|
-
BATT_STATUS_CHANGED = 0x06
|
|
634
|
-
SYSTEM_STATUS_CHANGED = 0x07
|
|
635
|
-
PLAYER_APPLICATION_SETTING_CHANGED = 0x08
|
|
636
|
-
NOW_PLAYING_CONTENT_CHANGED = 0x09
|
|
637
|
-
AVAILABLE_PLAYERS_CHANGED = 0x0A
|
|
638
|
-
ADDRESSED_PLAYER_CHANGED = 0x0B
|
|
639
|
-
UIDS_CHANGED = 0x0C
|
|
640
|
-
VOLUME_CHANGED = 0x0D
|
|
1186
|
+
@Response.response
|
|
1187
|
+
@dataclass
|
|
1188
|
+
class GetPlayerApplicationSettingValueTextResponse(Response):
|
|
1189
|
+
pdu_id = PduId.GET_PLAYER_APPLICATION_SETTING_VALUE_TEXT
|
|
641
1190
|
|
|
642
|
-
|
|
643
|
-
|
|
1191
|
+
value: Sequence[int] = field(metadata=hci.metadata(1, list_begin=True))
|
|
1192
|
+
character_set_id: Sequence[CharacterSetId] = field(
|
|
1193
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
1194
|
+
)
|
|
1195
|
+
attribute_string: Sequence[str] = field(
|
|
1196
|
+
metadata=hci.metadata(_string_spec(1), list_end=True)
|
|
1197
|
+
)
|
|
644
1198
|
|
|
645
1199
|
|
|
646
1200
|
# -----------------------------------------------------------------------------
|
|
647
|
-
|
|
648
|
-
|
|
1201
|
+
@Response.response
|
|
1202
|
+
@dataclass
|
|
1203
|
+
class InformDisplayableCharacterSetResponse(Response):
|
|
1204
|
+
pdu_id = PduId.INFORM_DISPLAYABLE_CHARACTER_SET
|
|
649
1205
|
|
|
650
1206
|
|
|
651
1207
|
# -----------------------------------------------------------------------------
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
TRACK_NUMBER = 0x04
|
|
657
|
-
TOTAL_NUMBER_OF_TRACKS = 0x05
|
|
658
|
-
GENRE = 0x06
|
|
659
|
-
PLAYING_TIME = 0x07
|
|
660
|
-
DEFAULT_COVER_ART = 0x08
|
|
1208
|
+
@Response.response
|
|
1209
|
+
@dataclass
|
|
1210
|
+
class InformBatteryStatusOfCtResponse(Response):
|
|
1211
|
+
pdu_id = PduId.INFORM_BATTERY_STATUS_OF_CT
|
|
661
1212
|
|
|
662
1213
|
|
|
663
1214
|
# -----------------------------------------------------------------------------
|
|
1215
|
+
@Response.response
|
|
664
1216
|
@dataclass
|
|
665
|
-
class
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1217
|
+
class GetPlayStatusResponse(Response):
|
|
1218
|
+
pdu_id = PduId.GET_PLAY_STATUS
|
|
1219
|
+
song_length: int = field(metadata=hci.metadata(">4"))
|
|
1220
|
+
song_position: int = field(metadata=hci.metadata(">4"))
|
|
1221
|
+
play_status: PlayStatus = field(metadata=PlayStatus.type_metadata(1))
|
|
669
1222
|
|
|
670
1223
|
|
|
671
1224
|
# -----------------------------------------------------------------------------
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
1225
|
+
@Response.response
|
|
1226
|
+
@dataclass
|
|
1227
|
+
class GetElementAttributesResponse(Response):
|
|
1228
|
+
pdu_id = PduId.GET_ELEMENT_ATTRIBUTES
|
|
1229
|
+
attributes: Sequence[MediaAttribute] = field(
|
|
1230
|
+
metadata=hci.metadata(
|
|
1231
|
+
MediaAttribute.parse_from_bytes, list_begin=True, list_end=True
|
|
1232
|
+
)
|
|
1233
|
+
)
|
|
679
1234
|
|
|
680
1235
|
|
|
681
1236
|
# -----------------------------------------------------------------------------
|
|
1237
|
+
@Response.response
|
|
682
1238
|
@dataclass
|
|
683
|
-
class
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
play_status: PlayStatus
|
|
1239
|
+
class SetAbsoluteVolumeResponse(Response):
|
|
1240
|
+
pdu_id = PduId.SET_ABSOLUTE_VOLUME
|
|
1241
|
+
volume: int = field(metadata=hci.metadata(1))
|
|
687
1242
|
|
|
688
1243
|
|
|
689
1244
|
# -----------------------------------------------------------------------------
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
ON = 0x02
|
|
1245
|
+
@Response.response
|
|
1246
|
+
@dataclass
|
|
1247
|
+
class RegisterNotificationResponse(Response):
|
|
1248
|
+
pdu_id = PduId.REGISTER_NOTIFICATION
|
|
1249
|
+
event: Event = field(
|
|
1250
|
+
metadata=hci.metadata(
|
|
1251
|
+
lambda data, offset: (len(data), Event.from_bytes(data[offset:]))
|
|
1252
|
+
)
|
|
1253
|
+
)
|
|
700
1254
|
|
|
701
|
-
class RepeatModeStatus(utils.OpenIntEnum):
|
|
702
|
-
OFF = 0x01
|
|
703
|
-
SINGLE_TRACK_REPEAT = 0x02
|
|
704
|
-
ALL_TRACK_REPEAT = 0x03
|
|
705
|
-
GROUP_REPEAT = 0x04
|
|
706
1255
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
class ScanOnOffStatus(utils.OpenIntEnum):
|
|
713
|
-
OFF = 0x01
|
|
714
|
-
ALL_TRACKS_SCAN = 0x02
|
|
715
|
-
GROUP_SCAN = 0x03
|
|
1256
|
+
# -----------------------------------------------------------------------------
|
|
1257
|
+
@Response.response
|
|
1258
|
+
@dataclass
|
|
1259
|
+
class SetAddressedPlayerResponse(Response):
|
|
1260
|
+
pdu_id = PduId.SET_ADDRESSED_PLAYER
|
|
716
1261
|
|
|
717
|
-
|
|
718
|
-
pass
|
|
1262
|
+
status: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
719
1263
|
|
|
720
1264
|
|
|
721
1265
|
# -----------------------------------------------------------------------------
|
|
1266
|
+
@Response.response
|
|
722
1267
|
@dataclass
|
|
723
|
-
class
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
@classmethod
|
|
727
|
-
def from_bytes(cls, pdu: bytes) -> Event:
|
|
728
|
-
event_id = EventId(pdu[0])
|
|
729
|
-
subclass = EVENT_SUBCLASSES.get(event_id, GenericEvent)
|
|
730
|
-
return subclass.from_bytes(pdu)
|
|
1268
|
+
class SetBrowsedPlayerResponse(Response):
|
|
1269
|
+
pdu_id = PduId.SET_BROWSED_PLAYER
|
|
731
1270
|
|
|
732
|
-
|
|
733
|
-
|
|
1271
|
+
status: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
1272
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
1273
|
+
numbers_of_items: int = field(metadata=hci.metadata('>4'))
|
|
1274
|
+
character_set_id: CharacterSetId = field(
|
|
1275
|
+
metadata=CharacterSetId.type_metadata(2, byteorder='big')
|
|
1276
|
+
)
|
|
1277
|
+
folder_names: Sequence[str] = field(
|
|
1278
|
+
metadata=hci.metadata(_string_spec(2), list_begin=True, list_end=True)
|
|
1279
|
+
)
|
|
734
1280
|
|
|
735
1281
|
|
|
736
1282
|
# -----------------------------------------------------------------------------
|
|
1283
|
+
@Response.response
|
|
737
1284
|
@dataclass
|
|
738
|
-
class
|
|
739
|
-
|
|
1285
|
+
class GetFolderItemsResponse(Response):
|
|
1286
|
+
pdu_id = PduId.GET_FOLDER_ITEMS
|
|
740
1287
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1288
|
+
status: StatusCode
|
|
1289
|
+
uid_counter: int
|
|
1290
|
+
items: Sequence[BrowseableItem]
|
|
744
1291
|
|
|
745
|
-
|
|
746
|
-
|
|
1292
|
+
@classmethod
|
|
1293
|
+
def from_parameters(cls, parameters: bytes) -> Response:
|
|
1294
|
+
status, uid_counter, count = struct.unpack_from('>BHH', parameters)
|
|
1295
|
+
items: list[BrowseableItem] = []
|
|
1296
|
+
offset = 5
|
|
1297
|
+
for _ in range(count):
|
|
1298
|
+
offset, item = BrowseableItem.parse_from_bytes(parameters, offset)
|
|
1299
|
+
items.append(item)
|
|
1300
|
+
instance = cls(status=StatusCode(status), uid_counter=uid_counter, items=items)
|
|
1301
|
+
instance._payload = parameters
|
|
1302
|
+
return instance
|
|
1303
|
+
|
|
1304
|
+
def __post_init__(self) -> None:
|
|
1305
|
+
if self._payload is None:
|
|
1306
|
+
self._payload = struct.pack(
|
|
1307
|
+
'>BHH', self.status, self.uid_counter, len(self.items)
|
|
1308
|
+
) + b''.join(map(bytes, self.items))
|
|
747
1309
|
|
|
748
1310
|
|
|
749
1311
|
# -----------------------------------------------------------------------------
|
|
1312
|
+
@Response.response
|
|
750
1313
|
@dataclass
|
|
751
|
-
class
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
@classmethod
|
|
755
|
-
def from_bytes(cls, pdu: bytes) -> PlaybackStatusChangedEvent:
|
|
756
|
-
return cls(play_status=PlayStatus(pdu[1]))
|
|
757
|
-
|
|
758
|
-
def __init__(self, play_status: PlayStatus) -> None:
|
|
759
|
-
super().__init__(EventId.PLAYBACK_STATUS_CHANGED)
|
|
760
|
-
self.play_status = play_status
|
|
1314
|
+
class ChangePathResponse(Response):
|
|
1315
|
+
pdu_id = PduId.CHANGE_PATH
|
|
761
1316
|
|
|
762
|
-
|
|
763
|
-
|
|
1317
|
+
status: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
1318
|
+
number_of_items: int = field(metadata=hci.metadata('>4'))
|
|
764
1319
|
|
|
765
1320
|
|
|
766
1321
|
# -----------------------------------------------------------------------------
|
|
1322
|
+
@Response.response
|
|
767
1323
|
@dataclass
|
|
768
|
-
class
|
|
769
|
-
|
|
1324
|
+
class GetItemAttributesResponse(Response):
|
|
1325
|
+
pdu_id = PduId.GET_ITEM_ATTRIBUTES
|
|
770
1326
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1327
|
+
status: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
1328
|
+
attribute_value_entry_list: Sequence[AttributeValueEntry] = field(
|
|
1329
|
+
metadata=hci.metadata(
|
|
1330
|
+
AttributeValueEntry.parse_from_bytes, list_begin=True, list_end=True
|
|
1331
|
+
)
|
|
1332
|
+
)
|
|
774
1333
|
|
|
775
|
-
def __init__(self, playback_position: int) -> None:
|
|
776
|
-
super().__init__(EventId.PLAYBACK_POS_CHANGED)
|
|
777
|
-
self.playback_position = playback_position
|
|
778
1334
|
|
|
779
|
-
|
|
780
|
-
|
|
1335
|
+
# -----------------------------------------------------------------------------
|
|
1336
|
+
@Response.response
|
|
1337
|
+
@dataclass
|
|
1338
|
+
class GetTotalNumberOfItemsResponse(Response):
|
|
1339
|
+
pdu_id = PduId.GET_TOTAL_NUMBER_OF_ITEMS
|
|
1340
|
+
|
|
1341
|
+
status: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
1342
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
1343
|
+
number_of_items: int = field(metadata=hci.metadata('>4'))
|
|
781
1344
|
|
|
782
1345
|
|
|
783
1346
|
# -----------------------------------------------------------------------------
|
|
1347
|
+
@Response.response
|
|
784
1348
|
@dataclass
|
|
785
|
-
class
|
|
786
|
-
|
|
1349
|
+
class SearchResponse(Response):
|
|
1350
|
+
pdu_id = PduId.SEARCH
|
|
787
1351
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1352
|
+
status: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
1353
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
1354
|
+
number_of_items: int = field(metadata=hci.metadata('>4'))
|
|
791
1355
|
|
|
792
|
-
def __init__(self, identifier: bytes) -> None:
|
|
793
|
-
super().__init__(EventId.TRACK_CHANGED)
|
|
794
|
-
self.identifier = identifier
|
|
795
1356
|
|
|
796
|
-
|
|
797
|
-
|
|
1357
|
+
# -----------------------------------------------------------------------------
|
|
1358
|
+
@Response.response
|
|
1359
|
+
@dataclass
|
|
1360
|
+
class PlayItemResponse(Response):
|
|
1361
|
+
pdu_id = PduId.PLAY_ITEM
|
|
1362
|
+
|
|
1363
|
+
status: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
798
1364
|
|
|
799
1365
|
|
|
800
1366
|
# -----------------------------------------------------------------------------
|
|
1367
|
+
@Response.response
|
|
801
1368
|
@dataclass
|
|
802
|
-
class
|
|
803
|
-
|
|
804
|
-
class Setting:
|
|
805
|
-
attribute_id: ApplicationSetting.AttributeId
|
|
806
|
-
value_id: utils.OpenIntEnum
|
|
1369
|
+
class AddToNowPlayingResponse(Response):
|
|
1370
|
+
pdu_id = PduId.ADD_TO_NOW_PLAYING
|
|
807
1371
|
|
|
808
|
-
|
|
1372
|
+
status: StatusCode = field(metadata=StatusCode.type_metadata(1))
|
|
809
1373
|
|
|
810
|
-
@classmethod
|
|
811
|
-
def from_bytes(cls, pdu: bytes) -> PlayerApplicationSettingChangedEvent:
|
|
812
|
-
def setting(attribute_id_int: int, value_id_int: int):
|
|
813
|
-
attribute_id = ApplicationSetting.AttributeId(attribute_id_int)
|
|
814
|
-
value_id: utils.OpenIntEnum
|
|
815
|
-
if attribute_id == ApplicationSetting.AttributeId.EQUALIZER_ON_OFF:
|
|
816
|
-
value_id = ApplicationSetting.EqualizerOnOffStatus(value_id_int)
|
|
817
|
-
elif attribute_id == ApplicationSetting.AttributeId.REPEAT_MODE:
|
|
818
|
-
value_id = ApplicationSetting.RepeatModeStatus(value_id_int)
|
|
819
|
-
elif attribute_id == ApplicationSetting.AttributeId.SHUFFLE_ON_OFF:
|
|
820
|
-
value_id = ApplicationSetting.ShuffleOnOffStatus(value_id_int)
|
|
821
|
-
elif attribute_id == ApplicationSetting.AttributeId.SCAN_ON_OFF:
|
|
822
|
-
value_id = ApplicationSetting.ScanOnOffStatus(value_id_int)
|
|
823
|
-
else:
|
|
824
|
-
value_id = ApplicationSetting.GenericValue(value_id_int)
|
|
825
1374
|
|
|
826
|
-
|
|
1375
|
+
# -----------------------------------------------------------------------------
|
|
1376
|
+
class Event:
|
|
1377
|
+
event_id: EventId
|
|
1378
|
+
_pdu: Optional[bytes] = None
|
|
827
1379
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
return cls(player_application_settings=settings)
|
|
1380
|
+
_Event = TypeVar('_Event', bound='Event')
|
|
1381
|
+
subclasses: ClassVar[dict[int, type[Event]]] = {}
|
|
1382
|
+
fields: ClassVar[hci.Fields] = ()
|
|
832
1383
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1384
|
+
@classmethod
|
|
1385
|
+
def event(cls, subclass: type[_Event]) -> type[_Event]:
|
|
1386
|
+
cls.subclasses[subclass.event_id] = subclass
|
|
1387
|
+
subclass.fields = hci.HCI_Object.fields_from_dataclass(subclass)
|
|
1388
|
+
return subclass
|
|
1389
|
+
|
|
1390
|
+
@classmethod
|
|
1391
|
+
def from_bytes(cls, pdu: bytes) -> Event:
|
|
1392
|
+
if not (subclass := cls.subclasses.get(pdu[0])):
|
|
1393
|
+
raise core.InvalidPacketError(f"Unimplemented Event {pdu[0]}")
|
|
1394
|
+
instance = subclass(**hci.HCI_Object.dict_from_bytes(pdu, 1, subclass.fields))
|
|
1395
|
+
instance._pdu = pdu
|
|
1396
|
+
return instance
|
|
836
1397
|
|
|
837
1398
|
def __bytes__(self) -> bytes:
|
|
838
|
-
|
|
839
|
-
bytes([self.event_id])
|
|
840
|
-
|
|
841
|
-
+ b''.join(
|
|
842
|
-
[
|
|
843
|
-
bytes([setting.attribute_id, setting.value_id])
|
|
844
|
-
for setting in self.player_application_settings
|
|
845
|
-
]
|
|
1399
|
+
if self._pdu is None:
|
|
1400
|
+
self._pdu = bytes([self.event_id]) + hci.HCI_Object.dict_to_bytes(
|
|
1401
|
+
self.__dict__, self.fields
|
|
846
1402
|
)
|
|
847
|
-
|
|
1403
|
+
return self._pdu
|
|
848
1404
|
|
|
849
1405
|
|
|
850
1406
|
# -----------------------------------------------------------------------------
|
|
851
1407
|
@dataclass
|
|
852
|
-
class
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1408
|
+
class GenericEvent(Event):
|
|
1409
|
+
event_id: EventId = field(metadata=EventId.type_metadata(1))
|
|
1410
|
+
data: bytes = field(metadata=hci.metadata('*'))
|
|
1411
|
+
|
|
856
1412
|
|
|
857
|
-
|
|
858
|
-
super().__init__(EventId.NOW_PLAYING_CONTENT_CHANGED)
|
|
1413
|
+
GenericEvent.fields = hci.HCI_Object.fields_from_dataclass(GenericEvent)
|
|
859
1414
|
|
|
860
1415
|
|
|
861
1416
|
# -----------------------------------------------------------------------------
|
|
1417
|
+
@Event.event
|
|
862
1418
|
@dataclass
|
|
863
|
-
class
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1419
|
+
class PlaybackStatusChangedEvent(Event):
|
|
1420
|
+
event_id = EventId.PLAYBACK_STATUS_CHANGED
|
|
1421
|
+
play_status: PlayStatus = field(metadata=PlayStatus.type_metadata(1))
|
|
1422
|
+
|
|
867
1423
|
|
|
868
|
-
|
|
869
|
-
|
|
1424
|
+
# -----------------------------------------------------------------------------
|
|
1425
|
+
@Event.event
|
|
1426
|
+
@dataclass
|
|
1427
|
+
class PlaybackPositionChangedEvent(Event):
|
|
1428
|
+
event_id = EventId.PLAYBACK_POS_CHANGED
|
|
1429
|
+
playback_position: int = field(metadata=hci.metadata('>4'))
|
|
870
1430
|
|
|
871
1431
|
|
|
872
1432
|
# -----------------------------------------------------------------------------
|
|
1433
|
+
@Event.event
|
|
873
1434
|
@dataclass
|
|
874
|
-
class
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
player_id: int
|
|
878
|
-
uid_counter: int
|
|
1435
|
+
class TrackChangedEvent(Event):
|
|
1436
|
+
event_id = EventId.TRACK_CHANGED
|
|
1437
|
+
identifier: bytes = field(metadata=hci.metadata('*'))
|
|
879
1438
|
|
|
880
|
-
@classmethod
|
|
881
|
-
def from_bytes(cls, pdu: bytes) -> AddressedPlayerChangedEvent:
|
|
882
|
-
player_id, uid_counter = struct.unpack_from("<HH", pdu, 1)
|
|
883
|
-
return cls(cls.Player(player_id, uid_counter))
|
|
884
1439
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1440
|
+
# -----------------------------------------------------------------------------
|
|
1441
|
+
@Event.event
|
|
1442
|
+
@dataclass
|
|
1443
|
+
class PlayerApplicationSettingChangedEvent(Event):
|
|
1444
|
+
event_id = EventId.PLAYER_APPLICATION_SETTING_CHANGED
|
|
888
1445
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1446
|
+
@dataclass
|
|
1447
|
+
class Setting(hci.HCI_Dataclass_Object):
|
|
1448
|
+
attribute_id: ApplicationSetting.AttributeId = field(
|
|
1449
|
+
metadata=ApplicationSetting.AttributeId.type_metadata(1)
|
|
892
1450
|
)
|
|
1451
|
+
value_id: Union[
|
|
1452
|
+
ApplicationSetting.EqualizerOnOffStatus,
|
|
1453
|
+
ApplicationSetting.RepeatModeStatus,
|
|
1454
|
+
ApplicationSetting.ShuffleOnOffStatus,
|
|
1455
|
+
ApplicationSetting.ScanOnOffStatus,
|
|
1456
|
+
ApplicationSetting.GenericValue,
|
|
1457
|
+
] = field(metadata=hci.metadata(1))
|
|
1458
|
+
|
|
1459
|
+
def __post_init__(self) -> None:
|
|
1460
|
+
super().__post_init__()
|
|
1461
|
+
if self.attribute_id == ApplicationSetting.AttributeId.EQUALIZER_ON_OFF:
|
|
1462
|
+
self.value_id = ApplicationSetting.EqualizerOnOffStatus(self.value_id)
|
|
1463
|
+
elif self.attribute_id == ApplicationSetting.AttributeId.REPEAT_MODE:
|
|
1464
|
+
self.value_id = ApplicationSetting.RepeatModeStatus(self.value_id)
|
|
1465
|
+
elif self.attribute_id == ApplicationSetting.AttributeId.SHUFFLE_ON_OFF:
|
|
1466
|
+
self.value_id = ApplicationSetting.ShuffleOnOffStatus(self.value_id)
|
|
1467
|
+
elif self.attribute_id == ApplicationSetting.AttributeId.SCAN_ON_OFF:
|
|
1468
|
+
self.value_id = ApplicationSetting.ScanOnOffStatus(self.value_id)
|
|
1469
|
+
else:
|
|
1470
|
+
self.value_id = ApplicationSetting.GenericValue(self.value_id)
|
|
1471
|
+
|
|
1472
|
+
player_application_settings: Sequence[Setting] = field(
|
|
1473
|
+
metadata=hci.metadata(Setting.parse_from_bytes, list_begin=True, list_end=True)
|
|
1474
|
+
)
|
|
893
1475
|
|
|
894
1476
|
|
|
895
1477
|
# -----------------------------------------------------------------------------
|
|
1478
|
+
@Event.event
|
|
896
1479
|
@dataclass
|
|
897
|
-
class
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
@classmethod
|
|
901
|
-
def from_bytes(cls, pdu: bytes) -> UidsChangedEvent:
|
|
902
|
-
return cls(uid_counter=struct.unpack_from(">H", pdu, 1)[0])
|
|
1480
|
+
class NowPlayingContentChangedEvent(Event):
|
|
1481
|
+
event_id = EventId.NOW_PLAYING_CONTENT_CHANGED
|
|
903
1482
|
|
|
904
|
-
def __init__(self, uid_counter: int) -> None:
|
|
905
|
-
super().__init__(EventId.UIDS_CHANGED)
|
|
906
|
-
self.uid_counter = uid_counter
|
|
907
1483
|
|
|
908
|
-
|
|
909
|
-
|
|
1484
|
+
# -----------------------------------------------------------------------------
|
|
1485
|
+
@Event.event
|
|
1486
|
+
@dataclass
|
|
1487
|
+
class AvailablePlayersChangedEvent(Event):
|
|
1488
|
+
event_id = EventId.AVAILABLE_PLAYERS_CHANGED
|
|
910
1489
|
|
|
911
1490
|
|
|
912
1491
|
# -----------------------------------------------------------------------------
|
|
1492
|
+
@Event.event
|
|
913
1493
|
@dataclass
|
|
914
|
-
class
|
|
915
|
-
|
|
1494
|
+
class AddressedPlayerChangedEvent(Event):
|
|
1495
|
+
event_id = EventId.ADDRESSED_PLAYER_CHANGED
|
|
916
1496
|
|
|
917
|
-
@
|
|
918
|
-
|
|
919
|
-
|
|
1497
|
+
@dataclass
|
|
1498
|
+
class Player(hci.HCI_Dataclass_Object):
|
|
1499
|
+
player_id: int = field(metadata=hci.metadata('>2'))
|
|
1500
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
920
1501
|
|
|
921
|
-
|
|
922
|
-
super().__init__(EventId.VOLUME_CHANGED)
|
|
923
|
-
self.volume = volume
|
|
1502
|
+
player: Player = field(metadata=hci.metadata(Player.parse_from_bytes))
|
|
924
1503
|
|
|
925
|
-
|
|
926
|
-
|
|
1504
|
+
|
|
1505
|
+
# -----------------------------------------------------------------------------
|
|
1506
|
+
@Event.event
|
|
1507
|
+
@dataclass
|
|
1508
|
+
class UidsChangedEvent(Event):
|
|
1509
|
+
event_id = EventId.UIDS_CHANGED
|
|
1510
|
+
uid_counter: int = field(metadata=hci.metadata('>2'))
|
|
927
1511
|
|
|
928
1512
|
|
|
929
1513
|
# -----------------------------------------------------------------------------
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
EventId.
|
|
934
|
-
|
|
935
|
-
EventId.NOW_PLAYING_CONTENT_CHANGED: NowPlayingContentChangedEvent,
|
|
936
|
-
EventId.AVAILABLE_PLAYERS_CHANGED: AvailablePlayersChangedEvent,
|
|
937
|
-
EventId.ADDRESSED_PLAYER_CHANGED: AddressedPlayerChangedEvent,
|
|
938
|
-
EventId.UIDS_CHANGED: UidsChangedEvent,
|
|
939
|
-
EventId.VOLUME_CHANGED: VolumeChangedEvent,
|
|
940
|
-
}
|
|
1514
|
+
@Event.event
|
|
1515
|
+
@dataclass
|
|
1516
|
+
class VolumeChangedEvent(Event):
|
|
1517
|
+
event_id = EventId.VOLUME_CHANGED
|
|
1518
|
+
volume: int = field(metadata=hci.metadata(1))
|
|
941
1519
|
|
|
942
1520
|
|
|
943
1521
|
# -----------------------------------------------------------------------------
|
|
@@ -952,7 +1530,7 @@ class Delegate:
|
|
|
952
1530
|
class Error(Exception):
|
|
953
1531
|
"""The delegate method failed, with a specified status code."""
|
|
954
1532
|
|
|
955
|
-
def __init__(self, status_code:
|
|
1533
|
+
def __init__(self, status_code: StatusCode) -> None:
|
|
956
1534
|
self.status_code = status_code
|
|
957
1535
|
|
|
958
1536
|
supported_events: list[EventId]
|
|
@@ -994,51 +1572,6 @@ class Protocol(utils.EventEmitter):
|
|
|
994
1572
|
CONTINUE = 0b10
|
|
995
1573
|
END = 0b11
|
|
996
1574
|
|
|
997
|
-
class PduId(utils.OpenIntEnum):
|
|
998
|
-
GET_CAPABILITIES = 0x10
|
|
999
|
-
LIST_PLAYER_APPLICATION_SETTING_ATTRIBUTES = 0x11
|
|
1000
|
-
LIST_PLAYER_APPLICATION_SETTING_VALUES = 0x12
|
|
1001
|
-
GET_CURRENT_PLAYER_APPLICATION_SETTING_VALUE = 0x13
|
|
1002
|
-
SET_PLAYER_APPLICATION_SETTING_VALUE = 0x14
|
|
1003
|
-
GET_PLAYER_APPLICATION_SETTING_ATTRIBUTE_TEXT = 0x15
|
|
1004
|
-
GET_PLAYER_APPLICATION_SETTING_VALUE_TEXT = 0x16
|
|
1005
|
-
INFORM_DISPLAYABLE_CHARACTER_SET = 0x17
|
|
1006
|
-
INFORM_BATTERY_STATUS_OF_CT = 0x18
|
|
1007
|
-
GET_ELEMENT_ATTRIBUTES = 0x20
|
|
1008
|
-
GET_PLAY_STATUS = 0x30
|
|
1009
|
-
REGISTER_NOTIFICATION = 0x31
|
|
1010
|
-
REQUEST_CONTINUING_RESPONSE = 0x40
|
|
1011
|
-
ABORT_CONTINUING_RESPONSE = 0x41
|
|
1012
|
-
SET_ABSOLUTE_VOLUME = 0x50
|
|
1013
|
-
SET_ADDRESSED_PLAYER = 0x60
|
|
1014
|
-
SET_BROWSED_PLAYER = 0x70
|
|
1015
|
-
GET_FOLDER_ITEMS = 0x71
|
|
1016
|
-
GET_TOTAL_NUMBER_OF_ITEMS = 0x75
|
|
1017
|
-
|
|
1018
|
-
class StatusCode(utils.OpenIntEnum):
|
|
1019
|
-
INVALID_COMMAND = 0x00
|
|
1020
|
-
INVALID_PARAMETER = 0x01
|
|
1021
|
-
PARAMETER_CONTENT_ERROR = 0x02
|
|
1022
|
-
INTERNAL_ERROR = 0x03
|
|
1023
|
-
OPERATION_COMPLETED = 0x04
|
|
1024
|
-
UID_CHANGED = 0x05
|
|
1025
|
-
INVALID_DIRECTION = 0x07
|
|
1026
|
-
NOT_A_DIRECTORY = 0x08
|
|
1027
|
-
DOES_NOT_EXIST = 0x09
|
|
1028
|
-
INVALID_SCOPE = 0x0A
|
|
1029
|
-
RANGE_OUT_OF_BOUNDS = 0x0B
|
|
1030
|
-
FOLDER_ITEM_IS_NOT_PLAYABLE = 0x0C
|
|
1031
|
-
MEDIA_IN_USE = 0x0D
|
|
1032
|
-
NOW_PLAYING_LIST_FULL = 0x0E
|
|
1033
|
-
SEARCH_NOT_SUPPORTED = 0x0F
|
|
1034
|
-
SEARCH_IN_PROGRESS = 0x10
|
|
1035
|
-
INVALID_PLAYER_ID = 0x11
|
|
1036
|
-
PLAYER_NOT_BROWSABLE = 0x12
|
|
1037
|
-
PLAYER_NOT_ADDRESSED = 0x13
|
|
1038
|
-
NO_VALID_SEARCH_RESULTS = 0x14
|
|
1039
|
-
NO_AVAILABLE_PLAYERS = 0x15
|
|
1040
|
-
ADDRESSED_PLAYER_CHANGED = 0x16
|
|
1041
|
-
|
|
1042
1575
|
class InvalidPidError(Exception):
|
|
1043
1576
|
"""A response frame with ipid==1 was received."""
|
|
1044
1577
|
|
|
@@ -1117,7 +1650,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1117
1650
|
|
|
1118
1651
|
@staticmethod
|
|
1119
1652
|
def _check_vendor_dependent_frame(
|
|
1120
|
-
frame: Union[avc.VendorDependentCommandFrame, avc.VendorDependentResponseFrame]
|
|
1653
|
+
frame: Union[avc.VendorDependentCommandFrame, avc.VendorDependentResponseFrame],
|
|
1121
1654
|
) -> bool:
|
|
1122
1655
|
if frame.company_id != AVRCP_BLUETOOTH_SIG_COMPANY_ID:
|
|
1123
1656
|
logger.debug("unsupported company id, ignoring")
|
|
@@ -1152,7 +1685,9 @@ class Protocol(utils.EventEmitter):
|
|
|
1152
1685
|
A 'connection' event will be emitted when a connection is made, and a 'start'
|
|
1153
1686
|
event will be emitted when the protocol is ready to be used on that connection.
|
|
1154
1687
|
"""
|
|
1155
|
-
device.
|
|
1688
|
+
device.create_l2cap_server(
|
|
1689
|
+
l2cap.ClassicChannelSpec(avctp.AVCTP_PSM), self._on_avctp_connection
|
|
1690
|
+
)
|
|
1156
1691
|
|
|
1157
1692
|
async def connect(self, connection: Connection) -> None:
|
|
1158
1693
|
"""
|
|
@@ -1213,7 +1748,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1213
1748
|
self.send_rejected_avrcp_response(
|
|
1214
1749
|
transaction_label,
|
|
1215
1750
|
command.pdu_id,
|
|
1216
|
-
|
|
1751
|
+
StatusCode.INTERNAL_ERROR,
|
|
1217
1752
|
)
|
|
1218
1753
|
|
|
1219
1754
|
utils.AsyncRunner.spawn(call())
|
|
@@ -1248,7 +1783,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1248
1783
|
GetElementAttributesCommand(element_identifier, attribute_ids),
|
|
1249
1784
|
)
|
|
1250
1785
|
response = self._check_response(response_context, GetElementAttributesResponse)
|
|
1251
|
-
return response.attributes
|
|
1786
|
+
return list(response.attributes)
|
|
1252
1787
|
|
|
1253
1788
|
async def monitor_events(
|
|
1254
1789
|
self, event_id: EventId, playback_interval: int = 0
|
|
@@ -1331,7 +1866,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1331
1866
|
if not isinstance(event, PlayerApplicationSettingChangedEvent):
|
|
1332
1867
|
logger.warning("unexpected event class")
|
|
1333
1868
|
continue
|
|
1334
|
-
yield event.player_application_settings
|
|
1869
|
+
yield list(event.player_application_settings)
|
|
1335
1870
|
|
|
1336
1871
|
async def monitor_now_playing_content(self) -> AsyncIterator[None]:
|
|
1337
1872
|
"""Monitor Now Playing changes from the connected peer."""
|
|
@@ -1601,29 +2136,24 @@ class Protocol(utils.EventEmitter):
|
|
|
1601
2136
|
avc.CommandFrame.CommandType.NOTIFY,
|
|
1602
2137
|
):
|
|
1603
2138
|
# TODO: catch exceptions from delegates
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
)
|
|
1612
|
-
elif pdu_id == self.PduId.REGISTER_NOTIFICATION:
|
|
1613
|
-
self._on_register_notification_command(
|
|
1614
|
-
transaction_label, RegisterNotificationCommand.from_bytes(pdu)
|
|
1615
|
-
)
|
|
2139
|
+
command = Command.from_bytes(pdu_id, pdu)
|
|
2140
|
+
if isinstance(command, GetCapabilitiesCommand):
|
|
2141
|
+
self._on_get_capabilities_command(transaction_label, command)
|
|
2142
|
+
elif isinstance(command, SetAbsoluteVolumeCommand):
|
|
2143
|
+
self._on_set_absolute_volume_command(transaction_label, command)
|
|
2144
|
+
elif isinstance(command, RegisterNotificationCommand):
|
|
2145
|
+
self._on_register_notification_command(transaction_label, command)
|
|
1616
2146
|
else:
|
|
1617
2147
|
# Not supported.
|
|
1618
2148
|
# TODO: check that this is the right way to respond in this case.
|
|
1619
2149
|
logger.debug("unsupported PDU ID")
|
|
1620
2150
|
self.send_rejected_avrcp_response(
|
|
1621
|
-
transaction_label, pdu_id,
|
|
2151
|
+
transaction_label, pdu_id, StatusCode.INVALID_PARAMETER
|
|
1622
2152
|
)
|
|
1623
2153
|
else:
|
|
1624
2154
|
logger.debug("unsupported command type")
|
|
1625
2155
|
self.send_rejected_avrcp_response(
|
|
1626
|
-
transaction_label, pdu_id,
|
|
2156
|
+
transaction_label, pdu_id, StatusCode.INVALID_COMMAND
|
|
1627
2157
|
)
|
|
1628
2158
|
|
|
1629
2159
|
self.receive_command_state = None
|
|
@@ -1648,34 +2178,16 @@ class Protocol(utils.EventEmitter):
|
|
|
1648
2178
|
# more appropriate.
|
|
1649
2179
|
response: Optional[Response] = None
|
|
1650
2180
|
if response_code == avc.ResponseFrame.ResponseCode.REJECTED:
|
|
1651
|
-
response = RejectedResponse
|
|
2181
|
+
response = RejectedResponse(pdu_id=pdu_id, status_code=StatusCode(pdu[0]))
|
|
1652
2182
|
elif response_code == avc.ResponseFrame.ResponseCode.NOT_IMPLEMENTED:
|
|
1653
|
-
response = NotImplementedResponse
|
|
2183
|
+
response = NotImplementedResponse(pdu_id=pdu_id, parameters=pdu)
|
|
1654
2184
|
elif response_code in (
|
|
1655
2185
|
avc.ResponseFrame.ResponseCode.IMPLEMENTED_OR_STABLE,
|
|
1656
2186
|
avc.ResponseFrame.ResponseCode.INTERIM,
|
|
1657
2187
|
avc.ResponseFrame.ResponseCode.CHANGED,
|
|
1658
2188
|
avc.ResponseFrame.ResponseCode.ACCEPTED,
|
|
1659
2189
|
):
|
|
1660
|
-
|
|
1661
|
-
response = GetCapabilitiesResponse.from_bytes(pdu)
|
|
1662
|
-
elif pdu_id == self.PduId.GET_PLAY_STATUS:
|
|
1663
|
-
response = GetPlayStatusResponse.from_bytes(pdu)
|
|
1664
|
-
elif pdu_id == self.PduId.GET_ELEMENT_ATTRIBUTES:
|
|
1665
|
-
response = GetElementAttributesResponse.from_bytes(pdu)
|
|
1666
|
-
elif pdu_id == self.PduId.SET_ABSOLUTE_VOLUME:
|
|
1667
|
-
response = SetAbsoluteVolumeResponse.from_bytes(pdu)
|
|
1668
|
-
elif pdu_id == self.PduId.REGISTER_NOTIFICATION:
|
|
1669
|
-
response = RegisterNotificationResponse.from_bytes(pdu)
|
|
1670
|
-
else:
|
|
1671
|
-
logger.debug("unexpected PDU ID")
|
|
1672
|
-
pending_command.response.set_exception(
|
|
1673
|
-
core.ProtocolError(
|
|
1674
|
-
error_code=None,
|
|
1675
|
-
error_namespace="avrcp",
|
|
1676
|
-
details="unexpected PDU ID",
|
|
1677
|
-
)
|
|
1678
|
-
)
|
|
2190
|
+
response = Response.from_bytes(pdu=pdu, pdu_id=PduId(pdu_id))
|
|
1679
2191
|
else:
|
|
1680
2192
|
logger.debug("unexpected response code")
|
|
1681
2193
|
pending_command.response.set_exception(
|
|
@@ -1762,10 +2274,8 @@ class Protocol(utils.EventEmitter):
|
|
|
1762
2274
|
# TODO: fragmentation
|
|
1763
2275
|
# Send the command.
|
|
1764
2276
|
logger.debug(f">>> AVRCP command PDU: {command}")
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
+ command.parameter
|
|
1768
|
-
)
|
|
2277
|
+
payload = bytes(command)
|
|
2278
|
+
pdu = struct.pack(">BBH", command.pdu_id, 0, len(payload)) + payload
|
|
1769
2279
|
command_frame = avc.VendorDependentCommandFrame(
|
|
1770
2280
|
command_type,
|
|
1771
2281
|
avc.Frame.SubunitType.PANEL,
|
|
@@ -1809,10 +2319,8 @@ class Protocol(utils.EventEmitter):
|
|
|
1809
2319
|
) -> None:
|
|
1810
2320
|
# TODO: fragmentation
|
|
1811
2321
|
logger.debug(f">>> AVRCP response PDU: {response}")
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
+ response.parameter
|
|
1815
|
-
)
|
|
2322
|
+
payload = bytes(response)
|
|
2323
|
+
pdu = struct.pack(">BBH", response.pdu_id, 0, len(payload)) + payload
|
|
1816
2324
|
response_frame = avc.VendorDependentResponseFrame(
|
|
1817
2325
|
response_code,
|
|
1818
2326
|
avc.Frame.SubunitType.PANEL,
|
|
@@ -1835,7 +2343,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1835
2343
|
self.send_response(transaction_label, response)
|
|
1836
2344
|
|
|
1837
2345
|
def send_rejected_avrcp_response(
|
|
1838
|
-
self, transaction_label: int, pdu_id:
|
|
2346
|
+
self, transaction_label: int, pdu_id: PduId, status_code: StatusCode
|
|
1839
2347
|
) -> None:
|
|
1840
2348
|
self.send_avrcp_response(
|
|
1841
2349
|
transaction_label,
|
|
@@ -1844,7 +2352,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1844
2352
|
)
|
|
1845
2353
|
|
|
1846
2354
|
def send_not_implemented_avrcp_response(
|
|
1847
|
-
self, transaction_label: int, pdu_id:
|
|
2355
|
+
self, transaction_label: int, pdu_id: PduId
|
|
1848
2356
|
) -> None:
|
|
1849
2357
|
self.send_avrcp_response(
|
|
1850
2358
|
transaction_label,
|
|
@@ -1900,7 +2408,7 @@ class Protocol(utils.EventEmitter):
|
|
|
1900
2408
|
if command.event_id not in supported_events:
|
|
1901
2409
|
logger.debug("event not supported")
|
|
1902
2410
|
self.send_not_implemented_avrcp_response(
|
|
1903
|
-
transaction_label,
|
|
2411
|
+
transaction_label, PduId.REGISTER_NOTIFICATION
|
|
1904
2412
|
)
|
|
1905
2413
|
return
|
|
1906
2414
|
|