Fingerprint Sensor Suite
Nach dem ich es sehr mühsam fand einen Fingerprint Sensor so zu programmieren wie ich es mir vorstellte, habe ich mich entschlossen eine Testsuite zu programmieren, so dass ich die Funktionsweise und Funktionsmöglichkeiten besser nutzen kann.
Damit nicht jeder die gleiche Erfahrung machen muss wie ich, und nicht alles selber zusammentragen muss, habe ich meine Lösung und Erkenntnisse hier festgehalten.
Es hat aber einige offenen Punkte die vielleicht der einte oder andere Leser mithelfen kann zu beantworten.
Facts:
- Visual Studio 2022 Community mit C# .net 8.0 und WPF
- XIAO RP2040
- Arduino Framework
- Visual Studio Code mit Platform.io
- Language De, En, Es
- Growm und Hi-link Sensoren
Motivation
- Wie macht man einen Fingerprint Türöffner.
- Wie sicher ist das?
- Wie management man Fingerprints?
- Wie funktioniert ein Fingerprint?
- Wie und mit was programmiert man einen Fingerprint Sensor?
Herausforderung:
- IDE
- Sensor
- Dokumentation
- Debugging
- Library
- Beispiele
Motivation zur Implementation
Für was?
- Wenn ich etwas anwende, dann will ich wissen wie es funktioniert.
- Wenn man Fingerprints speichert muss man sie auch verwalten können. Wenn man also eine Fingerprint nicht mehr zulassen will, wie löscht man ihn? Vorallem wie weiss man noch welcher es war?
- Wie ersetzt man einen Fingerprint Senor wenn er defekt ist?
- Wenn man mehrere Sensoren und Zugänge hat wie reproduziert man die Daten?
Aber Achtung!
- Es ist gefährlich Fingerabdruck zu speichern. Wenn jemand die Datei kopieren kann, dann kann er einen 3d Abdruck drucken.
- Ist der Sensor zugänglich kann man ihn auswechseln oder abhören.
- Wenn Sie den Fingerabdruck im Internet publizieren, dann kann er kopiert werden, auch vom Nachbar.
- Wenn der Fingerabdruck übers wifi transferiert wird kann er eventuell auch abgehört werden.
- Ist ein Sensor aktiv für Zugangsprüfung, dann muss das Password und Adresse unbedingt geändert werden.
- Wenn sie Fingerabdrucke zentral verwalten, was sehr sinnvoll ist, dann müssen sie unter Verschluss abgelegt sein.
Wünsche!
- Ich wünschte das Protokoll wäre besser standardisiert.
- ich wünschte Gruppen von Id’s könnte man enablen und disablen, damit könne man zeitliche Einschränkungen machen.
Sensor Funktionsweise
1. Der Finger wird mit kapazitiven Sensoren gelesen und gespeichert. Dabei verändert sich die Kapazität ob mehr oder weniger Luft oder Haut den Sensor berühren. Für unterschiedliche Kapazitäten werden verschiedene Graustufen gespeichert. Somit hat man ein graustufen Fingerprint.
2. Um die Qualität zu erhöhen sind mindestens zwei Fingerprint Abdrücke notwendig.
3. Das Bild wird analysiert und die Charakteristik des Abdruckes ausgewertet.
4. Aus den Charakteristik Dateien wird dann mit Hilfe von Algorithmen die essentiellen Einzigartigkeiten eruiert und gespeichert. Dann werden die beiden Resultate miteinander Verglichen und wenn sie eindeutig das gleiche Ergebnis geben als gut befunden. Das Ergebnis kann dann als Vorlage in der Datenbank als Fingerprint Referenz abgelegt werden.
Vergleich
Wenn man einen Fingerabdruck vergleicht, dann vergleicht man das ausgewertete Bild mit den gespeicherten Vorlagen. Also bei jedem Vergleich gibt es zuerst ein Bild dann ein Charakterdatei und dann ein Template die mit den Vorlagen in der DB verglichen wird.
Funktionsbeschreibung
Generell
Das Programm FIngerprint Suite dient dem Testen der Kommunikation zwischen Computer und Fingerprint Sensor. Das Programm ist eine WPF-Applikation entwickelt mit Visual Studio 2022 Community V17 auf Windows 11 mit C#. Die Test Sensoren sind zwei elektrisch identische Grown kapazitive Sensoren und zwei HI-Link kapazitive Sensoren, ein ZW0642 und ein ZW0905. Die Fingerprint Komponente wurde zuerst für Grown entwickelt und dann auf HI-Link (HLK) adaptiert. Vor der Implementation habe ich mit der Adafruit Arduino Library Experimentiert. Damit kann man arbeiten.
WPF-Scrreen
Der VIiew ist für eine Auflösung von 1280×1024 ausgelegt. Der View benutzt WPF Materialdesign, Dockpanel und Dialoghost.
- Es gibt ein Logger Fenster, welches von unten in den Dialog hineingletet (durch klick auf das Logo hinein und hinaus).
- Es gibt ein Setting Fenster, welches von oben in den Dialog hineingleitet (durch klick auf den Header Titel hinein und hinaus).
- Es gibt einen linken Bereich mit einem Bildausschnitt und einem Splitter der den Fingerabdruck und Charakteristik anzeigt. Darunter die Imagebuffer zur Auswahl (idR werden 2 benutzt, 6 aber möglich). dann folgen die Filelist der gespeicherten Bilder. Der unterste Bereich ist dann die Bedienung der Aura, LedRing, falls einer vorhanden ist.
- Es gibt ein rechter Bereich mit den vielen Funktionen die man einzeln aufrufen kann.
- Und es hat der mittlere Bereich mit den Informationen die man anzeigen kann. Die obere Hälfte mit der Template Table und die untere Hälfte mit den Notepad und Information pages.
im unteren Bereich kann man auch Fingerprint Test und Fingerprint Enroll aufrufen. Es handelt sich dabei um Task die nach der Verwendung wieder zu stoppen sind.
Beschreibung der Elemente
- Datum und Zeit
- Template Id, läuft wenn App nicht blockiert
- Titel und Click für Setting Slider
- Logo und Click für Logger Slider
- Charakteristic Finger Image
- Fingerprint Image
- Image Left/Right Splitter
- Command Button round robin page loader (page 0-3)
- nr of loaded Page
- Image Buffer Selected (normal 1-2, additonal -6)
- File List bin-file of defined Path
- Aura settings: first line mode, second on of color, black is off
- Blink speed slider to select 0-255 0> 0- 2.5 sec
- Fingerprint Quality (score) when verify
- Status bit of Status byte
- buttton start task for Verify Finger or Enroll Finger
- Information Window wenn task is running
- all posible Commands works only whit correct Sequence
- Windows for Information /Notepad / flash memory
- Input field for enter Filename part etc => !!!! you need to click on some button for change focus to save
- Result of Information Command
- Result of Get Parameter Command
- Template Tabelle:
- blau: ein Template ist vorhanden.
- rot: enroll ein neues Template ersetzt ein altes
- grün: ein neues Template wurde gespeichert
Programmbeschreibung
das Binding zwischen View und Viewmodel des WPF Views ist mit dem MVVM Community Toolkit gemacht. Die Settings wurden mit dem Setting.settings designer erstellt. Sie können aber auch im XML File angepasst werden. Das Programm verwendet den HOBRO Eventlogger AMQP des RabbitMQ Messagebus. Dies ist nicht unbedingt nötig und kann im Viewmodel einfach auskommentiert werden. Alle Fingerprint Funktionen werden mit der FIngerprint Library abgedeckt. Achtung!
Aus Bequemlichkeit habe ich für die Task kein Dialoghost implementiert. Das hat den Nachteil, dass man andere Knöpfe drücken kann die nicht zum Task gehören. Dies gibt dann im Programm ein deadlock weil zwei Thread antworten erwarten. Um mehrere Dialoghost mit Materialdesign zu implementieren muss man ein Ressourcen Datei erstellen und mit einer eigen Klasse im Code binden. Das ist etwas aufwendiger aber dann werden die anderen Knöpfe abgedunkelt und nicht mehr anwählbar.
XIAO RP2040
Die Fingerprint Sensoren kommunizieren mit UART über 3.3V TTL Signal. Damit kann man sie nicht ohne weiteres an eine USB Schnittstelle anschliessen. Ich habe den Sensor deshalb mit einem XIAO RP2040 und der Adafruit Fingerprint Library betrieben. Das funktioniert eigentlich ganz gut.
bei der Verwendung des Fingerprint Sensors mit dem XIAO Rp2040 braucht es keine weiteren Komponenten. Rx kann direkt an Tx und Tx an Rx angeschlossen werden. Für die Kommunikation mit dem Hostcomputer (Windows 11) wird dann Serial1 Read auf Serial2.Write verbunden und umgekehrt (sogenanntes PassTrough). Im folgend Code snipped ist eine Version mit 4 Modes (check finger, enroll finger, pass through, clear). Für die FingerprintSuit auf Windows muss man auf pass through einstellen.
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_Fingerprint.h>
#include <SoftwareSerial.h>
#include "main.h"
/// @brief The HOBRO FIPI is a Fingerprint reader sensor device that a GROW fingerprint
/// can manage and controls a output for opening the door. The FIPI has no bluetooth
/// and no wifi interface because it is local and independend for security reason.
///
/// @title: Fingerprint Sensor Device FIPI
/// @author Marc Brotbeck
/// @email: marc@brotbeck.com
/// @date: Nov 2024
/// @environment: Visual Studio Code with Platform.io and Arduino C++
/// @companyname: TIS ( Technische Informatik Solutions)
/// @copyright 2024 by Technische Informatik Solutions
///
/// @file: main.cpp with main.h
/// @version V1.0-0
// Note!! this code is based on the adafruit_Fingerprint library example
// Written by Limor Fried/Ladyada for Adafruit Industries.
/// const:
const int ledG = 16; // user rgb led
const int ledR = 17;
const int ledB = 25;
const int dipSwitch0 = 28; // changeing the mode of program
const int dipswitch1 = 29; // 0=reader, 1=enrol, 2=pass, 3=clear
const int fpow = 7; // out: finger reader power on if finger is present
const int fide = 2; // in: finger is present
const int neoPixPower = 11;
const int neoPixPin = 12;
//#define mySerial Serial1
/// global typdefs
// the order is important! the value is the combinination of r,b
// and g ; 1,2 and 4 ; bit 0, bit 1 and bit 2
enum statusColor {black, red, blue, magenta, green, yellow, cyan, white };
/// global variable:
int cnt = 1;
int col = 0;
uint8_t id;
/// instantiated class or struct:
Adafruit_NeoPixel pixels(NUMPIXELS, neoPixPin, NEO_GRB + NEO_KHZ800);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&Serial1);
/// function declarations:
void initializeIoPin();
void pixelTest();
void fingerprintLedTest();
void statusLed(statusColor x);
void fingerprintInfo();
void printHex(int num, int precision);
bool checkStatusIsOkay(int p, bool noprint = false);
int getFingerprintIDfast();
uint8_t getFingerprintEnroll();
uint8_t getFingerprintID();
uint8_t readnumber(void);
uint8_t downloadFingerprintTemplate(uint16_t id);
// ===================== S E T U P ==============================================
void setup() {
initializeIoPin();
digitalWrite(fpow, LOW);
statusLed(blue);
delay(6000);
pixels.begin();
pinMode(neoPixPower,OUTPUT);
digitalWrite(neoPixPower, HIGH);
Serial.begin(57600);
Serial.println("test fingerprint sensor!");
delay(1000);
finger.begin(57600);
delay(5);
if (finger.verifyPassword()) {
statusLed(green);
Serial.println("Found fingerprint sensor!");
} else {
statusLed(red);
Serial.println("Did not find fingerprint sensor :(");
while (1) { delay(1); }
}
fingerprintInfo();
// Try to get the templates for fingers 1 through 10
/// for (int fId = 1; fId < finger.templateCount; fId++) {
/// downloadFingerprintTemplate(fId);
/// }
}
bool firstTime = true ;
byte dipSwitch = 0;
// ===================== M A I N ==============================================
void loop() {
if (firstTime) {
if (DEBUGOUT) { Serial.println("main started !"); }
pixels.setBrightness(5);
pixels.clear();
dipSwitch = digitalRead(dipSwitch0) & 1 ;
dipSwitch = dipSwitch | (digitalRead(dipswitch1) * 2) ;
switch (dipSwitch) {
case 0: pixels.setPixelColor(0, pixels.Color(233, 233, 23)); break; // gelb
case 1: pixels.setPixelColor(0, pixels.Color( 52, 204, 235)); break; // hellblau
case 2: pixels.setPixelColor(0, pixels.Color(204, 52, 235)); break; //violet
case 3: pixels.setPixelColor(0, pixels.Color( 19, 26, 128)); break; // blue
default: pixels.setPixelColor(0, pixels.Color(245, 3, 3)); break;
}
pixels.show();
id = finger.templateCount; // workaround not overwritte existing store fingerprint
if (id < 1 ) id = 1;
firstTime = false;
}
switch (dipSwitch) {
case 0: // Led yellow => normal reading
getFingerprintID();
delay(50);
break;
case 1: // lightblue => enroll new fingerprint
if (DEBUGOUT) {
Serial.println("Ready to enroll a fingerprint!");
Serial.println("Please type in the ID # (from 1 to 127) you want to save this finger as...");
id = readnumber();
if (id == 0) return ; // ID #0 not allowed, try again!
Serial.print("Enrolling ID #"); Serial.println(id);
}
while (! getFingerprintEnroll() );
id++;
break;
case 2: // violet = > Pass throu uart to USB
// Serial.write("test");
while (Serial1.available() ) Serial.write(Serial1.read());
while ( Serial.available() ) Serial1.write(Serial.read());
break;
case 3: // blue => clear prints
finger.emptyDatabase();
statusLed(black);
while (1);
break;
default: break; //red
}
} // =============== E N D M A I N ==========================================
void pixelTest(){
pixels.setBrightness(5);
pixels.clear();
pixels.setPixelColor(0, pixels.Color(15, 25, 205));
delay(400);
pixels.show();
pixels.clear();
pixels.setPixelColor(0, pixels.Color(103, 25, 205));
delay(400);
pixels.show();
pixels.clear();
pixels.setPixelColor(0, pixels.Color(233, 242, 205));
delay(400);
pixels.show();
pixels.clear();
pixels.setPixelColor(0, pixels.Color(233, 23, 23));
delay(400);
pixels.show();
pixels.clear();
pixels.setPixelColor(0, pixels.Color(12, 66, 101));
delay(400);
pixels.show();
delay(500);
}
void initializeIoPin() {
// set status led 3 color
pinMode(ledG, OUTPUT);
pinMode(ledR, OUTPUT);
pinMode(ledB, OUTPUT);
// fingerprint power (not working , not using)
pinMode(fpow, OUTPUT);
pinMode(fide, INPUT_PULLDOWN);
// program mode
pinMode(dipSwitch0, INPUT_PULLUP);
pinMode(dipswitch1, INPUT_PULLUP);
}
void fingerprintLedTest(){
// LED fully on
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, red);
delay(250);
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, blue);
delay(250);
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, FINGERPRINT_LED_PURPLE);
delay(250);
// flash red LED
finger.LEDcontrol(FINGERPRINT_LED_FLASHING, 25, FINGERPRINT_LED_RED, 10);
delay(2000);
// Breathe blue LED till we say to stop
finger.LEDcontrol(FINGERPRINT_LED_BREATHING, 100, FINGERPRINT_LED_BLUE);
delay(3000);
finger.LEDcontrol(FINGERPRINT_LED_GRADUAL_ON, 200, FINGERPRINT_LED_PURPLE);
delay(2000);
finger.LEDcontrol(FINGERPRINT_LED_GRADUAL_OFF, 200, FINGERPRINT_LED_PURPLE);
delay(10000);
}
void statusLed(statusColor x){
switch (x) {
case black: digitalWrite(ledR, HIGH); digitalWrite(ledG, HIGH); digitalWrite(ledB, HIGH); break;
case red: digitalWrite(ledR, LOW); digitalWrite(ledG, HIGH); digitalWrite(ledB, HIGH); break;
case green: digitalWrite(ledR, HIGH); digitalWrite(ledG, LOW); digitalWrite(ledB, HIGH); break;
case yellow: digitalWrite(ledR, LOW); digitalWrite(ledG, LOW); digitalWrite(ledB, HIGH); break;
case blue: digitalWrite(ledR, HIGH); digitalWrite(ledG, HIGH); digitalWrite(ledB, LOW); break;
case magenta: digitalWrite(ledR, LOW); digitalWrite(ledG, HIGH); digitalWrite(ledB, LOW); break;
case cyan: digitalWrite(ledR, HIGH); digitalWrite(ledG, LOW); digitalWrite(ledB, LOW); break;
case white: digitalWrite(ledR, LOW); digitalWrite(ledG, LOW); digitalWrite(ledB, LOW); break;
default: digitalWrite(ledR, HIGH); digitalWrite(ledG, HIGH); digitalWrite(ledB, HIGH); x = black; break;
}
//currentColor = x;
}
void fingerprintInfo(){
Serial.println(F("Reading sensor parameters"));
finger.getParameters();
finger.getTemplateCount();
Serial.print(F("Status: 0x")); Serial.println(finger.status_reg, HEX);
Serial.print(F("Sys ID: 0x")); Serial.println(finger.system_id, HEX);
Serial.print(F("Capacity: ")); Serial.println(finger.capacity);
Serial.print(F("Security level: ")); Serial.println(finger.security_level);
Serial.print(F("Device address: ")); Serial.println(finger.device_addr, HEX);
Serial.print(F("Packet len: ")); Serial.println(finger.packet_len);
Serial.print(F("Baud rate: ")); Serial.println(finger.baud_rate);
Serial.print(F("Template count: ")); Serial.println(finger.templateCount);
}
uint8_t readnumber(void) {
uint8_t num = 0;
while (num == 0) {
while (! Serial.available());
num = Serial.parseInt();
}
return num;
}
/// @brief get Fingerprint Enroll
/// with this function we can save a new Fingerprint in the sensor. On the
/// terminal we enter the id of the fingerprint we want to save and then the
/// enroll is starting. you must 2 times scan the finger for storing the
/// fingerprint. It is controlled by led color of the sensor ( there
/// exit also serial output):
/// magenta breathing => put your finger on the sensor
/// blu solid => reading done, remove finger
/// green solid => first and second done, match and stored.
/// red solid => any error, see text
/// @return status code
///
uint8_t getFingerprintEnroll() {
int p = -1;
finger.LEDcontrol(FINGERPRINT_LED_BREATHING, 255, magenta);
Serial.print("Waiting for valid finger to enroll as #"); Serial.println(id);
while (p != FINGERPRINT_OK) {
p = finger.getImage();
if (checkStatusIsOkay(p)) { Serial.println("Image taken"); }
}
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, blue);
// OK success!
p = finger.image2Tz(1);
if (checkStatusIsOkay(p)) { Serial.println("Image converted"); } else return(p);
Serial.println("Remove finger");
delay(2000);
p = 0;
while (p != FINGERPRINT_NOFINGER) {
p = finger.getImage();
}
finger.LEDcontrol(FINGERPRINT_LED_BREATHING, 255, magenta);
Serial.print("ID "); Serial.println(id);
Serial.println("Place same finger again");
p = -1;
while (p != FINGERPRINT_OK) {
p = finger.getImage();
if (checkStatusIsOkay(p)) { Serial.println("Image taken"); }
}
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, blue);
// OK success!
p = finger.image2Tz(2);
if (checkStatusIsOkay(p)) { Serial.println("Image converted"); } else return(p);
// OK converted!
Serial.print("Creating model for #"); Serial.println(id);
p = finger.createModel();
if (checkStatusIsOkay(p)) { Serial.println("Prints matched!"); } else return(p);
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, green);
Serial.print("ID "); Serial.println(id);
p = finger.storeModel(id);
if (checkStatusIsOkay(p)) { Serial.println("Stored!"); } else return(p);
delay(4000);
finger.LEDcontrol(FINGERPRINT_LED_OFF, 0, white);
return true;
}
/// @brief check status is okay
/// the fingerprint function returns a status. If it is zero the
/// funktion returns ok. if > 0 then it is an error, whitch is printed
/// on the terminal and the function then returns false. Exept in case
/// of waiting for a finger to read the led is red for a while to indicate
/// the scan will not be saved.
/// @return status code
///
bool checkStatusIsOkay(int p, bool noprint) {
if (p == FINGERPRINT_OK) return true;
bool ln = col++ > 60 ;
if (!noprint)
switch (p) {
case FINGERPRINT_PACKETRECIEVEERR: Serial.println("Communication error"); break;
case FINGERPRINT_NOFINGER: Serial.print("."); if (ln) { Serial.println(); col=0;} return false;
case FINGERPRINT_IMAGEMESS: Serial.println("Image too messy"); break;
case FINGERPRINT_FEATUREFAIL: Serial.println("Could not find fingerprint features"); break;
case FINGERPRINT_NOMATCH: Serial.println("Finger does not match"); break;
case FINGERPRINT_NOTFOUND: Serial.println("Did not find a match"); break;
case FINGERPRINT_ENROLLMISMATCH: Serial.println("Fingerprints did not match"); break;
case FINGERPRINT_BADLOCATION: Serial.println("Could not store in that location"); break;
case FINGERPRINT_INVALIDIMAGE: Serial.println("Could not find fingerprint features"); break;
case FINGERPRINT_FLASHERR: Serial.println("Error writing to flash"); break;
default: Serial.println("Unknown error"); break;
} else {
if (p == FINGERPRINT_NOFINGER) return false;
}
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, red);
delay(1000);
finger.LEDcontrol(FINGERPRINT_LED_OFF, 0, white);
return false;
}
uint8_t getFingerprintID() {
uint8_t p =0;
p = finger.getImage();
if (checkStatusIsOkay(p, true)) { Serial.println("Image taken"); } else return p;
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, blue);
// OK success!
p = finger.image2Tz();
if (checkStatusIsOkay(p)) { Serial.println("Image converted"); } else return(p);
// OK converted!
p = finger.fingerSearch();
if (checkStatusIsOkay(p)) { Serial.println("Found a print match!"); } else return(p);
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, green);
// found a match!
Serial.print("Found ID #"); Serial.print(finger.fingerID);
Serial.print(" with confidence of "); Serial.println(finger.confidence);
delay(2000);
finger.LEDcontrol(FINGERPRINT_LED_OFF, 0, white);
return finger.fingerID;
}
// returns -1 if failed, otherwise returns ID #
int getFingerprintIDfast() {
uint8_t p = 0;
p = finger.getImage();
if (!checkStatusIsOkay(p)) return -1;
p = finger.image2Tz();
if (!checkStatusIsOkay(p)) return -1;
p = finger.fingerFastSearch();
if (!checkStatusIsOkay(p)) return -1;
// found a match!
Serial.print("Found ID #"); Serial.print(finger.fingerID);
Serial.print(" with confidence of "); Serial.println(finger.confidence);
return finger.fingerID;
}
/// @brief download fingerprint templae
///
///
///
///
/// @param id The id of the template to download
///
/// @return status code
///
uint8_t downloadFingerprintTemplate(uint16_t id) {
byte p = 0;
int idx = 0;
uint32_t starttime = 0;
Serial.println("------------------------------------");
Serial.print("Attempting to load #"); Serial.println(id);
p = finger.loadModel(id);
if (checkStatusIsOkay(p)) { Serial.print("Template "); Serial.print(id); Serial.println(" loaded"); } else return(p);
// OK success!
Serial.print("Attempting to get #"); Serial.println(id);
p = finger.getModel();
if (checkStatusIsOkay(p)) { Serial.print("Template "); Serial.print(id); Serial.println(" transferring"); } else return(p);
// one data packet is 267 bytes. in one data packet, 11 bytes are 'usesless' :D
uint8_t bytesReceived[534]; // 2 data packets
memset(bytesReceived, 0xff, 534);
starttime = millis();
while (idx < 534 && (millis() - starttime) < 20000) {
if (Serial1.available()) {
bytesReceived[idx++] = Serial1.read();
}
}
Serial.print(idx); Serial.println(" bytes read.");
Serial.println("Decoding packet...");
uint8_t fingerTemplate[512]; // the real template
memset(fingerTemplate, 0xff, 512);
// filtering only the data packets
int uindx = 9, index = 0;
memcpy(fingerTemplate + index, bytesReceived + uindx, 256); // first 256 bytes
uindx += 256; // skip data
uindx += 2; // skip checksum
uindx += 9; // skip next header
index += 256; // advance pointer
memcpy(fingerTemplate + index, bytesReceived + uindx, 256); // second 256 bytes
for (int i = 0; i < 512; ++i) {
//Serial.print("0x");
printHex(fingerTemplate[i], 2);
//Serial.print(", ");
}
Serial.println("\ndone.");
return p;
/*
uint8_t templateBuffer[256];
memset(templateBuffer, 0xff, 256); //zero out template buffer
int index=0;
uint32_t starttime = millis();
while ((index < 256) && ((millis() - starttime) < 1000))
{
if (mySerial.available())
{
templateBuffer[index] = mySerial.read();
index++;
}
}
Serial.print(index); Serial.println(" bytes read");
//dump entire templateBuffer. This prints out 16 lines of 16 bytes
for (int count= 0; count < 16; count++)
{
for (int i = 0; i < 16; i++)
{
Serial.print("0x");
Serial.print(templateBuffer[count*16+i], HEX);
Serial.print(", ");
}
Serial.println();
}*/
}
/// @brief get Fingerprint Enroll
///
///
///
///
///
void printHex(int num, int precision) {
char tmp[16];
char format[128];
sprintf(format, "%%.%dX", precision);
sprintf(tmp, format, num);
Serial.print(tmp);
}
C# USB
Auf der Windows Seite kommt die Komponente System.IO.Ports zur Anwendung. Dabei ist der Sender nicht weiter kompliziert, hingegen der Empfänger entpuppte sich etwas trickreich. Da es ganz normal ist einen Empfangshandler aufzusetzen wurde die Kommunikation etwas komplizierter.
Der Handler empfängt die Daten setzt sie zu Datenpakete und lädt sie in eine Datenqueue. Da der händler nicht weiss wann Daten eintreffen und wie gross sie sind, muss er selber feststellen wann ein Paket vollständig ist. Eine weitere Herausforderung ist, dass der Händler mehrmals aufgerufen werden kann. Dadurch ist ein Lock Mechanismus notwendig.
using System.IO.Ports;
namespace FipiSuite.Util {
///<header>
///
/// Project: HOBRO-1
/// Title: Home Brotbeck Automation
/// Author: Marc Brotbeck
/// EMail: marc@brotbeck.ch
/// Date: Nov 24
/// Environment: Visual Studio 2022 Community with C#
/// Companyname TIS technical informatic solution
/// Copyright MIT
///
/// Component: Fingerprint USP com port reader and writer
/// File FipiUSBService.cs
/// Part: Fingerprint Configuration
///
/// <readme>
/// Nothing yet
/// </readme>
///
/// <description>
///
/// </description>
///
/// <changeTags>
/// <!--
/// Wenn Änderungen am Code gemacht werden, dann solltest du es mit einem Tag vermerken,
/// damit andere sehen wo, was,wann und warum geändert wurde. Das Tag sollte für alle Programme und
/// Änderungen bis zum Release gelten. in der History schreibst du dann was die Änderung bezweckt.
/// -->
/// <tag name="MB-001"> Marc Brotbeck: go live </tag>
/// </changeTags>
///
/// <histories>
/// <history date="29.06.2024" version="V1.0-0" tag="MB-001" title="going live">
/// First release
/// </history>
/// </histories>
///
/// </header>
class FipiUSBServive {
const int MESLEN = 4096;
SerialPort BCPort ;
SemaphoreSlim semaphore = new SemaphoreSlim(1);
bool _initialized = false;
byte[] Message = new byte[MESLEN];
int Sidx = 0;
int Eidx = 0;
/// <summary>
/// ctor
/// </summary>
public FipiUSBServive( ) {
BCPort = new SerialPort(); // Create a new SerialPort Object
}
/// <summary>
/// Init Fipri USB:
/// setting up a usb com port, open it and declares. It must be called first,
/// after the ctor are initialized.
/// attaching the event handler to read from it.
/// The port setting RtS, Dtr and handshake is needed for not lost data when using arduino xiao rp2040
/// <note>you need to change the default paramter! </note>
/// </summary>
/// <returns>true if done</returns>
///
public bool InitFipriUSB(string? portName = "COM16", int baud =57600) {
try {
BCPort.PortName = portName;
BCPort.BaudRate = baud;
BCPort.Parity = Parity.None;
BCPort.DataBits = 8;
BCPort.Handshake = Handshake.RequestToSend;
BCPort.DtrEnable = true;
BCPort.RtsEnable = true;
BCPort.StopBits = StopBits.One;
BCPort.Open();
BCPort.DiscardInBuffer();
BCPort.DataReceived += new SerialDataReceivedEventHandler(ReceiveFipriStream);
_initialized = true;
} catch (Exception ex) {
throw new FingerprintException(Resource.ErorMsg.init_usb,ex);
}
return _initialized;
}
/// <summary>
/// Send Message:
/// Takes the message object and flatten to a bytestream. Then
/// sends th stream to the device. if the port is not avaiable an error info
/// is put in the answere queue.
/// </summary>
/// <param name="pack"></param>
public void SendMessage(DataPack pack) {
if (_initialized) {
byte[] cmd = Flatten(pack);
BCPort.Write(cmd, 0, cmd.Length);
} else {
MainWindow.FipriResponse.Add(LoadErrorPack(pack.Addr));
}
}
/// <summary>
/// Flatten:
/// Converts the DataPack to a byte array (using big endiam)
/// </summary>
/// <param name="pack"></param>
/// <returns></returns>
/// <exception cref="FingerprintException"></exception>
private byte[] Flatten(DataPack pack) {
try {
if (pack.Data == null) pack.Data = [];
int size = 9 + (pack.DatLen);
if (pack.Pid == 0x07) size++; // CMD has 1 byte more than data
int csum = 0;
byte[] arr = new byte[size];
HelperLib.ConvertToByteArray(ref arr, pack.Header);
HelperLib.ConvertToByteArray(ref arr, pack.Addr, 2);
int idx = 6;
arr[idx++] = pack.Pid;
HelperLib.ConvertToByteArray(ref arr, (UInt16)pack.DatLen, idx);
idx +=2;
for (int i = 0; i < pack.Data.Length; i++) arr[idx++] = pack.Data[i];
for (int i = 6; i < size - 2; i++) csum += arr[i];
HelperLib.ConvertCsumToByteArray(ref arr, csum, size - 2);
return arr;
} catch (Exception ex){
throw new FingerprintException(Resource.ErorMsg.convert_object,ex);
}
}
/// <summary>
/// Receive Fipri Stream:
/// Reads byte from the usb and appends it to the message buffer.
/// As soon the message buffer has the required length to get the expected response
/// length the incomming byte are collected until the required length is complete.
/// Then the response part is copied in the response array and then removed from
/// message buffert. Aftr that the response is assembled in the datapack and put
/// in the response queue for the corespondig fingerprint methode can check the
/// response. Note, the queue is a thread safe concurrent blocking queue!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ReceiveFipriStream(object sender, SerialDataReceivedEventArgs e) {
int readedByte;
int freeSpace;
int packLen;
byte[] responsArray; // the message for assembling the pack
if (sender is SerialPort port) {
try {
semaphore.Wait();
freeSpace = MESLEN - Eidx;
readedByte = port.Read(Message, Eidx, freeSpace);
Eidx += readedByte;
while (CheckforCompleteDataPack(out packLen)) {
responsArray = HelperLib.TakeFromHead(ref Message,packLen,Eidx);
Eidx -= packLen;
DataPack pack = LoadResponse(responsArray, packLen);
MainWindow.FipriResponse.Add(pack);
}
} catch (Exception ex) {
throw new FingerprintException(Resource.ErorMsg.read_response, ex);
} finally {
semaphore.Release();
}
}
}
/// <summary>
/// Check for Complete DataPack:
/// As soon there are data
/// 1. we remove the sensoor ready byte if exist
/// 2. make sure the SOH (start of Header) is the first byte
/// 3. we try to get the length of datapack
/// 4. evaluate the return
/// </summary>
/// <param name="packLength"> only a return value</param>
/// <returns> true if the message length is enough to evaluate the data</returns>
public bool CheckforCompleteDataPack(out int packLength) {
packLength = -1;
int len = Eidx - Sidx;
if (len > 0 && Message[Sidx] == 0x55) { Sidx++; len--; } // fipri ready byte removing
while (Message[Sidx] != 0xEF && Message[Sidx + 1] != 1 && len > 2) { Sidx++; }
if (len >= 12) {
packLength = (Message[Sidx+7] << 8) + Message[Sidx + 8] + 9; // lenH + lenL + Header + Addr + pid
}
return (len >= packLength && packLength >= 12 && Sidx < Eidx);
}
/// <summary>
/// Load Response:
/// if the reading bytes are complete the byte array is converted in a DataPack
/// object. the checksum is also compared at this point. But the error handling
/// is delegate to the corresponding methode that has initiate the response.
/// </summary>
/// <param name="arr"></param>
/// <param name="len"></param>
/// <returns> datapack data[0] = 128 if checksum is incorrect</returns>
private DataPack LoadResponse(byte[] arr, int len) {
DataPack pack = new();
int cSum = 0;
int idx = 9; // the data starts here with the confirmation code
if (len < 12) throw new FingerprintException(Resource.ErorMsg.response_short);
try {
pack = new() {
Header = (UInt16)((arr[0] << 8) + arr[1]),
Addr = (UInt32)((arr[2] << 24) + (arr[3] << 16) + (arr[4] << 8) + arr[5]),
DatLen = (UInt16)((arr[7] << 8) + arr[8]), // data + csum
Checksum = (UInt16)((arr[len - 2] << 8) + arr[len - 1]),
Pid = arr[6],
};
pack.Data = new byte[pack.DatLen - 2];
for (int i = 0; i < pack.DatLen - 2; i++) pack.Data[i] = arr[idx++];
for (int i = 0; i < pack.DatLen + 1; i++) cSum += arr[6 + i];
if (!HelperLib.CheckChecksum(cSum, pack.Checksum)) pack.Data[0] = 128;
} catch (Exception ex) {
string x = ex.Message;
}
return pack;
}
/// <summary>
/// Load Error Pack:
/// We send in the receive queue an error packet so the sender is not
/// blocked if we cannot send a message to the device.
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
private DataPack LoadErrorPack(UInt32 addr) {
DataPack pack = new() {
Header = 0xEF01,
Addr = addr,
DatLen = 3,
Checksum = 3+1+ 0xEE,
Pid = 0x01,
Data = [0xEA]
};
return pack;
}
}
}
Protokollbeschreibung
enum ColorCode BLACK, RED, BLUE, MAGENTA, GREEN, YELLOW, CYAN, WHITE }
enum FipriBrand DEFAULT, GROWN, HLK, WAVESHARE, DFROBOT, SEEED }
enum ControlCode BREATHING=1, FLASHING, LIGHT_ON, LIGHT_OFF, DIMM_ON, DIMM_OFF}
class Fingerprint
// parameter return
struct DeviceParameter {
UInt16 Status;
UInt16 RegNbrOfTemplate;
UInt16 Id;
UInt16 TemplateSize;
UInt16 LibSize;
UInt16 SecurityLevel;
UInt32 Address;
UInt16 PackSize;
int BaudRate;
int Success;
}
// Model information > Grown
struct DeviceModel {
string Model;
string BatchNr;
string SerialNr;
string HwVersion;
string SensorType;
int SensorWidth;
int SensorHeight;
int TemplateSize;
int DBSize;
int Success;
int ScoreLevel;
}
// Match result return
struct MatchResult {
int ResultCode
int TemplateID machted template
int MatchScore qulaity of match
}
struct VersionResult {
int ResultCode get; set; }
string Version get; set; }
}
struct DataResult {
int ResultCode get; set; }
byte[] Data get; set; }
int Length get; set; } // this is more or less superfluous
}
struct LastError {
byte Command;
string Function;
string Message;
string Kind;
int Code;
bool Valid;
}
Fingerprint C# Library
Die C# Fingerprint Library unterstützt mehr oder weniger alle Befehle die ein Fingerprint Sensor zur Verfügung stellt. Nicht implementiert sind die Automatik Mode. Die unterschiedlichen Fingerprint Sensoren erfüllen alle bloss die Grundfunktionen einheitlich. Parameter, Information, Images Template Charakteristics sind dann von Hersteller zu Hersteller unterschiedlich in der Handhabung oder Informationen.
Class Fingerprint(FipriBrand brand, UInt32 adr , UInt32 pw )
bool Initialize(string usbport, int baud = 57600)
int VerifyPassword(UInt32 pw)
int SetPassword(UInt32 pw)
int SetModuleAddress(UInt32 adr)
int SetModuleParameter(byte nr, byte val)
DeviceParameter GetModuleParameter()
int GetTemplateNumber()
DataResult GetTemplateTable(byte pageNr)
VersionResult GetAlgorithmLibrary()
VersionResult GetFirmwareVersion()
DeviceModel GetProductInfo(FipriBrand brand = FipriBrand.HLK)
int GetFingerprintImage()
int GetFingerprintImageEx()
int CancelCommand()
int Handshake()
int CheckSensor()
int SoftReset()
int GenerateTemplate()
int GenerateCharacterFile(byte id)
int ClearLibrary()
DataResult UploadFingerprintImage()
int DownloadFingerprintImage(byte[] img)
DataResult UploadTemplate()
int DownloadTemplate(byte[] template)
int StoreTemplate(byte buffer, UInt16 id)
int ReadTemplate(byte charId, UInt16 templateId)
int DeleteTemplate(UInt16 pageId, UInt16 nr)
int CheckMatch()
MatchResult SearchMatchInLibrary(byte charId, UInt16 startId, UInt16 nr)
DataResult ReadInfoPage()
DataResult ReadNotepad(byte pageNr)
int WriteNotepad(byte id, byte[] msg)
int AuraControlHLK(byte control, byte scolor, byte ecolor, byte repetition, string fun = "AuraControlHLK")
int AuraControl(byte control, byte speed, byte color, byte repetition, string fun = "AuraControl")
void AuraLedBreathing(ColorCode color, byte speed=50, byte repeat=0)
void AuraLedFlash(ColorCode color, byte speed = 50, byte repeat = 0)
void AuraLedGradualOn(ColorCode color, byte speed = 50, byte repeat = 0)
void AuraLedGradualOff(ColorCode color, byte speed = 50, byte repeat = 0)
int AuraLedOn(ColorCode color)
int AuraLedOff()
LastError GetLastError() return _lastError; }
Your content goes here. Edit or remove this text inline or in the module Content settings. You can also style every aspect of this content in the module Design settings and even apply custom CSS to this text in the module Advanced settings.
/**
* Eventlog view holder
* Remember! the names of binding.variables are generated from layout view
* if viewmodel building is enabled.
* @constructor
*
* @param binding
*/
class EventlogViewHolder(binding: ItemEventviewBinding) : RecyclerView.ViewHolder(binding.root) {
val viewRoot: LinearLayout? = binding.listEventItem
val viewEvImage: ImageView? = binding.imgEvSeverity
val viewEvNr: TextView? = binding.txtEvNr
val viewEvFacility: TextView? = binding.txtEvFacility
val viewEvMessage: TextView? = binding.txtEvMessage
val viewEvProgram: TextView? = binding.txtEvProgram
val viewEvTime: TextView? = binding.txtEvTime
}
}
üDateipfad
Der File Pfad wird verwendet um Fingerabdruck Bilder zu speichern oder auf den Device zu laden.
File extension:
- Fingerprint Bilder konvertiert => jpg
- Fingerprint Bilder unmodifiziert => bin
- Fingerprint Charakter => chr
- Fingerprint Template => tem
für das Verteilen von Fingerprints auf mehrere Sensoren eignet sich die Template Daei. Sie ist allerdings nicht auf unterschiedliche Sensortypen anwendbar da man nicht weiss wie die Daten organisiert sind. Es macht also Sinn, wenn man mehrere Zugänge hat, zB ein Burökomplex mit gesicherten Büro, eine einheitlichen Sensor zu verwenden und zentral zu verwalten.
Goodies
Challemges
- Image Upload:
Die Image werden komprimiert bevor sie hinaufgeladen werden, dh. ein Byte hat zwei Image bit mit 16 graustufen. Um sie anzuzeigen muss man sie expandieren. Das klappt bei den HLK Sensoren nicht.
– 101 Vielleicht hat jemand bessere Image Kenntnisse als ich und findet den Fehler? - DialogHost
Es kann nur ein DialogHost im XAML File definiert werden, darum hat Enroll und Finger Check kein eigenes Popup. Diese Einschränkung kann man umgehen wenn man mit Ressourcen arbeitet.
– 102 Vielleicht hat jemand Lust dies zu implementieren?
Protokoll Spezification
- Gnerell
Die Kommunikation Paketstruktur ist bei allen getesteten Sensoren gleich.
in den Grundfunktionen funktionieren alle Sensoren gleich. (Enroll, Verify Finger, Password, Address)
Die Datenstrukturen sind unterschiedlich. - Adafruit Library
Ich habe nur die Grundfunktioenn benutzt.
Sehr viel C++ Preprozessor Code, schreckliche Implementierung. Dokumentation mangelhaft. - Grow Spezfication
Gute Dokumentation, es funktioniert Viel. Guter Sensor.
Angaben über Statusword stimmen nicht oder funktioniert nicht (eg Image in Buffert) - HLK Speification
Kann man und muss man bei HI-Link anfordern. Klappt aber gut.
Die Information sind in der infopage (GetProductInfo(..)). Die Information sind verwirrend. Sieht so aus als die beiden getesteten Sensoren nicht vom gleichen Hersteller kommen.
beim einten kann ich das Bild lesen, beim anderen nicht. Es werden beim Upload zu viele Daten gesendet
192×192 => 36864. es sind dann einfach leere bytes.
Der Scorelevel ist hier viel ausgeprägter. Da müsste man meine Library Code noch etwas verbessern.
– 103 Vielleicht macht das jemand?
