bumble 0.0.203__py3-none-any.whl → 0.0.204__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +2 -2
- bumble/apps/bench.py +2 -1
- bumble/apps/pair.py +13 -8
- bumble/apps/show.py +6 -6
- bumble/att.py +10 -11
- bumble/drivers/common.py +2 -0
- bumble/drivers/intel.py +593 -24
- bumble/gatt.py +32 -6
- bumble/gatt_server.py +12 -1
- bumble/hci.py +46 -23
- bumble/host.py +4 -1
- bumble/pairing.py +3 -0
- bumble/profiles/aics.py +34 -50
- bumble/profiles/bass.py +4 -7
- bumble/profiles/device_information_service.py +4 -1
- bumble/profiles/heart_rate_service.py +5 -6
- bumble/profiles/vocs.py +330 -0
- bumble/smp.py +7 -2
- bumble/tools/intel_fw_download.py +130 -0
- bumble/tools/intel_util.py +154 -0
- bumble/transport/usb.py +8 -2
- bumble/utils.py +20 -5
- bumble/vendor/android/hci.py +29 -4
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/METADATA +1 -1
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/RECORD +29 -26
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/entry_points.txt +2 -0
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/LICENSE +0 -0
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/WHEEL +0 -0
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/top_level.txt +0 -0
bumble/gatt.py
CHANGED
|
@@ -28,12 +28,15 @@ import functools
|
|
|
28
28
|
import logging
|
|
29
29
|
import struct
|
|
30
30
|
from typing import (
|
|
31
|
+
Any,
|
|
31
32
|
Callable,
|
|
32
33
|
Dict,
|
|
33
34
|
Iterable,
|
|
34
35
|
List,
|
|
35
36
|
Optional,
|
|
36
37
|
Sequence,
|
|
38
|
+
SupportsBytes,
|
|
39
|
+
Type,
|
|
37
40
|
Union,
|
|
38
41
|
TYPE_CHECKING,
|
|
39
42
|
)
|
|
@@ -41,6 +44,7 @@ from typing import (
|
|
|
41
44
|
from bumble.colors import color
|
|
42
45
|
from bumble.core import BaseBumbleError, UUID
|
|
43
46
|
from bumble.att import Attribute, AttributeValue
|
|
47
|
+
from bumble.utils import ByteSerializable
|
|
44
48
|
|
|
45
49
|
if TYPE_CHECKING:
|
|
46
50
|
from bumble.gatt_client import AttributeProxy
|
|
@@ -343,7 +347,7 @@ class Service(Attribute):
|
|
|
343
347
|
def __init__(
|
|
344
348
|
self,
|
|
345
349
|
uuid: Union[str, UUID],
|
|
346
|
-
characteristics:
|
|
350
|
+
characteristics: Iterable[Characteristic],
|
|
347
351
|
primary=True,
|
|
348
352
|
included_services: Iterable[Service] = (),
|
|
349
353
|
) -> None:
|
|
@@ -362,7 +366,7 @@ class Service(Attribute):
|
|
|
362
366
|
)
|
|
363
367
|
self.uuid = uuid
|
|
364
368
|
self.included_services = list(included_services)
|
|
365
|
-
self.characteristics = characteristics
|
|
369
|
+
self.characteristics = list(characteristics)
|
|
366
370
|
self.primary = primary
|
|
367
371
|
|
|
368
372
|
def get_advertising_data(self) -> Optional[bytes]:
|
|
@@ -393,7 +397,7 @@ class TemplateService(Service):
|
|
|
393
397
|
|
|
394
398
|
def __init__(
|
|
395
399
|
self,
|
|
396
|
-
characteristics:
|
|
400
|
+
characteristics: Iterable[Characteristic],
|
|
397
401
|
primary: bool = True,
|
|
398
402
|
included_services: Iterable[Service] = (),
|
|
399
403
|
) -> None:
|
|
@@ -490,7 +494,7 @@ class Characteristic(Attribute):
|
|
|
490
494
|
uuid: Union[str, bytes, UUID],
|
|
491
495
|
properties: Characteristic.Properties,
|
|
492
496
|
permissions: Union[str, Attribute.Permissions],
|
|
493
|
-
value:
|
|
497
|
+
value: Any = b'',
|
|
494
498
|
descriptors: Sequence[Descriptor] = (),
|
|
495
499
|
):
|
|
496
500
|
super().__init__(uuid, permissions, value)
|
|
@@ -525,7 +529,11 @@ class CharacteristicDeclaration(Attribute):
|
|
|
525
529
|
|
|
526
530
|
characteristic: Characteristic
|
|
527
531
|
|
|
528
|
-
def __init__(
|
|
532
|
+
def __init__(
|
|
533
|
+
self,
|
|
534
|
+
characteristic: Characteristic,
|
|
535
|
+
value_handle: int,
|
|
536
|
+
) -> None:
|
|
529
537
|
declaration_bytes = (
|
|
530
538
|
struct.pack('<BH', characteristic.properties, value_handle)
|
|
531
539
|
+ characteristic.uuid.to_pdu_bytes()
|
|
@@ -705,7 +713,7 @@ class MappedCharacteristicAdapter(PackedCharacteristicAdapter):
|
|
|
705
713
|
'''
|
|
706
714
|
Adapter that packs/unpacks characteristic values according to a standard
|
|
707
715
|
Python `struct` format.
|
|
708
|
-
The adapted `read_value` and `write_value` methods return/accept
|
|
716
|
+
The adapted `read_value` and `write_value` methods return/accept a dictionary which
|
|
709
717
|
is packed/unpacked according to format, with the arguments extracted from the
|
|
710
718
|
dictionary by key, in the same order as they occur in the `keys` parameter.
|
|
711
719
|
'''
|
|
@@ -735,6 +743,24 @@ class UTF8CharacteristicAdapter(CharacteristicAdapter):
|
|
|
735
743
|
return value.decode('utf-8')
|
|
736
744
|
|
|
737
745
|
|
|
746
|
+
# -----------------------------------------------------------------------------
|
|
747
|
+
class SerializableCharacteristicAdapter(CharacteristicAdapter):
|
|
748
|
+
'''
|
|
749
|
+
Adapter that converts any class to/from bytes using the class'
|
|
750
|
+
`to_bytes` and `__bytes__` methods, respectively.
|
|
751
|
+
'''
|
|
752
|
+
|
|
753
|
+
def __init__(self, characteristic, cls: Type[ByteSerializable]):
|
|
754
|
+
super().__init__(characteristic)
|
|
755
|
+
self.cls = cls
|
|
756
|
+
|
|
757
|
+
def encode_value(self, value: SupportsBytes) -> bytes:
|
|
758
|
+
return bytes(value)
|
|
759
|
+
|
|
760
|
+
def decode_value(self, value: bytes) -> Any:
|
|
761
|
+
return self.cls.from_bytes(value)
|
|
762
|
+
|
|
763
|
+
|
|
738
764
|
# -----------------------------------------------------------------------------
|
|
739
765
|
class Descriptor(Attribute):
|
|
740
766
|
'''
|
bumble/gatt_server.py
CHANGED
|
@@ -28,7 +28,17 @@ import asyncio
|
|
|
28
28
|
import logging
|
|
29
29
|
from collections import defaultdict
|
|
30
30
|
import struct
|
|
31
|
-
from typing import
|
|
31
|
+
from typing import (
|
|
32
|
+
Dict,
|
|
33
|
+
Iterable,
|
|
34
|
+
List,
|
|
35
|
+
Optional,
|
|
36
|
+
Tuple,
|
|
37
|
+
TypeVar,
|
|
38
|
+
Type,
|
|
39
|
+
Union,
|
|
40
|
+
TYPE_CHECKING,
|
|
41
|
+
)
|
|
32
42
|
from pyee import EventEmitter
|
|
33
43
|
|
|
34
44
|
from bumble.colors import color
|
|
@@ -68,6 +78,7 @@ from bumble.gatt import (
|
|
|
68
78
|
GATT_REQUEST_TIMEOUT,
|
|
69
79
|
GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
|
|
70
80
|
Characteristic,
|
|
81
|
+
CharacteristicAdapter,
|
|
71
82
|
CharacteristicDeclaration,
|
|
72
83
|
CharacteristicValue,
|
|
73
84
|
IncludedServiceDeclaration,
|
bumble/hci.py
CHANGED
|
@@ -5068,6 +5068,7 @@ class HCI_Event(HCI_Packet):
|
|
|
5068
5068
|
hci_packet_type = HCI_EVENT_PACKET
|
|
5069
5069
|
event_names: Dict[int, str] = {}
|
|
5070
5070
|
event_classes: Dict[int, Type[HCI_Event]] = {}
|
|
5071
|
+
vendor_factory: Optional[Callable[[bytes], Optional[HCI_Event]]] = None
|
|
5071
5072
|
|
|
5072
5073
|
@staticmethod
|
|
5073
5074
|
def event(fields=()):
|
|
@@ -5125,37 +5126,41 @@ class HCI_Event(HCI_Packet):
|
|
|
5125
5126
|
|
|
5126
5127
|
return event_class
|
|
5127
5128
|
|
|
5128
|
-
@
|
|
5129
|
-
def from_bytes(packet: bytes) -> HCI_Event:
|
|
5129
|
+
@classmethod
|
|
5130
|
+
def from_bytes(cls, packet: bytes) -> HCI_Event:
|
|
5130
5131
|
event_code = packet[1]
|
|
5131
5132
|
length = packet[2]
|
|
5132
5133
|
parameters = packet[3:]
|
|
5133
5134
|
if len(parameters) != length:
|
|
5134
5135
|
raise InvalidPacketError('invalid packet length')
|
|
5135
5136
|
|
|
5136
|
-
|
|
5137
|
+
subclass: Any
|
|
5137
5138
|
if event_code == HCI_LE_META_EVENT:
|
|
5138
5139
|
# We do this dispatch here and not in the subclass in order to avoid call
|
|
5139
5140
|
# loops
|
|
5140
5141
|
subevent_code = parameters[0]
|
|
5141
|
-
|
|
5142
|
-
if
|
|
5142
|
+
subclass = HCI_LE_Meta_Event.subevent_classes.get(subevent_code)
|
|
5143
|
+
if subclass is None:
|
|
5143
5144
|
# No class registered, just use a generic class instance
|
|
5144
5145
|
return HCI_LE_Meta_Event(subevent_code, parameters)
|
|
5145
5146
|
elif event_code == HCI_VENDOR_EVENT:
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
if cls
|
|
5149
|
-
|
|
5150
|
-
|
|
5147
|
+
# Invoke all the registered factories to see if any of them can handle
|
|
5148
|
+
# the event
|
|
5149
|
+
if cls.vendor_factory:
|
|
5150
|
+
if event := cls.vendor_factory(parameters):
|
|
5151
|
+
return event
|
|
5152
|
+
|
|
5153
|
+
# No factory, or the factory could not create an instance,
|
|
5154
|
+
# return a generic vendor event
|
|
5155
|
+
return HCI_Event(event_code, parameters)
|
|
5151
5156
|
else:
|
|
5152
|
-
|
|
5153
|
-
if
|
|
5157
|
+
subclass = HCI_Event.event_classes.get(event_code)
|
|
5158
|
+
if subclass is None:
|
|
5154
5159
|
# No class registered, just use a generic class instance
|
|
5155
5160
|
return HCI_Event(event_code, parameters)
|
|
5156
5161
|
|
|
5157
5162
|
# Invoke the factory to create a new instance
|
|
5158
|
-
return
|
|
5163
|
+
return subclass.from_parameters(parameters) # type: ignore
|
|
5159
5164
|
|
|
5160
5165
|
@classmethod
|
|
5161
5166
|
def from_parameters(cls, parameters):
|
|
@@ -5198,11 +5203,11 @@ HCI_Event.register_events(globals())
|
|
|
5198
5203
|
# -----------------------------------------------------------------------------
|
|
5199
5204
|
class HCI_Extended_Event(HCI_Event):
|
|
5200
5205
|
'''
|
|
5201
|
-
HCI_Event subclass for events that
|
|
5206
|
+
HCI_Event subclass for events that have a subevent code.
|
|
5202
5207
|
'''
|
|
5203
5208
|
|
|
5204
5209
|
subevent_names: Dict[int, str] = {}
|
|
5205
|
-
subevent_classes: Dict[int, Type[HCI_Extended_Event]]
|
|
5210
|
+
subevent_classes: Dict[int, Type[HCI_Extended_Event]] = {}
|
|
5206
5211
|
|
|
5207
5212
|
@classmethod
|
|
5208
5213
|
def event(cls, fields=()):
|
|
@@ -5253,7 +5258,22 @@ class HCI_Extended_Event(HCI_Event):
|
|
|
5253
5258
|
cls.subevent_names.update(cls.subevent_map(symbols))
|
|
5254
5259
|
|
|
5255
5260
|
@classmethod
|
|
5256
|
-
def
|
|
5261
|
+
def subclass_from_parameters(
|
|
5262
|
+
cls, parameters: bytes
|
|
5263
|
+
) -> Optional[HCI_Extended_Event]:
|
|
5264
|
+
"""
|
|
5265
|
+
Factory method that parses the subevent code, finds a registered subclass,
|
|
5266
|
+
and creates an instance if found.
|
|
5267
|
+
"""
|
|
5268
|
+
subevent_code = parameters[0]
|
|
5269
|
+
if subclass := cls.subevent_classes.get(subevent_code):
|
|
5270
|
+
return subclass.from_parameters(parameters)
|
|
5271
|
+
|
|
5272
|
+
return None
|
|
5273
|
+
|
|
5274
|
+
@classmethod
|
|
5275
|
+
def from_parameters(cls, parameters: bytes) -> HCI_Extended_Event:
|
|
5276
|
+
"""Factory method for subclasses (the subevent code has already been parsed)"""
|
|
5257
5277
|
self = cls.__new__(cls)
|
|
5258
5278
|
HCI_Extended_Event.__init__(self, self.subevent_code, parameters)
|
|
5259
5279
|
if fields := getattr(self, 'fields', None):
|
|
@@ -5294,12 +5314,6 @@ class HCI_LE_Meta_Event(HCI_Extended_Event):
|
|
|
5294
5314
|
HCI_LE_Meta_Event.register_subevents(globals())
|
|
5295
5315
|
|
|
5296
5316
|
|
|
5297
|
-
# -----------------------------------------------------------------------------
|
|
5298
|
-
class HCI_Vendor_Event(HCI_Extended_Event):
|
|
5299
|
-
event_code: int = HCI_VENDOR_EVENT
|
|
5300
|
-
subevent_classes = {}
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
5317
|
# -----------------------------------------------------------------------------
|
|
5304
5318
|
@HCI_LE_Meta_Event.event(
|
|
5305
5319
|
[
|
|
@@ -6173,8 +6187,9 @@ class HCI_Command_Complete_Event(HCI_Event):
|
|
|
6173
6187
|
See Bluetooth spec @ 7.7.14 Command Complete Event
|
|
6174
6188
|
'''
|
|
6175
6189
|
|
|
6176
|
-
|
|
6190
|
+
num_hci_command_packets: int
|
|
6177
6191
|
command_opcode: int
|
|
6192
|
+
return_parameters = b''
|
|
6178
6193
|
|
|
6179
6194
|
def map_return_parameters(self, return_parameters):
|
|
6180
6195
|
'''Map simple 'status' return parameters to their named constant form'''
|
|
@@ -6710,6 +6725,14 @@ class HCI_Remote_Host_Supported_Features_Notification_Event(HCI_Event):
|
|
|
6710
6725
|
'''
|
|
6711
6726
|
|
|
6712
6727
|
|
|
6728
|
+
# -----------------------------------------------------------------------------
|
|
6729
|
+
@HCI_Event.event([('data', "*")])
|
|
6730
|
+
class HCI_Vendor_Event(HCI_Event):
|
|
6731
|
+
'''
|
|
6732
|
+
See Bluetooth spec @ 5.4.4 HCI Event packet
|
|
6733
|
+
'''
|
|
6734
|
+
|
|
6735
|
+
|
|
6713
6736
|
# -----------------------------------------------------------------------------
|
|
6714
6737
|
class HCI_AclDataPacket(HCI_Packet):
|
|
6715
6738
|
'''
|
bumble/host.py
CHANGED
|
@@ -552,7 +552,7 @@ class Host(AbortableEventEmitter):
|
|
|
552
552
|
|
|
553
553
|
return response
|
|
554
554
|
except Exception as error:
|
|
555
|
-
logger.
|
|
555
|
+
logger.exception(
|
|
556
556
|
f'{color("!!! Exception while sending command:", "red")} {error}'
|
|
557
557
|
)
|
|
558
558
|
raise error
|
|
@@ -1248,3 +1248,6 @@ class Host(AbortableEventEmitter):
|
|
|
1248
1248
|
event.connection_handle,
|
|
1249
1249
|
int.from_bytes(event.le_features, 'little'),
|
|
1250
1250
|
)
|
|
1251
|
+
|
|
1252
|
+
def on_hci_vendor_event(self, event):
|
|
1253
|
+
self.emit('vendor_event', event)
|
bumble/pairing.py
CHANGED
|
@@ -139,16 +139,19 @@ class PairingDelegate:
|
|
|
139
139
|
io_capability: IoCapability
|
|
140
140
|
local_initiator_key_distribution: KeyDistribution
|
|
141
141
|
local_responder_key_distribution: KeyDistribution
|
|
142
|
+
maximum_encryption_key_size: int
|
|
142
143
|
|
|
143
144
|
def __init__(
|
|
144
145
|
self,
|
|
145
146
|
io_capability: IoCapability = NO_OUTPUT_NO_INPUT,
|
|
146
147
|
local_initiator_key_distribution: KeyDistribution = DEFAULT_KEY_DISTRIBUTION,
|
|
147
148
|
local_responder_key_distribution: KeyDistribution = DEFAULT_KEY_DISTRIBUTION,
|
|
149
|
+
maximum_encryption_key_size: int = 16,
|
|
148
150
|
) -> None:
|
|
149
151
|
self.io_capability = io_capability
|
|
150
152
|
self.local_initiator_key_distribution = local_initiator_key_distribution
|
|
151
153
|
self.local_responder_key_distribution = local_responder_key_distribution
|
|
154
|
+
self.maximum_encryption_key_size = maximum_encryption_key_size
|
|
152
155
|
|
|
153
156
|
@property
|
|
154
157
|
def classic_io_capability(self) -> int:
|
bumble/profiles/aics.py
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
# Imports
|
|
19
19
|
# -----------------------------------------------------------------------------
|
|
20
|
+
from __future__ import annotations
|
|
20
21
|
import logging
|
|
21
22
|
import struct
|
|
22
23
|
|
|
@@ -28,10 +29,11 @@ from bumble.device import Connection
|
|
|
28
29
|
from bumble.att import ATT_Error
|
|
29
30
|
from bumble.gatt import (
|
|
30
31
|
Characteristic,
|
|
31
|
-
|
|
32
|
+
SerializableCharacteristicAdapter,
|
|
33
|
+
PackedCharacteristicAdapter,
|
|
32
34
|
TemplateService,
|
|
33
35
|
CharacteristicValue,
|
|
34
|
-
|
|
36
|
+
UTF8CharacteristicAdapter,
|
|
35
37
|
GATT_AUDIO_INPUT_CONTROL_SERVICE,
|
|
36
38
|
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC,
|
|
37
39
|
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC,
|
|
@@ -154,9 +156,6 @@ class AudioInputState:
|
|
|
154
156
|
attribute=self.attribute_value, value=bytes(self)
|
|
155
157
|
)
|
|
156
158
|
|
|
157
|
-
def on_read(self, _connection: Optional[Connection]) -> bytes:
|
|
158
|
-
return bytes(self)
|
|
159
|
-
|
|
160
159
|
|
|
161
160
|
@dataclass
|
|
162
161
|
class GainSettingsProperties:
|
|
@@ -173,7 +172,7 @@ class GainSettingsProperties:
|
|
|
173
172
|
(gain_settings_unit, gain_settings_minimum, gain_settings_maximum) = (
|
|
174
173
|
struct.unpack('BBB', data)
|
|
175
174
|
)
|
|
176
|
-
GainSettingsProperties(
|
|
175
|
+
return GainSettingsProperties(
|
|
177
176
|
gain_settings_unit, gain_settings_minimum, gain_settings_maximum
|
|
178
177
|
)
|
|
179
178
|
|
|
@@ -186,9 +185,6 @@ class GainSettingsProperties:
|
|
|
186
185
|
]
|
|
187
186
|
)
|
|
188
187
|
|
|
189
|
-
def on_read(self, _connection: Optional[Connection]) -> bytes:
|
|
190
|
-
return bytes(self)
|
|
191
|
-
|
|
192
188
|
|
|
193
189
|
@dataclass
|
|
194
190
|
class AudioInputControlPoint:
|
|
@@ -321,21 +317,14 @@ class AudioInputDescription:
|
|
|
321
317
|
audio_input_description: str = "Bluetooth"
|
|
322
318
|
attribute_value: Optional[CharacteristicValue] = None
|
|
323
319
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return cls(audio_input_description=data.decode('utf-8'))
|
|
327
|
-
|
|
328
|
-
def __bytes__(self) -> bytes:
|
|
329
|
-
return self.audio_input_description.encode('utf-8')
|
|
330
|
-
|
|
331
|
-
def on_read(self, _connection: Optional[Connection]) -> bytes:
|
|
332
|
-
return self.audio_input_description.encode('utf-8')
|
|
320
|
+
def on_read(self, _connection: Optional[Connection]) -> str:
|
|
321
|
+
return self.audio_input_description
|
|
333
322
|
|
|
334
|
-
async def on_write(self, connection: Optional[Connection], value:
|
|
323
|
+
async def on_write(self, connection: Optional[Connection], value: str) -> None:
|
|
335
324
|
assert connection
|
|
336
325
|
assert self.attribute_value
|
|
337
326
|
|
|
338
|
-
self.audio_input_description = value
|
|
327
|
+
self.audio_input_description = value
|
|
339
328
|
await connection.device.notify_subscribers(
|
|
340
329
|
attribute=self.attribute_value, value=value
|
|
341
330
|
)
|
|
@@ -375,26 +364,29 @@ class AICSService(TemplateService):
|
|
|
375
364
|
self.audio_input_state, self.gain_settings_properties
|
|
376
365
|
)
|
|
377
366
|
|
|
378
|
-
self.audio_input_state_characteristic =
|
|
367
|
+
self.audio_input_state_characteristic = SerializableCharacteristicAdapter(
|
|
379
368
|
Characteristic(
|
|
380
369
|
uuid=GATT_AUDIO_INPUT_STATE_CHARACTERISTIC,
|
|
381
370
|
properties=Characteristic.Properties.READ
|
|
382
371
|
| Characteristic.Properties.NOTIFY,
|
|
383
372
|
permissions=Characteristic.Permissions.READ_REQUIRES_ENCRYPTION,
|
|
384
|
-
value=
|
|
373
|
+
value=self.audio_input_state,
|
|
385
374
|
),
|
|
386
|
-
|
|
375
|
+
AudioInputState,
|
|
387
376
|
)
|
|
388
377
|
self.audio_input_state.attribute_value = (
|
|
389
378
|
self.audio_input_state_characteristic.value
|
|
390
379
|
)
|
|
391
380
|
|
|
392
|
-
self.gain_settings_properties_characteristic =
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
381
|
+
self.gain_settings_properties_characteristic = (
|
|
382
|
+
SerializableCharacteristicAdapter(
|
|
383
|
+
Characteristic(
|
|
384
|
+
uuid=GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC,
|
|
385
|
+
properties=Characteristic.Properties.READ,
|
|
386
|
+
permissions=Characteristic.Permissions.READ_REQUIRES_ENCRYPTION,
|
|
387
|
+
value=self.gain_settings_properties,
|
|
388
|
+
),
|
|
389
|
+
GainSettingsProperties,
|
|
398
390
|
)
|
|
399
391
|
)
|
|
400
392
|
|
|
@@ -402,7 +394,7 @@ class AICSService(TemplateService):
|
|
|
402
394
|
uuid=GATT_AUDIO_INPUT_TYPE_CHARACTERISTIC,
|
|
403
395
|
properties=Characteristic.Properties.READ,
|
|
404
396
|
permissions=Characteristic.Permissions.READ_REQUIRES_ENCRYPTION,
|
|
405
|
-
value=audio_input_type,
|
|
397
|
+
value=bytes(audio_input_type, 'utf-8'),
|
|
406
398
|
)
|
|
407
399
|
|
|
408
400
|
self.audio_input_status_characteristic = Characteristic(
|
|
@@ -412,18 +404,14 @@ class AICSService(TemplateService):
|
|
|
412
404
|
value=bytes([self.audio_input_status]),
|
|
413
405
|
)
|
|
414
406
|
|
|
415
|
-
self.audio_input_control_point_characteristic =
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
value=CharacteristicValue(
|
|
421
|
-
write=self.audio_input_control_point.on_write
|
|
422
|
-
),
|
|
423
|
-
)
|
|
407
|
+
self.audio_input_control_point_characteristic = Characteristic(
|
|
408
|
+
uuid=GATT_AUDIO_INPUT_CONTROL_POINT_CHARACTERISTIC,
|
|
409
|
+
properties=Characteristic.Properties.WRITE,
|
|
410
|
+
permissions=Characteristic.Permissions.WRITE_REQUIRES_ENCRYPTION,
|
|
411
|
+
value=CharacteristicValue(write=self.audio_input_control_point.on_write),
|
|
424
412
|
)
|
|
425
413
|
|
|
426
|
-
self.audio_input_description_characteristic =
|
|
414
|
+
self.audio_input_description_characteristic = UTF8CharacteristicAdapter(
|
|
427
415
|
Characteristic(
|
|
428
416
|
uuid=GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC,
|
|
429
417
|
properties=Characteristic.Properties.READ
|
|
@@ -469,8 +457,8 @@ class AICSServiceProxy(ProfileServiceProxy):
|
|
|
469
457
|
)
|
|
470
458
|
):
|
|
471
459
|
raise gatt.InvalidServiceError("Audio Input State Characteristic not found")
|
|
472
|
-
self.audio_input_state =
|
|
473
|
-
|
|
460
|
+
self.audio_input_state = SerializableCharacteristicAdapter(
|
|
461
|
+
characteristics[0], AudioInputState
|
|
474
462
|
)
|
|
475
463
|
|
|
476
464
|
if not (
|
|
@@ -481,9 +469,8 @@ class AICSServiceProxy(ProfileServiceProxy):
|
|
|
481
469
|
raise gatt.InvalidServiceError(
|
|
482
470
|
"Gain Settings Attribute Characteristic not found"
|
|
483
471
|
)
|
|
484
|
-
self.gain_settings_properties =
|
|
485
|
-
characteristics[0],
|
|
486
|
-
'BBB',
|
|
472
|
+
self.gain_settings_properties = SerializableCharacteristicAdapter(
|
|
473
|
+
characteristics[0], GainSettingsProperties
|
|
487
474
|
)
|
|
488
475
|
|
|
489
476
|
if not (
|
|
@@ -494,10 +481,7 @@ class AICSServiceProxy(ProfileServiceProxy):
|
|
|
494
481
|
raise gatt.InvalidServiceError(
|
|
495
482
|
"Audio Input Status Characteristic not found"
|
|
496
483
|
)
|
|
497
|
-
self.audio_input_status = PackedCharacteristicAdapter(
|
|
498
|
-
characteristics[0],
|
|
499
|
-
'B',
|
|
500
|
-
)
|
|
484
|
+
self.audio_input_status = PackedCharacteristicAdapter(characteristics[0], 'B')
|
|
501
485
|
|
|
502
486
|
if not (
|
|
503
487
|
characteristics := service_proxy.get_characteristics_by_uuid(
|
|
@@ -517,4 +501,4 @@ class AICSServiceProxy(ProfileServiceProxy):
|
|
|
517
501
|
raise gatt.InvalidServiceError(
|
|
518
502
|
"Audio Input Description Characteristic not found"
|
|
519
503
|
)
|
|
520
|
-
self.audio_input_description = characteristics[0]
|
|
504
|
+
self.audio_input_description = UTF8CharacteristicAdapter(characteristics[0])
|
bumble/profiles/bass.py
CHANGED
|
@@ -276,10 +276,7 @@ class BroadcastReceiveState:
|
|
|
276
276
|
subgroups: List[SubgroupInfo]
|
|
277
277
|
|
|
278
278
|
@classmethod
|
|
279
|
-
def from_bytes(cls, data: bytes) ->
|
|
280
|
-
if not data:
|
|
281
|
-
return None
|
|
282
|
-
|
|
279
|
+
def from_bytes(cls, data: bytes) -> BroadcastReceiveState:
|
|
283
280
|
source_id = data[0]
|
|
284
281
|
_, source_address = hci.Address.parse_address_preceded_by_type(data, 2)
|
|
285
282
|
source_adv_sid = data[8]
|
|
@@ -357,7 +354,7 @@ class BroadcastAudioScanServiceProxy(gatt_client.ProfileServiceProxy):
|
|
|
357
354
|
SERVICE_CLASS = BroadcastAudioScanService
|
|
358
355
|
|
|
359
356
|
broadcast_audio_scan_control_point: gatt_client.CharacteristicProxy
|
|
360
|
-
broadcast_receive_states: List[gatt.
|
|
357
|
+
broadcast_receive_states: List[gatt.SerializableCharacteristicAdapter]
|
|
361
358
|
|
|
362
359
|
def __init__(self, service_proxy: gatt_client.ServiceProxy):
|
|
363
360
|
self.service_proxy = service_proxy
|
|
@@ -381,8 +378,8 @@ class BroadcastAudioScanServiceProxy(gatt_client.ProfileServiceProxy):
|
|
|
381
378
|
"Broadcast Receive State characteristic not found"
|
|
382
379
|
)
|
|
383
380
|
self.broadcast_receive_states = [
|
|
384
|
-
gatt.
|
|
385
|
-
characteristic,
|
|
381
|
+
gatt.SerializableCharacteristicAdapter(
|
|
382
|
+
characteristic, BroadcastReceiveState
|
|
386
383
|
)
|
|
387
384
|
for characteristic in characteristics
|
|
388
385
|
]
|
|
@@ -64,7 +64,10 @@ class DeviceInformationService(TemplateService):
|
|
|
64
64
|
):
|
|
65
65
|
characteristics = [
|
|
66
66
|
Characteristic(
|
|
67
|
-
uuid,
|
|
67
|
+
uuid,
|
|
68
|
+
Characteristic.Properties.READ,
|
|
69
|
+
Characteristic.READABLE,
|
|
70
|
+
bytes(field, 'utf-8'),
|
|
68
71
|
)
|
|
69
72
|
for (field, uuid) in (
|
|
70
73
|
(manufacturer_name, GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC),
|
|
@@ -30,6 +30,7 @@ from ..gatt import (
|
|
|
30
30
|
TemplateService,
|
|
31
31
|
Characteristic,
|
|
32
32
|
CharacteristicValue,
|
|
33
|
+
SerializableCharacteristicAdapter,
|
|
33
34
|
DelegatedCharacteristicAdapter,
|
|
34
35
|
PackedCharacteristicAdapter,
|
|
35
36
|
)
|
|
@@ -150,15 +151,14 @@ class HeartRateService(TemplateService):
|
|
|
150
151
|
body_sensor_location=None,
|
|
151
152
|
reset_energy_expended=None,
|
|
152
153
|
):
|
|
153
|
-
self.heart_rate_measurement_characteristic =
|
|
154
|
+
self.heart_rate_measurement_characteristic = SerializableCharacteristicAdapter(
|
|
154
155
|
Characteristic(
|
|
155
156
|
GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
|
|
156
157
|
Characteristic.Properties.NOTIFY,
|
|
157
158
|
0,
|
|
158
159
|
CharacteristicValue(read=read_heart_rate_measurement),
|
|
159
160
|
),
|
|
160
|
-
|
|
161
|
-
encode=lambda value: bytes(value),
|
|
161
|
+
HeartRateService.HeartRateMeasurement,
|
|
162
162
|
)
|
|
163
163
|
characteristics = [self.heart_rate_measurement_characteristic]
|
|
164
164
|
|
|
@@ -204,9 +204,8 @@ class HeartRateServiceProxy(ProfileServiceProxy):
|
|
|
204
204
|
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
205
205
|
GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC
|
|
206
206
|
):
|
|
207
|
-
self.heart_rate_measurement =
|
|
208
|
-
characteristics[0],
|
|
209
|
-
decode=HeartRateService.HeartRateMeasurement.from_bytes,
|
|
207
|
+
self.heart_rate_measurement = SerializableCharacteristicAdapter(
|
|
208
|
+
characteristics[0], HeartRateService.HeartRateMeasurement
|
|
210
209
|
)
|
|
211
210
|
else:
|
|
212
211
|
self.heart_rate_measurement = None
|