bumble 0.0.207__py3-none-any.whl → 0.0.208__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +9 -4
- bumble/apps/auracast.py +29 -35
- bumble/apps/bench.py +13 -10
- bumble/apps/console.py +19 -12
- bumble/apps/gg_bridge.py +1 -1
- bumble/att.py +51 -37
- bumble/core.py +301 -155
- bumble/device.py +102 -56
- bumble/gatt.py +9 -228
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +38 -31
- bumble/gatt_server.py +5 -5
- bumble/host.py +11 -5
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +7 -12
- bumble/profiles/aics.py +33 -23
- bumble/profiles/ascs.py +2 -1
- bumble/profiles/asha.py +11 -9
- bumble/profiles/bass.py +8 -5
- bumble/profiles/battery_service.py +13 -3
- bumble/profiles/device_information_service.py +16 -14
- bumble/profiles/gap.py +12 -8
- bumble/profiles/gatt_service.py +1 -0
- bumble/profiles/gmap.py +16 -11
- bumble/profiles/hap.py +8 -6
- bumble/profiles/heart_rate_service.py +20 -4
- bumble/profiles/mcp.py +11 -9
- bumble/profiles/pacs.py +37 -24
- bumble/profiles/tmap.py +6 -4
- bumble/profiles/vcs.py +6 -5
- bumble/profiles/vocs.py +49 -41
- bumble/utils.py +10 -0
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/METADATA +3 -3
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/RECORD +38 -37
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/LICENSE +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/WHEEL +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.208.dist-info}/top_level.txt +0 -0
bumble/_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.208'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 208)
|
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
|
# -----------------------------------------------------------------------------
|
|
@@ -748,7 +754,7 @@ class ATT_Handle_Value_Confirmation(ATT_PDU):
|
|
|
748
754
|
|
|
749
755
|
|
|
750
756
|
# -----------------------------------------------------------------------------
|
|
751
|
-
class AttributeValue:
|
|
757
|
+
class AttributeValue(Generic[_T]):
|
|
752
758
|
'''
|
|
753
759
|
Attribute value where reading and/or writing is delegated to functions
|
|
754
760
|
passed as arguments to the constructor.
|
|
@@ -757,33 +763,34 @@ class AttributeValue:
|
|
|
757
763
|
def __init__(
|
|
758
764
|
self,
|
|
759
765
|
read: Union[
|
|
760
|
-
Callable[[Optional[Connection]],
|
|
761
|
-
Callable[[Optional[Connection]], Awaitable[
|
|
766
|
+
Callable[[Optional[Connection]], _T],
|
|
767
|
+
Callable[[Optional[Connection]], Awaitable[_T]],
|
|
762
768
|
None,
|
|
763
769
|
] = None,
|
|
764
770
|
write: Union[
|
|
765
|
-
Callable[[Optional[Connection],
|
|
766
|
-
Callable[[Optional[Connection],
|
|
771
|
+
Callable[[Optional[Connection], _T], None],
|
|
772
|
+
Callable[[Optional[Connection], _T], Awaitable[None]],
|
|
767
773
|
None,
|
|
768
774
|
] = None,
|
|
769
775
|
):
|
|
770
776
|
self._read = read
|
|
771
777
|
self._write = write
|
|
772
778
|
|
|
773
|
-
def read(self, connection: Optional[Connection]) -> Union[
|
|
774
|
-
|
|
779
|
+
def read(self, connection: Optional[Connection]) -> Union[_T, Awaitable[_T]]:
|
|
780
|
+
if self._read is None:
|
|
781
|
+
raise InvalidOperationError('AttributeValue has no read function')
|
|
782
|
+
return self._read(connection)
|
|
775
783
|
|
|
776
784
|
def write(
|
|
777
|
-
self, connection: Optional[Connection], value:
|
|
785
|
+
self, connection: Optional[Connection], value: _T
|
|
778
786
|
) -> Union[Awaitable[None], None]:
|
|
779
|
-
if self._write:
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
return None
|
|
787
|
+
if self._write is None:
|
|
788
|
+
raise InvalidOperationError('AttributeValue has no write function')
|
|
789
|
+
return self._write(connection, value)
|
|
783
790
|
|
|
784
791
|
|
|
785
792
|
# -----------------------------------------------------------------------------
|
|
786
|
-
class Attribute(EventEmitter):
|
|
793
|
+
class Attribute(EventEmitter, Generic[_T]):
|
|
787
794
|
class Permissions(enum.IntFlag):
|
|
788
795
|
READABLE = 0x01
|
|
789
796
|
WRITEABLE = 0x02
|
|
@@ -822,13 +829,13 @@ class Attribute(EventEmitter):
|
|
|
822
829
|
READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION
|
|
823
830
|
WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION
|
|
824
831
|
|
|
825
|
-
value:
|
|
832
|
+
value: Union[AttributeValue[_T], _T, None]
|
|
826
833
|
|
|
827
834
|
def __init__(
|
|
828
835
|
self,
|
|
829
836
|
attribute_type: Union[str, bytes, UUID],
|
|
830
837
|
permissions: Union[str, Attribute.Permissions],
|
|
831
|
-
value:
|
|
838
|
+
value: Union[AttributeValue[_T], _T, None] = None,
|
|
832
839
|
) -> None:
|
|
833
840
|
EventEmitter.__init__(self)
|
|
834
841
|
self.handle = 0
|
|
@@ -848,11 +855,11 @@ class Attribute(EventEmitter):
|
|
|
848
855
|
|
|
849
856
|
self.value = value
|
|
850
857
|
|
|
851
|
-
def encode_value(self, value:
|
|
852
|
-
return value
|
|
858
|
+
def encode_value(self, value: _T) -> bytes:
|
|
859
|
+
return value # type: ignore
|
|
853
860
|
|
|
854
|
-
def decode_value(self,
|
|
855
|
-
return
|
|
861
|
+
def decode_value(self, value: bytes) -> _T:
|
|
862
|
+
return value # type: ignore
|
|
856
863
|
|
|
857
864
|
async def read_value(self, connection: Optional[Connection]) -> bytes:
|
|
858
865
|
if (
|
|
@@ -877,11 +884,14 @@ class Attribute(EventEmitter):
|
|
|
877
884
|
error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
|
|
878
885
|
)
|
|
879
886
|
|
|
880
|
-
|
|
887
|
+
value: Union[_T, None]
|
|
888
|
+
if isinstance(self.value, AttributeValue):
|
|
881
889
|
try:
|
|
882
|
-
|
|
883
|
-
if inspect.isawaitable(
|
|
884
|
-
value = await
|
|
890
|
+
read_value = self.value.read(connection)
|
|
891
|
+
if inspect.isawaitable(read_value):
|
|
892
|
+
value = await read_value
|
|
893
|
+
else:
|
|
894
|
+
value = read_value
|
|
885
895
|
except ATT_Error as error:
|
|
886
896
|
raise ATT_Error(
|
|
887
897
|
error_code=error.error_code, att_handle=self.handle
|
|
@@ -889,20 +899,24 @@ class Attribute(EventEmitter):
|
|
|
889
899
|
else:
|
|
890
900
|
value = self.value
|
|
891
901
|
|
|
892
|
-
self.emit('read', connection, value)
|
|
902
|
+
self.emit('read', connection, b'' if value is None else value)
|
|
893
903
|
|
|
894
|
-
return self.encode_value(value)
|
|
904
|
+
return b'' if value is None else self.encode_value(value)
|
|
895
905
|
|
|
896
|
-
async def write_value(self, connection: Connection,
|
|
906
|
+
async def write_value(self, connection: Optional[Connection], value: bytes) -> None:
|
|
897
907
|
if (
|
|
898
|
-
self.permissions & self.WRITE_REQUIRES_ENCRYPTION
|
|
899
|
-
|
|
908
|
+
(self.permissions & self.WRITE_REQUIRES_ENCRYPTION)
|
|
909
|
+
and connection is not None
|
|
910
|
+
and not connection.encryption
|
|
911
|
+
):
|
|
900
912
|
raise ATT_Error(
|
|
901
913
|
error_code=ATT_INSUFFICIENT_ENCRYPTION_ERROR, att_handle=self.handle
|
|
902
914
|
)
|
|
903
915
|
if (
|
|
904
|
-
self.permissions & self.WRITE_REQUIRES_AUTHENTICATION
|
|
905
|
-
|
|
916
|
+
(self.permissions & self.WRITE_REQUIRES_AUTHENTICATION)
|
|
917
|
+
and connection is not None
|
|
918
|
+
and not connection.authenticated
|
|
919
|
+
):
|
|
906
920
|
raise ATT_Error(
|
|
907
921
|
error_code=ATT_INSUFFICIENT_AUTHENTICATION_ERROR, att_handle=self.handle
|
|
908
922
|
)
|
|
@@ -912,11 +926,11 @@ class Attribute(EventEmitter):
|
|
|
912
926
|
error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
|
|
913
927
|
)
|
|
914
928
|
|
|
915
|
-
|
|
929
|
+
decoded_value = self.decode_value(value)
|
|
916
930
|
|
|
917
|
-
if
|
|
931
|
+
if isinstance(self.value, AttributeValue):
|
|
918
932
|
try:
|
|
919
|
-
result = self.value.write(connection,
|
|
933
|
+
result = self.value.write(connection, decoded_value)
|
|
920
934
|
if inspect.isawaitable(result):
|
|
921
935
|
await result
|
|
922
936
|
except ATT_Error as error:
|
|
@@ -924,9 +938,9 @@ class Attribute(EventEmitter):
|
|
|
924
938
|
error_code=error.error_code, att_handle=self.handle
|
|
925
939
|
) from error
|
|
926
940
|
else:
|
|
927
|
-
self.value =
|
|
941
|
+
self.value = decoded_value
|
|
928
942
|
|
|
929
|
-
self.emit('write', connection,
|
|
943
|
+
self.emit('write', connection, decoded_value)
|
|
930
944
|
|
|
931
945
|
def __repr__(self):
|
|
932
946
|
if isinstance(self.value, bytes):
|