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/keys.py
CHANGED
|
@@ -28,11 +28,11 @@ import json
|
|
|
28
28
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
|
|
29
29
|
from typing_extensions import Self
|
|
30
30
|
|
|
31
|
-
from .colors import color
|
|
32
|
-
from .hci import Address
|
|
31
|
+
from bumble.colors import color
|
|
32
|
+
from bumble.hci import Address
|
|
33
33
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
35
|
-
from .device import Device
|
|
35
|
+
from bumble.device import Device
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
# -----------------------------------------------------------------------------
|
bumble/l2cap.py
CHANGED
|
@@ -23,7 +23,6 @@ import logging
|
|
|
23
23
|
import struct
|
|
24
24
|
|
|
25
25
|
from collections import deque
|
|
26
|
-
from pyee import EventEmitter
|
|
27
26
|
from typing import (
|
|
28
27
|
Dict,
|
|
29
28
|
Type,
|
|
@@ -39,16 +38,16 @@ from typing import (
|
|
|
39
38
|
TYPE_CHECKING,
|
|
40
39
|
)
|
|
41
40
|
|
|
42
|
-
from
|
|
43
|
-
from .colors import color
|
|
44
|
-
from .core import (
|
|
41
|
+
from bumble import utils
|
|
42
|
+
from bumble.colors import color
|
|
43
|
+
from bumble.core import (
|
|
45
44
|
InvalidStateError,
|
|
46
45
|
InvalidArgumentError,
|
|
47
46
|
InvalidPacketError,
|
|
48
47
|
OutOfResourcesError,
|
|
49
48
|
ProtocolError,
|
|
50
49
|
)
|
|
51
|
-
from .hci import (
|
|
50
|
+
from bumble.hci import (
|
|
52
51
|
HCI_LE_Connection_Update_Command,
|
|
53
52
|
HCI_Object,
|
|
54
53
|
Role,
|
|
@@ -720,7 +719,7 @@ class L2CAP_LE_Flow_Control_Credit(L2CAP_Control_Frame):
|
|
|
720
719
|
|
|
721
720
|
|
|
722
721
|
# -----------------------------------------------------------------------------
|
|
723
|
-
class ClassicChannel(EventEmitter):
|
|
722
|
+
class ClassicChannel(utils.EventEmitter):
|
|
724
723
|
class State(enum.IntEnum):
|
|
725
724
|
# States
|
|
726
725
|
CLOSED = 0x00
|
|
@@ -821,8 +820,8 @@ class ClassicChannel(EventEmitter):
|
|
|
821
820
|
|
|
822
821
|
# Wait for the connection to succeed or fail
|
|
823
822
|
try:
|
|
824
|
-
return await
|
|
825
|
-
'disconnection', self.connection_result
|
|
823
|
+
return await utils.cancel_on_event(
|
|
824
|
+
self.connection, 'disconnection', self.connection_result
|
|
826
825
|
)
|
|
827
826
|
finally:
|
|
828
827
|
self.connection_result = None
|
|
@@ -1026,7 +1025,7 @@ class ClassicChannel(EventEmitter):
|
|
|
1026
1025
|
|
|
1027
1026
|
|
|
1028
1027
|
# -----------------------------------------------------------------------------
|
|
1029
|
-
class LeCreditBasedChannel(EventEmitter):
|
|
1028
|
+
class LeCreditBasedChannel(utils.EventEmitter):
|
|
1030
1029
|
"""
|
|
1031
1030
|
LE Credit-based Connection Oriented Channel
|
|
1032
1031
|
"""
|
|
@@ -1381,7 +1380,7 @@ class LeCreditBasedChannel(EventEmitter):
|
|
|
1381
1380
|
|
|
1382
1381
|
|
|
1383
1382
|
# -----------------------------------------------------------------------------
|
|
1384
|
-
class ClassicChannelServer(EventEmitter):
|
|
1383
|
+
class ClassicChannelServer(utils.EventEmitter):
|
|
1385
1384
|
def __init__(
|
|
1386
1385
|
self,
|
|
1387
1386
|
manager: ChannelManager,
|
|
@@ -1406,7 +1405,7 @@ class ClassicChannelServer(EventEmitter):
|
|
|
1406
1405
|
|
|
1407
1406
|
|
|
1408
1407
|
# -----------------------------------------------------------------------------
|
|
1409
|
-
class LeCreditBasedChannelServer(EventEmitter):
|
|
1408
|
+
class LeCreditBasedChannelServer(utils.EventEmitter):
|
|
1410
1409
|
def __init__(
|
|
1411
1410
|
self,
|
|
1412
1411
|
manager: ChannelManager,
|
|
@@ -1521,6 +1520,9 @@ class ChannelManager:
|
|
|
1521
1520
|
|
|
1522
1521
|
def next_identifier(self, connection: Connection) -> int:
|
|
1523
1522
|
identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
|
|
1523
|
+
# 0x00 is an invalid ID (BT Core Spec, Vol 3, Part A, Sect 4
|
|
1524
|
+
if identifier == 0:
|
|
1525
|
+
identifier = 1
|
|
1524
1526
|
self.identifiers[connection.handle] = identifier
|
|
1525
1527
|
return identifier
|
|
1526
1528
|
|
|
@@ -1533,7 +1535,7 @@ class ChannelManager:
|
|
|
1533
1535
|
if cid in self.fixed_channels:
|
|
1534
1536
|
del self.fixed_channels[cid]
|
|
1535
1537
|
|
|
1536
|
-
@deprecated("Please use create_classic_server")
|
|
1538
|
+
@utils.deprecated("Please use create_classic_server")
|
|
1537
1539
|
def register_server(
|
|
1538
1540
|
self,
|
|
1539
1541
|
psm: int,
|
|
@@ -1579,7 +1581,7 @@ class ChannelManager:
|
|
|
1579
1581
|
|
|
1580
1582
|
return self.servers[spec.psm]
|
|
1581
1583
|
|
|
1582
|
-
@deprecated("Please use create_le_credit_based_server()")
|
|
1584
|
+
@utils.deprecated("Please use create_le_credit_based_server()")
|
|
1583
1585
|
def register_le_coc_server(
|
|
1584
1586
|
self,
|
|
1585
1587
|
psm: int,
|
|
@@ -2123,7 +2125,7 @@ class ChannelManager:
|
|
|
2123
2125
|
if channel.source_cid in connection_channels:
|
|
2124
2126
|
del connection_channels[channel.source_cid]
|
|
2125
2127
|
|
|
2126
|
-
@deprecated("Please use create_le_credit_based_channel()")
|
|
2128
|
+
@utils.deprecated("Please use create_le_credit_based_channel()")
|
|
2127
2129
|
async def open_le_coc(
|
|
2128
2130
|
self, connection: Connection, psm: int, max_credits: int, mtu: int, mps: int
|
|
2129
2131
|
) -> LeCreditBasedChannel:
|
|
@@ -2180,7 +2182,7 @@ class ChannelManager:
|
|
|
2180
2182
|
|
|
2181
2183
|
return channel
|
|
2182
2184
|
|
|
2183
|
-
@deprecated("Please use create_classic_channel()")
|
|
2185
|
+
@utils.deprecated("Please use create_classic_channel()")
|
|
2184
2186
|
async def connect(self, connection: Connection, psm: int) -> ClassicChannel:
|
|
2185
2187
|
return await self.create_classic_channel(
|
|
2186
2188
|
connection=connection, spec=ClassicChannelSpec(psm=psm)
|
|
@@ -2230,12 +2232,12 @@ class ChannelManager:
|
|
|
2230
2232
|
|
|
2231
2233
|
|
|
2232
2234
|
class Channel(ClassicChannel):
|
|
2233
|
-
@deprecated("Please use ClassicChannel")
|
|
2235
|
+
@utils.deprecated("Please use ClassicChannel")
|
|
2234
2236
|
def __init__(self, *args, **kwargs) -> None:
|
|
2235
2237
|
super().__init__(*args, **kwargs)
|
|
2236
2238
|
|
|
2237
2239
|
|
|
2238
2240
|
class LeConnectionOrientedChannel(LeCreditBasedChannel):
|
|
2239
|
-
@deprecated("Please use LeCreditBasedChannel")
|
|
2241
|
+
@utils.deprecated("Please use LeCreditBasedChannel")
|
|
2240
2242
|
def __init__(self, *args, **kwargs) -> None:
|
|
2241
2243
|
super().__init__(*args, **kwargs)
|
bumble/link.py
CHANGED
|
@@ -20,8 +20,7 @@ import asyncio
|
|
|
20
20
|
from functools import partial
|
|
21
21
|
|
|
22
22
|
from bumble.core import (
|
|
23
|
-
|
|
24
|
-
BT_LE_TRANSPORT,
|
|
23
|
+
PhysicalTransport,
|
|
25
24
|
InvalidStateError,
|
|
26
25
|
)
|
|
27
26
|
from bumble.colors import color
|
|
@@ -116,10 +115,10 @@ class LocalLink:
|
|
|
116
115
|
|
|
117
116
|
def send_acl_data(self, sender_controller, destination_address, transport, data):
|
|
118
117
|
# Send the data to the first controller with a matching address
|
|
119
|
-
if transport ==
|
|
118
|
+
if transport == PhysicalTransport.LE:
|
|
120
119
|
destination_controller = self.find_controller(destination_address)
|
|
121
120
|
source_address = sender_controller.random_address
|
|
122
|
-
elif transport ==
|
|
121
|
+
elif transport == PhysicalTransport.BR_EDR:
|
|
123
122
|
destination_controller = self.find_classic_controller(destination_address)
|
|
124
123
|
source_address = sender_controller.public_address
|
|
125
124
|
else:
|
bumble/pairing.py
CHANGED
|
@@ -20,14 +20,14 @@ import enum
|
|
|
20
20
|
from dataclasses import dataclass
|
|
21
21
|
from typing import Optional, Tuple
|
|
22
22
|
|
|
23
|
-
from .hci import (
|
|
23
|
+
from bumble.hci import (
|
|
24
24
|
Address,
|
|
25
25
|
HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
|
|
26
26
|
HCI_DISPLAY_ONLY_IO_CAPABILITY,
|
|
27
27
|
HCI_DISPLAY_YES_NO_IO_CAPABILITY,
|
|
28
28
|
HCI_KEYBOARD_ONLY_IO_CAPABILITY,
|
|
29
29
|
)
|
|
30
|
-
from .smp import (
|
|
30
|
+
from bumble.smp import (
|
|
31
31
|
SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
|
|
32
32
|
SMP_KEYBOARD_ONLY_IO_CAPABILITY,
|
|
33
33
|
SMP_DISPLAY_ONLY_IO_CAPABILITY,
|
|
@@ -41,7 +41,7 @@ from .smp import (
|
|
|
41
41
|
OobLegacyContext,
|
|
42
42
|
OobSharedData,
|
|
43
43
|
)
|
|
44
|
-
from .core import AdvertisingData, LeRole
|
|
44
|
+
from bumble.core import AdvertisingData, LeRole
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
# -----------------------------------------------------------------------------
|
bumble/pandora/__init__.py
CHANGED
|
@@ -22,11 +22,11 @@ __version__ = "0.0.1"
|
|
|
22
22
|
import grpc
|
|
23
23
|
import grpc.aio
|
|
24
24
|
|
|
25
|
-
from .config import Config
|
|
26
|
-
from .device import PandoraDevice
|
|
27
|
-
from .host import HostService
|
|
28
|
-
from .l2cap import L2CAPService
|
|
29
|
-
from .security import SecurityService, SecurityStorageService
|
|
25
|
+
from bumble.pandora.config import Config
|
|
26
|
+
from bumble.pandora.device import PandoraDevice
|
|
27
|
+
from bumble.pandora.host import HostService
|
|
28
|
+
from bumble.pandora.l2cap import L2CAPService
|
|
29
|
+
from bumble.pandora.security import SecurityService, SecurityStorageService
|
|
30
30
|
from pandora.host_grpc_aio import add_HostServicer_to_server
|
|
31
31
|
from pandora.l2cap_grpc_aio import add_L2CAPServicer_to_server
|
|
32
32
|
from pandora.security_grpc_aio import (
|
bumble/pandora/host.py
CHANGED
|
@@ -20,11 +20,11 @@ import grpc.aio
|
|
|
20
20
|
import logging
|
|
21
21
|
import struct
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
from .
|
|
23
|
+
import bumble.utils
|
|
24
|
+
from bumble.pandora import utils
|
|
25
|
+
from bumble.pandora.config import Config
|
|
25
26
|
from bumble.core import (
|
|
26
|
-
|
|
27
|
-
BT_LE_TRANSPORT,
|
|
27
|
+
PhysicalTransport,
|
|
28
28
|
UUID,
|
|
29
29
|
AdvertisingData,
|
|
30
30
|
Appearance,
|
|
@@ -185,7 +185,7 @@ class HostService(HostServicer):
|
|
|
185
185
|
|
|
186
186
|
try:
|
|
187
187
|
connection = await self.device.connect(
|
|
188
|
-
address, transport=
|
|
188
|
+
address, transport=PhysicalTransport.BR_EDR
|
|
189
189
|
)
|
|
190
190
|
except ConnectionError as e:
|
|
191
191
|
if e.error_code == HCI_PAGE_TIMEOUT_ERROR:
|
|
@@ -218,7 +218,7 @@ class HostService(HostServicer):
|
|
|
218
218
|
self.log.debug(f"WaitConnection from {address}...")
|
|
219
219
|
|
|
220
220
|
connection = self.device.find_connection_by_bd_addr(
|
|
221
|
-
address, transport=
|
|
221
|
+
address, transport=PhysicalTransport.BR_EDR
|
|
222
222
|
)
|
|
223
223
|
if connection and id(connection) in self.waited_connections:
|
|
224
224
|
# this connection was already returned: wait for a new one.
|
|
@@ -250,7 +250,7 @@ class HostService(HostServicer):
|
|
|
250
250
|
try:
|
|
251
251
|
connection = await self.device.connect(
|
|
252
252
|
address,
|
|
253
|
-
transport=
|
|
253
|
+
transport=PhysicalTransport.LE,
|
|
254
254
|
own_address_type=OwnAddressType(request.own_address_type),
|
|
255
255
|
)
|
|
256
256
|
except ConnectionError as e:
|
|
@@ -378,7 +378,7 @@ class HostService(HostServicer):
|
|
|
378
378
|
|
|
379
379
|
def on_connection(connection: bumble.device.Connection) -> None:
|
|
380
380
|
if (
|
|
381
|
-
connection.transport ==
|
|
381
|
+
connection.transport == PhysicalTransport.LE
|
|
382
382
|
and connection.role == Role.PERIPHERAL
|
|
383
383
|
):
|
|
384
384
|
connections.put_nowait(connection)
|
|
@@ -496,7 +496,7 @@ class HostService(HostServicer):
|
|
|
496
496
|
|
|
497
497
|
def on_connection(connection: bumble.device.Connection) -> None:
|
|
498
498
|
if (
|
|
499
|
-
connection.transport ==
|
|
499
|
+
connection.transport == PhysicalTransport.LE
|
|
500
500
|
and connection.role == Role.PERIPHERAL
|
|
501
501
|
):
|
|
502
502
|
connections.put_nowait(connection)
|
|
@@ -535,7 +535,9 @@ class HostService(HostServicer):
|
|
|
535
535
|
|
|
536
536
|
try:
|
|
537
537
|
self.log.debug('Stop advertising')
|
|
538
|
-
await
|
|
538
|
+
await bumble.utils.cancel_on_event(
|
|
539
|
+
self.device, 'flush', self.device.stop_advertising()
|
|
540
|
+
)
|
|
539
541
|
except:
|
|
540
542
|
pass
|
|
541
543
|
|
|
@@ -603,7 +605,9 @@ class HostService(HostServicer):
|
|
|
603
605
|
self.device.remove_listener('advertisement', handler) # type: ignore
|
|
604
606
|
try:
|
|
605
607
|
self.log.debug('Stop scanning')
|
|
606
|
-
await
|
|
608
|
+
await bumble.utils.cancel_on_event(
|
|
609
|
+
self.device, 'flush', self.device.stop_scanning()
|
|
610
|
+
)
|
|
607
611
|
except:
|
|
608
612
|
pass
|
|
609
613
|
|
|
@@ -643,7 +647,9 @@ class HostService(HostServicer):
|
|
|
643
647
|
self.device.remove_listener('inquiry_result', result_handler) # type: ignore
|
|
644
648
|
try:
|
|
645
649
|
self.log.debug('Stop inquiry')
|
|
646
|
-
await
|
|
650
|
+
await bumble.utils.cancel_on_event(
|
|
651
|
+
self.device, 'flush', self.device.stop_discovery()
|
|
652
|
+
)
|
|
647
653
|
except:
|
|
648
654
|
pass
|
|
649
655
|
|
bumble/pandora/l2cap.py
CHANGED
|
@@ -19,8 +19,8 @@ import logging
|
|
|
19
19
|
|
|
20
20
|
from asyncio import Queue as AsyncQueue, Future
|
|
21
21
|
|
|
22
|
-
from . import utils
|
|
23
|
-
from .config import Config
|
|
22
|
+
from bumble.pandora import utils
|
|
23
|
+
from bumble.pandora.config import Config
|
|
24
24
|
from bumble.core import OutOfResourcesError, InvalidArgumentError
|
|
25
25
|
from bumble.device import Device
|
|
26
26
|
from bumble.l2cap import (
|
bumble/pandora/security.py
CHANGED
|
@@ -18,17 +18,16 @@ import contextlib
|
|
|
18
18
|
import grpc
|
|
19
19
|
import logging
|
|
20
20
|
|
|
21
|
-
from . import utils
|
|
22
|
-
from .config import Config
|
|
21
|
+
from bumble.pandora import utils
|
|
22
|
+
from bumble.pandora.config import Config
|
|
23
23
|
from bumble import hci
|
|
24
24
|
from bumble.core import (
|
|
25
|
-
|
|
26
|
-
BT_LE_TRANSPORT,
|
|
25
|
+
PhysicalTransport,
|
|
27
26
|
ProtocolError,
|
|
28
27
|
)
|
|
28
|
+
import bumble.utils
|
|
29
29
|
from bumble.device import Connection as BumbleConnection, Device
|
|
30
30
|
from bumble.hci import HCI_Error, Role
|
|
31
|
-
from bumble.utils import EventWatcher
|
|
32
31
|
from bumble.pairing import PairingConfig, PairingDelegate as BasePairingDelegate
|
|
33
32
|
from google.protobuf import any_pb2 # pytype: disable=pyi-error
|
|
34
33
|
from google.protobuf import empty_pb2 # pytype: disable=pyi-error
|
|
@@ -94,7 +93,7 @@ class PairingDelegate(BasePairingDelegate):
|
|
|
94
93
|
else:
|
|
95
94
|
# In BR/EDR, connection may not be complete,
|
|
96
95
|
# use address instead
|
|
97
|
-
assert self.connection.transport ==
|
|
96
|
+
assert self.connection.transport == PhysicalTransport.BR_EDR
|
|
98
97
|
ev.address = bytes(reversed(bytes(self.connection.peer_address)))
|
|
99
98
|
|
|
100
99
|
return ev
|
|
@@ -173,7 +172,7 @@ class PairingDelegate(BasePairingDelegate):
|
|
|
173
172
|
|
|
174
173
|
async def display_number(self, number: int, digits: int = 6) -> None:
|
|
175
174
|
if (
|
|
176
|
-
self.connection.transport ==
|
|
175
|
+
self.connection.transport == PhysicalTransport.BR_EDR
|
|
177
176
|
and self.io_capability == BasePairingDelegate.DISPLAY_OUTPUT_ONLY
|
|
178
177
|
):
|
|
179
178
|
return
|
|
@@ -286,7 +285,7 @@ class SecurityService(SecurityServicer):
|
|
|
286
285
|
|
|
287
286
|
oneof = request.WhichOneof('level')
|
|
288
287
|
level = getattr(request, oneof)
|
|
289
|
-
assert {
|
|
288
|
+
assert {PhysicalTransport.BR_EDR: 'classic', PhysicalTransport.LE: 'le'}[
|
|
290
289
|
connection.transport
|
|
291
290
|
] == oneof
|
|
292
291
|
|
|
@@ -301,7 +300,7 @@ class SecurityService(SecurityServicer):
|
|
|
301
300
|
|
|
302
301
|
security_result = asyncio.get_running_loop().create_future()
|
|
303
302
|
|
|
304
|
-
with contextlib.closing(EventWatcher()) as watcher:
|
|
303
|
+
with contextlib.closing(bumble.utils.EventWatcher()) as watcher:
|
|
305
304
|
|
|
306
305
|
@watcher.on(connection, 'pairing')
|
|
307
306
|
def on_pairing(*_: Any) -> None:
|
|
@@ -316,7 +315,7 @@ class SecurityService(SecurityServicer):
|
|
|
316
315
|
security_result.set_result('connection_died')
|
|
317
316
|
|
|
318
317
|
if (
|
|
319
|
-
connection.transport ==
|
|
318
|
+
connection.transport == PhysicalTransport.LE
|
|
320
319
|
and connection.role == Role.PERIPHERAL
|
|
321
320
|
):
|
|
322
321
|
connection.request_pairing()
|
|
@@ -378,7 +377,7 @@ class SecurityService(SecurityServicer):
|
|
|
378
377
|
|
|
379
378
|
assert request.level
|
|
380
379
|
level = request.level
|
|
381
|
-
assert {
|
|
380
|
+
assert {PhysicalTransport.BR_EDR: 'classic', PhysicalTransport.LE: 'le'}[
|
|
382
381
|
connection.transport
|
|
383
382
|
] == request.level_variant()
|
|
384
383
|
|
|
@@ -426,7 +425,7 @@ class SecurityService(SecurityServicer):
|
|
|
426
425
|
self.log.debug('Wait for security: done')
|
|
427
426
|
wait_for_security.set_result('success')
|
|
428
427
|
elif (
|
|
429
|
-
connection.transport ==
|
|
428
|
+
connection.transport == PhysicalTransport.BR_EDR
|
|
430
429
|
and self.need_authentication(connection, level)
|
|
431
430
|
):
|
|
432
431
|
nonlocal authenticate_task
|
|
@@ -450,7 +449,7 @@ class SecurityService(SecurityServicer):
|
|
|
450
449
|
'security_request': pair,
|
|
451
450
|
}
|
|
452
451
|
|
|
453
|
-
with contextlib.closing(EventWatcher()) as watcher:
|
|
452
|
+
with contextlib.closing(bumble.utils.EventWatcher()) as watcher:
|
|
454
453
|
# register event handlers
|
|
455
454
|
for event, listener in listeners.items():
|
|
456
455
|
watcher.on(connection, event, listener)
|
|
@@ -504,12 +503,12 @@ class SecurityService(SecurityServicer):
|
|
|
504
503
|
return BR_LEVEL_REACHED[level](connection)
|
|
505
504
|
|
|
506
505
|
def need_pairing(self, connection: BumbleConnection, level: int) -> bool:
|
|
507
|
-
if connection.transport ==
|
|
506
|
+
if connection.transport == PhysicalTransport.LE:
|
|
508
507
|
return level >= LE_LEVEL3 and not connection.authenticated
|
|
509
508
|
return False
|
|
510
509
|
|
|
511
510
|
def need_authentication(self, connection: BumbleConnection, level: int) -> bool:
|
|
512
|
-
if connection.transport ==
|
|
511
|
+
if connection.transport == PhysicalTransport.LE:
|
|
513
512
|
return False
|
|
514
513
|
if level == LEVEL2 and connection.encryption != 0:
|
|
515
514
|
return not connection.authenticated
|
|
@@ -517,7 +516,7 @@ class SecurityService(SecurityServicer):
|
|
|
517
516
|
|
|
518
517
|
def need_encryption(self, connection: BumbleConnection, level: int) -> bool:
|
|
519
518
|
# TODO(abel): need to support MITM
|
|
520
|
-
if connection.transport ==
|
|
519
|
+
if connection.transport == PhysicalTransport.LE:
|
|
521
520
|
return level == LE_LEVEL2 and not connection.encryption
|
|
522
521
|
return level >= LEVEL2 and not connection.encryption
|
|
523
522
|
|
bumble/profiles/aics.py
CHANGED
|
@@ -48,7 +48,7 @@ from bumble.gatt_adapters import (
|
|
|
48
48
|
UTF8CharacteristicProxyAdapter,
|
|
49
49
|
)
|
|
50
50
|
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
|
|
51
|
-
from bumble
|
|
51
|
+
from bumble import utils
|
|
52
52
|
|
|
53
53
|
# -----------------------------------------------------------------------------
|
|
54
54
|
# Logging
|
|
@@ -64,7 +64,7 @@ GAIN_SETTINGS_MIN_VALUE = 0
|
|
|
64
64
|
GAIN_SETTINGS_MAX_VALUE = 255
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
class ErrorCode(OpenIntEnum):
|
|
67
|
+
class ErrorCode(utils.OpenIntEnum):
|
|
68
68
|
'''
|
|
69
69
|
Cf. 1.6 Application error codes
|
|
70
70
|
'''
|
|
@@ -76,7 +76,7 @@ class ErrorCode(OpenIntEnum):
|
|
|
76
76
|
GAIN_MODE_CHANGE_NOT_ALLOWED = 0x84
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
class Mute(OpenIntEnum):
|
|
79
|
+
class Mute(utils.OpenIntEnum):
|
|
80
80
|
'''
|
|
81
81
|
Cf. 2.2.1.2 Mute Field
|
|
82
82
|
'''
|
|
@@ -86,7 +86,7 @@ class Mute(OpenIntEnum):
|
|
|
86
86
|
DISABLED = 0x02
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
class GainMode(OpenIntEnum):
|
|
89
|
+
class GainMode(utils.OpenIntEnum):
|
|
90
90
|
'''
|
|
91
91
|
Cf. 2.2.1.3 Gain Mode
|
|
92
92
|
'''
|
|
@@ -97,7 +97,7 @@ class GainMode(OpenIntEnum):
|
|
|
97
97
|
AUTOMATIC = 0x03
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
class AudioInputStatus(OpenIntEnum):
|
|
100
|
+
class AudioInputStatus(utils.OpenIntEnum):
|
|
101
101
|
'''
|
|
102
102
|
Cf. 3.4 Audio Input Status
|
|
103
103
|
'''
|
|
@@ -106,7 +106,7 @@ class AudioInputStatus(OpenIntEnum):
|
|
|
106
106
|
ACTIVE = 0x01
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
class AudioInputControlPointOpCode(OpenIntEnum):
|
|
109
|
+
class AudioInputControlPointOpCode(utils.OpenIntEnum):
|
|
110
110
|
'''
|
|
111
111
|
Cf. 3.5.1 Audio Input Control Point procedure requirements
|
|
112
112
|
'''
|
bumble/profiles/ancs.py
CHANGED
|
@@ -28,7 +28,6 @@ import logging
|
|
|
28
28
|
import struct
|
|
29
29
|
from typing import Optional, Sequence, Union
|
|
30
30
|
|
|
31
|
-
from pyee import EventEmitter
|
|
32
31
|
|
|
33
32
|
from bumble.att import ATT_Error
|
|
34
33
|
from bumble.device import Peer
|
|
@@ -42,7 +41,7 @@ from bumble.gatt import (
|
|
|
42
41
|
)
|
|
43
42
|
from bumble.gatt_client import CharacteristicProxy, ProfileServiceProxy, ServiceProxy
|
|
44
43
|
from bumble.gatt_adapters import SerializableCharacteristicProxyAdapter
|
|
45
|
-
from bumble
|
|
44
|
+
from bumble import utils
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
# -----------------------------------------------------------------------------
|
|
@@ -60,16 +59,16 @@ logger = logging.getLogger(__name__)
|
|
|
60
59
|
# -----------------------------------------------------------------------------
|
|
61
60
|
# Protocol
|
|
62
61
|
# -----------------------------------------------------------------------------
|
|
63
|
-
class ActionId(OpenIntEnum):
|
|
62
|
+
class ActionId(utils.OpenIntEnum):
|
|
64
63
|
POSITIVE = 0
|
|
65
64
|
NEGATIVE = 1
|
|
66
65
|
|
|
67
66
|
|
|
68
|
-
class AppAttributeId(OpenIntEnum):
|
|
67
|
+
class AppAttributeId(utils.OpenIntEnum):
|
|
69
68
|
DISPLAY_NAME = 0
|
|
70
69
|
|
|
71
70
|
|
|
72
|
-
class CategoryId(OpenIntEnum):
|
|
71
|
+
class CategoryId(utils.OpenIntEnum):
|
|
73
72
|
OTHER = 0
|
|
74
73
|
INCOMING_CALL = 1
|
|
75
74
|
MISSED_CALL = 2
|
|
@@ -84,13 +83,13 @@ class CategoryId(OpenIntEnum):
|
|
|
84
83
|
ENTERTAINMENT = 11
|
|
85
84
|
|
|
86
85
|
|
|
87
|
-
class CommandId(OpenIntEnum):
|
|
86
|
+
class CommandId(utils.OpenIntEnum):
|
|
88
87
|
GET_NOTIFICATION_ATTRIBUTES = 0
|
|
89
88
|
GET_APP_ATTRIBUTES = 1
|
|
90
89
|
PERFORM_NOTIFICATION_ACTION = 2
|
|
91
90
|
|
|
92
91
|
|
|
93
|
-
class EventId(OpenIntEnum):
|
|
92
|
+
class EventId(utils.OpenIntEnum):
|
|
94
93
|
NOTIFICATION_ADDED = 0
|
|
95
94
|
NOTIFICATION_MODIFIED = 1
|
|
96
95
|
NOTIFICATION_REMOVED = 2
|
|
@@ -104,7 +103,7 @@ class EventFlags(enum.IntFlag):
|
|
|
104
103
|
NEGATIVE_ACTION = 1 << 4
|
|
105
104
|
|
|
106
105
|
|
|
107
|
-
class NotificationAttributeId(OpenIntEnum):
|
|
106
|
+
class NotificationAttributeId(utils.OpenIntEnum):
|
|
108
107
|
APP_IDENTIFIER = 0
|
|
109
108
|
TITLE = 1
|
|
110
109
|
SUBTITLE = 2
|
|
@@ -156,7 +155,7 @@ class Notification:
|
|
|
156
155
|
)
|
|
157
156
|
|
|
158
157
|
|
|
159
|
-
class ErrorCode(OpenIntEnum):
|
|
158
|
+
class ErrorCode(utils.OpenIntEnum):
|
|
160
159
|
UNKNOWN_COMMAND = 0xA0
|
|
161
160
|
INVALID_COMMAND = 0xA1
|
|
162
161
|
INVALID_PARAMETER = 0xA2
|
|
@@ -243,7 +242,7 @@ class AncsProxy(ProfileServiceProxy):
|
|
|
243
242
|
)
|
|
244
243
|
|
|
245
244
|
|
|
246
|
-
class AncsClient(EventEmitter):
|
|
245
|
+
class AncsClient(utils.EventEmitter):
|
|
247
246
|
_expected_response_command_id: Optional[CommandId]
|
|
248
247
|
_expected_response_notification_uid: Optional[int]
|
|
249
248
|
_expected_response_app_identifier: Optional[str]
|
bumble/profiles/ascs.py
CHANGED
|
@@ -23,6 +23,7 @@ import logging
|
|
|
23
23
|
import struct
|
|
24
24
|
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
|
|
25
25
|
|
|
26
|
+
from bumble import utils
|
|
26
27
|
from bumble import colors
|
|
27
28
|
from bumble.profiles.bap import CodecSpecificConfiguration
|
|
28
29
|
from bumble.profiles import le_audio
|
|
@@ -343,8 +344,10 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
343
344
|
and cis_id == self.cis_id
|
|
344
345
|
and self.state == self.State.ENABLING
|
|
345
346
|
):
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
utils.cancel_on_event(
|
|
348
|
+
acl_connection,
|
|
349
|
+
'flush',
|
|
350
|
+
self.service.device.accept_cis_request(cis_handle),
|
|
348
351
|
)
|
|
349
352
|
|
|
350
353
|
def on_cis_establishment(self, cis_link: device.CisLink) -> None:
|
|
@@ -361,7 +364,9 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
361
364
|
self.state = self.State.STREAMING
|
|
362
365
|
await self.service.device.notify_subscribers(self, self.value)
|
|
363
366
|
|
|
364
|
-
|
|
367
|
+
utils.cancel_on_event(
|
|
368
|
+
cis_link.acl_connection, 'flush', post_cis_established()
|
|
369
|
+
)
|
|
365
370
|
self.cis_link = cis_link
|
|
366
371
|
|
|
367
372
|
def on_cis_disconnection(self, _reason) -> None:
|
|
@@ -509,7 +514,7 @@ class AseStateMachine(gatt.Characteristic):
|
|
|
509
514
|
self.state = self.State.IDLE
|
|
510
515
|
await self.service.device.notify_subscribers(self, self.value)
|
|
511
516
|
|
|
512
|
-
self.service.device
|
|
517
|
+
utils.cancel_on_event(self.service.device, 'flush', remove_cis_async())
|
|
513
518
|
return (AseResponseCode.SUCCESS, AseReasonCode.NONE)
|
|
514
519
|
|
|
515
520
|
@property
|
|
@@ -594,7 +599,7 @@ class AudioStreamControlService(gatt.TemplateService):
|
|
|
594
599
|
UUID = gatt.GATT_AUDIO_STREAM_CONTROL_SERVICE
|
|
595
600
|
|
|
596
601
|
ase_state_machines: Dict[int, AseStateMachine]
|
|
597
|
-
ase_control_point: gatt.Characteristic
|
|
602
|
+
ase_control_point: gatt.Characteristic[bytes]
|
|
598
603
|
_active_client: Optional[device.Connection] = None
|
|
599
604
|
|
|
600
605
|
def __init__(
|
|
@@ -691,7 +696,8 @@ class AudioStreamControlService(gatt.TemplateService):
|
|
|
691
696
|
control_point_notification = bytes(
|
|
692
697
|
[operation.op_code, len(responses)]
|
|
693
698
|
) + b''.join(map(bytes, responses))
|
|
694
|
-
|
|
699
|
+
utils.cancel_on_event(
|
|
700
|
+
self.device,
|
|
695
701
|
'flush',
|
|
696
702
|
self.device.notify_subscribers(
|
|
697
703
|
self.ase_control_point, control_point_notification
|
|
@@ -700,7 +706,8 @@ class AudioStreamControlService(gatt.TemplateService):
|
|
|
700
706
|
|
|
701
707
|
for ase_id, *_ in responses:
|
|
702
708
|
if ase := self.ase_state_machines.get(ase_id):
|
|
703
|
-
|
|
709
|
+
utils.cancel_on_event(
|
|
710
|
+
self.device,
|
|
704
711
|
'flush',
|
|
705
712
|
self.device.notify_subscribers(ase, ase.value),
|
|
706
713
|
)
|
|
@@ -710,9 +717,9 @@ class AudioStreamControlService(gatt.TemplateService):
|
|
|
710
717
|
class AudioStreamControlServiceProxy(gatt_client.ProfileServiceProxy):
|
|
711
718
|
SERVICE_CLASS = AudioStreamControlService
|
|
712
719
|
|
|
713
|
-
sink_ase: List[gatt_client.CharacteristicProxy]
|
|
714
|
-
source_ase: List[gatt_client.CharacteristicProxy]
|
|
715
|
-
ase_control_point: gatt_client.CharacteristicProxy
|
|
720
|
+
sink_ase: List[gatt_client.CharacteristicProxy[bytes]]
|
|
721
|
+
source_ase: List[gatt_client.CharacteristicProxy[bytes]]
|
|
722
|
+
ase_control_point: gatt_client.CharacteristicProxy[bytes]
|
|
716
723
|
|
|
717
724
|
def __init__(self, service_proxy: gatt_client.ServiceProxy):
|
|
718
725
|
self.service_proxy = service_proxy
|
bumble/profiles/asha.py
CHANGED
|
@@ -259,11 +259,11 @@ class AshaService(gatt.TemplateService):
|
|
|
259
259
|
# -----------------------------------------------------------------------------
|
|
260
260
|
class AshaServiceProxy(gatt_client.ProfileServiceProxy):
|
|
261
261
|
SERVICE_CLASS = AshaService
|
|
262
|
-
read_only_properties_characteristic: gatt_client.CharacteristicProxy
|
|
263
|
-
audio_control_point_characteristic: gatt_client.CharacteristicProxy
|
|
264
|
-
audio_status_point_characteristic: gatt_client.CharacteristicProxy
|
|
265
|
-
volume_characteristic: gatt_client.CharacteristicProxy
|
|
266
|
-
psm_characteristic: gatt_client.CharacteristicProxy
|
|
262
|
+
read_only_properties_characteristic: gatt_client.CharacteristicProxy[bytes]
|
|
263
|
+
audio_control_point_characteristic: gatt_client.CharacteristicProxy[bytes]
|
|
264
|
+
audio_status_point_characteristic: gatt_client.CharacteristicProxy[bytes]
|
|
265
|
+
volume_characteristic: gatt_client.CharacteristicProxy[bytes]
|
|
266
|
+
psm_characteristic: gatt_client.CharacteristicProxy[bytes]
|
|
267
267
|
|
|
268
268
|
def __init__(self, service_proxy: gatt_client.ServiceProxy) -> None:
|
|
269
269
|
self.service_proxy = service_proxy
|
bumble/profiles/bass.py
CHANGED
|
@@ -354,7 +354,7 @@ class BroadcastAudioScanService(gatt.TemplateService):
|
|
|
354
354
|
class BroadcastAudioScanServiceProxy(gatt_client.ProfileServiceProxy):
|
|
355
355
|
SERVICE_CLASS = BroadcastAudioScanService
|
|
356
356
|
|
|
357
|
-
broadcast_audio_scan_control_point: gatt_client.CharacteristicProxy
|
|
357
|
+
broadcast_audio_scan_control_point: gatt_client.CharacteristicProxy[bytes]
|
|
358
358
|
broadcast_receive_states: list[
|
|
359
359
|
gatt_client.CharacteristicProxy[Optional[BroadcastReceiveState]]
|
|
360
360
|
]
|