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

Sensor Communication

.Der Sensor kommuniziert via serielle TT-Schnittstelle. USB oder RS232c ist etwas gefählrilich bezüglich der Spannung.

n

Fipri-Suite

Eine Windows 11 Applikation mit der man den Fingerprint Sensor testen und konfigurieren kann.

Protokoll

Das serielle Fingerprint Protokoll ist bei den einzelnen Fingerprint-Sensoren gleich, bzw. sagen wir ähnlich.
Also die Basis-Funktionen,

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

  1. Datum und Zeit
  2. Template Id, läuft wenn App nicht blockiert
  3.  Titel und Click für Setting Slider
  4.  Logo und Click für Logger Slider
  5.  Charakteristic Finger Image
  6. Fingerprint Image
  7. Image Left/Right Splitter
  8. Command Button round robin page loader (page 0-3)
  9. nr of loaded Page
  10. Image Buffer Selected (normal 1-2, additonal -6)
  11. File List bin-file  of defined Path
  12. Aura settings: first line mode, second on of color, black is off
  13. Blink speed slider to select 0-255 0> 0- 2.5 sec
  14. Fingerprint Quality (score) when verify
  15. Status bit of Status byte
  16. buttton start task for Verify Finger or Enroll Finger
  17. Information Window wenn task is running
  18. all posible Commands works only whit correct Sequence
  19.  Windows for Information /Notepad / flash memory
  20. Input field for enter Filename part etc => !!!! you need to click on some button for change focus to save
  21. Result of Information Command
  22. Result of Get Parameter Command
  23. Template Tabelle:
    1.  blau: ein Template ist vorhanden.
    2.  rot: enroll ein neues Template ersetzt ein altes
    3.  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?
Loading...