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