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/pandora/l2cap.py
CHANGED
|
@@ -18,15 +18,15 @@ import json
|
|
|
18
18
|
import logging
|
|
19
19
|
from asyncio import Future
|
|
20
20
|
from asyncio import Queue as AsyncQueue
|
|
21
|
+
from collections.abc import AsyncGenerator
|
|
21
22
|
from dataclasses import dataclass
|
|
22
|
-
from typing import AsyncGenerator, Optional, Union
|
|
23
23
|
|
|
24
24
|
import grpc
|
|
25
25
|
from google.protobuf import any_pb2, empty_pb2 # pytype: disable=pyi-error
|
|
26
26
|
from pandora.l2cap_grpc_aio import L2CAPServicer # pytype: disable=pyi-error
|
|
27
|
-
from pandora.l2cap_pb2 import COMMAND_NOT_UNDERSTOOD, INVALID_CID_IN_REQUEST
|
|
28
|
-
from pandora.l2cap_pb2 import Channel as PandoraChannel # pytype: disable=pyi-error
|
|
29
27
|
from pandora.l2cap_pb2 import (
|
|
28
|
+
COMMAND_NOT_UNDERSTOOD,
|
|
29
|
+
INVALID_CID_IN_REQUEST,
|
|
30
30
|
ConnectRequest,
|
|
31
31
|
ConnectResponse,
|
|
32
32
|
CreditBasedChannelRequest,
|
|
@@ -41,6 +41,7 @@ from pandora.l2cap_pb2 import (
|
|
|
41
41
|
WaitDisconnectionRequest,
|
|
42
42
|
WaitDisconnectionResponse,
|
|
43
43
|
)
|
|
44
|
+
from pandora.l2cap_pb2 import Channel as PandoraChannel # pytype: disable=pyi-error
|
|
44
45
|
|
|
45
46
|
from bumble.core import InvalidArgumentError, OutOfResourcesError
|
|
46
47
|
from bumble.device import Device
|
|
@@ -55,7 +56,7 @@ from bumble.l2cap import (
|
|
|
55
56
|
from bumble.pandora import utils
|
|
56
57
|
from bumble.pandora.config import Config
|
|
57
58
|
|
|
58
|
-
L2capChannel =
|
|
59
|
+
L2capChannel = ClassicChannel | LeCreditBasedChannel
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
@dataclass
|
|
@@ -106,10 +107,8 @@ class L2CAPService(L2CAPServicer):
|
|
|
106
107
|
oneof = request.WhichOneof('type')
|
|
107
108
|
self.log.debug(f'WaitConnection channel request type: {oneof}.')
|
|
108
109
|
channel_type = getattr(request, oneof)
|
|
109
|
-
spec:
|
|
110
|
-
l2cap_server:
|
|
111
|
-
Union[ClassicChannelServer, LeCreditBasedChannelServer]
|
|
112
|
-
] = None
|
|
110
|
+
spec: ClassicChannelSpec | LeCreditBasedChannelSpec | None = None
|
|
111
|
+
l2cap_server: ClassicChannelServer | LeCreditBasedChannelServer | None = None
|
|
113
112
|
if isinstance(channel_type, CreditBasedChannelRequest):
|
|
114
113
|
spec = LeCreditBasedChannelSpec(
|
|
115
114
|
psm=channel_type.spsm,
|
|
@@ -216,7 +215,7 @@ class L2CAPService(L2CAPServicer):
|
|
|
216
215
|
oneof = request.WhichOneof('type')
|
|
217
216
|
self.log.debug(f'Channel request type: {oneof}.')
|
|
218
217
|
channel_type = getattr(request, oneof)
|
|
219
|
-
spec:
|
|
218
|
+
spec: ClassicChannelSpec | LeCreditBasedChannelSpec | None = None
|
|
220
219
|
if isinstance(channel_type, CreditBasedChannelRequest):
|
|
221
220
|
spec = LeCreditBasedChannelSpec(
|
|
222
221
|
psm=channel_type.spsm,
|
bumble/pandora/security.py
CHANGED
|
@@ -17,13 +17,15 @@ from __future__ import annotations
|
|
|
17
17
|
import asyncio
|
|
18
18
|
import contextlib
|
|
19
19
|
import logging
|
|
20
|
-
from collections.abc import Awaitable
|
|
21
|
-
from typing import Any
|
|
20
|
+
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable
|
|
21
|
+
from typing import Any
|
|
22
22
|
|
|
23
23
|
import grpc
|
|
24
|
-
from google.protobuf import
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
from google.protobuf import (
|
|
25
|
+
any_pb2, # pytype: disable=pyi-error
|
|
26
|
+
empty_pb2, # pytype: disable=pyi-error
|
|
27
|
+
wrappers_pb2, # pytype: disable=pyi-error
|
|
28
|
+
)
|
|
27
29
|
from pandora.host_pb2 import Connection
|
|
28
30
|
from pandora.security_grpc_aio import SecurityServicer, SecurityStorageServicer
|
|
29
31
|
from pandora.security_pb2 import (
|
|
@@ -64,7 +66,7 @@ class PairingDelegate(BasePairingDelegate):
|
|
|
64
66
|
def __init__(
|
|
65
67
|
self,
|
|
66
68
|
connection: BumbleConnection,
|
|
67
|
-
service:
|
|
69
|
+
service: SecurityService,
|
|
68
70
|
io_capability: BasePairingDelegate.IoCapability = BasePairingDelegate.NO_OUTPUT_NO_INPUT,
|
|
69
71
|
local_initiator_key_distribution: BasePairingDelegate.KeyDistribution = BasePairingDelegate.DEFAULT_KEY_DISTRIBUTION,
|
|
70
72
|
local_responder_key_distribution: BasePairingDelegate.KeyDistribution = BasePairingDelegate.DEFAULT_KEY_DISTRIBUTION,
|
|
@@ -130,7 +132,7 @@ class PairingDelegate(BasePairingDelegate):
|
|
|
130
132
|
assert answer.answer_variant() == 'confirm' and answer.confirm is not None
|
|
131
133
|
return answer.confirm
|
|
132
134
|
|
|
133
|
-
async def get_number(self) ->
|
|
135
|
+
async def get_number(self) -> int | None:
|
|
134
136
|
self.log.debug(
|
|
135
137
|
f"Pairing event: `passkey_entry_request` (io_capability: {self.io_capability})"
|
|
136
138
|
)
|
|
@@ -147,7 +149,7 @@ class PairingDelegate(BasePairingDelegate):
|
|
|
147
149
|
assert answer.answer_variant() == 'passkey'
|
|
148
150
|
return answer.passkey
|
|
149
151
|
|
|
150
|
-
async def get_string(self, max_length: int) ->
|
|
152
|
+
async def get_string(self, max_length: int) -> str | None:
|
|
151
153
|
self.log.debug(
|
|
152
154
|
f"Pairing event: `pin_code_request` (io_capability: {self.io_capability})"
|
|
153
155
|
)
|
|
@@ -195,8 +197,8 @@ class SecurityService(SecurityServicer):
|
|
|
195
197
|
self.log = utils.BumbleServerLoggerAdapter(
|
|
196
198
|
logging.getLogger(), {'service_name': 'Security', 'device': device}
|
|
197
199
|
)
|
|
198
|
-
self.event_queue:
|
|
199
|
-
self.event_answer:
|
|
200
|
+
self.event_queue: asyncio.Queue[PairingEvent] | None = None
|
|
201
|
+
self.event_answer: AsyncIterator[PairingEventAnswer] | None = None
|
|
200
202
|
self.device = device
|
|
201
203
|
self.config = config
|
|
202
204
|
|
|
@@ -231,7 +233,7 @@ class SecurityService(SecurityServicer):
|
|
|
231
233
|
if level == LEVEL2:
|
|
232
234
|
return connection.encryption != 0 and connection.authenticated
|
|
233
235
|
|
|
234
|
-
link_key_type:
|
|
236
|
+
link_key_type: int | None = None
|
|
235
237
|
if (keystore := connection.device.keystore) and (
|
|
236
238
|
keys := await keystore.get(str(connection.peer_address))
|
|
237
239
|
):
|
|
@@ -410,8 +412,8 @@ class SecurityService(SecurityServicer):
|
|
|
410
412
|
wait_for_security: asyncio.Future[str] = (
|
|
411
413
|
asyncio.get_running_loop().create_future()
|
|
412
414
|
)
|
|
413
|
-
authenticate_task:
|
|
414
|
-
pair_task:
|
|
415
|
+
authenticate_task: asyncio.Future[None] | None = None
|
|
416
|
+
pair_task: asyncio.Future[None] | None = None
|
|
415
417
|
|
|
416
418
|
async def authenticate() -> None:
|
|
417
419
|
if (encryption := connection.encryption) != 0:
|
|
@@ -455,9 +457,9 @@ class SecurityService(SecurityServicer):
|
|
|
455
457
|
|
|
456
458
|
def pair(*_: Any) -> None:
|
|
457
459
|
if self.need_pairing(connection, level):
|
|
458
|
-
|
|
460
|
+
bumble.utils.AsyncRunner.spawn(connection.pair())
|
|
459
461
|
|
|
460
|
-
listeners: dict[str, Callable[...,
|
|
462
|
+
listeners: dict[str, Callable[..., None | Awaitable[None]]] = {
|
|
461
463
|
'disconnection': set_failure('connection_died'),
|
|
462
464
|
'pairing_failure': set_failure('pairing_failure'),
|
|
463
465
|
'connection_authentication_failure': set_failure('authentication_failure'),
|
|
@@ -500,7 +502,7 @@ class SecurityService(SecurityServicer):
|
|
|
500
502
|
return WaitSecurityResponse(**kwargs)
|
|
501
503
|
|
|
502
504
|
async def reached_security_level(
|
|
503
|
-
self, connection: BumbleConnection, level:
|
|
505
|
+
self, connection: BumbleConnection, level: SecurityLevel | LESecurityLevel
|
|
504
506
|
) -> bool:
|
|
505
507
|
self.log.debug(
|
|
506
508
|
str(
|
bumble/pandora/utils.py
CHANGED
|
@@ -18,7 +18,8 @@ import contextlib
|
|
|
18
18
|
import functools
|
|
19
19
|
import inspect
|
|
20
20
|
import logging
|
|
21
|
-
from
|
|
21
|
+
from collections.abc import Generator, MutableMapping
|
|
22
|
+
from typing import Any
|
|
22
23
|
|
|
23
24
|
import grpc
|
|
24
25
|
from google.protobuf.message import Message # pytype: disable=pyi-error
|
|
@@ -34,7 +35,7 @@ ADDRESS_TYPES: dict[str, AddressType] = {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
def address_from_request(request: Message, field:
|
|
38
|
+
def address_from_request(request: Message, field: str | None) -> Address:
|
|
38
39
|
if field is None:
|
|
39
40
|
return Address.ANY
|
|
40
41
|
return Address(bytes(reversed(getattr(request, field))), ADDRESS_TYPES[field])
|
|
@@ -95,8 +96,7 @@ def rpc(func: Any) -> Any:
|
|
|
95
96
|
@functools.wraps(func)
|
|
96
97
|
def gen_wrapper(self: Any, request: Any, context: grpc.ServicerContext) -> Any:
|
|
97
98
|
with exception_to_rpc_error(context):
|
|
98
|
-
|
|
99
|
-
yield v
|
|
99
|
+
yield from func(self, request, context)
|
|
100
100
|
|
|
101
101
|
@functools.wraps(func)
|
|
102
102
|
def wrapper(self: Any, request: Any, context: grpc.ServicerContext) -> Any:
|
bumble/profiles/aics.py
CHANGED
|
@@ -22,7 +22,6 @@ from __future__ import annotations
|
|
|
22
22
|
import logging
|
|
23
23
|
import struct
|
|
24
24
|
from dataclasses import dataclass
|
|
25
|
-
from typing import Optional
|
|
26
25
|
|
|
27
26
|
from bumble import utils
|
|
28
27
|
from bumble.att import ATT_Error
|
|
@@ -129,7 +128,7 @@ class AudioInputState:
|
|
|
129
128
|
mute: Mute = Mute.NOT_MUTED
|
|
130
129
|
gain_mode: GainMode = GainMode.MANUAL
|
|
131
130
|
change_counter: int = 0
|
|
132
|
-
attribute:
|
|
131
|
+
attribute: Attribute | None = None
|
|
133
132
|
|
|
134
133
|
def __bytes__(self) -> bytes:
|
|
135
134
|
return bytes(
|
|
@@ -199,7 +198,6 @@ class AudioInputControlPoint:
|
|
|
199
198
|
gain_settings_properties: GainSettingsProperties
|
|
200
199
|
|
|
201
200
|
async def on_write(self, connection: Connection, value: bytes) -> None:
|
|
202
|
-
|
|
203
201
|
opcode = AudioInputControlPointOpCode(value[0])
|
|
204
202
|
|
|
205
203
|
if opcode == AudioInputControlPointOpCode.SET_GAIN_SETTING:
|
|
@@ -317,7 +315,7 @@ class AudioInputDescription:
|
|
|
317
315
|
'''
|
|
318
316
|
|
|
319
317
|
audio_input_description: str = "Bluetooth"
|
|
320
|
-
attribute:
|
|
318
|
+
attribute: Attribute | None = None
|
|
321
319
|
|
|
322
320
|
def on_read(self, _connection: Connection) -> str:
|
|
323
321
|
return self.audio_input_description
|
|
@@ -340,11 +338,11 @@ class AICSService(TemplateService):
|
|
|
340
338
|
|
|
341
339
|
def __init__(
|
|
342
340
|
self,
|
|
343
|
-
audio_input_state:
|
|
344
|
-
gain_settings_properties:
|
|
341
|
+
audio_input_state: AudioInputState | None = None,
|
|
342
|
+
gain_settings_properties: GainSettingsProperties | None = None,
|
|
345
343
|
audio_input_type: str = "local",
|
|
346
|
-
audio_input_status:
|
|
347
|
-
audio_input_description:
|
|
344
|
+
audio_input_status: AudioInputStatus | None = None,
|
|
345
|
+
audio_input_description: AudioInputDescription | None = None,
|
|
348
346
|
):
|
|
349
347
|
self.audio_input_state = (
|
|
350
348
|
AudioInputState() if audio_input_state is None else audio_input_state
|
bumble/profiles/ams.py
CHANGED
|
@@ -25,7 +25,7 @@ import asyncio
|
|
|
25
25
|
import dataclasses
|
|
26
26
|
import enum
|
|
27
27
|
import logging
|
|
28
|
-
from
|
|
28
|
+
from collections.abc import Iterable
|
|
29
29
|
|
|
30
30
|
from bumble import utils
|
|
31
31
|
from bumble.device import Peer
|
|
@@ -230,7 +230,7 @@ class AmsClient(utils.EventEmitter):
|
|
|
230
230
|
self.supported_commands = set()
|
|
231
231
|
|
|
232
232
|
@classmethod
|
|
233
|
-
async def for_peer(cls, peer: Peer) ->
|
|
233
|
+
async def for_peer(cls, peer: Peer) -> AmsClient | None:
|
|
234
234
|
ams_proxy = await peer.discover_service_and_create_proxy(AmsProxy)
|
|
235
235
|
if ams_proxy is None:
|
|
236
236
|
return None
|
|
@@ -263,9 +263,7 @@ class AmsClient(utils.EventEmitter):
|
|
|
263
263
|
async def observe(
|
|
264
264
|
self,
|
|
265
265
|
entity: EntityId,
|
|
266
|
-
attributes: Iterable[
|
|
267
|
-
Union[PlayerAttributeId, QueueAttributeId, TrackAttributeId]
|
|
268
|
-
],
|
|
266
|
+
attributes: Iterable[PlayerAttributeId | QueueAttributeId | TrackAttributeId],
|
|
269
267
|
) -> None:
|
|
270
268
|
await self._ams_proxy.entity_update.write_value(
|
|
271
269
|
bytes([entity] + list(attributes)), with_response=True
|
bumble/profiles/ancs.py
CHANGED
|
@@ -27,7 +27,7 @@ import datetime
|
|
|
27
27
|
import enum
|
|
28
28
|
import logging
|
|
29
29
|
import struct
|
|
30
|
-
from
|
|
30
|
+
from collections.abc import Sequence
|
|
31
31
|
|
|
32
32
|
from bumble import utils
|
|
33
33
|
from bumble.att import ATT_Error
|
|
@@ -116,7 +116,7 @@ class NotificationAttributeId(utils.OpenIntEnum):
|
|
|
116
116
|
@dataclasses.dataclass
|
|
117
117
|
class NotificationAttribute:
|
|
118
118
|
attribute_id: NotificationAttributeId
|
|
119
|
-
value:
|
|
119
|
+
value: str | int | datetime.datetime
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
@dataclasses.dataclass
|
|
@@ -242,10 +242,10 @@ class AncsProxy(ProfileServiceProxy):
|
|
|
242
242
|
|
|
243
243
|
|
|
244
244
|
class AncsClient(utils.EventEmitter):
|
|
245
|
-
_expected_response_command_id:
|
|
246
|
-
_expected_response_notification_uid:
|
|
247
|
-
_expected_response_app_identifier:
|
|
248
|
-
_expected_app_identifier:
|
|
245
|
+
_expected_response_command_id: CommandId | None
|
|
246
|
+
_expected_response_notification_uid: int | None
|
|
247
|
+
_expected_response_app_identifier: str | None
|
|
248
|
+
_expected_app_identifier: str | None
|
|
249
249
|
_expected_response_tuples: int
|
|
250
250
|
_response_accumulator: bytes
|
|
251
251
|
|
|
@@ -255,12 +255,12 @@ class AncsClient(utils.EventEmitter):
|
|
|
255
255
|
super().__init__()
|
|
256
256
|
self._ancs_proxy = ancs_proxy
|
|
257
257
|
self._command_semaphore = asyncio.Semaphore()
|
|
258
|
-
self._response:
|
|
258
|
+
self._response: asyncio.Future | None = None
|
|
259
259
|
self._reset_response()
|
|
260
260
|
self._started = False
|
|
261
261
|
|
|
262
262
|
@classmethod
|
|
263
|
-
async def for_peer(cls, peer: Peer) ->
|
|
263
|
+
async def for_peer(cls, peer: Peer) -> AncsClient | None:
|
|
264
264
|
ancs_proxy = await peer.discover_service_and_create_proxy(AncsProxy)
|
|
265
265
|
if ancs_proxy is None:
|
|
266
266
|
return None
|
|
@@ -316,7 +316,7 @@ class AncsClient(utils.EventEmitter):
|
|
|
316
316
|
# Not enough data yet.
|
|
317
317
|
return
|
|
318
318
|
|
|
319
|
-
attributes: list[
|
|
319
|
+
attributes: list[NotificationAttribute | AppAttribute] = []
|
|
320
320
|
|
|
321
321
|
if command_id == CommandId.GET_NOTIFICATION_ATTRIBUTES:
|
|
322
322
|
(notification_uid,) = struct.unpack_from(
|
|
@@ -342,7 +342,7 @@ class AncsClient(utils.EventEmitter):
|
|
|
342
342
|
str_value = attribute_data[3 : 3 + attribute_data_length].decode(
|
|
343
343
|
"utf-8"
|
|
344
344
|
)
|
|
345
|
-
value:
|
|
345
|
+
value: str | int | datetime.datetime
|
|
346
346
|
if attribute_id == NotificationAttributeId.MESSAGE_SIZE:
|
|
347
347
|
value = int(str_value)
|
|
348
348
|
elif attribute_id == NotificationAttributeId.DATE:
|
|
@@ -415,7 +415,7 @@ class AncsClient(utils.EventEmitter):
|
|
|
415
415
|
self,
|
|
416
416
|
notification_uid: int,
|
|
417
417
|
attributes: Sequence[
|
|
418
|
-
|
|
418
|
+
NotificationAttributeId | tuple[NotificationAttributeId, int]
|
|
419
419
|
],
|
|
420
420
|
) -> list[NotificationAttribute]:
|
|
421
421
|
if not self._started:
|
bumble/profiles/ascs.py
CHANGED
|
@@ -24,7 +24,7 @@ import logging
|
|
|
24
24
|
import struct
|
|
25
25
|
from collections.abc import Sequence
|
|
26
26
|
from dataclasses import dataclass, field
|
|
27
|
-
from typing import Any,
|
|
27
|
+
from typing import Any, TypeVar
|
|
28
28
|
|
|
29
29
|
from bumble import colors, device, gatt, gatt_client, hci, utils
|
|
30
30
|
from bumble.profiles import le_audio
|
|
@@ -49,7 +49,7 @@ class ASE_Operation:
|
|
|
49
49
|
classes: dict[int, type[ASE_Operation]] = {}
|
|
50
50
|
op_code: Opcode
|
|
51
51
|
name: str
|
|
52
|
-
fields:
|
|
52
|
+
fields: Sequence[Any] | None = None
|
|
53
53
|
ase_id: Sequence[int]
|
|
54
54
|
|
|
55
55
|
class Opcode(enum.IntEnum):
|
|
@@ -278,7 +278,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
278
278
|
|
|
279
279
|
EVENT_STATE_CHANGE = "state_change"
|
|
280
280
|
|
|
281
|
-
cis_link:
|
|
281
|
+
cis_link: device.CisLink | None = None
|
|
282
282
|
|
|
283
283
|
# Additional parameters in CODEC_CONFIGURED State
|
|
284
284
|
preferred_framing = 0 # Unframed PDU supported
|
|
@@ -290,7 +290,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
290
290
|
preferred_presentation_delay_min = 0
|
|
291
291
|
preferred_presentation_delay_max = 0
|
|
292
292
|
codec_id = hci.CodingFormat(hci.CodecID.LC3)
|
|
293
|
-
codec_specific_configuration:
|
|
293
|
+
codec_specific_configuration: CodecSpecificConfiguration | bytes = b''
|
|
294
294
|
|
|
295
295
|
# Additional parameters in QOS_CONFIGURED State
|
|
296
296
|
cig_id = 0
|
|
@@ -610,7 +610,7 @@ class AudioStreamControlService(gatt.TemplateService):
|
|
|
610
610
|
|
|
611
611
|
ase_state_machines: dict[int, AseStateMachine]
|
|
612
612
|
ase_control_point: gatt.Characteristic[bytes]
|
|
613
|
-
_active_client:
|
|
613
|
+
_active_client: device.Connection | None = None
|
|
614
614
|
|
|
615
615
|
def __init__(
|
|
616
616
|
self,
|
bumble/profiles/asha.py
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
import enum
|
|
20
20
|
import logging
|
|
21
21
|
import struct
|
|
22
|
-
from
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
from typing import Any
|
|
23
24
|
|
|
24
25
|
from bumble import data_types, gatt, gatt_client, l2cap, utils
|
|
25
26
|
from bumble.core import AdvertisingData
|
|
@@ -90,20 +91,20 @@ class AshaService(gatt.TemplateService):
|
|
|
90
91
|
EVENT_DISCONNECTED = "disconnected"
|
|
91
92
|
EVENT_VOLUME_CHANGED = "volume_changed"
|
|
92
93
|
|
|
93
|
-
audio_sink:
|
|
94
|
-
active_codec:
|
|
95
|
-
audio_type:
|
|
96
|
-
volume:
|
|
97
|
-
other_state:
|
|
98
|
-
connection:
|
|
94
|
+
audio_sink: Callable[[bytes], Any] | None
|
|
95
|
+
active_codec: Codec | None = None
|
|
96
|
+
audio_type: AudioType | None = None
|
|
97
|
+
volume: int | None = None
|
|
98
|
+
other_state: int | None = None
|
|
99
|
+
connection: Connection | None = None
|
|
99
100
|
|
|
100
101
|
def __init__(
|
|
101
102
|
self,
|
|
102
103
|
capability: int,
|
|
103
|
-
hisyncid:
|
|
104
|
+
hisyncid: list[int] | bytes,
|
|
104
105
|
device: Device,
|
|
105
106
|
psm: int = 0,
|
|
106
|
-
audio_sink:
|
|
107
|
+
audio_sink: Callable[[bytes], Any] | None = None,
|
|
107
108
|
feature_map: int = FeatureMap.LE_COC_AUDIO_OUTPUT_STREAMING_SUPPORTED,
|
|
108
109
|
protocol_version: int = 0x01,
|
|
109
110
|
render_delay_milliseconds: int = 0,
|
bumble/profiles/bass.py
CHANGED
|
@@ -21,7 +21,8 @@ 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
|
+
from typing import ClassVar
|
|
25
26
|
|
|
26
27
|
from bumble import core, device, gatt, gatt_adapters, gatt_client, hci, utils
|
|
27
28
|
|
|
@@ -337,7 +338,12 @@ class BroadcastAudioScanService(gatt.TemplateService):
|
|
|
337
338
|
b"12", # TEST
|
|
338
339
|
)
|
|
339
340
|
|
|
340
|
-
super().__init__(
|
|
341
|
+
super().__init__(
|
|
342
|
+
[
|
|
343
|
+
self.broadcast_audio_scan_control_point_characteristic,
|
|
344
|
+
self.broadcast_receive_state_characteristic,
|
|
345
|
+
]
|
|
346
|
+
)
|
|
341
347
|
|
|
342
348
|
def on_broadcast_audio_scan_control_point_write(
|
|
343
349
|
self, connection: device.Connection, value: bytes
|
|
@@ -351,7 +357,7 @@ class BroadcastAudioScanServiceProxy(gatt_client.ProfileServiceProxy):
|
|
|
351
357
|
|
|
352
358
|
broadcast_audio_scan_control_point: gatt_client.CharacteristicProxy[bytes]
|
|
353
359
|
broadcast_receive_states: list[
|
|
354
|
-
gatt_client.CharacteristicProxy[
|
|
360
|
+
gatt_client.CharacteristicProxy[BroadcastReceiveState | None]
|
|
355
361
|
]
|
|
356
362
|
|
|
357
363
|
def __init__(self, service_proxy: gatt_client.ServiceProxy):
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
# -----------------------------------------------------------------------------
|
|
17
17
|
# Imports
|
|
18
18
|
# -----------------------------------------------------------------------------
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
from bumble.gatt import (
|
|
22
21
|
GATT_BATTERY_LEVEL_CHARACTERISTIC,
|
|
@@ -56,7 +55,7 @@ class BatteryService(TemplateService):
|
|
|
56
55
|
class BatteryServiceProxy(ProfileServiceProxy):
|
|
57
56
|
SERVICE_CLASS = BatteryService
|
|
58
57
|
|
|
59
|
-
battery_level:
|
|
58
|
+
battery_level: CharacteristicProxy[int] | None
|
|
60
59
|
|
|
61
60
|
def __init__(self, service_proxy):
|
|
62
61
|
self.service_proxy = service_proxy
|
bumble/profiles/csip.py
CHANGED
|
@@ -20,7 +20,6 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import enum
|
|
22
22
|
import struct
|
|
23
|
-
from typing import Optional
|
|
24
23
|
|
|
25
24
|
from bumble import core, crypto, device, gatt, gatt_client
|
|
26
25
|
|
|
@@ -96,17 +95,17 @@ class CoordinatedSetIdentificationService(gatt.TemplateService):
|
|
|
96
95
|
|
|
97
96
|
set_identity_resolving_key: bytes
|
|
98
97
|
set_identity_resolving_key_characteristic: gatt.Characteristic[bytes]
|
|
99
|
-
coordinated_set_size_characteristic:
|
|
100
|
-
set_member_lock_characteristic:
|
|
101
|
-
set_member_rank_characteristic:
|
|
98
|
+
coordinated_set_size_characteristic: gatt.Characteristic[bytes] | None = None
|
|
99
|
+
set_member_lock_characteristic: gatt.Characteristic[bytes] | None = None
|
|
100
|
+
set_member_rank_characteristic: gatt.Characteristic[bytes] | None = None
|
|
102
101
|
|
|
103
102
|
def __init__(
|
|
104
103
|
self,
|
|
105
104
|
set_identity_resolving_key: bytes,
|
|
106
105
|
set_identity_resolving_key_type: SirkType,
|
|
107
|
-
coordinated_set_size:
|
|
108
|
-
set_member_lock:
|
|
109
|
-
set_member_rank:
|
|
106
|
+
coordinated_set_size: int | None = None,
|
|
107
|
+
set_member_lock: MemberLock | None = None,
|
|
108
|
+
set_member_rank: int | None = None,
|
|
110
109
|
) -> None:
|
|
111
110
|
if len(set_identity_resolving_key) != SET_IDENTITY_RESOLVING_KEY_LENGTH:
|
|
112
111
|
raise core.InvalidArgumentError(
|
|
@@ -198,9 +197,9 @@ class CoordinatedSetIdentificationProxy(gatt_client.ProfileServiceProxy):
|
|
|
198
197
|
SERVICE_CLASS = CoordinatedSetIdentificationService
|
|
199
198
|
|
|
200
199
|
set_identity_resolving_key: gatt_client.CharacteristicProxy[bytes]
|
|
201
|
-
coordinated_set_size:
|
|
202
|
-
set_member_lock:
|
|
203
|
-
set_member_rank:
|
|
200
|
+
coordinated_set_size: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
201
|
+
set_member_lock: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
202
|
+
set_member_rank: gatt_client.CharacteristicProxy[bytes] | None = None
|
|
204
203
|
|
|
205
204
|
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
206
205
|
self.service_proxy = service_proxy
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
# Imports
|
|
18
18
|
# -----------------------------------------------------------------------------
|
|
19
19
|
import struct
|
|
20
|
-
from typing import Optional
|
|
21
20
|
|
|
22
21
|
from bumble.gatt import (
|
|
23
22
|
GATT_DEVICE_INFORMATION_SERVICE,
|
|
@@ -54,14 +53,14 @@ class DeviceInformationService(TemplateService):
|
|
|
54
53
|
|
|
55
54
|
def __init__(
|
|
56
55
|
self,
|
|
57
|
-
manufacturer_name:
|
|
58
|
-
model_number:
|
|
59
|
-
serial_number:
|
|
60
|
-
hardware_revision:
|
|
61
|
-
firmware_revision:
|
|
62
|
-
software_revision:
|
|
63
|
-
system_id:
|
|
64
|
-
ieee_regulatory_certification_data_list:
|
|
56
|
+
manufacturer_name: str | None = None,
|
|
57
|
+
model_number: str | None = None,
|
|
58
|
+
serial_number: str | None = None,
|
|
59
|
+
hardware_revision: str | None = None,
|
|
60
|
+
firmware_revision: str | None = None,
|
|
61
|
+
software_revision: str | None = None,
|
|
62
|
+
system_id: tuple[int, int] | None = None, # (OUI, Manufacturer ID)
|
|
63
|
+
ieee_regulatory_certification_data_list: bytes | None = None,
|
|
65
64
|
# TODO: pnp_id
|
|
66
65
|
):
|
|
67
66
|
characteristics: list[Characteristic[bytes]] = [
|
|
@@ -109,14 +108,14 @@ class DeviceInformationService(TemplateService):
|
|
|
109
108
|
class DeviceInformationServiceProxy(ProfileServiceProxy):
|
|
110
109
|
SERVICE_CLASS = DeviceInformationService
|
|
111
110
|
|
|
112
|
-
manufacturer_name:
|
|
113
|
-
model_number:
|
|
114
|
-
serial_number:
|
|
115
|
-
hardware_revision:
|
|
116
|
-
firmware_revision:
|
|
117
|
-
software_revision:
|
|
118
|
-
system_id:
|
|
119
|
-
ieee_regulatory_certification_data_list:
|
|
111
|
+
manufacturer_name: CharacteristicProxy[str] | None
|
|
112
|
+
model_number: CharacteristicProxy[str] | None
|
|
113
|
+
serial_number: CharacteristicProxy[str] | None
|
|
114
|
+
hardware_revision: CharacteristicProxy[str] | None
|
|
115
|
+
firmware_revision: CharacteristicProxy[str] | None
|
|
116
|
+
software_revision: CharacteristicProxy[str] | None
|
|
117
|
+
system_id: CharacteristicProxy[tuple[int, int]] | None
|
|
118
|
+
ieee_regulatory_certification_data_list: CharacteristicProxy[bytes] | None
|
|
120
119
|
|
|
121
120
|
def __init__(self, service_proxy: ServiceProxy):
|
|
122
121
|
self.service_proxy = service_proxy
|
bumble/profiles/gap.py
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
# -----------------------------------------------------------------------------
|
|
20
20
|
import logging
|
|
21
21
|
import struct
|
|
22
|
-
from typing import Optional, Union
|
|
23
22
|
|
|
24
23
|
from bumble.core import Appearance
|
|
25
24
|
from bumble.gatt import (
|
|
@@ -54,7 +53,7 @@ class GenericAccessService(TemplateService):
|
|
|
54
53
|
appearance_characteristic: Characteristic[bytes]
|
|
55
54
|
|
|
56
55
|
def __init__(
|
|
57
|
-
self, device_name: str, appearance:
|
|
56
|
+
self, device_name: str, appearance: Appearance | tuple[int, int] | int = 0
|
|
58
57
|
):
|
|
59
58
|
if isinstance(appearance, int):
|
|
60
59
|
appearance_int = appearance
|
|
@@ -88,8 +87,8 @@ class GenericAccessService(TemplateService):
|
|
|
88
87
|
class GenericAccessServiceProxy(ProfileServiceProxy):
|
|
89
88
|
SERVICE_CLASS = GenericAccessService
|
|
90
89
|
|
|
91
|
-
device_name:
|
|
92
|
-
appearance:
|
|
90
|
+
device_name: CharacteristicProxy[str] | None
|
|
91
|
+
appearance: CharacteristicProxy[Appearance] | None
|
|
93
92
|
|
|
94
93
|
def __init__(self, service_proxy: ServiceProxy):
|
|
95
94
|
self.service_proxy = service_proxy
|
bumble/profiles/gatt_service.py
CHANGED
|
@@ -40,7 +40,6 @@ class GenericAttributeProfileService(gatt.TemplateService):
|
|
|
40
40
|
database_hash_enabled: bool = True,
|
|
41
41
|
service_change_enabled: bool = True,
|
|
42
42
|
) -> None:
|
|
43
|
-
|
|
44
43
|
if server_supported_features is not None:
|
|
45
44
|
self.server_supported_features_characteristic = gatt.Characteristic(
|
|
46
45
|
uuid=gatt.GATT_SERVER_SUPPORTED_FEATURES_CHARACTERISTIC,
|
bumble/profiles/gmap.py
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
# -----------------------------------------------------------------------------
|
|
20
20
|
import struct
|
|
21
21
|
from enum import IntFlag
|
|
22
|
-
from typing import Optional
|
|
23
22
|
|
|
24
23
|
from bumble.gatt import (
|
|
25
24
|
GATT_BGR_FEATURES_CHARACTERISTIC,
|
|
@@ -77,18 +76,18 @@ class GamingAudioService(TemplateService):
|
|
|
77
76
|
UUID = GATT_GAMING_AUDIO_SERVICE
|
|
78
77
|
|
|
79
78
|
gmap_role: Characteristic
|
|
80
|
-
ugg_features:
|
|
81
|
-
ugt_features:
|
|
82
|
-
bgs_features:
|
|
83
|
-
bgr_features:
|
|
79
|
+
ugg_features: Characteristic | None = None
|
|
80
|
+
ugt_features: Characteristic | None = None
|
|
81
|
+
bgs_features: Characteristic | None = None
|
|
82
|
+
bgr_features: Characteristic | None = None
|
|
84
83
|
|
|
85
84
|
def __init__(
|
|
86
85
|
self,
|
|
87
86
|
gmap_role: GmapRole,
|
|
88
|
-
ugg_features:
|
|
89
|
-
ugt_features:
|
|
90
|
-
bgs_features:
|
|
91
|
-
bgr_features:
|
|
87
|
+
ugg_features: UggFeatures | None = None,
|
|
88
|
+
ugt_features: UgtFeatures | None = None,
|
|
89
|
+
bgs_features: BgsFeatures | None = None,
|
|
90
|
+
bgr_features: BgrFeatures | None = None,
|
|
92
91
|
) -> None:
|
|
93
92
|
characteristics = []
|
|
94
93
|
|
|
@@ -150,10 +149,10 @@ class GamingAudioService(TemplateService):
|
|
|
150
149
|
class GamingAudioServiceProxy(ProfileServiceProxy):
|
|
151
150
|
SERVICE_CLASS = GamingAudioService
|
|
152
151
|
|
|
153
|
-
ugg_features:
|
|
154
|
-
ugt_features:
|
|
155
|
-
bgs_features:
|
|
156
|
-
bgr_features:
|
|
152
|
+
ugg_features: CharacteristicProxy[UggFeatures] | None = None
|
|
153
|
+
ugt_features: CharacteristicProxy[UgtFeatures] | None = None
|
|
154
|
+
bgs_features: CharacteristicProxy[BgsFeatures] | None = None
|
|
155
|
+
bgr_features: CharacteristicProxy[BgrFeatures] | None = None
|
|
157
156
|
|
|
158
157
|
def __init__(self, service_proxy: ServiceProxy) -> None:
|
|
159
158
|
self.service_proxy = service_proxy
|