diff --git a/adafruit_fingerprint.py b/adafruit_fingerprint.py index ff1a3dd..4e2f54f 100644 --- a/adafruit_fingerprint.py +++ b/adafruit_fingerprint.py @@ -43,6 +43,7 @@ _GETIMAGE = const(0x01) _IMAGE2TZ = const(0x02) +_COMPARE = const(0x03) _FINGERPRINTSEARCH = const(0x04) _REGMODEL = const(0x05) _STORE = const(0x06) @@ -53,11 +54,13 @@ _DOWNLOADIMAGE = const(0x0B) _DELETE = const(0x0C) _EMPTY = const(0x0D) +_SETSYSPARA = const(0x0E) _READSYSPARA = const(0x0F) _HISPEEDSEARCH = const(0x1B) _VERIFYPASSWORD = const(0x13) _TEMPLATECOUNT = const(0x1D) _TEMPLATEREAD = const(0x1F) +_SOFTRESET = const(0x3D) _GETECHO = const(0x53) _SETAURA = const(0x35) @@ -87,9 +90,11 @@ MODULEOK = const(0x55) # pylint: disable=too-many-instance-attributes +# pylint: disable=too-many-public-methods class Adafruit_Fingerprint: """UART based fingerprint sensor.""" + _debug = False _uart = None password = None @@ -112,6 +117,8 @@ def __init__(self, uart, passwd=(0, 0, 0, 0)): self._uart = uart if self.verify_password() != OK: raise RuntimeError("Failed to find sensor, check wiring!") + if self.read_sysparam() != OK: + raise RuntimeError("Failed to read system parameters!") def check_module(self): """Checks the state of the fingerprint scanner module. @@ -149,6 +156,20 @@ def read_sysparam(self): self.baudrate = struct.unpack(">H", bytes(r[15:17]))[0] return r[0] + def set_sysparam(self, param_num, param_val): + """Set the system parameters (param_num)""" + self._send_packet([_SETSYSPARA, param_num, param_val]) + r = self._get_packet(12) + if r[0] != OK: + raise RuntimeError("Command failed.") + if param_num == 4: + self.baudrate = param_val + elif param_num == 5: + self.security_level = param_val + elif param_num == 6: + self.data_packet_size = param_val + 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""" @@ -188,7 +209,7 @@ def load_model(self, location, slot=1): 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: + if slot not in (1, 2): # raise error or use default value? slot = 2 if sensorbuffer == "image": @@ -199,14 +220,14 @@ def get_fpdata(self, sensorbuffer="char", slot=1): 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) + self._print_debug("get_fpdata data size:", str(len(res))) + self._print_debug("get_fdata res:", res, data_type="hex") 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: + if slot not in (1, 2): # raise error or use default value? slot = 2 if sensorbuffer == "image": @@ -217,8 +238,8 @@ def send_fpdata(self, data, sensorbuffer="char", slot=1): raise RuntimeError("Uknown sensor buffer type") if self._get_packet(12)[0] == 0: self._send_data(data) - # print('datasize: ' + str(len(res))) - # print(res) + self._print_debug("send_fpdata data size:", str(len(data))) + self._print_debug("sent_fdata data:", data, data_type="hex") return True def empty_library(self): @@ -268,9 +289,13 @@ def finger_fast_search(self): ) r = self._get_packet(16) self.finger_id, self.confidence = struct.unpack(">HH", bytes(r[1:5])) - # print(r) + self._print_debug("finger_fast_search packet:", r, data_type="hex") return r[0] + def close_uart(self): + """close serial port""" + self._uart.close() + 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 @@ -282,7 +307,17 @@ def finger_search(self): ) r = self._get_packet(16) self.finger_id, self.confidence = struct.unpack(">HH", bytes(r[1:5])) - # print(r) + self._print_debug("finger_search packet:", r, data_type="hex") + return r[0] + + def compare_templates(self): + """Compares two fingerprint templates in char buffers 1 and 2. Stores the confidence score + in self.finger_id and self.confidence. Returns the packet error code or + OK success""" + self._send_packet([_COMPARE]) + r = self._get_packet(14) + self.confidence = struct.unpack(">H", bytes(r[1:3])) + self._print_debug("compare_templates confidence:", self.confidence) return r[0] def set_led(self, color=1, mode=3, speed=0x80, cycles=0): @@ -302,7 +337,7 @@ 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) + self._print_debug("_get_packet received data:", res, data_type="hex") if (not res) or (len(res) != expected): raise RuntimeError("Failed to read data from sensor") @@ -328,7 +363,7 @@ def _get_packet(self, expected): # 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) + self._print_debug("_get_packet reply:", reply, data_type="hex") return reply def _get_data(self, expected): @@ -336,22 +371,24 @@ def _get_data(self, expected): and _ENDDATAPACKET. Alternate method for getting data such as fingerprint image, etc. Returns the data payload.""" res = self._uart.read(expected) + self._print_debug("_get_data received data:", res, data_type="hex") 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) + self._print_debug("_get_data received start pos:", 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) + self._print_debug("_get_data received address:", addr) if addr != self.address: raise RuntimeError("Incorrect address") packet_type, length = struct.unpack(">BH", res[6:9]) - # print(str(packet_type) + ' ' + str(length)) + self._print_debug("_get_data received packet_type:", packet_type) + self._print_debug("_get_data received length:", length) # todo: check checksum @@ -363,15 +400,19 @@ def _get_data(self, expected): 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 + received_checksum = struct.unpack(">H", self._uart.read(2)) + self._print_debug("_get_data received checksum:", received_checksum) + 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) + received_checksum = struct.unpack(">H", self._uart.read(2)) + self._print_debug("_get_data received checksum:", received_checksum) + + self._print_debug("_get_data reply length:", len(reply)) + self._print_debug("_get_data reply:", reply, data_type="hex") return reply def _send_packet(self, data): @@ -389,12 +430,14 @@ def _send_packet(self, data): packet.append(checksum >> 8) packet.append(checksum & 0xFF) - # print("Sending: ", [hex(i) for i in packet]) + self._print_debug("_send_packet length:", len(packet)) + self._print_debug("_send_packet data:", packet, data_type="hex") self._uart.write(bytearray(packet)) def _send_data(self, data): - print(len(data)) - self.read_sysparam() + self._print_debug("_send_data length:", len(data)) + self._print_debug("_send_data data:", data, data_type="hex") + # self.read_sysparam() #moved this to init if self.data_packet_size == 0: data_length = 32 elif self.data_packet_size == 1: @@ -403,58 +446,56 @@ def _send_data(self, data): data_length = 128 elif self.data_packet_size == 3: data_length = 256 - + self._print_debug("_send_data sensor data length:", data_length) 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) + left = len(data) + for i in range(int(len(data) / data_length)): + start = i * data_length + end = (i + 1) * data_length + left = left - data_length + self._print_debug("_send_data data start:", start) + self._print_debug("_send_data data end:", end) + self._print_debug("_send_data i:", i) packet = [_STARTCODE >> 8, _STARTCODE & 0xFF] packet = packet + self.address - packet.append(_DATAPACKET) + + if left <= 0: + packet.append(_ENDDATAPACKET) + else: + packet.append(_DATAPACKET) + length = len(data[start:end]) + 2 - # print(length) + self._print_debug("_send_data length:", length) packet.append(length >> 8) packet.append(length & 0xFF) checksum = _DATAPACKET + (length >> 8) + (length & 0xFF) - for j in range(len(data[start:end])): + # for j in range(len(data[start:end])): + for j in range(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._print_debug("_send_data sending packet:", packet, data_type="hex") 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) + def soft_reset(self): + """Performs a soft reset of the sensor""" + self._send_packet([_SOFTRESET]) + if self._get_packet(12)[0] == OK: + if self._uart.read(1)[0] != MODULEOK: + raise RuntimeError("Sensor did not send a handshake signal!") + + def _print_debug(self, info, data, data_type="str"): + """Prints debugging information. This is activated + by flag _debug""" + if not self._debug: + return + + if data_type == "hex": + print("*** DEBUG ==>", info, ["{:02x}".format(i) for i in data]) + elif data_type == "str": + print("*** DEBUG ==>", info, data) diff --git a/examples/fingerprint_template_file_compare.py b/examples/fingerprint_template_file_compare.py new file mode 100644 index 0000000..191d382 --- /dev/null +++ b/examples/fingerprint_template_file_compare.py @@ -0,0 +1,202 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + +""" +`fingerprint_template_file_compare.py` +==================================================== + +This is an example program to demo storing fingerprint templates in a file. It also allows +comparing a newly obtained print with one stored in the file in previous step. This is helpful +when fingerprint templates are stored centrally (not on sensor's flash memory) and shared +between multiple sensors. + +* Author(s): admiralmaggie + +Implementation Notes +-------------------- + +**Hardware:** + +* `Fingerprint sensor `_ (Product ID: 751) +* `Panel Mount Fingerprint sensor `_ (Product ID: 4651) +""" + + +import serial +import adafruit_fingerprint + + +# import board (if you are using a micropython board) +# uart = busio.UART(board.TX, board.RX, baudrate=57600) + +# If using with a computer such as Linux/RaspberryPi, Mac, Windows with USB/serial converter: +uart = serial.Serial("COM6", baudrate=57600, timeout=1) + +# If using with Linux/Raspberry Pi and hardware UART: +# uart = serial.Serial("/dev/ttyS0", baudrate=57600, timeout=1) + +# If using with Linux/Raspberry Pi 3 with pi3-disable-bte +# uart = serial.Serial("/dev/ttyAMA0", baudrate=57600, timeout=1) + +finger = adafruit_fingerprint.Adafruit_Fingerprint(uart) + +################################################## + + +def sensor_reset(): + """Reset sensor""" + print("Resetting sensor...") + if finger.soft_reset() != adafruit_fingerprint.OK: + print("Unable to reset sensor!") + print("Sensor is reset.") + + +# pylint: disable=too-many-branches +def fingerprint_check_file(): + """Compares a new fingerprint template to an existing template stored in a file + This is useful when templates are stored centrally (i.e. in a database)""" + print("Waiting for finger print...") + set_led_local(color=3, mode=1) + while finger.get_image() != adafruit_fingerprint.OK: + pass + print("Templating...") + if finger.image_2_tz(1) != adafruit_fingerprint.OK: + return False + + print("Loading file template...", end="", flush=True) + with open("template0.dat", "rb") as file: + data = file.read() + finger.send_fpdata(list(data), "char", 2) + + i = finger.compare_templates() + if i == adafruit_fingerprint.OK: + set_led_local(color=2, speed=150, mode=6) + print("Fingerprint match template in file.") + return True + if i == adafruit_fingerprint.NOMATCH: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Templates do not match!") + else: + print("Other error!") + return False + + +# pylint: disable=too-many-statements +def enroll_save_to_file(): + """Take a 2 finger images and template it, then store it in a file""" + set_led_local(color=3, mode=1) + for fingerimg in range(1, 3): + if fingerimg == 1: + print("Place finger on sensor...", end="", flush=True) + else: + print("Place same finger again...", end="", flush=True) + + while True: + i = finger.get_image() + if i == adafruit_fingerprint.OK: + print("Image taken") + break + if i == adafruit_fingerprint.NOFINGER: + print(".", end="", flush=True) + elif i == adafruit_fingerprint.IMAGEFAIL: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Imaging error") + return False + else: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Other error") + return False + + print("Templating...", end="", flush=True) + i = finger.image_2_tz(fingerimg) + if i == adafruit_fingerprint.OK: + print("Templated") + else: + if i == adafruit_fingerprint.IMAGEMESS: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Image too messy") + elif i == adafruit_fingerprint.FEATUREFAIL: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Could not identify features") + elif i == adafruit_fingerprint.INVALIDIMAGE: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Image invalid") + else: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Other error") + return False + + if fingerimg == 1: + print("Remove finger") + while i != adafruit_fingerprint.NOFINGER: + i = finger.get_image() + + print("Creating model...", end="", flush=True) + i = finger.create_model() + if i == adafruit_fingerprint.OK: + print("Created") + else: + if i == adafruit_fingerprint.ENROLLMISMATCH: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Prints did not match") + else: + set_led_local(color=1, mode=2, speed=20, cycles=10) + print("Other error") + return False + + print("Downloading template...") + data = finger.get_fpdata("char", 1) + with open("template0.dat", "wb") as file: + file.write(bytearray(data)) + set_led_local(color=2, speed=150, mode=6) + print("Template is saved in template0.dat file.") + + return True + + +# pylint: disable=broad-except +def set_led_local(color=1, mode=3, speed=0x80, cycles=0): + """this is to make sure LED doesn't interfer with example + running on models without LED support - needs testing""" + try: + finger.set_led(color, mode, speed, cycles) + except Exception as exc: + print("INFO: Sensor les not support LED. Error:", str(exc)) + + +set_led_local(color=3, mode=2, speed=10, cycles=10) + +while True: + print("----------------") + if finger.read_templates() != adafruit_fingerprint.OK: + raise RuntimeError("Failed to read templates") + print("Fingerprint templates: ", finger.templates) + if finger.count_templates() != adafruit_fingerprint.OK: + raise RuntimeError("Failed to read templates") + print("Number of templates found: ", finger.template_count) + if finger.set_sysparam(6, 2) != adafruit_fingerprint.OK: + raise RuntimeError("Unable to set package size to 128!") + if finger.read_sysparam() != adafruit_fingerprint.OK: + raise RuntimeError("Failed to get system parameters") + print("Package size (x128):", finger.data_packet_size) + print("Size of template library: ", finger.library_size) + print("e) enroll print and save to file") + print("c) compare print to file") + print("r) soft reset") + print("x) quit") + print("----------------") + c = input("> ") + + if c in ("x", "q"): + print("Exiting fingerprint example program") + # turn off LED + set_led_local(mode=4) + raise SystemExit + if c == "e": + enroll_save_to_file() + elif c == "c": + fingerprint_check_file() + elif c == "r": + sensor_reset() + else: + print("Invalid choice: Try again")