bumble 0.0.218__py3-none-any.whl → 0.0.220__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 +57 -18
- bumble/apps/auracast.py +7 -13
- bumble/audio/io.py +3 -3
- bumble/avctp.py +8 -12
- bumble/avdtp.py +584 -533
- bumble/avrcp.py +20 -20
- bumble/controller.py +993 -507
- bumble/device.py +107 -183
- bumble/drivers/rtk.py +3 -1
- bumble/hci.py +38 -0
- bumble/hid.py +1 -1
- bumble/link.py +68 -165
- bumble/lmp.py +324 -0
- bumble/rfcomm.py +7 -3
- bumble/snoop.py +10 -4
- bumble/transport/common.py +6 -3
- bumble/transport/ws_client.py +2 -2
- bumble/transport/ws_server.py +16 -8
- bumble/utils.py +1 -5
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/METADATA +3 -3
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/RECORD +26 -25
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/WHEEL +0 -0
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.218.dist-info → bumble-0.0.220.dist-info}/top_level.txt +0 -0
bumble/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.220'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 220)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
bumble/a2dp.py
CHANGED
|
@@ -21,11 +21,12 @@ import dataclasses
|
|
|
21
21
|
import enum
|
|
22
22
|
import logging
|
|
23
23
|
import struct
|
|
24
|
-
from collections.abc import AsyncGenerator
|
|
25
|
-
from typing import
|
|
24
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
25
|
+
from typing import Union
|
|
26
26
|
|
|
27
27
|
from typing_extensions import ClassVar, Self
|
|
28
28
|
|
|
29
|
+
from bumble import utils
|
|
29
30
|
from bumble.codecs import AacAudioRtpPacket
|
|
30
31
|
from bumble.company_ids import COMPANY_IDENTIFIERS
|
|
31
32
|
from bumble.core import (
|
|
@@ -59,19 +60,18 @@ logger = logging.getLogger(__name__)
|
|
|
59
60
|
# -----------------------------------------------------------------------------
|
|
60
61
|
# fmt: off
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
63
|
+
class CodecType(utils.OpenIntEnum):
|
|
64
|
+
SBC = 0x00
|
|
65
|
+
MPEG_1_2_AUDIO = 0x01
|
|
66
|
+
MPEG_2_4_AAC = 0x02
|
|
67
|
+
ATRAC_FAMILY = 0x03
|
|
68
|
+
NON_A2DP = 0xFF
|
|
69
|
+
|
|
70
|
+
A2DP_SBC_CODEC_TYPE = CodecType.SBC
|
|
71
|
+
A2DP_MPEG_1_2_AUDIO_CODEC_TYPE = CodecType.MPEG_1_2_AUDIO
|
|
72
|
+
A2DP_MPEG_2_4_AAC_CODEC_TYPE = CodecType.MPEG_2_4_AAC
|
|
73
|
+
A2DP_ATRAC_FAMILY_CODEC_TYPE = CodecType.ATRAC_FAMILY
|
|
74
|
+
A2DP_NON_A2DP_CODEC_TYPE = CodecType.NON_A2DP
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
SBC_SYNC_WORD = 0x9C
|
|
@@ -259,9 +259,48 @@ def make_audio_sink_service_sdp_records(service_record_handle, version=(1, 3)):
|
|
|
259
259
|
]
|
|
260
260
|
|
|
261
261
|
|
|
262
|
+
# -----------------------------------------------------------------------------
|
|
263
|
+
class MediaCodecInformation:
|
|
264
|
+
'''Base Media Codec Information.'''
|
|
265
|
+
|
|
266
|
+
@classmethod
|
|
267
|
+
def create(
|
|
268
|
+
cls, media_codec_type: int, data: bytes
|
|
269
|
+
) -> Union[MediaCodecInformation, bytes]:
|
|
270
|
+
if media_codec_type == CodecType.SBC:
|
|
271
|
+
return SbcMediaCodecInformation.from_bytes(data)
|
|
272
|
+
elif media_codec_type == CodecType.MPEG_2_4_AAC:
|
|
273
|
+
return AacMediaCodecInformation.from_bytes(data)
|
|
274
|
+
elif media_codec_type == CodecType.NON_A2DP:
|
|
275
|
+
vendor_media_codec_information = (
|
|
276
|
+
VendorSpecificMediaCodecInformation.from_bytes(data)
|
|
277
|
+
)
|
|
278
|
+
if (
|
|
279
|
+
vendor_class_map := A2DP_VENDOR_MEDIA_CODEC_INFORMATION_CLASSES.get(
|
|
280
|
+
vendor_media_codec_information.vendor_id
|
|
281
|
+
)
|
|
282
|
+
) and (
|
|
283
|
+
media_codec_information_class := vendor_class_map.get(
|
|
284
|
+
vendor_media_codec_information.codec_id
|
|
285
|
+
)
|
|
286
|
+
):
|
|
287
|
+
return media_codec_information_class.from_bytes(
|
|
288
|
+
vendor_media_codec_information.value
|
|
289
|
+
)
|
|
290
|
+
return vendor_media_codec_information
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def from_bytes(cls, data: bytes) -> Self:
|
|
294
|
+
del data # Unused.
|
|
295
|
+
raise NotImplementedError
|
|
296
|
+
|
|
297
|
+
def __bytes__(self) -> bytes:
|
|
298
|
+
raise NotImplementedError
|
|
299
|
+
|
|
300
|
+
|
|
262
301
|
# -----------------------------------------------------------------------------
|
|
263
302
|
@dataclasses.dataclass
|
|
264
|
-
class SbcMediaCodecInformation:
|
|
303
|
+
class SbcMediaCodecInformation(MediaCodecInformation):
|
|
265
304
|
'''
|
|
266
305
|
A2DP spec - 4.3.2 Codec Specific Information Elements
|
|
267
306
|
'''
|
|
@@ -345,7 +384,7 @@ class SbcMediaCodecInformation:
|
|
|
345
384
|
|
|
346
385
|
# -----------------------------------------------------------------------------
|
|
347
386
|
@dataclasses.dataclass
|
|
348
|
-
class AacMediaCodecInformation:
|
|
387
|
+
class AacMediaCodecInformation(MediaCodecInformation):
|
|
349
388
|
'''
|
|
350
389
|
A2DP spec - 4.5.2 Codec Specific Information Elements
|
|
351
390
|
'''
|
|
@@ -427,7 +466,7 @@ class AacMediaCodecInformation:
|
|
|
427
466
|
|
|
428
467
|
@dataclasses.dataclass
|
|
429
468
|
# -----------------------------------------------------------------------------
|
|
430
|
-
class VendorSpecificMediaCodecInformation:
|
|
469
|
+
class VendorSpecificMediaCodecInformation(MediaCodecInformation):
|
|
431
470
|
'''
|
|
432
471
|
A2DP spec - 4.7.2 Codec Specific Information Elements
|
|
433
472
|
'''
|
bumble/apps/auracast.py
CHANGED
|
@@ -742,10 +742,9 @@ async def run_receive(
|
|
|
742
742
|
]
|
|
743
743
|
packet_stats = [0, 0]
|
|
744
744
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
try:
|
|
745
|
+
async with contextlib.AsyncExitStack() as stack:
|
|
746
|
+
audio_output = await audio_io.create_audio_output(output)
|
|
747
|
+
stack.push_async_callback(audio_output.aclose)
|
|
749
748
|
await audio_output.open(
|
|
750
749
|
audio_io.PcmFormat(
|
|
751
750
|
audio_io.PcmFormat.Endianness.LITTLE,
|
|
@@ -793,8 +792,6 @@ async def run_receive(
|
|
|
793
792
|
terminated = asyncio.Event()
|
|
794
793
|
big_sync.on(big_sync.Event.TERMINATION, lambda _: terminated.set())
|
|
795
794
|
await terminated.wait()
|
|
796
|
-
finally:
|
|
797
|
-
await audio_output.aclose()
|
|
798
795
|
|
|
799
796
|
|
|
800
797
|
async def run_transmit(
|
|
@@ -891,11 +888,10 @@ async def run_transmit(
|
|
|
891
888
|
print('Start Periodic Advertising')
|
|
892
889
|
await advertising_set.start_periodic()
|
|
893
890
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
try:
|
|
891
|
+
async with contextlib.AsyncExitStack() as stack:
|
|
892
|
+
audio_input = await audio_io.create_audio_input(input, input_format)
|
|
893
|
+
pcm_format = await audio_input.open()
|
|
894
|
+
stack.push_async_callback(audio_input.aclose)
|
|
899
895
|
if pcm_format.channels != 2:
|
|
900
896
|
print("Only 2 channels PCM configurations are supported")
|
|
901
897
|
return
|
|
@@ -967,8 +963,6 @@ async def run_transmit(
|
|
|
967
963
|
await iso_queues[1].write(lc3_frame[mid:])
|
|
968
964
|
|
|
969
965
|
frame_count += 1
|
|
970
|
-
finally:
|
|
971
|
-
await audio_input.aclose()
|
|
972
966
|
|
|
973
967
|
|
|
974
968
|
def run_async(async_command: Coroutine) -> None:
|
bumble/audio/io.py
CHANGED
|
@@ -19,13 +19,13 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import abc
|
|
21
21
|
import asyncio
|
|
22
|
+
import concurrent.futures
|
|
22
23
|
import dataclasses
|
|
23
24
|
import enum
|
|
24
25
|
import logging
|
|
25
26
|
import pathlib
|
|
26
27
|
import sys
|
|
27
28
|
import wave
|
|
28
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
29
29
|
from typing import TYPE_CHECKING, AsyncGenerator, BinaryIO
|
|
30
30
|
|
|
31
31
|
from bumble.colors import color
|
|
@@ -176,7 +176,7 @@ class ThreadedAudioOutput(AudioOutput):
|
|
|
176
176
|
"""
|
|
177
177
|
|
|
178
178
|
def __init__(self) -> None:
|
|
179
|
-
self._thread_pool = ThreadPoolExecutor(1)
|
|
179
|
+
self._thread_pool = concurrent.futures.ThreadPoolExecutor(1)
|
|
180
180
|
self._pcm_samples: asyncio.Queue[bytes] = asyncio.Queue()
|
|
181
181
|
self._write_task = asyncio.create_task(self._write_loop())
|
|
182
182
|
|
|
@@ -405,7 +405,7 @@ class ThreadedAudioInput(AudioInput):
|
|
|
405
405
|
"""Base class for AudioInput implementation where reading samples may block."""
|
|
406
406
|
|
|
407
407
|
def __init__(self) -> None:
|
|
408
|
-
self._thread_pool = ThreadPoolExecutor(1)
|
|
408
|
+
self._thread_pool = concurrent.futures.ThreadPoolExecutor(1)
|
|
409
409
|
self._pcm_samples: asyncio.Queue[bytes] = asyncio.Queue()
|
|
410
410
|
|
|
411
411
|
@abc.abstractmethod
|
bumble/avctp.py
CHANGED
|
@@ -19,10 +19,11 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import struct
|
|
22
|
+
from collections.abc import Callable
|
|
22
23
|
from enum import IntEnum
|
|
23
|
-
from typing import
|
|
24
|
+
from typing import Optional
|
|
24
25
|
|
|
25
|
-
from bumble import
|
|
26
|
+
from bumble import core, l2cap
|
|
26
27
|
from bumble.colors import color
|
|
27
28
|
|
|
28
29
|
# -----------------------------------------------------------------------------
|
|
@@ -144,9 +145,9 @@ class MessageAssembler:
|
|
|
144
145
|
|
|
145
146
|
# -----------------------------------------------------------------------------
|
|
146
147
|
class Protocol:
|
|
147
|
-
CommandHandler = Callable[[int,
|
|
148
|
+
CommandHandler = Callable[[int, bytes], None]
|
|
148
149
|
command_handlers: dict[int, CommandHandler] # Command handlers, by PID
|
|
149
|
-
ResponseHandler = Callable[[int, Optional[
|
|
150
|
+
ResponseHandler = Callable[[int, Optional[bytes]], None]
|
|
150
151
|
response_handlers: dict[int, ResponseHandler] # Response handlers, by PID
|
|
151
152
|
next_transaction_label: int
|
|
152
153
|
message_assembler: MessageAssembler
|
|
@@ -204,20 +205,15 @@ class Protocol:
|
|
|
204
205
|
self.send_ipid(transaction_label, pid)
|
|
205
206
|
return
|
|
206
207
|
|
|
207
|
-
|
|
208
|
-
self.command_handlers[pid](transaction_label, command_frame)
|
|
208
|
+
self.command_handlers[pid](transaction_label, payload)
|
|
209
209
|
else:
|
|
210
210
|
if pid not in self.response_handlers:
|
|
211
211
|
logger.warning(f"no response handler for PID {pid}")
|
|
212
212
|
return
|
|
213
213
|
|
|
214
214
|
# By convention, for an ipid, send a None payload to the response handler.
|
|
215
|
-
if ipid
|
|
216
|
-
|
|
217
|
-
else:
|
|
218
|
-
response_frame = cast(avc.ResponseFrame, avc.Frame.from_bytes(payload))
|
|
219
|
-
|
|
220
|
-
self.response_handlers[pid](transaction_label, response_frame)
|
|
215
|
+
response_payload = None if ipid else payload
|
|
216
|
+
self.response_handlers[pid](transaction_label, response_payload)
|
|
221
217
|
|
|
222
218
|
def send_message(
|
|
223
219
|
self,
|