• 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# -----------------------------------------------------------------------------
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