bumble 0.0.178__py3-none-any.whl → 0.0.180__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 +83 -68
- bumble/apps/bench.py +180 -24
- bumble/apps/controller_info.py +14 -0
- bumble/apps/pair.py +9 -2
- bumble/avdtp.py +3 -3
- bumble/crypto.py +82 -66
- bumble/device.py +247 -23
- bumble/gatt.py +117 -7
- bumble/gatt_client.py +56 -20
- bumble/hci.py +351 -78
- bumble/helpers.py +67 -42
- bumble/hid.py +8 -7
- bumble/l2cap.py +8 -0
- bumble/profiles/csip.py +147 -0
- bumble/rfcomm.py +2 -3
- bumble/sdp.py +4 -4
- bumble/smp.py +66 -43
- bumble/transport/common.py +1 -1
- bumble/transport/usb.py +58 -61
- bumble/utils.py +17 -1
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/METADATA +1 -1
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/RECORD +27 -26
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/WHEEL +1 -1
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/LICENSE +0 -0
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.178.dist-info → bumble-0.0.180.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -23,6 +23,7 @@ import asyncio
|
|
|
23
23
|
import logging
|
|
24
24
|
from contextlib import asynccontextmanager, AsyncExitStack
|
|
25
25
|
from dataclasses import dataclass
|
|
26
|
+
from collections.abc import Iterable
|
|
26
27
|
from typing import (
|
|
27
28
|
Any,
|
|
28
29
|
Callable,
|
|
@@ -32,6 +33,8 @@ from typing import (
|
|
|
32
33
|
Optional,
|
|
33
34
|
Tuple,
|
|
34
35
|
Type,
|
|
36
|
+
TypeVar,
|
|
37
|
+
Set,
|
|
35
38
|
Union,
|
|
36
39
|
cast,
|
|
37
40
|
overload,
|
|
@@ -99,13 +102,20 @@ from .hci import (
|
|
|
99
102
|
HCI_LE_Extended_Create_Connection_Command,
|
|
100
103
|
HCI_LE_Rand_Command,
|
|
101
104
|
HCI_LE_Read_PHY_Command,
|
|
105
|
+
HCI_LE_Remove_Advertising_Set_Command,
|
|
102
106
|
HCI_LE_Set_Address_Resolution_Enable_Command,
|
|
103
107
|
HCI_LE_Set_Advertising_Data_Command,
|
|
104
108
|
HCI_LE_Set_Advertising_Enable_Command,
|
|
105
109
|
HCI_LE_Set_Advertising_Parameters_Command,
|
|
110
|
+
HCI_LE_Set_Advertising_Set_Random_Address_Command,
|
|
111
|
+
HCI_LE_Set_Data_Length_Command,
|
|
106
112
|
HCI_LE_Set_Default_PHY_Command,
|
|
107
113
|
HCI_LE_Set_Extended_Scan_Enable_Command,
|
|
108
114
|
HCI_LE_Set_Extended_Scan_Parameters_Command,
|
|
115
|
+
HCI_LE_Set_Extended_Scan_Response_Data_Command,
|
|
116
|
+
HCI_LE_Set_Extended_Advertising_Data_Command,
|
|
117
|
+
HCI_LE_Set_Extended_Advertising_Enable_Command,
|
|
118
|
+
HCI_LE_Set_Extended_Advertising_Parameters_Command,
|
|
109
119
|
HCI_LE_Set_PHY_Command,
|
|
110
120
|
HCI_LE_Set_Random_Address_Command,
|
|
111
121
|
HCI_LE_Set_Scan_Enable_Command,
|
|
@@ -154,6 +164,7 @@ from .utils import (
|
|
|
154
164
|
setup_event_forwarding,
|
|
155
165
|
composite_listener,
|
|
156
166
|
deprecated,
|
|
167
|
+
experimental,
|
|
157
168
|
)
|
|
158
169
|
from .keys import (
|
|
159
170
|
KeyStore,
|
|
@@ -188,6 +199,8 @@ DEVICE_MIN_SCAN_WINDOW = 25
|
|
|
188
199
|
DEVICE_MAX_SCAN_WINDOW = 10240
|
|
189
200
|
DEVICE_MIN_LE_RSSI = -127
|
|
190
201
|
DEVICE_MAX_LE_RSSI = 20
|
|
202
|
+
DEVICE_MIN_EXTENDED_ADVERTISING_SET_HANDLE = 0x00
|
|
203
|
+
DEVICE_MAX_EXTENDED_ADVERTISING_SET_HANDLE = 0xEF
|
|
191
204
|
|
|
192
205
|
DEVICE_DEFAULT_ADDRESS = '00:00:00:00:00:00'
|
|
193
206
|
DEVICE_DEFAULT_ADVERTISING_INTERVAL = 1000 # ms
|
|
@@ -429,8 +442,11 @@ class LePhyOptions:
|
|
|
429
442
|
|
|
430
443
|
|
|
431
444
|
# -----------------------------------------------------------------------------
|
|
445
|
+
_PROXY_CLASS = TypeVar('_PROXY_CLASS', bound=gatt_client.ProfileServiceProxy)
|
|
446
|
+
|
|
447
|
+
|
|
432
448
|
class Peer:
|
|
433
|
-
def __init__(self, connection):
|
|
449
|
+
def __init__(self, connection: Connection) -> None:
|
|
434
450
|
self.connection = connection
|
|
435
451
|
|
|
436
452
|
# Create a GATT client for the connection
|
|
@@ -438,77 +454,113 @@ class Peer:
|
|
|
438
454
|
connection.gatt_client = self.gatt_client
|
|
439
455
|
|
|
440
456
|
@property
|
|
441
|
-
def services(self):
|
|
457
|
+
def services(self) -> List[gatt_client.ServiceProxy]:
|
|
442
458
|
return self.gatt_client.services
|
|
443
459
|
|
|
444
|
-
async def request_mtu(self, mtu):
|
|
460
|
+
async def request_mtu(self, mtu: int) -> int:
|
|
445
461
|
mtu = await self.gatt_client.request_mtu(mtu)
|
|
446
462
|
self.connection.emit('connection_att_mtu_update')
|
|
447
463
|
return mtu
|
|
448
464
|
|
|
449
|
-
async def discover_service(
|
|
465
|
+
async def discover_service(
|
|
466
|
+
self, uuid: Union[core.UUID, str]
|
|
467
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
450
468
|
return await self.gatt_client.discover_service(uuid)
|
|
451
469
|
|
|
452
|
-
async def discover_services(
|
|
470
|
+
async def discover_services(
|
|
471
|
+
self, uuids: Iterable[core.UUID] = ()
|
|
472
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
453
473
|
return await self.gatt_client.discover_services(uuids)
|
|
454
474
|
|
|
455
|
-
async def discover_included_services(
|
|
475
|
+
async def discover_included_services(
|
|
476
|
+
self, service: gatt_client.ServiceProxy
|
|
477
|
+
) -> List[gatt_client.ServiceProxy]:
|
|
456
478
|
return await self.gatt_client.discover_included_services(service)
|
|
457
479
|
|
|
458
|
-
async def discover_characteristics(
|
|
480
|
+
async def discover_characteristics(
|
|
481
|
+
self,
|
|
482
|
+
uuids: Iterable[Union[core.UUID, str]] = (),
|
|
483
|
+
service: Optional[gatt_client.ServiceProxy] = None,
|
|
484
|
+
) -> List[gatt_client.CharacteristicProxy]:
|
|
459
485
|
return await self.gatt_client.discover_characteristics(
|
|
460
486
|
uuids=uuids, service=service
|
|
461
487
|
)
|
|
462
488
|
|
|
463
489
|
async def discover_descriptors(
|
|
464
|
-
self,
|
|
490
|
+
self,
|
|
491
|
+
characteristic: Optional[gatt_client.CharacteristicProxy] = None,
|
|
492
|
+
start_handle: Optional[int] = None,
|
|
493
|
+
end_handle: Optional[int] = None,
|
|
465
494
|
):
|
|
466
495
|
return await self.gatt_client.discover_descriptors(
|
|
467
496
|
characteristic, start_handle, end_handle
|
|
468
497
|
)
|
|
469
498
|
|
|
470
|
-
async def discover_attributes(self):
|
|
499
|
+
async def discover_attributes(self) -> List[gatt_client.AttributeProxy]:
|
|
471
500
|
return await self.gatt_client.discover_attributes()
|
|
472
501
|
|
|
473
|
-
async def subscribe(
|
|
502
|
+
async def subscribe(
|
|
503
|
+
self,
|
|
504
|
+
characteristic: gatt_client.CharacteristicProxy,
|
|
505
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
506
|
+
prefer_notify: bool = True,
|
|
507
|
+
) -> None:
|
|
474
508
|
return await self.gatt_client.subscribe(
|
|
475
509
|
characteristic, subscriber, prefer_notify
|
|
476
510
|
)
|
|
477
511
|
|
|
478
|
-
async def unsubscribe(
|
|
512
|
+
async def unsubscribe(
|
|
513
|
+
self,
|
|
514
|
+
characteristic: gatt_client.CharacteristicProxy,
|
|
515
|
+
subscriber: Optional[Callable[[bytes], Any]] = None,
|
|
516
|
+
) -> None:
|
|
479
517
|
return await self.gatt_client.unsubscribe(characteristic, subscriber)
|
|
480
518
|
|
|
481
|
-
async def read_value(
|
|
519
|
+
async def read_value(
|
|
520
|
+
self, attribute: Union[int, gatt_client.AttributeProxy]
|
|
521
|
+
) -> bytes:
|
|
482
522
|
return await self.gatt_client.read_value(attribute)
|
|
483
523
|
|
|
484
|
-
async def write_value(
|
|
524
|
+
async def write_value(
|
|
525
|
+
self,
|
|
526
|
+
attribute: Union[int, gatt_client.AttributeProxy],
|
|
527
|
+
value: bytes,
|
|
528
|
+
with_response: bool = False,
|
|
529
|
+
) -> None:
|
|
485
530
|
return await self.gatt_client.write_value(attribute, value, with_response)
|
|
486
531
|
|
|
487
|
-
async def read_characteristics_by_uuid(
|
|
532
|
+
async def read_characteristics_by_uuid(
|
|
533
|
+
self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None
|
|
534
|
+
) -> List[bytes]:
|
|
488
535
|
return await self.gatt_client.read_characteristics_by_uuid(uuid, service)
|
|
489
536
|
|
|
490
|
-
def get_services_by_uuid(self, uuid):
|
|
537
|
+
def get_services_by_uuid(self, uuid: core.UUID) -> List[gatt_client.ServiceProxy]:
|
|
491
538
|
return self.gatt_client.get_services_by_uuid(uuid)
|
|
492
539
|
|
|
493
|
-
def get_characteristics_by_uuid(
|
|
540
|
+
def get_characteristics_by_uuid(
|
|
541
|
+
self, uuid: core.UUID, service: Optional[gatt_client.ServiceProxy] = None
|
|
542
|
+
) -> List[gatt_client.CharacteristicProxy]:
|
|
494
543
|
return self.gatt_client.get_characteristics_by_uuid(uuid, service)
|
|
495
544
|
|
|
496
|
-
def create_service_proxy(self, proxy_class):
|
|
497
|
-
return proxy_class.from_client(self.gatt_client)
|
|
545
|
+
def create_service_proxy(self, proxy_class: Type[_PROXY_CLASS]) -> _PROXY_CLASS:
|
|
546
|
+
return cast(_PROXY_CLASS, proxy_class.from_client(self.gatt_client))
|
|
498
547
|
|
|
499
|
-
async def discover_service_and_create_proxy(
|
|
548
|
+
async def discover_service_and_create_proxy(
|
|
549
|
+
self, proxy_class: Type[_PROXY_CLASS]
|
|
550
|
+
) -> Optional[_PROXY_CLASS]:
|
|
500
551
|
# Discover the first matching service and its characteristics
|
|
501
552
|
services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID)
|
|
502
553
|
if services:
|
|
503
554
|
service = services[0]
|
|
504
555
|
await service.discover_characteristics()
|
|
505
556
|
return self.create_service_proxy(proxy_class)
|
|
557
|
+
return None
|
|
506
558
|
|
|
507
|
-
async def sustain(self, timeout=None):
|
|
559
|
+
async def sustain(self, timeout: Optional[float] = None) -> None:
|
|
508
560
|
await self.connection.sustain(timeout)
|
|
509
561
|
|
|
510
562
|
# [Classic only]
|
|
511
|
-
async def request_name(self):
|
|
563
|
+
async def request_name(self) -> str:
|
|
512
564
|
return await self.connection.request_remote_name()
|
|
513
565
|
|
|
514
566
|
async def __aenter__(self):
|
|
@@ -521,7 +573,7 @@ class Peer:
|
|
|
521
573
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
522
574
|
pass
|
|
523
575
|
|
|
524
|
-
def __str__(self):
|
|
576
|
+
def __str__(self) -> str:
|
|
525
577
|
return f'{self.connection.peer_address} as {self.connection.role_name}'
|
|
526
578
|
|
|
527
579
|
|
|
@@ -721,7 +773,7 @@ class Connection(CompositeEventEmitter):
|
|
|
721
773
|
async def switch_role(self, role: int) -> None:
|
|
722
774
|
return await self.device.switch_role(self, role)
|
|
723
775
|
|
|
724
|
-
async def sustain(self, timeout=None):
|
|
776
|
+
async def sustain(self, timeout: Optional[float] = None) -> None:
|
|
725
777
|
"""Idles the current task waiting for a disconnect or timeout"""
|
|
726
778
|
|
|
727
779
|
abort = asyncio.get_running_loop().create_future()
|
|
@@ -736,6 +788,9 @@ class Connection(CompositeEventEmitter):
|
|
|
736
788
|
self.remove_listener('disconnection', abort.set_result)
|
|
737
789
|
self.remove_listener('disconnection_failure', abort.set_exception)
|
|
738
790
|
|
|
791
|
+
async def set_data_length(self, tx_octets, tx_time) -> None:
|
|
792
|
+
return await self.device.set_data_length(self, tx_octets, tx_time)
|
|
793
|
+
|
|
739
794
|
async def update_parameters(
|
|
740
795
|
self,
|
|
741
796
|
connection_interval_min,
|
|
@@ -956,6 +1011,7 @@ class Device(CompositeEventEmitter):
|
|
|
956
1011
|
]
|
|
957
1012
|
advertisement_accumulators: Dict[Address, AdvertisementDataAccumulator]
|
|
958
1013
|
config: DeviceConfiguration
|
|
1014
|
+
extended_advertising_handles: Set[int]
|
|
959
1015
|
|
|
960
1016
|
@composite_listener
|
|
961
1017
|
class Listener:
|
|
@@ -1054,6 +1110,7 @@ class Device(CompositeEventEmitter):
|
|
|
1054
1110
|
self.classic_pending_accepts = {
|
|
1055
1111
|
Address.ANY: []
|
|
1056
1112
|
} # Futures, by BD address OR [Futures] for Address.ANY
|
|
1113
|
+
self.extended_advertising_handles = set()
|
|
1057
1114
|
|
|
1058
1115
|
# Own address type cache
|
|
1059
1116
|
self.advertising_own_address_type = None
|
|
@@ -1532,6 +1589,149 @@ class Device(CompositeEventEmitter):
|
|
|
1532
1589
|
self.advertising = False
|
|
1533
1590
|
self.auto_restart_advertising = False
|
|
1534
1591
|
|
|
1592
|
+
@experimental('Extended Advertising is still experimental - Might be changed soon.')
|
|
1593
|
+
async def start_extended_advertising(
|
|
1594
|
+
self,
|
|
1595
|
+
advertising_properties: HCI_LE_Set_Extended_Advertising_Parameters_Command.AdvertisingProperties = HCI_LE_Set_Extended_Advertising_Parameters_Command.AdvertisingProperties.CONNECTABLE_ADVERTISING,
|
|
1596
|
+
target: Address = Address.ANY,
|
|
1597
|
+
own_address_type: int = OwnAddressType.RANDOM,
|
|
1598
|
+
scan_response: Optional[bytes] = None,
|
|
1599
|
+
advertising_data: Optional[bytes] = None,
|
|
1600
|
+
) -> int:
|
|
1601
|
+
"""Starts an extended advertising set.
|
|
1602
|
+
|
|
1603
|
+
Args:
|
|
1604
|
+
advertising_properties: Properties to pass in HCI_LE_Set_Extended_Advertising_Parameters_Command
|
|
1605
|
+
target: Directed advertising target. Directed property should be set in advertising_properties arg.
|
|
1606
|
+
own_address_type: own address type to use in the advertising.
|
|
1607
|
+
scan_response: raw scan response. When a non-none value is set, HCI_LE_Set_Extended_Scan_Response_Data_Command will be sent.
|
|
1608
|
+
advertising_data: raw advertising data. When a non-none value is set, HCI_LE_Set_Advertising_Set_Random_Address_Command will be sent.
|
|
1609
|
+
|
|
1610
|
+
Returns:
|
|
1611
|
+
Handle of the new advertising set.
|
|
1612
|
+
"""
|
|
1613
|
+
|
|
1614
|
+
adv_handle = -1
|
|
1615
|
+
# Find a free handle
|
|
1616
|
+
for i in range(
|
|
1617
|
+
DEVICE_MIN_EXTENDED_ADVERTISING_SET_HANDLE,
|
|
1618
|
+
DEVICE_MAX_EXTENDED_ADVERTISING_SET_HANDLE + 1,
|
|
1619
|
+
):
|
|
1620
|
+
if i not in self.extended_advertising_handles:
|
|
1621
|
+
adv_handle = i
|
|
1622
|
+
break
|
|
1623
|
+
|
|
1624
|
+
if adv_handle == -1:
|
|
1625
|
+
raise InvalidStateError('No available advertising set.')
|
|
1626
|
+
|
|
1627
|
+
try:
|
|
1628
|
+
# Set the advertising parameters
|
|
1629
|
+
await self.send_command(
|
|
1630
|
+
HCI_LE_Set_Extended_Advertising_Parameters_Command(
|
|
1631
|
+
advertising_handle=adv_handle,
|
|
1632
|
+
advertising_event_properties=advertising_properties,
|
|
1633
|
+
primary_advertising_interval_min=self.advertising_interval_min,
|
|
1634
|
+
primary_advertising_interval_max=self.advertising_interval_max,
|
|
1635
|
+
primary_advertising_channel_map=(
|
|
1636
|
+
HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap.CHANNEL_37
|
|
1637
|
+
| HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap.CHANNEL_38
|
|
1638
|
+
| HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap.CHANNEL_39
|
|
1639
|
+
),
|
|
1640
|
+
own_address_type=own_address_type,
|
|
1641
|
+
peer_address_type=target.address_type,
|
|
1642
|
+
peer_address=target,
|
|
1643
|
+
advertising_tx_power=7,
|
|
1644
|
+
advertising_filter_policy=0,
|
|
1645
|
+
primary_advertising_phy=1, # LE 1M
|
|
1646
|
+
secondary_advertising_max_skip=0,
|
|
1647
|
+
secondary_advertising_phy=1, # LE 1M
|
|
1648
|
+
advertising_sid=0,
|
|
1649
|
+
scan_request_notification_enable=0,
|
|
1650
|
+
), # type: ignore[call-arg]
|
|
1651
|
+
check_result=True,
|
|
1652
|
+
)
|
|
1653
|
+
|
|
1654
|
+
# Set the advertising data if present
|
|
1655
|
+
if advertising_data is not None:
|
|
1656
|
+
await self.send_command(
|
|
1657
|
+
HCI_LE_Set_Extended_Advertising_Data_Command(
|
|
1658
|
+
advertising_handle=adv_handle,
|
|
1659
|
+
operation=HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
1660
|
+
fragment_preference=0x01, # Should not fragment
|
|
1661
|
+
advertising_data=advertising_data,
|
|
1662
|
+
), # type: ignore[call-arg]
|
|
1663
|
+
check_result=True,
|
|
1664
|
+
)
|
|
1665
|
+
|
|
1666
|
+
# Set the scan response if present
|
|
1667
|
+
if scan_response is not None:
|
|
1668
|
+
await self.send_command(
|
|
1669
|
+
HCI_LE_Set_Extended_Scan_Response_Data_Command(
|
|
1670
|
+
advertising_handle=adv_handle,
|
|
1671
|
+
operation=HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
1672
|
+
fragment_preference=0x01, # Should not fragment
|
|
1673
|
+
scan_response_data=scan_response,
|
|
1674
|
+
), # type: ignore[call-arg]
|
|
1675
|
+
check_result=True,
|
|
1676
|
+
)
|
|
1677
|
+
|
|
1678
|
+
if own_address_type in (
|
|
1679
|
+
OwnAddressType.RANDOM,
|
|
1680
|
+
OwnAddressType.RESOLVABLE_OR_RANDOM,
|
|
1681
|
+
):
|
|
1682
|
+
await self.send_command(
|
|
1683
|
+
HCI_LE_Set_Advertising_Set_Random_Address_Command(
|
|
1684
|
+
advertising_handle=adv_handle,
|
|
1685
|
+
random_address=self.random_address,
|
|
1686
|
+
), # type: ignore[call-arg]
|
|
1687
|
+
check_result=True,
|
|
1688
|
+
)
|
|
1689
|
+
|
|
1690
|
+
# Enable advertising
|
|
1691
|
+
await self.send_command(
|
|
1692
|
+
HCI_LE_Set_Extended_Advertising_Enable_Command(
|
|
1693
|
+
enable=1,
|
|
1694
|
+
advertising_handles=[adv_handle],
|
|
1695
|
+
durations=[0], # Forever
|
|
1696
|
+
max_extended_advertising_events=[0], # Infinite
|
|
1697
|
+
), # type: ignore[call-arg]
|
|
1698
|
+
check_result=True,
|
|
1699
|
+
)
|
|
1700
|
+
except HCI_Error as error:
|
|
1701
|
+
# When any step fails, cleanup the advertising handle.
|
|
1702
|
+
await self.send_command(
|
|
1703
|
+
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle), # type: ignore[call-arg]
|
|
1704
|
+
check_result=False,
|
|
1705
|
+
)
|
|
1706
|
+
raise error
|
|
1707
|
+
|
|
1708
|
+
self.extended_advertising_handles.add(adv_handle)
|
|
1709
|
+
return adv_handle
|
|
1710
|
+
|
|
1711
|
+
@experimental('Extended Advertising is still experimental - Might be changed soon.')
|
|
1712
|
+
async def stop_extended_advertising(self, adv_handle: int) -> None:
|
|
1713
|
+
"""Stops an extended advertising set.
|
|
1714
|
+
|
|
1715
|
+
Args:
|
|
1716
|
+
adv_handle: Handle of the advertising set to stop.
|
|
1717
|
+
"""
|
|
1718
|
+
# Disable advertising
|
|
1719
|
+
await self.send_command(
|
|
1720
|
+
HCI_LE_Set_Extended_Advertising_Enable_Command(
|
|
1721
|
+
enable=0,
|
|
1722
|
+
advertising_handles=[adv_handle],
|
|
1723
|
+
durations=[0],
|
|
1724
|
+
max_extended_advertising_events=[0],
|
|
1725
|
+
), # type: ignore[call-arg]
|
|
1726
|
+
check_result=True,
|
|
1727
|
+
)
|
|
1728
|
+
# Remove advertising set
|
|
1729
|
+
await self.send_command(
|
|
1730
|
+
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle), # type: ignore[call-arg]
|
|
1731
|
+
check_result=True,
|
|
1732
|
+
)
|
|
1733
|
+
self.extended_advertising_handles.remove(adv_handle)
|
|
1734
|
+
|
|
1535
1735
|
@property
|
|
1536
1736
|
def is_advertising(self):
|
|
1537
1737
|
return self.advertising
|
|
@@ -2193,6 +2393,22 @@ class Device(CompositeEventEmitter):
|
|
|
2193
2393
|
)
|
|
2194
2394
|
self.disconnecting = False
|
|
2195
2395
|
|
|
2396
|
+
async def set_data_length(self, connection, tx_octets, tx_time) -> None:
|
|
2397
|
+
if tx_octets < 0x001B or tx_octets > 0x00FB:
|
|
2398
|
+
raise ValueError('tx_octets must be between 0x001B and 0x00FB')
|
|
2399
|
+
|
|
2400
|
+
if tx_time < 0x0148 or tx_time > 0x4290:
|
|
2401
|
+
raise ValueError('tx_time must be between 0x0148 and 0x4290')
|
|
2402
|
+
|
|
2403
|
+
return await self.send_command(
|
|
2404
|
+
HCI_LE_Set_Data_Length_Command(
|
|
2405
|
+
connection_handle=connection.handle,
|
|
2406
|
+
tx_octets=tx_octets,
|
|
2407
|
+
tx_time=tx_time,
|
|
2408
|
+
), # type: ignore[call-arg]
|
|
2409
|
+
check_result=True,
|
|
2410
|
+
)
|
|
2411
|
+
|
|
2196
2412
|
async def update_connection_parameters(
|
|
2197
2413
|
self,
|
|
2198
2414
|
connection,
|
|
@@ -3138,10 +3354,18 @@ class Device(CompositeEventEmitter):
|
|
|
3138
3354
|
connection.encryption = encryption
|
|
3139
3355
|
if (
|
|
3140
3356
|
not connection.authenticated
|
|
3357
|
+
and connection.transport == BT_BR_EDR_TRANSPORT
|
|
3141
3358
|
and encryption == HCI_Encryption_Change_Event.AES_CCM
|
|
3142
3359
|
):
|
|
3143
3360
|
connection.authenticated = True
|
|
3144
3361
|
connection.sc = True
|
|
3362
|
+
if (
|
|
3363
|
+
not connection.authenticated
|
|
3364
|
+
and connection.transport == BT_LE_TRANSPORT
|
|
3365
|
+
and encryption == HCI_Encryption_Change_Event.E0_OR_AES_CCM
|
|
3366
|
+
):
|
|
3367
|
+
connection.authenticated = True
|
|
3368
|
+
connection.sc = True
|
|
3145
3369
|
connection.emit('connection_encryption_change')
|
|
3146
3370
|
|
|
3147
3371
|
@host_event_handler
|
bumble/gatt.py
CHANGED
|
@@ -93,20 +93,35 @@ GATT_RECONNECTION_CONFIGURATION_SERVICE = UUID.from_16_bits(0x1829, 'Reconne
|
|
|
93
93
|
GATT_INSULIN_DELIVERY_SERVICE = UUID.from_16_bits(0x183A, 'Insulin Delivery')
|
|
94
94
|
GATT_BINARY_SENSOR_SERVICE = UUID.from_16_bits(0x183B, 'Binary Sensor')
|
|
95
95
|
GATT_EMERGENCY_CONFIGURATION_SERVICE = UUID.from_16_bits(0x183C, 'Emergency Configuration')
|
|
96
|
+
GATT_AUTHORIZATION_CONTROL_SERVICE = UUID.from_16_bits(0x183D, 'Authorization Control')
|
|
96
97
|
GATT_PHYSICAL_ACTIVITY_MONITOR_SERVICE = UUID.from_16_bits(0x183E, 'Physical Activity Monitor')
|
|
98
|
+
GATT_ELAPSED_TIME_SERVICE = UUID.from_16_bits(0x183F, 'Elapsed Time')
|
|
99
|
+
GATT_GENERIC_HEALTH_SENSOR_SERVICE = UUID.from_16_bits(0x1840, 'Generic Health Sensor')
|
|
97
100
|
GATT_AUDIO_INPUT_CONTROL_SERVICE = UUID.from_16_bits(0x1843, 'Audio Input Control')
|
|
98
101
|
GATT_VOLUME_CONTROL_SERVICE = UUID.from_16_bits(0x1844, 'Volume Control')
|
|
99
102
|
GATT_VOLUME_OFFSET_CONTROL_SERVICE = UUID.from_16_bits(0x1845, 'Volume Offset Control')
|
|
100
|
-
GATT_COORDINATED_SET_IDENTIFICATION_SERVICE = UUID.from_16_bits(0x1846, 'Coordinated Set Identification
|
|
103
|
+
GATT_COORDINATED_SET_IDENTIFICATION_SERVICE = UUID.from_16_bits(0x1846, 'Coordinated Set Identification')
|
|
101
104
|
GATT_DEVICE_TIME_SERVICE = UUID.from_16_bits(0x1847, 'Device Time')
|
|
102
|
-
GATT_MEDIA_CONTROL_SERVICE = UUID.from_16_bits(0x1848, 'Media Control
|
|
103
|
-
GATT_GENERIC_MEDIA_CONTROL_SERVICE = UUID.from_16_bits(0x1849, 'Generic Media Control
|
|
105
|
+
GATT_MEDIA_CONTROL_SERVICE = UUID.from_16_bits(0x1848, 'Media Control')
|
|
106
|
+
GATT_GENERIC_MEDIA_CONTROL_SERVICE = UUID.from_16_bits(0x1849, 'Generic Media Control')
|
|
104
107
|
GATT_CONSTANT_TONE_EXTENSION_SERVICE = UUID.from_16_bits(0x184A, 'Constant Tone Extension')
|
|
105
|
-
GATT_TELEPHONE_BEARER_SERVICE = UUID.from_16_bits(0x184B, 'Telephone Bearer
|
|
106
|
-
GATT_GENERIC_TELEPHONE_BEARER_SERVICE = UUID.from_16_bits(0x184C, 'Generic Telephone Bearer
|
|
108
|
+
GATT_TELEPHONE_BEARER_SERVICE = UUID.from_16_bits(0x184B, 'Telephone Bearer')
|
|
109
|
+
GATT_GENERIC_TELEPHONE_BEARER_SERVICE = UUID.from_16_bits(0x184C, 'Generic Telephone Bearer')
|
|
107
110
|
GATT_MICROPHONE_CONTROL_SERVICE = UUID.from_16_bits(0x184D, 'Microphone Control')
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
GATT_AUDIO_STREAM_CONTROL_SERVICE = UUID.from_16_bits(0x184E, 'Audio Stream Control')
|
|
112
|
+
GATT_BROADCAST_AUDIO_SCAN_SERVICE = UUID.from_16_bits(0x184F, 'Broadcast Audio Scan')
|
|
113
|
+
GATT_PUBLISHED_AUDIO_CAPABILITIES_SERVICE = UUID.from_16_bits(0x1850, 'Published Audio Capabilities')
|
|
114
|
+
GATT_BASIC_AUDIO_ANNOUNCEMENT_SERVICE = UUID.from_16_bits(0x1851, 'Basic Audio Announcement')
|
|
115
|
+
GATT_BROADCAST_AUDIO_ANNOUNCEMENT_SERVICE = UUID.from_16_bits(0x1852, 'Broadcast Audio Announcement')
|
|
116
|
+
GATT_COMMON_AUDIO_SERVICE = UUID.from_16_bits(0x1853, 'Common Audio')
|
|
117
|
+
GATT_HEARING_ACCESS_SERVICE = UUID.from_16_bits(0x1854, 'Hearing Access')
|
|
118
|
+
GATT_TELEPHONY_AND_MEDIA_AUDIO_SERVICE = UUID.from_16_bits(0x1855, 'Telephony and Media Audio')
|
|
119
|
+
GATT_PUBLIC_BROADCAST_ANNOUNCEMENT_SERVICE = UUID.from_16_bits(0x1856, 'Public Broadcast Announcement')
|
|
120
|
+
GATT_ELECTRONIC_SHELF_LABEL_SERVICE = UUID.from_16_bits(0X1857, 'Electronic Shelf Label')
|
|
121
|
+
GATT_GAMING_AUDIO_SERVICE = UUID.from_16_bits(0x1858, 'Gaming Audio')
|
|
122
|
+
GATT_MESH_PROXY_SOLICITATION_SERVICE = UUID.from_16_bits(0x1859, 'Mesh Audio Solicitation')
|
|
123
|
+
|
|
124
|
+
# Attribute Types
|
|
110
125
|
GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE = UUID.from_16_bits(0x2800, 'Primary Service')
|
|
111
126
|
GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE = UUID.from_16_bits(0x2801, 'Secondary Service')
|
|
112
127
|
GATT_INCLUDE_ATTRIBUTE_TYPE = UUID.from_16_bits(0x2802, 'Include')
|
|
@@ -129,6 +144,8 @@ GATT_ENVIRONMENTAL_SENSING_MEASUREMENT_DESCRIPTOR = UUID.from_16_bits(0x290C,
|
|
|
129
144
|
GATT_ENVIRONMENTAL_SENSING_TRIGGER_DESCRIPTOR = UUID.from_16_bits(0x290D, 'Environmental Sensing Trigger Setting')
|
|
130
145
|
GATT_TIME_TRIGGER_DESCRIPTOR = UUID.from_16_bits(0x290E, 'Time Trigger Setting')
|
|
131
146
|
GATT_COMPLETE_BR_EDR_TRANSPORT_BLOCK_DATA_DESCRIPTOR = UUID.from_16_bits(0x290F, 'Complete BR-EDR Transport Block Data')
|
|
147
|
+
GATT_OBSERVATION_SCHEDULE_DESCRIPTOR = UUID.from_16_bits(0x290F, 'Observation Schedule')
|
|
148
|
+
GATT_VALID_RANGE_AND_ACCURACY_DESCRIPTOR = UUID.from_16_bits(0x290F, 'Valid Range And Accuracy')
|
|
132
149
|
|
|
133
150
|
# Device Information Service
|
|
134
151
|
GATT_SYSTEM_ID_CHARACTERISTIC = UUID.from_16_bits(0x2A23, 'System ID')
|
|
@@ -156,6 +173,96 @@ GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2A39, 'Heart
|
|
|
156
173
|
# Battery Service
|
|
157
174
|
GATT_BATTERY_LEVEL_CHARACTERISTIC = UUID.from_16_bits(0x2A19, 'Battery Level')
|
|
158
175
|
|
|
176
|
+
# Telephony And Media Audio Service (TMAS)
|
|
177
|
+
GATT_TMAP_ROLE_CHARACTERISTIC = UUID.from_16_bits(0x2B51, 'TMAP Role')
|
|
178
|
+
|
|
179
|
+
# Audio Input Control Service (AICS)
|
|
180
|
+
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2B77, 'Audio Input State')
|
|
181
|
+
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC = UUID.from_16_bits(0x2B78, 'Gain Settings Attribute')
|
|
182
|
+
GATT_AUDIO_INPUT_TYPE_CHARACTERISTIC = UUID.from_16_bits(0x2B79, 'Audio Input Type')
|
|
183
|
+
GATT_AUDIO_INPUT_STATUS_CHARACTERISTIC = UUID.from_16_bits(0x2B7A, 'Audio Input Status')
|
|
184
|
+
GATT_AUDIO_INPUT_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2B7B, 'Audio Input Control Point')
|
|
185
|
+
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC = UUID.from_16_bits(0x2B7C, 'Audio Input Description')
|
|
186
|
+
|
|
187
|
+
# Volume Control Service (VCS)
|
|
188
|
+
GATT_VOLUME_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2B7D, 'Volume State')
|
|
189
|
+
GATT_VOLUME_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2B7E, 'Volume Control Point')
|
|
190
|
+
GATT_VOLUME_FLAGS_CHARACTERISTIC = UUID.from_16_bits(0x2B7F, 'Volume Flags')
|
|
191
|
+
|
|
192
|
+
# Volume Offset Control Service (VOCS)
|
|
193
|
+
GATT_VOLUME_OFFSET_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2B80, 'Volume Offset State')
|
|
194
|
+
GATT_AUDIO_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2B81, 'Audio Location')
|
|
195
|
+
GATT_VOLUME_OFFSET_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2B82, 'Volume Offset Control Point')
|
|
196
|
+
GATT_AUDIO_OUTPUT_DESCRIPTION_CHARACTERISTIC = UUID.from_16_bits(0x2B83, 'Audio Output Description')
|
|
197
|
+
|
|
198
|
+
# Coordinated Set Identification Service (CSIS)
|
|
199
|
+
GATT_SET_IDENTITY_RESOLVING_KEY_CHARACTERISTIC = UUID.from_16_bits(0x2B84, 'Set Identity Resolving Key')
|
|
200
|
+
GATT_COORDINATED_SET_SIZE_CHARACTERISTIC = UUID.from_16_bits(0x2B85, 'Coordinated Set Size')
|
|
201
|
+
GATT_SET_MEMBER_LOCK_CHARACTERISTIC = UUID.from_16_bits(0x2B86, 'Set Member Lock')
|
|
202
|
+
GATT_SET_MEMBER_RANK_CHARACTERISTIC = UUID.from_16_bits(0x2B87, 'Set Member Rank')
|
|
203
|
+
|
|
204
|
+
# Media Control Service (MCS)
|
|
205
|
+
GATT_MEDIA_PLAYER_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2B93, 'Media Player Name')
|
|
206
|
+
GATT_MEDIA_PLAYER_ICON_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B94, 'Media Player Icon Object ID')
|
|
207
|
+
GATT_MEDIA_PLAYER_ICON_URL_CHARACTERISTIC = UUID.from_16_bits(0x2B95, 'Media Player Icon URL')
|
|
208
|
+
GATT_TRACK_CHANGED_CHARACTERISTIC = UUID.from_16_bits(0x2B96, 'Track Changed')
|
|
209
|
+
GATT_TRACK_TITLE_CHARACTERISTIC = UUID.from_16_bits(0x2B97, 'Track Title')
|
|
210
|
+
GATT_TRACK_DURATION_CHARACTERISTIC = UUID.from_16_bits(0x2B98, 'Track Duration')
|
|
211
|
+
GATT_TRACK_POSITION_CHARACTERISTIC = UUID.from_16_bits(0x2B99, 'Track Position')
|
|
212
|
+
GATT_PLAYBACK_SPEED_CHARACTERISTIC = UUID.from_16_bits(0x2B9A, 'Playback Speed')
|
|
213
|
+
GATT_SEEKING_SPEED_CHARACTERISTIC = UUID.from_16_bits(0x2B9B, 'Seeking Speed')
|
|
214
|
+
GATT_CURRENT_TRACK_SEGMENTS_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B9C, 'Current Track Segments Object ID')
|
|
215
|
+
GATT_CURRENT_TRACK_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B9D, 'Current Track Object ID')
|
|
216
|
+
GATT_NEXT_TRACK_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B9E, 'Next Track Object ID')
|
|
217
|
+
GATT_PARENT_GROUP_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2B9F, 'Parent Group Object ID')
|
|
218
|
+
GATT_CURRENT_GROUP_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BA0, 'Current Group Object ID')
|
|
219
|
+
GATT_PLAYING_ORDER_CHARACTERISTIC = UUID.from_16_bits(0x2BA1, 'Playing Order')
|
|
220
|
+
GATT_PLAYING_ORDERS_SUPPORTED_CHARACTERISTIC = UUID.from_16_bits(0x2BA2, 'Playing Orders Supported')
|
|
221
|
+
GATT_MEDIA_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2BA3, 'Media State')
|
|
222
|
+
GATT_MEDIA_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BA4, 'Media Control Point')
|
|
223
|
+
GATT_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_CHARACTERISTIC = UUID.from_16_bits(0x2BA5, 'Media Control Point Opcodes Supported')
|
|
224
|
+
GATT_SEARCH_RESULTS_OBJECT_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BA6, 'Search Results Object ID')
|
|
225
|
+
GATT_SEARCH_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BA7, 'Search Control Point')
|
|
226
|
+
GATT_CONTENT_CONTROL_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BBA, 'Content Control Id')
|
|
227
|
+
|
|
228
|
+
# Telephone Bearer Service (TBS)
|
|
229
|
+
GATT_BEARER_PROVIDER_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2BB4, 'Bearer Provider Name')
|
|
230
|
+
GATT_BEARER_UCI_CHARACTERISTIC = UUID.from_16_bits(0x2BB5, 'Bearer UCI')
|
|
231
|
+
GATT_BEARER_TECHNOLOGY_CHARACTERISTIC = UUID.from_16_bits(0x2BB6, 'Bearer Technology')
|
|
232
|
+
GATT_BEARER_URI_SCHEMES_SUPPORTED_LIST_CHARACTERISTIC = UUID.from_16_bits(0x2BB7, 'Bearer URI Schemes Supported List')
|
|
233
|
+
GATT_BEARER_SIGNAL_STRENGTH_CHARACTERISTIC = UUID.from_16_bits(0x2BB8, 'Bearer Signal Strength')
|
|
234
|
+
GATT_BEARER_SIGNAL_STRENGTH_REPORTING_INTERVAL_CHARACTERISTIC = UUID.from_16_bits(0x2BB9, 'Bearer Signal Strength Reporting Interval')
|
|
235
|
+
GATT_BEARER_LIST_CURRENT_CALLS_CHARACTERISTIC = UUID.from_16_bits(0x2BBA, 'Bearer List Current Calls')
|
|
236
|
+
GATT_CONTENT_CONTROL_ID_CHARACTERISTIC = UUID.from_16_bits(0x2BBB, 'Content Control ID')
|
|
237
|
+
GATT_STATUS_FLAGS_CHARACTERISTIC = UUID.from_16_bits(0x2BBC, 'Status Flags')
|
|
238
|
+
GATT_INCOMING_CALL_TARGET_BEARER_URI_CHARACTERISTIC = UUID.from_16_bits(0x2BBD, 'Incoming Call Target Bearer URI')
|
|
239
|
+
GATT_CALL_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2BBE, 'Call State')
|
|
240
|
+
GATT_CALL_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BBF, 'Call Control Point')
|
|
241
|
+
GATT_CALL_CONTROL_POINT_OPTIONAL_OPCODES_CHARACTERISTIC = UUID.from_16_bits(0x2BC0, 'Call Control Point Optional Opcodes')
|
|
242
|
+
GATT_TERMINATION_REASON_CHARACTERISTIC = UUID.from_16_bits(0x2BC1, 'Termination Reason')
|
|
243
|
+
GATT_INCOMING_CALL_CHARACTERISTIC = UUID.from_16_bits(0x2BC2, 'Incoming Call')
|
|
244
|
+
GATT_CALL_FRIENDLY_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2BC3, 'Call Friendly Name')
|
|
245
|
+
|
|
246
|
+
# Microphone Control Service (MICS)
|
|
247
|
+
GATT_MUTE_CHARACTERISTIC = UUID.from_16_bits(0x2BC3, 'Mute')
|
|
248
|
+
|
|
249
|
+
# Audio Stream Control Service (ASCS)
|
|
250
|
+
GATT_SINK_ASE_CHARACTERISTIC = UUID.from_16_bits(0x2BC4, 'Sink ASE')
|
|
251
|
+
GATT_SOURCE_ASE_CHARACTERISTIC = UUID.from_16_bits(0x2BC5, 'Source ASE')
|
|
252
|
+
GATT_ASE_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BC6, 'ASE Control Point')
|
|
253
|
+
|
|
254
|
+
# Broadcast Audio Scan Service (BASS)
|
|
255
|
+
GATT_BROADCAST_AUDIO_SCAN_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BC7, 'Broadcast Audio Scan Control Point')
|
|
256
|
+
GATT_BROADCAST_RECEIVE_STATE_CHARACTERISTIC = UUID.from_16_bits(0x2BC8, 'Broadcast Receive State')
|
|
257
|
+
|
|
258
|
+
# Published Audio Capabilities Service (PACS)
|
|
259
|
+
GATT_SINK_PAC_CHARACTERISTIC = UUID.from_16_bits(0x2BC9, 'Sink PAC')
|
|
260
|
+
GATT_SINK_AUDIO_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2BCA, 'Sink Audio Location')
|
|
261
|
+
GATT_SOURCE_PAC_CHARACTERISTIC = UUID.from_16_bits(0x2BCB, 'Source PAC')
|
|
262
|
+
GATT_SOURCE_AUDIO_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2BCC, 'Source Audio Location')
|
|
263
|
+
GATT_AVAILABLE_AUDIO_CONTEXTS_CHARACTERISTIC = UUID.from_16_bits(0x2BCD, 'Available Audio Contexts')
|
|
264
|
+
GATT_SUPPORTED_AUDIO_CONTEXTS_CHARACTERISTIC = UUID.from_16_bits(0x2BCE, 'Supported Audio Contexts')
|
|
265
|
+
|
|
159
266
|
# ASHA Service
|
|
160
267
|
GATT_ASHA_SERVICE = UUID.from_16_bits(0xFDF0, 'Audio Streaming for Hearing Aid')
|
|
161
268
|
GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC = UUID('6333651e-c481-4a3e-9169-7c902aad37bb', 'ReadOnlyProperties')
|
|
@@ -177,6 +284,9 @@ GATT_BOOT_KEYBOARD_INPUT_REPORT_CHARACTERISTIC = UUID.from_16_bi
|
|
|
177
284
|
GATT_CURRENT_TIME_CHARACTERISTIC = UUID.from_16_bits(0x2A2B, 'Current Time')
|
|
178
285
|
GATT_BOOT_KEYBOARD_OUTPUT_REPORT_CHARACTERISTIC = UUID.from_16_bits(0x2A32, 'Boot Keyboard Output Report')
|
|
179
286
|
GATT_CENTRAL_ADDRESS_RESOLUTION__CHARACTERISTIC = UUID.from_16_bits(0x2AA6, 'Central Address Resolution')
|
|
287
|
+
GATT_CLIENT_SUPPORTED_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2B29, 'Client Supported Features')
|
|
288
|
+
GATT_DATABASE_HASH_CHARACTERISTIC = UUID.from_16_bits(0x2B2A, 'Database Hash')
|
|
289
|
+
GATT_SERVER_SUPPORTED_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2B3A, 'Server Supported Features')
|
|
180
290
|
|
|
181
291
|
# fmt: on
|
|
182
292
|
# pylint: enable=line-too-long
|