/ 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)