Files
Doorbell/FingerprintDoorbell/src/FingerprintManager.cpp
T
admin 379b4887c3 Update housing and code
--add local timezone
--add sunrise/sunset calculation and LED illumination accordingly
--remove touch sensor check
--change mqtt messages
2025-05-13 19:07:14 +02:00

574 lines
18 KiB
C++

#include "FingerprintManager.h"
#include "global.h"
#include <Adafruit_Fingerprint.h>
bool FingerprintManager::connect() {
// initialize input pins
pinMode(touchRingPin, INPUT_PULLDOWN);
Serial.println("\n\nAdafruit finger detect test");
// set the data rate for the sensor serial port
finger.begin(57600);
delay(50);
if (finger.verifyPassword()) {
Serial.println("Found fingerprint sensor!");
} else {
delay(5000); // wait a bit longer for sensor to start before 2nd try (usually after a OTA-Update the esp32 is faster with startup than the fingerprint sensor)
if (finger.verifyPassword()) {
Serial.println("Found fingerprint sensor!");
} else {
Serial.println("Did not find fingerprint sensor :(");
connected = false;
return connected;
}
}
finger.LEDcontrol(FINGERPRINT_LED_FLASHING, 25, FINGERPRINT_LED_BLUE, 0); // sensor connected signal
Serial.println(F("Reading sensor parameters"));
finger.getParameters();
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);
finger.getTemplateCount();
Serial.print("Sensor contains "); Serial.print(finger.templateCount); Serial.println(" templates");
loadFingerListFromPrefs();
connected = true;
return connected;
//updateTouchState(false);
}
void FingerprintManager::updateTouchState(bool touched)
{
if ((touched != lastTouchState) || (ignoreTouchRing != lastIgnoreTouchRing) || (night != lastNightMode)) {
// check if sensor or ring is touched
if (touched) {
// turn touch indicator on:
finger.LEDcontrol(FINGERPRINT_LED_FLASHING, 25, FINGERPRINT_LED_BLUE, 0);
} else {
// turn touch indicator off:
setLedRingReady();
}
}
lastTouchState = touched;
lastIgnoreTouchRing = ignoreTouchRing;
lastNightMode = night;
}
void FingerprintManager::setNightMode(bool state){
night = state;
}
bool FingerprintManager::isNightMode(void){
return night;
}
Match FingerprintManager::scanFingerprint() {
Match match;
match.scanResult = ScanResult::error;
if (!connected) {
return match;
}
// finger detection by capacitive touchRing state (increased sensitivy but error prone due to rain)
bool ringTouched = false;
if (!ignoreTouchRing)
{
if (isRingTouched())
ringTouched = true;
if (ringTouched || lastTouchState) {
updateTouchState(true);
//Serial.println("touched");
} else {
updateTouchState(false);
match.scanResult = ScanResult::noFinger;
return match;
}
}
bool doAnotherScan = true;
int scanPass = 0;
while (doAnotherScan)
{
doAnotherScan = false;
scanPass++;
///////////////////////////////////////////////////////////
// STEP 1: Get Image from Sensor
///////////////////////////////////////////////////////////
bool doImaging = true;
int imagingPass = 0;
while (doImaging)
{
doImaging = false;
imagingPass++;
//Serial.println(String("Get Image try ") + imagingPass);
match.returnCode = finger.getImage();
switch (match.returnCode) {
case FINGERPRINT_OK:
// Important: do net set touch state to true yet! Reason:
// - if touchRing is NOT ignored, updateTouchState(true) was already called a few lines up, ring is already flashing red
// - if touchRing IS ignored, wait for next step because image still can be "too messy" (=raindrop on sensor), and we don't want to flash red in this case
//updateTouchState(true);
//Serial.println("Image taken");
break;
case FINGERPRINT_NOFINGER:
case FINGERPRINT_PACKETRECIEVEERR: // occurs from time to time, handle it like a "nofinger detected but touched" situation
if (ringTouched) {
// no finger on sensor but ring was touched -> ring event
//Serial.println("ring touched");
updateTouchState(true);
if (imagingPass < 15) // up to x image passes in a row are taken after touch ring was touched until noFinger will raise a noMatchFound event
{
doImaging = true; // scan another image
//delay(50);
break;
} else {
//Serial.println("15 times no image after touching ring");
match.scanResult = ScanResult::noMatchFound;
return match;
}
} else {
if (ignoreTouchRing && scanPass > 1) {
// the scan(s) in last iteration(s) have not found any match, now the finger was released (=no finger) -> return "no match" as result
match.scanResult = ScanResult::noMatchFound;
} else {
match.scanResult = ScanResult::noFinger;
updateTouchState(false);
}
return match;
}
case FINGERPRINT_IMAGEFAIL:
Serial.println("Imaging error");
updateTouchState(true);
return match;
default:
Serial.println("Unknown error");
return match;
}
}
///////////////////////////////////////////////////////////
// STEP 2: Convert Image to feature map
///////////////////////////////////////////////////////////
match.returnCode = finger.image2Tz();
switch (match.returnCode) {
case FINGERPRINT_OK:
//Serial.println("Image converted");
updateTouchState(true);
break;
case FINGERPRINT_IMAGEMESS:
Serial.println("Image too messy");
return match;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.println("Communication error");
return match;
case FINGERPRINT_FEATUREFAIL:
Serial.println("Could not find fingerprint features");
return match;
case FINGERPRINT_INVALIDIMAGE:
Serial.println("Could not find fingerprint features");
return match;
default:
Serial.println("Unknown error");
return match;
}
///////////////////////////////////////////////////////////
// STEP 3: Search DB for matching features
///////////////////////////////////////////////////////////
match.returnCode = finger.fingerSearch();
if (match.returnCode == FINGERPRINT_OK) {
// found a match!
//setLedRingOk();
match.scanResult = ScanResult::matchFound;
match.matchId = finger.fingerID;
match.matchConfidence = finger.confidence;
match.matchName = fingerList[finger.fingerID];
} else if (match.returnCode == FINGERPRINT_PACKETRECIEVEERR) {
Serial.println("Communication error");
} else if (match.returnCode == FINGERPRINT_NOTFOUND) {
Serial.println(String("Did not find a match. (Scan #") + scanPass + String(" of 5)"));
match.scanResult = ScanResult::noMatchFound;
if (scanPass < 5) // max 5 Scans until no match found is given back as result
doAnotherScan = true;
} else {
Serial.println("Unknown error");
}
} //while
return match;
}
// Preferences
void FingerprintManager::loadFingerListFromPrefs() {
Preferences preferences;
preferences.begin("fingerList", true);
int counter = 0;
for (int i=1; i<=200; i++) {
String key = String(i);
if (preferences.isKey(key.c_str())) {
fingerList[i] = preferences.getString(key.c_str(), String("@empty"));
counter++;
}
else
fingerList[i] = String("@empty");
}
Serial.println(String(counter) + " fingers loaded from preferences.");
if (counter != finger.templateCount)
notifyClients(String("Warning: Fingerprint count mismatch! ") + finger.templateCount + " fingerprints stored on sensor, but we are aware of " + counter + " fingerprints.");
preferences.end();
}
// Add/Enroll fingerprint
NewFinger FingerprintManager::enrollFinger(int id, String name) {
NewFinger newFinger;
newFinger.enrollResult = EnrollResult::error;
lastTouchState = true; // after enrollment, scan mode kicks in again. Force update of the ring light back to normal on first iteration of scan mode.
notifyClients(String("Enrollment for id #") + id + " started. We need to scan your finger 5 times until enrollment is completed.");
// Repeat n times to get better resulting templates (as stated in R503 documentation up to 6 combined image samples possible, but I got an communication error when trying more than 5 samples, so dont go >5)
for (int nTimes=1; nTimes<=5; nTimes++)
{
notifyClients(String("Take #" + String(nTimes))+ " (place your finger on the sensor until led ring stops flashing, then remove it).");
if (nTimes != 1) // not on first run
{
//delay(2000);
newFinger.returnCode = 0xFF;
while (newFinger.returnCode != FINGERPRINT_NOFINGER) {
newFinger.returnCode = finger.getImage();
}
}
Serial.print("Taking image sample "); Serial.print(nTimes); Serial.print(": ");
finger.LEDcontrol(FINGERPRINT_LED_FLASHING, 25, FINGERPRINT_LED_PURPLE, 0);
newFinger.returnCode = 0xFF;
while (newFinger.returnCode != FINGERPRINT_OK) {
newFinger.returnCode = finger.getImage();
switch (newFinger.returnCode) {
case FINGERPRINT_OK:
Serial.print("taken, ");
break;
case FINGERPRINT_NOFINGER:
break;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.print("Communication error, ");
break;
case FINGERPRINT_IMAGEFAIL:
Serial.print("Imaging error, ");
break;
default:
Serial.print("Unknown error, ");
break;
}
}
// OK success!
newFinger.returnCode = finger.image2Tz(nTimes);
switch (newFinger.returnCode) {
case FINGERPRINT_OK:
Serial.print("converted");
break;
case FINGERPRINT_IMAGEMESS:
Serial.print("too messy");
return newFinger;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.print("Communication error");
return newFinger;
case FINGERPRINT_FEATUREFAIL:
Serial.print("Could not find fingerprint features");
return newFinger;
case FINGERPRINT_INVALIDIMAGE:
Serial.print("Could not find fingerprint features");
return newFinger;
default:
Serial.print("Unknown error");
return newFinger;
}
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, FINGERPRINT_LED_PURPLE);
}
// OK converted!
Serial.println();
Serial.print("Creating model for #"); Serial.println(id);
newFinger.returnCode = finger.createModel();
if (newFinger.returnCode == FINGERPRINT_OK) {
Serial.println("Prints matched!");
} else if (newFinger.returnCode == FINGERPRINT_PACKETRECIEVEERR) {
Serial.println("Communication error");
return newFinger;
} else if (newFinger.returnCode == FINGERPRINT_ENROLLMISMATCH) {
Serial.println("Fingerprints did not match");
return newFinger;
} else {
Serial.println("Unknown error");
return newFinger;
}
Serial.print("ID "); Serial.println(id);
newFinger.returnCode = finger.storeModel(id);
if (newFinger.returnCode == FINGERPRINT_OK) {
Serial.println("Stored!");
newFinger.enrollResult = EnrollResult::ok;
// save to prefs
fingerList[id] = name;
Preferences preferences;
preferences.begin("fingerList", false);
preferences.putString(String(id).c_str(), name);
preferences.end();
} else if (newFinger.returnCode == FINGERPRINT_PACKETRECIEVEERR) {
Serial.println("Communication error");
return newFinger;
} else if (newFinger.returnCode == FINGERPRINT_BADLOCATION) {
Serial.println("Could not store in that location");
return newFinger;
} else if (newFinger.returnCode == FINGERPRINT_FLASHERR) {
Serial.println("Error writing to flash");
return newFinger;
} else {
Serial.println("Unknown error");
return newFinger;
}
//finger.LEDcontrol(FINGERPRINT_LED_OFF, 0, FINGERPRINT_LED_RED);
return newFinger;
}
void FingerprintManager::deleteFinger(int id) {
if ((id > 0) && (id <= 200)) {
int8_t result = finger.deleteModel(id);
if (result != FINGERPRINT_OK) {
notifyClients(String("Delete of finger template #") + id + " from sensor failed with code " + result);
return;
} else {
fingerList[id] = "@empty";
Preferences preferences;
preferences.begin("fingerList", false);
preferences.remove (String(id).c_str());
preferences.end();
Serial.println(String("Finger template #") + id + " deleted from sensor and prefs.");
}
}
}
void FingerprintManager::renameFinger(int id, String newName) {
if ((id > 0) && (id <= 200)) {
Preferences preferences;
preferences.begin("fingerList", false);
preferences.putString(String(id).c_str(), newName);
preferences.end();
Serial.println(String("Finger template #") + id + " renamed from " + fingerList[id] + " to " + newName);
fingerList[id] = newName;
}
}
String FingerprintManager::getFingerListAsHtmlOptionList() {
String htmlOptions = "";
int counter = 0;
for (int i=1; i<=200; i++) {
if (fingerList[i].compareTo("@empty") != 0) {
String option;
if (counter == 0)
option = "<option value=\"" + String(i) + "\" selected>" + String(i) + " - " + fingerList[i] + "</option>";
else
option = "<option value=\"" + String(i) + "\">" + String(i) + " - " + fingerList[i] + "</option>";
htmlOptions += option;
counter++;
}
}
return htmlOptions;
}
void FingerprintManager::setIgnoreTouchRing(bool state) {
if (ignoreTouchRing != state) {
ignoreTouchRing = state;
if (state == true)
notifyClients("IgnoreTouchRing is now 'on'");
else
notifyClients("IgnoreTouchRing is now 'off'");
}
}
bool FingerprintManager::isRingTouched() {
if (digitalRead(touchRingPin) == LOW) // LOW = touched. Caution: touchSignal on this pin occour only once (at beginning of touching the ring, not every iteration if you keep your finger on the ring)
return true;
else
return false;
}
bool FingerprintManager::isFingerOnSensor() {
// get an image
uint8_t returnCode = finger.getImage();
if (returnCode == FINGERPRINT_OK) {
// try to find fingerprint features in image, because image taken does not already means finger on sensor, could also be a raindrop
returnCode = finger.image2Tz();
if (returnCode == FINGERPRINT_OK)
return true;
}
return false;
}
void FingerprintManager::setLedRingError() {
finger.LEDcontrol(FINGERPRINT_LED_ON, 0, FINGERPRINT_LED_RED);
}
void FingerprintManager::setLedRingWifiConfig() {
finger.LEDcontrol(FINGERPRINT_LED_BREATHING, 250, FINGERPRINT_LED_RED);
}
void FingerprintManager::setLedRingReady() {
if(night){
if (!ignoreTouchRing)
finger.LEDcontrol(FINGERPRINT_LED_OFF, 250, FINGERPRINT_LED_BLUE);
else
finger.LEDcontrol(FINGERPRINT_LED_ON, 55, FINGERPRINT_LED_BLUE); // just an indicator for me to see if touch ring is active or not
}else{
finger.LEDcontrol(FINGERPRINT_LED_OFF, 250, FINGERPRINT_LED_BLUE);
}
}
void FingerprintManager::setLedRingOk() {
finger.LEDcontrol(FINGERPRINT_LED_BREATHING, 250, 0x04);
}
bool FingerprintManager::deleteAll() {
if (finger.emptyDatabase() == FINGERPRINT_OK)
{
bool rc;
Preferences preferences;
rc = preferences.begin("fingerList", false);
if (rc)
rc = preferences.clear();
preferences.end();
for (int i=1; i<=200; i++) {
fingerList[i] = String("@empty");
};
return rc;
}
else
return false;
}
uint8_t FingerprintManager::writeNotepad(uint8_t pageNumber, const char *text, uint8_t length) {
uint8_t data[34];
if (length>32)
length = 32;
data[0] = FINGERPRINT_WRITENOTEPAD;
data[1] = pageNumber;
for (int i=0; i<length; i++)
data[i+2] = text[i];
Adafruit_Fingerprint_Packet packet(FINGERPRINT_COMMANDPACKET, sizeof(data), data);
finger.writeStructuredPacket(packet);
if (finger.getStructuredPacket(&packet) != FINGERPRINT_OK)
return FINGERPRINT_PACKETRECIEVEERR;
if (packet.type != FINGERPRINT_ACKPACKET)
return FINGERPRINT_PACKETRECIEVEERR;
return packet.data[0];
}
uint8_t FingerprintManager::readNotepad(uint8_t pageNumber, char *text, uint8_t length) {
uint8_t data[2];
data[0] = FINGERPRINT_READNOTEPAD;
data[1] = pageNumber;
Adafruit_Fingerprint_Packet packet(FINGERPRINT_COMMANDPACKET, sizeof(data), data);
finger.writeStructuredPacket(packet);
if (finger.getStructuredPacket(&packet) != FINGERPRINT_OK)
return FINGERPRINT_PACKETRECIEVEERR;
if (packet.type != FINGERPRINT_ACKPACKET)
return FINGERPRINT_PACKETRECIEVEERR;
if (packet.data[0] == FINGERPRINT_OK) {
// read data payload
for (uint8_t i=0; i<length; i++) {
text[i] = packet.data[i+1];
}
}
return packet.data[0];
}
String FingerprintManager::getPairingCode() {
char buffer[33];
buffer[32] = 0; // null termination needed for convertion to string at the end
if (readNotepad(0, (char*)buffer, 32) == FINGERPRINT_OK)
return String((char*)buffer);
else
return "";
}
bool FingerprintManager::setPairingCode(String pairingCode) {
if (writeNotepad(0, pairingCode.c_str(), 32) == FINGERPRINT_OK)
return true;
else
return false;
}
// ToDo: support sensor replacement by enable transferring of sensor DB to another sensor
void FingerprintManager::exportSensorDB() {
}
void FingerprintManager::importSensorDB() {
}