bumble 0.0.207__py3-none-any.whl → 0.0.209__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 +9 -4
- bumble/apps/auracast.py +29 -35
- bumble/apps/bench.py +13 -10
- bumble/apps/console.py +19 -12
- bumble/apps/gg_bridge.py +1 -1
- bumble/att.py +61 -39
- bumble/controller.py +7 -8
- bumble/core.py +306 -159
- bumble/device.py +127 -82
- bumble/gatt.py +25 -228
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +38 -31
- bumble/gatt_server.py +5 -5
- bumble/hci.py +76 -71
- bumble/host.py +19 -8
- bumble/l2cap.py +2 -2
- bumble/link.py +2 -2
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +19 -23
- bumble/pandora/security.py +2 -3
- bumble/pandora/utils.py +2 -2
- bumble/profiles/aics.py +33 -23
- bumble/profiles/ancs.py +514 -0
- bumble/profiles/ascs.py +2 -1
- bumble/profiles/asha.py +11 -9
- bumble/profiles/bass.py +8 -5
- bumble/profiles/battery_service.py +13 -3
- bumble/profiles/device_information_service.py +16 -14
- bumble/profiles/gap.py +12 -8
- bumble/profiles/gatt_service.py +1 -0
- bumble/profiles/gmap.py +16 -11
- bumble/profiles/hap.py +8 -6
- bumble/profiles/heart_rate_service.py +20 -4
- bumble/profiles/mcp.py +11 -9
- bumble/profiles/pacs.py +37 -24
- bumble/profiles/tmap.py +6 -4
- bumble/profiles/vcs.py +6 -5
- bumble/profiles/vocs.py +49 -41
- bumble/smp.py +3 -3
- bumble/transport/usb.py +1 -3
- bumble/utils.py +10 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/METADATA +3 -3
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/RECORD +47 -45
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/WHEEL +1 -1
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/LICENSE +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -53,14 +53,12 @@ from pyee import EventEmitter
|
|
|
53
53
|
|
|
54
54
|
from .colors import color
|
|
55
55
|
from .att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
|
|
56
|
-
from .gatt import Characteristic, Descriptor, Service
|
|
56
|
+
from .gatt import Attribute, Characteristic, Descriptor, Service
|
|
57
57
|
from .host import DataPacketQueue, Host
|
|
58
58
|
from .profiles.gap import GenericAccessService
|
|
59
59
|
from .core import (
|
|
60
60
|
BT_BR_EDR_TRANSPORT,
|
|
61
|
-
BT_CENTRAL_ROLE,
|
|
62
61
|
BT_LE_TRANSPORT,
|
|
63
|
-
BT_PERIPHERAL_ROLE,
|
|
64
62
|
AdvertisingData,
|
|
65
63
|
BaseBumbleError,
|
|
66
64
|
ConnectionParameterUpdateError,
|
|
@@ -1555,13 +1553,13 @@ class IsoPacketStream:
|
|
|
1555
1553
|
class Connection(CompositeEventEmitter):
|
|
1556
1554
|
device: Device
|
|
1557
1555
|
handle: int
|
|
1558
|
-
transport:
|
|
1556
|
+
transport: core.PhysicalTransport
|
|
1559
1557
|
self_address: hci.Address
|
|
1560
1558
|
self_resolvable_address: Optional[hci.Address]
|
|
1561
1559
|
peer_address: hci.Address
|
|
1562
1560
|
peer_resolvable_address: Optional[hci.Address]
|
|
1563
1561
|
peer_le_features: Optional[hci.LeFeatureMask]
|
|
1564
|
-
role:
|
|
1562
|
+
role: hci.Role
|
|
1565
1563
|
encryption: int
|
|
1566
1564
|
authenticated: bool
|
|
1567
1565
|
sc: bool
|
|
@@ -1569,8 +1567,8 @@ class Connection(CompositeEventEmitter):
|
|
|
1569
1567
|
gatt_client: gatt_client.Client
|
|
1570
1568
|
pairing_peer_io_capability: Optional[int]
|
|
1571
1569
|
pairing_peer_authentication_requirements: Optional[int]
|
|
1572
|
-
cs_configs: dict[int, ChannelSoundingConfig]
|
|
1573
|
-
cs_procedures: dict[int, ChannelSoundingProcedure]
|
|
1570
|
+
cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
|
|
1571
|
+
cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
|
|
1574
1572
|
|
|
1575
1573
|
@composite_listener
|
|
1576
1574
|
class Listener:
|
|
@@ -1586,7 +1584,7 @@ class Connection(CompositeEventEmitter):
|
|
|
1586
1584
|
def on_connection_data_length_change(self):
|
|
1587
1585
|
pass
|
|
1588
1586
|
|
|
1589
|
-
def on_connection_phy_update(self):
|
|
1587
|
+
def on_connection_phy_update(self, phy):
|
|
1590
1588
|
pass
|
|
1591
1589
|
|
|
1592
1590
|
def on_connection_phy_update_failure(self, error):
|
|
@@ -1612,7 +1610,6 @@ class Connection(CompositeEventEmitter):
|
|
|
1612
1610
|
peer_resolvable_address,
|
|
1613
1611
|
role,
|
|
1614
1612
|
parameters,
|
|
1615
|
-
phy,
|
|
1616
1613
|
):
|
|
1617
1614
|
super().__init__()
|
|
1618
1615
|
self.device = device
|
|
@@ -1629,7 +1626,6 @@ class Connection(CompositeEventEmitter):
|
|
|
1629
1626
|
self.authenticated = False
|
|
1630
1627
|
self.sc = False
|
|
1631
1628
|
self.link_key_type = None
|
|
1632
|
-
self.phy = phy
|
|
1633
1629
|
self.att_mtu = ATT_DEFAULT_MTU
|
|
1634
1630
|
self.data_length = DEVICE_DEFAULT_DATA_LENGTH
|
|
1635
1631
|
self.gatt_client = None # Per-connection client
|
|
@@ -1639,6 +1635,8 @@ class Connection(CompositeEventEmitter):
|
|
|
1639
1635
|
self.pairing_peer_io_capability = None
|
|
1640
1636
|
self.pairing_peer_authentication_requirements = None
|
|
1641
1637
|
self.peer_le_features = None
|
|
1638
|
+
self.cs_configs = {}
|
|
1639
|
+
self.cs_procedures = {}
|
|
1642
1640
|
|
|
1643
1641
|
# [Classic only]
|
|
1644
1642
|
@classmethod
|
|
@@ -1658,7 +1656,6 @@ class Connection(CompositeEventEmitter):
|
|
|
1658
1656
|
None,
|
|
1659
1657
|
role,
|
|
1660
1658
|
None,
|
|
1661
|
-
None,
|
|
1662
1659
|
)
|
|
1663
1660
|
|
|
1664
1661
|
# [Classic only]
|
|
@@ -1675,9 +1672,9 @@ class Connection(CompositeEventEmitter):
|
|
|
1675
1672
|
def role_name(self):
|
|
1676
1673
|
if self.role is None:
|
|
1677
1674
|
return 'NOT-SET'
|
|
1678
|
-
if self.role ==
|
|
1675
|
+
if self.role == hci.Role.CENTRAL:
|
|
1679
1676
|
return 'CENTRAL'
|
|
1680
|
-
if self.role ==
|
|
1677
|
+
if self.role == hci.Role.PERIPHERAL:
|
|
1681
1678
|
return 'PERIPHERAL'
|
|
1682
1679
|
return f'UNKNOWN[{self.role}]'
|
|
1683
1680
|
|
|
@@ -1735,7 +1732,7 @@ class Connection(CompositeEventEmitter):
|
|
|
1735
1732
|
async def encrypt(self, enable: bool = True) -> None:
|
|
1736
1733
|
return await self.device.encrypt(self, enable)
|
|
1737
1734
|
|
|
1738
|
-
async def switch_role(self, role:
|
|
1735
|
+
async def switch_role(self, role: hci.Role) -> None:
|
|
1739
1736
|
return await self.device.switch_role(self, role)
|
|
1740
1737
|
|
|
1741
1738
|
async def sustain(self, timeout: Optional[float] = None) -> None:
|
|
@@ -1774,12 +1771,12 @@ class Connection(CompositeEventEmitter):
|
|
|
1774
1771
|
async def set_phy(self, tx_phys=None, rx_phys=None, phy_options=None):
|
|
1775
1772
|
return await self.device.set_connection_phy(self, tx_phys, rx_phys, phy_options)
|
|
1776
1773
|
|
|
1774
|
+
async def get_phy(self) -> ConnectionPHY:
|
|
1775
|
+
return await self.device.get_connection_phy(self)
|
|
1776
|
+
|
|
1777
1777
|
async def get_rssi(self):
|
|
1778
1778
|
return await self.device.get_connection_rssi(self)
|
|
1779
1779
|
|
|
1780
|
-
async def get_phy(self):
|
|
1781
|
-
return await self.device.get_connection_phy(self)
|
|
1782
|
-
|
|
1783
1780
|
async def transfer_periodic_sync(
|
|
1784
1781
|
self, sync_handle: int, service_data: int = 0
|
|
1785
1782
|
) -> None:
|
|
@@ -2049,9 +2046,9 @@ class Device(CompositeEventEmitter):
|
|
|
2049
2046
|
legacy_advertiser: Optional[LegacyAdvertiser]
|
|
2050
2047
|
sco_links: Dict[int, ScoLink]
|
|
2051
2048
|
cis_links: Dict[int, CisLink]
|
|
2052
|
-
bigs
|
|
2053
|
-
bis_links
|
|
2054
|
-
big_syncs
|
|
2049
|
+
bigs: dict[int, Big]
|
|
2050
|
+
bis_links: dict[int, BisLink]
|
|
2051
|
+
big_syncs: dict[int, BigSync]
|
|
2055
2052
|
_pending_cis: Dict[int, tuple[int, int]]
|
|
2056
2053
|
gatt_service: gatt_service.GenericAttributeProfileService | None = None
|
|
2057
2054
|
|
|
@@ -2144,6 +2141,9 @@ class Device(CompositeEventEmitter):
|
|
|
2144
2141
|
self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only)
|
|
2145
2142
|
self.cis_links = {} # CisLinks, by connection handle (LE only)
|
|
2146
2143
|
self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle
|
|
2144
|
+
self.bigs = {}
|
|
2145
|
+
self.bis_links = {}
|
|
2146
|
+
self.big_syncs = {}
|
|
2147
2147
|
self.classic_enabled = False
|
|
2148
2148
|
self.inquiry_response = None
|
|
2149
2149
|
self.address_resolver = None
|
|
@@ -2221,7 +2221,7 @@ class Device(CompositeEventEmitter):
|
|
|
2221
2221
|
permissions=descriptor["permissions"],
|
|
2222
2222
|
)
|
|
2223
2223
|
descriptors.append(new_descriptor)
|
|
2224
|
-
new_characteristic = Characteristic(
|
|
2224
|
+
new_characteristic: Characteristic[bytes] = Characteristic(
|
|
2225
2225
|
uuid=characteristic["uuid"],
|
|
2226
2226
|
properties=Characteristic.Properties.from_string(
|
|
2227
2227
|
characteristic["properties"]
|
|
@@ -2711,7 +2711,7 @@ class Device(CompositeEventEmitter):
|
|
|
2711
2711
|
if phy == hci.HCI_LE_1M_PHY:
|
|
2712
2712
|
return True
|
|
2713
2713
|
|
|
2714
|
-
feature_map = {
|
|
2714
|
+
feature_map: dict[int, hci.LeFeatureMask] = {
|
|
2715
2715
|
hci.HCI_LE_2M_PHY: hci.LeFeatureMask.LE_2M_PHY,
|
|
2716
2716
|
hci.HCI_LE_CODED_PHY: hci.LeFeatureMask.LE_CODED_PHY,
|
|
2717
2717
|
}
|
|
@@ -2732,7 +2732,7 @@ class Device(CompositeEventEmitter):
|
|
|
2732
2732
|
self,
|
|
2733
2733
|
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
|
2734
2734
|
target: Optional[hci.Address] = None,
|
|
2735
|
-
own_address_type:
|
|
2735
|
+
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
2736
2736
|
auto_restart: bool = False,
|
|
2737
2737
|
advertising_data: Optional[bytes] = None,
|
|
2738
2738
|
scan_response_data: Optional[bytes] = None,
|
|
@@ -3013,7 +3013,7 @@ class Device(CompositeEventEmitter):
|
|
|
3013
3013
|
active: bool = True,
|
|
3014
3014
|
scan_interval: float = DEVICE_DEFAULT_SCAN_INTERVAL, # Scan interval in ms
|
|
3015
3015
|
scan_window: float = DEVICE_DEFAULT_SCAN_WINDOW, # Scan window in ms
|
|
3016
|
-
own_address_type:
|
|
3016
|
+
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
3017
3017
|
filter_duplicates: bool = False,
|
|
3018
3018
|
scanning_phys: Sequence[int] = (hci.HCI_LE_1M_PHY, hci.HCI_LE_CODED_PHY),
|
|
3019
3019
|
) -> None:
|
|
@@ -3089,7 +3089,7 @@ class Device(CompositeEventEmitter):
|
|
|
3089
3089
|
# pylint: disable=line-too-long
|
|
3090
3090
|
hci.HCI_LE_Set_Scan_Parameters_Command(
|
|
3091
3091
|
le_scan_type=scan_type,
|
|
3092
|
-
le_scan_interval=int(
|
|
3092
|
+
le_scan_interval=int(scan_interval / 0.625),
|
|
3093
3093
|
le_scan_window=int(scan_window / 0.625),
|
|
3094
3094
|
own_address_type=own_address_type,
|
|
3095
3095
|
scanning_filter_policy=hci.HCI_LE_Set_Scan_Parameters_Command.BASIC_UNFILTERED_POLICY,
|
|
@@ -3379,11 +3379,11 @@ class Device(CompositeEventEmitter):
|
|
|
3379
3379
|
async def connect(
|
|
3380
3380
|
self,
|
|
3381
3381
|
peer_address: Union[hci.Address, str],
|
|
3382
|
-
transport:
|
|
3382
|
+
transport: core.PhysicalTransport = BT_LE_TRANSPORT,
|
|
3383
3383
|
connection_parameters_preferences: Optional[
|
|
3384
|
-
|
|
3384
|
+
dict[hci.Phy, ConnectionParametersPreferences]
|
|
3385
3385
|
] = None,
|
|
3386
|
-
own_address_type:
|
|
3386
|
+
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
3387
3387
|
timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3388
3388
|
always_resolve: bool = False,
|
|
3389
3389
|
) -> Connection:
|
|
@@ -3431,6 +3431,7 @@ class Device(CompositeEventEmitter):
|
|
|
3431
3431
|
# Check parameters
|
|
3432
3432
|
if transport not in (BT_LE_TRANSPORT, BT_BR_EDR_TRANSPORT):
|
|
3433
3433
|
raise InvalidArgumentError('invalid transport')
|
|
3434
|
+
transport = core.PhysicalTransport(transport)
|
|
3434
3435
|
|
|
3435
3436
|
# Adjust the transport automatically if we need to
|
|
3436
3437
|
if transport == BT_LE_TRANSPORT and not self.le_enabled:
|
|
@@ -3626,7 +3627,7 @@ class Device(CompositeEventEmitter):
|
|
|
3626
3627
|
else:
|
|
3627
3628
|
# Save pending connection
|
|
3628
3629
|
self.pending_connections[peer_address] = Connection.incomplete(
|
|
3629
|
-
self, peer_address,
|
|
3630
|
+
self, peer_address, hci.Role.CENTRAL
|
|
3630
3631
|
)
|
|
3631
3632
|
|
|
3632
3633
|
# TODO: allow passing other settings
|
|
@@ -3681,7 +3682,7 @@ class Device(CompositeEventEmitter):
|
|
|
3681
3682
|
async def accept(
|
|
3682
3683
|
self,
|
|
3683
3684
|
peer_address: Union[hci.Address, str] = hci.Address.ANY,
|
|
3684
|
-
role:
|
|
3685
|
+
role: hci.Role = hci.Role.PERIPHERAL,
|
|
3685
3686
|
timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3686
3687
|
) -> Connection:
|
|
3687
3688
|
'''
|
|
@@ -3767,12 +3768,12 @@ class Device(CompositeEventEmitter):
|
|
|
3767
3768
|
self.on('connection', on_connection)
|
|
3768
3769
|
self.on('connection_failure', on_connection_failure)
|
|
3769
3770
|
|
|
3770
|
-
# Save pending connection, with the Peripheral role.
|
|
3771
|
+
# Save pending connection, with the Peripheral hci.role.
|
|
3771
3772
|
# Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
|
|
3772
3773
|
# command, this connection is still considered Peripheral until an eventual
|
|
3773
3774
|
# role change event.
|
|
3774
3775
|
self.pending_connections[peer_address] = Connection.incomplete(
|
|
3775
|
-
self, peer_address,
|
|
3776
|
+
self, peer_address, hci.Role.PERIPHERAL
|
|
3776
3777
|
)
|
|
3777
3778
|
|
|
3778
3779
|
try:
|
|
@@ -3901,7 +3902,7 @@ class Device(CompositeEventEmitter):
|
|
|
3901
3902
|
'''
|
|
3902
3903
|
|
|
3903
3904
|
if use_l2cap:
|
|
3904
|
-
if connection.role !=
|
|
3905
|
+
if connection.role != hci.Role.PERIPHERAL:
|
|
3905
3906
|
raise InvalidStateError(
|
|
3906
3907
|
'only peripheral can update connection parameters with l2cap'
|
|
3907
3908
|
)
|
|
@@ -3937,12 +3938,14 @@ class Device(CompositeEventEmitter):
|
|
|
3937
3938
|
)
|
|
3938
3939
|
return result.return_parameters.rssi
|
|
3939
3940
|
|
|
3940
|
-
async def get_connection_phy(self, connection):
|
|
3941
|
+
async def get_connection_phy(self, connection: Connection) -> ConnectionPHY:
|
|
3941
3942
|
result = await self.send_command(
|
|
3942
3943
|
hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle),
|
|
3943
3944
|
check_result=True,
|
|
3944
3945
|
)
|
|
3945
|
-
return (
|
|
3946
|
+
return ConnectionPHY(
|
|
3947
|
+
result.return_parameters.tx_phy, result.return_parameters.rx_phy
|
|
3948
|
+
)
|
|
3946
3949
|
|
|
3947
3950
|
async def set_connection_phy(
|
|
3948
3951
|
self, connection, tx_phys=None, rx_phys=None, phy_options=None
|
|
@@ -4006,13 +4009,12 @@ class Device(CompositeEventEmitter):
|
|
|
4006
4009
|
# Create a future to wait for an address to be found
|
|
4007
4010
|
peer_address = asyncio.get_running_loop().create_future()
|
|
4008
4011
|
|
|
4009
|
-
def on_peer_found(address, ad_data):
|
|
4010
|
-
local_name = ad_data.get(
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
if local_name
|
|
4014
|
-
|
|
4015
|
-
peer_address.set_result(address)
|
|
4012
|
+
def on_peer_found(address: hci.Address, ad_data: AdvertisingData) -> None:
|
|
4013
|
+
local_name = ad_data.get(
|
|
4014
|
+
AdvertisingData.Type.COMPLETE_LOCAL_NAME
|
|
4015
|
+
) or ad_data.get(AdvertisingData.Type.SHORTENED_LOCAL_NAME)
|
|
4016
|
+
if local_name == name:
|
|
4017
|
+
peer_address.set_result(address)
|
|
4016
4018
|
|
|
4017
4019
|
listener = None
|
|
4018
4020
|
was_scanning = self.scanning
|
|
@@ -4145,10 +4147,10 @@ class Device(CompositeEventEmitter):
|
|
|
4145
4147
|
if keys.ltk:
|
|
4146
4148
|
return keys.ltk.value
|
|
4147
4149
|
|
|
4148
|
-
if connection.role ==
|
|
4150
|
+
if connection.role == hci.Role.CENTRAL and keys.ltk_central:
|
|
4149
4151
|
return keys.ltk_central.value
|
|
4150
4152
|
|
|
4151
|
-
if connection.role ==
|
|
4153
|
+
if connection.role == hci.Role.PERIPHERAL and keys.ltk_peripheral:
|
|
4152
4154
|
return keys.ltk_peripheral.value
|
|
4153
4155
|
return None
|
|
4154
4156
|
|
|
@@ -4300,7 +4302,7 @@ class Device(CompositeEventEmitter):
|
|
|
4300
4302
|
self.emit('key_store_update')
|
|
4301
4303
|
|
|
4302
4304
|
# [Classic only]
|
|
4303
|
-
async def switch_role(self, connection: Connection, role:
|
|
4305
|
+
async def switch_role(self, connection: Connection, role: hci.Role):
|
|
4304
4306
|
pending_role_change = asyncio.get_running_loop().create_future()
|
|
4305
4307
|
|
|
4306
4308
|
def on_role_change(new_role):
|
|
@@ -4920,16 +4922,84 @@ class Device(CompositeEventEmitter):
|
|
|
4920
4922
|
self.gatt_service = gatt_service.GenericAttributeProfileService()
|
|
4921
4923
|
self.gatt_server.add_service(self.gatt_service)
|
|
4922
4924
|
|
|
4923
|
-
async def notify_subscriber(
|
|
4925
|
+
async def notify_subscriber(
|
|
4926
|
+
self,
|
|
4927
|
+
connection: Connection,
|
|
4928
|
+
attribute: Attribute,
|
|
4929
|
+
value: Optional[Any] = None,
|
|
4930
|
+
force: bool = False,
|
|
4931
|
+
) -> None:
|
|
4932
|
+
"""
|
|
4933
|
+
Send a notification to an attribute subscriber.
|
|
4934
|
+
|
|
4935
|
+
Args:
|
|
4936
|
+
connection:
|
|
4937
|
+
The connection of the subscriber.
|
|
4938
|
+
attribute:
|
|
4939
|
+
The attribute whose value is notified.
|
|
4940
|
+
value:
|
|
4941
|
+
The value of the attribute (if None, the value is read from the attribute)
|
|
4942
|
+
force:
|
|
4943
|
+
If True, send a notification even if there is no subscriber.
|
|
4944
|
+
"""
|
|
4924
4945
|
await self.gatt_server.notify_subscriber(connection, attribute, value, force)
|
|
4925
4946
|
|
|
4926
|
-
async def notify_subscribers(
|
|
4947
|
+
async def notify_subscribers(
|
|
4948
|
+
self, attribute: Attribute, value=None, force=False
|
|
4949
|
+
) -> None:
|
|
4950
|
+
"""
|
|
4951
|
+
Send a notification to all the subscribers of an attribute.
|
|
4952
|
+
|
|
4953
|
+
Args:
|
|
4954
|
+
attribute:
|
|
4955
|
+
The attribute whose value is notified.
|
|
4956
|
+
value:
|
|
4957
|
+
The value of the attribute (if None, the value is read from the attribute)
|
|
4958
|
+
force:
|
|
4959
|
+
If True, send a notification for every connection even if there is no
|
|
4960
|
+
subscriber.
|
|
4961
|
+
"""
|
|
4927
4962
|
await self.gatt_server.notify_subscribers(attribute, value, force)
|
|
4928
4963
|
|
|
4929
|
-
async def indicate_subscriber(
|
|
4964
|
+
async def indicate_subscriber(
|
|
4965
|
+
self,
|
|
4966
|
+
connection: Connection,
|
|
4967
|
+
attribute: Attribute,
|
|
4968
|
+
value: Optional[Any] = None,
|
|
4969
|
+
force: bool = False,
|
|
4970
|
+
):
|
|
4971
|
+
"""
|
|
4972
|
+
Send an indication to an attribute subscriber.
|
|
4973
|
+
|
|
4974
|
+
This method returns when the response to the indication has been received.
|
|
4975
|
+
|
|
4976
|
+
Args:
|
|
4977
|
+
connection:
|
|
4978
|
+
The connection of the subscriber.
|
|
4979
|
+
attribute:
|
|
4980
|
+
The attribute whose value is indicated.
|
|
4981
|
+
value:
|
|
4982
|
+
The value of the attribute (if None, the value is read from the attribute)
|
|
4983
|
+
force:
|
|
4984
|
+
If True, send an indication even if there is no subscriber.
|
|
4985
|
+
"""
|
|
4930
4986
|
await self.gatt_server.indicate_subscriber(connection, attribute, value, force)
|
|
4931
4987
|
|
|
4932
|
-
async def indicate_subscribers(
|
|
4988
|
+
async def indicate_subscribers(
|
|
4989
|
+
self, attribute: Attribute, value: Optional[Any] = None, force: bool = False
|
|
4990
|
+
):
|
|
4991
|
+
"""
|
|
4992
|
+
Send an indication to all the subscribers of an attribute.
|
|
4993
|
+
|
|
4994
|
+
Args:
|
|
4995
|
+
attribute:
|
|
4996
|
+
The attribute whose value is notified.
|
|
4997
|
+
value:
|
|
4998
|
+
The value of the attribute (if None, the value is read from the attribute)
|
|
4999
|
+
force:
|
|
5000
|
+
If True, send an indication for every connection even if there is no
|
|
5001
|
+
subscriber.
|
|
5002
|
+
"""
|
|
4933
5003
|
await self.gatt_server.indicate_subscribers(attribute, value, force)
|
|
4934
5004
|
|
|
4935
5005
|
@host_event_handler
|
|
@@ -5101,40 +5171,17 @@ class Device(CompositeEventEmitter):
|
|
|
5101
5171
|
lambda _: self.abort_on('flush', advertising_set.start()),
|
|
5102
5172
|
)
|
|
5103
5173
|
|
|
5104
|
-
self._emit_le_connection(connection)
|
|
5105
|
-
|
|
5106
|
-
def _emit_le_connection(self, connection: Connection) -> None:
|
|
5107
|
-
# If supported, read which PHY we're connected with before
|
|
5108
|
-
# notifying listeners of the new connection.
|
|
5109
|
-
if self.host.supports_command(hci.HCI_LE_READ_PHY_COMMAND):
|
|
5110
|
-
|
|
5111
|
-
async def read_phy():
|
|
5112
|
-
result = await self.send_command(
|
|
5113
|
-
hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle),
|
|
5114
|
-
check_result=True,
|
|
5115
|
-
)
|
|
5116
|
-
connection.phy = ConnectionPHY(
|
|
5117
|
-
result.return_parameters.tx_phy, result.return_parameters.rx_phy
|
|
5118
|
-
)
|
|
5119
|
-
# Emit an event to notify listeners of the new connection
|
|
5120
|
-
self.emit('connection', connection)
|
|
5121
|
-
|
|
5122
|
-
# Do so asynchronously to not block the current event handler
|
|
5123
|
-
connection.abort_on('disconnection', read_phy())
|
|
5124
|
-
|
|
5125
|
-
return
|
|
5126
|
-
|
|
5127
5174
|
self.emit('connection', connection)
|
|
5128
5175
|
|
|
5129
5176
|
@host_event_handler
|
|
5130
5177
|
def on_connection(
|
|
5131
5178
|
self,
|
|
5132
5179
|
connection_handle: int,
|
|
5133
|
-
transport:
|
|
5180
|
+
transport: core.PhysicalTransport,
|
|
5134
5181
|
peer_address: hci.Address,
|
|
5135
5182
|
self_resolvable_address: Optional[hci.Address],
|
|
5136
5183
|
peer_resolvable_address: Optional[hci.Address],
|
|
5137
|
-
role:
|
|
5184
|
+
role: hci.Role,
|
|
5138
5185
|
connection_parameters: ConnectionParameters,
|
|
5139
5186
|
) -> None:
|
|
5140
5187
|
# Convert all-zeros addresses into None.
|
|
@@ -5177,7 +5224,7 @@ class Device(CompositeEventEmitter):
|
|
|
5177
5224
|
peer_address = resolved_address
|
|
5178
5225
|
|
|
5179
5226
|
self_address = None
|
|
5180
|
-
own_address_type: Optional[
|
|
5227
|
+
own_address_type: Optional[hci.OwnAddressType] = None
|
|
5181
5228
|
if role == hci.HCI_CENTRAL_ROLE:
|
|
5182
5229
|
own_address_type = self.connect_own_address_type
|
|
5183
5230
|
assert own_address_type is not None
|
|
@@ -5222,7 +5269,6 @@ class Device(CompositeEventEmitter):
|
|
|
5222
5269
|
peer_resolvable_address,
|
|
5223
5270
|
role,
|
|
5224
5271
|
connection_parameters,
|
|
5225
|
-
ConnectionPHY(hci.HCI_LE_1M_PHY, hci.HCI_LE_1M_PHY),
|
|
5226
5272
|
)
|
|
5227
5273
|
self.connections[connection_handle] = connection
|
|
5228
5274
|
|
|
@@ -5238,7 +5284,7 @@ class Device(CompositeEventEmitter):
|
|
|
5238
5284
|
|
|
5239
5285
|
if role == hci.HCI_CENTRAL_ROLE or not self.supports_le_extended_advertising:
|
|
5240
5286
|
# We can emit now, we have all the info we need
|
|
5241
|
-
self.
|
|
5287
|
+
self.emit('connection', connection)
|
|
5242
5288
|
return
|
|
5243
5289
|
|
|
5244
5290
|
if role == hci.HCI_PERIPHERAL_ROLE and self.supports_le_extended_advertising:
|
|
@@ -5306,7 +5352,7 @@ class Device(CompositeEventEmitter):
|
|
|
5306
5352
|
elif self.classic_accept_any:
|
|
5307
5353
|
# Save pending connection
|
|
5308
5354
|
self.pending_connections[bd_addr] = Connection.incomplete(
|
|
5309
|
-
self, bd_addr,
|
|
5355
|
+
self, bd_addr, hci.Role.PERIPHERAL
|
|
5310
5356
|
)
|
|
5311
5357
|
|
|
5312
5358
|
self.host.send_command_sync(
|
|
@@ -5792,14 +5838,13 @@ class Device(CompositeEventEmitter):
|
|
|
5792
5838
|
|
|
5793
5839
|
@host_event_handler
|
|
5794
5840
|
@with_connection_from_handle
|
|
5795
|
-
def on_connection_phy_update(self, connection,
|
|
5841
|
+
def on_connection_phy_update(self, connection, phy):
|
|
5796
5842
|
logger.debug(
|
|
5797
5843
|
f'*** Connection PHY Update: [0x{connection.handle:04X}] '
|
|
5798
5844
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
5799
|
-
f'{
|
|
5845
|
+
f'{phy}'
|
|
5800
5846
|
)
|
|
5801
|
-
connection.phy
|
|
5802
|
-
connection.emit('connection_phy_update')
|
|
5847
|
+
connection.emit('connection_phy_update', phy)
|
|
5803
5848
|
|
|
5804
5849
|
@host_event_handler
|
|
5805
5850
|
@with_connection_from_handle
|