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.
Files changed (51) hide show
  1. bumble/_version.py +9 -4
  2. bumble/apps/auracast.py +631 -98
  3. bumble/apps/bench.py +238 -157
  4. bumble/apps/console.py +19 -12
  5. bumble/apps/controller_info.py +23 -7
  6. bumble/apps/device_info.py +50 -4
  7. bumble/apps/gg_bridge.py +1 -1
  8. bumble/apps/lea_unicast/app.py +61 -201
  9. bumble/att.py +51 -37
  10. bumble/audio/__init__.py +17 -0
  11. bumble/audio/io.py +553 -0
  12. bumble/controller.py +24 -9
  13. bumble/core.py +305 -156
  14. bumble/device.py +1090 -99
  15. bumble/gatt.py +36 -226
  16. bumble/gatt_adapters.py +374 -0
  17. bumble/gatt_client.py +52 -33
  18. bumble/gatt_server.py +5 -5
  19. bumble/hci.py +812 -14
  20. bumble/host.py +367 -65
  21. bumble/l2cap.py +3 -16
  22. bumble/pairing.py +5 -5
  23. bumble/pandora/host.py +7 -12
  24. bumble/profiles/aics.py +48 -57
  25. bumble/profiles/ascs.py +8 -19
  26. bumble/profiles/asha.py +16 -14
  27. bumble/profiles/bass.py +16 -22
  28. bumble/profiles/battery_service.py +13 -3
  29. bumble/profiles/device_information_service.py +16 -14
  30. bumble/profiles/gap.py +12 -8
  31. bumble/profiles/gatt_service.py +167 -0
  32. bumble/profiles/gmap.py +198 -0
  33. bumble/profiles/hap.py +8 -6
  34. bumble/profiles/heart_rate_service.py +20 -4
  35. bumble/profiles/le_audio.py +87 -4
  36. bumble/profiles/mcp.py +11 -9
  37. bumble/profiles/pacs.py +61 -16
  38. bumble/profiles/tmap.py +8 -12
  39. bumble/profiles/{vcp.py → vcs.py} +35 -29
  40. bumble/profiles/vocs.py +62 -85
  41. bumble/sdp.py +223 -93
  42. bumble/smp.py +1 -1
  43. bumble/utils.py +12 -2
  44. bumble/vendor/android/hci.py +1 -1
  45. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/METADATA +13 -11
  46. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/RECORD +50 -46
  47. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/WHEEL +1 -1
  48. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/entry_points.txt +1 -0
  49. bumble/apps/lea_unicast/liblc3.wasm +0 -0
  50. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/LICENSE +0 -0
  51. {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: Any = b'',
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
@@ -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)