# The MIT License (MIT) # # Copyright (c) 2017 ladyada for Adafruit Industries # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ `adafruit_fingerprint` ==================================================== This library will let you use an Adafruit Fingerprint sensor on any UART to get, store, retreive and query fingerprints! Great for adding bio-sensing security to your next build. * Author(s): ladyada Implementation Notes -------------------- **Hardware:** * `Fingerprint sensor `_ (Product ID: 751) * `Panel Mount Fingerprint sensor `_ (Product ID: 4651) **Software and Dependencies:** * Adafruit CircuitPython firmware (2.2.0+) for the ESP8622 and M0-based boards: https://github.com/adafruit/circuitpython/releases """ from micropython import const try: import struct except ImportError: import ustruct as struct __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Fingerprint.git" _STARTCODE = const(0xEF01) _COMMANDPACKET = const(0x1) _DATAPACKET = const(0x2) _ACKPACKET = const(0x7) _ENDDATAPACKET = const(0x8) _GETIMAGE = const(0x01) _IMAGE2TZ = const(0x02) _FINGERPRINTSEARCH = const(0x04) _REGMODEL = const(0x05) _STORE = const(0x06) _LOAD = const(0x07) _UPLOAD = const(0x08) _DOWNLOAD = const(0x09) _UPLOADIMAGE = const(0x0A) _DOWNLOADIMAGE = const(0x0B) _DELETE = const(0x0C) _EMPTY = const(0x0D) _READSYSPARA = const(0x0F) _HISPEEDSEARCH = const(0x1B) _VERIFYPASSWORD = const(0x13) _TEMPLATECOUNT = const(0x1D) _TEMPLATEREAD = const(0x1F) _GETECHO = const(0x53) # Packet error code OK = const(0x0) PACKETRECIEVEERR = const(0x01) NOFINGER = const(0x02) IMAGEFAIL = const(0x03) IMAGEMESS = const(0x06) FEATUREFAIL = const(0x07) NOMATCH = const(0x08) NOTFOUND = const(0x09) ENROLLMISMATCH = const(0x0A) BADLOCATION = const(0x0B) DBRANGEFAIL = const(0x0C) UPLOADFEATUREFAIL = const(0x0D) PACKETRESPONSEFAIL = const(0x0E) UPLOADFAIL = const(0x0F) DELETEFAIL = const(0x10) DBCLEARFAIL = const(0x11) PASSFAIL = const(0x13) INVALIDIMAGE = const(0x15) FLASHERR = const(0x18) INVALIDREG = const(0x1A) ADDRCODE = const(0x20) PASSVERIFY = const(0x21) MODULEOK = const(0x55) # pylint: disable=too-many-instance-attributes class Adafruit_Fingerprint: """UART based fingerprint sensor.""" _uart = None password = None address = [0xFF, 0xFF, 0xFF, 0xFF] finger_id = None confidence = None templates = [] template_count = None library_size = None security_level = None device_address = None data_packet_size = None baudrate = None system_id = None status_register = None def __init__(self, uart, passwd=(0, 0, 0, 0)): # Create object with UART for interface, and default 32-bit password self.password = passwd self._uart = uart if self.verify_password() != OK: raise RuntimeError("Failed to find sensor, check wiring!") def check_module(self): """Checks the state of the fingerprint scanner module. Returns OK or error.""" self._send_packet([_GETECHO]) if self._get_packet(12)[0] != MODULEOK: raise RuntimeError("Something is wrong with the sensor.") return True def verify_password(self): """Checks if the password/connection is correct, returns True/False""" self._send_packet([_VERIFYPASSWORD] + list(self.password)) return self._get_packet(12)[0] def count_templates(self): """Requests the sensor to count the number of templates and stores it in ``self.template_count``. Returns the packet error code or OK success""" self._send_packet([_TEMPLATECOUNT]) r = self._get_packet(14) self.template_count = struct.unpack(">H", bytes(r[1:3]))[0] return r[0] def read_sysparam(self): """Returns the system parameters on success via attributes.""" self._send_packet([_READSYSPARA]) r = self._get_packet(28) if r[0] != OK: raise RuntimeError("Command failed.") self.status_register = struct.unpack(">H", bytes(r[1:3]))[0] self.system_id = struct.unpack(">H", bytes(r[3:5]))[0] self.library_size = struct.unpack(">H", bytes(r[5:7]))[0] self.security_level = struct.unpack(">H", bytes(r[7:9]))[0] self.device_address = bytes(r[9:13]) self.data_packet_size = struct.unpack(">H", bytes(r[13:15]))[0] self.baudrate = struct.unpack(">H", bytes(r[15:17]))[0] return r[0] def get_image(self): """Requests the sensor to take an image and store it memory, returns the packet error code or OK success""" self._send_packet([_GETIMAGE]) return self._get_packet(12)[0] def image_2_tz(self, slot=1): """Requests the sensor convert the image to a template, returns the packet error code or OK success""" self._send_packet([_IMAGE2TZ, slot]) return self._get_packet(12)[0] def create_model(self): """Requests the sensor take the template data and turn it into a model returns the packet error code or OK success""" self._send_packet([_REGMODEL]) return self._get_packet(12)[0] def store_model(self, location, slot=1): """Requests the sensor store the model into flash memory and assign a location. Returns the packet error code or OK success""" self._send_packet([_STORE, slot, location >> 8, location & 0xFF]) return self._get_packet(12)[0] def delete_model(self, location): """Requests the sensor delete a model from flash memory given by the argument location. Returns the packet error code or OK success""" self._send_packet([_DELETE, location >> 8, location & 0xFF, 0x00, 0x01]) return self._get_packet(12)[0] def load_model(self, location, slot=1): """Requests the sensor to load a model from the given memory location to the given slot. Returns the packet error code or success""" self._send_packet([_LOAD, slot, location >> 8, location & 0xFF]) return self._get_packet(12)[0] def get_fpdata(self, sensorbuffer="char", slot=1): """Requests the sensor to transfer the fingerprint image or template. Returns the data payload only.""" if slot != 1 or slot != 2: # raise error or use default value? slot = 2 if sensorbuffer == "image": self._send_packet([_UPLOADIMAGE]) elif sensorbuffer == "char": self._send_packet([_UPLOAD, slot]) else: raise RuntimeError("Uknown sensor buffer type") if self._get_packet(12)[0] == 0: res = self._get_data(9) # print('datasize: ' + str(len(res))) # print(res) return res def send_fpdata(self, data, sensorbuffer="char", slot=1): """Requests the sensor to receive data, either a fingerprint image or a character/template data. Data is the payload only.""" if slot != 1 or slot != 2: # raise error or use default value? slot = 2 if sensorbuffer == "image": self._send_packet([_DOWNLOADIMAGE]) elif sensorbuffer == "char": self._send_packet([_DOWNLOAD, slot]) else: raise RuntimeError("Uknown sensor buffer type") if self._get_packet(12)[0] == 0: self._send_data(data) # print('datasize: ' + str(len(res))) # print(res) return True def empty_library(self): """Requests the sensor to delete all models from flash memory. Returns the packet error code or OK success""" self._send_packet([_EMPTY]) return self._get_packet(12)[0] def read_templates(self): """Requests the sensor to list of all template locations in use and stores them in self.templates. Returns the packet error code or OK success""" from math import ceil # pylint: disable=import-outside-toplevel self.templates = [] self.read_sysparam() temp_r = [ 0x0C, ] for j in range(ceil(self.library_size / 256)): self._send_packet([_TEMPLATEREAD, j]) r = self._get_packet(44) if r[0] == OK: for i in range(32): byte = r[i + 1] for bit in range(8): if byte & (1 << bit): self.templates.append((i * 8) + bit + (j * 256)) temp_r = r else: r = temp_r return r[0] def finger_fast_search(self): """Asks the sensor to search for a matching fingerprint template to the last model generated. Stores the location and confidence in self.finger_id and self.confidence. Returns the packet error code or OK success""" # high speed search of slot #1 starting at page 0x0000 and page #0x00A3 # self._send_packet([_HISPEEDSEARCH, 0x01, 0x00, 0x00, 0x00, 0xA3]) # or page #0x03E9 to accommodate modules with up to 1000 capacity # self._send_packet([_HISPEEDSEARCH, 0x01, 0x00, 0x00, 0x03, 0xE9]) # or base the page on module's capacity self.read_sysparam() capacity = self.library_size self._send_packet( [_HISPEEDSEARCH, 0x01, 0x00, 0x00, capacity >> 8, capacity & 0xFF] ) r = self._get_packet(16) self.finger_id, self.confidence = struct.unpack(">HH", bytes(r[1:5])) # print(r) return r[0] def finger_search(self): """Asks the sensor to search for a matching fingerprint starting at slot 1. Stores the location and confidence in self.finger_id and self.confidence. Returns the packet error code or OK success""" self.read_sysparam() capacity = self.library_size self._send_packet( [_FINGERPRINTSEARCH, 0x01, 0x00, 0x00, capacity >> 8, capacity & 0xFF] ) r = self._get_packet(16) self.finger_id, self.confidence = struct.unpack(">HH", bytes(r[1:5])) # print(r) return r[0] ################################################## def _get_packet(self, expected): """ Helper to parse out a packet from the UART and check structure. Returns just the data payload from the packet""" res = self._uart.read(expected) # print("Got", res) if (not res) or (len(res) != expected): raise RuntimeError("Failed to read data from sensor") # first two bytes are start code start = struct.unpack(">H", res[0:2])[0] if start != _STARTCODE: raise RuntimeError("Incorrect packet data") # next 4 bytes are address addr = list(i for i in res[2:6]) if addr != self.address: raise RuntimeError("Incorrect address") packet_type, length = struct.unpack(">BH", res[6:9]) if packet_type != _ACKPACKET: raise RuntimeError("Incorrect packet data") # we should check the checksum # but i don't know how # not yet anyway # packet_sum = struct.unpack('>H', res[9+(length-2):9+length])[0] # print(packet_sum) # print(packet_type + length + struct.unpack('>HHHH', res[9:9+(length-2)])) reply = list(i for i in res[9 : 9 + (length - 2)]) # print(reply) return reply def _get_data(self, expected): """ Gets packet from serial and checks structure for _DATAPACKET and _ENDDATAPACKET. Alternate method for getting data such as fingerprint image, etc. Returns the data payload.""" res = self._uart.read(expected) if (not res) or (len(res) != expected): raise RuntimeError("Failed to read data from sensor") # first two bytes are start code start = struct.unpack(">H", res[0:2])[0] # print(start) if start != _STARTCODE: raise RuntimeError("Incorrect packet data") # next 4 bytes are address addr = list(i for i in res[2:6]) # print(addr) if addr != self.address: raise RuntimeError("Incorrect address") packet_type, length = struct.unpack(">BH", res[6:9]) # print(str(packet_type) + ' ' + str(length)) # todo: check checksum if packet_type != _DATAPACKET: if packet_type != _ENDDATAPACKET: raise RuntimeError("Incorrect packet data") if packet_type == _DATAPACKET: res = self._uart.read(length - 2) # todo: we should really inspect the headers and checksum reply = list(i for i in res[0:length]) self._uart.read(2) # disregard checksum but we really shouldn't reply += self._get_data(9) elif packet_type == _ENDDATAPACKET: res = self._uart.read(length - 2) # todo: we should really inspect the headers and checksum reply = list(i for i in res[0:length]) self._uart.read(2) # disregard checksum but we really shouldn't # print(len(reply)) # print(reply) return reply def _send_packet(self, data): packet = [_STARTCODE >> 8, _STARTCODE & 0xFF] packet = packet + self.address packet.append(_COMMANDPACKET) # the packet type length = len(data) + 2 packet.append(length >> 8) packet.append(length & 0xFF) packet = packet + data checksum = sum(packet[6:]) packet.append(checksum >> 8) packet.append(checksum & 0xFF) # print("Sending: ", [hex(i) for i in packet]) self._uart.write(bytearray(packet)) def _send_data(self, data): print(len(data)) self.read_sysparam() if self.data_packet_size == 0: data_length = 32 elif self.data_packet_size == 1: data_length = 64 elif self.data_packet_size == 2: data_length = 128 elif self.data_packet_size == 3: data_length = 256 i = 0 for i in range(int(len(data) / (data_length - 2))): start = i * (data_length - 2) end = (i + 1) * (data_length - 2) # print(start) # print(end) # print(i) packet = [_STARTCODE >> 8, _STARTCODE & 0xFF] packet = packet + self.address packet.append(_DATAPACKET) length = len(data[start:end]) + 2 # print(length) packet.append(length >> 8) packet.append(length & 0xFF) checksum = _DATAPACKET + (length >> 8) + (length & 0xFF) for j in range(len(data[start:end])): packet.append(data[j]) checksum += data[j] packet.append(checksum >> 8) packet.append(checksum & 0xFF) # print("Sending: ", [hex(i) for i in packet]) self._uart.write(packet) # print(i) i += 1 start = i * (data_length - 2) end = (i + 1) * (data_length - 2) # print(start) # print(end) # print(i) packet = [_STARTCODE >> 8, _STARTCODE & 0xFF] packet = packet + self.address packet.append(_ENDDATAPACKET) length = len(data[start:end]) + 2 # print(length) packet.append(length >> 8) packet.append(length & 0xFF) checksum = _ENDDATAPACKET + (length >> 8) + (length & 0xFF) for j in range(len(data[start:end])): packet.append(data[j]) checksum += data[j] packet.append(checksum >> 8) packet.append(checksum & 0xFF) # print("Sending: ", [hex(i) for i in packet]) self._uart.write(packet) # print(i)