bumble 0.0.220__py3-none-any.whl → 0.0.221__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 +5 -5
- bumble/apps/auracast.py +746 -473
- bumble/apps/bench.py +4 -5
- bumble/apps/console.py +5 -10
- bumble/apps/controller_info.py +12 -7
- bumble/apps/controller_loopback.py +1 -2
- bumble/apps/device_info.py +2 -3
- bumble/apps/gatt_dump.py +0 -1
- bumble/apps/lea_unicast/app.py +1 -1
- bumble/apps/pair.py +49 -46
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +10 -12
- bumble/apps/rfcomm_bridge.py +10 -11
- bumble/apps/scan.py +1 -3
- bumble/apps/speaker/speaker.py +3 -4
- bumble/at.py +4 -5
- bumble/att.py +91 -25
- bumble/audio/io.py +5 -3
- bumble/avc.py +1 -2
- bumble/avctp.py +2 -3
- bumble/avdtp.py +53 -57
- bumble/avrcp.py +25 -27
- bumble/codecs.py +15 -15
- bumble/colors.py +7 -8
- bumble/controller.py +663 -391
- bumble/core.py +41 -49
- bumble/crypto/__init__.py +2 -1
- bumble/crypto/builtin.py +2 -8
- bumble/data_types.py +2 -1
- bumble/decoder.py +2 -3
- bumble/device.py +171 -142
- bumble/drivers/__init__.py +3 -2
- bumble/drivers/intel.py +6 -8
- bumble/drivers/rtk.py +1 -1
- bumble/gatt.py +9 -9
- bumble/gatt_adapters.py +6 -6
- bumble/gatt_client.py +110 -60
- bumble/gatt_server.py +209 -139
- bumble/hci.py +87 -74
- bumble/helpers.py +5 -5
- bumble/hfp.py +27 -26
- bumble/hid.py +9 -9
- bumble/host.py +44 -50
- bumble/keys.py +17 -17
- bumble/l2cap.py +1015 -218
- bumble/link.py +26 -159
- bumble/ll.py +200 -0
- bumble/pairing.py +14 -15
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/device.py +6 -4
- bumble/pandora/host.py +19 -10
- bumble/pandora/l2cap.py +8 -9
- bumble/pandora/security.py +18 -16
- bumble/pandora/utils.py +4 -4
- bumble/profiles/aics.py +6 -8
- bumble/profiles/ams.py +3 -5
- bumble/profiles/ancs.py +11 -11
- bumble/profiles/ascs.py +5 -5
- bumble/profiles/asha.py +10 -9
- bumble/profiles/bass.py +9 -3
- bumble/profiles/battery_service.py +1 -2
- bumble/profiles/csip.py +9 -10
- bumble/profiles/device_information_service.py +16 -17
- bumble/profiles/gap.py +3 -4
- bumble/profiles/gatt_service.py +0 -1
- bumble/profiles/gmap.py +12 -13
- bumble/profiles/hap.py +3 -3
- bumble/profiles/heart_rate_service.py +7 -8
- bumble/profiles/le_audio.py +1 -1
- bumble/profiles/mcp.py +28 -28
- bumble/profiles/pacs.py +13 -17
- bumble/profiles/pbp.py +16 -0
- bumble/profiles/vcs.py +2 -2
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +19 -18
- bumble/sdp.py +12 -11
- bumble/smp.py +20 -30
- bumble/snoop.py +2 -1
- bumble/tools/generate_company_id_list.py +1 -1
- bumble/tools/intel_util.py +2 -2
- bumble/tools/rtk_fw_download.py +1 -1
- bumble/tools/rtk_util.py +1 -1
- bumble/transport/__init__.py +1 -2
- bumble/transport/android_emulator.py +2 -3
- bumble/transport/android_netsim.py +49 -40
- bumble/transport/common.py +9 -9
- bumble/transport/file.py +1 -2
- bumble/transport/hci_socket.py +2 -3
- bumble/transport/pty.py +3 -5
- bumble/transport/pyusb.py +8 -5
- bumble/transport/serial.py +1 -2
- bumble/transport/vhci.py +1 -2
- bumble/transport/ws_server.py +2 -3
- bumble/utils.py +22 -9
- bumble/vendor/android/hci.py +4 -2
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/METADATA +3 -2
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/RECORD +102 -101
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/profiles/hap.py
CHANGED
|
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
import asyncio
|
|
21
21
|
import logging
|
|
22
22
|
from dataclasses import dataclass, field
|
|
23
|
-
from typing import Any
|
|
23
|
+
from typing import Any
|
|
24
24
|
|
|
25
25
|
from bumble import att, gatt, gatt_adapters, gatt_client, utils
|
|
26
26
|
from bumble.core import InvalidArgumentError, InvalidStateError
|
|
@@ -145,7 +145,7 @@ class PresetChangedOperation:
|
|
|
145
145
|
return bytes([self.prev_index]) + bytes(self.preset_record)
|
|
146
146
|
|
|
147
147
|
change_id: ChangeId
|
|
148
|
-
additional_parameters:
|
|
148
|
+
additional_parameters: Generic | int
|
|
149
149
|
|
|
150
150
|
def to_bytes(self, is_last: bool) -> bytes:
|
|
151
151
|
if isinstance(self.additional_parameters, PresetChangedOperation.Generic):
|
|
@@ -235,7 +235,7 @@ class HearingAccessService(gatt.TemplateService):
|
|
|
235
235
|
preset_records: dict[int, PresetRecord] # key is the preset index
|
|
236
236
|
read_presets_request_in_progress: bool
|
|
237
237
|
|
|
238
|
-
other_server_in_binaural_set:
|
|
238
|
+
other_server_in_binaural_set: HearingAccessService | None = None
|
|
239
239
|
|
|
240
240
|
preset_changed_operations_history_per_device: dict[
|
|
241
241
|
Address, list[PresetChangedOperation]
|
|
@@ -20,7 +20,6 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import struct
|
|
22
22
|
from enum import IntEnum
|
|
23
|
-
from typing import Optional
|
|
24
23
|
|
|
25
24
|
from bumble import core
|
|
26
25
|
from bumble.att import ATT_Error
|
|
@@ -207,13 +206,13 @@ class HeartRateService(TemplateService):
|
|
|
207
206
|
class HeartRateServiceProxy(ProfileServiceProxy):
|
|
208
207
|
SERVICE_CLASS = HeartRateService
|
|
209
208
|
|
|
210
|
-
heart_rate_measurement:
|
|
211
|
-
CharacteristicProxy[HeartRateService.HeartRateMeasurement]
|
|
212
|
-
|
|
213
|
-
body_sensor_location:
|
|
214
|
-
CharacteristicProxy[HeartRateService.BodySensorLocation]
|
|
215
|
-
|
|
216
|
-
heart_rate_control_point:
|
|
209
|
+
heart_rate_measurement: (
|
|
210
|
+
CharacteristicProxy[HeartRateService.HeartRateMeasurement] | None
|
|
211
|
+
)
|
|
212
|
+
body_sensor_location: (
|
|
213
|
+
CharacteristicProxy[HeartRateService.BodySensorLocation] | None
|
|
214
|
+
)
|
|
215
|
+
heart_rate_control_point: CharacteristicProxy[int] | None
|
|
217
216
|
|
|
218
217
|
def __init__(self, service_proxy):
|
|
219
218
|
self.service_proxy = service_proxy
|
bumble/profiles/le_audio.py
CHANGED
bumble/profiles/mcp.py
CHANGED
|
@@ -22,7 +22,7 @@ import asyncio
|
|
|
22
22
|
import dataclasses
|
|
23
23
|
import enum
|
|
24
24
|
import struct
|
|
25
|
-
from typing import TYPE_CHECKING, ClassVar
|
|
25
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
26
26
|
|
|
27
27
|
from typing_extensions import Self
|
|
28
28
|
|
|
@@ -196,7 +196,7 @@ class MediaControlService(gatt.TemplateService):
|
|
|
196
196
|
|
|
197
197
|
UUID = gatt.GATT_MEDIA_CONTROL_SERVICE
|
|
198
198
|
|
|
199
|
-
def __init__(self, media_player_name:
|
|
199
|
+
def __init__(self, media_player_name: str | None = None) -> None:
|
|
200
200
|
self.track_position = 0
|
|
201
201
|
|
|
202
202
|
self.media_player_name_characteristic = gatt.Characteristic(
|
|
@@ -337,32 +337,32 @@ class MediaControlServiceProxy(
|
|
|
337
337
|
EVENT_TRACK_DURATION = "track_duration"
|
|
338
338
|
EVENT_TRACK_POSITION = "track_position"
|
|
339
339
|
|
|
340
|
-
media_player_name:
|
|
341
|
-
media_player_icon_object_id:
|
|
342
|
-
media_player_icon_url:
|
|
343
|
-
track_changed:
|
|
344
|
-
track_title:
|
|
345
|
-
track_duration:
|
|
346
|
-
track_position:
|
|
347
|
-
playback_speed:
|
|
348
|
-
seeking_speed:
|
|
349
|
-
current_track_segments_object_id:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
current_track_object_id:
|
|
353
|
-
next_track_object_id:
|
|
354
|
-
parent_group_object_id:
|
|
355
|
-
current_group_object_id:
|
|
356
|
-
playing_order:
|
|
357
|
-
playing_orders_supported:
|
|
358
|
-
media_state:
|
|
359
|
-
media_control_point:
|
|
360
|
-
media_control_point_opcodes_supported:
|
|
361
|
-
gatt_client.CharacteristicProxy[bytes]
|
|
362
|
-
|
|
363
|
-
search_control_point:
|
|
364
|
-
search_results_object_id:
|
|
365
|
-
content_control_id:
|
|
340
|
+
media_player_name: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
341
|
+
media_player_icon_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
342
|
+
media_player_icon_url: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
343
|
+
track_changed: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
344
|
+
track_title: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
345
|
+
track_duration: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
346
|
+
track_position: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
347
|
+
playback_speed: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
348
|
+
seeking_speed: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
349
|
+
current_track_segments_object_id: gatt_client.CharacteristicProxy[bytes] | None = (
|
|
350
|
+
None
|
|
351
|
+
)
|
|
352
|
+
current_track_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
353
|
+
next_track_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
354
|
+
parent_group_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
355
|
+
current_group_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
356
|
+
playing_order: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
357
|
+
playing_orders_supported: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
358
|
+
media_state: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
359
|
+
media_control_point: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
360
|
+
media_control_point_opcodes_supported: (
|
|
361
|
+
gatt_client.CharacteristicProxy[bytes] | None
|
|
362
|
+
) = None
|
|
363
|
+
search_control_point: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
364
|
+
search_results_object_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
365
|
+
content_control_id: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
366
366
|
|
|
367
367
|
if TYPE_CHECKING:
|
|
368
368
|
media_control_point_notifications: asyncio.Queue[bytes]
|
bumble/profiles/pacs.py
CHANGED
|
@@ -21,7 +21,7 @@ from __future__ import annotations
|
|
|
21
21
|
import dataclasses
|
|
22
22
|
import logging
|
|
23
23
|
import struct
|
|
24
|
-
from
|
|
24
|
+
from collections.abc import Sequence
|
|
25
25
|
|
|
26
26
|
from bumble import gatt, gatt_adapters, gatt_client, hci
|
|
27
27
|
from bumble.profiles import le_audio
|
|
@@ -39,7 +39,7 @@ class PacRecord:
|
|
|
39
39
|
'''Published Audio Capabilities Service, Table 3.2/3.4.'''
|
|
40
40
|
|
|
41
41
|
coding_format: hci.CodingFormat
|
|
42
|
-
codec_specific_capabilities:
|
|
42
|
+
codec_specific_capabilities: CodecSpecificCapabilities | bytes
|
|
43
43
|
metadata: le_audio.Metadata = dataclasses.field(default_factory=le_audio.Metadata)
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
@@ -56,7 +56,7 @@ class PacRecord:
|
|
|
56
56
|
offset += 1
|
|
57
57
|
metadata = le_audio.Metadata.from_bytes(data[offset : offset + metadata_size])
|
|
58
58
|
|
|
59
|
-
codec_specific_capabilities:
|
|
59
|
+
codec_specific_capabilities: CodecSpecificCapabilities | bytes
|
|
60
60
|
if coding_format.codec_id == hci.CodecID.VENDOR_SPECIFIC:
|
|
61
61
|
codec_specific_capabilities = codec_specific_capabilities_bytes
|
|
62
62
|
else:
|
|
@@ -101,10 +101,10 @@ class PacRecord:
|
|
|
101
101
|
class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|
102
102
|
UUID = gatt.GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE
|
|
103
103
|
|
|
104
|
-
sink_pac:
|
|
105
|
-
sink_audio_locations:
|
|
106
|
-
source_pac:
|
|
107
|
-
source_audio_locations:
|
|
104
|
+
sink_pac: gatt.Characteristic[bytes] | None
|
|
105
|
+
sink_audio_locations: gatt.Characteristic[bytes] | None
|
|
106
|
+
source_pac: gatt.Characteristic[bytes] | None
|
|
107
|
+
source_audio_locations: gatt.Characteristic[bytes] | None
|
|
108
108
|
available_audio_contexts: gatt.Characteristic[bytes]
|
|
109
109
|
supported_audio_contexts: gatt.Characteristic[bytes]
|
|
110
110
|
|
|
@@ -115,9 +115,9 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|
|
115
115
|
available_source_context: ContextType,
|
|
116
116
|
available_sink_context: ContextType,
|
|
117
117
|
sink_pac: Sequence[PacRecord] = (),
|
|
118
|
-
sink_audio_locations:
|
|
118
|
+
sink_audio_locations: AudioLocation | None = None,
|
|
119
119
|
source_pac: Sequence[PacRecord] = (),
|
|
120
|
-
source_audio_locations:
|
|
120
|
+
source_audio_locations: AudioLocation | None = None,
|
|
121
121
|
) -> None:
|
|
122
122
|
characteristics = []
|
|
123
123
|
|
|
@@ -183,14 +183,10 @@ class PublishedAudioCapabilitiesService(gatt.TemplateService):
|
|
|
183
183
|
class PublishedAudioCapabilitiesServiceProxy(gatt_client.ProfileServiceProxy):
|
|
184
184
|
SERVICE_CLASS = PublishedAudioCapabilitiesService
|
|
185
185
|
|
|
186
|
-
sink_pac:
|
|
187
|
-
sink_audio_locations:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
source_pac: Optional[gatt_client.CharacteristicProxy[list[PacRecord]]] = None
|
|
191
|
-
source_audio_locations: Optional[gatt_client.CharacteristicProxy[AudioLocation]] = (
|
|
192
|
-
None
|
|
193
|
-
)
|
|
186
|
+
sink_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None
|
|
187
|
+
sink_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None
|
|
188
|
+
source_pac: gatt_client.CharacteristicProxy[list[PacRecord]] | None = None
|
|
189
|
+
source_audio_locations: gatt_client.CharacteristicProxy[AudioLocation] | None = None
|
|
194
190
|
available_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
|
195
191
|
supported_audio_contexts: gatt_client.CharacteristicProxy[tuple[ContextType, ...]]
|
|
196
192
|
|
bumble/profiles/pbp.py
CHANGED
|
@@ -22,6 +22,7 @@ import enum
|
|
|
22
22
|
|
|
23
23
|
from typing_extensions import Self
|
|
24
24
|
|
|
25
|
+
from bumble import core, data_types, gatt
|
|
25
26
|
from bumble.profiles import le_audio
|
|
26
27
|
|
|
27
28
|
|
|
@@ -46,3 +47,18 @@ class PublicBroadcastAnnouncement:
|
|
|
46
47
|
return cls(
|
|
47
48
|
features=features, metadata=le_audio.Metadata.from_bytes(metadata_ltv)
|
|
48
49
|
)
|
|
50
|
+
|
|
51
|
+
def get_advertising_data(self) -> bytes:
|
|
52
|
+
return bytes(
|
|
53
|
+
core.AdvertisingData(
|
|
54
|
+
[
|
|
55
|
+
data_types.ServiceData16BitUUID(
|
|
56
|
+
gatt.GATT_PUBLIC_BROADCAST_ANNOUNCEMENT_SERVICE, bytes(self)
|
|
57
|
+
)
|
|
58
|
+
]
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def __bytes__(self) -> bytes:
|
|
63
|
+
metadata_bytes = bytes(self.metadata)
|
|
64
|
+
return bytes([self.features, len(metadata_bytes)]) + metadata_bytes
|
bumble/profiles/vcs.py
CHANGED
|
@@ -20,9 +20,9 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import dataclasses
|
|
22
22
|
import enum
|
|
23
|
-
from
|
|
23
|
+
from collections.abc import Sequence
|
|
24
24
|
|
|
25
|
-
from bumble import att, device, gatt, gatt_adapters, gatt_client
|
|
25
|
+
from bumble import att, device, gatt, gatt_adapters, gatt_client
|
|
26
26
|
|
|
27
27
|
# -----------------------------------------------------------------------------
|
|
28
28
|
# Constants
|
bumble/profiles/vocs.py
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
# -----------------------------------------------------------------------------
|
|
19
19
|
import struct
|
|
20
20
|
from dataclasses import dataclass
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
23
22
|
from bumble import utils
|
|
24
23
|
from bumble.att import ATT_Error
|
|
@@ -69,7 +68,7 @@ class ErrorCode(utils.OpenIntEnum):
|
|
|
69
68
|
class VolumeOffsetState:
|
|
70
69
|
volume_offset: int = 0
|
|
71
70
|
change_counter: int = 0
|
|
72
|
-
attribute:
|
|
71
|
+
attribute: Characteristic | None = None
|
|
73
72
|
|
|
74
73
|
def __bytes__(self) -> bytes:
|
|
75
74
|
return struct.pack('<hB', self.volume_offset, self.change_counter)
|
|
@@ -93,7 +92,7 @@ class VolumeOffsetState:
|
|
|
93
92
|
@dataclass
|
|
94
93
|
class VocsAudioLocation:
|
|
95
94
|
audio_location: AudioLocation = AudioLocation.NOT_ALLOWED
|
|
96
|
-
attribute:
|
|
95
|
+
attribute: Characteristic | None = None
|
|
97
96
|
|
|
98
97
|
def __bytes__(self) -> bytes:
|
|
99
98
|
return struct.pack('<I', self.audio_location)
|
|
@@ -118,7 +117,6 @@ class VolumeOffsetControlPoint:
|
|
|
118
117
|
volume_offset_state: VolumeOffsetState
|
|
119
118
|
|
|
120
119
|
async def on_write(self, connection: Connection, value: bytes) -> None:
|
|
121
|
-
|
|
122
120
|
opcode = value[0]
|
|
123
121
|
if opcode != SetVolumeOffsetOpCode.SET_VOLUME_OFFSET:
|
|
124
122
|
raise ATT_Error(ErrorCode.OPCODE_NOT_SUPPORTED)
|
|
@@ -148,7 +146,7 @@ class VolumeOffsetControlPoint:
|
|
|
148
146
|
@dataclass
|
|
149
147
|
class AudioOutputDescription:
|
|
150
148
|
audio_output_description: str = ''
|
|
151
|
-
attribute:
|
|
149
|
+
attribute: Characteristic | None = None
|
|
152
150
|
|
|
153
151
|
@classmethod
|
|
154
152
|
def from_bytes(cls, data: bytes):
|
|
@@ -173,11 +171,10 @@ class VolumeOffsetControlService(TemplateService):
|
|
|
173
171
|
|
|
174
172
|
def __init__(
|
|
175
173
|
self,
|
|
176
|
-
volume_offset_state:
|
|
177
|
-
audio_location:
|
|
178
|
-
audio_output_description:
|
|
174
|
+
volume_offset_state: VolumeOffsetState | None = None,
|
|
175
|
+
audio_location: VocsAudioLocation | None = None,
|
|
176
|
+
audio_output_description: AudioOutputDescription | None = None,
|
|
179
177
|
) -> None:
|
|
180
|
-
|
|
181
178
|
self.volume_offset_state = (
|
|
182
179
|
VolumeOffsetState() if volume_offset_state is None else volume_offset_state
|
|
183
180
|
)
|
bumble/rfcomm.py
CHANGED
|
@@ -22,7 +22,8 @@ import collections
|
|
|
22
22
|
import dataclasses
|
|
23
23
|
import enum
|
|
24
24
|
import logging
|
|
25
|
-
from
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
26
27
|
|
|
27
28
|
from typing_extensions import Self
|
|
28
29
|
|
|
@@ -119,7 +120,7 @@ RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30
|
|
|
119
120
|
|
|
120
121
|
# -----------------------------------------------------------------------------
|
|
121
122
|
def make_service_sdp_records(
|
|
122
|
-
service_record_handle: int, channel: int, uuid:
|
|
123
|
+
service_record_handle: int, channel: int, uuid: UUID | None = None
|
|
123
124
|
) -> list[sdp.ServiceAttribute]:
|
|
124
125
|
"""
|
|
125
126
|
Create SDP records for an RFComm service given a channel number and an
|
|
@@ -186,7 +187,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
|
|
|
186
187
|
)
|
|
187
188
|
for attribute_lists in search_result:
|
|
188
189
|
service_classes: list[UUID] = []
|
|
189
|
-
channel:
|
|
190
|
+
channel: int | None = None
|
|
190
191
|
for attribute in attribute_lists:
|
|
191
192
|
# The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
|
|
192
193
|
if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
|
@@ -207,7 +208,7 @@ async def find_rfcomm_channels(connection: Connection) -> dict[int, list[UUID]]:
|
|
|
207
208
|
# -----------------------------------------------------------------------------
|
|
208
209
|
async def find_rfcomm_channel_with_uuid(
|
|
209
210
|
connection: Connection, uuid: str | UUID
|
|
210
|
-
) ->
|
|
211
|
+
) -> int | None:
|
|
211
212
|
"""Searches an RFCOMM channel associated with given UUID from service records.
|
|
212
213
|
|
|
213
214
|
Args:
|
|
@@ -473,15 +474,15 @@ class DLC(utils.EventEmitter):
|
|
|
473
474
|
self.state = DLC.State.INIT
|
|
474
475
|
self.role = multiplexer.role
|
|
475
476
|
self.c_r = 1 if self.role == Multiplexer.Role.INITIATOR else 0
|
|
476
|
-
self.connection_result:
|
|
477
|
-
self.disconnection_result:
|
|
477
|
+
self.connection_result: asyncio.Future | None = None
|
|
478
|
+
self.disconnection_result: asyncio.Future | None = None
|
|
478
479
|
self.drained = asyncio.Event()
|
|
479
480
|
self.drained.set()
|
|
480
481
|
# Queued packets when sink is not set.
|
|
481
482
|
self._enqueued_rx_packets: collections.deque[bytes] = collections.deque(
|
|
482
483
|
maxlen=DEFAULT_RX_QUEUE_SIZE
|
|
483
484
|
)
|
|
484
|
-
self._sink:
|
|
485
|
+
self._sink: Callable[[bytes], None] | None = None
|
|
485
486
|
|
|
486
487
|
# Compute the MTU
|
|
487
488
|
max_overhead = 4 + 1 # header with 2-byte length + fcs
|
|
@@ -490,11 +491,11 @@ class DLC(utils.EventEmitter):
|
|
|
490
491
|
)
|
|
491
492
|
|
|
492
493
|
@property
|
|
493
|
-
def sink(self) ->
|
|
494
|
+
def sink(self) -> Callable[[bytes], None] | None:
|
|
494
495
|
return self._sink
|
|
495
496
|
|
|
496
497
|
@sink.setter
|
|
497
|
-
def sink(self, sink:
|
|
498
|
+
def sink(self, sink: Callable[[bytes], None] | None) -> None:
|
|
498
499
|
self._sink = sink
|
|
499
500
|
# Dump queued packets to sink
|
|
500
501
|
if sink:
|
|
@@ -712,7 +713,7 @@ class DLC(utils.EventEmitter):
|
|
|
712
713
|
self.drained.set()
|
|
713
714
|
|
|
714
715
|
# Stream protocol
|
|
715
|
-
def write(self, data:
|
|
716
|
+
def write(self, data: bytes | str) -> None:
|
|
716
717
|
# We can only send bytes
|
|
717
718
|
if not isinstance(data, bytes):
|
|
718
719
|
if isinstance(data, str):
|
|
@@ -769,10 +770,10 @@ class Multiplexer(utils.EventEmitter):
|
|
|
769
770
|
|
|
770
771
|
EVENT_DLC = "dlc"
|
|
771
772
|
|
|
772
|
-
connection_result:
|
|
773
|
-
disconnection_result:
|
|
774
|
-
open_result:
|
|
775
|
-
acceptor:
|
|
773
|
+
connection_result: asyncio.Future | None
|
|
774
|
+
disconnection_result: asyncio.Future | None
|
|
775
|
+
open_result: asyncio.Future | None
|
|
776
|
+
acceptor: Callable[[int], tuple[int, int] | None] | None
|
|
776
777
|
dlcs: dict[int, DLC]
|
|
777
778
|
|
|
778
779
|
def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
|
|
@@ -784,7 +785,7 @@ class Multiplexer(utils.EventEmitter):
|
|
|
784
785
|
self.connection_result = None
|
|
785
786
|
self.disconnection_result = None
|
|
786
787
|
self.open_result = None
|
|
787
|
-
self.open_pn:
|
|
788
|
+
self.open_pn: RFCOMM_MCC_PN | None = None
|
|
788
789
|
self.open_rx_max_credits = 0
|
|
789
790
|
self.acceptor = None
|
|
790
791
|
|
|
@@ -1031,8 +1032,8 @@ class Multiplexer(utils.EventEmitter):
|
|
|
1031
1032
|
|
|
1032
1033
|
# -----------------------------------------------------------------------------
|
|
1033
1034
|
class Client:
|
|
1034
|
-
multiplexer:
|
|
1035
|
-
l2cap_channel:
|
|
1035
|
+
multiplexer: Multiplexer | None
|
|
1036
|
+
l2cap_channel: l2cap.ClassicChannel | None
|
|
1036
1037
|
|
|
1037
1038
|
def __init__(
|
|
1038
1039
|
self, connection: Connection, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
|
|
@@ -1145,7 +1146,7 @@ class Server(utils.EventEmitter):
|
|
|
1145
1146
|
# Notify
|
|
1146
1147
|
self.emit(self.EVENT_START, multiplexer)
|
|
1147
1148
|
|
|
1148
|
-
def accept_dlc(self, channel_number: int) ->
|
|
1149
|
+
def accept_dlc(self, channel_number: int) -> tuple[int, int] | None:
|
|
1149
1150
|
return self.dlc_configs.get(channel_number)
|
|
1150
1151
|
|
|
1151
1152
|
def on_dlc(self, dlc: DLC) -> None:
|
bumble/sdp.py
CHANGED
|
@@ -20,7 +20,8 @@ from __future__ import annotations
|
|
|
20
20
|
import asyncio
|
|
21
21
|
import logging
|
|
22
22
|
import struct
|
|
23
|
-
from
|
|
23
|
+
from collections.abc import Iterable, Sequence
|
|
24
|
+
from typing import TYPE_CHECKING, NewType
|
|
24
25
|
|
|
25
26
|
from typing_extensions import Self
|
|
26
27
|
|
|
@@ -497,7 +498,7 @@ class ServiceAttribute:
|
|
|
497
498
|
@staticmethod
|
|
498
499
|
def find_attribute_in_list(
|
|
499
500
|
attribute_list: Iterable[ServiceAttribute], attribute_id: int
|
|
500
|
-
) ->
|
|
501
|
+
) -> DataElement | None:
|
|
501
502
|
return next(
|
|
502
503
|
(
|
|
503
504
|
attribute.value
|
|
@@ -528,7 +529,7 @@ class ServiceAttribute:
|
|
|
528
529
|
def to_string(self, with_colors=False):
|
|
529
530
|
if with_colors:
|
|
530
531
|
return (
|
|
531
|
-
f'Attribute(id={color(self.id_name(self.id),"magenta")},'
|
|
532
|
+
f'Attribute(id={color(self.id_name(self.id), "magenta")},'
|
|
532
533
|
f'value={self.value})'
|
|
533
534
|
)
|
|
534
535
|
|
|
@@ -778,11 +779,11 @@ class SDP_ServiceSearchAttributeResponse(SDP_PDU):
|
|
|
778
779
|
class Client:
|
|
779
780
|
def __init__(self, connection: Connection, mtu: int = 0) -> None:
|
|
780
781
|
self.connection = connection
|
|
781
|
-
self.channel:
|
|
782
|
+
self.channel: l2cap.ClassicChannel | None = None
|
|
782
783
|
self.mtu = mtu
|
|
783
784
|
self.request_semaphore = asyncio.Semaphore(1)
|
|
784
|
-
self.pending_request:
|
|
785
|
-
self.pending_response:
|
|
785
|
+
self.pending_request: SDP_PDU | None = None
|
|
786
|
+
self.pending_response: asyncio.futures.Future[SDP_PDU] | None = None
|
|
786
787
|
self.next_transaction_id = 0
|
|
787
788
|
|
|
788
789
|
async def connect(self) -> None:
|
|
@@ -898,7 +899,7 @@ class Client:
|
|
|
898
899
|
async def search_attributes(
|
|
899
900
|
self,
|
|
900
901
|
uuids: Iterable[core.UUID],
|
|
901
|
-
attribute_ids: Iterable[
|
|
902
|
+
attribute_ids: Iterable[int | tuple[int, int]],
|
|
902
903
|
) -> list[list[ServiceAttribute]]:
|
|
903
904
|
"""
|
|
904
905
|
Search for attributes by UUID and attribute IDs.
|
|
@@ -970,7 +971,7 @@ class Client:
|
|
|
970
971
|
async def get_attributes(
|
|
971
972
|
self,
|
|
972
973
|
service_record_handle: int,
|
|
973
|
-
attribute_ids: Iterable[
|
|
974
|
+
attribute_ids: Iterable[int | tuple[int, int]],
|
|
974
975
|
) -> list[ServiceAttribute]:
|
|
975
976
|
"""
|
|
976
977
|
Get attributes for a service.
|
|
@@ -1042,10 +1043,10 @@ class Client:
|
|
|
1042
1043
|
# -----------------------------------------------------------------------------
|
|
1043
1044
|
class Server:
|
|
1044
1045
|
CONTINUATION_STATE = bytes([0x01, 0x00])
|
|
1045
|
-
channel:
|
|
1046
|
+
channel: l2cap.ClassicChannel | None
|
|
1046
1047
|
Service = NewType('Service', list[ServiceAttribute])
|
|
1047
1048
|
service_records: dict[int, Service]
|
|
1048
|
-
current_response:
|
|
1049
|
+
current_response: None | bytes | tuple[int, list[int]]
|
|
1049
1050
|
|
|
1050
1051
|
def __init__(self, device: Device) -> None:
|
|
1051
1052
|
self.device = device
|
|
@@ -1123,7 +1124,7 @@ class Server:
|
|
|
1123
1124
|
self,
|
|
1124
1125
|
continuation_state: bytes,
|
|
1125
1126
|
transaction_id: int,
|
|
1126
|
-
) ->
|
|
1127
|
+
) -> bool | None:
|
|
1127
1128
|
# Check if this is a valid continuation
|
|
1128
1129
|
if len(continuation_state) > 1:
|
|
1129
1130
|
if (
|