• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021-2022 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 - Generic Attribute Profile
17#
18# See Bluetooth spec @ Vol 3, Part G
19#
20# -----------------------------------------------------------------------------
21
22# -----------------------------------------------------------------------------
23# Imports
24# -----------------------------------------------------------------------------
25from __future__ import annotations
26import asyncio
27import enum
28import functools
29import logging
30import struct
31from typing import Optional, Sequence
32
33from .colors import color
34from .core import UUID, get_dict_key_by_value
35from .att import Attribute
36
37
38# -----------------------------------------------------------------------------
39# Logging
40# -----------------------------------------------------------------------------
41logger = logging.getLogger(__name__)
42
43# -----------------------------------------------------------------------------
44# Constants
45# -----------------------------------------------------------------------------
46# fmt: off
47# pylint: disable=line-too-long
48
49GATT_REQUEST_TIMEOUT = 30  # seconds
50
51GATT_MAX_ATTRIBUTE_VALUE_SIZE = 512
52
53# Services
54GATT_GENERIC_ACCESS_SERVICE                 = UUID.from_16_bits(0x1800, 'Generic Access')
55GATT_GENERIC_ATTRIBUTE_SERVICE              = UUID.from_16_bits(0x1801, 'Generic Attribute')
56GATT_IMMEDIATE_ALERT_SERVICE                = UUID.from_16_bits(0x1802, 'Immediate Alert')
57GATT_LINK_LOSS_SERVICE                      = UUID.from_16_bits(0x1803, 'Link Loss')
58GATT_TX_POWER_SERVICE                       = UUID.from_16_bits(0x1804, 'TX Power')
59GATT_CURRENT_TIME_SERVICE                   = UUID.from_16_bits(0x1805, 'Current Time')
60GATT_REFERENCE_TIME_UPDATE_SERVICE          = UUID.from_16_bits(0x1806, 'Reference Time Update')
61GATT_NEXT_DST_CHANGE_SERVICE                = UUID.from_16_bits(0x1807, 'Next DST Change')
62GATT_GLUCOSE_SERVICE                        = UUID.from_16_bits(0x1808, 'Glucose')
63GATT_HEALTH_THERMOMETER_SERVICE             = UUID.from_16_bits(0x1809, 'Health Thermometer')
64GATT_DEVICE_INFORMATION_SERVICE             = UUID.from_16_bits(0x180A, 'Device Information')
65GATT_HEART_RATE_SERVICE                     = UUID.from_16_bits(0x180D, 'Heart Rate')
66GATT_PHONE_ALERT_STATUS_SERVICE             = UUID.from_16_bits(0x180E, 'Phone Alert Status')
67GATT_BATTERY_SERVICE                        = UUID.from_16_bits(0x180F, 'Battery')
68GATT_BLOOD_PRESSURE_SERVICE                 = UUID.from_16_bits(0x1810, 'Blood Pressure')
69GATT_ALERT_NOTIFICATION_SERVICE             = UUID.from_16_bits(0x1811, 'Alert Notification')
70GATT_HUMAN_INTERFACE_DEVICE_SERVICE         = UUID.from_16_bits(0x1812, 'Human Interface Device')
71GATT_SCAN_PARAMETERS_SERVICE                = UUID.from_16_bits(0x1813, 'Scan Parameters')
72GATT_RUNNING_SPEED_AND_CADENCE_SERVICE      = UUID.from_16_bits(0x1814, 'Running Speed and Cadence')
73GATT_AUTOMATION_IO_SERVICE                  = UUID.from_16_bits(0x1815, 'Automation IO')
74GATT_CYCLING_SPEED_AND_CADENCE_SERVICE      = UUID.from_16_bits(0x1816, 'Cycling Speed and Cadence')
75GATT_CYCLING_POWER_SERVICE                  = UUID.from_16_bits(0x1818, 'Cycling Power')
76GATT_LOCATION_AND_NAVIGATION_SERVICE        = UUID.from_16_bits(0x1819, 'Location and Navigation')
77GATT_ENVIRONMENTAL_SENSING_SERVICE          = UUID.from_16_bits(0x181A, 'Environmental Sensing')
78GATT_BODY_COMPOSITION_SERVICE               = UUID.from_16_bits(0x181B, 'Body Composition')
79GATT_USER_DATA_SERVICE                      = UUID.from_16_bits(0x181C, 'User Data')
80GATT_WEIGHT_SCALE_SERVICE                   = UUID.from_16_bits(0x181D, 'Weight Scale')
81GATT_BOND_MANAGEMENT_SERVICE                = UUID.from_16_bits(0x181E, 'Bond Management')
82GATT_CONTINUOUS_GLUCOSE_MONITORING_SERVICE  = UUID.from_16_bits(0x181F, 'Continuous Glucose Monitoring')
83GATT_INTERNET_PROTOCOL_SUPPORT_SERVICE      = UUID.from_16_bits(0x1820, 'Internet Protocol Support')
84GATT_INDOOR_POSITIONING_SERVICE             = UUID.from_16_bits(0x1821, 'Indoor Positioning')
85GATT_PULSE_OXIMETER_SERVICE                 = UUID.from_16_bits(0x1822, 'Pulse Oximeter')
86GATT_HTTP_PROXY_SERVICE                     = UUID.from_16_bits(0x1823, 'HTTP Proxy')
87GATT_TRANSPORT_DISCOVERY_SERVICE            = UUID.from_16_bits(0x1824, 'Transport Discovery')
88GATT_OBJECT_TRANSFER_SERVICE                = UUID.from_16_bits(0x1825, 'Object Transfer')
89GATT_FITNESS_MACHINE_SERVICE                = UUID.from_16_bits(0x1826, 'Fitness Machine')
90GATT_MESH_PROVISIONING_SERVICE              = UUID.from_16_bits(0x1827, 'Mesh Provisioning')
91GATT_MESH_PROXY_SERVICE                     = UUID.from_16_bits(0x1828, 'Mesh Proxy')
92GATT_RECONNECTION_CONFIGURATION_SERVICE     = UUID.from_16_bits(0x1829, 'Reconnection Configuration')
93GATT_INSULIN_DELIVERY_SERVICE               = UUID.from_16_bits(0x183A, 'Insulin Delivery')
94GATT_BINARY_SENSOR_SERVICE                  = UUID.from_16_bits(0x183B, 'Binary Sensor')
95GATT_EMERGENCY_CONFIGURATION_SERVICE        = UUID.from_16_bits(0x183C, 'Emergency Configuration')
96GATT_PHYSICAL_ACTIVITY_MONITOR_SERVICE      = UUID.from_16_bits(0x183E, 'Physical Activity Monitor')
97GATT_AUDIO_INPUT_CONTROL_SERVICE            = UUID.from_16_bits(0x1843, 'Audio Input Control')
98GATT_VOLUME_CONTROL_SERVICE                 = UUID.from_16_bits(0x1844, 'Volume Control')
99GATT_VOLUME_OFFSET_CONTROL_SERVICE          = UUID.from_16_bits(0x1845, 'Volume Offset Control')
100GATT_COORDINATED_SET_IDENTIFICATION_SERVICE = UUID.from_16_bits(0x1846, 'Coordinated Set Identification Service')
101GATT_DEVICE_TIME_SERVICE                    = UUID.from_16_bits(0x1847, 'Device Time')
102GATT_MEDIA_CONTROL_SERVICE                  = UUID.from_16_bits(0x1848, 'Media Control Service')
103GATT_GENERIC_MEDIA_CONTROL_SERVICE          = UUID.from_16_bits(0x1849, 'Generic Media Control Service')
104GATT_CONSTANT_TONE_EXTENSION_SERVICE        = UUID.from_16_bits(0x184A, 'Constant Tone Extension')
105GATT_TELEPHONE_BEARER_SERVICE               = UUID.from_16_bits(0x184B, 'Telephone Bearer Service')
106GATT_GENERIC_TELEPHONE_BEARER_SERVICE       = UUID.from_16_bits(0x184C, 'Generic Telephone Bearer Service')
107GATT_MICROPHONE_CONTROL_SERVICE             = UUID.from_16_bits(0x184D, 'Microphone Control')
108
109# Types
110GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE   = UUID.from_16_bits(0x2800, 'Primary Service')
111GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE = UUID.from_16_bits(0x2801, 'Secondary Service')
112GATT_INCLUDE_ATTRIBUTE_TYPE           = UUID.from_16_bits(0x2802, 'Include')
113GATT_CHARACTERISTIC_ATTRIBUTE_TYPE    = UUID.from_16_bits(0x2803, 'Characteristic')
114
115# Descriptors
116GATT_CHARACTERISTIC_EXTENDED_PROPERTIES_DESCRIPTOR   = UUID.from_16_bits(0x2900, 'Characteristic Extended Properties')
117GATT_CHARACTERISTIC_USER_DESCRIPTION_DESCRIPTOR      = UUID.from_16_bits(0x2901, 'Characteristic User Description')
118GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR  = UUID.from_16_bits(0x2902, 'Client Characteristic Configuration')
119GATT_SERVER_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR  = UUID.from_16_bits(0x2903, 'Server Characteristic Configuration')
120GATT_CHARACTERISTIC_PRESENTATION_FORMAT_DESCRIPTOR   = UUID.from_16_bits(0x2904, 'Characteristic Format')
121GATT_CHARACTERISTIC_AGGREGATE_FORMAT_DESCRIPTOR      = UUID.from_16_bits(0x2905, 'Characteristic Aggregate Format')
122GATT_VALID_RANGE_DESCRIPTOR                          = UUID.from_16_bits(0x2906, 'Valid Range')
123GATT_EXTERNAL_REPORT_DESCRIPTOR                      = UUID.from_16_bits(0x2907, 'External Report')
124GATT_REPORT_REFERENCE_DESCRIPTOR                     = UUID.from_16_bits(0x2908, 'Report Reference')
125GATT_NUMBER_OF_DIGITALS_DESCRIPTOR                   = UUID.from_16_bits(0x2909, 'Number of Digitals')
126GATT_VALUE_TRIGGER_SETTING_DESCRIPTOR                = UUID.from_16_bits(0x290A, 'Value Trigger Setting')
127GATT_ENVIRONMENTAL_SENSING_CONFIGURATION_DESCRIPTOR  = UUID.from_16_bits(0x290B, 'Environmental Sensing Configuration')
128GATT_ENVIRONMENTAL_SENSING_MEASUREMENT_DESCRIPTOR    = UUID.from_16_bits(0x290C, 'Environmental Sensing Measurement')
129GATT_ENVIRONMENTAL_SENSING_TRIGGER_DESCRIPTOR        = UUID.from_16_bits(0x290D, 'Environmental Sensing Trigger Setting')
130GATT_TIME_TRIGGER_DESCRIPTOR                         = UUID.from_16_bits(0x290E, 'Time Trigger Setting')
131GATT_COMPLETE_BR_EDR_TRANSPORT_BLOCK_DATA_DESCRIPTOR = UUID.from_16_bits(0x290F, 'Complete BR-EDR Transport Block Data')
132
133# Device Information Service
134GATT_SYSTEM_ID_CHARACTERISTIC                          = UUID.from_16_bits(0x2A23, 'System ID')
135GATT_MODEL_NUMBER_STRING_CHARACTERISTIC                = UUID.from_16_bits(0x2A24, 'Model Number String')
136GATT_SERIAL_NUMBER_STRING_CHARACTERISTIC               = UUID.from_16_bits(0x2A25, 'Serial Number String')
137GATT_FIRMWARE_REVISION_STRING_CHARACTERISTIC           = UUID.from_16_bits(0x2A26, 'Firmware Revision String')
138GATT_HARDWARE_REVISION_STRING_CHARACTERISTIC           = UUID.from_16_bits(0x2A27, 'Hardware Revision String')
139GATT_SOFTWARE_REVISION_STRING_CHARACTERISTIC           = UUID.from_16_bits(0x2A28, 'Software Revision String')
140GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC           = UUID.from_16_bits(0x2A29, 'Manufacturer Name String')
141GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC = UUID.from_16_bits(0x2A2A, 'IEEE 11073-20601 Regulatory Certification Data List')
142GATT_PNP_ID_CHARACTERISTIC                             = UUID.from_16_bits(0x2A50, 'PnP ID')
143
144# Human Interface Device Service
145GATT_HID_INFORMATION_CHARACTERISTIC   = UUID.from_16_bits(0x2A4A, 'HID Information')
146GATT_REPORT_MAP_CHARACTERISTIC        = UUID.from_16_bits(0x2A4B, 'Report Map')
147GATT_HID_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2A4C, 'HID Control Point')
148GATT_REPORT_CHARACTERISTIC            = UUID.from_16_bits(0x2A4D, 'Report')
149GATT_PROTOCOL_MODE_CHARACTERISTIC     = UUID.from_16_bits(0x2A4E, 'Protocol Mode')
150
151# Heart Rate Service
152GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC   = UUID.from_16_bits(0x2A37, 'Heart Rate Measurement')
153GATT_BODY_SENSOR_LOCATION_CHARACTERISTIC     = UUID.from_16_bits(0x2A38, 'Body Sensor Location')
154GATT_HEART_RATE_CONTROL_POINT_CHARACTERISTIC = UUID.from_16_bits(0x2A39, 'Heart Rate Control Point')
155
156# Battery Service
157GATT_BATTERY_LEVEL_CHARACTERISTIC = UUID.from_16_bits(0x2A19, 'Battery Level')
158
159# ASHA Service
160GATT_ASHA_SERVICE                             = UUID.from_16_bits(0xFDF0, 'Audio Streaming for Hearing Aid')
161GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC = UUID('6333651e-c481-4a3e-9169-7c902aad37bb', 'ReadOnlyProperties')
162GATT_ASHA_AUDIO_CONTROL_POINT_CHARACTERISTIC  = UUID('f0d4de7e-4a88-476c-9d9f-1937b0996cc0', 'AudioControlPoint')
163GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC         = UUID('38663f1a-e711-4cac-b641-326b56404837', 'AudioStatus')
164GATT_ASHA_VOLUME_CHARACTERISTIC               = UUID('00e4ca9e-ab14-41e4-8823-f9e70c7e91df', 'Volume')
165GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC           = UUID('2d410339-82b6-42aa-b34e-e2e01df8cc1a', 'LE_PSM_OUT')
166
167# Misc
168GATT_DEVICE_NAME_CHARACTERISTIC                                = UUID.from_16_bits(0x2A00, 'Device Name')
169GATT_APPEARANCE_CHARACTERISTIC                                 = UUID.from_16_bits(0x2A01, 'Appearance')
170GATT_PERIPHERAL_PRIVACY_FLAG_CHARACTERISTIC                    = UUID.from_16_bits(0x2A02, 'Peripheral Privacy Flag')
171GATT_RECONNECTION_ADDRESS_CHARACTERISTIC                       = UUID.from_16_bits(0x2A03, 'Reconnection Address')
172GATT_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS_CHARACTERISTIC = UUID.from_16_bits(0x2A04, 'Peripheral Preferred Connection Parameters')
173GATT_SERVICE_CHANGED_CHARACTERISTIC                            = UUID.from_16_bits(0x2A05, 'Service Changed')
174GATT_ALERT_LEVEL_CHARACTERISTIC                                = UUID.from_16_bits(0x2A06, 'Alert Level')
175GATT_TX_POWER_LEVEL_CHARACTERISTIC                             = UUID.from_16_bits(0x2A07, 'Tx Power Level')
176GATT_BOOT_KEYBOARD_INPUT_REPORT_CHARACTERISTIC                 = UUID.from_16_bits(0x2A22, 'Boot Keyboard Input Report')
177GATT_CURRENT_TIME_CHARACTERISTIC                               = UUID.from_16_bits(0x2A2B, 'Current Time')
178GATT_BOOT_KEYBOARD_OUTPUT_REPORT_CHARACTERISTIC                = UUID.from_16_bits(0x2A32, 'Boot Keyboard Output Report')
179GATT_CENTRAL_ADDRESS_RESOLUTION__CHARACTERISTIC                = UUID.from_16_bits(0x2AA6, 'Central Address Resolution')
180
181# fmt: on
182# pylint: enable=line-too-long
183
184
185# -----------------------------------------------------------------------------
186# Utils
187# -----------------------------------------------------------------------------
188
189
190def show_services(services):
191    for service in services:
192        print(color(str(service), 'cyan'))
193
194        for characteristic in service.characteristics:
195            print(color('  ' + str(characteristic), 'magenta'))
196
197            for descriptor in characteristic.descriptors:
198                print(color('    ' + str(descriptor), 'green'))
199
200
201# -----------------------------------------------------------------------------
202class Service(Attribute):
203    '''
204    See Vol 3, Part G - 3.1 SERVICE DEFINITION
205    '''
206
207    uuid: UUID
208
209    def __init__(self, uuid, characteristics: list[Characteristic], primary=True):
210        # Convert the uuid to a UUID object if it isn't already
211        if isinstance(uuid, str):
212            uuid = UUID(uuid)
213
214        super().__init__(
215            GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE
216            if primary
217            else GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
218            Attribute.READABLE,
219            uuid.to_pdu_bytes(),
220        )
221        self.uuid = uuid
222        # self.included_services = []
223        self.characteristics = characteristics[:]
224        self.primary = primary
225
226    def get_advertising_data(self) -> Optional[bytes]:
227        """
228        Get Service specific advertising data
229        Defined by each Service, default value is empty
230        :return Service data for advertising
231        """
232        return None
233
234    def __str__(self):
235        return (
236            f'Service(handle=0x{self.handle:04X}, '
237            f'end=0x{self.end_group_handle:04X}, '
238            f'uuid={self.uuid})'
239            f'{"" if self.primary else "*"}'
240        )
241
242
243# -----------------------------------------------------------------------------
244class TemplateService(Service):
245    '''
246    Convenience abstract class that can be used by profile-specific subclasses that want
247    to expose their UUID as a class property
248    '''
249
250    UUID = None
251
252    def __init__(self, characteristics, primary=True):
253        super().__init__(self.UUID, characteristics, primary)
254
255
256# -----------------------------------------------------------------------------
257class Characteristic(Attribute):
258    '''
259    See Vol 3, Part G - 3.3 CHARACTERISTIC DEFINITION
260    '''
261
262    # Property flags
263    BROADCAST = 0x01
264    READ = 0x02
265    WRITE_WITHOUT_RESPONSE = 0x04
266    WRITE = 0x08
267    NOTIFY = 0x10
268    INDICATE = 0x20
269    AUTHENTICATED_SIGNED_WRITES = 0x40
270    EXTENDED_PROPERTIES = 0x80
271
272    PROPERTY_NAMES = {
273        BROADCAST: 'BROADCAST',
274        READ: 'READ',
275        WRITE_WITHOUT_RESPONSE: 'WRITE_WITHOUT_RESPONSE',
276        WRITE: 'WRITE',
277        NOTIFY: 'NOTIFY',
278        INDICATE: 'INDICATE',
279        AUTHENTICATED_SIGNED_WRITES: 'AUTHENTICATED_SIGNED_WRITES',
280        EXTENDED_PROPERTIES: 'EXTENDED_PROPERTIES',
281    }
282
283    @staticmethod
284    def property_name(property_int):
285        return Characteristic.PROPERTY_NAMES.get(property_int, '')
286
287    @staticmethod
288    def properties_as_string(properties):
289        return ','.join(
290            [
291                Characteristic.property_name(p)
292                for p in Characteristic.PROPERTY_NAMES
293                if properties & p
294            ]
295        )
296
297    @staticmethod
298    def string_to_properties(properties_str: str):
299        return functools.reduce(
300            lambda x, y: x | get_dict_key_by_value(Characteristic.PROPERTY_NAMES, y),
301            properties_str.split(","),
302            0,
303        )
304
305    def __init__(
306        self,
307        uuid,
308        properties,
309        permissions,
310        value=b'',
311        descriptors: Sequence[Descriptor] = (),
312    ):
313        super().__init__(uuid, permissions, value)
314        self.uuid = self.type
315        if isinstance(properties, str):
316            self.properties = Characteristic.string_to_properties(properties)
317        else:
318            self.properties = properties
319        self.descriptors = descriptors
320
321    def get_descriptor(self, descriptor_type):
322        for descriptor in self.descriptors:
323            if descriptor.type == descriptor_type:
324                return descriptor
325
326        return None
327
328    def __str__(self):
329        return (
330            f'Characteristic(handle=0x{self.handle:04X}, '
331            f'end=0x{self.end_group_handle:04X}, '
332            f'uuid={self.uuid}, '
333            f'properties={Characteristic.properties_as_string(self.properties)})'
334        )
335
336
337# -----------------------------------------------------------------------------
338class CharacteristicDeclaration(Attribute):
339    '''
340    See Vol 3, Part G - 3.3.1 CHARACTERISTIC DECLARATION
341    '''
342
343    def __init__(self, characteristic, value_handle):
344        declaration_bytes = (
345            struct.pack('<BH', characteristic.properties, value_handle)
346            + characteristic.uuid.to_pdu_bytes()
347        )
348        super().__init__(
349            GATT_CHARACTERISTIC_ATTRIBUTE_TYPE, Attribute.READABLE, declaration_bytes
350        )
351        self.value_handle = value_handle
352        self.characteristic = characteristic
353
354    def __str__(self):
355        return (
356            f'CharacteristicDeclaration(handle=0x{self.handle:04X}, '
357            f'value_handle=0x{self.value_handle:04X}, '
358            f'uuid={self.characteristic.uuid}, properties='
359            f'{Characteristic.properties_as_string(self.characteristic.properties)})'
360        )
361
362
363# -----------------------------------------------------------------------------
364class CharacteristicValue:
365    '''
366    Characteristic value where reading and/or writing is delegated to functions
367    passed as arguments to the constructor.
368    '''
369
370    def __init__(self, read=None, write=None):
371        self._read = read
372        self._write = write
373
374    def read(self, connection):
375        return self._read(connection) if self._read else b''
376
377    def write(self, connection, value):
378        if self._write:
379            self._write(connection, value)
380
381
382# -----------------------------------------------------------------------------
383class CharacteristicAdapter:
384    '''
385    An adapter that can adapt any object with `read_value` and `write_value`
386    methods (like Characteristic and CharacteristicProxy objects) by wrapping
387    those methods with ones that return/accept encoded/decoded values.
388    Objects with async methods are considered proxies, so the adaptation is one
389    where the return value of `read_value` is decoded and the value passed to
390    `write_value` is encoded. Other objects are considered local characteristics
391    so the adaptation is one where the return value of `read_value` is encoded
392    and the value passed to `write_value` is decoded.
393    If the characteristic has a `subscribe` method, it is wrapped with one where
394    the values are decoded before being passed to the subscriber.
395    '''
396
397    def __init__(self, characteristic):
398        self.wrapped_characteristic = characteristic
399        self.subscribers = {}  # Map from subscriber to proxy subscriber
400
401        if asyncio.iscoroutinefunction(
402            characteristic.read_value
403        ) and asyncio.iscoroutinefunction(characteristic.write_value):
404            self.read_value = self.read_decoded_value
405            self.write_value = self.write_decoded_value
406        else:
407            self.read_value = self.read_encoded_value
408            self.write_value = self.write_encoded_value
409
410        if hasattr(self.wrapped_characteristic, 'subscribe'):
411            self.subscribe = self.wrapped_subscribe
412
413        if hasattr(self.wrapped_characteristic, 'unsubscribe'):
414            self.unsubscribe = self.wrapped_unsubscribe
415
416    def __getattr__(self, name):
417        return getattr(self.wrapped_characteristic, name)
418
419    def __setattr__(self, name, value):
420        if name in (
421            'wrapped_characteristic',
422            'subscribers',
423            'read_value',
424            'write_value',
425            'subscribe',
426            'unsubscribe',
427        ):
428            super().__setattr__(name, value)
429        else:
430            setattr(self.wrapped_characteristic, name, value)
431
432    def read_encoded_value(self, connection):
433        return self.encode_value(self.wrapped_characteristic.read_value(connection))
434
435    def write_encoded_value(self, connection, value):
436        return self.wrapped_characteristic.write_value(
437            connection, self.decode_value(value)
438        )
439
440    async def read_decoded_value(self):
441        return self.decode_value(await self.wrapped_characteristic.read_value())
442
443    async def write_decoded_value(self, value, with_response=False):
444        return await self.wrapped_characteristic.write_value(
445            self.encode_value(value), with_response
446        )
447
448    def encode_value(self, value):
449        return value
450
451    def decode_value(self, value):
452        return value
453
454    def wrapped_subscribe(self, subscriber=None):
455        if subscriber is not None:
456            if subscriber in self.subscribers:
457                # We already have a proxy subscriber
458                subscriber = self.subscribers[subscriber]
459            else:
460                # Create and register a proxy that will decode the value
461                original_subscriber = subscriber
462
463                def on_change(value):
464                    original_subscriber(self.decode_value(value))
465
466                self.subscribers[subscriber] = on_change
467                subscriber = on_change
468
469        return self.wrapped_characteristic.subscribe(subscriber)
470
471    def wrapped_unsubscribe(self, subscriber=None):
472        if subscriber in self.subscribers:
473            subscriber = self.subscribers.pop(subscriber)
474
475        return self.wrapped_characteristic.unsubscribe(subscriber)
476
477    def __str__(self):
478        wrapped = str(self.wrapped_characteristic)
479        return f'{self.__class__.__name__}({wrapped})'
480
481
482# -----------------------------------------------------------------------------
483class DelegatedCharacteristicAdapter(CharacteristicAdapter):
484    '''
485    Adapter that converts bytes values using an encode and a decode function.
486    '''
487
488    def __init__(self, characteristic, encode=None, decode=None):
489        super().__init__(characteristic)
490        self.encode = encode
491        self.decode = decode
492
493    def encode_value(self, value):
494        return self.encode(value) if self.encode else value
495
496    def decode_value(self, value):
497        return self.decode(value) if self.decode else value
498
499
500# -----------------------------------------------------------------------------
501class PackedCharacteristicAdapter(CharacteristicAdapter):
502    '''
503    Adapter that packs/unpacks characteristic values according to a standard
504    Python `struct` format.
505    For formats with a single value, the adapted `read_value` and `write_value`
506    methods return/accept single values. For formats with multiple values,
507    they return/accept a tuple with the same number of elements as is required for
508    the format.
509    '''
510
511    def __init__(self, characteristic, pack_format):
512        super().__init__(characteristic)
513        self.struct = struct.Struct(pack_format)
514
515    def pack(self, *values):
516        return self.struct.pack(*values)
517
518    def unpack(self, buffer):
519        return self.struct.unpack(buffer)
520
521    def encode_value(self, value):
522        return self.pack(*value if isinstance(value, tuple) else (value,))
523
524    def decode_value(self, value):
525        unpacked = self.unpack(value)
526        return unpacked[0] if len(unpacked) == 1 else unpacked
527
528
529# -----------------------------------------------------------------------------
530class MappedCharacteristicAdapter(PackedCharacteristicAdapter):
531    '''
532    Adapter that packs/unpacks characteristic values according to a standard
533    Python `struct` format.
534    The adapted `read_value` and `write_value` methods return/accept aa dictionary which
535    is packed/unpacked according to format, with the arguments extracted from the
536    dictionary by key, in the same order as they occur in the `keys` parameter.
537    '''
538
539    def __init__(self, characteristic, pack_format, keys):
540        super().__init__(characteristic, pack_format)
541        self.keys = keys
542
543    # pylint: disable=arguments-differ
544    def pack(self, values):
545        return super().pack(*(values[key] for key in self.keys))
546
547    def unpack(self, buffer):
548        return dict(zip(self.keys, super().unpack(buffer)))
549
550
551# -----------------------------------------------------------------------------
552class UTF8CharacteristicAdapter(CharacteristicAdapter):
553    '''
554    Adapter that converts strings to/from bytes using UTF-8 encoding
555    '''
556
557    def encode_value(self, value):
558        return value.encode('utf-8')
559
560    def decode_value(self, value):
561        return value.decode('utf-8')
562
563
564# -----------------------------------------------------------------------------
565class Descriptor(Attribute):
566    '''
567    See Vol 3, Part G - 3.3.3 Characteristic Descriptor Declarations
568    '''
569
570    def __str__(self):
571        return (
572            f'Descriptor(handle=0x{self.handle:04X}, '
573            f'type={self.type}, '
574            f'value={self.read_value(None).hex()})'
575        )
576
577
578class ClientCharacteristicConfigurationBits(enum.IntFlag):
579    '''
580    See Vol 3, Part G - 3.3.3.3 - Table 3.11 Client Characteristic Configuration bit
581    field definition
582    '''
583
584    DEFAULT = 0x0000
585    NOTIFICATION = 0x0001
586    INDICATION = 0x0002
587