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/gatt.py
CHANGED
|
@@ -27,28 +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
|
-
from bumble.core import BaseBumbleError,
|
|
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
35
|
|
|
36
|
+
# -----------------------------------------------------------------------------
|
|
37
|
+
# Typing
|
|
38
|
+
# -----------------------------------------------------------------------------
|
|
39
|
+
_T = TypeVar('_T')
|
|
52
40
|
|
|
53
41
|
# -----------------------------------------------------------------------------
|
|
54
42
|
# Logging
|
|
@@ -298,6 +286,22 @@ GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC = UUID('38663f1a-e711-4cac-b641-32
|
|
|
298
286
|
GATT_ASHA_VOLUME_CHARACTERISTIC = UUID('00e4ca9e-ab14-41e4-8823-f9e70c7e91df', 'Volume')
|
|
299
287
|
GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC = UUID('2d410339-82b6-42aa-b34e-e2e01df8cc1a', 'LE_PSM_OUT')
|
|
300
288
|
|
|
289
|
+
# Apple Notification Center Service
|
|
290
|
+
GATT_ANCS_SERVICE = UUID('7905F431-B5CE-4E99-A40F-4B1E122D00D0', 'Apple Notification Center')
|
|
291
|
+
GATT_ANCS_NOTIFICATION_SOURCE_CHARACTERISTIC = UUID('9FBF120D-6301-42D9-8C58-25E699A21DBD', 'Notification Source')
|
|
292
|
+
GATT_ANCS_CONTROL_POINT_CHARACTERISTIC = UUID('69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9', 'Control Point')
|
|
293
|
+
GATT_ANCS_DATA_SOURCE_CHARACTERISTIC = UUID('22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB', 'Data Source')
|
|
294
|
+
|
|
295
|
+
# Apple Media Service
|
|
296
|
+
GATT_AMS_SERVICE = UUID('89D3502B-0F36-433A-8EF4-C502AD55F8DC', 'Apple Media')
|
|
297
|
+
GATT_AMS_REMOTE_COMMAND_CHARACTERISTIC = UUID('9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2', 'Remote Command')
|
|
298
|
+
GATT_AMS_ENTITY_UPDATE_CHARACTERISTIC = UUID('2F7CABCE-808D-411F-9A0C-BB92BA96C102', 'Entity Update')
|
|
299
|
+
GATT_AMS_ENTITY_ATTRIBUTE_CHARACTERISTIC = UUID('C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7', 'Entity Attribute')
|
|
300
|
+
|
|
301
|
+
# Misc Apple Services
|
|
302
|
+
GATT_APPLE_CONTINUITY_SERVICE = UUID('D0611E78-BBB4-4591-A5F8-487910AE4366', 'Apple Continuity')
|
|
303
|
+
GATT_APPLE_NEARBY_SERVICE = UUID('9FA480E0-4967-4542-9390-D343DC5D04AE', 'Apple Nearby')
|
|
304
|
+
|
|
301
305
|
# Misc
|
|
302
306
|
GATT_DEVICE_NAME_CHARACTERISTIC = UUID.from_16_bits(0x2A00, 'Device Name')
|
|
303
307
|
GATT_APPEARANCE_CHARACTERISTIC = UUID.from_16_bits(0x2A01, 'Appearance')
|
|
@@ -436,7 +440,7 @@ class IncludedServiceDeclaration(Attribute):
|
|
|
436
440
|
|
|
437
441
|
|
|
438
442
|
# -----------------------------------------------------------------------------
|
|
439
|
-
class Characteristic(Attribute):
|
|
443
|
+
class Characteristic(Attribute[_T]):
|
|
440
444
|
'''
|
|
441
445
|
See Vol 3, Part G - 3.3 CHARACTERISTIC DEFINITION
|
|
442
446
|
'''
|
|
@@ -499,7 +503,7 @@ class Characteristic(Attribute):
|
|
|
499
503
|
uuid: Union[str, bytes, UUID],
|
|
500
504
|
properties: Characteristic.Properties,
|
|
501
505
|
permissions: Union[str, Attribute.Permissions],
|
|
502
|
-
value:
|
|
506
|
+
value: Union[AttributeValue[_T], _T, None] = None,
|
|
503
507
|
descriptors: Sequence[Descriptor] = (),
|
|
504
508
|
):
|
|
505
509
|
super().__init__(uuid, permissions, value)
|
|
@@ -559,217 +563,10 @@ class CharacteristicDeclaration(Attribute):
|
|
|
559
563
|
|
|
560
564
|
|
|
561
565
|
# -----------------------------------------------------------------------------
|
|
562
|
-
class CharacteristicValue(AttributeValue):
|
|
566
|
+
class CharacteristicValue(AttributeValue[_T]):
|
|
563
567
|
"""Same as AttributeValue, for backward compatibility"""
|
|
564
568
|
|
|
565
569
|
|
|
566
|
-
# -----------------------------------------------------------------------------
|
|
567
|
-
class CharacteristicAdapter:
|
|
568
|
-
'''
|
|
569
|
-
An adapter that can adapt Characteristic and AttributeProxy objects
|
|
570
|
-
by wrapping their `read_value()` and `write_value()` methods with ones that
|
|
571
|
-
return/accept encoded/decoded values.
|
|
572
|
-
|
|
573
|
-
For proxies (i.e used by a GATT client), the adaptation is one where the return
|
|
574
|
-
value of `read_value()` is decoded and the value passed to `write_value()` is
|
|
575
|
-
encoded. The `subscribe()` method, is wrapped with one where the values are decoded
|
|
576
|
-
before being passed to the subscriber.
|
|
577
|
-
|
|
578
|
-
For local values (i.e hosted by a GATT server) the adaptation is one where the
|
|
579
|
-
return value of `read_value()` is encoded and the value passed to `write_value()`
|
|
580
|
-
is decoded.
|
|
581
|
-
'''
|
|
582
|
-
|
|
583
|
-
read_value: Callable
|
|
584
|
-
write_value: Callable
|
|
585
|
-
|
|
586
|
-
def __init__(self, characteristic: Union[Characteristic, AttributeProxy]):
|
|
587
|
-
self.wrapped_characteristic = characteristic
|
|
588
|
-
self.subscribers: Dict[Callable, Callable] = (
|
|
589
|
-
{}
|
|
590
|
-
) # Map from subscriber to proxy subscriber
|
|
591
|
-
|
|
592
|
-
if isinstance(characteristic, Characteristic):
|
|
593
|
-
self.read_value = self.read_encoded_value
|
|
594
|
-
self.write_value = self.write_encoded_value
|
|
595
|
-
else:
|
|
596
|
-
self.read_value = self.read_decoded_value
|
|
597
|
-
self.write_value = self.write_decoded_value
|
|
598
|
-
self.subscribe = self.wrapped_subscribe
|
|
599
|
-
self.unsubscribe = self.wrapped_unsubscribe
|
|
600
|
-
|
|
601
|
-
def __getattr__(self, name):
|
|
602
|
-
return getattr(self.wrapped_characteristic, name)
|
|
603
|
-
|
|
604
|
-
def __setattr__(self, name, value):
|
|
605
|
-
if name in (
|
|
606
|
-
'wrapped_characteristic',
|
|
607
|
-
'subscribers',
|
|
608
|
-
'read_value',
|
|
609
|
-
'write_value',
|
|
610
|
-
'subscribe',
|
|
611
|
-
'unsubscribe',
|
|
612
|
-
):
|
|
613
|
-
super().__setattr__(name, value)
|
|
614
|
-
else:
|
|
615
|
-
setattr(self.wrapped_characteristic, name, value)
|
|
616
|
-
|
|
617
|
-
async def read_encoded_value(self, connection):
|
|
618
|
-
return self.encode_value(
|
|
619
|
-
await self.wrapped_characteristic.read_value(connection)
|
|
620
|
-
)
|
|
621
|
-
|
|
622
|
-
async def write_encoded_value(self, connection, value):
|
|
623
|
-
return await self.wrapped_characteristic.write_value(
|
|
624
|
-
connection, self.decode_value(value)
|
|
625
|
-
)
|
|
626
|
-
|
|
627
|
-
async def read_decoded_value(self):
|
|
628
|
-
return self.decode_value(await self.wrapped_characteristic.read_value())
|
|
629
|
-
|
|
630
|
-
async def write_decoded_value(self, value, with_response=False):
|
|
631
|
-
return await self.wrapped_characteristic.write_value(
|
|
632
|
-
self.encode_value(value), with_response
|
|
633
|
-
)
|
|
634
|
-
|
|
635
|
-
def encode_value(self, value):
|
|
636
|
-
return value
|
|
637
|
-
|
|
638
|
-
def decode_value(self, value):
|
|
639
|
-
return value
|
|
640
|
-
|
|
641
|
-
def wrapped_subscribe(self, subscriber=None):
|
|
642
|
-
if subscriber is not None:
|
|
643
|
-
if subscriber in self.subscribers:
|
|
644
|
-
# We already have a proxy subscriber
|
|
645
|
-
subscriber = self.subscribers[subscriber]
|
|
646
|
-
else:
|
|
647
|
-
# Create and register a proxy that will decode the value
|
|
648
|
-
original_subscriber = subscriber
|
|
649
|
-
|
|
650
|
-
def on_change(value):
|
|
651
|
-
original_subscriber(self.decode_value(value))
|
|
652
|
-
|
|
653
|
-
self.subscribers[subscriber] = on_change
|
|
654
|
-
subscriber = on_change
|
|
655
|
-
|
|
656
|
-
return self.wrapped_characteristic.subscribe(subscriber)
|
|
657
|
-
|
|
658
|
-
def wrapped_unsubscribe(self, subscriber=None):
|
|
659
|
-
if subscriber in self.subscribers:
|
|
660
|
-
subscriber = self.subscribers.pop(subscriber)
|
|
661
|
-
|
|
662
|
-
return self.wrapped_characteristic.unsubscribe(subscriber)
|
|
663
|
-
|
|
664
|
-
def __str__(self) -> str:
|
|
665
|
-
wrapped = str(self.wrapped_characteristic)
|
|
666
|
-
return f'{self.__class__.__name__}({wrapped})'
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
# -----------------------------------------------------------------------------
|
|
670
|
-
class DelegatedCharacteristicAdapter(CharacteristicAdapter):
|
|
671
|
-
'''
|
|
672
|
-
Adapter that converts bytes values using an encode and a decode function.
|
|
673
|
-
'''
|
|
674
|
-
|
|
675
|
-
def __init__(self, characteristic, encode=None, decode=None):
|
|
676
|
-
super().__init__(characteristic)
|
|
677
|
-
self.encode = encode
|
|
678
|
-
self.decode = decode
|
|
679
|
-
|
|
680
|
-
def encode_value(self, value):
|
|
681
|
-
if self.encode is None:
|
|
682
|
-
raise InvalidOperationError('delegated adapter does not have an encoder')
|
|
683
|
-
return self.encode(value)
|
|
684
|
-
|
|
685
|
-
def decode_value(self, value):
|
|
686
|
-
if self.decode is None:
|
|
687
|
-
raise InvalidOperationError('delegate adapter does not have a decoder')
|
|
688
|
-
return self.decode(value)
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
# -----------------------------------------------------------------------------
|
|
692
|
-
class PackedCharacteristicAdapter(CharacteristicAdapter):
|
|
693
|
-
'''
|
|
694
|
-
Adapter that packs/unpacks characteristic values according to a standard
|
|
695
|
-
Python `struct` format.
|
|
696
|
-
For formats with a single value, the adapted `read_value` and `write_value`
|
|
697
|
-
methods return/accept single values. For formats with multiple values,
|
|
698
|
-
they return/accept a tuple with the same number of elements as is required for
|
|
699
|
-
the format.
|
|
700
|
-
'''
|
|
701
|
-
|
|
702
|
-
def __init__(self, characteristic, pack_format):
|
|
703
|
-
super().__init__(characteristic)
|
|
704
|
-
self.struct = struct.Struct(pack_format)
|
|
705
|
-
|
|
706
|
-
def pack(self, *values):
|
|
707
|
-
return self.struct.pack(*values)
|
|
708
|
-
|
|
709
|
-
def unpack(self, buffer):
|
|
710
|
-
return self.struct.unpack(buffer)
|
|
711
|
-
|
|
712
|
-
def encode_value(self, value):
|
|
713
|
-
return self.pack(*value if isinstance(value, tuple) else (value,))
|
|
714
|
-
|
|
715
|
-
def decode_value(self, value):
|
|
716
|
-
unpacked = self.unpack(value)
|
|
717
|
-
return unpacked[0] if len(unpacked) == 1 else unpacked
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
# -----------------------------------------------------------------------------
|
|
721
|
-
class MappedCharacteristicAdapter(PackedCharacteristicAdapter):
|
|
722
|
-
'''
|
|
723
|
-
Adapter that packs/unpacks characteristic values according to a standard
|
|
724
|
-
Python `struct` format.
|
|
725
|
-
The adapted `read_value` and `write_value` methods return/accept a dictionary which
|
|
726
|
-
is packed/unpacked according to format, with the arguments extracted from the
|
|
727
|
-
dictionary by key, in the same order as they occur in the `keys` parameter.
|
|
728
|
-
'''
|
|
729
|
-
|
|
730
|
-
def __init__(self, characteristic, pack_format, keys):
|
|
731
|
-
super().__init__(characteristic, pack_format)
|
|
732
|
-
self.keys = keys
|
|
733
|
-
|
|
734
|
-
# pylint: disable=arguments-differ
|
|
735
|
-
def pack(self, values):
|
|
736
|
-
return super().pack(*(values[key] for key in self.keys))
|
|
737
|
-
|
|
738
|
-
def unpack(self, buffer):
|
|
739
|
-
return dict(zip(self.keys, super().unpack(buffer)))
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
# -----------------------------------------------------------------------------
|
|
743
|
-
class UTF8CharacteristicAdapter(CharacteristicAdapter):
|
|
744
|
-
'''
|
|
745
|
-
Adapter that converts strings to/from bytes using UTF-8 encoding
|
|
746
|
-
'''
|
|
747
|
-
|
|
748
|
-
def encode_value(self, value: str) -> bytes:
|
|
749
|
-
return value.encode('utf-8')
|
|
750
|
-
|
|
751
|
-
def decode_value(self, value: bytes) -> str:
|
|
752
|
-
return value.decode('utf-8')
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
# -----------------------------------------------------------------------------
|
|
756
|
-
class SerializableCharacteristicAdapter(CharacteristicAdapter):
|
|
757
|
-
'''
|
|
758
|
-
Adapter that converts any class to/from bytes using the class'
|
|
759
|
-
`to_bytes` and `__bytes__` methods, respectively.
|
|
760
|
-
'''
|
|
761
|
-
|
|
762
|
-
def __init__(self, characteristic, cls: Type[ByteSerializable]):
|
|
763
|
-
super().__init__(characteristic)
|
|
764
|
-
self.cls = cls
|
|
765
|
-
|
|
766
|
-
def encode_value(self, value: SupportsBytes) -> bytes:
|
|
767
|
-
return bytes(value)
|
|
768
|
-
|
|
769
|
-
def decode_value(self, value: bytes) -> Any:
|
|
770
|
-
return self.cls.from_bytes(value)
|
|
771
|
-
|
|
772
|
-
|
|
773
570
|
# -----------------------------------------------------------------------------
|
|
774
571
|
class Descriptor(Attribute):
|
|
775
572
|
'''
|
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)
|