bumble 0.0.179__py3-none-any.whl → 0.0.180__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/device.py +64 -23
- bumble/gatt.py +117 -7
- bumble/gatt_client.py +56 -20
- bumble/hci.py +4 -0
- bumble/helpers.py +67 -42
- bumble/l2cap.py +8 -0
- bumble/profiles/csip.py +147 -0
- bumble/transport/common.py +1 -1
- {bumble-0.0.179.dist-info → bumble-0.0.180.dist-info}/METADATA +1 -1
- {bumble-0.0.179.dist-info → bumble-0.0.180.dist-info}/RECORD +15 -14
- {bumble-0.0.179.dist-info → bumble-0.0.180.dist-info}/LICENSE +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.180.dist-info}/WHEEL +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.180.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.179.dist-info → bumble-0.0.180.dist-info}/top_level.txt +0 -0
bumble/_version.py
CHANGED
bumble/device.py
CHANGED
|
@@ -23,6 +23,7 @@ import asyncio
|
|
|
23
23
|
import logging
|
|
24
24
|
from contextlib import asynccontextmanager, AsyncExitStack
|
|
25
25
|
from dataclasses import dataclass
|
|
26
|
+
from collections.abc import Iterable
|
|
26
27
|
from typing import (
|
|
27
28
|
Any,
|
|
28
29
|
Callable,
|
|
@@ -32,6 +33,7 @@ from typing import (
|
|
|
32
33
|
Optional,
|
|
33
34
|
Tuple,
|
|
34
35
|
Type,
|
|
36
|
+
TypeVar,
|
|
35
37
|
Set,
|
|
36
38
|
Union,
|
|
37
39
|
cast,
|
|
@@ -440,8 +442,11 @@ class LePhyOptions:
|
|
|
440
442
|
|
|
441
443
|
|
|
442
444
|
# -----------------------------------------------------------------------------
|
|
445
|
+
_PROXY_CLASS = TypeVar('_PROXY_CLASS', bound=gatt_client.ProfileServiceProxy)
|
|
446
|
+
|
|
447
|
+
|
|
443
448
|
class Peer:
|
|
444
|
-
def __init__(self, connection):
|
|
449
|
+
def __init__(self, connection: Connection) -> None:
|
|
445
450
|
self.connection = connection
|
|
446
451
|
|
|
447
452
|
# Create a GATT client for the connection
|
|
@@ -449,77 +454,113 @@ class Peer:
|
|
|
449
454
|
connection.gatt_client = self.gatt_client
|
|
450
455
|
|
|
451
456
|
@property
|
|
452
|
-
def services(self):
|
|
457
|
+
def services(self) -> List[gatt_client.ServiceProxy]:
|
|
453
458
|
return self.gatt_client.services
|
|
454
459
|
|
|
455
|
-
async def request_mtu(self, mtu):
|
|
460
|
+
async def request_mtu(self, mtu: int) -> int:
|
|
456
461
|
mtu = await self.gatt_client.request_mtu(mtu)
|
|
457
462
|
self.connection.emit('connection_att_mtu_update')
|
|
458
463
|
return mtu
|
|
459
464
|
|
|
460
|
-
async def discover_service(
|
|
465
|
+
async def discover_service(
|
|
466
|
+
self, uuid: Union[core.UUID, str]
|
|
467
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
461
468
|
return await self.gatt_client.discover_service(uuid)
|
|
462
469
|
|
|
463
|
-
async def discover_services(
|
|
470
|
+
async def discover_services(
|
|
471
|
+
self, uuids: Iterable[core.UUID] = ()
|
|
472
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
464
473
|
return await self.gatt_client.discover_services(uuids)
|
|
465
474
|
|
|
466
|
-
async def discover_included_services(
|
|
475
|
+
async def discover_included_services(
|
|
476
|
+
self, service: gatt_client.ServiceProxy
|
|
477
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
467
478
|
return await self.gatt_client.discover_included_services(service)
|
|
468
479
|
|
|
469
|
-
async def discover_characteristics(
|
|
480
|
+
async def discover_characteristics(
|
|
481
|
+
self,
|
|
482
|
+
uuids: Iterable[Union[core.UUID, str]] = (),
|
|
483
|
+
service: Optional[gatt_client.ServiceProxy] = None,
|
|
484
|
+
) -> List[gatt_client.CharacteristicProxy]:
|
|
470
485
|
return await self.gatt_client.discover_characteristics(
|
|
471
486
|
uuids=uuids, service=service
|
|
472
487
|
)
|
|
473
488
|
|
|
474
489
|
async def discover_descriptors(
|
|
475
|
-
self,
|
|
490
|
+
self,
|
|
491
|
+
characteristic: Optional[gatt_client.CharacteristicProxy] = None,
|
|
492
|
+
start_handle: Optional[int] = None,
|
|
493
|
+
end_handle: Optional[int] = None,
|
|
476
494
|
):
|
|
477
495
|
return await self.gatt_client.discover_descriptors(
|
|
478
496
|
characteristic, start_handle, end_handle
|
|
479
497
|
)
|
|
480
498
|
|
|
481
|
-
async def discover_attributes(self):
|
|
499
|
+
async def discover_attributes(self) -> List[gatt_client.AttributeProxy]:
|
|
482
500
|
return await self.gatt_client.discover_attributes()
|
|
483
501
|
|
|
484
|
-
async def subscribe(
|
|
502
|
+
async def subscribe(
|
|
503
|
+
self,
|
|
504
|
+
characteristic: gatt_client.CharacteristicProxy,
|
|
505
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
506
|
+
prefer_notify: bool = True,
|
|
507
|
+
) -> None:
|
|
485
508
|
return await self.gatt_client.subscribe(
|
|
486
509
|
characteristic, subscriber, prefer_notify
|
|
487
510
|
)
|
|
488
511
|
|
|
489
|
-
async def unsubscribe(
|
|
512
|
+
async def unsubscribe(
|
|
513
|
+
self,
|
|
514
|
+
characteristic: gatt_client.CharacteristicProxy,
|
|
515
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
516
|
+
) -> None:
|
|
490
517
|
return await self.gatt_client.unsubscribe(characteristic, subscriber)
|
|
491
518
|
|
|
492
|
-
async def read_value(
|
|
519
|
+
async def read_value(
|
|
520
|
+
self, attribute: Union[int, gatt_client.AttributeProxy]
|
|
521
|
+
) -> bytes:
|
|
493
522
|
return await self.gatt_client.read_value(attribute)
|
|
494
523
|
|
|
495
|
-
async def write_value(
|
|
524
|
+
async def write_value(
|
|
525
|
+
self,
|
|
526
|
+
attribute: Union[int, gatt_client.AttributeProxy],
|
|
527
|
+
value: bytes,
|
|
528
|
+
with_response: bool = False,
|
|
529
|
+
) -> None:
|
|
496
530
|
return await self.gatt_client.write_value(attribute, value, with_response)
|
|
497
531
|
|
|
498
|
-
async def read_characteristics_by_uuid(
|
|
532
|
+
async def read_characteristics_by_uuid(
|
|
533
|
+
self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None
|
|
534
|
+
) -> List[bytes]:
|
|
499
535
|
return await self.gatt_client.read_characteristics_by_uuid(uuid, service)
|
|
500
536
|
|
|
501
|
-
def get_services_by_uuid(self, uuid):
|
|
537
|
+
def get_services_by_uuid(self, uuid: core.UUID) -> List[gatt_client.ServiceProxy]:
|
|
502
538
|
return self.gatt_client.get_services_by_uuid(uuid)
|
|
503
539
|
|
|
504
|
-
def get_characteristics_by_uuid(
|
|
540
|
+
def get_characteristics_by_uuid(
|
|
541
|
+
self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None
|
|
542
|
+
) -> List[gatt_client.CharacteristicProxy]:
|
|
505
543
|
return self.gatt_client.get_characteristics_by_uuid(uuid, service)
|
|
506
544
|
|
|
507
|
-
def create_service_proxy(self, proxy_class):
|
|
508
|
-
return proxy_class.from_client(self.gatt_client)
|
|
545
|
+
def create_service_proxy(self, proxy_class: Type[_PROXY_CLASS]) -> _PROXY_CLASS:
|
|
546
|
+
return cast(_PROXY_CLASS, proxy_class.from_client(self.gatt_client))
|
|
509
547
|
|
|
510
|
-
async def discover_service_and_create_proxy(
|
|
548
|
+
async def discover_service_and_create_proxy(
|
|
549
|
+
self, proxy_class: Type[_PROXY_CLASS]
|
|
550
|
+
) -> Optional[_PROXY_CLASS]:
|
|
511
551
|
# Discover the first matching service and its characteristics
|
|
512
552
|
services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID)
|
|
513
553
|
if services:
|
|
514
554
|
service = services[0]
|
|
515
555
|
await service.discover_characteristics()
|
|
516
556
|
return self.create_service_proxy(proxy_class)
|
|
557
|
+
return None
|
|
517
558
|
|
|
518
|
-
async def sustain(self, timeout=None):
|
|
559
|
+
async def sustain(self, timeout: Optional[float] = None) -> None:
|
|
519
560
|
await self.connection.sustain(timeout)
|
|
520
561
|
|
|
521
562
|
# [Classic only]
|
|
522
|
-
async def request_name(self):
|
|
563
|
+
async def request_name(self) -> str:
|
|
523
564
|
return await self.connection.request_remote_name()
|
|
524
565
|
|
|
525
566
|
async def __aenter__(self):
|
|
@@ -532,7 +573,7 @@ class Peer:
|
|
|
532
573
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
533
574
|
pass
|
|
534
575
|
|
|
535
|
-
def __str__(self):
|
|
576
|
+
def __str__(self) -> str:
|
|
536
577
|
return f'{self.connection.peer_address} as {self.connection.role_name}'
|
|
537
578
|
|
|
538
579
|
|
|
@@ -732,7 +773,7 @@ class Connection(CompositeEventEmitter):
|
|
|
732
773
|
async def switch_role(self, role: int) -> None:
|
|
733
774
|
return await self.device.switch_role(self, role)
|
|
734
775
|
|
|
735
|
-
async def sustain(self, timeout=None):
|
|
776
|
+
async def sustain(self, timeout: Optional[float] = None) -> None:
|
|
736
777
|
"""Idles the current task waiting for a disconnect or timeout"""
|
|
737
778
|
|
|
738
779
|
abort = asyncio.get_running_loop().create_future()
|
bumble/gatt.py
CHANGED
|
@@ -93,20 +93,35 @@ GATT_RECONNECTION_CONFIGURATION_SERVICE = UUID.from_16_bits(0x1829, 'Reconne
|
|
|
93
93
|
GATT_INSULIN_DELIVERY_SERVICE = UUID.from_16_bits(0x183A, 'Insulin Delivery')
|
|
94
94
|
GATT_BINARY_SENSOR_SERVICE = UUID.from_16_bits(0x183B, 'Binary Sensor')
|
|
95
95
|
GATT_EMERGENCY_CONFIGURATION_SERVICE = UUID.from_16_bits(0x183C, 'Emergency Configuration')
|
|
96
|
+
GATT_AUTHORIZATION_CONTROL_SERVICE = UUID.from_16_bits(0x183D, 'Authorization Control')
|
|
96
97
|
GATT_PHYSICAL_ACTIVITY_MONITOR_SERVICE = UUID.from_16_bits(0x183E, 'Physical Activity Monitor')
|
|
98
|
+
GATT_ELAPSED_TIME_SERVICE = UUID.from_16_bits(0x183F, 'Elapsed Time')
|
|
99
|
+
GATT_GENERIC_HEALTH_SENSOR_SERVICE = UUID.from_16_bits(0x1840, 'Generic Health Sensor')
|
|
97
100
|
GATT_AUDIO_INPUT_CONTROL_SERVICE = UUID.from_16_bits(0x1843, 'Audio Input Control')
|
|
98
101
|
GATT_VOLUME_CONTROL_SERVICE = UUID.from_16_bits(0x1844, 'Volume Control')
|
|
99
102
|
GATT_VOLUME_OFFSET_CONTROL_SERVICE = UUID.from_16_bits(0x1845, 'Volume Offset Control')
|
|
100
|
-
GATT_COORDINATED_SET_IDENTIFICATION_SERVICE = UUID.from_16_bits(0x1846, 'Coordinated Set Identification
|
|
103
|
+
GATT_COORDINATED_SET_IDENTIFICATION_SERVICE = UUID.from_16_bits(0x1846, 'Coordinated Set Identification')
|
|
101
104
|
GATT_DEVICE_TIME_SERVICE = UUID.from_16_bits(0x1847, 'Device Time')
|
|
102
|
-
GATT_MEDIA_CONTROL_SERVICE = UUID.from_16_bits(0x1848, 'Media Control
|
|
103
|
-
GATT_GENERIC_MEDIA_CONTROL_SERVICE = UUID.from_16_bits(0x1849, 'Generic Media Control
|
|
105
|
+
GATT_MEDIA_CONTROL_SERVICE = UUID.from_16_bits(0x1848, 'Media Control')
|
|
106
|
+
GATT_GENERIC_MEDIA_CONTROL_SERVICE = UUID.from_16_bits(0x1849, 'Generic Media Control')
|
|
104
107
|
GATT_CONSTANT_TONE_EXTENSION_SERVICE = UUID.from_16_bits(0x184A, 'Constant Tone Extension')
|
|
105
|
-
GATT_TELEPHONE_BEARER_SERVICE = UUID.from_16_bits(0x184B, 'Telephone Bearer
|
|
106
|
-
GATT_GENERIC_TELEPHONE_BEARER_SERVICE = UUID.from_16_bits(0x184C, 'Generic Telephone Bearer
|
|
108
|
+
GATT_TELEPHONE_BEARER_SERVICE = UUID.from_16_bits(0x184B, 'Telephone Bearer')
|
|
109
|
+
GATT_GENERIC_TELEPHONE_BEARER_SERVICE = UUID.from_16_bits(0x184C, 'Generic Telephone Bearer')
|
|
107
110
|
GATT_MICROPHONE_CONTROL_SERVICE = UUID.from_16_bits(0x184D, 'Microphone Control')
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
GATT_AUDIO_STREAM_CONTROL_SERVICE = UUID.from_16_bits(0x184E, 'Audio Stream Control')
|
|
112
|
+
GATT_BROADCAST_AUDIO_SCAN_SERVICE = UUID.from_16_bits(0x184F, 'Broadcast Audio Scan')
|
|
113
|
+
GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE = UUID.from_16_bits(0x1850, 'Published Audio Capabilities')
|
|
114
|
+
GATT_BASIC_AUDIO_ANNOUNCEMENT_SERVICE = UUID.from_16_bits(0x1851, 'Basic Audio Announcement')
|
|
115
|
+
GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE = UUID.from_16_bits(0x1852, 'Broadcast Audio Announcement')
|
|
116
|
+
GATT_COMMON_AUDIO_SERVICE = UUID.from_16_bits(0x1853, 'Common Audio')
|
|
117
|
+
GATT_HEARING_ACCESS_SERVICE = UUID.from_16_bits(0x1854, 'Hearing Access')
|
|
118
|
+
GATT_TELEPHONY_AND_MEDIA_AUDIO_SERVICE = UUID.from_16_bits(0x1855, 'Telephony and Media Audio')
|
|
119
|
+
GATT_PUBLIC_BROADCAST_ANNOUNCEMENT_SERVICE = UUID.from_16_bits(0x1856, 'Public Broadcast Announcement')
|
|
120
|
+
GATT_ELECTRONIC_SHELF_LABEL_SERVICE = UUID.from_16_bits(0X1857, 'Electronic Shelf Label')
|
|
121
|
+
GATT_GAMING_AUDIO_SERVICE = UUID.from_16_bits(0x1858, 'Gaming Audio')
|
|
122
|
+
GATT_MESH_PROXY_SOLICITATION_SERVICE = UUID.from_16_bits(0x1859, 'Mesh Audio Solicitation')
|
|
123
|
+
|
|
124
|
+
# Attribute Types
|
|
110
125
|
GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE = UUID.from_16_bits(0x2800, 'Primary Service')
|
|
111
126
|
GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE = UUID.from_16_bits(0x2801, 'Secondary Service')
|
|
112
127
|
GATT_INCLUDE_ATTRIBUTE_TYPE = UUID.from_16_bits(0x2802, 'Include')
|
|
@@ -129,6 +144,8 @@ GATT_ENVIRONMENTAL_SENSING_MEASUREMENT_DESCRIPTOR = UUID.from_16_bits(0x290C,
|
|
|
129
144
|
GATT_ENVIRONMENTAL_SENSING_TRIGGER_DESCRIPTOR = UUID.from_16_bits(0x290D, 'Environmental Sensing Trigger Setting')
|
|
130
145
|
GATT_TIME_TRIGGER_DESCRIPTOR = UUID.from_16_bits(0x290E, 'Time Trigger Setting')
|
|
131
146
|
GATT_COMPLETE_BR_EDR_TRANSPORT_BLOCK_DATA_DESCRIPTOR = UUID.from_16_bits(0x290F, 'Complete BR-EDR Transport Block Data')
|
|
147
|
+
GATT_OBSERVATION_SCHEDULE_DESCRIPTOR = UUID.from_16_bits(0x290F, 'Observation Schedule')
|
|
148
|
+
GATT_VALID_RANGE_AND_ACCURACY_DESCRIPTOR = UUID.from_16_bits(0x290F, 'Valid Range And Accuracy')
|
|
132
149
|
|
|
133
150
|
# Device Information Service
|
|
134
151
|
GATT_SYSTEM_ID_CHARACTERISTIC = UUID.from_16_bits(0x2A23, 'System ID')
|
|
@@ -156,6 +173,96 @@ GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2A39, 'Heart
|
|
|
156
173
|
# Battery Service
|
|
157
174
|
GATT_BATTERY_LEVEL_CHARACTERISTIC = UUID.from_16_bits(0x2A19, 'Battery Level')
|
|
158
175
|
|
|
176
|
+
# Telephony And Media Audio Service (TMAS)
|
|
177
|
+
GATT_TMAP_ROLE_CHARACTERISTIC = UUID.from_16_bits(0x2B51, 'TMAP Role')
|
|
178
|
+
|
|
179
|
+
# Audio Input Control Service (AICS)
|
|
180
|
+
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2B77, 'Audio Input State')
|
|
181
|
+
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC = UUID.from_16_bits(0x2B78, 'Gain Settings Attribute')
|
|
182
|
+
GATT_AUDIO_INPUT_TYPE_CHARACTERISTIC = UUID.from_16_bits(0x2B79, 'Audio Input Type')
|
|
183
|
+
GATT_AUDIO_INPUT_STATUS_CHARACTERISTIC = UUID.from_16_bits(0x2B7A, 'Audio Input Status')
|
|
184
|
+
GATT_AUDIO_INPUT_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2B7B, 'Audio Input Control Point')
|
|
185
|
+
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC = UUID.from_16_bits(0x2B7C, 'Audio Input Description')
|
|
186
|
+
|
|
187
|
+
# Volume Control Service (VCS)
|
|
188
|
+
GATT_VOLUME_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2B7D, 'Volume State')
|
|
189
|
+
GATT_VOLUME_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2B7E, 'Volume Control Point')
|
|
190
|
+
GATT_VOLUME_FLAGS_CHARACTERISTIC = UUID.from_16_bits(0x2B7F, 'Volume Flags')
|
|
191
|
+
|
|
192
|
+
# Volume Offset Control Service (VOCS)
|
|
193
|
+
GATT_VOLUME_OFFSET_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2B80, 'Volume Offset State')
|
|
194
|
+
GATT_AUDIO_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2B81, 'Audio Location')
|
|
195
|
+
GATT_VOLUME_OFFSET_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2B82, 'Volume Offset Control Point')
|
|
196
|
+
GATT_AUDIO_OUTPUT_DESCRIPTION_CHARACTERISTIC = UUID.from_16_bits(0x2B83, 'Audio Output Description')
|
|
197
|
+
|
|
198
|
+
# Coordinated Set Identification Service (CSIS)
|
|
199
|
+
GATT_SET_IDENTITY_RESOLVING_KEY_CHARACTERISTIC = UUID.from_16_bits(0x2B84, 'Set Identity Resolving Key')
|
|
200
|
+
GATT_COORDINATED_SET_SIZE_CHARACTERISTIC = UUID.from_16_bits(0x2B85, 'Coordinated Set Size')
|
|
201
|
+
GATT_SET_MEMBER_LOCK_CHARACTERISTIC = UUID.from_16_bits(0x2B86, 'Set Member Lock')
|
|
202
|
+
GATT_SET_MEMBER_RANK_CHARACTERISTIC = UUID.from_16_bits(0x2B87, 'Set Member Rank')
|
|
203
|
+
|
|
204
|
+
# Media Control Service (MCS)
|
|
205
|
+
GATT_MEDIA_PLAYER_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2B93, 'Media Player Name')
|
|
206
|
+
GATT_MEDIA_PLAYER_ICON_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B94, 'Media Player Icon Object ID')
|
|
207
|
+
GATT_MEDIA_PLAYER_ICON_URL_CHARACTERISTIC = UUID.from_16_bits(0x2B95, 'Media Player Icon URL')
|
|
208
|
+
GATT_TRACK_CHANGED_CHARACTERISTIC = UUID.from_16_bits(0x2B96, 'Track Changed')
|
|
209
|
+
GATT_TRACK_TITLE_CHARACTERISTIC = UUID.from_16_bits(0x2B97, 'Track Title')
|
|
210
|
+
GATT_TRACK_DURATION_CHARACTERISTIC = UUID.from_16_bits(0x2B98, 'Track Duration')
|
|
211
|
+
GATT_TRACK_POSITION_CHARACTERISTIC = UUID.from_16_bits(0x2B99, 'Track Position')
|
|
212
|
+
GATT_PLAYBACK_SPEED_CHARACTERISTIC = UUID.from_16_bits(0x2B9A, 'Playback Speed')
|
|
213
|
+
GATT_SEEKING_SPEED_CHARACTERISTIC = UUID.from_16_bits(0x2B9B, 'Seeking Speed')
|
|
214
|
+
GATT_CURRENT_TRACK_SEGMENTS_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B9C, 'Current Track Segments Object ID')
|
|
215
|
+
GATT_CURRENT_TRACK_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B9D, 'Current Track Object ID')
|
|
216
|
+
GATT_NEXT_TRACK_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B9E, 'Next Track Object ID')
|
|
217
|
+
GATT_PARENT_GROUP_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B9F, 'Parent Group Object ID')
|
|
218
|
+
GATT_CURRENT_GROUP_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BA0, 'Current Group Object ID')
|
|
219
|
+
GATT_PLAYING_ORDER_CHARACTERISTIC = UUID.from_16_bits(0x2BA1, 'Playing Order')
|
|
220
|
+
GATT_PLAYING_ORDERS_SUPPORTED_CHARACTERISTIC = UUID.from_16_bits(0x2BA2, 'Playing Orders Supported')
|
|
221
|
+
GATT_MEDIA_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2BA3, 'Media State')
|
|
222
|
+
GATT_MEDIA_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BA4, 'Media Control Point')
|
|
223
|
+
GATT_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_CHARACTERISTIC = UUID.from_16_bits(0x2BA5, 'Media Control Point Opcodes Supported')
|
|
224
|
+
GATT_SEARCH_RESULTS_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BA6, 'Search Results Object ID')
|
|
225
|
+
GATT_SEARCH_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BA7, 'Search Control Point')
|
|
226
|
+
GATT_CONTENT_CONTROL_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BBA, 'Content Control Id')
|
|
227
|
+
|
|
228
|
+
# Telephone Bearer Service (TBS)
|
|
229
|
+
GATT_BEARER_PROVIDER_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2BB4, 'Bearer Provider Name')
|
|
230
|
+
GATT_BEARER_UCI_CHARACTERISTIC = UUID.from_16_bits(0x2BB5, 'Bearer UCI')
|
|
231
|
+
GATT_BEARER_TECHNOLOGY_CHARACTERISTIC = UUID.from_16_bits(0x2BB6, 'Bearer Technology')
|
|
232
|
+
GATT_BEARER_URI_SCHEMES_SUPPORTED_LIST_CHARACTERISTIC = UUID.from_16_bits(0x2BB7, 'Bearer URI Schemes Supported List')
|
|
233
|
+
GATT_BEARER_SIGNAL_STRENGTH_CHARACTERISTIC = UUID.from_16_bits(0x2BB8, 'Bearer Signal Strength')
|
|
234
|
+
GATT_BEARER_SIGNAL_STRENGTH_REPORTING_INTERVAL_CHARACTERISTIC = UUID.from_16_bits(0x2BB9, 'Bearer Signal Strength Reporting Interval')
|
|
235
|
+
GATT_BEARER_LIST_CURRENT_CALLS_CHARACTERISTIC = UUID.from_16_bits(0x2BBA, 'Bearer List Current Calls')
|
|
236
|
+
GATT_CONTENT_CONTROL_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BBB, 'Content Control ID')
|
|
237
|
+
GATT_STATUS_FLAGS_CHARACTERISTIC = UUID.from_16_bits(0x2BBC, 'Status Flags')
|
|
238
|
+
GATT_INCOMING_CALL_TARGET_BEARER_URI_CHARACTERISTIC = UUID.from_16_bits(0x2BBD, 'Incoming Call Target Bearer URI')
|
|
239
|
+
GATT_CALL_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2BBE, 'Call State')
|
|
240
|
+
GATT_CALL_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BBF, 'Call Control Point')
|
|
241
|
+
GATT_CALL_CONTROL_POINT_OPTIONAL_OPCODES_CHARACTERISTIC = UUID.from_16_bits(0x2BC0, 'Call Control Point Optional Opcodes')
|
|
242
|
+
GATT_TERMINATION_REASON_CHARACTERISTIC = UUID.from_16_bits(0x2BC1, 'Termination Reason')
|
|
243
|
+
GATT_INCOMING_CALL_CHARACTERISTIC = UUID.from_16_bits(0x2BC2, 'Incoming Call')
|
|
244
|
+
GATT_CALL_FRIENDLY_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2BC3, 'Call Friendly Name')
|
|
245
|
+
|
|
246
|
+
# Microphone Control Service (MICS)
|
|
247
|
+
GATT_MUTE_CHARACTERISTIC = UUID.from_16_bits(0x2BC3, 'Mute')
|
|
248
|
+
|
|
249
|
+
# Audio Stream Control Service (ASCS)
|
|
250
|
+
GATT_SINK_ASE_CHARACTERISTIC = UUID.from_16_bits(0x2BC4, 'Sink ASE')
|
|
251
|
+
GATT_SOURCE_ASE_CHARACTERISTIC = UUID.from_16_bits(0x2BC5, 'Source ASE')
|
|
252
|
+
GATT_ASE_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BC6, 'ASE Control Point')
|
|
253
|
+
|
|
254
|
+
# Broadcast Audio Scan Service (BASS)
|
|
255
|
+
GATT_BROADCAST_AUDIO_SCAN_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BC7, 'Broadcast Audio Scan Control Point')
|
|
256
|
+
GATT_BROADCAST_RECEIVE_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2BC8, 'Broadcast Receive State')
|
|
257
|
+
|
|
258
|
+
# Published Audio Capabilities Service (PACS)
|
|
259
|
+
GATT_SINK_PAC_CHARACTERISTIC = UUID.from_16_bits(0x2BC9, 'Sink PAC')
|
|
260
|
+
GATT_SINK_AUDIO_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2BCA, 'Sink Audio Location')
|
|
261
|
+
GATT_SOURCE_PAC_CHARACTERISTIC = UUID.from_16_bits(0x2BCB, 'Source PAC')
|
|
262
|
+
GATT_SOURCE_AUDIO_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2BCC, 'Source Audio Location')
|
|
263
|
+
GATT_AVAILABLE_AUDIO_CONTEXTS_CHARACTERISTIC = UUID.from_16_bits(0x2BCD, 'Available Audio Contexts')
|
|
264
|
+
GATT_SUPPORTED_AUDIO_CONTEXTS_CHARACTERISTIC = UUID.from_16_bits(0x2BCE, 'Supported Audio Contexts')
|
|
265
|
+
|
|
159
266
|
# ASHA Service
|
|
160
267
|
GATT_ASHA_SERVICE = UUID.from_16_bits(0xFDF0, 'Audio Streaming for Hearing Aid')
|
|
161
268
|
GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC = UUID('6333651e-c481-4a3e-9169-7c902aad37bb', 'ReadOnlyProperties')
|
|
@@ -177,6 +284,9 @@ GATT_BOOT_KEYBOARD_INPUT_REPORT_CHARACTERISTIC = UUID.from_16_bi
|
|
|
177
284
|
GATT_CURRENT_TIME_CHARACTERISTIC = UUID.from_16_bits(0x2A2B, 'Current Time')
|
|
178
285
|
GATT_BOOT_KEYBOARD_OUTPUT_REPORT_CHARACTERISTIC = UUID.from_16_bits(0x2A32, 'Boot Keyboard Output Report')
|
|
179
286
|
GATT_CENTRAL_ADDRESS_RESOLUTION__CHARACTERISTIC = UUID.from_16_bits(0x2AA6, 'Central Address Resolution')
|
|
287
|
+
GATT_CLIENT_SUPPORTED_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2B29, 'Client Supported Features')
|
|
288
|
+
GATT_DATABASE_HASH_CHARACTERISTIC = UUID.from_16_bits(0x2B2A, 'Database Hash')
|
|
289
|
+
GATT_SERVER_SUPPORTED_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2B3A, 'Server Supported Features')
|
|
180
290
|
|
|
181
291
|
# fmt: on
|
|
182
292
|
# pylint: enable=line-too-long
|
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/hci.py
CHANGED
|
@@ -5296,6 +5296,10 @@ class HCI_Disconnection_Complete_Event(HCI_Event):
|
|
|
5296
5296
|
See Bluetooth spec @ 7.7.5 Disconnection Complete Event
|
|
5297
5297
|
'''
|
|
5298
5298
|
|
|
5299
|
+
status: int
|
|
5300
|
+
connection_handle: int
|
|
5301
|
+
reason: int
|
|
5302
|
+
|
|
5299
5303
|
|
|
5300
5304
|
# -----------------------------------------------------------------------------
|
|
5301
5305
|
@HCI_Event.event([('status', STATUS_SPEC), ('connection_handle', 2)])
|
bumble/helpers.py
CHANGED
|
@@ -15,30 +15,39 @@
|
|
|
15
15
|
# -----------------------------------------------------------------------------
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections.abc import Callable, MutableMapping
|
|
21
|
+
from typing import cast, Any
|
|
18
22
|
import logging
|
|
19
23
|
|
|
20
|
-
from
|
|
21
|
-
from .
|
|
22
|
-
from .
|
|
23
|
-
from .
|
|
24
|
-
from .
|
|
24
|
+
from bumble import avdtp
|
|
25
|
+
from bumble.colors import color
|
|
26
|
+
from bumble.att import ATT_CID, ATT_PDU
|
|
27
|
+
from bumble.smp import SMP_CID, SMP_Command
|
|
28
|
+
from bumble.core import name_or_number
|
|
29
|
+
from bumble.l2cap import (
|
|
25
30
|
L2CAP_PDU,
|
|
26
31
|
L2CAP_CONNECTION_REQUEST,
|
|
27
32
|
L2CAP_CONNECTION_RESPONSE,
|
|
28
33
|
L2CAP_SIGNALING_CID,
|
|
29
34
|
L2CAP_LE_SIGNALING_CID,
|
|
30
35
|
L2CAP_Control_Frame,
|
|
36
|
+
L2CAP_Connection_Request,
|
|
31
37
|
L2CAP_Connection_Response,
|
|
32
38
|
)
|
|
33
|
-
from .hci import (
|
|
39
|
+
from bumble.hci import (
|
|
34
40
|
HCI_EVENT_PACKET,
|
|
35
41
|
HCI_ACL_DATA_PACKET,
|
|
36
42
|
HCI_DISCONNECTION_COMPLETE_EVENT,
|
|
37
43
|
HCI_AclDataPacketAssembler,
|
|
44
|
+
HCI_Packet,
|
|
45
|
+
HCI_Event,
|
|
46
|
+
HCI_AclDataPacket,
|
|
47
|
+
HCI_Disconnection_Complete_Event,
|
|
38
48
|
)
|
|
39
|
-
from .rfcomm import RFCOMM_Frame, RFCOMM_PSM
|
|
40
|
-
from .sdp import SDP_PDU, SDP_PSM
|
|
41
|
-
from .avdtp import MessageAssembler as AVDTP_MessageAssembler, AVDTP_PSM
|
|
49
|
+
from bumble.rfcomm import RFCOMM_Frame, RFCOMM_PSM
|
|
50
|
+
from bumble.sdp import SDP_PDU, SDP_PSM
|
|
42
51
|
|
|
43
52
|
# -----------------------------------------------------------------------------
|
|
44
53
|
# Logging
|
|
@@ -50,23 +59,25 @@ logger = logging.getLogger(__name__)
|
|
|
50
59
|
PSM_NAMES = {
|
|
51
60
|
RFCOMM_PSM: 'RFCOMM',
|
|
52
61
|
SDP_PSM: 'SDP',
|
|
53
|
-
AVDTP_PSM: 'AVDTP'
|
|
54
|
-
# TODO: add more PSM values
|
|
62
|
+
avdtp.AVDTP_PSM: 'AVDTP',
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
|
|
58
66
|
# -----------------------------------------------------------------------------
|
|
59
67
|
class PacketTracer:
|
|
60
68
|
class AclStream:
|
|
61
|
-
|
|
69
|
+
psms: MutableMapping[int, int]
|
|
70
|
+
peer: PacketTracer.AclStream
|
|
71
|
+
avdtp_assemblers: MutableMapping[int, avdtp.MessageAssembler]
|
|
72
|
+
|
|
73
|
+
def __init__(self, analyzer: PacketTracer.Analyzer) -> None:
|
|
62
74
|
self.analyzer = analyzer
|
|
63
75
|
self.packet_assembler = HCI_AclDataPacketAssembler(self.on_acl_pdu)
|
|
64
76
|
self.avdtp_assemblers = {} # AVDTP assemblers, by source_cid
|
|
65
77
|
self.psms = {} # PSM, by source_cid
|
|
66
|
-
self.peer = None # ACL stream in the other direction
|
|
67
78
|
|
|
68
79
|
# pylint: disable=too-many-nested-blocks
|
|
69
|
-
def on_acl_pdu(self, pdu):
|
|
80
|
+
def on_acl_pdu(self, pdu: bytes) -> None:
|
|
70
81
|
l2cap_pdu = L2CAP_PDU.from_bytes(pdu)
|
|
71
82
|
|
|
72
83
|
if l2cap_pdu.cid == ATT_CID:
|
|
@@ -81,26 +92,30 @@ class PacketTracer:
|
|
|
81
92
|
|
|
82
93
|
# Check if this signals a new channel
|
|
83
94
|
if control_frame.code == L2CAP_CONNECTION_REQUEST:
|
|
84
|
-
|
|
95
|
+
connection_request = cast(L2CAP_Connection_Request, control_frame)
|
|
96
|
+
self.psms[connection_request.source_cid] = connection_request.psm
|
|
85
97
|
elif control_frame.code == L2CAP_CONNECTION_RESPONSE:
|
|
98
|
+
connection_response = cast(L2CAP_Connection_Response, control_frame)
|
|
86
99
|
if (
|
|
87
|
-
|
|
100
|
+
connection_response.result
|
|
88
101
|
== L2CAP_Connection_Response.CONNECTION_SUCCESSFUL
|
|
89
102
|
):
|
|
90
103
|
if self.peer:
|
|
91
|
-
if psm := self.peer.psms.get(
|
|
104
|
+
if psm := self.peer.psms.get(
|
|
105
|
+
connection_response.source_cid
|
|
106
|
+
):
|
|
92
107
|
# Found a pending connection
|
|
93
|
-
self.psms[
|
|
108
|
+
self.psms[connection_response.destination_cid] = psm
|
|
94
109
|
|
|
95
110
|
# For AVDTP connections, create a packet assembler for
|
|
96
111
|
# each direction
|
|
97
|
-
if psm == AVDTP_PSM:
|
|
112
|
+
if psm == avdtp.AVDTP_PSM:
|
|
98
113
|
self.avdtp_assemblers[
|
|
99
|
-
|
|
100
|
-
] =
|
|
114
|
+
connection_response.source_cid
|
|
115
|
+
] = avdtp.MessageAssembler(self.on_avdtp_message)
|
|
101
116
|
self.peer.avdtp_assemblers[
|
|
102
|
-
|
|
103
|
-
] =
|
|
117
|
+
connection_response.destination_cid
|
|
118
|
+
] = avdtp.MessageAssembler(
|
|
104
119
|
self.peer.on_avdtp_message
|
|
105
120
|
)
|
|
106
121
|
|
|
@@ -113,7 +128,7 @@ class PacketTracer:
|
|
|
113
128
|
elif psm == RFCOMM_PSM:
|
|
114
129
|
rfcomm_frame = RFCOMM_Frame.from_bytes(l2cap_pdu.payload)
|
|
115
130
|
self.analyzer.emit(rfcomm_frame)
|
|
116
|
-
elif psm == AVDTP_PSM:
|
|
131
|
+
elif psm == avdtp.AVDTP_PSM:
|
|
117
132
|
self.analyzer.emit(
|
|
118
133
|
f'{color("L2CAP", "green")} [CID={l2cap_pdu.cid}, '
|
|
119
134
|
f'PSM=AVDTP]: {l2cap_pdu.payload.hex()}'
|
|
@@ -130,22 +145,26 @@ class PacketTracer:
|
|
|
130
145
|
else:
|
|
131
146
|
self.analyzer.emit(l2cap_pdu)
|
|
132
147
|
|
|
133
|
-
def on_avdtp_message(
|
|
148
|
+
def on_avdtp_message(
|
|
149
|
+
self, transaction_label: int, message: avdtp.Message
|
|
150
|
+
) -> None:
|
|
134
151
|
self.analyzer.emit(
|
|
135
152
|
f'{color("AVDTP", "green")} [{transaction_label}] {message}'
|
|
136
153
|
)
|
|
137
154
|
|
|
138
|
-
def feed_packet(self, packet):
|
|
155
|
+
def feed_packet(self, packet: HCI_AclDataPacket) -> None:
|
|
139
156
|
self.packet_assembler.feed_packet(packet)
|
|
140
157
|
|
|
141
158
|
class Analyzer:
|
|
142
|
-
|
|
159
|
+
acl_streams: MutableMapping[int, PacketTracer.AclStream]
|
|
160
|
+
peer: PacketTracer.Analyzer
|
|
161
|
+
|
|
162
|
+
def __init__(self, label: str, emit_message: Callable[..., None]) -> None:
|
|
143
163
|
self.label = label
|
|
144
164
|
self.emit_message = emit_message
|
|
145
165
|
self.acl_streams = {} # ACL streams, by connection handle
|
|
146
|
-
self.peer = None # Analyzer in the other direction
|
|
147
166
|
|
|
148
|
-
def start_acl_stream(self, connection_handle):
|
|
167
|
+
def start_acl_stream(self, connection_handle: int) -> PacketTracer.AclStream:
|
|
149
168
|
logger.info(
|
|
150
169
|
f'[{self.label}] +++ Creating ACL stream for connection '
|
|
151
170
|
f'0x{connection_handle:04X}'
|
|
@@ -160,7 +179,7 @@ class PacketTracer:
|
|
|
160
179
|
|
|
161
180
|
return stream
|
|
162
181
|
|
|
163
|
-
def end_acl_stream(self, connection_handle):
|
|
182
|
+
def end_acl_stream(self, connection_handle: int) -> None:
|
|
164
183
|
if connection_handle in self.acl_streams:
|
|
165
184
|
logger.info(
|
|
166
185
|
f'[{self.label}] --- Removing ACL stream for connection '
|
|
@@ -171,23 +190,29 @@ class PacketTracer:
|
|
|
171
190
|
# Let the other forwarder know so it can cleanup its stream as well
|
|
172
191
|
self.peer.end_acl_stream(connection_handle)
|
|
173
192
|
|
|
174
|
-
def on_packet(self, packet):
|
|
193
|
+
def on_packet(self, packet: HCI_Packet) -> None:
|
|
175
194
|
self.emit(packet)
|
|
176
195
|
|
|
177
196
|
if packet.hci_packet_type == HCI_ACL_DATA_PACKET:
|
|
197
|
+
acl_packet = cast(HCI_AclDataPacket, packet)
|
|
178
198
|
# Look for an existing stream for this handle, create one if it is the
|
|
179
199
|
# first ACL packet for that connection handle
|
|
180
|
-
if (
|
|
181
|
-
stream
|
|
182
|
-
|
|
200
|
+
if (
|
|
201
|
+
stream := self.acl_streams.get(acl_packet.connection_handle)
|
|
202
|
+
) is None:
|
|
203
|
+
stream = self.start_acl_stream(acl_packet.connection_handle)
|
|
204
|
+
stream.feed_packet(acl_packet)
|
|
183
205
|
elif packet.hci_packet_type == HCI_EVENT_PACKET:
|
|
184
|
-
|
|
185
|
-
|
|
206
|
+
event_packet = cast(HCI_Event, packet)
|
|
207
|
+
if event_packet.event_code == HCI_DISCONNECTION_COMPLETE_EVENT:
|
|
208
|
+
self.end_acl_stream(
|
|
209
|
+
cast(HCI_Disconnection_Complete_Event, packet).connection_handle
|
|
210
|
+
)
|
|
186
211
|
|
|
187
|
-
def emit(self, message):
|
|
212
|
+
def emit(self, message: Any) -> None:
|
|
188
213
|
self.emit_message(f'[{self.label}] {message}')
|
|
189
214
|
|
|
190
|
-
def trace(self, packet, direction=0):
|
|
215
|
+
def trace(self, packet: HCI_Packet, direction: int = 0) -> None:
|
|
191
216
|
if direction == 0:
|
|
192
217
|
self.host_to_controller_analyzer.on_packet(packet)
|
|
193
218
|
else:
|
|
@@ -195,10 +220,10 @@ class PacketTracer:
|
|
|
195
220
|
|
|
196
221
|
def __init__(
|
|
197
222
|
self,
|
|
198
|
-
host_to_controller_label=color('HOST->CONTROLLER', 'blue'),
|
|
199
|
-
controller_to_host_label=color('CONTROLLER->HOST', 'cyan'),
|
|
200
|
-
emit_message=logger.info,
|
|
201
|
-
):
|
|
223
|
+
host_to_controller_label: str = color('HOST->CONTROLLER', 'blue'),
|
|
224
|
+
controller_to_host_label: str = color('CONTROLLER->HOST', 'cyan'),
|
|
225
|
+
emit_message: Callable[..., None] = logger.info,
|
|
226
|
+
) -> None:
|
|
202
227
|
self.host_to_controller_analyzer = PacketTracer.Analyzer(
|
|
203
228
|
host_to_controller_label, emit_message
|
|
204
229
|
)
|
bumble/l2cap.py
CHANGED
|
@@ -391,6 +391,9 @@ class L2CAP_Connection_Request(L2CAP_Control_Frame):
|
|
|
391
391
|
See Bluetooth spec @ Vol 3, Part A - 4.2 CONNECTION REQUEST
|
|
392
392
|
'''
|
|
393
393
|
|
|
394
|
+
psm: int
|
|
395
|
+
source_cid: int
|
|
396
|
+
|
|
394
397
|
@staticmethod
|
|
395
398
|
def parse_psm(data: bytes, offset: int = 0) -> Tuple[int, int]:
|
|
396
399
|
psm_length = 2
|
|
@@ -432,6 +435,11 @@ class L2CAP_Connection_Response(L2CAP_Control_Frame):
|
|
|
432
435
|
See Bluetooth spec @ Vol 3, Part A - 4.3 CONNECTION RESPONSE
|
|
433
436
|
'''
|
|
434
437
|
|
|
438
|
+
source_cid: int
|
|
439
|
+
destination_cid: int
|
|
440
|
+
status: int
|
|
441
|
+
result: int
|
|
442
|
+
|
|
435
443
|
CONNECTION_SUCCESSFUL = 0x0000
|
|
436
444
|
CONNECTION_PENDING = 0x0001
|
|
437
445
|
CONNECTION_REFUSED_PSM_NOT_SUPPORTED = 0x0002
|
bumble/profiles/csip.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
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
|
+
|
|
16
|
+
# -----------------------------------------------------------------------------
|
|
17
|
+
# Imports
|
|
18
|
+
# -----------------------------------------------------------------------------
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
import enum
|
|
21
|
+
import struct
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
from bumble import gatt
|
|
25
|
+
from bumble import gatt_client
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# -----------------------------------------------------------------------------
|
|
29
|
+
# Constants
|
|
30
|
+
# -----------------------------------------------------------------------------
|
|
31
|
+
class SirkType(enum.IntEnum):
|
|
32
|
+
'''Coordinated Set Identification Service - 5.1 Set Identity Resolving Key.'''
|
|
33
|
+
|
|
34
|
+
ENCRYPTED = 0x00
|
|
35
|
+
PLAINTEXT = 0x01
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MemberLock(enum.IntEnum):
|
|
39
|
+
'''Coordinated Set Identification Service - 5.3 Set Member Lock.'''
|
|
40
|
+
|
|
41
|
+
UNLOCKED = 0x01
|
|
42
|
+
LOCKED = 0x02
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# -----------------------------------------------------------------------------
|
|
46
|
+
# Utils
|
|
47
|
+
# -----------------------------------------------------------------------------
|
|
48
|
+
# TODO: Implement RSI Generator
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# -----------------------------------------------------------------------------
|
|
52
|
+
# Server
|
|
53
|
+
# -----------------------------------------------------------------------------
|
|
54
|
+
class CoordinatedSetIdentificationService(gatt.TemplateService):
|
|
55
|
+
UUID = gatt.GATT_COORDINATED_SET_IDENTIFICATION_SERVICE
|
|
56
|
+
|
|
57
|
+
set_identity_resolving_key_characteristic: gatt.Characteristic
|
|
58
|
+
coordinated_set_size_characteristic: Optional[gatt.Characteristic] = None
|
|
59
|
+
set_member_lock_characteristic: Optional[gatt.Characteristic] = None
|
|
60
|
+
set_member_rank_characteristic: Optional[gatt.Characteristic] = None
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
set_identity_resolving_key: bytes,
|
|
65
|
+
coordinated_set_size: Optional[int] = None,
|
|
66
|
+
set_member_lock: Optional[MemberLock] = None,
|
|
67
|
+
set_member_rank: Optional[int] = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
characteristics = []
|
|
70
|
+
|
|
71
|
+
self.set_identity_resolving_key_characteristic = gatt.Characteristic(
|
|
72
|
+
uuid=gatt.GATT_SET_IDENTITY_RESOLVING_KEY_CHARACTERISTIC,
|
|
73
|
+
properties=gatt.Characteristic.Properties.READ
|
|
74
|
+
| gatt.Characteristic.Properties.NOTIFY,
|
|
75
|
+
permissions=gatt.Characteristic.Permissions.READABLE,
|
|
76
|
+
# TODO: Implement encrypted SIRK reader.
|
|
77
|
+
value=struct.pack('B', SirkType.PLAINTEXT) + set_identity_resolving_key,
|
|
78
|
+
)
|
|
79
|
+
characteristics.append(self.set_identity_resolving_key_characteristic)
|
|
80
|
+
|
|
81
|
+
if coordinated_set_size is not None:
|
|
82
|
+
self.coordinated_set_size_characteristic = gatt.Characteristic(
|
|
83
|
+
uuid=gatt.GATT_COORDINATED_SET_SIZE_CHARACTERISTIC,
|
|
84
|
+
properties=gatt.Characteristic.Properties.READ
|
|
85
|
+
| gatt.Characteristic.Properties.NOTIFY,
|
|
86
|
+
permissions=gatt.Characteristic.Permissions.READABLE,
|
|
87
|
+
value=struct.pack('B', coordinated_set_size),
|
|
88
|
+
)
|
|
89
|
+
characteristics.append(self.coordinated_set_size_characteristic)
|
|
90
|
+
|
|
91
|
+
if set_member_lock is not None:
|
|
92
|
+
self.set_member_lock_characteristic = gatt.Characteristic(
|
|
93
|
+
uuid=gatt.GATT_SET_MEMBER_LOCK_CHARACTERISTIC,
|
|
94
|
+
properties=gatt.Characteristic.Properties.READ
|
|
95
|
+
| gatt.Characteristic.Properties.NOTIFY
|
|
96
|
+
| gatt.Characteristic.Properties.WRITE,
|
|
97
|
+
permissions=gatt.Characteristic.Permissions.READABLE
|
|
98
|
+
| gatt.Characteristic.Permissions.WRITEABLE,
|
|
99
|
+
value=struct.pack('B', set_member_lock),
|
|
100
|
+
)
|
|
101
|
+
characteristics.append(self.set_member_lock_characteristic)
|
|
102
|
+
|
|
103
|
+
if set_member_rank is not None:
|
|
104
|
+
self.set_member_rank_characteristic = gatt.Characteristic(
|
|
105
|
+
uuid=gatt.GATT_SET_MEMBER_RANK_CHARACTERISTIC,
|
|
106
|
+
properties=gatt.Characteristic.Properties.READ
|
|
107
|
+
| gatt.Characteristic.Properties.NOTIFY,
|
|
108
|
+
permissions=gatt.Characteristic.Permissions.READABLE,
|
|
109
|
+
value=struct.pack('B', set_member_rank),
|
|
110
|
+
)
|
|
111
|
+
characteristics.append(self.set_member_rank_characteristic)
|
|
112
|
+
|
|
113
|
+
super().__init__(characteristics)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# -----------------------------------------------------------------------------
|
|
117
|
+
# Client
|
|
118
|
+
# -----------------------------------------------------------------------------
|
|
119
|
+
class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy):
|
|
120
|
+
SERVICE_CLASS = CoordinatedSetIdentificationService
|
|
121
|
+
|
|
122
|
+
set_identity_resolving_key: gatt_client.CharacteristicProxy
|
|
123
|
+
coordinated_set_size: Optional[gatt_client.CharacteristicProxy] = None
|
|
124
|
+
set_member_lock: Optional[gatt_client.CharacteristicProxy] = None
|
|
125
|
+
set_member_rank: Optional[gatt_client.CharacteristicProxy] = None
|
|
126
|
+
|
|
127
|
+
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
128
|
+
self.service_proxy = service_proxy
|
|
129
|
+
|
|
130
|
+
self.set_identity_resolving_key = service_proxy.get_characteristics_by_uuid(
|
|
131
|
+
gatt.GATT_SET_IDENTITY_RESOLVING_KEY_CHARACTERISTIC
|
|
132
|
+
)[0]
|
|
133
|
+
|
|
134
|
+
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
135
|
+
gatt.GATT_COORDINATED_SET_SIZE_CHARACTERISTIC
|
|
136
|
+
):
|
|
137
|
+
self.coordinated_set_size = characteristics[0]
|
|
138
|
+
|
|
139
|
+
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
140
|
+
gatt.GATT_SET_MEMBER_LOCK_CHARACTERISTIC
|
|
141
|
+
):
|
|
142
|
+
self.set_member_lock = characteristics[0]
|
|
143
|
+
|
|
144
|
+
if characteristics := service_proxy.get_characteristics_by_uuid(
|
|
145
|
+
gatt.GATT_SET_MEMBER_RANK_CHARACTERISTIC
|
|
146
|
+
):
|
|
147
|
+
self.set_member_rank = characteristics[0]
|
bumble/transport/common.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
bumble/__init__.py,sha256=Q8jkz6rgl95IMAeInQVt_2GLoJl3DcEP2cxtrQ-ho5c,110
|
|
2
|
-
bumble/_version.py,sha256=
|
|
2
|
+
bumble/_version.py,sha256=5oaj9BgfeJteuh4NknBm42CZG1RsQs3FBEs6JGSoHXE,415
|
|
3
3
|
bumble/a2dp.py,sha256=NqdmHIeEe3kThoHqdOgC3Wkywc6H7zqwGbjRYYFtNoQ,22321
|
|
4
4
|
bumble/at.py,sha256=kdrcsx2C8Rg61EWESD2QHwpZntkXkRBJLrPn9auv9K8,2961
|
|
5
5
|
bumble/att.py,sha256=emTsIoc_C067QE3osdyocic52Wr8N5TJdkpZeJJH6dk,31328
|
|
@@ -12,18 +12,18 @@ bumble/controller.py,sha256=9lyDMD836oK5nqvwfjFEQgQ3bTl56qn4kT5ix-HJc8w,45204
|
|
|
12
12
|
bumble/core.py,sha256=HjRpUseyVsueNkZ-KhPPTf1vNkNnI02eNcaOe685V_0,52949
|
|
13
13
|
bumble/crypto.py,sha256=o3lzVPeiaPX_aNeeg2Ir-UHiGpNU7Z2VyimKzDwMsHw,9012
|
|
14
14
|
bumble/decoder.py,sha256=N9nMvuVhuwpnfw7EDVuNe9uYY6B6c3RY2dh8RhRPC1U,9608
|
|
15
|
-
bumble/device.py,sha256=
|
|
15
|
+
bumble/device.py,sha256=JMRrTiK6ua9tpNRYL_491NpwC0mjBsd3TDBUVzj7Ms4,136996
|
|
16
16
|
bumble/gap.py,sha256=axlOZIv99357Ehq2vMokeioU85z81qdQvplGn0pF70Q,2137
|
|
17
|
-
bumble/gatt.py,sha256=
|
|
18
|
-
bumble/gatt_client.py,sha256=
|
|
17
|
+
bumble/gatt.py,sha256=ASbYauOKyOHgHrN67Ue12MFU1rayqePG5mEI0vaS0SY,38191
|
|
18
|
+
bumble/gatt_client.py,sha256=JuyFUHEc5xd2astr2XCZrTiAla2FoDkKe4lH1oSQOPA,42551
|
|
19
19
|
bumble/gatt_server.py,sha256=pLkydlyqQTyi9g89-WH7tZTIVtOd8gi6bpUn2b-OzAU,36728
|
|
20
|
-
bumble/hci.py,sha256=
|
|
21
|
-
bumble/helpers.py,sha256=
|
|
20
|
+
bumble/hci.py,sha256=Kbu2oESm6Xa3AkdyVzbU64OLzAzHXiQfLTO1tfGfoGo,231749
|
|
21
|
+
bumble/helpers.py,sha256=M7yGMbyI6_ZxIUNZA2krWJXP1qkedVcCOYog1lNPm-M,10084
|
|
22
22
|
bumble/hfp.py,sha256=SzoYpcXgY3LEHVEKUT64PFAJ7G5TZ29hhBbNnW9v8Cs,39067
|
|
23
23
|
bumble/hid.py,sha256=O_FzevrBsCVzuCL5v28NY5VoXmTNQ_4ditW3MYuOaP8,11341
|
|
24
24
|
bumble/host.py,sha256=woncM0_1siA9WKIAVkKjut3dUKiiDJbVzkTj3V3WKIw,36999
|
|
25
25
|
bumble/keys.py,sha256=_ibaJ4CxM5zyxnTp7wnihIQSsEAEkCiPxx9qHsGA04Q,12601
|
|
26
|
-
bumble/l2cap.py,sha256=
|
|
26
|
+
bumble/l2cap.py,sha256=1wtMmpVMAY5Djfo7Rs8pKKU6ulBqutCY7UGvv8Vnfck,80778
|
|
27
27
|
bumble/link.py,sha256=dvb3fdmV8689A4rLhFVHHKMR3u4wQbUapFi-GeheK0M,20220
|
|
28
28
|
bumble/pairing.py,sha256=tgPUba6xNxMi-2plm3xfRlzHq-uPRNZEIGWaN0qNGCs,9853
|
|
29
29
|
bumble/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -69,6 +69,7 @@ bumble/pandora/utils.py,sha256=Fq4glL0T5cJ2FODoDotmDNdYFOkTOR7DyyL8vkcxp20,3949
|
|
|
69
69
|
bumble/profiles/__init__.py,sha256=yBGC8Ti5LvZuoh1F42XtfrBilb39T77_yuxESZeX2yI,581
|
|
70
70
|
bumble/profiles/asha_service.py,sha256=wKAYgc36a9mBWx9wfIofA2e1fpnErrjHM-U1GfCX9LQ,7200
|
|
71
71
|
bumble/profiles/battery_service.py,sha256=w-uF4jLoDozJOoykimb2RkrKjVyCke6ts2-h-F1PYyc,2292
|
|
72
|
+
bumble/profiles/csip.py,sha256=PQ8A_5pJa2R96g4_wKM4JGgEFuvK7SAjoXS1xDLXt4g,6144
|
|
72
73
|
bumble/profiles/device_information_service.py,sha256=H1Db4BAOnsC-rRtfpoAIsDETYT4F9yM_WgByn_3LfRQ,5658
|
|
73
74
|
bumble/profiles/heart_rate_service.py,sha256=7V2LGcWLp6RurjWxsVgMWr3wPDt5aS9qjNxTbHcOK6o,8575
|
|
74
75
|
bumble/profiles/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -79,7 +80,7 @@ bumble/tools/rtk_util.py,sha256=TwZhupHQrQYsYHLdRGyzXKd24pwCk8kkzqK1Rj2guco,5087
|
|
|
79
80
|
bumble/transport/__init__.py,sha256=Yz2aau52Hi0B6sh06WtfJzMwOYr1Y5VlqjkJCw4hcbA,5901
|
|
80
81
|
bumble/transport/android_emulator.py,sha256=n6nPti0eb6JqPkAj5-fdtiMfSzA2Hgd2q4B1arudIhM,4333
|
|
81
82
|
bumble/transport/android_netsim.py,sha256=SVh-IUZ2bhcIESZFGzOsofybsi4H0qoBRwBieeqUINE,16215
|
|
82
|
-
bumble/transport/common.py,sha256=
|
|
83
|
+
bumble/transport/common.py,sha256=pOnr1GdIQtz8BM7-O3Q4OlEbMs5tfU3Fc9zFo2b62S0,15607
|
|
83
84
|
bumble/transport/file.py,sha256=eVM2V6Nk2nDAFdE7Rt01ZI3JdTovsH9OEU1gKYPJjpE,2010
|
|
84
85
|
bumble/transport/hci_socket.py,sha256=y9hrIY7QIgP994lffJHaAi2jfpC9FCANhzHO5F6k3vk,6377
|
|
85
86
|
bumble/transport/pty.py,sha256=grTl-yvjMWHflNwuME4ccVqDbk6NIEgQMgH6Y9lf1fU,2732
|
|
@@ -126,9 +127,9 @@ bumble/vendor/android/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
|
126
127
|
bumble/vendor/android/hci.py,sha256=GZrkhaWmcMt1JpnRhv0NoySGkf2H4lNUV2f_omRZW0I,10741
|
|
127
128
|
bumble/vendor/zephyr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
128
129
|
bumble/vendor/zephyr/hci.py,sha256=d83bC0TvT947eN4roFjLkQefWtHOoNsr4xib2ctSkvA,3195
|
|
129
|
-
bumble-0.0.
|
|
130
|
-
bumble-0.0.
|
|
131
|
-
bumble-0.0.
|
|
132
|
-
bumble-0.0.
|
|
133
|
-
bumble-0.0.
|
|
134
|
-
bumble-0.0.
|
|
130
|
+
bumble-0.0.180.dist-info/LICENSE,sha256=FvaYh4NRWIGgS_OwoBs5gFgkCmAghZ-DYnIGBZPuw-s,12142
|
|
131
|
+
bumble-0.0.180.dist-info/METADATA,sha256=U4ohfLC-oVuJa-g-Dllxl0QaucDvft6qL2o7O43T3Sw,5681
|
|
132
|
+
bumble-0.0.180.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
133
|
+
bumble-0.0.180.dist-info/entry_points.txt,sha256=AjCwgm9SvZDOhV7T6jWwAhWdE728pd759LQCscMLjnM,765
|
|
134
|
+
bumble-0.0.180.dist-info/top_level.txt,sha256=tV6JJKaHPYMFiJYiBYFW24PCcfLxTJZdlu6BmH3Cb00,7
|
|
135
|
+
bumble-0.0.180.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|