bumble 0.0.207__py3-none-any.whl → 0.0.209__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 +61 -39
- bumble/controller.py +7 -8
- bumble/core.py +306 -159
- bumble/device.py +127 -82
- bumble/gatt.py +25 -228
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +38 -31
- bumble/gatt_server.py +5 -5
- bumble/hci.py +76 -71
- bumble/host.py +19 -8
- bumble/l2cap.py +2 -2
- bumble/link.py +2 -2
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +19 -23
- bumble/pandora/security.py +2 -3
- bumble/pandora/utils.py +2 -2
- bumble/profiles/aics.py +33 -23
- bumble/profiles/ancs.py +514 -0
- 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/smp.py +3 -3
- bumble/transport/usb.py +1 -3
- bumble/utils.py +10 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/METADATA +3 -3
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/RECORD +47 -45
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/WHEEL +1 -1
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/LICENSE +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/top_level.txt +0 -0
bumble/_version.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
20
|
+
__version__ = version = '0.0.209'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 209)
|
bumble/apps/auracast.py
CHANGED
|
@@ -27,7 +27,6 @@ import logging
|
|
|
27
27
|
import os
|
|
28
28
|
import struct
|
|
29
29
|
from typing import (
|
|
30
|
-
cast,
|
|
31
30
|
Any,
|
|
32
31
|
AsyncGenerator,
|
|
33
32
|
Coroutine,
|
|
@@ -127,11 +126,9 @@ class BroadcastScanner(pyee.EventEmitter):
|
|
|
127
126
|
def update(self, advertisement: bumble.device.Advertisement) -> None:
|
|
128
127
|
self.rssi = advertisement.rssi
|
|
129
128
|
for service_data in advertisement.data.get_all(
|
|
130
|
-
core.AdvertisingData.
|
|
129
|
+
core.AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID
|
|
131
130
|
):
|
|
132
|
-
assert isinstance(service_data, tuple)
|
|
133
131
|
service_uuid, data = service_data
|
|
134
|
-
assert isinstance(data, bytes)
|
|
135
132
|
|
|
136
133
|
if service_uuid == gatt.GATT_PUBLIC_BROADCAST_ANNOUNCEMENT_SERVICE:
|
|
137
134
|
self.public_broadcast_announcement = (
|
|
@@ -145,16 +142,14 @@ class BroadcastScanner(pyee.EventEmitter):
|
|
|
145
142
|
)
|
|
146
143
|
continue
|
|
147
144
|
|
|
148
|
-
self.appearance = advertisement.data.get(
|
|
149
|
-
core.AdvertisingData.APPEARANCE
|
|
145
|
+
self.appearance = advertisement.data.get(
|
|
146
|
+
core.AdvertisingData.Type.APPEARANCE
|
|
150
147
|
)
|
|
151
148
|
|
|
152
149
|
if manufacturer_data := advertisement.data.get(
|
|
153
|
-
core.AdvertisingData.MANUFACTURER_SPECIFIC_DATA
|
|
150
|
+
core.AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA
|
|
154
151
|
):
|
|
155
|
-
|
|
156
|
-
company_id = cast(int, manufacturer_data[0])
|
|
157
|
-
data = cast(bytes, manufacturer_data[1])
|
|
152
|
+
company_id, data = manufacturer_data
|
|
158
153
|
self.manufacturer_data = (
|
|
159
154
|
company_ids.COMPANY_IDENTIFIERS.get(
|
|
160
155
|
company_id, f'0x{company_id:04X}'
|
|
@@ -271,11 +266,9 @@ class BroadcastScanner(pyee.EventEmitter):
|
|
|
271
266
|
return
|
|
272
267
|
|
|
273
268
|
for service_data in advertisement.data.get_all(
|
|
274
|
-
core.AdvertisingData.
|
|
269
|
+
core.AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID
|
|
275
270
|
):
|
|
276
|
-
assert isinstance(service_data, tuple)
|
|
277
271
|
service_uuid, data = service_data
|
|
278
|
-
assert isinstance(data, bytes)
|
|
279
272
|
|
|
280
273
|
if service_uuid == gatt.GATT_BASIC_AUDIO_ANNOUNCEMENT_SERVICE:
|
|
281
274
|
self.basic_audio_announcement = (
|
|
@@ -316,24 +309,23 @@ class BroadcastScanner(pyee.EventEmitter):
|
|
|
316
309
|
def on_advertisement(self, advertisement: bumble.device.Advertisement) -> None:
|
|
317
310
|
if not (
|
|
318
311
|
ads := advertisement.data.get_all(
|
|
319
|
-
core.AdvertisingData.SERVICE_DATA_16_BIT_UUID
|
|
312
|
+
core.AdvertisingData.Type.SERVICE_DATA_16_BIT_UUID
|
|
320
313
|
)
|
|
321
314
|
) or not (
|
|
322
315
|
broadcast_audio_announcement := next(
|
|
323
316
|
(
|
|
324
317
|
ad
|
|
325
318
|
for ad in ads
|
|
326
|
-
if
|
|
327
|
-
and ad[0] == gatt.GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE
|
|
319
|
+
if ad[0] == gatt.GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE
|
|
328
320
|
),
|
|
329
321
|
None,
|
|
330
322
|
)
|
|
331
323
|
):
|
|
332
324
|
return
|
|
333
325
|
|
|
334
|
-
broadcast_name = advertisement.data.
|
|
335
|
-
|
|
336
|
-
|
|
326
|
+
broadcast_name = advertisement.data.get_all(
|
|
327
|
+
core.AdvertisingData.Type.BROADCAST_NAME
|
|
328
|
+
)
|
|
337
329
|
|
|
338
330
|
if broadcast := self.broadcasts.get(advertisement.address):
|
|
339
331
|
broadcast.update(advertisement)
|
|
@@ -341,7 +333,7 @@ class BroadcastScanner(pyee.EventEmitter):
|
|
|
341
333
|
|
|
342
334
|
bumble.utils.AsyncRunner.spawn(
|
|
343
335
|
self.on_new_broadcast(
|
|
344
|
-
broadcast_name,
|
|
336
|
+
broadcast_name[0] if broadcast_name else None,
|
|
345
337
|
advertisement,
|
|
346
338
|
bap.BroadcastAudioAnnouncement.from_bytes(
|
|
347
339
|
broadcast_audio_announcement[1]
|
|
@@ -522,14 +514,19 @@ async def run_assist(
|
|
|
522
514
|
return
|
|
523
515
|
|
|
524
516
|
# Subscribe to and read the broadcast receive state characteristics
|
|
517
|
+
def on_broadcast_receive_state_update(
|
|
518
|
+
value: bass.BroadcastReceiveState, index: int
|
|
519
|
+
) -> None:
|
|
520
|
+
print(
|
|
521
|
+
f"{color(f'Broadcast Receive State Update [{index}]:', 'green')} {value}"
|
|
522
|
+
)
|
|
523
|
+
|
|
525
524
|
for i, broadcast_receive_state in enumerate(
|
|
526
525
|
bass_client.broadcast_receive_states
|
|
527
526
|
):
|
|
528
527
|
try:
|
|
529
528
|
await broadcast_receive_state.subscribe(
|
|
530
|
-
|
|
531
|
-
f"{color(f'Broadcast Receive State Update [{i}]:', 'green')} {value}"
|
|
532
|
-
)
|
|
529
|
+
functools.partial(on_broadcast_receive_state_update, index=i)
|
|
533
530
|
)
|
|
534
531
|
except core.ProtocolError as error:
|
|
535
532
|
print(
|
|
@@ -790,16 +787,8 @@ async def run_receive(
|
|
|
790
787
|
for i, bis_link in enumerate(big_sync.bis_links):
|
|
791
788
|
print(f'Setup ISO for BIS {bis_link.handle}')
|
|
792
789
|
bis_link.sink = functools.partial(sink, lc3_queues[i])
|
|
793
|
-
await
|
|
794
|
-
|
|
795
|
-
connection_handle=bis_link.handle,
|
|
796
|
-
data_path_direction=hci.HCI_LE_Setup_ISO_Data_Path_Command.Direction.CONTROLLER_TO_HOST,
|
|
797
|
-
data_path_id=0,
|
|
798
|
-
codec_id=hci.CodingFormat(codec_id=hci.CodecID.TRANSPARENT),
|
|
799
|
-
controller_delay=0,
|
|
800
|
-
codec_configuration=b'',
|
|
801
|
-
),
|
|
802
|
-
check_result=True,
|
|
790
|
+
await bis_link.setup_data_path(
|
|
791
|
+
direction=bis_link.Direction.CONTROLLER_TO_HOST
|
|
803
792
|
)
|
|
804
793
|
|
|
805
794
|
terminated = asyncio.Event()
|
|
@@ -959,10 +948,15 @@ async def run_transmit(
|
|
|
959
948
|
),
|
|
960
949
|
),
|
|
961
950
|
)
|
|
951
|
+
for bis_link in big.bis_links:
|
|
952
|
+
print(f'Setup ISO for BIS {bis_link.handle}')
|
|
953
|
+
await bis_link.setup_data_path(
|
|
954
|
+
direction=bis_link.Direction.HOST_TO_CONTROLLER
|
|
955
|
+
)
|
|
962
956
|
|
|
963
957
|
iso_queues = [
|
|
964
|
-
bumble.device.IsoPacketStream(
|
|
965
|
-
|
|
958
|
+
bumble.device.IsoPacketStream(bis_link, 64)
|
|
959
|
+
for bis_link in big.bis_links
|
|
966
960
|
]
|
|
967
961
|
|
|
968
962
|
def on_flow():
|
bumble/apps/bench.py
CHANGED
|
@@ -104,15 +104,16 @@ def le_phy_name(phy_id):
|
|
|
104
104
|
)
|
|
105
105
|
|
|
106
106
|
|
|
107
|
+
def print_connection_phy(phy):
|
|
108
|
+
logging.info(
|
|
109
|
+
color('@@@ PHY: ', 'yellow') + f'TX:{le_phy_name(phy.tx_phy)}/'
|
|
110
|
+
f'RX:{le_phy_name(phy.rx_phy)}'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
107
114
|
def print_connection(connection):
|
|
108
115
|
params = []
|
|
109
116
|
if connection.transport == BT_LE_TRANSPORT:
|
|
110
|
-
params.append(
|
|
111
|
-
'PHY='
|
|
112
|
-
f'TX:{le_phy_name(connection.phy.tx_phy)}/'
|
|
113
|
-
f'RX:{le_phy_name(connection.phy.rx_phy)}'
|
|
114
|
-
)
|
|
115
|
-
|
|
116
117
|
params.append(
|
|
117
118
|
'DL=('
|
|
118
119
|
f'TX:{connection.data_length[0]}/{connection.data_length[1]},'
|
|
@@ -1288,6 +1289,8 @@ class Central(Connection.Listener):
|
|
|
1288
1289
|
logging.info(color('### Connected', 'cyan'))
|
|
1289
1290
|
self.connection.listener = self
|
|
1290
1291
|
print_connection(self.connection)
|
|
1292
|
+
phy = await self.connection.get_phy()
|
|
1293
|
+
print_connection_phy(phy)
|
|
1291
1294
|
|
|
1292
1295
|
# Switch roles if needed.
|
|
1293
1296
|
if self.role_switch:
|
|
@@ -1345,8 +1348,8 @@ class Central(Connection.Listener):
|
|
|
1345
1348
|
def on_connection_parameters_update(self):
|
|
1346
1349
|
print_connection(self.connection)
|
|
1347
1350
|
|
|
1348
|
-
def on_connection_phy_update(self):
|
|
1349
|
-
|
|
1351
|
+
def on_connection_phy_update(self, phy):
|
|
1352
|
+
print_connection_phy(phy)
|
|
1350
1353
|
|
|
1351
1354
|
def on_connection_att_mtu_update(self):
|
|
1352
1355
|
print_connection(self.connection)
|
|
@@ -1472,8 +1475,8 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1472
1475
|
def on_connection_parameters_update(self):
|
|
1473
1476
|
print_connection(self.connection)
|
|
1474
1477
|
|
|
1475
|
-
def on_connection_phy_update(self):
|
|
1476
|
-
|
|
1478
|
+
def on_connection_phy_update(self, phy):
|
|
1479
|
+
print_connection_phy(phy)
|
|
1477
1480
|
|
|
1478
1481
|
def on_connection_att_mtu_update(self):
|
|
1479
1482
|
print_connection(self.connection)
|
bumble/apps/console.py
CHANGED
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
import asyncio
|
|
23
23
|
import logging
|
|
24
24
|
import os
|
|
25
|
-
import random
|
|
26
25
|
import re
|
|
27
26
|
import humanize
|
|
28
27
|
from typing import Optional, Union
|
|
@@ -57,7 +56,13 @@ from bumble import __version__
|
|
|
57
56
|
import bumble.core
|
|
58
57
|
from bumble import colors
|
|
59
58
|
from bumble.core import UUID, AdvertisingData, BT_LE_TRANSPORT
|
|
60
|
-
from bumble.device import
|
|
59
|
+
from bumble.device import (
|
|
60
|
+
ConnectionParametersPreferences,
|
|
61
|
+
ConnectionPHY,
|
|
62
|
+
Device,
|
|
63
|
+
Connection,
|
|
64
|
+
Peer,
|
|
65
|
+
)
|
|
61
66
|
from bumble.utils import AsyncRunner
|
|
62
67
|
from bumble.transport import open_transport_or_link
|
|
63
68
|
from bumble.gatt import Characteristic, Service, CharacteristicDeclaration, Descriptor
|
|
@@ -125,6 +130,7 @@ def parse_phys(phys):
|
|
|
125
130
|
# -----------------------------------------------------------------------------
|
|
126
131
|
class ConsoleApp:
|
|
127
132
|
connected_peer: Optional[Peer]
|
|
133
|
+
connection_phy: Optional[ConnectionPHY]
|
|
128
134
|
|
|
129
135
|
def __init__(self):
|
|
130
136
|
self.known_addresses = set()
|
|
@@ -132,6 +138,7 @@ class ConsoleApp:
|
|
|
132
138
|
self.known_local_attributes = []
|
|
133
139
|
self.device = None
|
|
134
140
|
self.connected_peer = None
|
|
141
|
+
self.connection_phy = None
|
|
135
142
|
self.top_tab = 'device'
|
|
136
143
|
self.monitor_rssi = False
|
|
137
144
|
self.connection_rssi = None
|
|
@@ -332,10 +339,10 @@ class ConsoleApp:
|
|
|
332
339
|
f'{connection.parameters.peripheral_latency}/'
|
|
333
340
|
f'{connection.parameters.supervision_timeout}'
|
|
334
341
|
)
|
|
335
|
-
if
|
|
342
|
+
if self.connection_phy is not None:
|
|
336
343
|
phy_state = (
|
|
337
|
-
f' RX={le_phy_name(
|
|
338
|
-
f'TX={le_phy_name(
|
|
344
|
+
f' RX={le_phy_name(self.connection_phy.rx_phy)}/'
|
|
345
|
+
f'TX={le_phy_name(self.connection_phy.tx_phy)}'
|
|
339
346
|
)
|
|
340
347
|
else:
|
|
341
348
|
phy_state = ''
|
|
@@ -654,11 +661,12 @@ class ConsoleApp:
|
|
|
654
661
|
self.append_to_output('connecting...')
|
|
655
662
|
|
|
656
663
|
try:
|
|
657
|
-
await self.device.connect(
|
|
664
|
+
connection = await self.device.connect(
|
|
658
665
|
params[0],
|
|
659
666
|
connection_parameters_preferences=connection_parameters_preferences,
|
|
660
667
|
timeout=DEFAULT_CONNECTION_TIMEOUT,
|
|
661
668
|
)
|
|
669
|
+
self.connection_phy = await connection.get_phy()
|
|
662
670
|
self.top_tab = 'services'
|
|
663
671
|
except bumble.core.TimeoutError:
|
|
664
672
|
self.show_error('connection timed out')
|
|
@@ -838,8 +846,8 @@ class ConsoleApp:
|
|
|
838
846
|
|
|
839
847
|
phy = await self.connected_peer.connection.get_phy()
|
|
840
848
|
self.append_to_output(
|
|
841
|
-
f'PHY: RX={HCI_Constant.le_phy_name(phy
|
|
842
|
-
f'TX={HCI_Constant.le_phy_name(phy
|
|
849
|
+
f'PHY: RX={HCI_Constant.le_phy_name(phy.rx_phy)}, '
|
|
850
|
+
f'TX={HCI_Constant.le_phy_name(phy.tx_phy)}'
|
|
843
851
|
)
|
|
844
852
|
|
|
845
853
|
async def do_request_mtu(self, params):
|
|
@@ -1076,10 +1084,9 @@ class DeviceListener(Device.Listener, Connection.Listener):
|
|
|
1076
1084
|
f'{self.app.connected_peer.connection.parameters}'
|
|
1077
1085
|
)
|
|
1078
1086
|
|
|
1079
|
-
def on_connection_phy_update(self):
|
|
1080
|
-
self.app.
|
|
1081
|
-
|
|
1082
|
-
)
|
|
1087
|
+
def on_connection_phy_update(self, phy):
|
|
1088
|
+
self.app.connection_phy = phy
|
|
1089
|
+
self.app.append_to_output(f'connection phy update: {phy}')
|
|
1083
1090
|
|
|
1084
1091
|
def on_connection_att_mtu_update(self):
|
|
1085
1092
|
self.app.append_to_output(
|
bumble/apps/gg_bridge.py
CHANGED
|
@@ -234,7 +234,7 @@ class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
|
|
|
234
234
|
Characteristic.WRITEABLE,
|
|
235
235
|
CharacteristicValue(write=self.on_rx_write),
|
|
236
236
|
)
|
|
237
|
-
self.tx_characteristic = Characteristic(
|
|
237
|
+
self.tx_characteristic: Characteristic[bytes] = Characteristic(
|
|
238
238
|
GG_GATTLINK_TX_CHARACTERISTIC_UUID,
|
|
239
239
|
Characteristic.Properties.NOTIFY,
|
|
240
240
|
Characteristic.READABLE,
|
bumble/att.py
CHANGED
|
@@ -29,13 +29,14 @@ import functools
|
|
|
29
29
|
import inspect
|
|
30
30
|
import struct
|
|
31
31
|
from typing import (
|
|
32
|
-
Any,
|
|
33
32
|
Awaitable,
|
|
34
33
|
Callable,
|
|
34
|
+
Generic,
|
|
35
35
|
Dict,
|
|
36
36
|
List,
|
|
37
37
|
Optional,
|
|
38
38
|
Type,
|
|
39
|
+
TypeVar,
|
|
39
40
|
Union,
|
|
40
41
|
TYPE_CHECKING,
|
|
41
42
|
)
|
|
@@ -43,13 +44,18 @@ from typing import (
|
|
|
43
44
|
from pyee import EventEmitter
|
|
44
45
|
|
|
45
46
|
from bumble import utils
|
|
46
|
-
from bumble.core import UUID, name_or_number, ProtocolError
|
|
47
|
+
from bumble.core import UUID, name_or_number, InvalidOperationError, ProtocolError
|
|
47
48
|
from bumble.hci import HCI_Object, key_with_value
|
|
48
49
|
from bumble.colors import color
|
|
49
50
|
|
|
51
|
+
# -----------------------------------------------------------------------------
|
|
52
|
+
# Typing
|
|
53
|
+
# -----------------------------------------------------------------------------
|
|
50
54
|
if TYPE_CHECKING:
|
|
51
55
|
from bumble.device import Connection
|
|
52
56
|
|
|
57
|
+
_T = TypeVar('_T')
|
|
58
|
+
|
|
53
59
|
# -----------------------------------------------------------------------------
|
|
54
60
|
# Constants
|
|
55
61
|
# -----------------------------------------------------------------------------
|
|
@@ -217,7 +223,12 @@ UUID_2_FIELD_SPEC = lambda x, y: UUID.parse_uuid_2(x, y) # noqa: E731
|
|
|
217
223
|
# Exceptions
|
|
218
224
|
# -----------------------------------------------------------------------------
|
|
219
225
|
class ATT_Error(ProtocolError):
|
|
220
|
-
|
|
226
|
+
error_code: int
|
|
227
|
+
att_handle: int
|
|
228
|
+
|
|
229
|
+
def __init__(
|
|
230
|
+
self, error_code: int, att_handle: int = 0x0000, message: str = ''
|
|
231
|
+
) -> None:
|
|
221
232
|
super().__init__(
|
|
222
233
|
error_code,
|
|
223
234
|
error_namespace='att',
|
|
@@ -227,7 +238,10 @@ class ATT_Error(ProtocolError):
|
|
|
227
238
|
self.message = message
|
|
228
239
|
|
|
229
240
|
def __str__(self):
|
|
230
|
-
return
|
|
241
|
+
return (
|
|
242
|
+
f'ATT_Error(error={self.error_name}, '
|
|
243
|
+
f'handle={self.att_handle:04X}): {self.message}'
|
|
244
|
+
)
|
|
231
245
|
|
|
232
246
|
|
|
233
247
|
# -----------------------------------------------------------------------------
|
|
@@ -748,7 +762,7 @@ class ATT_Handle_Value_Confirmation(ATT_PDU):
|
|
|
748
762
|
|
|
749
763
|
|
|
750
764
|
# -----------------------------------------------------------------------------
|
|
751
|
-
class AttributeValue:
|
|
765
|
+
class AttributeValue(Generic[_T]):
|
|
752
766
|
'''
|
|
753
767
|
Attribute value where reading and/or writing is delegated to functions
|
|
754
768
|
passed as arguments to the constructor.
|
|
@@ -757,33 +771,34 @@ class AttributeValue:
|
|
|
757
771
|
def __init__(
|
|
758
772
|
self,
|
|
759
773
|
read: Union[
|
|
760
|
-
Callable[[Optional[Connection]],
|
|
761
|
-
Callable[[Optional[Connection]], Awaitable[
|
|
774
|
+
Callable[[Optional[Connection]], _T],
|
|
775
|
+
Callable[[Optional[Connection]], Awaitable[_T]],
|
|
762
776
|
None,
|
|
763
777
|
] = None,
|
|
764
778
|
write: Union[
|
|
765
|
-
Callable[[Optional[Connection],
|
|
766
|
-
Callable[[Optional[Connection],
|
|
779
|
+
Callable[[Optional[Connection], _T], None],
|
|
780
|
+
Callable[[Optional[Connection], _T], Awaitable[None]],
|
|
767
781
|
None,
|
|
768
782
|
] = None,
|
|
769
783
|
):
|
|
770
784
|
self._read = read
|
|
771
785
|
self._write = write
|
|
772
786
|
|
|
773
|
-
def read(self, connection: Optional[Connection]) -> Union[
|
|
774
|
-
|
|
787
|
+
def read(self, connection: Optional[Connection]) -> Union[_T, Awaitable[_T]]:
|
|
788
|
+
if self._read is None:
|
|
789
|
+
raise InvalidOperationError('AttributeValue has no read function')
|
|
790
|
+
return self._read(connection)
|
|
775
791
|
|
|
776
792
|
def write(
|
|
777
|
-
self, connection: Optional[Connection], value:
|
|
793
|
+
self, connection: Optional[Connection], value: _T
|
|
778
794
|
) -> Union[Awaitable[None], None]:
|
|
779
|
-
if self._write:
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
return None
|
|
795
|
+
if self._write is None:
|
|
796
|
+
raise InvalidOperationError('AttributeValue has no write function')
|
|
797
|
+
return self._write(connection, value)
|
|
783
798
|
|
|
784
799
|
|
|
785
800
|
# -----------------------------------------------------------------------------
|
|
786
|
-
class Attribute(EventEmitter):
|
|
801
|
+
class Attribute(EventEmitter, Generic[_T]):
|
|
787
802
|
class Permissions(enum.IntFlag):
|
|
788
803
|
READABLE = 0x01
|
|
789
804
|
WRITEABLE = 0x02
|
|
@@ -822,13 +837,13 @@ class Attribute(EventEmitter):
|
|
|
822
837
|
READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION
|
|
823
838
|
WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION
|
|
824
839
|
|
|
825
|
-
value:
|
|
840
|
+
value: Union[AttributeValue[_T], _T, None]
|
|
826
841
|
|
|
827
842
|
def __init__(
|
|
828
843
|
self,
|
|
829
844
|
attribute_type: Union[str, bytes, UUID],
|
|
830
845
|
permissions: Union[str, Attribute.Permissions],
|
|
831
|
-
value:
|
|
846
|
+
value: Union[AttributeValue[_T], _T, None] = None,
|
|
832
847
|
) -> None:
|
|
833
848
|
EventEmitter.__init__(self)
|
|
834
849
|
self.handle = 0
|
|
@@ -848,11 +863,11 @@ class Attribute(EventEmitter):
|
|
|
848
863
|
|
|
849
864
|
self.value = value
|
|
850
865
|
|
|
851
|
-
def encode_value(self, value:
|
|
852
|
-
return value
|
|
866
|
+
def encode_value(self, value: _T) -> bytes:
|
|
867
|
+
return value # type: ignore
|
|
853
868
|
|
|
854
|
-
def decode_value(self,
|
|
855
|
-
return
|
|
869
|
+
def decode_value(self, value: bytes) -> _T:
|
|
870
|
+
return value # type: ignore
|
|
856
871
|
|
|
857
872
|
async def read_value(self, connection: Optional[Connection]) -> bytes:
|
|
858
873
|
if (
|
|
@@ -877,11 +892,14 @@ class Attribute(EventEmitter):
|
|
|
877
892
|
error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
|
|
878
893
|
)
|
|
879
894
|
|
|
880
|
-
|
|
895
|
+
value: Union[_T, None]
|
|
896
|
+
if isinstance(self.value, AttributeValue):
|
|
881
897
|
try:
|
|
882
|
-
|
|
883
|
-
if inspect.isawaitable(
|
|
884
|
-
value = await
|
|
898
|
+
read_value = self.value.read(connection)
|
|
899
|
+
if inspect.isawaitable(read_value):
|
|
900
|
+
value = await read_value
|
|
901
|
+
else:
|
|
902
|
+
value = read_value
|
|
885
903
|
except ATT_Error as error:
|
|
886
904
|
raise ATT_Error(
|
|
887
905
|
error_code=error.error_code, att_handle=self.handle
|
|
@@ -889,20 +907,24 @@ class Attribute(EventEmitter):
|
|
|
889
907
|
else:
|
|
890
908
|
value = self.value
|
|
891
909
|
|
|
892
|
-
self.emit('read', connection, value)
|
|
910
|
+
self.emit('read', connection, b'' if value is None else value)
|
|
893
911
|
|
|
894
|
-
return self.encode_value(value)
|
|
912
|
+
return b'' if value is None else self.encode_value(value)
|
|
895
913
|
|
|
896
|
-
async def write_value(self, connection: Connection,
|
|
914
|
+
async def write_value(self, connection: Optional[Connection], value: bytes) -> None:
|
|
897
915
|
if (
|
|
898
|
-
self.permissions & self.WRITE_REQUIRES_ENCRYPTION
|
|
899
|
-
|
|
916
|
+
(self.permissions & self.WRITE_REQUIRES_ENCRYPTION)
|
|
917
|
+
and connection is not None
|
|
918
|
+
and not connection.encryption
|
|
919
|
+
):
|
|
900
920
|
raise ATT_Error(
|
|
901
921
|
error_code=ATT_INSUFFICIENT_ENCRYPTION_ERROR, att_handle=self.handle
|
|
902
922
|
)
|
|
903
923
|
if (
|
|
904
|
-
self.permissions & self.WRITE_REQUIRES_AUTHENTICATION
|
|
905
|
-
|
|
924
|
+
(self.permissions & self.WRITE_REQUIRES_AUTHENTICATION)
|
|
925
|
+
and connection is not None
|
|
926
|
+
and not connection.authenticated
|
|
927
|
+
):
|
|
906
928
|
raise ATT_Error(
|
|
907
929
|
error_code=ATT_INSUFFICIENT_AUTHENTICATION_ERROR, att_handle=self.handle
|
|
908
930
|
)
|
|
@@ -912,11 +934,11 @@ class Attribute(EventEmitter):
|
|
|
912
934
|
error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
|
|
913
935
|
)
|
|
914
936
|
|
|
915
|
-
|
|
937
|
+
decoded_value = self.decode_value(value)
|
|
916
938
|
|
|
917
|
-
if
|
|
939
|
+
if isinstance(self.value, AttributeValue):
|
|
918
940
|
try:
|
|
919
|
-
result = self.value.write(connection,
|
|
941
|
+
result = self.value.write(connection, decoded_value)
|
|
920
942
|
if inspect.isawaitable(result):
|
|
921
943
|
await result
|
|
922
944
|
except ATT_Error as error:
|
|
@@ -924,9 +946,9 @@ class Attribute(EventEmitter):
|
|
|
924
946
|
error_code=error.error_code, att_handle=self.handle
|
|
925
947
|
) from error
|
|
926
948
|
else:
|
|
927
|
-
self.value =
|
|
949
|
+
self.value = decoded_value
|
|
928
950
|
|
|
929
|
-
self.emit('write', connection,
|
|
951
|
+
self.emit('write', connection, decoded_value)
|
|
930
952
|
|
|
931
953
|
def __repr__(self):
|
|
932
954
|
if isinstance(self.value, bytes):
|
bumble/controller.py
CHANGED
|
@@ -25,8 +25,6 @@ import random
|
|
|
25
25
|
import struct
|
|
26
26
|
from bumble.colors import color
|
|
27
27
|
from bumble.core import (
|
|
28
|
-
BT_CENTRAL_ROLE,
|
|
29
|
-
BT_PERIPHERAL_ROLE,
|
|
30
28
|
BT_LE_TRANSPORT,
|
|
31
29
|
BT_BR_EDR_TRANSPORT,
|
|
32
30
|
)
|
|
@@ -47,6 +45,7 @@ from bumble.hci import (
|
|
|
47
45
|
HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
48
46
|
HCI_VERSION_BLUETOOTH_CORE_5_0,
|
|
49
47
|
Address,
|
|
48
|
+
Role,
|
|
50
49
|
HCI_AclDataPacket,
|
|
51
50
|
HCI_AclDataPacketAssembler,
|
|
52
51
|
HCI_Command_Complete_Event,
|
|
@@ -98,7 +97,7 @@ class CisLink:
|
|
|
98
97
|
class Connection:
|
|
99
98
|
controller: Controller
|
|
100
99
|
handle: int
|
|
101
|
-
role:
|
|
100
|
+
role: Role
|
|
102
101
|
peer_address: Address
|
|
103
102
|
link: Any
|
|
104
103
|
transport: int
|
|
@@ -390,7 +389,7 @@ class Controller:
|
|
|
390
389
|
connection = Connection(
|
|
391
390
|
controller=self,
|
|
392
391
|
handle=connection_handle,
|
|
393
|
-
role=
|
|
392
|
+
role=Role.PERIPHERAL,
|
|
394
393
|
peer_address=peer_address,
|
|
395
394
|
link=self.link,
|
|
396
395
|
transport=BT_LE_TRANSPORT,
|
|
@@ -450,7 +449,7 @@ class Controller:
|
|
|
450
449
|
connection = Connection(
|
|
451
450
|
controller=self,
|
|
452
451
|
handle=connection_handle,
|
|
453
|
-
role=
|
|
452
|
+
role=Role.CENTRAL,
|
|
454
453
|
peer_address=peer_address,
|
|
455
454
|
link=self.link,
|
|
456
455
|
transport=BT_LE_TRANSPORT,
|
|
@@ -469,7 +468,7 @@ class Controller:
|
|
|
469
468
|
HCI_LE_Connection_Complete_Event(
|
|
470
469
|
status=status,
|
|
471
470
|
connection_handle=connection.handle if connection else 0,
|
|
472
|
-
role=
|
|
471
|
+
role=Role.CENTRAL,
|
|
473
472
|
peer_address_type=le_create_connection_command.peer_address_type,
|
|
474
473
|
peer_address=le_create_connection_command.peer_address,
|
|
475
474
|
connection_interval=le_create_connection_command.connection_interval_min,
|
|
@@ -693,7 +692,7 @@ class Controller:
|
|
|
693
692
|
controller=self,
|
|
694
693
|
handle=connection_handle,
|
|
695
694
|
# Role doesn't matter in Classic because they are managed by HCI_Role_Change and HCI_Role_Discovery
|
|
696
|
-
role=
|
|
695
|
+
role=Role.CENTRAL,
|
|
697
696
|
peer_address=peer_address,
|
|
698
697
|
link=self.link,
|
|
699
698
|
transport=BT_BR_EDR_TRANSPORT,
|
|
@@ -761,7 +760,7 @@ class Controller:
|
|
|
761
760
|
controller=self,
|
|
762
761
|
handle=connection_handle,
|
|
763
762
|
# Role doesn't matter in SCO.
|
|
764
|
-
role=
|
|
763
|
+
role=Role.CENTRAL,
|
|
765
764
|
peer_address=peer_address,
|
|
766
765
|
link=self.link,
|
|
767
766
|
transport=BT_BR_EDR_TRANSPORT,
|