__init__.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2019 Scott Shawcroft for Adafruit Industries 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 """ 23 24 This module provides core BLE characteristic classes that are used within Services. 25 26 """ 27 28 import struct 29 import _bleio 30 31 from ..attributes import Attribute 32 33 __version__ = "0.0.0-auto.0" 34 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BLE.git" 35 36 37 class Characteristic: 38 """ 39 Top level Characteristic class that does basic binding. 40 41 :param UUID uuid: The uuid of the characteristic 42 :param int properties: The properties of the characteristic, 43 specified as a bitmask of these values bitwise-or'd together: 44 `BROADCAST`, `INDICATE`, `NOTIFY`, `READ`, `WRITE`, `WRITE_NO_RESPONSE`. 45 :param int read_perm: Specifies whether the characteristic can be read by a client, 46 and if so, which security mode is required. 47 Must be one of the integer values `Attribute.NO_ACCESS`, `Attribute.OPEN`, 48 `Attribute.ENCRYPT_NO_MITM`, `Attribute.ENCRYPT_WITH_MITM`, 49 `Attribute.LESC_ENCRYPT_WITH_MITM`, 50 `Attribute.SIGNED_NO_MITM`, or `Attribute.SIGNED_WITH_MITM`. 51 :param int write_perm: Specifies whether the characteristic can be written by a client, 52 and if so, which security mode is required. Values allowed are the same as ``read_perm``. 53 :param int max_length: Maximum length in bytes of the characteristic value. The maximum allowed 54 by the BLE specification is 512. On nRF, if ``fixed_length`` is ``True``, the maximum 55 is 510. The default value is 20, which is the maximum 56 number of data bytes that fit in a single BLE 4.x ATT packet. 57 :param bool fixed_length: True if the characteristic value is of fixed length. 58 :param buf initial_value: The initial value for this characteristic. If not given, will be 59 filled with zeros. 60 61 .. data:: BROADCAST 62 63 property: allowed in advertising packets 64 65 .. data:: INDICATE 66 67 property: server will indicate to the client when the value is set and wait for a response 68 69 .. data:: NOTIFY 70 71 property: server will notify the client when the value is set 72 73 .. data:: READ 74 75 property: clients may read this characteristic 76 77 .. data:: WRITE 78 79 property: clients may write this characteristic; a response will be sent back 80 81 .. data:: WRITE_NO_RESPONSE 82 83 property: clients may write this characteristic; no response will be sent back 84 """ 85 86 BROADCAST = _bleio.Characteristic.BROADCAST 87 INDICATE = _bleio.Characteristic.INDICATE 88 NOTIFY = _bleio.Characteristic.NOTIFY 89 READ = _bleio.Characteristic.READ 90 WRITE = _bleio.Characteristic.WRITE 91 WRITE_NO_RESPONSE = _bleio.Characteristic.WRITE_NO_RESPONSE 92 93 def __init__( 94 self, 95 *, 96 uuid=None, 97 properties=0, 98 read_perm=Attribute.OPEN, 99 write_perm=Attribute.OPEN, 100 max_length=None, 101 fixed_length=False, 102 initial_value=None 103 ): 104 self.field_name = None # Set by Service during basic binding 105 106 if uuid: 107 self.uuid = uuid 108 self.properties = properties 109 self.read_perm = read_perm 110 self.write_perm = write_perm 111 self.max_length = max_length 112 self.fixed_length = fixed_length 113 self.initial_value = initial_value 114 115 def _ensure_bound(self, service, initial_value=None): 116 """Binds the characteristic to the local Service or remote Characteristic object given.""" 117 if self.field_name in service.bleio_characteristics: 118 return 119 if service.remote: 120 for characteristic in service.bleio_service.characteristics: 121 if characteristic.uuid == self.uuid.bleio_uuid: 122 bleio_characteristic = characteristic 123 break 124 else: 125 raise AttributeError("Characteristic not available on remote service") 126 else: 127 bleio_characteristic = self.__bind_locally(service, initial_value) 128 129 service.bleio_characteristics[self.field_name] = bleio_characteristic 130 131 def __bind_locally(self, service, initial_value): 132 if initial_value is None: 133 initial_value = self.initial_value 134 if initial_value is None and self.max_length: 135 initial_value = bytes(self.max_length) 136 max_length = self.max_length 137 if max_length is None and initial_value is None: 138 max_length = 0 139 initial_value = b"" 140 elif max_length is None: 141 max_length = len(initial_value) 142 return _bleio.Characteristic.add_to_service( 143 service.bleio_service, 144 self.uuid.bleio_uuid, 145 initial_value=initial_value, 146 max_length=max_length, 147 fixed_length=self.fixed_length, 148 properties=self.properties, 149 read_perm=self.read_perm, 150 write_perm=self.write_perm, 151 ) 152 153 def __get__(self, service, cls=None): 154 # CircuitPython doesn't invoke descriptor protocol on obj's class, 155 # but CPython does. In the CPython case, pretend that it doesn't. 156 if service is None: 157 return self 158 self._ensure_bound(service) 159 bleio_characteristic = service.bleio_characteristics[self.field_name] 160 return bleio_characteristic.value 161 162 def __set__(self, service, value): 163 self._ensure_bound(service, value) 164 if value is None: 165 value = b"" 166 bleio_characteristic = service.bleio_characteristics[self.field_name] 167 bleio_characteristic.value = value 168 169 170 class ComplexCharacteristic: 171 """ 172 Characteristic class that does complex binding where the subclass returns a full object for 173 interacting with the characteristic data. The Characteristic itself will be shadowed once it 174 has been bound to the corresponding instance attribute. 175 """ 176 177 def __init__( 178 self, 179 *, 180 uuid=None, 181 properties=0, 182 read_perm=Attribute.OPEN, 183 write_perm=Attribute.OPEN, 184 max_length=20, 185 fixed_length=False, 186 initial_value=None 187 ): 188 self.field_name = None # Set by Service during basic binding 189 190 if uuid: 191 self.uuid = uuid 192 self.properties = properties 193 self.read_perm = read_perm 194 self.write_perm = write_perm 195 self.max_length = max_length 196 self.fixed_length = fixed_length 197 self.initial_value = initial_value 198 199 def bind(self, service): 200 """Binds the characteristic to the local Service or remote Characteristic object given.""" 201 if service.remote: 202 for characteristic in service.bleio_service.characteristics: 203 if characteristic.uuid == self.uuid.bleio_uuid: 204 return characteristic 205 raise AttributeError("Characteristic not available on remote service") 206 return _bleio.Characteristic.add_to_service( 207 service.bleio_service, 208 self.uuid.bleio_uuid, 209 initial_value=self.initial_value, 210 max_length=self.max_length, 211 properties=self.properties, 212 read_perm=self.read_perm, 213 write_perm=self.write_perm, 214 ) 215 216 def __get__(self, service, cls=None): 217 if service is None: 218 return self 219 bound_object = self.bind(service) 220 setattr(service, self.field_name, bound_object) 221 return bound_object 222 223 224 class StructCharacteristic(Characteristic): 225 """ 226 Data descriptor for a structure with a fixed format. 227 228 :param struct_format: a `struct` format string describing how to pack multiple values 229 into the characteristic bytestring 230 :param UUID uuid: The uuid of the characteristic 231 :param int properties: see `Characteristic` 232 :param int read_perm: see `Characteristic` 233 :param int write_perm: see `Characteristic` 234 :param buf initial_value: see `Characteristic` 235 """ 236 237 def __init__( 238 self, 239 struct_format, 240 *, 241 uuid=None, 242 properties=0, 243 read_perm=Attribute.OPEN, 244 write_perm=Attribute.OPEN, 245 initial_value=None 246 ): 247 self._struct_format = struct_format 248 self._expected_size = struct.calcsize(struct_format) 249 if initial_value is not None: 250 initial_value = struct.pack(self._struct_format, *initial_value) 251 super().__init__( 252 uuid=uuid, 253 initial_value=initial_value, 254 max_length=self._expected_size, 255 fixed_length=True, 256 properties=properties, 257 read_perm=read_perm, 258 write_perm=write_perm, 259 ) 260 261 def __get__(self, obj, cls=None): 262 if obj is None: 263 return self 264 raw_data = super().__get__(obj, cls) 265 if len(raw_data) < self._expected_size: 266 return None 267 return struct.unpack(self._struct_format, raw_data) 268 269 def __set__(self, obj, value): 270 encoded = struct.pack(self._struct_format, *value) 271 super().__set__(obj, encoded)