diff --git a/adafruit_fingerprint.py b/adafruit_fingerprint.py index f40f60b..ee07119 100644 --- a/adafruit_fingerprint.py +++ b/adafruit_fingerprint.py @@ -62,12 +62,17 @@ _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) @@ -92,6 +97,7 @@ INVALIDREG = const(0x1A) ADDRCODE = const(0x20) PASSVERIFY = const(0x21) +MODULEOK = const(0x55) class Adafruit_Fingerprint: """UART based fingerprint sensor.""" @@ -103,6 +109,11 @@ class Adafruit_Fingerprint: confidence = None templates = [] template_count = None + library_size = None + security_level = None + device_address = None + data_packet_size = None + baudrate = None def __init__(self, uart, passwd=(0, 0, 0, 0)): # Create object with UART for interface, and default 32-bit password @@ -111,6 +122,14 @@ def __init__(self, uart, passwd=(0, 0, 0, 0)): 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)) @@ -124,13 +143,26 @@ def count_templates(self): 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.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): + 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]) @@ -142,10 +174,10 @@ def create_model(self): self._send_packet([_REGMODEL]) return self._get_packet(12)[0] - def store_model(self, location): + 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, 1, location >> 8, location & 0xFF]) + self._send_packet([_STORE, slot, location >> 8, location & 0xFF]) return self._get_packet(12)[0] def delete_model(self, location): @@ -154,17 +186,74 @@ def delete_model(self, location): 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""" - self._send_packet([_TEMPLATEREAD, 0x00]) - r = self._get_packet(44) + stores them in self.templates. Returns the packet error code or + OK success""" + import math self.templates = [] - for i in range(32): - byte = r[i+1] - for bit in range(8): - if byte & (1 << bit): - self.templates.append(i * 8 + bit) + self.read_sysparam() + temp_r = [0x0c, ] + for j in range(math.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): @@ -172,9 +261,17 @@ def finger_fast_search(self): 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]) + #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] ################################################## @@ -201,10 +298,60 @@ def _get_packet(self, expected): 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 = [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 = [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 = [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 = [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 @@ -222,3 +369,70 @@ def _send_packet(self, data): #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) diff --git a/examples/fingerprint_simpletest_rpi.py b/examples/fingerprint_simpletest_rpi.py new file mode 100644 index 0000000..6d5ff6b --- /dev/null +++ b/examples/fingerprint_simpletest_rpi.py @@ -0,0 +1,253 @@ +import time +import board +#import busio +import serial +from digitalio import DigitalInOut, Direction +import adafruit_fingerprint + +led = DigitalInOut(board.D13) +led.direction = Direction.OUTPUT + +#uart = busio.UART(board.TX, board.RX, baudrate=57600) + +# If using with a computer such as Linux/RaspberryPi, Mac, Windows with USB/serial converter: +#import serial +#uart = serial.Serial("/dev/ttyUSB0", baudrate=57600, timeout=1) + +# If using with Linux/Raspberry Pi and hardware UART: +#import serial +#uart = serial.Serial("/dev/ttyS0", baudrate=57600, timeout=1) + +# If using with Linux/Raspberry Pi 3 with pi3-disable-bt +#import serial +uart = serial.Serial("/dev/ttyAMA0", baudrate=57600, timeout=1) + +finger = adafruit_fingerprint.Adafruit_Fingerprint(uart) + +################################################## + + +def get_fingerprint(): + """Get a finger print image, template it, and see if it matches!""" + print("Waiting for image...") + while finger.get_image() != adafruit_fingerprint.OK: + pass + print("Templating...") + if finger.image_2_tz(1) != adafruit_fingerprint.OK: + return False + print("Searching...") + if finger.finger_fast_search() != adafruit_fingerprint.OK: + return False + return True + +# pylint: disable=too-many-branches +def get_fingerprint_detail(): + """Get a finger print image, template it, and see if it matches! + This time, print out each error instead of just returning on failure""" + print("Getting image...", end="", flush=True) + i = finger.get_image() + if i == adafruit_fingerprint.OK: + print("Image taken") + else: + if i == adafruit_fingerprint.NOFINGER: + print("No finger detected") + elif i == adafruit_fingerprint.IMAGEFAIL: + print("Imaging error") + else: + print("Other error") + return False + + print("Templating...", end="", flush=True) + i = finger.image_2_tz(1) + if i == adafruit_fingerprint.OK: + print("Templated") + else: + if i == adafruit_fingerprint.IMAGEMESS: + print("Image too messy") + elif i == adafruit_fingerprint.FEATUREFAIL: + print("Could not identify features") + elif i == adafruit_fingerprint.INVALIDIMAGE: + print("Image invalid") + else: + print("Other error") + return False + + print("Searching...", end="", flush=True) + i = finger.finger_fast_search() + # pylint: disable=no-else-return + # This block needs to be refactored when it can be tested. + if i == adafruit_fingerprint.OK: + print("Found fingerprint!") + return True + else: + if i == adafruit_fingerprint.NOTFOUND: + print("No match found") + else: + print("Other error") + return False + +# pylint: disable=too-many-statements +def enroll_finger(location): + """Take a 2 finger images and template it, then store in 'location'""" + 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 + elif i == adafruit_fingerprint.NOFINGER: + print(".", end="", flush=True) + elif i == adafruit_fingerprint.IMAGEFAIL: + print("Imaging error") + return False + else: + 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: + print("Image too messy") + elif i == adafruit_fingerprint.FEATUREFAIL: + print("Could not identify features") + elif i == adafruit_fingerprint.INVALIDIMAGE: + print("Image invalid") + else: + print("Other error") + return False + + if fingerimg == 1: + print("Remove finger") + time.sleep(1) + 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: + print("Prints did not match") + else: + print("Other error") + return False + + print("Storing model #%d..." % location, end="", flush=True) + i = finger.store_model(location) + if i == adafruit_fingerprint.OK: + print("Stored") + else: + if i == adafruit_fingerprint.BADLOCATION: + print("Bad storage location") + elif i == adafruit_fingerprint.FLASHERR: + print("Flash storage error") + else: + print("Other error") + return False + + return True + +def save_fingerprint_image(filename): + """Scan fingerprint then save image to filename.""" + while finger.get_image(): + pass + + # let PIL take care of the image headers and file structure + from PIL import Image + img = Image.new('L', (256, 288), 'white') + pixeldata = img.load() + mask = 0b00001111 + result = finger.get_fpdata(sensorbuffer="image") + + # this block "unpacks" the data received from the fingerprint + # module then copies the image data to the image placeholder "img" + # pixel by pixel. please refer to section 4.2.1 of the manual for + # more details. thanks to Bastian Raschke and Danylo Esterman. + # pylint: disable=invalid-name + x = 0 + # pylint: disable=invalid-name + y = 0 + # pylint: disable=consider-using-enumerate + for i in range(len(result)): + pixeldata[x, y] = (int(result[i]) >> 4) * 17 + x += 1 + pixeldata[x, y] = (int(result[i]) & mask) * 17 + if x == 255: + x = 0 + y += 1 + else: + x += 1 + + if not img.save(filename): + return True + return False + + +################################################## + +def get_num(max_number): + """Use input() to get a valid number from 0 to the maximum size + of the library. Retry till success!""" + i = -1 + while (i > max_number - 1) or (i < 0): + try: + i = int(input("Enter ID # from 0-{}: ".format(max_number - 1))) + except ValueError: + pass + return i + + +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.read_sysparam() != adafruit_fingerprint.OK: + raise RuntimeError('Failed to get system parameters') + print("Size of template library: ", finger.library_size) + print("e) enroll print") + print("f) find print") + print("d) delete print") + print("s) save fingerprint image") + print("r) reset library") + print("q) quit") + print("----------------") + c = input("> ") + + if c == 'e': + enroll_finger(get_num(finger.library_size)) + if c == 'f': + if get_fingerprint(): + print("Detected #", finger.finger_id, "with confidence", finger.confidence) + else: + print("Finger not found") + if c == 'd': + if finger.delete_model(get_num(finger.library_size)) == adafruit_fingerprint.OK: + print("Deleted!") + else: + print("Failed to delete") + if c == 's': + if save_fingerprint_image("fingerprint.png"): + print("Fingerprint image saved") + else: + print("Failed to save fingerprint image") + if c == 'r': + if finger.empty_library() == adafruit_fingerprint.OK: + print("Library empty!") + else: + print("Failed to empty library") + if c == 'q': + print("Exiting fingerprint example program") + raise SystemExit