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/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,57 +453,43 @@ 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
|
-
|
|
455
|
-
|
|
464
|
+
self.audio_input_state = SerializableCharacteristicProxyAdapter(
|
|
465
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
456
466
|
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
raise gatt.InvalidServiceError("Audio Input State Characteristic not found")
|
|
460
|
-
self.audio_input_state = SerializableCharacteristicAdapter(
|
|
461
|
-
characteristics[0], AudioInputState
|
|
467
|
+
),
|
|
468
|
+
AudioInputState,
|
|
462
469
|
)
|
|
463
470
|
|
|
464
|
-
|
|
465
|
-
|
|
471
|
+
self.gain_settings_properties = SerializableCharacteristicProxyAdapter(
|
|
472
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
466
473
|
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
raise gatt.InvalidServiceError(
|
|
470
|
-
"Gain Settings Attribute Characteristic not found"
|
|
471
|
-
)
|
|
472
|
-
self.gain_settings_properties = SerializableCharacteristicAdapter(
|
|
473
|
-
characteristics[0], GainSettingsProperties
|
|
474
|
+
),
|
|
475
|
+
GainSettingsProperties,
|
|
474
476
|
)
|
|
475
477
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
+
self.audio_input_status = PackedCharacteristicProxyAdapter(
|
|
479
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
478
480
|
GATT_AUDIO_INPUT_STATUS_CHARACTERISTIC
|
|
479
|
-
)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
"Audio Input Status Characteristic not found"
|
|
483
|
-
)
|
|
484
|
-
self.audio_input_status = PackedCharacteristicAdapter(characteristics[0], 'B')
|
|
481
|
+
),
|
|
482
|
+
'B',
|
|
483
|
+
)
|
|
485
484
|
|
|
486
|
-
|
|
487
|
-
|
|
485
|
+
self.audio_input_control_point = (
|
|
486
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
488
487
|
GATT_AUDIO_INPUT_CONTROL_POINT_CHARACTERISTIC
|
|
489
488
|
)
|
|
490
|
-
)
|
|
491
|
-
raise gatt.InvalidServiceError(
|
|
492
|
-
"Audio Input Control Point Characteristic not found"
|
|
493
|
-
)
|
|
494
|
-
self.audio_input_control_point = characteristics[0]
|
|
489
|
+
)
|
|
495
490
|
|
|
496
|
-
|
|
497
|
-
|
|
491
|
+
self.audio_input_description = UTF8CharacteristicProxyAdapter(
|
|
492
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
498
493
|
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC
|
|
499
494
|
)
|
|
500
|
-
)
|
|
501
|
-
raise gatt.InvalidServiceError(
|
|
502
|
-
"Audio Input Description Characteristic not found"
|
|
503
|
-
)
|
|
504
|
-
self.audio_input_description = UTF8CharacteristicAdapter(characteristics[0])
|
|
495
|
+
)
|
bumble/profiles/ascs.py
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
# Imports
|
|
18
18
|
# -----------------------------------------------------------------------------
|
|
19
19
|
from __future__ import annotations
|
|
20
|
+
|
|
20
21
|
import enum
|
|
21
22
|
import logging
|
|
22
23
|
import struct
|
|
@@ -258,8 +259,8 @@ class AseReasonCode(enum.IntEnum):
|
|
|
258
259
|
|
|
259
260
|
# -----------------------------------------------------------------------------
|
|
260
261
|
class AudioRole(enum.IntEnum):
|
|
261
|
-
SINK =
|
|
262
|
-
SOURCE =
|
|
262
|
+
SINK = device.CisLink.Direction.CONTROLLER_TO_HOST
|
|
263
|
+
SOURCE = device.CisLink.Direction.HOST_TO_CONTROLLER
|
|
263
264
|
|
|
264
265
|
|
|
265
266
|
# -----------------------------------------------------------------------------
|
|
@@ -300,7 +301,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
300
301
|
presentation_delay = 0
|
|
301
302
|
|
|
302
303
|
# Additional parameters in ENABLING, STREAMING, DISABLING State
|
|
303
|
-
metadata
|
|
304
|
+
metadata: le_audio.Metadata
|
|
304
305
|
|
|
305
306
|
def __init__(
|
|
306
307
|
self,
|
|
@@ -312,6 +313,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
312
313
|
self.ase_id = ase_id
|
|
313
314
|
self._state = AseStateMachine.State.IDLE
|
|
314
315
|
self.role = role
|
|
316
|
+
self.metadata = le_audio.Metadata()
|
|
315
317
|
|
|
316
318
|
uuid = (
|
|
317
319
|
gatt.GATT_SINK_ASE_CHARACTERISTIC
|
|
@@ -354,16 +356,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
354
356
|
cis_link.on('disconnection', self.on_cis_disconnection)
|
|
355
357
|
|
|
356
358
|
async def post_cis_established():
|
|
357
|
-
await self.
|
|
358
|
-
hci.HCI_LE_Setup_ISO_Data_Path_Command(
|
|
359
|
-
connection_handle=cis_link.handle,
|
|
360
|
-
data_path_direction=self.role,
|
|
361
|
-
data_path_id=0x00, # Fixed HCI
|
|
362
|
-
codec_id=hci.CodingFormat(hci.CodecID.TRANSPARENT),
|
|
363
|
-
controller_delay=0,
|
|
364
|
-
codec_configuration=b'',
|
|
365
|
-
)
|
|
366
|
-
)
|
|
359
|
+
await cis_link.setup_data_path(direction=self.role)
|
|
367
360
|
if self.role == AudioRole.SINK:
|
|
368
361
|
self.state = self.State.STREAMING
|
|
369
362
|
await self.service.device.notify_subscribers(self, self.value)
|
|
@@ -511,12 +504,8 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
511
504
|
self.state = self.State.RELEASING
|
|
512
505
|
|
|
513
506
|
async def remove_cis_async():
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
connection_handle=self.cis_link.handle,
|
|
517
|
-
data_path_direction=self.role,
|
|
518
|
-
)
|
|
519
|
-
)
|
|
507
|
+
if self.cis_link:
|
|
508
|
+
await self.cis_link.remove_data_path(self.role)
|
|
520
509
|
self.state = self.State.IDLE
|
|
521
510
|
await self.service.device.notify_subscribers(self, self.value)
|
|
522
511
|
|
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
|
|
|
@@ -288,8 +290,8 @@ class AshaServiceProxy(gatt_client.ProfileServiceProxy):
|
|
|
288
290
|
'psm_characteristic',
|
|
289
291
|
),
|
|
290
292
|
):
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
setattr(
|
|
294
|
+
self,
|
|
295
|
+
attribute_name,
|
|
296
|
+
self.service_proxy.get_required_characteristic_by_uuid(uuid),
|
|
297
|
+
)
|
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,34 +355,27 @@ 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
|
|
361
364
|
|
|
362
|
-
|
|
363
|
-
|
|
365
|
+
self.broadcast_audio_scan_control_point = (
|
|
366
|
+
service_proxy.get_required_characteristic_by_uuid(
|
|
364
367
|
gatt.GATT_BROADCAST_AUDIO_SCAN_CONTROL_POINT_CHARACTERISTIC
|
|
365
368
|
)
|
|
366
|
-
)
|
|
367
|
-
raise gatt.InvalidServiceError(
|
|
368
|
-
"Broadcast Audio Scan Control Point characteristic not found"
|
|
369
|
-
)
|
|
370
|
-
self.broadcast_audio_scan_control_point = characteristics[0]
|
|
369
|
+
)
|
|
371
370
|
|
|
372
|
-
if not (
|
|
373
|
-
characteristics := service_proxy.get_characteristics_by_uuid(
|
|
374
|
-
gatt.GATT_BROADCAST_RECEIVE_STATE_CHARACTERISTIC
|
|
375
|
-
)
|
|
376
|
-
):
|
|
377
|
-
raise gatt.InvalidServiceError(
|
|
378
|
-
"Broadcast Receive State characteristic not found"
|
|
379
|
-
)
|
|
380
371
|
self.broadcast_receive_states = [
|
|
381
|
-
|
|
382
|
-
characteristic,
|
|
372
|
+
gatt_adapters.DelegatedCharacteristicProxyAdapter(
|
|
373
|
+
characteristic,
|
|
374
|
+
decode=lambda x: BroadcastReceiveState.from_bytes(x) if x else None,
|
|
375
|
+
)
|
|
376
|
+
for characteristic in service_proxy.get_characteristics_by_uuid(
|
|
377
|
+
gatt.GATT_BROADCAST_RECEIVE_STATE_CHARACTERISTIC
|
|
383
378
|
)
|
|
384
|
-
for characteristic in characteristics
|
|
385
379
|
]
|
|
386
380
|
|
|
387
381
|
async def send_control_point_operation(
|
|
@@ -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],
|