bumble 0.0.209__py3-none-any.whl → 0.0.211__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/a2dp.py +7 -7
- bumble/apps/auracast.py +37 -29
- bumble/apps/bench.py +13 -9
- bumble/apps/console.py +1 -1
- bumble/apps/lea_unicast/app.py +6 -2
- bumble/apps/pair.py +4 -3
- bumble/apps/player/player.py +3 -3
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/speaker/speaker.py +4 -2
- bumble/att.py +2 -3
- bumble/avc.py +5 -5
- bumble/avdtp.py +9 -10
- bumble/avrcp.py +18 -19
- bumble/bridge.py +2 -2
- bumble/controller.py +6 -7
- bumble/core.py +56 -56
- bumble/device.py +172 -137
- bumble/drivers/__init__.py +2 -2
- bumble/gap.py +1 -1
- bumble/gatt_adapters.py +3 -3
- bumble/gatt_client.py +27 -21
- bumble/gatt_server.py +9 -10
- bumble/hci.py +48 -22
- bumble/hfp.py +3 -3
- bumble/hid.py +4 -3
- bumble/host.py +22 -16
- bumble/keys.py +3 -3
- bumble/l2cap.py +19 -17
- bumble/link.py +3 -4
- bumble/pairing.py +3 -3
- bumble/pandora/__init__.py +5 -5
- bumble/pandora/host.py +18 -12
- bumble/pandora/l2cap.py +2 -2
- bumble/pandora/security.py +15 -16
- bumble/profiles/aics.py +6 -6
- bumble/profiles/ancs.py +9 -10
- bumble/profiles/ascs.py +17 -10
- bumble/profiles/asha.py +5 -5
- bumble/profiles/bass.py +1 -1
- bumble/profiles/csip.py +10 -10
- bumble/profiles/gatt_service.py +12 -12
- bumble/profiles/hap.py +16 -16
- bumble/profiles/mcp.py +26 -24
- bumble/profiles/pacs.py +6 -6
- bumble/profiles/pbp.py +1 -1
- bumble/profiles/vcs.py +6 -4
- bumble/profiles/vocs.py +3 -3
- bumble/rfcomm.py +8 -8
- bumble/sdp.py +1 -1
- bumble/smp.py +36 -30
- bumble/transport/__init__.py +24 -19
- bumble/transport/android_emulator.py +8 -4
- bumble/transport/android_netsim.py +8 -5
- bumble/transport/common.py +5 -1
- bumble/transport/file.py +1 -1
- bumble/transport/hci_socket.py +1 -1
- bumble/transport/pty.py +1 -1
- bumble/transport/pyusb.py +3 -3
- bumble/transport/serial.py +1 -1
- bumble/transport/tcp_client.py +1 -1
- bumble/transport/tcp_server.py +1 -1
- bumble/transport/udp.py +1 -1
- bumble/transport/unix.py +1 -1
- bumble/transport/vhci.py +2 -2
- bumble/transport/ws_client.py +6 -1
- bumble/transport/ws_server.py +1 -1
- bumble/utils.py +89 -76
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/METADATA +3 -2
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/RECORD +74 -74
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/WHEEL +1 -1
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info/licenses}/LICENSE +0 -0
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/top_level.txt +0 -0
bumble/profiles/csip.py
CHANGED
|
@@ -99,10 +99,10 @@ class CoordinatedSetIdentificationService(gatt.TemplateService):
|
|
|
99
99
|
UUID = gatt.GATT_COORDINATED_SET_IDENTIFICATION_SERVICE
|
|
100
100
|
|
|
101
101
|
set_identity_resolving_key: bytes
|
|
102
|
-
set_identity_resolving_key_characteristic: gatt.Characteristic
|
|
103
|
-
coordinated_set_size_characteristic: Optional[gatt.Characteristic] = None
|
|
104
|
-
set_member_lock_characteristic: Optional[gatt.Characteristic] = None
|
|
105
|
-
set_member_rank_characteristic: Optional[gatt.Characteristic] = None
|
|
102
|
+
set_identity_resolving_key_characteristic: gatt.Characteristic[bytes]
|
|
103
|
+
coordinated_set_size_characteristic: Optional[gatt.Characteristic[bytes]] = None
|
|
104
|
+
set_member_lock_characteristic: Optional[gatt.Characteristic[bytes]] = None
|
|
105
|
+
set_member_rank_characteristic: Optional[gatt.Characteristic[bytes]] = None
|
|
106
106
|
|
|
107
107
|
def __init__(
|
|
108
108
|
self,
|
|
@@ -170,7 +170,7 @@ class CoordinatedSetIdentificationService(gatt.TemplateService):
|
|
|
170
170
|
else:
|
|
171
171
|
assert connection
|
|
172
172
|
|
|
173
|
-
if connection.transport == core.
|
|
173
|
+
if connection.transport == core.PhysicalTransport.LE:
|
|
174
174
|
key = await connection.device.get_long_term_key(
|
|
175
175
|
connection_handle=connection.handle, rand=b'', ediv=0
|
|
176
176
|
)
|
|
@@ -203,10 +203,10 @@ class CoordinatedSetIdentificationService(gatt.TemplateService):
|
|
|
203
203
|
class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy):
|
|
204
204
|
SERVICE_CLASS = CoordinatedSetIdentificationService
|
|
205
205
|
|
|
206
|
-
set_identity_resolving_key: gatt_client.CharacteristicProxy
|
|
207
|
-
coordinated_set_size: Optional[gatt_client.CharacteristicProxy] = None
|
|
208
|
-
set_member_lock: Optional[gatt_client.CharacteristicProxy] = None
|
|
209
|
-
set_member_rank: Optional[gatt_client.CharacteristicProxy] = None
|
|
206
|
+
set_identity_resolving_key: gatt_client.CharacteristicProxy[bytes]
|
|
207
|
+
coordinated_set_size: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
208
|
+
set_member_lock: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
209
|
+
set_member_rank: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
210
210
|
|
|
211
211
|
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
212
212
|
self.service_proxy = service_proxy
|
|
@@ -242,7 +242,7 @@ class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy):
|
|
|
242
242
|
else:
|
|
243
243
|
connection = self.service_proxy.client.connection
|
|
244
244
|
device = connection.device
|
|
245
|
-
if connection.transport == core.
|
|
245
|
+
if connection.transport == core.PhysicalTransport.LE:
|
|
246
246
|
key = await device.get_long_term_key(
|
|
247
247
|
connection_handle=connection.handle, rand=b'', ediv=0
|
|
248
248
|
)
|
bumble/profiles/gatt_service.py
CHANGED
|
@@ -32,10 +32,10 @@ class GenericAttributeProfileService(gatt.TemplateService):
|
|
|
32
32
|
|
|
33
33
|
UUID = gatt.GATT_GENERIC_ATTRIBUTE_SERVICE
|
|
34
34
|
|
|
35
|
-
client_supported_features_characteristic: gatt.Characteristic | None = None
|
|
36
|
-
server_supported_features_characteristic: gatt.Characteristic | None = None
|
|
37
|
-
database_hash_characteristic: gatt.Characteristic | None = None
|
|
38
|
-
service_changed_characteristic: gatt.Characteristic | None = None
|
|
35
|
+
client_supported_features_characteristic: gatt.Characteristic[bytes] | None = None
|
|
36
|
+
server_supported_features_characteristic: gatt.Characteristic[bytes] | None = None
|
|
37
|
+
database_hash_characteristic: gatt.Characteristic[bytes] | None = None
|
|
38
|
+
service_changed_characteristic: gatt.Characteristic[bytes] | None = None
|
|
39
39
|
|
|
40
40
|
def __init__(
|
|
41
41
|
self,
|
|
@@ -143,14 +143,14 @@ class GenericAttributeProfileService(gatt.TemplateService):
|
|
|
143
143
|
class GenericAttributeProfileServiceProxy(gatt_client.ProfileServiceProxy):
|
|
144
144
|
SERVICE_CLASS = GenericAttributeProfileService
|
|
145
145
|
|
|
146
|
-
client_supported_features_characteristic:
|
|
147
|
-
None
|
|
148
|
-
)
|
|
149
|
-
server_supported_features_characteristic:
|
|
150
|
-
None
|
|
151
|
-
)
|
|
152
|
-
database_hash_characteristic: gatt_client.CharacteristicProxy | None = None
|
|
153
|
-
service_changed_characteristic: gatt_client.CharacteristicProxy | None = None
|
|
146
|
+
client_supported_features_characteristic: (
|
|
147
|
+
gatt_client.CharacteristicProxy[bytes] | None
|
|
148
|
+
) = None
|
|
149
|
+
server_supported_features_characteristic: (
|
|
150
|
+
gatt_client.CharacteristicProxy[bytes] | None
|
|
151
|
+
) = None
|
|
152
|
+
database_hash_characteristic: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
153
|
+
service_changed_characteristic: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
154
154
|
|
|
155
155
|
_CHARACTERISTICS = {
|
|
156
156
|
gatt.GATT_CLIENT_SUPPORTED_FEATURES_CHARACTERISTIC: 'client_supported_features_characteristic',
|
bumble/profiles/hap.py
CHANGED
|
@@ -25,14 +25,14 @@ from typing import Any, Dict, List, Optional, Set, Union
|
|
|
25
25
|
from bumble import att, gatt, gatt_adapters, gatt_client
|
|
26
26
|
from bumble.core import InvalidArgumentError, InvalidStateError
|
|
27
27
|
from bumble.device import Device, Connection
|
|
28
|
-
from bumble
|
|
28
|
+
from bumble import utils
|
|
29
29
|
from bumble.hci import Address
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
# -----------------------------------------------------------------------------
|
|
33
33
|
# Constants
|
|
34
34
|
# -----------------------------------------------------------------------------
|
|
35
|
-
class ErrorCode(OpenIntEnum):
|
|
35
|
+
class ErrorCode(utils.OpenIntEnum):
|
|
36
36
|
'''See Hearing Access Service 2.4. Attribute Profile error codes.'''
|
|
37
37
|
|
|
38
38
|
INVALID_OPCODE = 0x80
|
|
@@ -42,7 +42,7 @@ class ErrorCode(OpenIntEnum):
|
|
|
42
42
|
INVALID_PARAMETERS_LENGTH = 0x84
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
class HearingAidType(OpenIntEnum):
|
|
45
|
+
class HearingAidType(utils.OpenIntEnum):
|
|
46
46
|
'''See Hearing Access Service 3.1. Hearing Aid Features.'''
|
|
47
47
|
|
|
48
48
|
BINAURAL_HEARING_AID = 0b00
|
|
@@ -50,35 +50,35 @@ class HearingAidType(OpenIntEnum):
|
|
|
50
50
|
BANDED_HEARING_AID = 0b10
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
class PresetSynchronizationSupport(OpenIntEnum):
|
|
53
|
+
class PresetSynchronizationSupport(utils.OpenIntEnum):
|
|
54
54
|
'''See Hearing Access Service 3.1. Hearing Aid Features.'''
|
|
55
55
|
|
|
56
56
|
PRESET_SYNCHRONIZATION_IS_NOT_SUPPORTED = 0b0
|
|
57
57
|
PRESET_SYNCHRONIZATION_IS_SUPPORTED = 0b1
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
class IndependentPresets(OpenIntEnum):
|
|
60
|
+
class IndependentPresets(utils.OpenIntEnum):
|
|
61
61
|
'''See Hearing Access Service 3.1. Hearing Aid Features.'''
|
|
62
62
|
|
|
63
63
|
IDENTICAL_PRESET_RECORD = 0b0
|
|
64
64
|
DIFFERENT_PRESET_RECORD = 0b1
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
class DynamicPresets(OpenIntEnum):
|
|
67
|
+
class DynamicPresets(utils.OpenIntEnum):
|
|
68
68
|
'''See Hearing Access Service 3.1. Hearing Aid Features.'''
|
|
69
69
|
|
|
70
70
|
PRESET_RECORDS_DOES_NOT_CHANGE = 0b0
|
|
71
71
|
PRESET_RECORDS_MAY_CHANGE = 0b1
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
class WritablePresetsSupport(OpenIntEnum):
|
|
74
|
+
class WritablePresetsSupport(utils.OpenIntEnum):
|
|
75
75
|
'''See Hearing Access Service 3.1. Hearing Aid Features.'''
|
|
76
76
|
|
|
77
77
|
WRITABLE_PRESET_RECORDS_NOT_SUPPORTED = 0b0
|
|
78
78
|
WRITABLE_PRESET_RECORDS_SUPPORTED = 0b1
|
|
79
79
|
|
|
80
80
|
|
|
81
|
-
class HearingAidPresetControlPointOpcode(OpenIntEnum):
|
|
81
|
+
class HearingAidPresetControlPointOpcode(utils.OpenIntEnum):
|
|
82
82
|
'''See Hearing Access Service 3.3.1 Hearing Aid Preset Control Point operation requirements.'''
|
|
83
83
|
|
|
84
84
|
# fmt: off
|
|
@@ -130,7 +130,7 @@ def HearingAidFeatures_from_bytes(data: int) -> HearingAidFeatures:
|
|
|
130
130
|
class PresetChangedOperation:
|
|
131
131
|
'''See Hearing Access Service 3.2.2.2. Preset Changed operation.'''
|
|
132
132
|
|
|
133
|
-
class ChangeId(OpenIntEnum):
|
|
133
|
+
class ChangeId(utils.OpenIntEnum):
|
|
134
134
|
# fmt: off
|
|
135
135
|
GENERIC_UPDATE = 0x00
|
|
136
136
|
PRESET_RECORD_DELETED = 0x01
|
|
@@ -190,11 +190,11 @@ class PresetRecord:
|
|
|
190
190
|
|
|
191
191
|
@dataclass
|
|
192
192
|
class Property:
|
|
193
|
-
class Writable(OpenIntEnum):
|
|
193
|
+
class Writable(utils.OpenIntEnum):
|
|
194
194
|
CANNOT_BE_WRITTEN = 0b0
|
|
195
195
|
CAN_BE_WRITTEN = 0b1
|
|
196
196
|
|
|
197
|
-
class IsAvailable(OpenIntEnum):
|
|
197
|
+
class IsAvailable(utils.OpenIntEnum):
|
|
198
198
|
IS_UNAVAILABLE = 0b0
|
|
199
199
|
IS_AVAILABLE = 0b1
|
|
200
200
|
|
|
@@ -224,9 +224,9 @@ class PresetRecord:
|
|
|
224
224
|
class HearingAccessService(gatt.TemplateService):
|
|
225
225
|
UUID = gatt.GATT_HEARING_ACCESS_SERVICE
|
|
226
226
|
|
|
227
|
-
hearing_aid_features_characteristic: gatt.Characteristic
|
|
228
|
-
hearing_aid_preset_control_point: gatt.Characteristic
|
|
229
|
-
active_preset_index_characteristic: gatt.Characteristic
|
|
227
|
+
hearing_aid_features_characteristic: gatt.Characteristic[bytes]
|
|
228
|
+
hearing_aid_preset_control_point: gatt.Characteristic[bytes]
|
|
229
|
+
active_preset_index_characteristic: gatt.Characteristic[bytes]
|
|
230
230
|
active_preset_index: int
|
|
231
231
|
active_preset_index_per_device: Dict[Address, int]
|
|
232
232
|
|
|
@@ -333,7 +333,7 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
333
333
|
# Update the active preset index if needed
|
|
334
334
|
await self.notify_active_preset_for_connection(connection)
|
|
335
335
|
|
|
336
|
-
|
|
336
|
+
utils.cancel_on_event(connection, 'disconnection', on_connection_async())
|
|
337
337
|
|
|
338
338
|
def _on_read_active_preset_index(
|
|
339
339
|
self, __connection__: Optional[Connection]
|
|
@@ -382,7 +382,7 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
382
382
|
if len(presets) == 0:
|
|
383
383
|
raise att.ATT_Error(att.ErrorCode.OUT_OF_RANGE)
|
|
384
384
|
|
|
385
|
-
AsyncRunner.spawn(self._read_preset_response(connection, presets))
|
|
385
|
+
utils.AsyncRunner.spawn(self._read_preset_response(connection, presets))
|
|
386
386
|
|
|
387
387
|
async def _read_preset_response(
|
|
388
388
|
self, connection: Connection, presets: List[PresetRecord]
|
bumble/profiles/mcp.py
CHANGED
|
@@ -338,30 +338,32 @@ class MediaControlServiceProxy(
|
|
|
338
338
|
'content_control_id': gatt.GATT_CONTENT_CONTROL_ID_CHARACTERISTIC,
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
media_player_name: Optional[gatt_client.CharacteristicProxy] = None
|
|
342
|
-
media_player_icon_object_id: Optional[gatt_client.CharacteristicProxy] = None
|
|
343
|
-
media_player_icon_url: Optional[gatt_client.CharacteristicProxy] = None
|
|
344
|
-
track_changed: Optional[gatt_client.CharacteristicProxy] = None
|
|
345
|
-
track_title: Optional[gatt_client.CharacteristicProxy] = None
|
|
346
|
-
track_duration: Optional[gatt_client.CharacteristicProxy] = None
|
|
347
|
-
track_position: Optional[gatt_client.CharacteristicProxy] = None
|
|
348
|
-
playback_speed: Optional[gatt_client.CharacteristicProxy] = None
|
|
349
|
-
seeking_speed: Optional[gatt_client.CharacteristicProxy] = None
|
|
350
|
-
current_track_segments_object_id: Optional[
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
341
|
+
media_player_name: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
342
|
+
media_player_icon_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
343
|
+
media_player_icon_url: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
344
|
+
track_changed: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
345
|
+
track_title: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
346
|
+
track_duration: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
347
|
+
track_position: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
348
|
+
playback_speed: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
349
|
+
seeking_speed: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
350
|
+
current_track_segments_object_id: Optional[
|
|
351
|
+
gatt_client.CharacteristicProxy[bytes]
|
|
352
|
+
] = None
|
|
353
|
+
current_track_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
354
|
+
next_track_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
355
|
+
parent_group_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
356
|
+
current_group_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
357
|
+
playing_order: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
358
|
+
playing_orders_supported: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
359
|
+
media_state: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
360
|
+
media_control_point: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
361
|
+
media_control_point_opcodes_supported: Optional[
|
|
362
|
+
gatt_client.CharacteristicProxy[bytes]
|
|
363
|
+
] = None
|
|
364
|
+
search_control_point: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
365
|
+
search_results_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
366
|
+
content_control_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
|
|
365
367
|
|
|
366
368
|
if TYPE_CHECKING:
|
|
367
369
|
media_control_point_notifications: asyncio.Queue[bytes]
|
bumble/profiles/pacs.py
CHANGED
|
@@ -104,12 +104,12 @@ class PacRecord:
|
|
|
104
104
|
class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|
105
105
|
UUID = gatt.GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE
|
|
106
106
|
|
|
107
|
-
sink_pac: Optional[gatt.Characteristic]
|
|
108
|
-
sink_audio_locations: Optional[gatt.Characteristic]
|
|
109
|
-
source_pac: Optional[gatt.Characteristic]
|
|
110
|
-
source_audio_locations: Optional[gatt.Characteristic]
|
|
111
|
-
available_audio_contexts: gatt.Characteristic
|
|
112
|
-
supported_audio_contexts: gatt.Characteristic
|
|
107
|
+
sink_pac: Optional[gatt.Characteristic[bytes]]
|
|
108
|
+
sink_audio_locations: Optional[gatt.Characteristic[bytes]]
|
|
109
|
+
source_pac: Optional[gatt.Characteristic[bytes]]
|
|
110
|
+
source_audio_locations: Optional[gatt.Characteristic[bytes]]
|
|
111
|
+
available_audio_contexts: gatt.Characteristic[bytes]
|
|
112
|
+
supported_audio_contexts: gatt.Characteristic[bytes]
|
|
113
113
|
|
|
114
114
|
def __init__(
|
|
115
115
|
self,
|
bumble/profiles/pbp.py
CHANGED
|
@@ -40,7 +40,7 @@ class PublicBroadcastAnnouncement:
|
|
|
40
40
|
def from_bytes(cls, data: bytes) -> Self:
|
|
41
41
|
features = cls.Features(data[0])
|
|
42
42
|
metadata_length = data[1]
|
|
43
|
-
metadata_ltv = data[
|
|
43
|
+
metadata_ltv = data[2 : 2 + metadata_length]
|
|
44
44
|
return cls(
|
|
45
45
|
features=features, metadata=le_audio.Metadata.from_bytes(metadata_ltv)
|
|
46
46
|
)
|
bumble/profiles/vcs.py
CHANGED
|
@@ -23,6 +23,7 @@ import enum
|
|
|
23
23
|
from typing import Optional, Sequence
|
|
24
24
|
|
|
25
25
|
from bumble import att
|
|
26
|
+
from bumble import utils
|
|
26
27
|
from bumble import device
|
|
27
28
|
from bumble import gatt
|
|
28
29
|
from bumble import gatt_adapters
|
|
@@ -90,9 +91,9 @@ class VolumeState:
|
|
|
90
91
|
class VolumeControlService(gatt.TemplateService):
|
|
91
92
|
UUID = gatt.GATT_VOLUME_CONTROL_SERVICE
|
|
92
93
|
|
|
93
|
-
volume_state: gatt.Characteristic
|
|
94
|
-
volume_control_point: gatt.Characteristic
|
|
95
|
-
volume_flags: gatt.Characteristic
|
|
94
|
+
volume_state: gatt.Characteristic[bytes]
|
|
95
|
+
volume_control_point: gatt.Characteristic[bytes]
|
|
96
|
+
volume_flags: gatt.Characteristic[bytes]
|
|
96
97
|
|
|
97
98
|
volume_setting: int
|
|
98
99
|
muted: int
|
|
@@ -160,7 +161,8 @@ class VolumeControlService(gatt.TemplateService):
|
|
|
160
161
|
handler = getattr(self, '_on_' + opcode.name.lower())
|
|
161
162
|
if handler(*value[2:]):
|
|
162
163
|
self.change_counter = (self.change_counter + 1) % 256
|
|
163
|
-
|
|
164
|
+
utils.cancel_on_event(
|
|
165
|
+
connection,
|
|
164
166
|
'disconnection',
|
|
165
167
|
connection.device.notify_subscribers(attribute=self.volume_state),
|
|
166
168
|
)
|
bumble/profiles/vocs.py
CHANGED
|
@@ -38,7 +38,7 @@ from bumble.gatt_adapters import (
|
|
|
38
38
|
UTF8CharacteristicProxyAdapter,
|
|
39
39
|
)
|
|
40
40
|
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
|
|
41
|
-
from bumble
|
|
41
|
+
from bumble import utils
|
|
42
42
|
from bumble.profiles.bap import AudioLocation
|
|
43
43
|
|
|
44
44
|
# -----------------------------------------------------------------------------
|
|
@@ -50,11 +50,11 @@ MAX_VOLUME_OFFSET = 255
|
|
|
50
50
|
CHANGE_COUNTER_MAX_VALUE = 0xFF
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
class SetVolumeOffsetOpCode(OpenIntEnum):
|
|
53
|
+
class SetVolumeOffsetOpCode(utils.OpenIntEnum):
|
|
54
54
|
SET_VOLUME_OFFSET = 0x01
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
class ErrorCode(OpenIntEnum):
|
|
57
|
+
class ErrorCode(utils.OpenIntEnum):
|
|
58
58
|
"""
|
|
59
59
|
See Volume Offset Control Service 1.6. Application error codes.
|
|
60
60
|
"""
|
bumble/rfcomm.py
CHANGED
|
@@ -25,16 +25,16 @@ import enum
|
|
|
25
25
|
from typing import Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
|
|
26
26
|
from typing_extensions import Self
|
|
27
27
|
|
|
28
|
-
from pyee import EventEmitter
|
|
29
28
|
|
|
30
29
|
from bumble import core
|
|
31
30
|
from bumble import l2cap
|
|
32
31
|
from bumble import sdp
|
|
33
|
-
from
|
|
34
|
-
from .
|
|
32
|
+
from bumble import utils
|
|
33
|
+
from bumble.colors import color
|
|
34
|
+
from bumble.core import (
|
|
35
35
|
UUID,
|
|
36
36
|
BT_RFCOMM_PROTOCOL_ID,
|
|
37
|
-
|
|
37
|
+
PhysicalTransport,
|
|
38
38
|
BT_L2CAP_PROTOCOL_ID,
|
|
39
39
|
InvalidArgumentError,
|
|
40
40
|
InvalidStateError,
|
|
@@ -441,7 +441,7 @@ class RFCOMM_MCC_MSC:
|
|
|
441
441
|
|
|
442
442
|
|
|
443
443
|
# -----------------------------------------------------------------------------
|
|
444
|
-
class DLC(EventEmitter):
|
|
444
|
+
class DLC(utils.EventEmitter):
|
|
445
445
|
class State(enum.IntEnum):
|
|
446
446
|
INIT = 0x00
|
|
447
447
|
CONNECTING = 0x01
|
|
@@ -749,7 +749,7 @@ class DLC(EventEmitter):
|
|
|
749
749
|
|
|
750
750
|
|
|
751
751
|
# -----------------------------------------------------------------------------
|
|
752
|
-
class Multiplexer(EventEmitter):
|
|
752
|
+
class Multiplexer(utils.EventEmitter):
|
|
753
753
|
class Role(enum.IntEnum):
|
|
754
754
|
INITIATOR = 0x00
|
|
755
755
|
RESPONDER = 0x01
|
|
@@ -845,7 +845,7 @@ class Multiplexer(EventEmitter):
|
|
|
845
845
|
self.open_result.set_exception(
|
|
846
846
|
core.ConnectionError(
|
|
847
847
|
core.ConnectionError.CONNECTION_REFUSED,
|
|
848
|
-
|
|
848
|
+
PhysicalTransport.BR_EDR,
|
|
849
849
|
self.l2cap_channel.connection.peer_address,
|
|
850
850
|
'rfcomm',
|
|
851
851
|
)
|
|
@@ -1075,7 +1075,7 @@ class Client:
|
|
|
1075
1075
|
|
|
1076
1076
|
|
|
1077
1077
|
# -----------------------------------------------------------------------------
|
|
1078
|
-
class Server(EventEmitter):
|
|
1078
|
+
class Server(utils.EventEmitter):
|
|
1079
1079
|
def __init__(
|
|
1080
1080
|
self, device: Device, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
|
|
1081
1081
|
) -> None:
|
bumble/sdp.py
CHANGED
|
@@ -33,7 +33,7 @@ from bumble.core import (
|
|
|
33
33
|
from bumble.hci import HCI_Object, name_or_number, key_with_value
|
|
34
34
|
|
|
35
35
|
if TYPE_CHECKING:
|
|
36
|
-
from .device import Device, Connection
|
|
36
|
+
from bumble.device import Device, Connection
|
|
37
37
|
|
|
38
38
|
# -----------------------------------------------------------------------------
|
|
39
39
|
# Logging
|
bumble/smp.py
CHANGED
|
@@ -41,26 +41,25 @@ from typing import (
|
|
|
41
41
|
cast,
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
from pyee import EventEmitter
|
|
45
44
|
|
|
46
|
-
from .colors import color
|
|
47
|
-
from .hci import (
|
|
45
|
+
from bumble.colors import color
|
|
46
|
+
from bumble.hci import (
|
|
48
47
|
Address,
|
|
49
48
|
Role,
|
|
50
49
|
HCI_LE_Enable_Encryption_Command,
|
|
51
50
|
HCI_Object,
|
|
52
51
|
key_with_value,
|
|
53
52
|
)
|
|
54
|
-
from .core import (
|
|
55
|
-
|
|
56
|
-
BT_LE_TRANSPORT,
|
|
53
|
+
from bumble.core import (
|
|
54
|
+
PhysicalTransport,
|
|
57
55
|
AdvertisingData,
|
|
58
56
|
InvalidArgumentError,
|
|
59
57
|
ProtocolError,
|
|
60
58
|
name_or_number,
|
|
61
59
|
)
|
|
62
|
-
from .keys import PairingKeys
|
|
63
|
-
from
|
|
60
|
+
from bumble.keys import PairingKeys
|
|
61
|
+
from bumble import crypto
|
|
62
|
+
from bumble import utils
|
|
64
63
|
|
|
65
64
|
if TYPE_CHECKING:
|
|
66
65
|
from bumble.device import Connection, Device
|
|
@@ -857,7 +856,7 @@ class Session:
|
|
|
857
856
|
initiator_io_capability: int,
|
|
858
857
|
responder_io_capability: int,
|
|
859
858
|
) -> None:
|
|
860
|
-
if self.connection.transport ==
|
|
859
|
+
if self.connection.transport == PhysicalTransport.BR_EDR:
|
|
861
860
|
self.pairing_method = PairingMethod.CTKD_OVER_CLASSIC
|
|
862
861
|
return
|
|
863
862
|
if (not self.mitm) and (auth_req & SMP_MITM_AUTHREQ == 0):
|
|
@@ -900,7 +899,7 @@ class Session:
|
|
|
900
899
|
|
|
901
900
|
self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
|
|
902
901
|
|
|
903
|
-
self.connection
|
|
902
|
+
utils.cancel_on_event(self.connection, 'disconnection', prompt())
|
|
904
903
|
|
|
905
904
|
def prompt_user_for_numeric_comparison(
|
|
906
905
|
self, code: int, next_steps: Callable[[], None]
|
|
@@ -919,7 +918,7 @@ class Session:
|
|
|
919
918
|
|
|
920
919
|
self.send_pairing_failed(SMP_CONFIRM_VALUE_FAILED_ERROR)
|
|
921
920
|
|
|
922
|
-
self.connection
|
|
921
|
+
utils.cancel_on_event(self.connection, 'disconnection', prompt())
|
|
923
922
|
|
|
924
923
|
def prompt_user_for_number(self, next_steps: Callable[[int], None]) -> None:
|
|
925
924
|
async def prompt() -> None:
|
|
@@ -936,7 +935,7 @@ class Session:
|
|
|
936
935
|
logger.warning(f'exception while prompting: {error}')
|
|
937
936
|
self.send_pairing_failed(SMP_PASSKEY_ENTRY_FAILED_ERROR)
|
|
938
937
|
|
|
939
|
-
self.connection
|
|
938
|
+
utils.cancel_on_event(self.connection, 'disconnection', prompt())
|
|
940
939
|
|
|
941
940
|
def display_passkey(self) -> None:
|
|
942
941
|
# Generate random Passkey/PIN code
|
|
@@ -951,7 +950,8 @@ class Session:
|
|
|
951
950
|
logger.debug(f'TK from passkey = {self.tk.hex()}')
|
|
952
951
|
|
|
953
952
|
try:
|
|
954
|
-
|
|
953
|
+
utils.cancel_on_event(
|
|
954
|
+
self.connection,
|
|
955
955
|
'disconnection',
|
|
956
956
|
self.pairing_config.delegate.display_number(self.passkey, digits=6),
|
|
957
957
|
)
|
|
@@ -1050,7 +1050,7 @@ class Session:
|
|
|
1050
1050
|
)
|
|
1051
1051
|
|
|
1052
1052
|
# Perform the next steps asynchronously in case we need to wait for input
|
|
1053
|
-
self.connection
|
|
1053
|
+
utils.cancel_on_event(self.connection, 'disconnection', next_steps())
|
|
1054
1054
|
else:
|
|
1055
1055
|
confirm_value = crypto.c1(
|
|
1056
1056
|
self.tk,
|
|
@@ -1170,11 +1170,11 @@ class Session:
|
|
|
1170
1170
|
if self.is_initiator:
|
|
1171
1171
|
# CTKD: Derive LTK from LinkKey
|
|
1172
1172
|
if (
|
|
1173
|
-
self.connection.transport ==
|
|
1173
|
+
self.connection.transport == PhysicalTransport.BR_EDR
|
|
1174
1174
|
and self.initiator_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG
|
|
1175
1175
|
):
|
|
1176
|
-
self.ctkd_task =
|
|
1177
|
-
'disconnection', self.get_link_key_and_derive_ltk()
|
|
1176
|
+
self.ctkd_task = utils.cancel_on_event(
|
|
1177
|
+
self.connection, 'disconnection', self.get_link_key_and_derive_ltk()
|
|
1178
1178
|
)
|
|
1179
1179
|
elif not self.sc:
|
|
1180
1180
|
# Distribute the LTK, EDIV and RAND
|
|
@@ -1209,11 +1209,11 @@ class Session:
|
|
|
1209
1209
|
else:
|
|
1210
1210
|
# CTKD: Derive LTK from LinkKey
|
|
1211
1211
|
if (
|
|
1212
|
-
self.connection.transport ==
|
|
1212
|
+
self.connection.transport == PhysicalTransport.BR_EDR
|
|
1213
1213
|
and self.responder_key_distribution & SMP_ENC_KEY_DISTRIBUTION_FLAG
|
|
1214
1214
|
):
|
|
1215
|
-
self.ctkd_task =
|
|
1216
|
-
'disconnection', self.get_link_key_and_derive_ltk()
|
|
1215
|
+
self.ctkd_task = utils.cancel_on_event(
|
|
1216
|
+
self.connection, 'disconnection', self.get_link_key_and_derive_ltk()
|
|
1217
1217
|
)
|
|
1218
1218
|
# Distribute the LTK, EDIV and RAND
|
|
1219
1219
|
elif not self.sc:
|
|
@@ -1248,7 +1248,7 @@ class Session:
|
|
|
1248
1248
|
def compute_peer_expected_distributions(self, key_distribution_flags: int) -> None:
|
|
1249
1249
|
# Set our expectations for what to wait for in the key distribution phase
|
|
1250
1250
|
self.peer_expected_distributions = []
|
|
1251
|
-
if not self.sc and self.connection.transport ==
|
|
1251
|
+
if not self.sc and self.connection.transport == PhysicalTransport.LE:
|
|
1252
1252
|
if key_distribution_flags & SMP_ENC_KEY_DISTRIBUTION_FLAG != 0:
|
|
1253
1253
|
self.peer_expected_distributions.append(
|
|
1254
1254
|
SMP_Encryption_Information_Command
|
|
@@ -1305,7 +1305,9 @@ class Session:
|
|
|
1305
1305
|
|
|
1306
1306
|
# Wait for the pairing process to finish
|
|
1307
1307
|
assert self.pairing_result
|
|
1308
|
-
await
|
|
1308
|
+
await utils.cancel_on_event(
|
|
1309
|
+
self.connection, 'disconnection', self.pairing_result
|
|
1310
|
+
)
|
|
1309
1311
|
|
|
1310
1312
|
def on_disconnection(self, _: int) -> None:
|
|
1311
1313
|
self.connection.remove_listener('disconnection', self.on_disconnection)
|
|
@@ -1323,7 +1325,7 @@ class Session:
|
|
|
1323
1325
|
if self.is_initiator:
|
|
1324
1326
|
self.distribute_keys()
|
|
1325
1327
|
|
|
1326
|
-
self.connection
|
|
1328
|
+
utils.cancel_on_event(self.connection, 'disconnection', self.on_pairing())
|
|
1327
1329
|
|
|
1328
1330
|
def on_connection_encryption_change(self) -> None:
|
|
1329
1331
|
if self.connection.is_encrypted and not self.completed:
|
|
@@ -1365,7 +1367,7 @@ class Session:
|
|
|
1365
1367
|
keys = PairingKeys()
|
|
1366
1368
|
keys.address_type = peer_address.address_type
|
|
1367
1369
|
authenticated = self.pairing_method != PairingMethod.JUST_WORKS
|
|
1368
|
-
if self.sc or self.connection.transport ==
|
|
1370
|
+
if self.sc or self.connection.transport == PhysicalTransport.BR_EDR:
|
|
1369
1371
|
keys.ltk = PairingKeys.Key(value=self.ltk, authenticated=authenticated)
|
|
1370
1372
|
else:
|
|
1371
1373
|
our_ltk_key = PairingKeys.Key(
|
|
@@ -1432,8 +1434,10 @@ class Session:
|
|
|
1432
1434
|
def on_smp_pairing_request_command(
|
|
1433
1435
|
self, command: SMP_Pairing_Request_Command
|
|
1434
1436
|
) -> None:
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
+
utils.cancel_on_event(
|
|
1438
|
+
self.connection,
|
|
1439
|
+
'disconnection',
|
|
1440
|
+
self.on_smp_pairing_request_command_async(command),
|
|
1437
1441
|
)
|
|
1438
1442
|
|
|
1439
1443
|
async def on_smp_pairing_request_command_async(
|
|
@@ -1506,7 +1510,7 @@ class Session:
|
|
|
1506
1510
|
# CTKD over BR/EDR should happen after the connection has been encrypted,
|
|
1507
1511
|
# so when receiving pairing requests, responder should start distributing keys
|
|
1508
1512
|
if (
|
|
1509
|
-
self.connection.transport ==
|
|
1513
|
+
self.connection.transport == PhysicalTransport.BR_EDR
|
|
1510
1514
|
and self.connection.is_encrypted
|
|
1511
1515
|
and self.is_responder
|
|
1512
1516
|
and accepted
|
|
@@ -1878,7 +1882,7 @@ class Session:
|
|
|
1878
1882
|
self.wait_before_continuing = None
|
|
1879
1883
|
self.send_pairing_dhkey_check_command()
|
|
1880
1884
|
|
|
1881
|
-
self.connection
|
|
1885
|
+
utils.cancel_on_event(self.connection, 'disconnection', next_steps())
|
|
1882
1886
|
else:
|
|
1883
1887
|
self.send_pairing_dhkey_check_command()
|
|
1884
1888
|
else:
|
|
@@ -1922,7 +1926,7 @@ class Session:
|
|
|
1922
1926
|
|
|
1923
1927
|
|
|
1924
1928
|
# -----------------------------------------------------------------------------
|
|
1925
|
-
class Manager(EventEmitter):
|
|
1929
|
+
class Manager(utils.EventEmitter):
|
|
1926
1930
|
'''
|
|
1927
1931
|
Implements the Initiator and Responder roles of the Security Manager Protocol
|
|
1928
1932
|
'''
|
|
@@ -1950,7 +1954,9 @@ class Manager(EventEmitter):
|
|
|
1950
1954
|
f'>>> Sending SMP Command on connection [0x{connection.handle:04X}] '
|
|
1951
1955
|
f'{connection.peer_address}: {command}'
|
|
1952
1956
|
)
|
|
1953
|
-
cid =
|
|
1957
|
+
cid = (
|
|
1958
|
+
SMP_BR_CID if connection.transport == PhysicalTransport.BR_EDR else SMP_CID
|
|
1959
|
+
)
|
|
1954
1960
|
connection.send_l2cap_pdu(cid, bytes(command))
|
|
1955
1961
|
|
|
1956
1962
|
def on_smp_security_request_command(
|