bumble 0.0.202__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/auracast.py +22 -13
- bumble/apps/bench.py +2 -1
- bumble/apps/hci_bridge.py +1 -1
- bumble/apps/lea_unicast/app.py +24 -6
- bumble/apps/pair.py +13 -8
- bumble/apps/show.py +6 -6
- bumble/att.py +11 -15
- bumble/controller.py +58 -2
- bumble/device.py +454 -494
- bumble/drivers/common.py +2 -0
- bumble/drivers/intel.py +593 -24
- bumble/gatt.py +33 -7
- bumble/gatt_client.py +3 -3
- bumble/gatt_server.py +14 -3
- bumble/hci.py +139 -56
- bumble/hfp.py +20 -17
- bumble/host.py +5 -2
- bumble/l2cap.py +2 -8
- bumble/pairing.py +3 -0
- bumble/pandora/host.py +1 -1
- bumble/profiles/aics.py +37 -53
- bumble/profiles/bap.py +114 -42
- 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/sdp.py +1 -7
- bumble/smp.py +9 -7
- 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.202.dist-info → bumble-0.0.204.dist-info}/METADATA +15 -15
- {bumble-0.0.202.dist-info → bumble-0.0.204.dist-info}/RECORD +40 -37
- {bumble-0.0.202.dist-info → bumble-0.0.204.dist-info}/WHEEL +1 -1
- {bumble-0.0.202.dist-info → bumble-0.0.204.dist-info}/entry_points.txt +2 -0
- {bumble-0.0.202.dist-info → bumble-0.0.204.dist-info}/LICENSE +0 -0
- {bumble-0.0.202.dist-info → bumble-0.0.204.dist-info}/top_level.txt +0 -0
bumble/hfp.py
CHANGED
|
@@ -141,7 +141,7 @@ class HfFeature(enum.IntFlag):
|
|
|
141
141
|
"""
|
|
142
142
|
HF supported features (AT+BRSF=) (normative).
|
|
143
143
|
|
|
144
|
-
Hands-Free Profile v1.
|
|
144
|
+
Hands-Free Profile v1.9, 4.34.2, AT Capabilities Re-Used from GSM 07.07 and 3GPP 27.007.
|
|
145
145
|
"""
|
|
146
146
|
|
|
147
147
|
EC_NR = 0x001 # Echo Cancel & Noise reduction
|
|
@@ -155,14 +155,14 @@ class HfFeature(enum.IntFlag):
|
|
|
155
155
|
HF_INDICATORS = 0x100
|
|
156
156
|
ESCO_S4_SETTINGS_SUPPORTED = 0x200
|
|
157
157
|
ENHANCED_VOICE_RECOGNITION_STATUS = 0x400
|
|
158
|
-
|
|
158
|
+
VOICE_RECOGNITION_TEXT = 0x800
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
class AgFeature(enum.IntFlag):
|
|
162
162
|
"""
|
|
163
163
|
AG supported features (+BRSF:) (normative).
|
|
164
164
|
|
|
165
|
-
Hands-Free Profile v1.
|
|
165
|
+
Hands-Free Profile v1.9, 4.34.2, AT Capabilities Re-Used from GSM 07.07 and 3GPP 27.007.
|
|
166
166
|
"""
|
|
167
167
|
|
|
168
168
|
THREE_WAY_CALLING = 0x001
|
|
@@ -178,7 +178,7 @@ class AgFeature(enum.IntFlag):
|
|
|
178
178
|
HF_INDICATORS = 0x400
|
|
179
179
|
ESCO_S4_SETTINGS_SUPPORTED = 0x800
|
|
180
180
|
ENHANCED_VOICE_RECOGNITION_STATUS = 0x1000
|
|
181
|
-
|
|
181
|
+
VOICE_RECOGNITION_TEXT = 0x2000
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
class AudioCodec(enum.IntEnum):
|
|
@@ -1390,6 +1390,7 @@ class AgProtocol(pyee.EventEmitter):
|
|
|
1390
1390
|
|
|
1391
1391
|
def _on_bac(self, *args) -> None:
|
|
1392
1392
|
self.supported_audio_codecs = [AudioCodec(int(value)) for value in args]
|
|
1393
|
+
self.emit('supported_audio_codecs', self.supported_audio_codecs)
|
|
1393
1394
|
self.send_ok()
|
|
1394
1395
|
|
|
1395
1396
|
def _on_bcs(self, codec: bytes) -> None:
|
|
@@ -1618,7 +1619,7 @@ class ProfileVersion(enum.IntEnum):
|
|
|
1618
1619
|
"""
|
|
1619
1620
|
Profile version (normative).
|
|
1620
1621
|
|
|
1621
|
-
Hands-Free Profile v1.8,
|
|
1622
|
+
Hands-Free Profile v1.8, 6.3 SDP Interoperability Requirements.
|
|
1622
1623
|
"""
|
|
1623
1624
|
|
|
1624
1625
|
V1_5 = 0x0105
|
|
@@ -1632,7 +1633,7 @@ class HfSdpFeature(enum.IntFlag):
|
|
|
1632
1633
|
"""
|
|
1633
1634
|
HF supported features (normative).
|
|
1634
1635
|
|
|
1635
|
-
Hands-Free Profile v1.
|
|
1636
|
+
Hands-Free Profile v1.9, 6.3 SDP Interoperability Requirements.
|
|
1636
1637
|
"""
|
|
1637
1638
|
|
|
1638
1639
|
EC_NR = 0x01 # Echo Cancel & Noise reduction
|
|
@@ -1640,16 +1641,17 @@ class HfSdpFeature(enum.IntFlag):
|
|
|
1640
1641
|
CLI_PRESENTATION_CAPABILITY = 0x04
|
|
1641
1642
|
VOICE_RECOGNITION_ACTIVATION = 0x08
|
|
1642
1643
|
REMOTE_VOLUME_CONTROL = 0x10
|
|
1643
|
-
|
|
1644
|
+
WIDE_BAND_SPEECH = 0x20
|
|
1644
1645
|
ENHANCED_VOICE_RECOGNITION_STATUS = 0x40
|
|
1645
|
-
|
|
1646
|
+
VOICE_RECOGNITION_TEXT = 0x80
|
|
1647
|
+
SUPER_WIDE_BAND = 0x100
|
|
1646
1648
|
|
|
1647
1649
|
|
|
1648
1650
|
class AgSdpFeature(enum.IntFlag):
|
|
1649
1651
|
"""
|
|
1650
1652
|
AG supported features (normative).
|
|
1651
1653
|
|
|
1652
|
-
Hands-Free Profile v1.
|
|
1654
|
+
Hands-Free Profile v1.9, 6.3 SDP Interoperability Requirements.
|
|
1653
1655
|
"""
|
|
1654
1656
|
|
|
1655
1657
|
THREE_WAY_CALLING = 0x01
|
|
@@ -1657,9 +1659,10 @@ class AgSdpFeature(enum.IntFlag):
|
|
|
1657
1659
|
VOICE_RECOGNITION_FUNCTION = 0x04
|
|
1658
1660
|
IN_BAND_RING_TONE_CAPABILITY = 0x08
|
|
1659
1661
|
VOICE_TAG = 0x10 # Attach a number to voice tag
|
|
1660
|
-
|
|
1662
|
+
WIDE_BAND_SPEECH = 0x20
|
|
1661
1663
|
ENHANCED_VOICE_RECOGNITION_STATUS = 0x40
|
|
1662
|
-
|
|
1664
|
+
VOICE_RECOGNITION_TEXT = 0x80
|
|
1665
|
+
SUPER_WIDE_BAND_SPEED_SPEECH = 0x100
|
|
1663
1666
|
|
|
1664
1667
|
|
|
1665
1668
|
def make_hf_sdp_records(
|
|
@@ -1692,11 +1695,11 @@ def make_hf_sdp_records(
|
|
|
1692
1695
|
in configuration.supported_hf_features
|
|
1693
1696
|
):
|
|
1694
1697
|
hf_supported_features |= HfSdpFeature.ENHANCED_VOICE_RECOGNITION_STATUS
|
|
1695
|
-
if HfFeature.
|
|
1696
|
-
hf_supported_features |= HfSdpFeature.
|
|
1698
|
+
if HfFeature.VOICE_RECOGNITION_TEXT in configuration.supported_hf_features:
|
|
1699
|
+
hf_supported_features |= HfSdpFeature.VOICE_RECOGNITION_TEXT
|
|
1697
1700
|
|
|
1698
1701
|
if AudioCodec.MSBC in configuration.supported_audio_codecs:
|
|
1699
|
-
hf_supported_features |= HfSdpFeature.
|
|
1702
|
+
hf_supported_features |= HfSdpFeature.WIDE_BAND_SPEECH
|
|
1700
1703
|
|
|
1701
1704
|
return [
|
|
1702
1705
|
sdp.ServiceAttribute(
|
|
@@ -1772,14 +1775,14 @@ def make_ag_sdp_records(
|
|
|
1772
1775
|
in configuration.supported_ag_features
|
|
1773
1776
|
):
|
|
1774
1777
|
ag_supported_features |= AgSdpFeature.ENHANCED_VOICE_RECOGNITION_STATUS
|
|
1775
|
-
if AgFeature.
|
|
1776
|
-
ag_supported_features |= AgSdpFeature.
|
|
1778
|
+
if AgFeature.VOICE_RECOGNITION_TEXT in configuration.supported_ag_features:
|
|
1779
|
+
ag_supported_features |= AgSdpFeature.VOICE_RECOGNITION_TEXT
|
|
1777
1780
|
if AgFeature.IN_BAND_RING_TONE_CAPABILITY in configuration.supported_ag_features:
|
|
1778
1781
|
ag_supported_features |= AgSdpFeature.IN_BAND_RING_TONE_CAPABILITY
|
|
1779
1782
|
if AgFeature.VOICE_RECOGNITION_FUNCTION in configuration.supported_ag_features:
|
|
1780
1783
|
ag_supported_features |= AgSdpFeature.VOICE_RECOGNITION_FUNCTION
|
|
1781
1784
|
if AudioCodec.MSBC in configuration.supported_audio_codecs:
|
|
1782
|
-
ag_supported_features |= AgSdpFeature.
|
|
1785
|
+
ag_supported_features |= AgSdpFeature.WIDE_BAND_SPEECH
|
|
1783
1786
|
|
|
1784
1787
|
return [
|
|
1785
1788
|
sdp.ServiceAttribute(
|
bumble/host.py
CHANGED
|
@@ -199,7 +199,7 @@ class Host(AbortableEventEmitter):
|
|
|
199
199
|
check_address_type: bool = False,
|
|
200
200
|
) -> Optional[Connection]:
|
|
201
201
|
for connection in self.connections.values():
|
|
202
|
-
if connection.peer_address
|
|
202
|
+
if bytes(connection.peer_address) == bytes(bd_addr):
|
|
203
203
|
if (
|
|
204
204
|
check_address_type
|
|
205
205
|
and connection.peer_address.address_type != bd_addr.address_type
|
|
@@ -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/l2cap.py
CHANGED
|
@@ -225,7 +225,7 @@ class L2CAP_PDU:
|
|
|
225
225
|
|
|
226
226
|
return L2CAP_PDU(l2cap_pdu_cid, l2cap_pdu_payload)
|
|
227
227
|
|
|
228
|
-
def
|
|
228
|
+
def __bytes__(self) -> bytes:
|
|
229
229
|
header = struct.pack('<HH', len(self.payload), self.cid)
|
|
230
230
|
return header + self.payload
|
|
231
231
|
|
|
@@ -233,9 +233,6 @@ class L2CAP_PDU:
|
|
|
233
233
|
self.cid = cid
|
|
234
234
|
self.payload = payload
|
|
235
235
|
|
|
236
|
-
def __bytes__(self) -> bytes:
|
|
237
|
-
return self.to_bytes()
|
|
238
|
-
|
|
239
236
|
def __str__(self) -> str:
|
|
240
237
|
return f'{color("L2CAP", "green")} [CID={self.cid}]: {self.payload.hex()}'
|
|
241
238
|
|
|
@@ -333,11 +330,8 @@ class L2CAP_Control_Frame:
|
|
|
333
330
|
def init_from_bytes(self, pdu, offset):
|
|
334
331
|
return HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
|
|
335
332
|
|
|
336
|
-
def to_bytes(self) -> bytes:
|
|
337
|
-
return self.pdu
|
|
338
|
-
|
|
339
333
|
def __bytes__(self) -> bytes:
|
|
340
|
-
return self.
|
|
334
|
+
return self.pdu
|
|
341
335
|
|
|
342
336
|
def __str__(self) -> str:
|
|
343
337
|
result = f'{color(self.name, "yellow")} [ID={self.identifier}]'
|
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/pandora/host.py
CHANGED
|
@@ -39,7 +39,6 @@ from bumble.device import (
|
|
|
39
39
|
AdvertisingEventProperties,
|
|
40
40
|
AdvertisingType,
|
|
41
41
|
Device,
|
|
42
|
-
Phy,
|
|
43
42
|
)
|
|
44
43
|
from bumble.gatt import Service
|
|
45
44
|
from bumble.hci import (
|
|
@@ -47,6 +46,7 @@ from bumble.hci import (
|
|
|
47
46
|
HCI_PAGE_TIMEOUT_ERROR,
|
|
48
47
|
HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
49
48
|
Address,
|
|
49
|
+
Phy,
|
|
50
50
|
)
|
|
51
51
|
from google.protobuf import any_pb2 # pytype: disable=pyi-error
|
|
52
52
|
from google.protobuf import empty_pb2 # pytype: disable=pyi-error
|
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,
|
|
@@ -95,7 +97,7 @@ class AudioInputStatus(OpenIntEnum):
|
|
|
95
97
|
Cf. 3.4 Audio Input Status
|
|
96
98
|
'''
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
INACTIVE = 0x00
|
|
99
101
|
ACTIVE = 0x01
|
|
100
102
|
|
|
101
103
|
|
|
@@ -104,7 +106,7 @@ class AudioInputControlPointOpCode(OpenIntEnum):
|
|
|
104
106
|
Cf. 3.5.1 Audio Input Control Point procedure requirements
|
|
105
107
|
'''
|
|
106
108
|
|
|
107
|
-
SET_GAIN_SETTING =
|
|
109
|
+
SET_GAIN_SETTING = 0x01
|
|
108
110
|
UNMUTE = 0x02
|
|
109
111
|
MUTE = 0x03
|
|
110
112
|
SET_MANUAL_GAIN_MODE = 0x04
|
|
@@ -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:
|
|
@@ -239,7 +235,7 @@ class AudioInputControlPoint:
|
|
|
239
235
|
or gain_settings_operand
|
|
240
236
|
> self.gain_settings_properties.gain_settings_maximum
|
|
241
237
|
):
|
|
242
|
-
logger.error("
|
|
238
|
+
logger.error("gain_settings value out of range")
|
|
243
239
|
raise ATT_Error(ErrorCode.VALUE_OUT_OF_RANGE)
|
|
244
240
|
|
|
245
241
|
if self.audio_input_state.gain_settings != gain_settings_operand:
|
|
@@ -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/bap.py
CHANGED
|
@@ -265,7 +265,7 @@ class UnicastServerAdvertisingData:
|
|
|
265
265
|
core.AdvertisingData.SERVICE_DATA_16_BIT_UUID,
|
|
266
266
|
struct.pack(
|
|
267
267
|
'<2sBIB',
|
|
268
|
-
gatt.GATT_AUDIO_STREAM_CONTROL_SERVICE
|
|
268
|
+
bytes(gatt.GATT_AUDIO_STREAM_CONTROL_SERVICE),
|
|
269
269
|
self.announcement_type,
|
|
270
270
|
self.available_audio_contexts,
|
|
271
271
|
len(self.metadata),
|
|
@@ -398,18 +398,21 @@ class CodecSpecificConfiguration:
|
|
|
398
398
|
OCTETS_PER_FRAME = 0x04
|
|
399
399
|
CODEC_FRAMES_PER_SDU = 0x05
|
|
400
400
|
|
|
401
|
-
sampling_frequency: SamplingFrequency
|
|
402
|
-
frame_duration: FrameDuration
|
|
403
|
-
audio_channel_allocation: AudioLocation
|
|
404
|
-
octets_per_codec_frame: int
|
|
405
|
-
codec_frames_per_sdu: int
|
|
401
|
+
sampling_frequency: SamplingFrequency | None = None
|
|
402
|
+
frame_duration: FrameDuration | None = None
|
|
403
|
+
audio_channel_allocation: AudioLocation | None = None
|
|
404
|
+
octets_per_codec_frame: int | None = None
|
|
405
|
+
codec_frames_per_sdu: int | None = None
|
|
406
406
|
|
|
407
407
|
@classmethod
|
|
408
408
|
def from_bytes(cls, data: bytes) -> CodecSpecificConfiguration:
|
|
409
409
|
offset = 0
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
410
|
+
sampling_frequency: SamplingFrequency | None = None
|
|
411
|
+
frame_duration: FrameDuration | None = None
|
|
412
|
+
audio_channel_allocation: AudioLocation | None = None
|
|
413
|
+
octets_per_codec_frame: int | None = None
|
|
414
|
+
codec_frames_per_sdu: int | None = None
|
|
415
|
+
|
|
413
416
|
while offset < len(data):
|
|
414
417
|
length, type = struct.unpack_from('BB', data, offset)
|
|
415
418
|
offset += 2
|
|
@@ -427,8 +430,6 @@ class CodecSpecificConfiguration:
|
|
|
427
430
|
elif type == CodecSpecificConfiguration.Type.CODEC_FRAMES_PER_SDU:
|
|
428
431
|
codec_frames_per_sdu = value
|
|
429
432
|
|
|
430
|
-
# It is expected here that if some fields are missing, an error should be raised.
|
|
431
|
-
# pylint: disable=possibly-used-before-assignment,used-before-assignment
|
|
432
433
|
return CodecSpecificConfiguration(
|
|
433
434
|
sampling_frequency=sampling_frequency,
|
|
434
435
|
frame_duration=frame_duration,
|
|
@@ -438,23 +439,43 @@ class CodecSpecificConfiguration:
|
|
|
438
439
|
)
|
|
439
440
|
|
|
440
441
|
def __bytes__(self) -> bytes:
|
|
441
|
-
return
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
442
|
+
return b''.join(
|
|
443
|
+
[
|
|
444
|
+
struct.pack(fmt, length, tag, value)
|
|
445
|
+
for fmt, length, tag, value in [
|
|
446
|
+
(
|
|
447
|
+
'<BBB',
|
|
448
|
+
2,
|
|
449
|
+
CodecSpecificConfiguration.Type.SAMPLING_FREQUENCY,
|
|
450
|
+
self.sampling_frequency,
|
|
451
|
+
),
|
|
452
|
+
(
|
|
453
|
+
'<BBB',
|
|
454
|
+
2,
|
|
455
|
+
CodecSpecificConfiguration.Type.FRAME_DURATION,
|
|
456
|
+
self.frame_duration,
|
|
457
|
+
),
|
|
458
|
+
(
|
|
459
|
+
'<BBI',
|
|
460
|
+
5,
|
|
461
|
+
CodecSpecificConfiguration.Type.AUDIO_CHANNEL_ALLOCATION,
|
|
462
|
+
self.audio_channel_allocation,
|
|
463
|
+
),
|
|
464
|
+
(
|
|
465
|
+
'<BBH',
|
|
466
|
+
3,
|
|
467
|
+
CodecSpecificConfiguration.Type.OCTETS_PER_FRAME,
|
|
468
|
+
self.octets_per_codec_frame,
|
|
469
|
+
),
|
|
470
|
+
(
|
|
471
|
+
'<BBB',
|
|
472
|
+
2,
|
|
473
|
+
CodecSpecificConfiguration.Type.CODEC_FRAMES_PER_SDU,
|
|
474
|
+
self.codec_frames_per_sdu,
|
|
475
|
+
),
|
|
476
|
+
]
|
|
477
|
+
if value is not None
|
|
478
|
+
]
|
|
458
479
|
)
|
|
459
480
|
|
|
460
481
|
|
|
@@ -466,6 +487,24 @@ class BroadcastAudioAnnouncement:
|
|
|
466
487
|
def from_bytes(cls, data: bytes) -> Self:
|
|
467
488
|
return cls(int.from_bytes(data[:3], 'little'))
|
|
468
489
|
|
|
490
|
+
def __bytes__(self) -> bytes:
|
|
491
|
+
return self.broadcast_id.to_bytes(3, 'little')
|
|
492
|
+
|
|
493
|
+
def get_advertising_data(self) -> bytes:
|
|
494
|
+
return bytes(
|
|
495
|
+
core.AdvertisingData(
|
|
496
|
+
[
|
|
497
|
+
(
|
|
498
|
+
core.AdvertisingData.SERVICE_DATA_16_BIT_UUID,
|
|
499
|
+
(
|
|
500
|
+
bytes(gatt.GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE)
|
|
501
|
+
+ bytes(self)
|
|
502
|
+
),
|
|
503
|
+
)
|
|
504
|
+
]
|
|
505
|
+
)
|
|
506
|
+
)
|
|
507
|
+
|
|
469
508
|
|
|
470
509
|
@dataclasses.dataclass
|
|
471
510
|
class BasicAudioAnnouncement:
|
|
@@ -474,26 +513,37 @@ class BasicAudioAnnouncement:
|
|
|
474
513
|
index: int
|
|
475
514
|
codec_specific_configuration: CodecSpecificConfiguration
|
|
476
515
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
coding_format = hci.CodecID(data[0])
|
|
486
|
-
company_id = int.from_bytes(data[1:3], 'little')
|
|
487
|
-
vendor_specific_codec_id = int.from_bytes(data[3:5], 'little')
|
|
488
|
-
return cls(coding_format, company_id, vendor_specific_codec_id)
|
|
516
|
+
def __bytes__(self) -> bytes:
|
|
517
|
+
codec_specific_configuration_bytes = bytes(
|
|
518
|
+
self.codec_specific_configuration
|
|
519
|
+
)
|
|
520
|
+
return (
|
|
521
|
+
bytes([self.index, len(codec_specific_configuration_bytes)])
|
|
522
|
+
+ codec_specific_configuration_bytes
|
|
523
|
+
)
|
|
489
524
|
|
|
490
525
|
@dataclasses.dataclass
|
|
491
526
|
class Subgroup:
|
|
492
|
-
codec_id:
|
|
527
|
+
codec_id: hci.CodingFormat
|
|
493
528
|
codec_specific_configuration: CodecSpecificConfiguration
|
|
494
529
|
metadata: le_audio.Metadata
|
|
495
530
|
bis: List[BasicAudioAnnouncement.BIS]
|
|
496
531
|
|
|
532
|
+
def __bytes__(self) -> bytes:
|
|
533
|
+
metadata_bytes = bytes(self.metadata)
|
|
534
|
+
codec_specific_configuration_bytes = bytes(
|
|
535
|
+
self.codec_specific_configuration
|
|
536
|
+
)
|
|
537
|
+
return (
|
|
538
|
+
bytes([len(self.bis)])
|
|
539
|
+
+ bytes(self.codec_id)
|
|
540
|
+
+ bytes([len(codec_specific_configuration_bytes)])
|
|
541
|
+
+ codec_specific_configuration_bytes
|
|
542
|
+
+ bytes([len(metadata_bytes)])
|
|
543
|
+
+ metadata_bytes
|
|
544
|
+
+ b''.join(map(bytes, self.bis))
|
|
545
|
+
)
|
|
546
|
+
|
|
497
547
|
presentation_delay: int
|
|
498
548
|
subgroups: List[BasicAudioAnnouncement.Subgroup]
|
|
499
549
|
|
|
@@ -505,7 +555,7 @@ class BasicAudioAnnouncement:
|
|
|
505
555
|
for _ in range(data[3]):
|
|
506
556
|
num_bis = data[offset]
|
|
507
557
|
offset += 1
|
|
508
|
-
codec_id =
|
|
558
|
+
codec_id = hci.CodingFormat.from_bytes(data[offset : offset + 5])
|
|
509
559
|
offset += 5
|
|
510
560
|
codec_specific_configuration_length = data[offset]
|
|
511
561
|
offset += 1
|
|
@@ -549,3 +599,25 @@ class BasicAudioAnnouncement:
|
|
|
549
599
|
)
|
|
550
600
|
|
|
551
601
|
return cls(presentation_delay, subgroups)
|
|
602
|
+
|
|
603
|
+
def __bytes__(self) -> bytes:
|
|
604
|
+
return (
|
|
605
|
+
self.presentation_delay.to_bytes(3, 'little')
|
|
606
|
+
+ bytes([len(self.subgroups)])
|
|
607
|
+
+ b''.join(map(bytes, self.subgroups))
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
def get_advertising_data(self) -> bytes:
|
|
611
|
+
return bytes(
|
|
612
|
+
core.AdvertisingData(
|
|
613
|
+
[
|
|
614
|
+
(
|
|
615
|
+
core.AdvertisingData.SERVICE_DATA_16_BIT_UUID,
|
|
616
|
+
(
|
|
617
|
+
bytes(gatt.GATT_BASIC_AUDIO_ANNOUNCEMENT_SERVICE)
|
|
618
|
+
+ bytes(self)
|
|
619
|
+
),
|
|
620
|
+
)
|
|
621
|
+
]
|
|
622
|
+
)
|
|
623
|
+
)
|