bumble 0.0.180__py3-none-any.whl → 0.0.181__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 +110 -81
- bumble/apps/ble_rpa_tool.py +63 -0
- bumble/apps/console.py +4 -4
- bumble/apps/controller_info.py +34 -2
- bumble/apps/pair.py +6 -8
- bumble/att.py +53 -11
- bumble/controller.py +28 -1
- bumble/crypto.py +10 -0
- bumble/device.py +566 -111
- bumble/drivers/__init__.py +27 -31
- bumble/drivers/common.py +45 -0
- bumble/drivers/rtk.py +11 -4
- bumble/gatt.py +66 -51
- bumble/gatt_server.py +30 -22
- bumble/hci.py +223 -92
- bumble/helpers.py +14 -0
- bumble/hfp.py +37 -27
- bumble/hid.py +282 -61
- bumble/host.py +158 -93
- bumble/l2cap.py +3 -3
- bumble/profiles/asha_service.py +2 -2
- bumble/profiles/bap.py +1247 -0
- bumble/profiles/cap.py +52 -0
- bumble/profiles/csip.py +62 -4
- bumble/rfcomm.py +24 -17
- bumble/smp.py +1 -1
- bumble/transport/__init__.py +49 -21
- bumble/transport/android_emulator.py +1 -1
- bumble/transport/common.py +2 -1
- bumble/transport/hci_socket.py +1 -4
- bumble/transport/usb.py +1 -1
- bumble/utils.py +3 -6
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/METADATA +1 -1
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/RECORD +39 -35
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/LICENSE +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/WHEEL +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/top_level.txt +0 -0
bumble/drivers/__init__.py
CHANGED
|
@@ -19,12 +19,17 @@ like loading firmware after a cold start.
|
|
|
19
19
|
# -----------------------------------------------------------------------------
|
|
20
20
|
# Imports
|
|
21
21
|
# -----------------------------------------------------------------------------
|
|
22
|
-
import
|
|
22
|
+
from __future__ import annotations
|
|
23
23
|
import logging
|
|
24
24
|
import pathlib
|
|
25
25
|
import platform
|
|
26
|
+
from typing import Dict, Iterable, Optional, Type, TYPE_CHECKING
|
|
27
|
+
|
|
26
28
|
from . import rtk
|
|
29
|
+
from .common import Driver
|
|
27
30
|
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from bumble.host import Host
|
|
28
33
|
|
|
29
34
|
# -----------------------------------------------------------------------------
|
|
30
35
|
# Logging
|
|
@@ -32,40 +37,31 @@ from . import rtk
|
|
|
32
37
|
logger = logging.getLogger(__name__)
|
|
33
38
|
|
|
34
39
|
|
|
35
|
-
# -----------------------------------------------------------------------------
|
|
36
|
-
# Classes
|
|
37
|
-
# -----------------------------------------------------------------------------
|
|
38
|
-
class Driver(abc.ABC):
|
|
39
|
-
"""Base class for drivers."""
|
|
40
|
-
|
|
41
|
-
@staticmethod
|
|
42
|
-
async def for_host(_host):
|
|
43
|
-
"""Return a driver instance for a host.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
host: Host object for which a driver should be created.
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
A Driver instance if a driver should be instantiated for this host, or
|
|
50
|
-
None if no driver instance of this class is needed.
|
|
51
|
-
"""
|
|
52
|
-
return None
|
|
53
|
-
|
|
54
|
-
@abc.abstractmethod
|
|
55
|
-
async def init_controller(self):
|
|
56
|
-
"""Initialize the controller."""
|
|
57
|
-
|
|
58
|
-
|
|
59
40
|
# -----------------------------------------------------------------------------
|
|
60
41
|
# Functions
|
|
61
42
|
# -----------------------------------------------------------------------------
|
|
62
|
-
async def get_driver_for_host(host):
|
|
63
|
-
"""Probe
|
|
64
|
-
|
|
43
|
+
async def get_driver_for_host(host: Host) -> Optional[Driver]:
|
|
44
|
+
"""Probe diver classes until one returns a valid instance for a host, or none is
|
|
45
|
+
found.
|
|
46
|
+
If a "driver" HCI metadata entry is present, only that driver class will be probed.
|
|
65
47
|
"""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
48
|
+
driver_classes: Dict[str, Type[Driver]] = {"rtk": rtk.Driver}
|
|
49
|
+
probe_list: Iterable[str]
|
|
50
|
+
if driver_name := host.hci_metadata.get("driver"):
|
|
51
|
+
# Only probe a single driver
|
|
52
|
+
probe_list = [driver_name]
|
|
53
|
+
else:
|
|
54
|
+
# Probe all drivers
|
|
55
|
+
probe_list = driver_classes.keys()
|
|
56
|
+
|
|
57
|
+
for driver_name in probe_list:
|
|
58
|
+
if driver_class := driver_classes.get(driver_name):
|
|
59
|
+
logger.debug(f"Probing driver class: {driver_name}")
|
|
60
|
+
if driver := await driver_class.for_host(host):
|
|
61
|
+
logger.debug(f"Instantiated {driver_name} driver")
|
|
62
|
+
return driver
|
|
63
|
+
else:
|
|
64
|
+
logger.debug(f"Skipping unknown driver class: {driver_name}")
|
|
69
65
|
|
|
70
66
|
return None
|
|
71
67
|
|
bumble/drivers/common.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright 2021-2023 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
Common types for drivers.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# -----------------------------------------------------------------------------
|
|
19
|
+
# Imports
|
|
20
|
+
# -----------------------------------------------------------------------------
|
|
21
|
+
import abc
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# -----------------------------------------------------------------------------
|
|
25
|
+
# Classes
|
|
26
|
+
# -----------------------------------------------------------------------------
|
|
27
|
+
class Driver(abc.ABC):
|
|
28
|
+
"""Base class for drivers."""
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
async def for_host(_host):
|
|
32
|
+
"""Return a driver instance for a host.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
host: Host object for which a driver should be created.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A Driver instance if a driver should be instantiated for this host, or
|
|
39
|
+
None if no driver instance of this class is needed.
|
|
40
|
+
"""
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
@abc.abstractmethod
|
|
44
|
+
async def init_controller(self):
|
|
45
|
+
"""Initialize the controller."""
|
bumble/drivers/rtk.py
CHANGED
|
@@ -41,7 +41,7 @@ from bumble.hci import (
|
|
|
41
41
|
HCI_Reset_Command,
|
|
42
42
|
HCI_Read_Local_Version_Information_Command,
|
|
43
43
|
)
|
|
44
|
-
|
|
44
|
+
from bumble.drivers import common
|
|
45
45
|
|
|
46
46
|
# -----------------------------------------------------------------------------
|
|
47
47
|
# Logging
|
|
@@ -285,7 +285,7 @@ class Firmware:
|
|
|
285
285
|
)
|
|
286
286
|
|
|
287
287
|
|
|
288
|
-
class Driver:
|
|
288
|
+
class Driver(common.Driver):
|
|
289
289
|
@dataclass
|
|
290
290
|
class DriverInfo:
|
|
291
291
|
rom: int
|
|
@@ -470,8 +470,12 @@ class Driver:
|
|
|
470
470
|
logger.debug("USB metadata not found")
|
|
471
471
|
return False
|
|
472
472
|
|
|
473
|
-
|
|
474
|
-
|
|
473
|
+
if host.hci_metadata.get('driver') == 'rtk':
|
|
474
|
+
# Forced driver
|
|
475
|
+
return True
|
|
476
|
+
|
|
477
|
+
vendor_id = host.hci_metadata.get("vendor_id")
|
|
478
|
+
product_id = host.hci_metadata.get("product_id")
|
|
475
479
|
if vendor_id is None or product_id is None:
|
|
476
480
|
logger.debug("USB metadata not sufficient")
|
|
477
481
|
return False
|
|
@@ -486,6 +490,9 @@ class Driver:
|
|
|
486
490
|
|
|
487
491
|
@classmethod
|
|
488
492
|
async def driver_info_for_host(cls, host):
|
|
493
|
+
await host.send_command(HCI_Reset_Command(), check_result=True)
|
|
494
|
+
host.ready = True # Needed to let the host know the controller is ready.
|
|
495
|
+
|
|
489
496
|
response = await host.send_command(
|
|
490
497
|
HCI_Read_Local_Version_Information_Command(), check_result=True
|
|
491
498
|
)
|
bumble/gatt.py
CHANGED
|
@@ -23,16 +23,28 @@
|
|
|
23
23
|
# Imports
|
|
24
24
|
# -----------------------------------------------------------------------------
|
|
25
25
|
from __future__ import annotations
|
|
26
|
-
import asyncio
|
|
27
26
|
import enum
|
|
28
27
|
import functools
|
|
29
28
|
import logging
|
|
30
29
|
import struct
|
|
31
|
-
from typing import
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
from typing import (
|
|
31
|
+
Callable,
|
|
32
|
+
Dict,
|
|
33
|
+
Iterable,
|
|
34
|
+
List,
|
|
35
|
+
Optional,
|
|
36
|
+
Sequence,
|
|
37
|
+
Union,
|
|
38
|
+
TYPE_CHECKING,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
from bumble.colors import color
|
|
42
|
+
from bumble.core import UUID
|
|
43
|
+
from bumble.att import Attribute, AttributeValue
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from bumble.gatt_client import AttributeProxy
|
|
47
|
+
from bumble.device import Connection
|
|
36
48
|
|
|
37
49
|
|
|
38
50
|
# -----------------------------------------------------------------------------
|
|
@@ -368,9 +380,12 @@ class TemplateService(Service):
|
|
|
368
380
|
UUID: UUID
|
|
369
381
|
|
|
370
382
|
def __init__(
|
|
371
|
-
self,
|
|
383
|
+
self,
|
|
384
|
+
characteristics: List[Characteristic],
|
|
385
|
+
primary: bool = True,
|
|
386
|
+
included_services: List[Service] = [],
|
|
372
387
|
) -> None:
|
|
373
|
-
super().__init__(self.UUID, characteristics, primary)
|
|
388
|
+
super().__init__(self.UUID, characteristics, primary, included_services)
|
|
374
389
|
|
|
375
390
|
|
|
376
391
|
# -----------------------------------------------------------------------------
|
|
@@ -519,56 +534,43 @@ class CharacteristicDeclaration(Attribute):
|
|
|
519
534
|
|
|
520
535
|
|
|
521
536
|
# -----------------------------------------------------------------------------
|
|
522
|
-
class CharacteristicValue:
|
|
523
|
-
|
|
524
|
-
Characteristic value where reading and/or writing is delegated to functions
|
|
525
|
-
passed as arguments to the constructor.
|
|
526
|
-
'''
|
|
527
|
-
|
|
528
|
-
def __init__(self, read=None, write=None):
|
|
529
|
-
self._read = read
|
|
530
|
-
self._write = write
|
|
531
|
-
|
|
532
|
-
def read(self, connection):
|
|
533
|
-
return self._read(connection) if self._read else b''
|
|
534
|
-
|
|
535
|
-
def write(self, connection, value):
|
|
536
|
-
if self._write:
|
|
537
|
-
self._write(connection, value)
|
|
537
|
+
class CharacteristicValue(AttributeValue):
|
|
538
|
+
"""Same as AttributeValue, for backward compatibility"""
|
|
538
539
|
|
|
539
540
|
|
|
540
541
|
# -----------------------------------------------------------------------------
|
|
541
542
|
class CharacteristicAdapter:
|
|
542
543
|
'''
|
|
543
|
-
An adapter that can adapt
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
`
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
544
|
+
An adapter that can adapt Characteristic and AttributeProxy objects
|
|
545
|
+
by wrapping their `read_value()` and `write_value()` methods with ones that
|
|
546
|
+
return/accept encoded/decoded values.
|
|
547
|
+
|
|
548
|
+
For proxies (i.e used by a GATT client), the adaptation is one where the return
|
|
549
|
+
value of `read_value()` is decoded and the value passed to `write_value()` is
|
|
550
|
+
encoded. The `subscribe()` method, is wrapped with one where the values are decoded
|
|
551
|
+
before being passed to the subscriber.
|
|
552
|
+
|
|
553
|
+
For local values (i.e hosted by a GATT server) the adaptation is one where the
|
|
554
|
+
return value of `read_value()` is encoded and the value passed to `write_value()`
|
|
555
|
+
is decoded.
|
|
553
556
|
'''
|
|
554
557
|
|
|
555
|
-
|
|
558
|
+
read_value: Callable
|
|
559
|
+
write_value: Callable
|
|
560
|
+
|
|
561
|
+
def __init__(self, characteristic: Union[Characteristic, AttributeProxy]):
|
|
556
562
|
self.wrapped_characteristic = characteristic
|
|
557
|
-
self.subscribers
|
|
563
|
+
self.subscribers: Dict[
|
|
564
|
+
Callable, Callable
|
|
565
|
+
] = {} # Map from subscriber to proxy subscriber
|
|
558
566
|
|
|
559
|
-
if
|
|
560
|
-
characteristic.read_value
|
|
561
|
-
) and asyncio.iscoroutinefunction(characteristic.write_value):
|
|
562
|
-
self.read_value = self.read_decoded_value
|
|
563
|
-
self.write_value = self.write_decoded_value
|
|
564
|
-
else:
|
|
567
|
+
if isinstance(characteristic, Characteristic):
|
|
565
568
|
self.read_value = self.read_encoded_value
|
|
566
569
|
self.write_value = self.write_encoded_value
|
|
567
|
-
|
|
568
|
-
|
|
570
|
+
else:
|
|
571
|
+
self.read_value = self.read_decoded_value
|
|
572
|
+
self.write_value = self.write_decoded_value
|
|
569
573
|
self.subscribe = self.wrapped_subscribe
|
|
570
|
-
|
|
571
|
-
if hasattr(self.wrapped_characteristic, 'unsubscribe'):
|
|
572
574
|
self.unsubscribe = self.wrapped_unsubscribe
|
|
573
575
|
|
|
574
576
|
def __getattr__(self, name):
|
|
@@ -587,11 +589,13 @@ class CharacteristicAdapter:
|
|
|
587
589
|
else:
|
|
588
590
|
setattr(self.wrapped_characteristic, name, value)
|
|
589
591
|
|
|
590
|
-
def read_encoded_value(self, connection):
|
|
591
|
-
return self.encode_value(
|
|
592
|
+
async def read_encoded_value(self, connection):
|
|
593
|
+
return self.encode_value(
|
|
594
|
+
await self.wrapped_characteristic.read_value(connection)
|
|
595
|
+
)
|
|
592
596
|
|
|
593
|
-
def write_encoded_value(self, connection, value):
|
|
594
|
-
return self.wrapped_characteristic.write_value(
|
|
597
|
+
async def write_encoded_value(self, connection, value):
|
|
598
|
+
return await self.wrapped_characteristic.write_value(
|
|
595
599
|
connection, self.decode_value(value)
|
|
596
600
|
)
|
|
597
601
|
|
|
@@ -726,13 +730,24 @@ class Descriptor(Attribute):
|
|
|
726
730
|
'''
|
|
727
731
|
|
|
728
732
|
def __str__(self) -> str:
|
|
733
|
+
if isinstance(self.value, bytes):
|
|
734
|
+
value_str = self.value.hex()
|
|
735
|
+
elif isinstance(self.value, CharacteristicValue):
|
|
736
|
+
value = self.value.read(None)
|
|
737
|
+
if isinstance(value, bytes):
|
|
738
|
+
value_str = value.hex()
|
|
739
|
+
else:
|
|
740
|
+
value_str = '<async>'
|
|
741
|
+
else:
|
|
742
|
+
value_str = '<...>'
|
|
729
743
|
return (
|
|
730
744
|
f'Descriptor(handle=0x{self.handle:04X}, '
|
|
731
745
|
f'type={self.type}, '
|
|
732
|
-
f'value={
|
|
746
|
+
f'value={value_str})'
|
|
733
747
|
)
|
|
734
748
|
|
|
735
749
|
|
|
750
|
+
# -----------------------------------------------------------------------------
|
|
736
751
|
class ClientCharacteristicConfigurationBits(enum.IntFlag):
|
|
737
752
|
'''
|
|
738
753
|
See Vol 3, Part G - 3.3.3.3 - Table 3.11 Client Characteristic Configuration bit
|
bumble/gatt_server.py
CHANGED
|
@@ -31,9 +31,9 @@ import struct
|
|
|
31
31
|
from typing import List, Tuple, Optional, TypeVar, Type, Dict, Iterable, TYPE_CHECKING
|
|
32
32
|
from pyee import EventEmitter
|
|
33
33
|
|
|
34
|
-
from .colors import color
|
|
35
|
-
from .core import UUID
|
|
36
|
-
from .att import (
|
|
34
|
+
from bumble.colors import color
|
|
35
|
+
from bumble.core import UUID
|
|
36
|
+
from bumble.att import (
|
|
37
37
|
ATT_ATTRIBUTE_NOT_FOUND_ERROR,
|
|
38
38
|
ATT_ATTRIBUTE_NOT_LONG_ERROR,
|
|
39
39
|
ATT_CID,
|
|
@@ -60,7 +60,7 @@ from .att import (
|
|
|
60
60
|
ATT_Write_Response,
|
|
61
61
|
Attribute,
|
|
62
62
|
)
|
|
63
|
-
from .gatt import (
|
|
63
|
+
from bumble.gatt import (
|
|
64
64
|
GATT_CHARACTERISTIC_ATTRIBUTE_TYPE,
|
|
65
65
|
GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
|
|
66
66
|
GATT_MAX_ATTRIBUTE_VALUE_SIZE,
|
|
@@ -74,6 +74,7 @@ from .gatt import (
|
|
|
74
74
|
Descriptor,
|
|
75
75
|
Service,
|
|
76
76
|
)
|
|
77
|
+
from bumble.utils import AsyncRunner
|
|
77
78
|
|
|
78
79
|
if TYPE_CHECKING:
|
|
79
80
|
from bumble.device import Device, Connection
|
|
@@ -379,7 +380,7 @@ class Server(EventEmitter):
|
|
|
379
380
|
|
|
380
381
|
# Get or encode the value
|
|
381
382
|
value = (
|
|
382
|
-
attribute.read_value(connection)
|
|
383
|
+
await attribute.read_value(connection)
|
|
383
384
|
if value is None
|
|
384
385
|
else attribute.encode_value(value)
|
|
385
386
|
)
|
|
@@ -422,7 +423,7 @@ class Server(EventEmitter):
|
|
|
422
423
|
|
|
423
424
|
# Get or encode the value
|
|
424
425
|
value = (
|
|
425
|
-
attribute.read_value(connection)
|
|
426
|
+
await attribute.read_value(connection)
|
|
426
427
|
if value is None
|
|
427
428
|
else attribute.encode_value(value)
|
|
428
429
|
)
|
|
@@ -650,7 +651,8 @@ class Server(EventEmitter):
|
|
|
650
651
|
|
|
651
652
|
self.send_response(connection, response)
|
|
652
653
|
|
|
653
|
-
|
|
654
|
+
@AsyncRunner.run_in_task()
|
|
655
|
+
async def on_att_find_by_type_value_request(self, connection, request):
|
|
654
656
|
'''
|
|
655
657
|
See Bluetooth spec Vol 3, Part F - 3.4.3.3 Find By Type Value Request
|
|
656
658
|
'''
|
|
@@ -658,13 +660,13 @@ class Server(EventEmitter):
|
|
|
658
660
|
# Build list of returned attributes
|
|
659
661
|
pdu_space_available = connection.att_mtu - 2
|
|
660
662
|
attributes = []
|
|
661
|
-
for attribute in (
|
|
663
|
+
async for attribute in (
|
|
662
664
|
attribute
|
|
663
665
|
for attribute in self.attributes
|
|
664
666
|
if attribute.handle >= request.starting_handle
|
|
665
667
|
and attribute.handle <= request.ending_handle
|
|
666
668
|
and attribute.type == request.attribute_type
|
|
667
|
-
and attribute.read_value(connection) == request.attribute_value
|
|
669
|
+
and (await attribute.read_value(connection)) == request.attribute_value
|
|
668
670
|
and pdu_space_available >= 4
|
|
669
671
|
):
|
|
670
672
|
# TODO: check permissions
|
|
@@ -702,7 +704,8 @@ class Server(EventEmitter):
|
|
|
702
704
|
|
|
703
705
|
self.send_response(connection, response)
|
|
704
706
|
|
|
705
|
-
|
|
707
|
+
@AsyncRunner.run_in_task()
|
|
708
|
+
async def on_att_read_by_type_request(self, connection, request):
|
|
706
709
|
'''
|
|
707
710
|
See Bluetooth spec Vol 3, Part F - 3.4.4.1 Read By Type Request
|
|
708
711
|
'''
|
|
@@ -725,7 +728,7 @@ class Server(EventEmitter):
|
|
|
725
728
|
and pdu_space_available
|
|
726
729
|
):
|
|
727
730
|
try:
|
|
728
|
-
attribute_value = attribute.read_value(connection)
|
|
731
|
+
attribute_value = await attribute.read_value(connection)
|
|
729
732
|
except ATT_Error as error:
|
|
730
733
|
# If the first attribute is unreadable, return an error
|
|
731
734
|
# Otherwise return attributes up to this point
|
|
@@ -767,14 +770,15 @@ class Server(EventEmitter):
|
|
|
767
770
|
|
|
768
771
|
self.send_response(connection, response)
|
|
769
772
|
|
|
770
|
-
|
|
773
|
+
@AsyncRunner.run_in_task()
|
|
774
|
+
async def on_att_read_request(self, connection, request):
|
|
771
775
|
'''
|
|
772
776
|
See Bluetooth spec Vol 3, Part F - 3.4.4.3 Read Request
|
|
773
777
|
'''
|
|
774
778
|
|
|
775
779
|
if attribute := self.get_attribute(request.attribute_handle):
|
|
776
780
|
try:
|
|
777
|
-
value = attribute.read_value(connection)
|
|
781
|
+
value = await attribute.read_value(connection)
|
|
778
782
|
except ATT_Error as error:
|
|
779
783
|
response = ATT_Error_Response(
|
|
780
784
|
request_opcode_in_error=request.op_code,
|
|
@@ -792,14 +796,15 @@ class Server(EventEmitter):
|
|
|
792
796
|
)
|
|
793
797
|
self.send_response(connection, response)
|
|
794
798
|
|
|
795
|
-
|
|
799
|
+
@AsyncRunner.run_in_task()
|
|
800
|
+
async def on_att_read_blob_request(self, connection, request):
|
|
796
801
|
'''
|
|
797
802
|
See Bluetooth spec Vol 3, Part F - 3.4.4.5 Read Blob Request
|
|
798
803
|
'''
|
|
799
804
|
|
|
800
805
|
if attribute := self.get_attribute(request.attribute_handle):
|
|
801
806
|
try:
|
|
802
|
-
value = attribute.read_value(connection)
|
|
807
|
+
value = await attribute.read_value(connection)
|
|
803
808
|
except ATT_Error as error:
|
|
804
809
|
response = ATT_Error_Response(
|
|
805
810
|
request_opcode_in_error=request.op_code,
|
|
@@ -836,7 +841,8 @@ class Server(EventEmitter):
|
|
|
836
841
|
)
|
|
837
842
|
self.send_response(connection, response)
|
|
838
843
|
|
|
839
|
-
|
|
844
|
+
@AsyncRunner.run_in_task()
|
|
845
|
+
async def on_att_read_by_group_type_request(self, connection, request):
|
|
840
846
|
'''
|
|
841
847
|
See Bluetooth spec Vol 3, Part F - 3.4.4.9 Read by Group Type Request
|
|
842
848
|
'''
|
|
@@ -864,7 +870,7 @@ class Server(EventEmitter):
|
|
|
864
870
|
):
|
|
865
871
|
# No need to catch permission errors here, since these attributes
|
|
866
872
|
# must all be world-readable
|
|
867
|
-
attribute_value = attribute.read_value(connection)
|
|
873
|
+
attribute_value = await attribute.read_value(connection)
|
|
868
874
|
# Check the attribute value size
|
|
869
875
|
max_attribute_size = min(connection.att_mtu - 6, 251)
|
|
870
876
|
if len(attribute_value) > max_attribute_size:
|
|
@@ -903,7 +909,8 @@ class Server(EventEmitter):
|
|
|
903
909
|
|
|
904
910
|
self.send_response(connection, response)
|
|
905
911
|
|
|
906
|
-
|
|
912
|
+
@AsyncRunner.run_in_task()
|
|
913
|
+
async def on_att_write_request(self, connection, request):
|
|
907
914
|
'''
|
|
908
915
|
See Bluetooth spec Vol 3, Part F - 3.4.5.1 Write Request
|
|
909
916
|
'''
|
|
@@ -936,12 +943,13 @@ class Server(EventEmitter):
|
|
|
936
943
|
return
|
|
937
944
|
|
|
938
945
|
# Accept the value
|
|
939
|
-
attribute.write_value(connection, request.attribute_value)
|
|
946
|
+
await attribute.write_value(connection, request.attribute_value)
|
|
940
947
|
|
|
941
948
|
# Done
|
|
942
949
|
self.send_response(connection, ATT_Write_Response())
|
|
943
950
|
|
|
944
|
-
|
|
951
|
+
@AsyncRunner.run_in_task()
|
|
952
|
+
async def on_att_write_command(self, connection, request):
|
|
945
953
|
'''
|
|
946
954
|
See Bluetooth spec Vol 3, Part F - 3.4.5.3 Write Command
|
|
947
955
|
'''
|
|
@@ -959,9 +967,9 @@ class Server(EventEmitter):
|
|
|
959
967
|
|
|
960
968
|
# Accept the value
|
|
961
969
|
try:
|
|
962
|
-
attribute.write_value(connection, request.attribute_value)
|
|
970
|
+
await attribute.write_value(connection, request.attribute_value)
|
|
963
971
|
except Exception as error:
|
|
964
|
-
logger.
|
|
972
|
+
logger.exception(f'!!! ignoring exception: {error}')
|
|
965
973
|
|
|
966
974
|
def on_att_handle_value_confirmation(self, connection, _confirmation):
|
|
967
975
|
'''
|