bumble 0.0.179__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 +630 -134
- bumble/drivers/__init__.py +27 -31
- bumble/drivers/common.py +45 -0
- bumble/drivers/rtk.py +11 -4
- bumble/gatt.py +183 -58
- bumble/gatt_client.py +56 -20
- bumble/gatt_server.py +30 -22
- bumble/hci.py +227 -92
- bumble/helpers.py +81 -42
- bumble/hfp.py +37 -27
- bumble/hid.py +282 -61
- bumble/host.py +158 -93
- bumble/l2cap.py +11 -3
- bumble/profiles/asha_service.py +2 -2
- bumble/profiles/bap.py +1247 -0
- bumble/profiles/cap.py +52 -0
- bumble/profiles/csip.py +205 -0
- 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 +3 -2
- bumble/transport/hci_socket.py +1 -4
- bumble/transport/usb.py +1 -1
- bumble/utils.py +3 -6
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/METADATA +1 -1
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/RECORD +40 -35
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/LICENSE +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/WHEEL +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.181.dist-info}/top_level.txt +0 -0
bumble/gatt_client.py
CHANGED
|
@@ -38,6 +38,7 @@ from typing import (
|
|
|
38
38
|
Any,
|
|
39
39
|
Iterable,
|
|
40
40
|
Type,
|
|
41
|
+
Set,
|
|
41
42
|
TYPE_CHECKING,
|
|
42
43
|
)
|
|
43
44
|
|
|
@@ -128,7 +129,7 @@ class ServiceProxy(AttributeProxy):
|
|
|
128
129
|
included_services: List[ServiceProxy]
|
|
129
130
|
|
|
130
131
|
@staticmethod
|
|
131
|
-
def from_client(service_class, client, service_uuid):
|
|
132
|
+
def from_client(service_class, client: Client, service_uuid: UUID):
|
|
132
133
|
# The service and its characteristics are considered to have already been
|
|
133
134
|
# discovered
|
|
134
135
|
services = client.get_services_by_uuid(service_uuid)
|
|
@@ -206,11 +207,11 @@ class CharacteristicProxy(AttributeProxy):
|
|
|
206
207
|
|
|
207
208
|
return await self.client.subscribe(self, subscriber, prefer_notify)
|
|
208
209
|
|
|
209
|
-
async def unsubscribe(self, subscriber=None):
|
|
210
|
+
async def unsubscribe(self, subscriber=None, force=False):
|
|
210
211
|
if subscriber in self.subscribers:
|
|
211
212
|
subscriber = self.subscribers.pop(subscriber)
|
|
212
213
|
|
|
213
|
-
return await self.client.unsubscribe(self, subscriber)
|
|
214
|
+
return await self.client.unsubscribe(self, subscriber, force)
|
|
214
215
|
|
|
215
216
|
def __str__(self) -> str:
|
|
216
217
|
return (
|
|
@@ -246,8 +247,12 @@ class ProfileServiceProxy:
|
|
|
246
247
|
class Client:
|
|
247
248
|
services: List[ServiceProxy]
|
|
248
249
|
cached_values: Dict[int, Tuple[datetime, bytes]]
|
|
249
|
-
notification_subscribers: Dict[
|
|
250
|
-
|
|
250
|
+
notification_subscribers: Dict[
|
|
251
|
+
int, Set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
|
|
252
|
+
]
|
|
253
|
+
indication_subscribers: Dict[
|
|
254
|
+
int, Set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
|
|
255
|
+
]
|
|
251
256
|
pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
|
|
252
257
|
pending_request: Optional[ATT_PDU]
|
|
253
258
|
|
|
@@ -257,10 +262,8 @@ class Client:
|
|
|
257
262
|
self.request_semaphore = asyncio.Semaphore(1)
|
|
258
263
|
self.pending_request = None
|
|
259
264
|
self.pending_response = None
|
|
260
|
-
self.notification_subscribers =
|
|
261
|
-
|
|
262
|
-
) # Notification subscribers, by attribute handle
|
|
263
|
-
self.indication_subscribers = {} # Indication subscribers, by attribute handle
|
|
265
|
+
self.notification_subscribers = {} # Subscriber set, by attribute handle
|
|
266
|
+
self.indication_subscribers = {} # Subscriber set, by attribute handle
|
|
264
267
|
self.services = []
|
|
265
268
|
self.cached_values = {}
|
|
266
269
|
|
|
@@ -682,8 +685,8 @@ class Client:
|
|
|
682
685
|
async def discover_descriptors(
|
|
683
686
|
self,
|
|
684
687
|
characteristic: Optional[CharacteristicProxy] = None,
|
|
685
|
-
start_handle=None,
|
|
686
|
-
end_handle=None,
|
|
688
|
+
start_handle: Optional[int] = None,
|
|
689
|
+
end_handle: Optional[int] = None,
|
|
687
690
|
) -> List[DescriptorProxy]:
|
|
688
691
|
'''
|
|
689
692
|
See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
|
|
@@ -789,7 +792,12 @@ class Client:
|
|
|
789
792
|
|
|
790
793
|
return attributes
|
|
791
794
|
|
|
792
|
-
async def subscribe(
|
|
795
|
+
async def subscribe(
|
|
796
|
+
self,
|
|
797
|
+
characteristic: CharacteristicProxy,
|
|
798
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
799
|
+
prefer_notify: bool = True,
|
|
800
|
+
) -> None:
|
|
793
801
|
# If we haven't already discovered the descriptors for this characteristic,
|
|
794
802
|
# do it now
|
|
795
803
|
if not characteristic.descriptors_discovered:
|
|
@@ -826,6 +834,7 @@ class Client:
|
|
|
826
834
|
subscriber_set = subscribers.setdefault(characteristic.handle, set())
|
|
827
835
|
if subscriber is not None:
|
|
828
836
|
subscriber_set.add(subscriber)
|
|
837
|
+
|
|
829
838
|
# Add the characteristic as a subscriber, which will result in the
|
|
830
839
|
# characteristic emitting an 'update' event when a notification or indication
|
|
831
840
|
# is received
|
|
@@ -833,7 +842,18 @@ class Client:
|
|
|
833
842
|
|
|
834
843
|
await self.write_value(cccd, struct.pack('<H', bits), with_response=True)
|
|
835
844
|
|
|
836
|
-
async def unsubscribe(
|
|
845
|
+
async def unsubscribe(
|
|
846
|
+
self,
|
|
847
|
+
characteristic: CharacteristicProxy,
|
|
848
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
849
|
+
force: bool = False,
|
|
850
|
+
) -> None:
|
|
851
|
+
'''
|
|
852
|
+
Unsubscribe from a characteristic.
|
|
853
|
+
|
|
854
|
+
If `force` is True, this will write zeros to the CCCD when there are no
|
|
855
|
+
subscribers left, even if there were already no registered subscribers.
|
|
856
|
+
'''
|
|
837
857
|
# If we haven't already discovered the descriptors for this characteristic,
|
|
838
858
|
# do it now
|
|
839
859
|
if not characteristic.descriptors_discovered:
|
|
@@ -847,31 +867,45 @@ class Client:
|
|
|
847
867
|
logger.warning('unsubscribing from characteristic with no CCCD descriptor')
|
|
848
868
|
return
|
|
849
869
|
|
|
870
|
+
# Check if the characteristic has subscribers
|
|
871
|
+
if not (
|
|
872
|
+
characteristic.handle in self.notification_subscribers
|
|
873
|
+
or characteristic.handle in self.indication_subscribers
|
|
874
|
+
):
|
|
875
|
+
if not force:
|
|
876
|
+
return
|
|
877
|
+
|
|
878
|
+
# Remove the subscriber(s)
|
|
850
879
|
if subscriber is not None:
|
|
851
880
|
# Remove matching subscriber from subscriber sets
|
|
852
881
|
for subscriber_set in (
|
|
853
882
|
self.notification_subscribers,
|
|
854
883
|
self.indication_subscribers,
|
|
855
884
|
):
|
|
856
|
-
|
|
857
|
-
|
|
885
|
+
if (
|
|
886
|
+
subscribers := subscriber_set.get(characteristic.handle)
|
|
887
|
+
) and subscriber in subscribers:
|
|
858
888
|
subscribers.remove(subscriber)
|
|
859
889
|
|
|
860
890
|
# Cleanup if we removed the last one
|
|
861
891
|
if not subscribers:
|
|
862
892
|
del subscriber_set[characteristic.handle]
|
|
863
893
|
else:
|
|
864
|
-
# Remove all subscribers for this attribute from the sets
|
|
894
|
+
# Remove all subscribers for this attribute from the sets
|
|
865
895
|
self.notification_subscribers.pop(characteristic.handle, None)
|
|
866
896
|
self.indication_subscribers.pop(characteristic.handle, None)
|
|
867
897
|
|
|
868
|
-
|
|
898
|
+
# Update the CCCD
|
|
899
|
+
if not (
|
|
900
|
+
characteristic.handle in self.notification_subscribers
|
|
901
|
+
or characteristic.handle in self.indication_subscribers
|
|
902
|
+
):
|
|
869
903
|
# No more subscribers left
|
|
870
904
|
await self.write_value(cccd, b'\x00\x00', with_response=True)
|
|
871
905
|
|
|
872
906
|
async def read_value(
|
|
873
907
|
self, attribute: Union[int, AttributeProxy], no_long_read: bool = False
|
|
874
|
-
) ->
|
|
908
|
+
) -> bytes:
|
|
875
909
|
'''
|
|
876
910
|
See Vol 3, Part G - 4.8.1 Read Characteristic Value
|
|
877
911
|
|
|
@@ -1067,7 +1101,7 @@ class Client:
|
|
|
1067
1101
|
def on_att_handle_value_notification(self, notification):
|
|
1068
1102
|
# Call all subscribers
|
|
1069
1103
|
subscribers = self.notification_subscribers.get(
|
|
1070
|
-
notification.attribute_handle,
|
|
1104
|
+
notification.attribute_handle, set()
|
|
1071
1105
|
)
|
|
1072
1106
|
if not subscribers:
|
|
1073
1107
|
logger.warning('!!! received notification with no subscriber')
|
|
@@ -1081,7 +1115,9 @@ class Client:
|
|
|
1081
1115
|
|
|
1082
1116
|
def on_att_handle_value_indication(self, indication):
|
|
1083
1117
|
# Call all subscribers
|
|
1084
|
-
subscribers = self.indication_subscribers.get(
|
|
1118
|
+
subscribers = self.indication_subscribers.get(
|
|
1119
|
+
indication.attribute_handle, set()
|
|
1120
|
+
)
|
|
1085
1121
|
if not subscribers:
|
|
1086
1122
|
logger.warning('!!! received indication with no subscriber')
|
|
1087
1123
|
|
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
|
'''
|