bumble 0.0.204__py3-none-any.whl → 0.0.208__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 +9 -4
- bumble/apps/auracast.py +631 -98
- bumble/apps/bench.py +238 -157
- bumble/apps/console.py +19 -12
- bumble/apps/controller_info.py +23 -7
- bumble/apps/device_info.py +50 -4
- bumble/apps/gg_bridge.py +1 -1
- bumble/apps/lea_unicast/app.py +61 -201
- bumble/att.py +51 -37
- bumble/audio/__init__.py +17 -0
- bumble/audio/io.py +553 -0
- bumble/controller.py +24 -9
- bumble/core.py +305 -156
- bumble/device.py +1090 -99
- bumble/gatt.py +36 -226
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +52 -33
- bumble/gatt_server.py +5 -5
- bumble/hci.py +812 -14
- bumble/host.py +367 -65
- bumble/l2cap.py +3 -16
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +7 -12
- bumble/profiles/aics.py +48 -57
- bumble/profiles/ascs.py +8 -19
- bumble/profiles/asha.py +16 -14
- bumble/profiles/bass.py +16 -22
- bumble/profiles/battery_service.py +13 -3
- bumble/profiles/device_information_service.py +16 -14
- bumble/profiles/gap.py +12 -8
- bumble/profiles/gatt_service.py +167 -0
- bumble/profiles/gmap.py +198 -0
- bumble/profiles/hap.py +8 -6
- bumble/profiles/heart_rate_service.py +20 -4
- bumble/profiles/le_audio.py +87 -4
- bumble/profiles/mcp.py +11 -9
- bumble/profiles/pacs.py +61 -16
- bumble/profiles/tmap.py +8 -12
- bumble/profiles/{vcp.py → vcs.py} +35 -29
- bumble/profiles/vocs.py +62 -85
- bumble/sdp.py +223 -93
- bumble/smp.py +1 -1
- bumble/utils.py +12 -2
- bumble/vendor/android/hci.py +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/METADATA +13 -11
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/RECORD +50 -46
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/WHEEL +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/entry_points.txt +1 -0
- bumble/apps/lea_unicast/liblc3.wasm +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/LICENSE +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/top_level.txt +0 -0
bumble/profiles/pacs.py
CHANGED
|
@@ -25,6 +25,7 @@ from typing import Optional, Sequence, Union
|
|
|
25
25
|
from bumble.profiles.bap import AudioLocation, CodecSpecificCapabilities, ContextType
|
|
26
26
|
from bumble.profiles import le_audio
|
|
27
27
|
from bumble import gatt
|
|
28
|
+
from bumble import gatt_adapters
|
|
28
29
|
from bumble import gatt_client
|
|
29
30
|
from bumble import hci
|
|
30
31
|
|
|
@@ -72,6 +73,19 @@ class PacRecord:
|
|
|
72
73
|
metadata=metadata,
|
|
73
74
|
)
|
|
74
75
|
|
|
76
|
+
@classmethod
|
|
77
|
+
def list_from_bytes(cls, data: bytes) -> list[PacRecord]:
|
|
78
|
+
"""Parse a serialized list of records preceded by a one byte list length."""
|
|
79
|
+
record_count = data[0]
|
|
80
|
+
records = []
|
|
81
|
+
offset = 1
|
|
82
|
+
for _ in range(record_count):
|
|
83
|
+
record = PacRecord.from_bytes(data[offset:])
|
|
84
|
+
offset += len(bytes(record))
|
|
85
|
+
records.append(record)
|
|
86
|
+
|
|
87
|
+
return records
|
|
88
|
+
|
|
75
89
|
def __bytes__(self) -> bytes:
|
|
76
90
|
capabilities_bytes = bytes(self.codec_specific_capabilities)
|
|
77
91
|
metadata_bytes = bytes(self.metadata)
|
|
@@ -172,39 +186,70 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|
|
172
186
|
class PublishedAudioCapabilitiesServiceProxy(gatt_client.ProfileServiceProxy):
|
|
173
187
|
SERVICE_CLASS = PublishedAudioCapabilitiesService
|
|
174
188
|
|
|
175
|
-
sink_pac: Optional[gatt_client.CharacteristicProxy] = None
|
|
176
|
-
sink_audio_locations: Optional[gatt_client.CharacteristicProxy] =
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
sink_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None
|
|
190
|
+
sink_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = (
|
|
191
|
+
None
|
|
192
|
+
)
|
|
193
|
+
source_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None
|
|
194
|
+
source_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = (
|
|
195
|
+
None
|
|
196
|
+
)
|
|
197
|
+
available_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
|
198
|
+
supported_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
|
181
199
|
|
|
182
200
|
def __init__(self, service_proxy: gatt_client.ServiceProxy):
|
|
183
201
|
self.service_proxy = service_proxy
|
|
184
202
|
|
|
185
|
-
self.available_audio_contexts =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
203
|
+
self.available_audio_contexts = (
|
|
204
|
+
gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
205
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
206
|
+
gatt.GATT_AVAILABLE_AUDIO_CONTEXTS_CHARACTERISTIC
|
|
207
|
+
),
|
|
208
|
+
decode=lambda x: tuple(map(ContextType, struct.unpack('<HH', x))),
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
self.supported_audio_contexts = (
|
|
213
|
+
gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
214
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
215
|
+
gatt.GATT_SUPPORTED_AUDIO_CONTEXTS_CHARACTERISTIC
|
|
216
|
+
),
|
|
217
|
+
decode=lambda x: tuple(map(ContextType, struct.unpack('<HH', x))),
|
|
218
|
+
)
|
|
219
|
+
)
|
|
191
220
|
|
|
192
221
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
193
222
|
gatt.GATT_SINK_PAC_CHARACTERISTIC
|
|
194
223
|
):
|
|
195
|
-
self.sink_pac =
|
|
224
|
+
self.sink_pac = gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
225
|
+
characteristics[0],
|
|
226
|
+
decode=PacRecord.list_from_bytes,
|
|
227
|
+
)
|
|
196
228
|
|
|
197
229
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
198
230
|
gatt.GATT_SOURCE_PAC_CHARACTERISTIC
|
|
199
231
|
):
|
|
200
|
-
self.source_pac =
|
|
232
|
+
self.source_pac = gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
233
|
+
characteristics[0],
|
|
234
|
+
decode=PacRecord.list_from_bytes,
|
|
235
|
+
)
|
|
201
236
|
|
|
202
237
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
203
238
|
gatt.GATT_SINK_AUDIO_LOCATION_CHARACTERISTIC
|
|
204
239
|
):
|
|
205
|
-
self.sink_audio_locations =
|
|
240
|
+
self.sink_audio_locations = (
|
|
241
|
+
gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
242
|
+
characteristics[0],
|
|
243
|
+
decode=lambda x: AudioLocation(struct.unpack('<I', x)[0]),
|
|
244
|
+
)
|
|
245
|
+
)
|
|
206
246
|
|
|
207
247
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
208
248
|
gatt.GATT_SOURCE_AUDIO_LOCATION_CHARACTERISTIC
|
|
209
249
|
):
|
|
210
|
-
self.source_audio_locations =
|
|
250
|
+
self.source_audio_locations = (
|
|
251
|
+
gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
252
|
+
characteristics[0],
|
|
253
|
+
decode=lambda x: AudioLocation(struct.unpack('<I', x)[0]),
|
|
254
|
+
)
|
|
255
|
+
)
|
bumble/profiles/tmap.py
CHANGED
|
@@ -24,12 +24,11 @@ import struct
|
|
|
24
24
|
from bumble.gatt import (
|
|
25
25
|
TemplateService,
|
|
26
26
|
Characteristic,
|
|
27
|
-
DelegatedCharacteristicAdapter,
|
|
28
|
-
InvalidServiceError,
|
|
29
27
|
GATT_TELEPHONY_AND_MEDIA_AUDIO_SERVICE,
|
|
30
28
|
GATT_TMAP_ROLE_CHARACTERISTIC,
|
|
31
29
|
)
|
|
32
|
-
from bumble.
|
|
30
|
+
from bumble.gatt_adapters import DelegatedCharacteristicProxyAdapter
|
|
31
|
+
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
# -----------------------------------------------------------------------------
|
|
@@ -54,6 +53,8 @@ class Role(enum.IntFlag):
|
|
|
54
53
|
class TelephonyAndMediaAudioService(TemplateService):
|
|
55
54
|
UUID = GATT_TELEPHONY_AND_MEDIA_AUDIO_SERVICE
|
|
56
55
|
|
|
56
|
+
role_characteristic: Characteristic[bytes]
|
|
57
|
+
|
|
57
58
|
def __init__(self, role: Role):
|
|
58
59
|
self.role_characteristic = Characteristic(
|
|
59
60
|
GATT_TMAP_ROLE_CHARACTERISTIC,
|
|
@@ -69,20 +70,15 @@ class TelephonyAndMediaAudioService(TemplateService):
|
|
|
69
70
|
class TelephonyAndMediaAudioServiceProxy(ProfileServiceProxy):
|
|
70
71
|
SERVICE_CLASS = TelephonyAndMediaAudioService
|
|
71
72
|
|
|
72
|
-
role:
|
|
73
|
+
role: CharacteristicProxy[Role]
|
|
73
74
|
|
|
74
75
|
def __init__(self, service_proxy: ServiceProxy):
|
|
75
76
|
self.service_proxy = service_proxy
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
self.role = DelegatedCharacteristicProxyAdapter(
|
|
79
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
79
80
|
GATT_TMAP_ROLE_CHARACTERISTIC
|
|
80
|
-
)
|
|
81
|
-
):
|
|
82
|
-
raise InvalidServiceError('TMAP Role characteristic not found')
|
|
83
|
-
|
|
84
|
-
self.role = DelegatedCharacteristicAdapter(
|
|
85
|
-
characteristics[0],
|
|
81
|
+
),
|
|
86
82
|
decode=lambda value: Role(
|
|
87
83
|
struct.unpack_from('<H', value, 0)[0],
|
|
88
84
|
),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2021-
|
|
1
|
+
# Copyright 2021-2025 Google LLC
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -17,14 +17,17 @@
|
|
|
17
17
|
# Imports
|
|
18
18
|
# -----------------------------------------------------------------------------
|
|
19
19
|
from __future__ import annotations
|
|
20
|
+
import dataclasses
|
|
20
21
|
import enum
|
|
21
22
|
|
|
23
|
+
from typing import Optional, Sequence
|
|
24
|
+
|
|
22
25
|
from bumble import att
|
|
23
26
|
from bumble import device
|
|
24
27
|
from bumble import gatt
|
|
28
|
+
from bumble import gatt_adapters
|
|
25
29
|
from bumble import gatt_client
|
|
26
30
|
|
|
27
|
-
from typing import Optional, Sequence
|
|
28
31
|
|
|
29
32
|
# -----------------------------------------------------------------------------
|
|
30
33
|
# Constants
|
|
@@ -67,6 +70,20 @@ class VolumeControlPointOpcode(enum.IntEnum):
|
|
|
67
70
|
MUTE = 0x06
|
|
68
71
|
|
|
69
72
|
|
|
73
|
+
@dataclasses.dataclass
|
|
74
|
+
class VolumeState:
|
|
75
|
+
volume_setting: int
|
|
76
|
+
mute: int
|
|
77
|
+
change_counter: int
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_bytes(cls, data: bytes) -> VolumeState:
|
|
81
|
+
return cls(data[0], data[1], data[2])
|
|
82
|
+
|
|
83
|
+
def __bytes__(self) -> bytes:
|
|
84
|
+
return bytes([self.volume_setting, self.mute, self.change_counter])
|
|
85
|
+
|
|
86
|
+
|
|
70
87
|
# -----------------------------------------------------------------------------
|
|
71
88
|
# Server
|
|
72
89
|
# -----------------------------------------------------------------------------
|
|
@@ -126,16 +143,8 @@ class VolumeControlService(gatt.TemplateService):
|
|
|
126
143
|
included_services=list(included_services),
|
|
127
144
|
)
|
|
128
145
|
|
|
129
|
-
@property
|
|
130
|
-
def volume_state_bytes(self) -> bytes:
|
|
131
|
-
return bytes([self.volume_setting, self.muted, self.change_counter])
|
|
132
|
-
|
|
133
|
-
@volume_state_bytes.setter
|
|
134
|
-
def volume_state_bytes(self, new_value: bytes) -> None:
|
|
135
|
-
self.volume_setting, self.muted, self.change_counter = new_value
|
|
136
|
-
|
|
137
146
|
def _on_read_volume_state(self, _connection: Optional[device.Connection]) -> bytes:
|
|
138
|
-
return self.
|
|
147
|
+
return bytes(VolumeState(self.volume_setting, self.muted, self.change_counter))
|
|
139
148
|
|
|
140
149
|
def _on_write_volume_control_point(
|
|
141
150
|
self, connection: Optional[device.Connection], value: bytes
|
|
@@ -153,14 +162,9 @@ class VolumeControlService(gatt.TemplateService):
|
|
|
153
162
|
self.change_counter = (self.change_counter + 1) % 256
|
|
154
163
|
connection.abort_on(
|
|
155
164
|
'disconnection',
|
|
156
|
-
connection.device.notify_subscribers(
|
|
157
|
-
attribute=self.volume_state,
|
|
158
|
-
value=self.volume_state_bytes,
|
|
159
|
-
),
|
|
160
|
-
)
|
|
161
|
-
self.emit(
|
|
162
|
-
'volume_state', self.volume_setting, self.muted, self.change_counter
|
|
165
|
+
connection.device.notify_subscribers(attribute=self.volume_state),
|
|
163
166
|
)
|
|
167
|
+
self.emit('volume_state_change')
|
|
164
168
|
|
|
165
169
|
def _on_relative_volume_down(self) -> bool:
|
|
166
170
|
old_volume = self.volume_setting
|
|
@@ -206,25 +210,27 @@ class VolumeControlService(gatt.TemplateService):
|
|
|
206
210
|
class VolumeControlServiceProxy(gatt_client.ProfileServiceProxy):
|
|
207
211
|
SERVICE_CLASS = VolumeControlService
|
|
208
212
|
|
|
209
|
-
volume_control_point: gatt_client.CharacteristicProxy
|
|
213
|
+
volume_control_point: gatt_client.CharacteristicProxy[bytes]
|
|
214
|
+
volume_state: gatt_client.CharacteristicProxy[VolumeState]
|
|
215
|
+
volume_flags: gatt_client.CharacteristicProxy[VolumeFlags]
|
|
210
216
|
|
|
211
217
|
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
212
218
|
self.service_proxy = service_proxy
|
|
213
219
|
|
|
214
|
-
self.volume_state =
|
|
215
|
-
service_proxy.
|
|
220
|
+
self.volume_state = gatt_adapters.SerializableCharacteristicProxyAdapter(
|
|
221
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
216
222
|
gatt.GATT_VOLUME_STATE_CHARACTERISTIC
|
|
217
|
-
)
|
|
218
|
-
|
|
223
|
+
),
|
|
224
|
+
VolumeState,
|
|
219
225
|
)
|
|
220
226
|
|
|
221
|
-
self.volume_control_point = service_proxy.
|
|
227
|
+
self.volume_control_point = service_proxy.get_required_characteristic_by_uuid(
|
|
222
228
|
gatt.GATT_VOLUME_CONTROL_POINT_CHARACTERISTIC
|
|
223
|
-
)
|
|
229
|
+
)
|
|
224
230
|
|
|
225
|
-
self.volume_flags =
|
|
226
|
-
service_proxy.
|
|
231
|
+
self.volume_flags = gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
232
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
227
233
|
gatt.GATT_VOLUME_FLAGS_CHARACTERISTIC
|
|
228
|
-
)
|
|
229
|
-
|
|
234
|
+
),
|
|
235
|
+
decode=lambda data: VolumeFlags(data[0]),
|
|
230
236
|
)
|
bumble/profiles/vocs.py
CHANGED
|
@@ -24,17 +24,19 @@ from bumble.device import Connection
|
|
|
24
24
|
from bumble.att import ATT_Error
|
|
25
25
|
from bumble.gatt import (
|
|
26
26
|
Characteristic,
|
|
27
|
-
DelegatedCharacteristicAdapter,
|
|
28
27
|
TemplateService,
|
|
29
28
|
CharacteristicValue,
|
|
30
|
-
UTF8CharacteristicAdapter,
|
|
31
|
-
InvalidServiceError,
|
|
32
29
|
GATT_VOLUME_OFFSET_CONTROL_SERVICE,
|
|
33
30
|
GATT_VOLUME_OFFSET_STATE_CHARACTERISTIC,
|
|
34
31
|
GATT_AUDIO_LOCATION_CHARACTERISTIC,
|
|
35
32
|
GATT_VOLUME_OFFSET_CONTROL_POINT_CHARACTERISTIC,
|
|
36
33
|
GATT_AUDIO_OUTPUT_DESCRIPTION_CHARACTERISTIC,
|
|
37
34
|
)
|
|
35
|
+
from bumble.gatt_adapters import (
|
|
36
|
+
DelegatedCharacteristicProxyAdapter,
|
|
37
|
+
SerializableCharacteristicProxyAdapter,
|
|
38
|
+
UTF8CharacteristicProxyAdapter,
|
|
39
|
+
)
|
|
38
40
|
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
|
|
39
41
|
from bumble.utils import OpenIntEnum
|
|
40
42
|
from bumble.profiles.bap import AudioLocation
|
|
@@ -67,7 +69,7 @@ class ErrorCode(OpenIntEnum):
|
|
|
67
69
|
class VolumeOffsetState:
|
|
68
70
|
volume_offset: int = 0
|
|
69
71
|
change_counter: int = 0
|
|
70
|
-
|
|
72
|
+
attribute: Optional[Characteristic] = None
|
|
71
73
|
|
|
72
74
|
def __bytes__(self) -> bytes:
|
|
73
75
|
return struct.pack('<hB', self.volume_offset, self.change_counter)
|
|
@@ -81,10 +83,8 @@ class VolumeOffsetState:
|
|
|
81
83
|
self.change_counter = (self.change_counter + 1) % (CHANGE_COUNTER_MAX_VALUE + 1)
|
|
82
84
|
|
|
83
85
|
async def notify_subscribers_via_connection(self, connection: Connection) -> None:
|
|
84
|
-
assert self.
|
|
85
|
-
await connection.device.notify_subscribers(
|
|
86
|
-
attribute=self.attribute_value, value=bytes(self)
|
|
87
|
-
)
|
|
86
|
+
assert self.attribute is not None
|
|
87
|
+
await connection.device.notify_subscribers(attribute=self.attribute)
|
|
88
88
|
|
|
89
89
|
def on_read(self, _connection: Optional[Connection]) -> bytes:
|
|
90
90
|
return bytes(self)
|
|
@@ -93,7 +93,7 @@ class VolumeOffsetState:
|
|
|
93
93
|
@dataclass
|
|
94
94
|
class VocsAudioLocation:
|
|
95
95
|
audio_location: AudioLocation = AudioLocation.NOT_ALLOWED
|
|
96
|
-
|
|
96
|
+
attribute: Optional[Characteristic] = None
|
|
97
97
|
|
|
98
98
|
def __bytes__(self) -> bytes:
|
|
99
99
|
return struct.pack('<I', self.audio_location)
|
|
@@ -108,12 +108,10 @@ class VocsAudioLocation:
|
|
|
108
108
|
|
|
109
109
|
async def on_write(self, connection: Optional[Connection], value: bytes) -> None:
|
|
110
110
|
assert connection
|
|
111
|
-
assert self.
|
|
111
|
+
assert self.attribute
|
|
112
112
|
|
|
113
113
|
self.audio_location = AudioLocation(int.from_bytes(value, 'little'))
|
|
114
|
-
await connection.device.notify_subscribers(
|
|
115
|
-
attribute=self.attribute_value, value=value
|
|
116
|
-
)
|
|
114
|
+
await connection.device.notify_subscribers(attribute=self.attribute)
|
|
117
115
|
|
|
118
116
|
|
|
119
117
|
@dataclass
|
|
@@ -152,7 +150,7 @@ class VolumeOffsetControlPoint:
|
|
|
152
150
|
@dataclass
|
|
153
151
|
class AudioOutputDescription:
|
|
154
152
|
audio_output_description: str = ''
|
|
155
|
-
|
|
153
|
+
attribute: Optional[Characteristic] = None
|
|
156
154
|
|
|
157
155
|
@classmethod
|
|
158
156
|
def from_bytes(cls, data: bytes):
|
|
@@ -166,12 +164,10 @@ class AudioOutputDescription:
|
|
|
166
164
|
|
|
167
165
|
async def on_write(self, connection: Optional[Connection], value: bytes) -> None:
|
|
168
166
|
assert connection
|
|
169
|
-
assert self.
|
|
167
|
+
assert self.attribute
|
|
170
168
|
|
|
171
169
|
self.audio_output_description = value.decode('utf-8')
|
|
172
|
-
await connection.device.notify_subscribers(
|
|
173
|
-
attribute=self.attribute_value, value=value
|
|
174
|
-
)
|
|
170
|
+
await connection.device.notify_subscribers(attribute=self.attribute)
|
|
175
171
|
|
|
176
172
|
|
|
177
173
|
# -----------------------------------------------------------------------------
|
|
@@ -203,48 +199,45 @@ class VolumeOffsetControlService(TemplateService):
|
|
|
203
199
|
VolumeOffsetControlPoint(self.volume_offset_state)
|
|
204
200
|
)
|
|
205
201
|
|
|
206
|
-
self.volume_offset_state_characteristic =
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
Characteristic.Properties.READ | Characteristic.Properties.NOTIFY
|
|
211
|
-
),
|
|
212
|
-
permissions=Characteristic.Permissions.READ_REQUIRES_ENCRYPTION,
|
|
213
|
-
value=CharacteristicValue(read=self.volume_offset_state.on_read),
|
|
202
|
+
self.volume_offset_state_characteristic: Characteristic[bytes] = Characteristic(
|
|
203
|
+
uuid=GATT_VOLUME_OFFSET_STATE_CHARACTERISTIC,
|
|
204
|
+
properties=(
|
|
205
|
+
Characteristic.Properties.READ | Characteristic.Properties.NOTIFY
|
|
214
206
|
),
|
|
215
|
-
|
|
207
|
+
permissions=Characteristic.Permissions.READ_REQUIRES_ENCRYPTION,
|
|
208
|
+
value=CharacteristicValue(read=self.volume_offset_state.on_read),
|
|
216
209
|
)
|
|
217
210
|
|
|
218
|
-
self.audio_location_characteristic =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
write=self.audio_location.on_write,
|
|
233
|
-
),
|
|
211
|
+
self.audio_location_characteristic: Characteristic[bytes] = Characteristic(
|
|
212
|
+
uuid=GATT_AUDIO_LOCATION_CHARACTERISTIC,
|
|
213
|
+
properties=(
|
|
214
|
+
Characteristic.Properties.READ
|
|
215
|
+
| Characteristic.Properties.NOTIFY
|
|
216
|
+
| Characteristic.Properties.WRITE_WITHOUT_RESPONSE
|
|
217
|
+
),
|
|
218
|
+
permissions=(
|
|
219
|
+
Characteristic.Permissions.READ_REQUIRES_ENCRYPTION
|
|
220
|
+
| Characteristic.Permissions.WRITE_REQUIRES_ENCRYPTION
|
|
221
|
+
),
|
|
222
|
+
value=CharacteristicValue(
|
|
223
|
+
read=self.audio_location.on_read,
|
|
224
|
+
write=self.audio_location.on_write,
|
|
234
225
|
),
|
|
235
|
-
encode=lambda value: bytes(value),
|
|
236
|
-
decode=VocsAudioLocation.from_bytes,
|
|
237
226
|
)
|
|
238
|
-
self.audio_location.
|
|
227
|
+
self.audio_location.attribute = self.audio_location_characteristic
|
|
239
228
|
|
|
240
|
-
self.volume_offset_control_point_characteristic =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
229
|
+
self.volume_offset_control_point_characteristic: Characteristic[bytes] = (
|
|
230
|
+
Characteristic(
|
|
231
|
+
uuid=GATT_VOLUME_OFFSET_CONTROL_POINT_CHARACTERISTIC,
|
|
232
|
+
properties=Characteristic.Properties.WRITE,
|
|
233
|
+
permissions=Characteristic.Permissions.WRITE_REQUIRES_ENCRYPTION,
|
|
234
|
+
value=CharacteristicValue(
|
|
235
|
+
write=self.volume_offset_control_point.on_write
|
|
236
|
+
),
|
|
237
|
+
)
|
|
245
238
|
)
|
|
246
239
|
|
|
247
|
-
self.audio_output_description_characteristic =
|
|
240
|
+
self.audio_output_description_characteristic: Characteristic[bytes] = (
|
|
248
241
|
Characteristic(
|
|
249
242
|
uuid=GATT_AUDIO_OUTPUT_DESCRIPTION_CHARACTERISTIC,
|
|
250
243
|
properties=(
|
|
@@ -262,9 +255,8 @@ class VolumeOffsetControlService(TemplateService):
|
|
|
262
255
|
),
|
|
263
256
|
)
|
|
264
257
|
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
self.audio_output_description_characteristic.value
|
|
258
|
+
self.audio_output_description.attribute = (
|
|
259
|
+
self.audio_output_description_characteristic
|
|
268
260
|
)
|
|
269
261
|
|
|
270
262
|
super().__init__(
|
|
@@ -287,44 +279,29 @@ class VolumeOffsetControlServiceProxy(ProfileServiceProxy):
|
|
|
287
279
|
def __init__(self, service_proxy: ServiceProxy) -> None:
|
|
288
280
|
self.service_proxy = service_proxy
|
|
289
281
|
|
|
290
|
-
|
|
291
|
-
|
|
282
|
+
self.volume_offset_state = SerializableCharacteristicProxyAdapter(
|
|
283
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
292
284
|
GATT_VOLUME_OFFSET_STATE_CHARACTERISTIC
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
raise InvalidServiceError("Volume Offset State characteristic not found")
|
|
296
|
-
self.volume_offset_state = DelegatedCharacteristicAdapter(
|
|
297
|
-
characteristics[0], decode=VolumeOffsetState.from_bytes
|
|
285
|
+
),
|
|
286
|
+
VolumeOffsetState,
|
|
298
287
|
)
|
|
299
288
|
|
|
300
|
-
|
|
301
|
-
|
|
289
|
+
self.audio_location = DelegatedCharacteristicProxyAdapter(
|
|
290
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
302
291
|
GATT_AUDIO_LOCATION_CHARACTERISTIC
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
self.audio_location = DelegatedCharacteristicAdapter(
|
|
307
|
-
characteristics[0],
|
|
308
|
-
encode=lambda value: bytes(value),
|
|
309
|
-
decode=VocsAudioLocation.from_bytes,
|
|
292
|
+
),
|
|
293
|
+
encode=lambda value: bytes([int(value)]),
|
|
294
|
+
decode=lambda data: AudioLocation(data[0]),
|
|
310
295
|
)
|
|
311
296
|
|
|
312
|
-
|
|
313
|
-
|
|
297
|
+
self.volume_offset_control_point = (
|
|
298
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
314
299
|
GATT_VOLUME_OFFSET_CONTROL_POINT_CHARACTERISTIC
|
|
315
300
|
)
|
|
316
|
-
)
|
|
317
|
-
raise InvalidServiceError(
|
|
318
|
-
"Volume Offset Control Point characteristic not found"
|
|
319
|
-
)
|
|
320
|
-
self.volume_offset_control_point = characteristics[0]
|
|
301
|
+
)
|
|
321
302
|
|
|
322
|
-
|
|
323
|
-
|
|
303
|
+
self.audio_output_description = UTF8CharacteristicProxyAdapter(
|
|
304
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
324
305
|
GATT_AUDIO_OUTPUT_DESCRIPTION_CHARACTERISTIC
|
|
325
306
|
)
|
|
326
|
-
)
|
|
327
|
-
raise InvalidServiceError(
|
|
328
|
-
"Audio Output Description characteristic not found"
|
|
329
|
-
)
|
|
330
|
-
self.audio_output_description = UTF8CharacteristicAdapter(characteristics[0])
|
|
307
|
+
)
|