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# ----------------------------------------------------------------------------- 17# Imports 18# ----------------------------------------------------------------------------- 19import struct 20from typing import Optional, Tuple 21 22from ..gatt_client import ProfileServiceProxy 23from ..gatt import ( 24 GATT_DEVICE_INFORMATION_SERVICE, 25 GATT_FIRMWARE_REVISION_STRING_CHARACTERISTIC, 26 GATT_HARDWARE_REVISION_STRING_CHARACTERISTIC, 27 GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC, 28 GATT_MODEL_NUMBER_STRING_CHARACTERISTIC, 29 GATT_SERIAL_NUMBER_STRING_CHARACTERISTIC, 30 GATT_SOFTWARE_REVISION_STRING_CHARACTERISTIC, 31 GATT_SYSTEM_ID_CHARACTERISTIC, 32 GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC, 33 TemplateService, 34 Characteristic, 35 DelegatedCharacteristicAdapter, 36 UTF8CharacteristicAdapter, 37) 38 39 40# ----------------------------------------------------------------------------- 41class DeviceInformationService(TemplateService): 42 UUID = GATT_DEVICE_INFORMATION_SERVICE 43 44 @staticmethod 45 def pack_system_id(oui, manufacturer_id): 46 return struct.pack('<Q', oui << 40 | manufacturer_id) 47 48 @staticmethod 49 def unpack_system_id(buffer): 50 system_id = struct.unpack('<Q', buffer)[0] 51 return (system_id >> 40, system_id & 0xFFFFFFFFFF) 52 53 def __init__( 54 self, 55 manufacturer_name: Optional[str] = None, 56 model_number: Optional[str] = None, 57 serial_number: Optional[str] = None, 58 hardware_revision: Optional[str] = None, 59 firmware_revision: Optional[str] = None, 60 software_revision: Optional[str] = None, 61 system_id: Optional[Tuple[int, int]] = None, # (OUI, Manufacturer ID) 62 ieee_regulatory_certification_data_list: Optional[bytes] = None 63 # TODO: pnp_id 64 ): 65 characteristics = [ 66 Characteristic(uuid, Characteristic.READ, Characteristic.READABLE, field) 67 for (field, uuid) in ( 68 (manufacturer_name, GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC), 69 (model_number, GATT_MODEL_NUMBER_STRING_CHARACTERISTIC), 70 (serial_number, GATT_SERIAL_NUMBER_STRING_CHARACTERISTIC), 71 (hardware_revision, GATT_HARDWARE_REVISION_STRING_CHARACTERISTIC), 72 (firmware_revision, GATT_FIRMWARE_REVISION_STRING_CHARACTERISTIC), 73 (software_revision, GATT_SOFTWARE_REVISION_STRING_CHARACTERISTIC), 74 ) 75 if field is not None 76 ] 77 78 if system_id is not None: 79 characteristics.append( 80 Characteristic( 81 GATT_SYSTEM_ID_CHARACTERISTIC, 82 Characteristic.READ, 83 Characteristic.READABLE, 84 self.pack_system_id(*system_id), 85 ) 86 ) 87 88 if ieee_regulatory_certification_data_list is not None: 89 characteristics.append( 90 Characteristic( 91 GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC, 92 Characteristic.READ, 93 Characteristic.READABLE, 94 ieee_regulatory_certification_data_list, 95 ) 96 ) 97 98 super().__init__(characteristics) 99 100 101# ----------------------------------------------------------------------------- 102class DeviceInformationServiceProxy(ProfileServiceProxy): 103 SERVICE_CLASS = DeviceInformationService 104 105 def __init__(self, service_proxy): 106 self.service_proxy = service_proxy 107 108 for (field, uuid) in ( 109 ('manufacturer_name', GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC), 110 ('model_number', GATT_MODEL_NUMBER_STRING_CHARACTERISTIC), 111 ('serial_number', GATT_SERIAL_NUMBER_STRING_CHARACTERISTIC), 112 ('hardware_revision', GATT_HARDWARE_REVISION_STRING_CHARACTERISTIC), 113 ('firmware_revision', GATT_FIRMWARE_REVISION_STRING_CHARACTERISTIC), 114 ('software_revision', GATT_SOFTWARE_REVISION_STRING_CHARACTERISTIC), 115 ): 116 if characteristics := service_proxy.get_characteristics_by_uuid(uuid): 117 characteristic = UTF8CharacteristicAdapter(characteristics[0]) 118 else: 119 characteristic = None 120 self.__setattr__(field, characteristic) 121 122 if characteristics := service_proxy.get_characteristics_by_uuid( 123 GATT_SYSTEM_ID_CHARACTERISTIC 124 ): 125 self.system_id = DelegatedCharacteristicAdapter( 126 characteristics[0], 127 encode=lambda v: DeviceInformationService.pack_system_id(*v), 128 decode=DeviceInformationService.unpack_system_id, 129 ) 130 else: 131 self.system_id = None 132 133 if characteristics := service_proxy.get_characteristics_by_uuid( 134 GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC 135 ): 136 self.ieee_regulatory_certification_data_list = characteristics[0] 137 else: 138 self.ieee_regulatory_certification_data_list = None 139