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.
Files changed (47) hide show
  1. bumble/_version.py +9 -4
  2. bumble/apps/auracast.py +29 -35
  3. bumble/apps/bench.py +13 -10
  4. bumble/apps/console.py +19 -12
  5. bumble/apps/gg_bridge.py +1 -1
  6. bumble/att.py +61 -39
  7. bumble/controller.py +7 -8
  8. bumble/core.py +306 -159
  9. bumble/device.py +127 -82
  10. bumble/gatt.py +25 -228
  11. bumble/gatt_adapters.py +374 -0
  12. bumble/gatt_client.py +38 -31
  13. bumble/gatt_server.py +5 -5
  14. bumble/hci.py +76 -71
  15. bumble/host.py +19 -8
  16. bumble/l2cap.py +2 -2
  17. bumble/link.py +2 -2
  18. bumble/pairing.py +5 -5
  19. bumble/pandora/host.py +19 -23
  20. bumble/pandora/security.py +2 -3
  21. bumble/pandora/utils.py +2 -2
  22. bumble/profiles/aics.py +33 -23
  23. bumble/profiles/ancs.py +514 -0
  24. bumble/profiles/ascs.py +2 -1
  25. bumble/profiles/asha.py +11 -9
  26. bumble/profiles/bass.py +8 -5
  27. bumble/profiles/battery_service.py +13 -3
  28. bumble/profiles/device_information_service.py +16 -14
  29. bumble/profiles/gap.py +12 -8
  30. bumble/profiles/gatt_service.py +1 -0
  31. bumble/profiles/gmap.py +16 -11
  32. bumble/profiles/hap.py +8 -6
  33. bumble/profiles/heart_rate_service.py +20 -4
  34. bumble/profiles/mcp.py +11 -9
  35. bumble/profiles/pacs.py +37 -24
  36. bumble/profiles/tmap.py +6 -4
  37. bumble/profiles/vcs.py +6 -5
  38. bumble/profiles/vocs.py +49 -41
  39. bumble/smp.py +3 -3
  40. bumble/transport/usb.py +1 -3
  41. bumble/utils.py +10 -0
  42. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/METADATA +3 -3
  43. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/RECORD +47 -45
  44. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/WHEEL +1 -1
  45. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/LICENSE +0 -0
  46. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/entry_points.txt +0 -0
  47. {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, InvalidOperationError, UUID
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: Any = b'',
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
  '''
@@ -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)