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 bumble.gatt_client import ServiceProxy, ProfileServiceProxy, CharacteristicProxy 23from bumble.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( 67 uuid, Characteristic.Properties.READ, Characteristic.READABLE, field 68 ) 69 for (field, uuid) in ( 70 (manufacturer_name, GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC), 71 (model_number, GATT_MODEL_NUMBER_STRING_CHARACTERISTIC), 72 (serial_number, GATT_SERIAL_NUMBER_STRING_CHARACTERISTIC), 73 (hardware_revision, GATT_HARDWARE_REVISION_STRING_CHARACTERISTIC), 74 (firmware_revision, GATT_FIRMWARE_REVISION_STRING_CHARACTERISTIC), 75 (software_revision, GATT_SOFTWARE_REVISION_STRING_CHARACTERISTIC), 76 ) 77 if field is not None 78 ] 79 80 if system_id is not None: 81 characteristics.append( 82 Characteristic( 83 GATT_SYSTEM_ID_CHARACTERISTIC, 84 Characteristic.Properties.READ, 85 Characteristic.READABLE, 86 self.pack_system_id(*system_id), 87 ) 88 ) 89 90 if ieee_regulatory_certification_data_list is not None: 91 characteristics.append( 92 Characteristic( 93 GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC, 94 Characteristic.Properties.READ, 95 Characteristic.READABLE, 96 ieee_regulatory_certification_data_list, 97 ) 98 ) 99 100 super().__init__(characteristics) 101 102 103# ----------------------------------------------------------------------------- 104class DeviceInformationServiceProxy(ProfileServiceProxy): 105 SERVICE_CLASS = DeviceInformationService 106 107 manufacturer_name: Optional[UTF8CharacteristicAdapter] 108 model_number: Optional[UTF8CharacteristicAdapter] 109 serial_number: Optional[UTF8CharacteristicAdapter] 110 hardware_revision: Optional[UTF8CharacteristicAdapter] 111 firmware_revision: Optional[UTF8CharacteristicAdapter] 112 software_revision: Optional[UTF8CharacteristicAdapter] 113 system_id: Optional[DelegatedCharacteristicAdapter] 114 ieee_regulatory_certification_data_list: Optional[CharacteristicProxy] 115 116 def __init__(self, service_proxy: ServiceProxy): 117 self.service_proxy = service_proxy 118 119 for field, uuid in ( 120 ('manufacturer_name', GATT_MANUFACTURER_NAME_STRING_CHARACTERISTIC), 121 ('model_number', GATT_MODEL_NUMBER_STRING_CHARACTERISTIC), 122 ('serial_number', GATT_SERIAL_NUMBER_STRING_CHARACTERISTIC), 123 ('hardware_revision', GATT_HARDWARE_REVISION_STRING_CHARACTERISTIC), 124 ('firmware_revision', GATT_FIRMWARE_REVISION_STRING_CHARACTERISTIC), 125 ('software_revision', GATT_SOFTWARE_REVISION_STRING_CHARACTERISTIC), 126 ): 127 if characteristics := service_proxy.get_characteristics_by_uuid(uuid): 128 characteristic = UTF8CharacteristicAdapter(characteristics[0]) 129 else: 130 characteristic = None 131 self.__setattr__(field, characteristic) 132 133 if characteristics := service_proxy.get_characteristics_by_uuid( 134 GATT_SYSTEM_ID_CHARACTERISTIC 135 ): 136 self.system_id = DelegatedCharacteristicAdapter( 137 characteristics[0], 138 encode=lambda v: DeviceInformationService.pack_system_id(*v), 139 decode=DeviceInformationService.unpack_system_id, 140 ) 141 else: 142 self.system_id = None 143 144 if characteristics := service_proxy.get_characteristics_by_uuid( 145 GATT_REGULATORY_CERTIFICATION_DATA_LIST_CHARACTERISTIC 146 ): 147 self.ieee_regulatory_certification_data_list = characteristics[0] 148 else: 149 self.ieee_regulatory_certification_data_list = None 150