bumble 0.0.204__py3-none-any.whl → 0.0.208__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 +631 -98
- bumble/apps/bench.py +238 -157
- bumble/apps/console.py +19 -12
- bumble/apps/controller_info.py +23 -7
- bumble/apps/device_info.py +50 -4
- bumble/apps/gg_bridge.py +1 -1
- bumble/apps/lea_unicast/app.py +61 -201
- bumble/att.py +51 -37
- bumble/audio/__init__.py +17 -0
- bumble/audio/io.py +553 -0
- bumble/controller.py +24 -9
- bumble/core.py +305 -156
- bumble/device.py +1090 -99
- bumble/gatt.py +36 -226
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +52 -33
- bumble/gatt_server.py +5 -5
- bumble/hci.py +812 -14
- bumble/host.py +367 -65
- bumble/l2cap.py +3 -16
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +7 -12
- bumble/profiles/aics.py +48 -57
- bumble/profiles/ascs.py +8 -19
- bumble/profiles/asha.py +16 -14
- bumble/profiles/bass.py +16 -22
- 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 +167 -0
- bumble/profiles/gmap.py +198 -0
- bumble/profiles/hap.py +8 -6
- bumble/profiles/heart_rate_service.py +20 -4
- bumble/profiles/le_audio.py +87 -4
- bumble/profiles/mcp.py +11 -9
- bumble/profiles/pacs.py +61 -16
- bumble/profiles/tmap.py +8 -12
- bumble/profiles/{vcp.py → vcs.py} +35 -29
- bumble/profiles/vocs.py +62 -85
- bumble/sdp.py +223 -93
- bumble/smp.py +1 -1
- bumble/utils.py +12 -2
- bumble/vendor/android/hci.py +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/METADATA +13 -11
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/RECORD +50 -46
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/WHEEL +1 -1
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/entry_points.txt +1 -0
- bumble/apps/lea_unicast/liblc3.wasm +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/LICENSE +0 -0
- {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/top_level.txt +0 -0
bumble/gatt.py
CHANGED
|
@@ -27,29 +27,16 @@ import enum
|
|
|
27
27
|
import functools
|
|
28
28
|
import logging
|
|
29
29
|
import struct
|
|
30
|
-
from typing import
|
|
31
|
-
Any,
|
|
32
|
-
Callable,
|
|
33
|
-
Dict,
|
|
34
|
-
Iterable,
|
|
35
|
-
List,
|
|
36
|
-
Optional,
|
|
37
|
-
Sequence,
|
|
38
|
-
SupportsBytes,
|
|
39
|
-
Type,
|
|
40
|
-
Union,
|
|
41
|
-
TYPE_CHECKING,
|
|
42
|
-
)
|
|
30
|
+
from typing import Iterable, List, Optional, Sequence, TypeVar, Union
|
|
43
31
|
|
|
44
32
|
from bumble.colors import color
|
|
45
33
|
from bumble.core import BaseBumbleError, UUID
|
|
46
34
|
from bumble.att import Attribute, AttributeValue
|
|
47
|
-
from bumble.utils import ByteSerializable
|
|
48
|
-
|
|
49
|
-
if TYPE_CHECKING:
|
|
50
|
-
from bumble.gatt_client import AttributeProxy
|
|
51
|
-
from bumble.device import Connection
|
|
52
35
|
|
|
36
|
+
# -----------------------------------------------------------------------------
|
|
37
|
+
# Typing
|
|
38
|
+
# -----------------------------------------------------------------------------
|
|
39
|
+
_T = TypeVar('_T')
|
|
53
40
|
|
|
54
41
|
# -----------------------------------------------------------------------------
|
|
55
42
|
# Logging
|
|
@@ -279,6 +266,13 @@ GATT_SOURCE_AUDIO_LOCATION_CHARACTERISTIC = UUID.from_16_bits(0x2BCC, 'Sou
|
|
|
279
266
|
GATT_AVAILABLE_AUDIO_CONTEXTS_CHARACTERISTIC = UUID.from_16_bits(0x2BCD, 'Available Audio Contexts')
|
|
280
267
|
GATT_SUPPORTED_AUDIO_CONTEXTS_CHARACTERISTIC = UUID.from_16_bits(0x2BCE, 'Supported Audio Contexts')
|
|
281
268
|
|
|
269
|
+
# Gaming Audio Service (GMAS)
|
|
270
|
+
GATT_GMAP_ROLE_CHARACTERISTIC = UUID.from_16_bits(0x2C00, 'GMAP Role')
|
|
271
|
+
GATT_UGG_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2C01, 'UGG Features')
|
|
272
|
+
GATT_UGT_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2C02, 'UGT Features')
|
|
273
|
+
GATT_BGS_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2C03, 'BGS Features')
|
|
274
|
+
GATT_BGR_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2C04, 'BGR Features')
|
|
275
|
+
|
|
282
276
|
# Hearing Access Service
|
|
283
277
|
GATT_HEARING_AID_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2BDA, 'Hearing Aid Features')
|
|
284
278
|
GATT_HEARING_AID_PRESET_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2BDB, 'Hearing Aid Preset Control Point')
|
|
@@ -308,6 +302,7 @@ GATT_CENTRAL_ADDRESS_RESOLUTION__CHARACTERISTIC = UUID.from_16_bi
|
|
|
308
302
|
GATT_CLIENT_SUPPORTED_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2B29, 'Client Supported Features')
|
|
309
303
|
GATT_DATABASE_HASH_CHARACTERISTIC = UUID.from_16_bits(0x2B2A, 'Database Hash')
|
|
310
304
|
GATT_SERVER_SUPPORTED_FEATURES_CHARACTERISTIC = UUID.from_16_bits(0x2B3A, 'Server Supported Features')
|
|
305
|
+
GATT_LE_GATT_SECURITY_LEVELS_CHARACTERISTIC = UUID.from_16_bits(0x2BF5, 'E GATT Security Levels')
|
|
311
306
|
|
|
312
307
|
# fmt: on
|
|
313
308
|
# pylint: enable=line-too-long
|
|
@@ -316,8 +311,6 @@ GATT_SERVER_SUPPORTED_FEATURES_CHARACTERISTIC = UUID.from_16_bi
|
|
|
316
311
|
# -----------------------------------------------------------------------------
|
|
317
312
|
# Utils
|
|
318
313
|
# -----------------------------------------------------------------------------
|
|
319
|
-
|
|
320
|
-
|
|
321
314
|
def show_services(services: Iterable[Service]) -> None:
|
|
322
315
|
for service in services:
|
|
323
316
|
print(color(str(service), 'cyan'))
|
|
@@ -431,7 +424,7 @@ class IncludedServiceDeclaration(Attribute):
|
|
|
431
424
|
|
|
432
425
|
|
|
433
426
|
# -----------------------------------------------------------------------------
|
|
434
|
-
class Characteristic(Attribute):
|
|
427
|
+
class Characteristic(Attribute[_T]):
|
|
435
428
|
'''
|
|
436
429
|
See Vol 3, Part G - 3.3 CHARACTERISTIC DEFINITION
|
|
437
430
|
'''
|
|
@@ -494,7 +487,7 @@ class Characteristic(Attribute):
|
|
|
494
487
|
uuid: Union[str, bytes, UUID],
|
|
495
488
|
properties: Characteristic.Properties,
|
|
496
489
|
permissions: Union[str, Attribute.Permissions],
|
|
497
|
-
value:
|
|
490
|
+
value: Union[AttributeValue[_T], _T, None] = None,
|
|
498
491
|
descriptors: Sequence[Descriptor] = (),
|
|
499
492
|
):
|
|
500
493
|
super().__init__(uuid, permissions, value)
|
|
@@ -554,213 +547,10 @@ class CharacteristicDeclaration(Attribute):
|
|
|
554
547
|
|
|
555
548
|
|
|
556
549
|
# -----------------------------------------------------------------------------
|
|
557
|
-
class CharacteristicValue(AttributeValue):
|
|
550
|
+
class CharacteristicValue(AttributeValue[_T]):
|
|
558
551
|
"""Same as AttributeValue, for backward compatibility"""
|
|
559
552
|
|
|
560
553
|
|
|
561
|
-
# -----------------------------------------------------------------------------
|
|
562
|
-
class CharacteristicAdapter:
|
|
563
|
-
'''
|
|
564
|
-
An adapter that can adapt Characteristic and AttributeProxy objects
|
|
565
|
-
by wrapping their `read_value()` and `write_value()` methods with ones that
|
|
566
|
-
return/accept encoded/decoded values.
|
|
567
|
-
|
|
568
|
-
For proxies (i.e used by a GATT client), the adaptation is one where the return
|
|
569
|
-
value of `read_value()` is decoded and the value passed to `write_value()` is
|
|
570
|
-
encoded. The `subscribe()` method, is wrapped with one where the values are decoded
|
|
571
|
-
before being passed to the subscriber.
|
|
572
|
-
|
|
573
|
-
For local values (i.e hosted by a GATT server) the adaptation is one where the
|
|
574
|
-
return value of `read_value()` is encoded and the value passed to `write_value()`
|
|
575
|
-
is decoded.
|
|
576
|
-
'''
|
|
577
|
-
|
|
578
|
-
read_value: Callable
|
|
579
|
-
write_value: Callable
|
|
580
|
-
|
|
581
|
-
def __init__(self, characteristic: Union[Characteristic, AttributeProxy]):
|
|
582
|
-
self.wrapped_characteristic = characteristic
|
|
583
|
-
self.subscribers: Dict[Callable, Callable] = (
|
|
584
|
-
{}
|
|
585
|
-
) # Map from subscriber to proxy subscriber
|
|
586
|
-
|
|
587
|
-
if isinstance(characteristic, Characteristic):
|
|
588
|
-
self.read_value = self.read_encoded_value
|
|
589
|
-
self.write_value = self.write_encoded_value
|
|
590
|
-
else:
|
|
591
|
-
self.read_value = self.read_decoded_value
|
|
592
|
-
self.write_value = self.write_decoded_value
|
|
593
|
-
self.subscribe = self.wrapped_subscribe
|
|
594
|
-
self.unsubscribe = self.wrapped_unsubscribe
|
|
595
|
-
|
|
596
|
-
def __getattr__(self, name):
|
|
597
|
-
return getattr(self.wrapped_characteristic, name)
|
|
598
|
-
|
|
599
|
-
def __setattr__(self, name, value):
|
|
600
|
-
if name in (
|
|
601
|
-
'wrapped_characteristic',
|
|
602
|
-
'subscribers',
|
|
603
|
-
'read_value',
|
|
604
|
-
'write_value',
|
|
605
|
-
'subscribe',
|
|
606
|
-
'unsubscribe',
|
|
607
|
-
):
|
|
608
|
-
super().__setattr__(name, value)
|
|
609
|
-
else:
|
|
610
|
-
setattr(self.wrapped_characteristic, name, value)
|
|
611
|
-
|
|
612
|
-
async def read_encoded_value(self, connection):
|
|
613
|
-
return self.encode_value(
|
|
614
|
-
await self.wrapped_characteristic.read_value(connection)
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
async def write_encoded_value(self, connection, value):
|
|
618
|
-
return await self.wrapped_characteristic.write_value(
|
|
619
|
-
connection, self.decode_value(value)
|
|
620
|
-
)
|
|
621
|
-
|
|
622
|
-
async def read_decoded_value(self):
|
|
623
|
-
return self.decode_value(await self.wrapped_characteristic.read_value())
|
|
624
|
-
|
|
625
|
-
async def write_decoded_value(self, value, with_response=False):
|
|
626
|
-
return await self.wrapped_characteristic.write_value(
|
|
627
|
-
self.encode_value(value), with_response
|
|
628
|
-
)
|
|
629
|
-
|
|
630
|
-
def encode_value(self, value):
|
|
631
|
-
return value
|
|
632
|
-
|
|
633
|
-
def decode_value(self, value):
|
|
634
|
-
return value
|
|
635
|
-
|
|
636
|
-
def wrapped_subscribe(self, subscriber=None):
|
|
637
|
-
if subscriber is not None:
|
|
638
|
-
if subscriber in self.subscribers:
|
|
639
|
-
# We already have a proxy subscriber
|
|
640
|
-
subscriber = self.subscribers[subscriber]
|
|
641
|
-
else:
|
|
642
|
-
# Create and register a proxy that will decode the value
|
|
643
|
-
original_subscriber = subscriber
|
|
644
|
-
|
|
645
|
-
def on_change(value):
|
|
646
|
-
original_subscriber(self.decode_value(value))
|
|
647
|
-
|
|
648
|
-
self.subscribers[subscriber] = on_change
|
|
649
|
-
subscriber = on_change
|
|
650
|
-
|
|
651
|
-
return self.wrapped_characteristic.subscribe(subscriber)
|
|
652
|
-
|
|
653
|
-
def wrapped_unsubscribe(self, subscriber=None):
|
|
654
|
-
if subscriber in self.subscribers:
|
|
655
|
-
subscriber = self.subscribers.pop(subscriber)
|
|
656
|
-
|
|
657
|
-
return self.wrapped_characteristic.unsubscribe(subscriber)
|
|
658
|
-
|
|
659
|
-
def __str__(self) -> str:
|
|
660
|
-
wrapped = str(self.wrapped_characteristic)
|
|
661
|
-
return f'{self.__class__.__name__}({wrapped})'
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
# -----------------------------------------------------------------------------
|
|
665
|
-
class DelegatedCharacteristicAdapter(CharacteristicAdapter):
|
|
666
|
-
'''
|
|
667
|
-
Adapter that converts bytes values using an encode and a decode function.
|
|
668
|
-
'''
|
|
669
|
-
|
|
670
|
-
def __init__(self, characteristic, encode=None, decode=None):
|
|
671
|
-
super().__init__(characteristic)
|
|
672
|
-
self.encode = encode
|
|
673
|
-
self.decode = decode
|
|
674
|
-
|
|
675
|
-
def encode_value(self, value):
|
|
676
|
-
return self.encode(value) if self.encode else value
|
|
677
|
-
|
|
678
|
-
def decode_value(self, value):
|
|
679
|
-
return self.decode(value) if self.decode else value
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
# -----------------------------------------------------------------------------
|
|
683
|
-
class PackedCharacteristicAdapter(CharacteristicAdapter):
|
|
684
|
-
'''
|
|
685
|
-
Adapter that packs/unpacks characteristic values according to a standard
|
|
686
|
-
Python `struct` format.
|
|
687
|
-
For formats with a single value, the adapted `read_value` and `write_value`
|
|
688
|
-
methods return/accept single values. For formats with multiple values,
|
|
689
|
-
they return/accept a tuple with the same number of elements as is required for
|
|
690
|
-
the format.
|
|
691
|
-
'''
|
|
692
|
-
|
|
693
|
-
def __init__(self, characteristic, pack_format):
|
|
694
|
-
super().__init__(characteristic)
|
|
695
|
-
self.struct = struct.Struct(pack_format)
|
|
696
|
-
|
|
697
|
-
def pack(self, *values):
|
|
698
|
-
return self.struct.pack(*values)
|
|
699
|
-
|
|
700
|
-
def unpack(self, buffer):
|
|
701
|
-
return self.struct.unpack(buffer)
|
|
702
|
-
|
|
703
|
-
def encode_value(self, value):
|
|
704
|
-
return self.pack(*value if isinstance(value, tuple) else (value,))
|
|
705
|
-
|
|
706
|
-
def decode_value(self, value):
|
|
707
|
-
unpacked = self.unpack(value)
|
|
708
|
-
return unpacked[0] if len(unpacked) == 1 else unpacked
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
# -----------------------------------------------------------------------------
|
|
712
|
-
class MappedCharacteristicAdapter(PackedCharacteristicAdapter):
|
|
713
|
-
'''
|
|
714
|
-
Adapter that packs/unpacks characteristic values according to a standard
|
|
715
|
-
Python `struct` format.
|
|
716
|
-
The adapted `read_value` and `write_value` methods return/accept a dictionary which
|
|
717
|
-
is packed/unpacked according to format, with the arguments extracted from the
|
|
718
|
-
dictionary by key, in the same order as they occur in the `keys` parameter.
|
|
719
|
-
'''
|
|
720
|
-
|
|
721
|
-
def __init__(self, characteristic, pack_format, keys):
|
|
722
|
-
super().__init__(characteristic, pack_format)
|
|
723
|
-
self.keys = keys
|
|
724
|
-
|
|
725
|
-
# pylint: disable=arguments-differ
|
|
726
|
-
def pack(self, values):
|
|
727
|
-
return super().pack(*(values[key] for key in self.keys))
|
|
728
|
-
|
|
729
|
-
def unpack(self, buffer):
|
|
730
|
-
return dict(zip(self.keys, super().unpack(buffer)))
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
# -----------------------------------------------------------------------------
|
|
734
|
-
class UTF8CharacteristicAdapter(CharacteristicAdapter):
|
|
735
|
-
'''
|
|
736
|
-
Adapter that converts strings to/from bytes using UTF-8 encoding
|
|
737
|
-
'''
|
|
738
|
-
|
|
739
|
-
def encode_value(self, value: str) -> bytes:
|
|
740
|
-
return value.encode('utf-8')
|
|
741
|
-
|
|
742
|
-
def decode_value(self, value: bytes) -> str:
|
|
743
|
-
return value.decode('utf-8')
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
# -----------------------------------------------------------------------------
|
|
747
|
-
class SerializableCharacteristicAdapter(CharacteristicAdapter):
|
|
748
|
-
'''
|
|
749
|
-
Adapter that converts any class to/from bytes using the class'
|
|
750
|
-
`to_bytes` and `__bytes__` methods, respectively.
|
|
751
|
-
'''
|
|
752
|
-
|
|
753
|
-
def __init__(self, characteristic, cls: Type[ByteSerializable]):
|
|
754
|
-
super().__init__(characteristic)
|
|
755
|
-
self.cls = cls
|
|
756
|
-
|
|
757
|
-
def encode_value(self, value: SupportsBytes) -> bytes:
|
|
758
|
-
return bytes(value)
|
|
759
|
-
|
|
760
|
-
def decode_value(self, value: bytes) -> Any:
|
|
761
|
-
return self.cls.from_bytes(value)
|
|
762
|
-
|
|
763
|
-
|
|
764
554
|
# -----------------------------------------------------------------------------
|
|
765
555
|
class Descriptor(Attribute):
|
|
766
556
|
'''
|
|
@@ -795,3 +585,23 @@ class ClientCharacteristicConfigurationBits(enum.IntFlag):
|
|
|
795
585
|
DEFAULT = 0x0000
|
|
796
586
|
NOTIFICATION = 0x0001
|
|
797
587
|
INDICATION = 0x0002
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
# -----------------------------------------------------------------------------
|
|
591
|
+
class ClientSupportedFeatures(enum.IntFlag):
|
|
592
|
+
'''
|
|
593
|
+
See Vol 3, Part G - 7.2 - Table 7.6: Client Supported Features bit assignments.
|
|
594
|
+
'''
|
|
595
|
+
|
|
596
|
+
ROBUST_CACHING = 0x01
|
|
597
|
+
ENHANCED_ATT_BEARER = 0x02
|
|
598
|
+
MULTIPLE_HANDLE_VALUE_NOTIFICATIONS = 0x04
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
# -----------------------------------------------------------------------------
|
|
602
|
+
class ServerSupportedFeatures(enum.IntFlag):
|
|
603
|
+
'''
|
|
604
|
+
See Vol 3, Part G - 7.4 - Table 7.11: Server Supported Features bit assignments.
|
|
605
|
+
'''
|
|
606
|
+
|
|
607
|
+
EATT_SUPPORTED = 0x01
|
bumble/gatt_adapters.py
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# -----------------------------------------------------------------------------
|
|
16
|
+
# GATT - Type Adapters
|
|
17
|
+
# -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
# -----------------------------------------------------------------------------
|
|
20
|
+
# Imports
|
|
21
|
+
# -----------------------------------------------------------------------------
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
import struct
|
|
24
|
+
from typing import (
|
|
25
|
+
Any,
|
|
26
|
+
Callable,
|
|
27
|
+
Generic,
|
|
28
|
+
Iterable,
|
|
29
|
+
Literal,
|
|
30
|
+
Optional,
|
|
31
|
+
Type,
|
|
32
|
+
TypeVar,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from bumble.core import InvalidOperationError
|
|
36
|
+
from bumble.gatt import Characteristic
|
|
37
|
+
from bumble.gatt_client import CharacteristicProxy
|
|
38
|
+
from bumble.utils import ByteSerializable, IntConvertible
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# -----------------------------------------------------------------------------
|
|
42
|
+
# Typing
|
|
43
|
+
# -----------------------------------------------------------------------------
|
|
44
|
+
_T = TypeVar('_T')
|
|
45
|
+
_T2 = TypeVar('_T2', bound=ByteSerializable)
|
|
46
|
+
_T3 = TypeVar('_T3', bound=IntConvertible)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# -----------------------------------------------------------------------------
|
|
50
|
+
class CharacteristicAdapter(Characteristic, Generic[_T]):
|
|
51
|
+
'''Base class for GATT Characteristic adapters.'''
|
|
52
|
+
|
|
53
|
+
def __init__(self, characteristic: Characteristic) -> None:
|
|
54
|
+
super().__init__(
|
|
55
|
+
characteristic.uuid,
|
|
56
|
+
characteristic.properties,
|
|
57
|
+
characteristic.permissions,
|
|
58
|
+
characteristic.value,
|
|
59
|
+
characteristic.descriptors,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# -----------------------------------------------------------------------------
|
|
64
|
+
class CharacteristicProxyAdapter(CharacteristicProxy[_T]):
|
|
65
|
+
'''Base class for GATT CharacteristicProxy adapters.'''
|
|
66
|
+
|
|
67
|
+
def __init__(self, characteristic_proxy: CharacteristicProxy):
|
|
68
|
+
super().__init__(
|
|
69
|
+
characteristic_proxy.client,
|
|
70
|
+
characteristic_proxy.handle,
|
|
71
|
+
characteristic_proxy.end_group_handle,
|
|
72
|
+
characteristic_proxy.uuid,
|
|
73
|
+
characteristic_proxy.properties,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# -----------------------------------------------------------------------------
|
|
78
|
+
class DelegatedCharacteristicAdapter(CharacteristicAdapter[_T]):
|
|
79
|
+
'''
|
|
80
|
+
Adapter that converts bytes values using an encode and/or a decode function.
|
|
81
|
+
'''
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
characteristic: Characteristic,
|
|
86
|
+
encode: Optional[Callable[[_T], bytes]] = None,
|
|
87
|
+
decode: Optional[Callable[[bytes], _T]] = None,
|
|
88
|
+
):
|
|
89
|
+
super().__init__(characteristic)
|
|
90
|
+
self.encode = encode
|
|
91
|
+
self.decode = decode
|
|
92
|
+
|
|
93
|
+
def encode_value(self, value: _T) -> bytes:
|
|
94
|
+
if self.encode is None:
|
|
95
|
+
raise InvalidOperationError('delegated adapter does not have an encoder')
|
|
96
|
+
return self.encode(value)
|
|
97
|
+
|
|
98
|
+
def decode_value(self, value: bytes) -> _T:
|
|
99
|
+
if self.decode is None:
|
|
100
|
+
raise InvalidOperationError('delegate adapter does not have a decoder')
|
|
101
|
+
return self.decode(value)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# -----------------------------------------------------------------------------
|
|
105
|
+
class DelegatedCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T]):
|
|
106
|
+
'''
|
|
107
|
+
Adapter that converts bytes values using an encode and a decode function.
|
|
108
|
+
'''
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
characteristic_proxy: CharacteristicProxy,
|
|
113
|
+
encode: Optional[Callable[[_T], bytes]] = None,
|
|
114
|
+
decode: Optional[Callable[[bytes], _T]] = None,
|
|
115
|
+
):
|
|
116
|
+
super().__init__(characteristic_proxy)
|
|
117
|
+
self.encode = encode
|
|
118
|
+
self.decode = decode
|
|
119
|
+
|
|
120
|
+
def encode_value(self, value: _T) -> bytes:
|
|
121
|
+
if self.encode is None:
|
|
122
|
+
raise InvalidOperationError('delegated adapter does not have an encoder')
|
|
123
|
+
return self.encode(value)
|
|
124
|
+
|
|
125
|
+
def decode_value(self, value: bytes) -> _T:
|
|
126
|
+
if self.decode is None:
|
|
127
|
+
raise InvalidOperationError('delegate adapter does not have a decoder')
|
|
128
|
+
return self.decode(value)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# -----------------------------------------------------------------------------
|
|
132
|
+
class PackedCharacteristicAdapter(CharacteristicAdapter):
|
|
133
|
+
'''
|
|
134
|
+
Adapter that packs/unpacks characteristic values according to a standard
|
|
135
|
+
Python `struct` format.
|
|
136
|
+
For formats with a single value, the adapted `read_value` and `write_value`
|
|
137
|
+
methods return/accept single values. For formats with multiple values,
|
|
138
|
+
they return/accept a tuple with the same number of elements as is required for
|
|
139
|
+
the format.
|
|
140
|
+
'''
|
|
141
|
+
|
|
142
|
+
def __init__(self, characteristic: Characteristic, pack_format: str) -> None:
|
|
143
|
+
super().__init__(characteristic)
|
|
144
|
+
self.struct = struct.Struct(pack_format)
|
|
145
|
+
|
|
146
|
+
def pack(self, *values) -> bytes:
|
|
147
|
+
return self.struct.pack(*values)
|
|
148
|
+
|
|
149
|
+
def unpack(self, buffer: bytes) -> tuple:
|
|
150
|
+
return self.struct.unpack(buffer)
|
|
151
|
+
|
|
152
|
+
def encode_value(self, value: Any) -> bytes:
|
|
153
|
+
return self.pack(*value if isinstance(value, tuple) else (value,))
|
|
154
|
+
|
|
155
|
+
def decode_value(self, value: bytes) -> Any:
|
|
156
|
+
unpacked = self.unpack(value)
|
|
157
|
+
return unpacked[0] if len(unpacked) == 1 else unpacked
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# -----------------------------------------------------------------------------
|
|
161
|
+
class PackedCharacteristicProxyAdapter(CharacteristicProxyAdapter):
|
|
162
|
+
'''
|
|
163
|
+
Adapter that packs/unpacks characteristic values according to a standard
|
|
164
|
+
Python `struct` format.
|
|
165
|
+
For formats with a single value, the adapted `read_value` and `write_value`
|
|
166
|
+
methods return/accept single values. For formats with multiple values,
|
|
167
|
+
they return/accept a tuple with the same number of elements as is required for
|
|
168
|
+
the format.
|
|
169
|
+
'''
|
|
170
|
+
|
|
171
|
+
def __init__(self, characteristic_proxy, pack_format):
|
|
172
|
+
super().__init__(characteristic_proxy)
|
|
173
|
+
self.struct = struct.Struct(pack_format)
|
|
174
|
+
|
|
175
|
+
def pack(self, *values) -> bytes:
|
|
176
|
+
return self.struct.pack(*values)
|
|
177
|
+
|
|
178
|
+
def unpack(self, buffer: bytes) -> tuple:
|
|
179
|
+
return self.struct.unpack(buffer)
|
|
180
|
+
|
|
181
|
+
def encode_value(self, value: Any) -> bytes:
|
|
182
|
+
return self.pack(*value if isinstance(value, tuple) else (value,))
|
|
183
|
+
|
|
184
|
+
def decode_value(self, value: bytes) -> Any:
|
|
185
|
+
unpacked = self.unpack(value)
|
|
186
|
+
return unpacked[0] if len(unpacked) == 1 else unpacked
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# -----------------------------------------------------------------------------
|
|
190
|
+
class MappedCharacteristicAdapter(PackedCharacteristicAdapter):
|
|
191
|
+
'''
|
|
192
|
+
Adapter that packs/unpacks characteristic values according to a standard
|
|
193
|
+
Python `struct` format.
|
|
194
|
+
The adapted `read_value` and `write_value` methods return/accept a dictionary which
|
|
195
|
+
is packed/unpacked according to format, with the arguments extracted from the
|
|
196
|
+
dictionary by key, in the same order as they occur in the `keys` parameter.
|
|
197
|
+
'''
|
|
198
|
+
|
|
199
|
+
def __init__(
|
|
200
|
+
self, characteristic: Characteristic, pack_format: str, keys: Iterable[str]
|
|
201
|
+
) -> None:
|
|
202
|
+
super().__init__(characteristic, pack_format)
|
|
203
|
+
self.keys = keys
|
|
204
|
+
|
|
205
|
+
# pylint: disable=arguments-differ
|
|
206
|
+
def pack(self, values) -> bytes:
|
|
207
|
+
return super().pack(*(values[key] for key in self.keys))
|
|
208
|
+
|
|
209
|
+
def unpack(self, buffer: bytes) -> Any:
|
|
210
|
+
return dict(zip(self.keys, super().unpack(buffer)))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# -----------------------------------------------------------------------------
|
|
214
|
+
class MappedCharacteristicProxyAdapter(PackedCharacteristicProxyAdapter):
|
|
215
|
+
'''
|
|
216
|
+
Adapter that packs/unpacks characteristic values according to a standard
|
|
217
|
+
Python `struct` format.
|
|
218
|
+
The adapted `read_value` and `write_value` methods return/accept a dictionary which
|
|
219
|
+
is packed/unpacked according to format, with the arguments extracted from the
|
|
220
|
+
dictionary by key, in the same order as they occur in the `keys` parameter.
|
|
221
|
+
'''
|
|
222
|
+
|
|
223
|
+
def __init__(
|
|
224
|
+
self,
|
|
225
|
+
characteristic_proxy: CharacteristicProxy,
|
|
226
|
+
pack_format: str,
|
|
227
|
+
keys: Iterable[str],
|
|
228
|
+
) -> None:
|
|
229
|
+
super().__init__(characteristic_proxy, pack_format)
|
|
230
|
+
self.keys = keys
|
|
231
|
+
|
|
232
|
+
# pylint: disable=arguments-differ
|
|
233
|
+
def pack(self, values) -> bytes:
|
|
234
|
+
return super().pack(*(values[key] for key in self.keys))
|
|
235
|
+
|
|
236
|
+
def unpack(self, buffer: bytes) -> Any:
|
|
237
|
+
return dict(zip(self.keys, super().unpack(buffer)))
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# -----------------------------------------------------------------------------
|
|
241
|
+
class UTF8CharacteristicAdapter(CharacteristicAdapter[str]):
|
|
242
|
+
'''
|
|
243
|
+
Adapter that converts strings to/from bytes using UTF-8 encoding
|
|
244
|
+
'''
|
|
245
|
+
|
|
246
|
+
def encode_value(self, value: str) -> bytes:
|
|
247
|
+
return value.encode('utf-8')
|
|
248
|
+
|
|
249
|
+
def decode_value(self, value: bytes) -> str:
|
|
250
|
+
return value.decode('utf-8')
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# -----------------------------------------------------------------------------
|
|
254
|
+
class UTF8CharacteristicProxyAdapter(CharacteristicProxyAdapter[str]):
|
|
255
|
+
'''
|
|
256
|
+
Adapter that converts strings to/from bytes using UTF-8 encoding
|
|
257
|
+
'''
|
|
258
|
+
|
|
259
|
+
def encode_value(self, value: str) -> bytes:
|
|
260
|
+
return value.encode('utf-8')
|
|
261
|
+
|
|
262
|
+
def decode_value(self, value: bytes) -> str:
|
|
263
|
+
return value.decode('utf-8')
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# -----------------------------------------------------------------------------
|
|
267
|
+
class SerializableCharacteristicAdapter(CharacteristicAdapter[_T2]):
|
|
268
|
+
'''
|
|
269
|
+
Adapter that converts any class to/from bytes using the class'
|
|
270
|
+
`to_bytes` and `__bytes__` methods, respectively.
|
|
271
|
+
'''
|
|
272
|
+
|
|
273
|
+
def __init__(self, characteristic: Characteristic, cls: Type[_T2]) -> None:
|
|
274
|
+
super().__init__(characteristic)
|
|
275
|
+
self.cls = cls
|
|
276
|
+
|
|
277
|
+
def encode_value(self, value: _T2) -> bytes:
|
|
278
|
+
return bytes(value)
|
|
279
|
+
|
|
280
|
+
def decode_value(self, value: bytes) -> _T2:
|
|
281
|
+
return self.cls.from_bytes(value)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# -----------------------------------------------------------------------------
|
|
285
|
+
class SerializableCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T2]):
|
|
286
|
+
'''
|
|
287
|
+
Adapter that converts any class to/from bytes using the class'
|
|
288
|
+
`to_bytes` and `__bytes__` methods, respectively.
|
|
289
|
+
'''
|
|
290
|
+
|
|
291
|
+
def __init__(
|
|
292
|
+
self, characteristic_proxy: CharacteristicProxy, cls: Type[_T2]
|
|
293
|
+
) -> None:
|
|
294
|
+
super().__init__(characteristic_proxy)
|
|
295
|
+
self.cls = cls
|
|
296
|
+
|
|
297
|
+
def encode_value(self, value: _T2) -> bytes:
|
|
298
|
+
return bytes(value)
|
|
299
|
+
|
|
300
|
+
def decode_value(self, value: bytes) -> _T2:
|
|
301
|
+
return self.cls.from_bytes(value)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# -----------------------------------------------------------------------------
|
|
305
|
+
class EnumCharacteristicAdapter(CharacteristicAdapter[_T3]):
|
|
306
|
+
'''
|
|
307
|
+
Adapter that converts int-enum-like classes to/from bytes using the class'
|
|
308
|
+
`int().to_bytes()` and `from_bytes()` methods, respectively.
|
|
309
|
+
'''
|
|
310
|
+
|
|
311
|
+
def __init__(
|
|
312
|
+
self,
|
|
313
|
+
characteristic: Characteristic,
|
|
314
|
+
cls: Type[_T3],
|
|
315
|
+
length: int,
|
|
316
|
+
byteorder: Literal['little', 'big'] = 'little',
|
|
317
|
+
):
|
|
318
|
+
"""
|
|
319
|
+
Initialize an instance.
|
|
320
|
+
|
|
321
|
+
Params:
|
|
322
|
+
characteristic: the Characteristic to adapt to/from
|
|
323
|
+
cls: the class to/from which to convert integer values
|
|
324
|
+
length: number of bytes used to represent integer values
|
|
325
|
+
byteorder: byte order of the byte representation of integers.
|
|
326
|
+
"""
|
|
327
|
+
super().__init__(characteristic)
|
|
328
|
+
self.cls = cls
|
|
329
|
+
self.length = length
|
|
330
|
+
self.byteorder = byteorder
|
|
331
|
+
|
|
332
|
+
def encode_value(self, value: _T3) -> bytes:
|
|
333
|
+
return int(value).to_bytes(self.length, self.byteorder)
|
|
334
|
+
|
|
335
|
+
def decode_value(self, value: bytes) -> _T3:
|
|
336
|
+
int_value = int.from_bytes(value, self.byteorder)
|
|
337
|
+
return self.cls(int_value)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# -----------------------------------------------------------------------------
|
|
341
|
+
class EnumCharacteristicProxyAdapter(CharacteristicProxyAdapter[_T3]):
|
|
342
|
+
'''
|
|
343
|
+
Adapter that converts int-enum-like classes to/from bytes using the class'
|
|
344
|
+
`int().to_bytes()` and `from_bytes()` methods, respectively.
|
|
345
|
+
'''
|
|
346
|
+
|
|
347
|
+
def __init__(
|
|
348
|
+
self,
|
|
349
|
+
characteristic_proxy: CharacteristicProxy,
|
|
350
|
+
cls: Type[_T3],
|
|
351
|
+
length: int,
|
|
352
|
+
byteorder: Literal['little', 'big'] = 'little',
|
|
353
|
+
):
|
|
354
|
+
"""
|
|
355
|
+
Initialize an instance.
|
|
356
|
+
|
|
357
|
+
Params:
|
|
358
|
+
characteristic_proxy: the CharacteristicProxy to adapt to/from
|
|
359
|
+
cls: the class to/from which to convert integer values
|
|
360
|
+
length: number of bytes used to represent integer values
|
|
361
|
+
byteorder: byte order of the byte representation of integers.
|
|
362
|
+
"""
|
|
363
|
+
super().__init__(characteristic_proxy)
|
|
364
|
+
self.cls = cls
|
|
365
|
+
self.length = length
|
|
366
|
+
self.byteorder = byteorder
|
|
367
|
+
|
|
368
|
+
def encode_value(self, value: _T3) -> bytes:
|
|
369
|
+
return int(value).to_bytes(self.length, self.byteorder)
|
|
370
|
+
|
|
371
|
+
def decode_value(self, value: bytes) -> _T3:
|
|
372
|
+
int_value = int.from_bytes(value, self.byteorder)
|
|
373
|
+
a = self.cls(int_value)
|
|
374
|
+
return self.cls(int_value)
|