/ adafruit_fingerprint.py
adafruit_fingerprint.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2017 ladyada 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 `adafruit_fingerprint` 24 ==================================================== 25 26 This library will let you use an Adafruit Fingerprint sensor on any UART to get, store, 27 retreive and query fingerprints! Great for adding bio-sensing security to your next build. 28 29 * Author(s): ladyada 30 31 Implementation Notes 32 -------------------- 33 34 **Hardware:** 35 36 * `Fingerprint sensor <https://www.adafruit.com/product/751>`_ (Product ID: 751) 37 * `Panel Mount Fingerprint sensor <https://www.adafruit.com/product/4651>`_ (Product ID: 4651) 38 39 **Software and Dependencies:** 40 41 * Adafruit CircuitPython firmware (2.2.0+) for the ESP8622 and M0-based boards: 42 https://github.com/adafruit/circuitpython/releases 43 """ 44 45 from micropython import const 46 47 try: 48 import struct 49 except ImportError: 50 import ustruct as struct 51 52 __version__ = "0.0.0-auto.0" 53 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Fingerprint.git" 54 55 _STARTCODE = const(0xEF01) 56 _COMMANDPACKET = const(0x1) 57 _DATAPACKET = const(0x2) 58 _ACKPACKET = const(0x7) 59 _ENDDATAPACKET = const(0x8) 60 61 _GETIMAGE = const(0x01) 62 _IMAGE2TZ = const(0x02) 63 _FINGERPRINTSEARCH = const(0x04) 64 _REGMODEL = const(0x05) 65 _STORE = const(0x06) 66 _LOAD = const(0x07) 67 _UPLOAD = const(0x08) 68 _DOWNLOAD = const(0x09) 69 _UPLOADIMAGE = const(0x0A) 70 _DOWNLOADIMAGE = const(0x0B) 71 _DELETE = const(0x0C) 72 _EMPTY = const(0x0D) 73 _READSYSPARA = const(0x0F) 74 _HISPEEDSEARCH = const(0x1B) 75 _VERIFYPASSWORD = const(0x13) 76 _TEMPLATECOUNT = const(0x1D) 77 _TEMPLATEREAD = const(0x1F) 78 _GETECHO = const(0x53) 79 80 # Packet error code 81 OK = const(0x0) 82 PACKETRECIEVEERR = const(0x01) 83 NOFINGER = const(0x02) 84 IMAGEFAIL = const(0x03) 85 IMAGEMESS = const(0x06) 86 FEATUREFAIL = const(0x07) 87 NOMATCH = const(0x08) 88 NOTFOUND = const(0x09) 89 ENROLLMISMATCH = const(0x0A) 90 BADLOCATION = const(0x0B) 91 DBRANGEFAIL = const(0x0C) 92 UPLOADFEATUREFAIL = const(0x0D) 93 PACKETRESPONSEFAIL = const(0x0E) 94 UPLOADFAIL = const(0x0F) 95 DELETEFAIL = const(0x10) 96 DBCLEARFAIL = const(0x11) 97 PASSFAIL = const(0x13) 98 INVALIDIMAGE = const(0x15) 99 FLASHERR = const(0x18) 100 INVALIDREG = const(0x1A) 101 ADDRCODE = const(0x20) 102 PASSVERIFY = const(0x21) 103 MODULEOK = const(0x55) 104 105 # pylint: disable=too-many-instance-attributes 106 class Adafruit_Fingerprint: 107 """UART based fingerprint sensor.""" 108 109 _uart = None 110 111 password = None 112 address = [0xFF, 0xFF, 0xFF, 0xFF] 113 finger_id = None 114 confidence = None 115 templates = [] 116 template_count = None 117 library_size = None 118 security_level = None 119 device_address = None 120 data_packet_size = None 121 baudrate = None 122 system_id = None 123 status_register = None 124 125 def __init__(self, uart, passwd=(0, 0, 0, 0)): 126 # Create object with UART for interface, and default 32-bit password 127 self.password = passwd 128 self._uart = uart 129 if self.verify_password() != OK: 130 raise RuntimeError("Failed to find sensor, check wiring!") 131 132 def check_module(self): 133 """Checks the state of the fingerprint scanner module. 134 Returns OK or error.""" 135 self._send_packet([_GETECHO]) 136 if self._get_packet(12)[0] != MODULEOK: 137 raise RuntimeError("Something is wrong with the sensor.") 138 return True 139 140 def verify_password(self): 141 """Checks if the password/connection is correct, returns True/False""" 142 self._send_packet([_VERIFYPASSWORD] + list(self.password)) 143 return self._get_packet(12)[0] 144 145 def count_templates(self): 146 """Requests the sensor to count the number of templates and stores it 147 in ``self.template_count``. Returns the packet error code or OK success""" 148 self._send_packet([_TEMPLATECOUNT]) 149 r = self._get_packet(14) 150 self.template_count = struct.unpack(">H", bytes(r[1:3]))[0] 151 return r[0] 152 153 def read_sysparam(self): 154 """Returns the system parameters on success via attributes.""" 155 self._send_packet([_READSYSPARA]) 156 r = self._get_packet(28) 157 if r[0] != OK: 158 raise RuntimeError("Command failed.") 159 self.status_register = struct.unpack(">H", bytes(r[1:3]))[0] 160 self.system_id = struct.unpack(">H", bytes(r[3:5]))[0] 161 self.library_size = struct.unpack(">H", bytes(r[5:7]))[0] 162 self.security_level = struct.unpack(">H", bytes(r[7:9]))[0] 163 self.device_address = bytes(r[9:13]) 164 self.data_packet_size = struct.unpack(">H", bytes(r[13:15]))[0] 165 self.baudrate = struct.unpack(">H", bytes(r[15:17]))[0] 166 return r[0] 167 168 def get_image(self): 169 """Requests the sensor to take an image and store it memory, returns 170 the packet error code or OK success""" 171 self._send_packet([_GETIMAGE]) 172 return self._get_packet(12)[0] 173 174 def image_2_tz(self, slot=1): 175 """Requests the sensor convert the image to a template, returns 176 the packet error code or OK success""" 177 self._send_packet([_IMAGE2TZ, slot]) 178 return self._get_packet(12)[0] 179 180 def create_model(self): 181 """Requests the sensor take the template data and turn it into a model 182 returns the packet error code or OK success""" 183 self._send_packet([_REGMODEL]) 184 return self._get_packet(12)[0] 185 186 def store_model(self, location, slot=1): 187 """Requests the sensor store the model into flash memory and assign 188 a location. Returns the packet error code or OK success""" 189 self._send_packet([_STORE, slot, location >> 8, location & 0xFF]) 190 return self._get_packet(12)[0] 191 192 def delete_model(self, location): 193 """Requests the sensor delete a model from flash memory given by 194 the argument location. Returns the packet error code or OK success""" 195 self._send_packet([_DELETE, location >> 8, location & 0xFF, 0x00, 0x01]) 196 return self._get_packet(12)[0] 197 198 def load_model(self, location, slot=1): 199 """Requests the sensor to load a model from the given memory location 200 to the given slot. Returns the packet error code or success""" 201 self._send_packet([_LOAD, slot, location >> 8, location & 0xFF]) 202 return self._get_packet(12)[0] 203 204 def get_fpdata(self, sensorbuffer="char", slot=1): 205 """Requests the sensor to transfer the fingerprint image or 206 template. Returns the data payload only.""" 207 if slot != 1 or slot != 2: 208 # raise error or use default value? 209 slot = 2 210 if sensorbuffer == "image": 211 self._send_packet([_UPLOADIMAGE]) 212 elif sensorbuffer == "char": 213 self._send_packet([_UPLOAD, slot]) 214 else: 215 raise RuntimeError("Uknown sensor buffer type") 216 if self._get_packet(12)[0] == 0: 217 res = self._get_data(9) 218 # print('datasize: ' + str(len(res))) 219 # print(res) 220 return res 221 222 def send_fpdata(self, data, sensorbuffer="char", slot=1): 223 """Requests the sensor to receive data, either a fingerprint image or 224 a character/template data. Data is the payload only.""" 225 if slot != 1 or slot != 2: 226 # raise error or use default value? 227 slot = 2 228 if sensorbuffer == "image": 229 self._send_packet([_DOWNLOADIMAGE]) 230 elif sensorbuffer == "char": 231 self._send_packet([_DOWNLOAD, slot]) 232 else: 233 raise RuntimeError("Uknown sensor buffer type") 234 if self._get_packet(12)[0] == 0: 235 self._send_data(data) 236 # print('datasize: ' + str(len(res))) 237 # print(res) 238 return True 239 240 def empty_library(self): 241 """Requests the sensor to delete all models from flash memory. 242 Returns the packet error code or OK success""" 243 self._send_packet([_EMPTY]) 244 return self._get_packet(12)[0] 245 246 def read_templates(self): 247 """Requests the sensor to list of all template locations in use and 248 stores them in self.templates. Returns the packet error code or 249 OK success""" 250 from math import ceil # pylint: disable=import-outside-toplevel 251 252 self.templates = [] 253 self.read_sysparam() 254 temp_r = [ 255 0x0C, 256 ] 257 for j in range(ceil(self.library_size / 256)): 258 self._send_packet([_TEMPLATEREAD, j]) 259 r = self._get_packet(44) 260 if r[0] == OK: 261 for i in range(32): 262 byte = r[i + 1] 263 for bit in range(8): 264 if byte & (1 << bit): 265 self.templates.append((i * 8) + bit + (j * 256)) 266 temp_r = r 267 else: 268 r = temp_r 269 return r[0] 270 271 def finger_fast_search(self): 272 """Asks the sensor to search for a matching fingerprint template to the 273 last model generated. Stores the location and confidence in self.finger_id 274 and self.confidence. Returns the packet error code or OK success""" 275 # high speed search of slot #1 starting at page 0x0000 and page #0x00A3 276 # self._send_packet([_HISPEEDSEARCH, 0x01, 0x00, 0x00, 0x00, 0xA3]) 277 # or page #0x03E9 to accommodate modules with up to 1000 capacity 278 # self._send_packet([_HISPEEDSEARCH, 0x01, 0x00, 0x00, 0x03, 0xE9]) 279 # or base the page on module's capacity 280 self.read_sysparam() 281 capacity = self.library_size 282 self._send_packet( 283 [_HISPEEDSEARCH, 0x01, 0x00, 0x00, capacity >> 8, capacity & 0xFF] 284 ) 285 r = self._get_packet(16) 286 self.finger_id, self.confidence = struct.unpack(">HH", bytes(r[1:5])) 287 # print(r) 288 return r[0] 289 290 def finger_search(self): 291 """Asks the sensor to search for a matching fingerprint starting at 292 slot 1. Stores the location and confidence in self.finger_id 293 and self.confidence. Returns the packet error code or OK success""" 294 self.read_sysparam() 295 capacity = self.library_size 296 self._send_packet( 297 [_FINGERPRINTSEARCH, 0x01, 0x00, 0x00, capacity >> 8, capacity & 0xFF] 298 ) 299 r = self._get_packet(16) 300 self.finger_id, self.confidence = struct.unpack(">HH", bytes(r[1:5])) 301 # print(r) 302 return r[0] 303 304 ################################################## 305 306 def _get_packet(self, expected): 307 """ Helper to parse out a packet from the UART and check structure. 308 Returns just the data payload from the packet""" 309 res = self._uart.read(expected) 310 # print("Got", res) 311 if (not res) or (len(res) != expected): 312 raise RuntimeError("Failed to read data from sensor") 313 314 # first two bytes are start code 315 start = struct.unpack(">H", res[0:2])[0] 316 317 if start != _STARTCODE: 318 raise RuntimeError("Incorrect packet data") 319 # next 4 bytes are address 320 addr = list(i for i in res[2:6]) 321 if addr != self.address: 322 raise RuntimeError("Incorrect address") 323 324 packet_type, length = struct.unpack(">BH", res[6:9]) 325 if packet_type != _ACKPACKET: 326 raise RuntimeError("Incorrect packet data") 327 328 # we should check the checksum 329 # but i don't know how 330 # not yet anyway 331 # packet_sum = struct.unpack('>H', res[9+(length-2):9+length])[0] 332 # print(packet_sum) 333 # print(packet_type + length + struct.unpack('>HHHH', res[9:9+(length-2)])) 334 335 reply = list(i for i in res[9 : 9 + (length - 2)]) 336 # print(reply) 337 return reply 338 339 def _get_data(self, expected): 340 """ Gets packet from serial and checks structure for _DATAPACKET 341 and _ENDDATAPACKET. Alternate method for getting data such 342 as fingerprint image, etc. Returns the data payload.""" 343 res = self._uart.read(expected) 344 if (not res) or (len(res) != expected): 345 raise RuntimeError("Failed to read data from sensor") 346 347 # first two bytes are start code 348 start = struct.unpack(">H", res[0:2])[0] 349 # print(start) 350 if start != _STARTCODE: 351 raise RuntimeError("Incorrect packet data") 352 # next 4 bytes are address 353 addr = list(i for i in res[2:6]) 354 # print(addr) 355 if addr != self.address: 356 raise RuntimeError("Incorrect address") 357 358 packet_type, length = struct.unpack(">BH", res[6:9]) 359 # print(str(packet_type) + ' ' + str(length)) 360 361 # todo: check checksum 362 363 if packet_type != _DATAPACKET: 364 if packet_type != _ENDDATAPACKET: 365 raise RuntimeError("Incorrect packet data") 366 367 if packet_type == _DATAPACKET: 368 res = self._uart.read(length - 2) 369 # todo: we should really inspect the headers and checksum 370 reply = list(i for i in res[0:length]) 371 self._uart.read(2) # disregard checksum but we really shouldn't 372 reply += self._get_data(9) 373 elif packet_type == _ENDDATAPACKET: 374 res = self._uart.read(length - 2) 375 # todo: we should really inspect the headers and checksum 376 reply = list(i for i in res[0:length]) 377 self._uart.read(2) # disregard checksum but we really shouldn't 378 # print(len(reply)) 379 # print(reply) 380 return reply 381 382 def _send_packet(self, data): 383 packet = [_STARTCODE >> 8, _STARTCODE & 0xFF] 384 packet = packet + self.address 385 packet.append(_COMMANDPACKET) # the packet type 386 387 length = len(data) + 2 388 packet.append(length >> 8) 389 packet.append(length & 0xFF) 390 391 packet = packet + data 392 393 checksum = sum(packet[6:]) 394 packet.append(checksum >> 8) 395 packet.append(checksum & 0xFF) 396 397 # print("Sending: ", [hex(i) for i in packet]) 398 self._uart.write(bytearray(packet)) 399 400 def _send_data(self, data): 401 print(len(data)) 402 self.read_sysparam() 403 if self.data_packet_size == 0: 404 data_length = 32 405 elif self.data_packet_size == 1: 406 data_length = 64 407 elif self.data_packet_size == 2: 408 data_length = 128 409 elif self.data_packet_size == 3: 410 data_length = 256 411 412 i = 0 413 for i in range(int(len(data) / (data_length - 2))): 414 start = i * (data_length - 2) 415 end = (i + 1) * (data_length - 2) 416 # print(start) 417 # print(end) 418 # print(i) 419 420 packet = [_STARTCODE >> 8, _STARTCODE & 0xFF] 421 packet = packet + self.address 422 packet.append(_DATAPACKET) 423 length = len(data[start:end]) + 2 424 # print(length) 425 packet.append(length >> 8) 426 packet.append(length & 0xFF) 427 checksum = _DATAPACKET + (length >> 8) + (length & 0xFF) 428 429 for j in range(len(data[start:end])): 430 packet.append(data[j]) 431 checksum += data[j] 432 433 packet.append(checksum >> 8) 434 packet.append(checksum & 0xFF) 435 436 # print("Sending: ", [hex(i) for i in packet]) 437 self._uart.write(packet) 438 # print(i) 439 440 i += 1 441 start = i * (data_length - 2) 442 end = (i + 1) * (data_length - 2) 443 # print(start) 444 # print(end) 445 # print(i) 446 447 packet = [_STARTCODE >> 8, _STARTCODE & 0xFF] 448 packet = packet + self.address 449 packet.append(_ENDDATAPACKET) 450 length = len(data[start:end]) + 2 451 # print(length) 452 packet.append(length >> 8) 453 packet.append(length & 0xFF) 454 checksum = _ENDDATAPACKET + (length >> 8) + (length & 0xFF) 455 456 for j in range(len(data[start:end])): 457 packet.append(data[j]) 458 checksum += data[j] 459 460 packet.append(checksum >> 8) 461 packet.append(checksum & 0xFF) 462 463 # print("Sending: ", [hex(i) for i in packet]) 464 self._uart.write(packet) 465 # print(i)