bumble 0.0.207__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 +29 -35
- bumble/apps/bench.py +13 -10
- bumble/apps/console.py +19 -12
- bumble/apps/gg_bridge.py +1 -1
- bumble/att.py +51 -37
- bumble/core.py +301 -155
- bumble/device.py +102 -56
- bumble/gatt.py +9 -228
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +38 -31
- bumble/gatt_server.py +5 -5
- bumble/host.py +11 -5
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +7 -12
- bumble/profiles/aics.py +33 -23
- bumble/profiles/ascs.py +2 -1
- bumble/profiles/asha.py +11 -9
- bumble/profiles/bass.py +8 -5
- 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 +1 -0
- bumble/profiles/gmap.py +16 -11
- bumble/profiles/hap.py +8 -6
- bumble/profiles/heart_rate_service.py +20 -4
- bumble/profiles/mcp.py +11 -9
- bumble/profiles/pacs.py +37 -24
- bumble/profiles/tmap.py +6 -4
- bumble/profiles/vcs.py +6 -5
- bumble/profiles/vocs.py +49 -41
- bumble/utils.py +10 -0
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/METADATA +3 -3
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/RECORD +38 -37
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/LICENSE +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/WHEEL +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/top_level.txt +0 -0
bumble/pandora/host.py
CHANGED
|
@@ -371,9 +371,7 @@ class HostService(HostServicer):
|
|
|
371
371
|
scan_response_data=scan_response_data,
|
|
372
372
|
)
|
|
373
373
|
|
|
374
|
-
|
|
375
|
-
asyncio.get_running_loop().create_future()
|
|
376
|
-
)
|
|
374
|
+
connections: asyncio.Queue[bumble.device.Connection] = asyncio.Queue()
|
|
377
375
|
|
|
378
376
|
if request.connectable:
|
|
379
377
|
|
|
@@ -382,7 +380,7 @@ class HostService(HostServicer):
|
|
|
382
380
|
connection.transport == BT_LE_TRANSPORT
|
|
383
381
|
and connection.role == BT_PERIPHERAL_ROLE
|
|
384
382
|
):
|
|
385
|
-
|
|
383
|
+
connections.put_nowait(connection)
|
|
386
384
|
|
|
387
385
|
self.device.on('connection', on_connection)
|
|
388
386
|
|
|
@@ -397,8 +395,7 @@ class HostService(HostServicer):
|
|
|
397
395
|
await asyncio.sleep(1)
|
|
398
396
|
continue
|
|
399
397
|
|
|
400
|
-
connection = await
|
|
401
|
-
pending_connection = asyncio.get_running_loop().create_future()
|
|
398
|
+
connection = await connections.get()
|
|
402
399
|
|
|
403
400
|
cookie = any_pb2.Any(value=connection.handle.to_bytes(4, 'big'))
|
|
404
401
|
yield AdvertiseResponse(connection=Connection(cookie=cookie))
|
|
@@ -492,6 +489,8 @@ class HostService(HostServicer):
|
|
|
492
489
|
target = Address(target_bytes, Address.RANDOM_DEVICE_ADDRESS)
|
|
493
490
|
advertising_type = AdvertisingType.DIRECTED_CONNECTABLE_LOW_DUTY
|
|
494
491
|
|
|
492
|
+
connections: asyncio.Queue[bumble.device.Connection] = asyncio.Queue()
|
|
493
|
+
|
|
495
494
|
if request.connectable:
|
|
496
495
|
|
|
497
496
|
def on_connection(connection: bumble.device.Connection) -> None:
|
|
@@ -499,7 +498,7 @@ class HostService(HostServicer):
|
|
|
499
498
|
connection.transport == BT_LE_TRANSPORT
|
|
500
499
|
and connection.role == BT_PERIPHERAL_ROLE
|
|
501
500
|
):
|
|
502
|
-
|
|
501
|
+
connections.put_nowait(connection)
|
|
503
502
|
|
|
504
503
|
self.device.on('connection', on_connection)
|
|
505
504
|
|
|
@@ -517,12 +516,8 @@ class HostService(HostServicer):
|
|
|
517
516
|
await asyncio.sleep(1)
|
|
518
517
|
continue
|
|
519
518
|
|
|
520
|
-
pending_connection: asyncio.Future[bumble.device.Connection] = (
|
|
521
|
-
asyncio.get_running_loop().create_future()
|
|
522
|
-
)
|
|
523
|
-
|
|
524
519
|
self.log.debug('Wait for LE connection...')
|
|
525
|
-
connection = await
|
|
520
|
+
connection = await connections.get()
|
|
526
521
|
|
|
527
522
|
self.log.debug(
|
|
528
523
|
f"Advertise: Connected to {connection.peer_address} (handle={connection.handle})"
|
bumble/profiles/aics.py
CHANGED
|
@@ -24,16 +24,13 @@ import struct
|
|
|
24
24
|
from dataclasses import dataclass
|
|
25
25
|
from typing import Optional
|
|
26
26
|
|
|
27
|
-
from bumble import gatt
|
|
28
27
|
from bumble.device import Connection
|
|
29
28
|
from bumble.att import ATT_Error
|
|
30
29
|
from bumble.gatt import (
|
|
30
|
+
Attribute,
|
|
31
31
|
Characteristic,
|
|
32
|
-
SerializableCharacteristicAdapter,
|
|
33
|
-
PackedCharacteristicAdapter,
|
|
34
32
|
TemplateService,
|
|
35
33
|
CharacteristicValue,
|
|
36
|
-
UTF8CharacteristicAdapter,
|
|
37
34
|
GATT_AUDIO_INPUT_CONTROL_SERVICE,
|
|
38
35
|
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC,
|
|
39
36
|
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC,
|
|
@@ -42,6 +39,14 @@ from bumble.gatt import (
|
|
|
42
39
|
GATT_AUDIO_INPUT_CONTROL_POINT_CHARACTERISTIC,
|
|
43
40
|
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC,
|
|
44
41
|
)
|
|
42
|
+
from bumble.gatt_adapters import (
|
|
43
|
+
CharacteristicProxy,
|
|
44
|
+
PackedCharacteristicProxyAdapter,
|
|
45
|
+
SerializableCharacteristicAdapter,
|
|
46
|
+
SerializableCharacteristicProxyAdapter,
|
|
47
|
+
UTF8CharacteristicAdapter,
|
|
48
|
+
UTF8CharacteristicProxyAdapter,
|
|
49
|
+
)
|
|
45
50
|
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
|
|
46
51
|
from bumble.utils import OpenIntEnum
|
|
47
52
|
|
|
@@ -124,7 +129,7 @@ class AudioInputState:
|
|
|
124
129
|
mute: Mute = Mute.NOT_MUTED
|
|
125
130
|
gain_mode: GainMode = GainMode.MANUAL
|
|
126
131
|
change_counter: int = 0
|
|
127
|
-
|
|
132
|
+
attribute: Optional[Attribute] = None
|
|
128
133
|
|
|
129
134
|
def __bytes__(self) -> bytes:
|
|
130
135
|
return bytes(
|
|
@@ -151,10 +156,8 @@ class AudioInputState:
|
|
|
151
156
|
self.change_counter = (self.change_counter + 1) % (CHANGE_COUNTER_MAX_VALUE + 1)
|
|
152
157
|
|
|
153
158
|
async def notify_subscribers_via_connection(self, connection: Connection) -> None:
|
|
154
|
-
assert self.
|
|
155
|
-
await connection.device.notify_subscribers(
|
|
156
|
-
attribute=self.attribute_value, value=bytes(self)
|
|
157
|
-
)
|
|
159
|
+
assert self.attribute is not None
|
|
160
|
+
await connection.device.notify_subscribers(attribute=self.attribute)
|
|
158
161
|
|
|
159
162
|
|
|
160
163
|
@dataclass
|
|
@@ -315,24 +318,28 @@ class AudioInputDescription:
|
|
|
315
318
|
'''
|
|
316
319
|
|
|
317
320
|
audio_input_description: str = "Bluetooth"
|
|
318
|
-
|
|
321
|
+
attribute: Optional[Attribute] = None
|
|
319
322
|
|
|
320
323
|
def on_read(self, _connection: Optional[Connection]) -> str:
|
|
321
324
|
return self.audio_input_description
|
|
322
325
|
|
|
323
326
|
async def on_write(self, connection: Optional[Connection], value: str) -> None:
|
|
324
327
|
assert connection
|
|
325
|
-
assert self.
|
|
328
|
+
assert self.attribute
|
|
326
329
|
|
|
327
330
|
self.audio_input_description = value
|
|
328
|
-
await connection.device.notify_subscribers(
|
|
329
|
-
attribute=self.attribute_value, value=value
|
|
330
|
-
)
|
|
331
|
+
await connection.device.notify_subscribers(attribute=self.attribute)
|
|
331
332
|
|
|
332
333
|
|
|
333
334
|
class AICSService(TemplateService):
|
|
334
335
|
UUID = GATT_AUDIO_INPUT_CONTROL_SERVICE
|
|
335
336
|
|
|
337
|
+
audio_input_state_characteristic: Characteristic[AudioInputState]
|
|
338
|
+
audio_input_type_characteristic: Characteristic[bytes]
|
|
339
|
+
audio_input_status_characteristic: Characteristic[bytes]
|
|
340
|
+
audio_input_control_point_characteristic: Characteristic[bytes]
|
|
341
|
+
gain_settings_properties_characteristic: Characteristic[GainSettingsProperties]
|
|
342
|
+
|
|
336
343
|
def __init__(
|
|
337
344
|
self,
|
|
338
345
|
audio_input_state: Optional[AudioInputState] = None,
|
|
@@ -374,9 +381,7 @@ class AICSService(TemplateService):
|
|
|
374
381
|
),
|
|
375
382
|
AudioInputState,
|
|
376
383
|
)
|
|
377
|
-
self.audio_input_state.
|
|
378
|
-
self.audio_input_state_characteristic.value
|
|
379
|
-
)
|
|
384
|
+
self.audio_input_state.attribute = self.audio_input_state_characteristic
|
|
380
385
|
|
|
381
386
|
self.gain_settings_properties_characteristic = (
|
|
382
387
|
SerializableCharacteristicAdapter(
|
|
@@ -425,8 +430,8 @@ class AICSService(TemplateService):
|
|
|
425
430
|
),
|
|
426
431
|
)
|
|
427
432
|
)
|
|
428
|
-
self.audio_input_description.
|
|
429
|
-
self.audio_input_control_point_characteristic
|
|
433
|
+
self.audio_input_description.attribute = (
|
|
434
|
+
self.audio_input_control_point_characteristic
|
|
430
435
|
)
|
|
431
436
|
|
|
432
437
|
super().__init__(
|
|
@@ -448,24 +453,29 @@ class AICSService(TemplateService):
|
|
|
448
453
|
class AICSServiceProxy(ProfileServiceProxy):
|
|
449
454
|
SERVICE_CLASS = AICSService
|
|
450
455
|
|
|
456
|
+
audio_input_state: CharacteristicProxy[AudioInputState]
|
|
457
|
+
gain_settings_properties: CharacteristicProxy[GainSettingsProperties]
|
|
458
|
+
audio_input_status: CharacteristicProxy[int]
|
|
459
|
+
audio_input_control_point: CharacteristicProxy[bytes]
|
|
460
|
+
|
|
451
461
|
def __init__(self, service_proxy: ServiceProxy) -> None:
|
|
452
462
|
self.service_proxy = service_proxy
|
|
453
463
|
|
|
454
|
-
self.audio_input_state =
|
|
464
|
+
self.audio_input_state = SerializableCharacteristicProxyAdapter(
|
|
455
465
|
service_proxy.get_required_characteristic_by_uuid(
|
|
456
466
|
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC
|
|
457
467
|
),
|
|
458
468
|
AudioInputState,
|
|
459
469
|
)
|
|
460
470
|
|
|
461
|
-
self.gain_settings_properties =
|
|
471
|
+
self.gain_settings_properties = SerializableCharacteristicProxyAdapter(
|
|
462
472
|
service_proxy.get_required_characteristic_by_uuid(
|
|
463
473
|
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC
|
|
464
474
|
),
|
|
465
475
|
GainSettingsProperties,
|
|
466
476
|
)
|
|
467
477
|
|
|
468
|
-
self.audio_input_status =
|
|
478
|
+
self.audio_input_status = PackedCharacteristicProxyAdapter(
|
|
469
479
|
service_proxy.get_required_characteristic_by_uuid(
|
|
470
480
|
GATT_AUDIO_INPUT_STATUS_CHARACTERISTIC
|
|
471
481
|
),
|
|
@@ -478,7 +488,7 @@ class AICSServiceProxy(ProfileServiceProxy):
|
|
|
478
488
|
)
|
|
479
489
|
)
|
|
480
490
|
|
|
481
|
-
self.audio_input_description =
|
|
491
|
+
self.audio_input_description = UTF8CharacteristicProxyAdapter(
|
|
482
492
|
service_proxy.get_required_characteristic_by_uuid(
|
|
483
493
|
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC
|
|
484
494
|
)
|
bumble/profiles/ascs.py
CHANGED
|
@@ -301,7 +301,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
301
301
|
presentation_delay = 0
|
|
302
302
|
|
|
303
303
|
# Additional parameters in ENABLING, STREAMING, DISABLING State
|
|
304
|
-
metadata
|
|
304
|
+
metadata: le_audio.Metadata
|
|
305
305
|
|
|
306
306
|
def __init__(
|
|
307
307
|
self,
|
|
@@ -313,6 +313,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
313
313
|
self.ase_id = ase_id
|
|
314
314
|
self._state = AseStateMachine.State.IDLE
|
|
315
315
|
self.role = role
|
|
316
|
+
self.metadata = le_audio.Metadata()
|
|
316
317
|
|
|
317
318
|
uuid = (
|
|
318
319
|
gatt.GATT_SINK_ASE_CHARACTERISTIC
|
bumble/profiles/asha.py
CHANGED
|
@@ -134,12 +134,14 @@ class AshaService(gatt.TemplateService):
|
|
|
134
134
|
),
|
|
135
135
|
)
|
|
136
136
|
|
|
137
|
-
self.audio_control_point_characteristic
|
|
138
|
-
gatt.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
self.audio_control_point_characteristic: gatt.Characteristic[bytes] = (
|
|
138
|
+
gatt.Characteristic(
|
|
139
|
+
gatt.GATT_ASHA_AUDIO_CONTROL_POINT_CHARACTERISTIC,
|
|
140
|
+
gatt.Characteristic.Properties.WRITE
|
|
141
|
+
| gatt.Characteristic.Properties.WRITE_WITHOUT_RESPONSE,
|
|
142
|
+
gatt.Characteristic.WRITEABLE,
|
|
143
|
+
gatt.CharacteristicValue(write=self._on_audio_control_point_write),
|
|
144
|
+
)
|
|
143
145
|
)
|
|
144
146
|
self.audio_status_characteristic = gatt.Characteristic(
|
|
145
147
|
gatt.GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC,
|
|
@@ -147,7 +149,7 @@ class AshaService(gatt.TemplateService):
|
|
|
147
149
|
gatt.Characteristic.READABLE,
|
|
148
150
|
bytes([AudioStatus.OK]),
|
|
149
151
|
)
|
|
150
|
-
self.volume_characteristic = gatt.Characteristic(
|
|
152
|
+
self.volume_characteristic: gatt.Characteristic[bytes] = gatt.Characteristic(
|
|
151
153
|
gatt.GATT_ASHA_VOLUME_CHARACTERISTIC,
|
|
152
154
|
gatt.Characteristic.Properties.WRITE_WITHOUT_RESPONSE,
|
|
153
155
|
gatt.Characteristic.WRITEABLE,
|
|
@@ -166,13 +168,13 @@ class AshaService(gatt.TemplateService):
|
|
|
166
168
|
struct.pack('<H', self.psm),
|
|
167
169
|
)
|
|
168
170
|
|
|
169
|
-
characteristics =
|
|
171
|
+
characteristics = (
|
|
170
172
|
self.read_only_properties_characteristic,
|
|
171
173
|
self.audio_control_point_characteristic,
|
|
172
174
|
self.audio_status_characteristic,
|
|
173
175
|
self.volume_characteristic,
|
|
174
176
|
self.le_psm_out_characteristic,
|
|
175
|
-
|
|
177
|
+
)
|
|
176
178
|
|
|
177
179
|
super().__init__(characteristics)
|
|
178
180
|
|
bumble/profiles/bass.py
CHANGED
|
@@ -20,11 +20,12 @@ from __future__ import annotations
|
|
|
20
20
|
import dataclasses
|
|
21
21
|
import logging
|
|
22
22
|
import struct
|
|
23
|
-
from typing import ClassVar,
|
|
23
|
+
from typing import ClassVar, Optional, Sequence
|
|
24
24
|
|
|
25
25
|
from bumble import core
|
|
26
26
|
from bumble import device
|
|
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
|
from bumble import utils
|
|
@@ -52,7 +53,7 @@ def encode_subgroups(subgroups: Sequence[SubgroupInfo]) -> bytes:
|
|
|
52
53
|
)
|
|
53
54
|
|
|
54
55
|
|
|
55
|
-
def decode_subgroups(data: bytes) ->
|
|
56
|
+
def decode_subgroups(data: bytes) -> list[SubgroupInfo]:
|
|
56
57
|
num_subgroups = data[0]
|
|
57
58
|
offset = 1
|
|
58
59
|
subgroups = []
|
|
@@ -273,7 +274,7 @@ class BroadcastReceiveState:
|
|
|
273
274
|
pa_sync_state: PeriodicAdvertisingSyncState
|
|
274
275
|
big_encryption: BigEncryption
|
|
275
276
|
bad_code: bytes
|
|
276
|
-
subgroups:
|
|
277
|
+
subgroups: list[SubgroupInfo]
|
|
277
278
|
|
|
278
279
|
@classmethod
|
|
279
280
|
def from_bytes(cls, data: bytes) -> BroadcastReceiveState:
|
|
@@ -354,7 +355,9 @@ class BroadcastAudioScanServiceProxy(gatt_client.ProfileServiceProxy):
|
|
|
354
355
|
SERVICE_CLASS = BroadcastAudioScanService
|
|
355
356
|
|
|
356
357
|
broadcast_audio_scan_control_point: gatt_client.CharacteristicProxy
|
|
357
|
-
broadcast_receive_states:
|
|
358
|
+
broadcast_receive_states: list[
|
|
359
|
+
gatt_client.CharacteristicProxy[Optional[BroadcastReceiveState]]
|
|
360
|
+
]
|
|
358
361
|
|
|
359
362
|
def __init__(self, service_proxy: gatt_client.ServiceProxy):
|
|
360
363
|
self.service_proxy = service_proxy
|
|
@@ -366,7 +369,7 @@ class BroadcastAudioScanServiceProxy(gatt_client.ProfileServiceProxy):
|
|
|
366
369
|
)
|
|
367
370
|
|
|
368
371
|
self.broadcast_receive_states = [
|
|
369
|
-
|
|
372
|
+
gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
370
373
|
characteristic,
|
|
371
374
|
decode=lambda x: BroadcastReceiveState.from_bytes(x) if x else None,
|
|
372
375
|
)
|
|
@@ -16,14 +16,20 @@
|
|
|
16
16
|
# -----------------------------------------------------------------------------
|
|
17
17
|
# Imports
|
|
18
18
|
# -----------------------------------------------------------------------------
|
|
19
|
-
from
|
|
20
|
-
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from bumble.gatt_client import ProfileServiceProxy
|
|
22
|
+
from bumble.gatt import (
|
|
21
23
|
GATT_BATTERY_SERVICE,
|
|
22
24
|
GATT_BATTERY_LEVEL_CHARACTERISTIC,
|
|
23
25
|
TemplateService,
|
|
24
26
|
Characteristic,
|
|
25
27
|
CharacteristicValue,
|
|
28
|
+
)
|
|
29
|
+
from bumble.gatt_client import CharacteristicProxy
|
|
30
|
+
from bumble.gatt_adapters import (
|
|
26
31
|
PackedCharacteristicAdapter,
|
|
32
|
+
PackedCharacteristicProxyAdapter,
|
|
27
33
|
)
|
|
28
34
|
|
|
29
35
|
|
|
@@ -32,6 +38,8 @@ class BatteryService(TemplateService):
|
|
|
32
38
|
UUID = GATT_BATTERY_SERVICE
|
|
33
39
|
BATTERY_LEVEL_FORMAT = 'B'
|
|
34
40
|
|
|
41
|
+
battery_level_characteristic: Characteristic[int]
|
|
42
|
+
|
|
35
43
|
def __init__(self, read_battery_level):
|
|
36
44
|
self.battery_level_characteristic = PackedCharacteristicAdapter(
|
|
37
45
|
Characteristic(
|
|
@@ -49,13 +57,15 @@ class BatteryService(TemplateService):
|
|
|
49
57
|
class BatteryServiceProxy(ProfileServiceProxy):
|
|
50
58
|
SERVICE_CLASS = BatteryService
|
|
51
59
|
|
|
60
|
+
battery_level: Optional[CharacteristicProxy[int]]
|
|
61
|
+
|
|
52
62
|
def __init__(self, service_proxy):
|
|
53
63
|
self.service_proxy = service_proxy
|
|
54
64
|
|
|
55
65
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
56
66
|
GATT_BATTERY_LEVEL_CHARACTERISTIC
|
|
57
67
|
):
|
|
58
|
-
self.battery_level =
|
|
68
|
+
self.battery_level = PackedCharacteristicProxyAdapter(
|
|
59
69
|
characteristics[0], pack_format=BatteryService.BATTERY_LEVEL_FORMAT
|
|
60
70
|
)
|
|
61
71
|
else:
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
import struct
|
|
20
20
|
from typing import Optional, Tuple
|
|
21
21
|
|
|
22
|
-
from bumble.gatt_client import ServiceProxy, ProfileServiceProxy, CharacteristicProxy
|
|
23
22
|
from bumble.gatt import (
|
|
24
23
|
GATT_DEVICE_INFORMATION_SERVICE,
|
|
25
24
|
GATT_FIRMWARE_REVISION_STRING_CHARACTERISTIC,
|
|
@@ -32,9 +31,12 @@ from bumble.gatt import (
|
|
|
32
31
|
GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC,
|
|
33
32
|
TemplateService,
|
|
34
33
|
Characteristic,
|
|
35
|
-
DelegatedCharacteristicAdapter,
|
|
36
|
-
UTF8CharacteristicAdapter,
|
|
37
34
|
)
|
|
35
|
+
from bumble.gatt_adapters import (
|
|
36
|
+
DelegatedCharacteristicProxyAdapter,
|
|
37
|
+
UTF8CharacteristicProxyAdapter,
|
|
38
|
+
)
|
|
39
|
+
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
# -----------------------------------------------------------------------------
|
|
@@ -62,7 +64,7 @@ class DeviceInformationService(TemplateService):
|
|
|
62
64
|
ieee_regulatory_certification_data_list: Optional[bytes] = None,
|
|
63
65
|
# TODO: pnp_id
|
|
64
66
|
):
|
|
65
|
-
characteristics = [
|
|
67
|
+
characteristics: list[Characteristic[bytes]] = [
|
|
66
68
|
Characteristic(
|
|
67
69
|
uuid,
|
|
68
70
|
Characteristic.Properties.READ,
|
|
@@ -107,14 +109,14 @@ class DeviceInformationService(TemplateService):
|
|
|
107
109
|
class DeviceInformationServiceProxy(ProfileServiceProxy):
|
|
108
110
|
SERVICE_CLASS = DeviceInformationService
|
|
109
111
|
|
|
110
|
-
manufacturer_name: Optional[
|
|
111
|
-
model_number: Optional[
|
|
112
|
-
serial_number: Optional[
|
|
113
|
-
hardware_revision: Optional[
|
|
114
|
-
firmware_revision: Optional[
|
|
115
|
-
software_revision: Optional[
|
|
116
|
-
system_id: Optional[
|
|
117
|
-
ieee_regulatory_certification_data_list: Optional[CharacteristicProxy]
|
|
112
|
+
manufacturer_name: Optional[CharacteristicProxy[str]]
|
|
113
|
+
model_number: Optional[CharacteristicProxy[str]]
|
|
114
|
+
serial_number: Optional[CharacteristicProxy[str]]
|
|
115
|
+
hardware_revision: Optional[CharacteristicProxy[str]]
|
|
116
|
+
firmware_revision: Optional[CharacteristicProxy[str]]
|
|
117
|
+
software_revision: Optional[CharacteristicProxy[str]]
|
|
118
|
+
system_id: Optional[CharacteristicProxy[tuple[int, int]]]
|
|
119
|
+
ieee_regulatory_certification_data_list: Optional[CharacteristicProxy[bytes]]
|
|
118
120
|
|
|
119
121
|
def __init__(self, service_proxy: ServiceProxy):
|
|
120
122
|
self.service_proxy = service_proxy
|
|
@@ -128,7 +130,7 @@ class DeviceInformationServiceProxy(ProfileServiceProxy):
|
|
|
128
130
|
('software_revision', GATT_SOFTWARE_REVISION_STRING_CHARACTERISTIC),
|
|
129
131
|
):
|
|
130
132
|
if characteristics := service_proxy.get_characteristics_by_uuid(uuid):
|
|
131
|
-
characteristic =
|
|
133
|
+
characteristic = UTF8CharacteristicProxyAdapter(characteristics[0])
|
|
132
134
|
else:
|
|
133
135
|
characteristic = None
|
|
134
136
|
self.__setattr__(field, characteristic)
|
|
@@ -136,7 +138,7 @@ class DeviceInformationServiceProxy(ProfileServiceProxy):
|
|
|
136
138
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
137
139
|
GATT_SYSTEM_ID_CHARACTERISTIC
|
|
138
140
|
):
|
|
139
|
-
self.system_id =
|
|
141
|
+
self.system_id = DelegatedCharacteristicProxyAdapter(
|
|
140
142
|
characteristics[0],
|
|
141
143
|
encode=lambda v: DeviceInformationService.pack_system_id(*v),
|
|
142
144
|
decode=DeviceInformationService.unpack_system_id,
|
bumble/profiles/gap.py
CHANGED
|
@@ -25,14 +25,15 @@ from bumble.core import Appearance
|
|
|
25
25
|
from bumble.gatt import (
|
|
26
26
|
TemplateService,
|
|
27
27
|
Characteristic,
|
|
28
|
-
CharacteristicAdapter,
|
|
29
|
-
DelegatedCharacteristicAdapter,
|
|
30
|
-
UTF8CharacteristicAdapter,
|
|
31
28
|
GATT_GENERIC_ACCESS_SERVICE,
|
|
32
29
|
GATT_DEVICE_NAME_CHARACTERISTIC,
|
|
33
30
|
GATT_APPEARANCE_CHARACTERISTIC,
|
|
34
31
|
)
|
|
35
|
-
from bumble.
|
|
32
|
+
from bumble.gatt_adapters import (
|
|
33
|
+
DelegatedCharacteristicProxyAdapter,
|
|
34
|
+
UTF8CharacteristicProxyAdapter,
|
|
35
|
+
)
|
|
36
|
+
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
|
36
37
|
|
|
37
38
|
# -----------------------------------------------------------------------------
|
|
38
39
|
# Logging
|
|
@@ -49,6 +50,9 @@ logger = logging.getLogger(__name__)
|
|
|
49
50
|
class GenericAccessService(TemplateService):
|
|
50
51
|
UUID = GATT_GENERIC_ACCESS_SERVICE
|
|
51
52
|
|
|
53
|
+
device_name_characteristic: Characteristic[bytes]
|
|
54
|
+
appearance_characteristic: Characteristic[bytes]
|
|
55
|
+
|
|
52
56
|
def __init__(
|
|
53
57
|
self, device_name: str, appearance: Union[Appearance, Tuple[int, int], int] = 0
|
|
54
58
|
):
|
|
@@ -84,8 +88,8 @@ class GenericAccessService(TemplateService):
|
|
|
84
88
|
class GenericAccessServiceProxy(ProfileServiceProxy):
|
|
85
89
|
SERVICE_CLASS = GenericAccessService
|
|
86
90
|
|
|
87
|
-
device_name: Optional[
|
|
88
|
-
appearance: Optional[
|
|
91
|
+
device_name: Optional[CharacteristicProxy[str]]
|
|
92
|
+
appearance: Optional[CharacteristicProxy[Appearance]]
|
|
89
93
|
|
|
90
94
|
def __init__(self, service_proxy: ServiceProxy):
|
|
91
95
|
self.service_proxy = service_proxy
|
|
@@ -93,14 +97,14 @@ class GenericAccessServiceProxy(ProfileServiceProxy):
|
|
|
93
97
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
94
98
|
GATT_DEVICE_NAME_CHARACTERISTIC
|
|
95
99
|
):
|
|
96
|
-
self.device_name =
|
|
100
|
+
self.device_name = UTF8CharacteristicProxyAdapter(characteristics[0])
|
|
97
101
|
else:
|
|
98
102
|
self.device_name = None
|
|
99
103
|
|
|
100
104
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
101
105
|
GATT_APPEARANCE_CHARACTERISTIC
|
|
102
106
|
):
|
|
103
|
-
self.appearance =
|
|
107
|
+
self.appearance = DelegatedCharacteristicProxyAdapter(
|
|
104
108
|
characteristics[0],
|
|
105
109
|
decode=lambda value: Appearance.from_int(
|
|
106
110
|
struct.unpack_from('<H', value, 0)[0],
|
bumble/profiles/gatt_service.py
CHANGED
|
@@ -110,6 +110,7 @@ class GenericAttributeProfileService(gatt.TemplateService):
|
|
|
110
110
|
gatt.GATT_CHARACTERISTIC_ATTRIBUTE_TYPE,
|
|
111
111
|
gatt.GATT_CHARACTERISTIC_EXTENDED_PROPERTIES_DESCRIPTOR,
|
|
112
112
|
):
|
|
113
|
+
assert isinstance(attribute.value, bytes)
|
|
113
114
|
return (
|
|
114
115
|
struct.pack("<H", attribute.handle)
|
|
115
116
|
+ attribute.type.to_bytes()
|
bumble/profiles/gmap.py
CHANGED
|
@@ -22,7 +22,6 @@ from typing import Optional
|
|
|
22
22
|
|
|
23
23
|
from bumble.gatt import (
|
|
24
24
|
TemplateService,
|
|
25
|
-
DelegatedCharacteristicAdapter,
|
|
26
25
|
Characteristic,
|
|
27
26
|
GATT_GAMING_AUDIO_SERVICE,
|
|
28
27
|
GATT_GMAP_ROLE_CHARACTERISTIC,
|
|
@@ -31,7 +30,8 @@ from bumble.gatt import (
|
|
|
31
30
|
GATT_BGS_FEATURES_CHARACTERISTIC,
|
|
32
31
|
GATT_BGR_FEATURES_CHARACTERISTIC,
|
|
33
32
|
)
|
|
34
|
-
from bumble.
|
|
33
|
+
from bumble.gatt_adapters import DelegatedCharacteristicProxyAdapter
|
|
34
|
+
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
|
35
35
|
from enum import IntFlag
|
|
36
36
|
|
|
37
37
|
|
|
@@ -150,10 +150,15 @@ class GamingAudioService(TemplateService):
|
|
|
150
150
|
class GamingAudioServiceProxy(ProfileServiceProxy):
|
|
151
151
|
SERVICE_CLASS = GamingAudioService
|
|
152
152
|
|
|
153
|
+
ugg_features: Optional[CharacteristicProxy[UggFeatures]] = None
|
|
154
|
+
ugt_features: Optional[CharacteristicProxy[UgtFeatures]] = None
|
|
155
|
+
bgs_features: Optional[CharacteristicProxy[BgsFeatures]] = None
|
|
156
|
+
bgr_features: Optional[CharacteristicProxy[BgrFeatures]] = None
|
|
157
|
+
|
|
153
158
|
def __init__(self, service_proxy: ServiceProxy) -> None:
|
|
154
159
|
self.service_proxy = service_proxy
|
|
155
160
|
|
|
156
|
-
self.gmap_role =
|
|
161
|
+
self.gmap_role = DelegatedCharacteristicProxyAdapter(
|
|
157
162
|
service_proxy.get_required_characteristic_by_uuid(
|
|
158
163
|
GATT_GMAP_ROLE_CHARACTERISTIC
|
|
159
164
|
),
|
|
@@ -163,31 +168,31 @@ class GamingAudioServiceProxy(ProfileServiceProxy):
|
|
|
163
168
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
164
169
|
GATT_UGG_FEATURES_CHARACTERISTIC
|
|
165
170
|
):
|
|
166
|
-
self.ugg_features =
|
|
167
|
-
|
|
171
|
+
self.ugg_features = DelegatedCharacteristicProxyAdapter(
|
|
172
|
+
characteristics[0],
|
|
168
173
|
decode=lambda value: UggFeatures(value[0]),
|
|
169
174
|
)
|
|
170
175
|
|
|
171
176
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
172
177
|
GATT_UGT_FEATURES_CHARACTERISTIC
|
|
173
178
|
):
|
|
174
|
-
self.ugt_features =
|
|
175
|
-
|
|
179
|
+
self.ugt_features = DelegatedCharacteristicProxyAdapter(
|
|
180
|
+
characteristics[0],
|
|
176
181
|
decode=lambda value: UgtFeatures(value[0]),
|
|
177
182
|
)
|
|
178
183
|
|
|
179
184
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
180
185
|
GATT_BGS_FEATURES_CHARACTERISTIC
|
|
181
186
|
):
|
|
182
|
-
self.bgs_features =
|
|
183
|
-
|
|
187
|
+
self.bgs_features = DelegatedCharacteristicProxyAdapter(
|
|
188
|
+
characteristics[0],
|
|
184
189
|
decode=lambda value: BgsFeatures(value[0]),
|
|
185
190
|
)
|
|
186
191
|
|
|
187
192
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
188
193
|
GATT_BGR_FEATURES_CHARACTERISTIC
|
|
189
194
|
):
|
|
190
|
-
self.bgr_features =
|
|
191
|
-
|
|
195
|
+
self.bgr_features = DelegatedCharacteristicProxyAdapter(
|
|
196
|
+
characteristics[0],
|
|
192
197
|
decode=lambda value: BgrFeatures(value[0]),
|
|
193
198
|
)
|
bumble/profiles/hap.py
CHANGED
|
@@ -18,14 +18,15 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
import asyncio
|
|
20
20
|
import functools
|
|
21
|
-
from
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
import logging
|
|
23
|
+
from typing import Any, Dict, List, Optional, Set, Union
|
|
24
|
+
|
|
25
|
+
from bumble import att, gatt, gatt_adapters, gatt_client
|
|
22
26
|
from bumble.core import InvalidArgumentError, InvalidStateError
|
|
23
27
|
from bumble.device import Device, Connection
|
|
24
28
|
from bumble.utils import AsyncRunner, OpenIntEnum
|
|
25
29
|
from bumble.hci import Address
|
|
26
|
-
from dataclasses import dataclass, field
|
|
27
|
-
import logging
|
|
28
|
-
from typing import Any, Dict, List, Optional, Set, Union
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
# -----------------------------------------------------------------------------
|
|
@@ -631,11 +632,12 @@ class HearingAccessServiceProxy(gatt_client.ProfileServiceProxy):
|
|
|
631
632
|
|
|
632
633
|
hearing_aid_preset_control_point: gatt_client.CharacteristicProxy
|
|
633
634
|
preset_control_point_indications: asyncio.Queue
|
|
635
|
+
active_preset_index_notification: asyncio.Queue
|
|
634
636
|
|
|
635
637
|
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
636
638
|
self.service_proxy = service_proxy
|
|
637
639
|
|
|
638
|
-
self.server_features =
|
|
640
|
+
self.server_features = gatt_adapters.PackedCharacteristicProxyAdapter(
|
|
639
641
|
service_proxy.get_characteristics_by_uuid(
|
|
640
642
|
gatt.GATT_HEARING_AID_FEATURES_CHARACTERISTIC
|
|
641
643
|
)[0],
|
|
@@ -648,7 +650,7 @@ class HearingAccessServiceProxy(gatt_client.ProfileServiceProxy):
|
|
|
648
650
|
)[0]
|
|
649
651
|
)
|
|
650
652
|
|
|
651
|
-
self.active_preset_index =
|
|
653
|
+
self.active_preset_index = gatt_adapters.PackedCharacteristicProxyAdapter(
|
|
652
654
|
service_proxy.get_characteristics_by_uuid(
|
|
653
655
|
gatt.GATT_ACTIVE_PRESET_INDEX_CHARACTERISTIC
|
|
654
656
|
)[0],
|