From 356d7de2cfbe325322d0f4d10cd44494025b64c4 Mon Sep 17 00:00:00 2001 From: BotoX Date: Tue, 6 Jun 2023 19:34:45 +0200 Subject: [PATCH] first commit --- Makefile | 9 + app/FingerLogic.cpp | 598 +++++++++++++++++++++++++++++++++++ app/FingerLogic.h | 105 ++++++ app/FingerPrint.cpp | 267 ++++++++++++++++ app/FingerPrint.h | 163 ++++++++++ app/FingerPrint_API.cpp | 101 ++++++ app/FingerPrint_AsyncAPI.cpp | 510 +++++++++++++++++++++++++++++ app/Settings.cpp | 118 +++++++ app/Settings.h | 38 +++ app/application.cpp | 54 ++++ app/main.cpp | 462 +++++++++++++++++++++++++++ app/main.h | 65 ++++ app/utils.cpp | 75 +++++ app/utils.h | 8 + component.mk | 4 + files/.gitkeep | 0 files/bootstrap.min.css.gz | Bin 0 -> 18466 bytes files/bootstrap.min.js.gz | Bin 0 -> 12913 bytes files/favicon.ico | Bin 0 -> 1150 bytes files/index.html | 25 ++ files/jquery-3.2.1.min.js.gz | Bin 0 -> 29995 bytes files/main.css.gz | Bin 0 -> 653 bytes files/main.js.gz | Bin 0 -> 742 bytes files/nunjucks.min.js.gz | Bin 0 -> 22603 bytes files/popper.min.js.gz | Bin 0 -> 6963 bytes files/settings.conf | 25 ++ files/templates.js.gz | Bin 0 -> 4065 bytes files/wifi-sprites.png | Bin 0 -> 1815 bytes out/.gitignore | 2 + 29 files changed, 2629 insertions(+) create mode 100644 Makefile create mode 100644 app/FingerLogic.cpp create mode 100644 app/FingerLogic.h create mode 100644 app/FingerPrint.cpp create mode 100644 app/FingerPrint.h create mode 100644 app/FingerPrint_API.cpp create mode 100644 app/FingerPrint_AsyncAPI.cpp create mode 100644 app/Settings.cpp create mode 100644 app/Settings.h create mode 100644 app/application.cpp create mode 100644 app/main.cpp create mode 100644 app/main.h create mode 100644 app/utils.cpp create mode 100644 app/utils.h create mode 100644 component.mk create mode 100644 files/.gitkeep create mode 100644 files/bootstrap.min.css.gz create mode 100644 files/bootstrap.min.js.gz create mode 100644 files/favicon.ico create mode 100644 files/index.html create mode 100644 files/jquery-3.2.1.min.js.gz create mode 100644 files/main.css.gz create mode 100644 files/main.js.gz create mode 100644 files/nunjucks.min.js.gz create mode 100644 files/popper.min.js.gz create mode 100644 files/settings.conf create mode 100644 files/templates.js.gz create mode 100644 files/wifi-sprites.png create mode 100644 out/.gitignore diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ff51b6c --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +##################################################################### +#### Please don't change this file. Use component.mk instead #### +##################################################################### + +ifndef SMING_HOME +$(error SMING_HOME is not set: please configure it as an environment variable) +endif + +include $(SMING_HOME)/project.mk diff --git a/app/FingerLogic.cpp b/app/FingerLogic.cpp new file mode 100644 index 0000000..c0ac909 --- /dev/null +++ b/app/FingerLogic.cpp @@ -0,0 +1,598 @@ +#include +#include +#include "utils.h" +#include "main.h" +#include "FingerPrint.h" +#include "FingerLogic.h" + +CFingerLogic::CFingerLogic(CMain *pMain) +{ + m_pFingerPrint = NULL; + m_State = STATE_INITIAL; + m_Finger = false; + m_Power = false; + m_pMain = pMain; + + memset(&m_FingerParams, 0, sizeof(m_FingerParams)); + memset(m_aFingerSlots, 0, sizeof(m_aFingerSlots)); + m_FingerSlotsAdded = 0; + + m_PowerOffTimer.initializeMs(1000, TimerDelegate(&CFingerLogic::PowerOff, this)); +} + +void CFingerLogic::Init(CFingerPrint *pFingerPrint) +{ + m_pFingerPrint = pFingerPrint; + + InitFinger(); +} + +void CFingerLogic::SetState(FingerLogicState state) +{ + m_State = state; + + if(m_State == STATE_READY || m_State == STATE_ERROR) + m_PowerOffTimer.start(false); + else + { + m_PowerOffTimer.stop(); + PowerOn(); + } +} + +void CFingerLogic::PowerOff() +{ + if(!m_Power) + return; + + debugf("PowerOff()"); + m_Power = false; + Main().FingerEnable(false); + wifi_set_sleep_type(LIGHT_SLEEP_T); + system_soft_wdt_feed(); +} + +void CFingerLogic::PowerOn() +{ + if(m_Power) + return; + + debugf("PowerOn()"); + m_Power = true; + Main().FingerEnable(true); + wifi_set_sleep_type(MODEM_SLEEP_T); + delayMilliseconds(100); +} + +void CFingerLogic::OnFingerInterrupt(bool finger) +{ + debugf("OnFingerInterrupt: %s", finger ? "DOWN" : "UP"); + m_Finger = finger; + + if(finger) + { + if(m_State == STATE_READY) + VerifyFinger(); + else if(m_State == STATE_ENROLL_WAITFINGER1 || m_State == STATE_ENROLL_WAITFINGER2) + EnrollFinger_OnFinger(); + } +} + +bool CFingerLogic::FingerSlot(uint16_t index) +{ + if(index > m_FingerSlotsAdded) + return false; + + return m_aFingerSlots[index / 8] & (1 << (index % 8)); +} + +bool CFingerLogic::FingerSlot(uint16_t index, bool value) +{ + if(index > m_FingerSlotsAdded) + return false; + + if(value) + m_aFingerSlots[index / 8] |= (1 << (index % 8)); + else + m_aFingerSlots[index / 8] &= ~(1 << (index % 8)); + return true; +} + +int CFingerLogic::FirstFreeFingerSlot() +{ + for(int i = 0; i < m_FingerSlotsAdded / 8; i++) + { + if(m_aFingerSlots[i] != 0xFF) + { + uint8_t val = m_aFingerSlots[i]; + for(int j = 0; j < 8; j++) + { + if(!(val & (1 << j))) + return (i * 8) + j; + } + } + } + + return -1; +} + + +void CFingerLogic::InitFinger() +{ + if(m_State > STATE_INIT_VERIFYPASSWORD) + return; + + SetState(STATE_INIT_VERIFYPASSWORD); + FingerPrint().AsyncVerifyPassword((VerifyPasswordCallback)InitFinger_OnVerifyPassword, this); + + m_Timer.initializeMs(100, TimerDelegate(&CFingerLogic::InitFinger, this)).start(); +} + +void CFingerLogic::InitFinger_OnVerifyPassword(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + pThis->m_Timer.stop(); + debugf("InitFinger_OnVerifyPassword: (%d) %s", error, errorStr); + + if(error == ERROR_OK) + { + pThis->SetState(STATE_INIT_READSYSTEMPARAMETERS); + pThis->FingerPrint().AsyncReadSystemParameters((ReadSystemParametersCallback)InitFinger_OnReadSystemParameters, pThis); + } + else + pThis->SetState(STATE_ERROR); +} + +void CFingerLogic::InitFinger_OnReadSystemParameters(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, CFingerSystemParameters *param) +{ + debugf("InitFinger_OnReadSystemParameters: (%d) %s", error, errorStr); + + if(error == ERROR_OK) + { + memcpy(&pThis->m_FingerParams, param, sizeof(CFingerSystemParameters)); + debugf("statusRegister: %d", param->statusRegister); + debugf("systemID: %d", param->systemID); + debugf("storageCapacity: %d", param->storageCapacity); + debugf("securityLevel: %d", param->securityLevel); + debugf("deviceAddress: %X", param->deviceAddress); + debugf("packetLength: %d", param->packetLength); + debugf("baudRate: %d", param->baudRate); + + pThis->SetState(STATE_INIT_READTEMPLATEMAP); + pThis->FingerPrint().AsyncReadTemplateMap((ReadTemplateMapCallback)InitFinger_OnReadTemplateMap, pThis, 0); + } + else + pThis->SetState(STATE_ERROR); +} + +void CFingerLogic::InitFinger_OnReadTemplateMap(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, uint8_t *pData, uint16_t dataLen) +{ + debugf("InitFinger_OnReadTemplateMap: (%d) %s (%p, %d)", error, errorStr, pData, dataLen); + + if(error == ERROR_OK) + { + memcpy(&pThis->m_aFingerSlots[pThis->m_FingerSlotsAdded/8], pData, dataLen); + pThis->m_FingerSlotsAdded += dataLen * 8; + + if(pThis->m_FingerSlotsAdded < pThis->m_FingerParams.storageCapacity) + { + uint8_t page = pThis->m_FingerSlotsAdded / 8 / dataLen; + pThis->FingerPrint().AsyncReadTemplateMap((ReadTemplateMapCallback)InitFinger_OnReadTemplateMap, pThis, page); + } + else + { + pThis->InitFinger_VerifyTemplates(); + } + } +} + +void CFingerLogic::InitFinger_VerifyTemplates() +{ + SetState(STATE_INIT_VERIFYTEMPLATES); + + HashMap &fingerMap = Main().Settings().m_FingerPrints; + uint16_t fingerCount = fingerMap.count(); + + // Check consistency (1) + for(uint16_t i = 0; i < fingerCount; i++) + { + uint16_t id = fingerMap.keyAt(i); + if(!FingerSlot(id)) + { + SetState(STATE_ERROR); + FingerPrint().EmptyDatabase(); + fingerMap.clear(); + Main().Settings().Save(); + debugf("InitFinger_VerifyTemplates: INCONSITENCY(1) AT SLOT %d !!!", id); + return; + } + } + + // Check consistency (2) + for(uint16_t id = 0; id < m_FingerSlotsAdded; id++) + { + if(FingerSlot(id) && !fingerMap.contains(id)) + { + SetState(STATE_ERROR); + FingerPrint().EmptyDatabase(); + fingerMap.clear(); + Main().Settings().Save(); + debugf("InitFinger_VerifyTemplates: INCONSITENCY(2) AT SLOT %d !!!", id); + return; + } + } + + if(!fingerCount) + { + SetState(STATE_READY); + return; + } + + m_iBuffer = 0; + uint16_t position = fingerMap.keyAt(m_iBuffer); + FingerPrint().AsyncLoadTemplate((LoadTemplateCallback)InitFinger_OnLoadTemplate, this, position, 0x01); +} + +void CFingerLogic::InitFinger_OnLoadTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("InitFinger_OnLoadTemplate: (%d) %s", error, errorStr); + + if(error == ERROR_OK) + { + pThis->FingerPrint().AsyncDownloadCharacteristics((DownloadCharacteristicsCallback)InitFinger_OnDownloadCharacteristics, pThis, 0x01); + } + else + pThis->SetState(STATE_ERROR); +} + +void CFingerLogic::InitFinger_OnDownloadCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int8_t *pChar, uint16_t charLen) +{ + debugf("InitFinger_OnDownloadCharacteristics: (%d) %s (%p, %d)", error, errorStr, pChar, charLen); + + if(error == ERROR_OK) + { + Crypto::Sha256 ctx; + ctx.update(pChar, charLen); + uint8_t *digest = ctx.getHash().data(); + + const CSettings::CFingerPrint &finger = pThis->Main().Settings().m_FingerPrints.valueAt(pThis->m_iBuffer); + + char aHexDigest1[SHA256_SIZE*2+1]; + char aHexDigest2[SHA256_SIZE*2+1]; + bytes2hex(digest, sizeof(digest), aHexDigest1, sizeof(aHexDigest1)); + bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest2, sizeof(aHexDigest2)); + + debugf("Index: %d -> Num: %d \"%s\" (%s ?= %s)", pThis->m_iBuffer, finger.m_FingerNum, finger.m_aLabel, aHexDigest1, aHexDigest2); + + if(memcmp(digest, finger.m_aDigest, SHA256_SIZE) != 0) + { + pThis->SetState(STATE_ERROR); + debugf("InitFinger_VerifyTemplates: DIVERGENT DIGEST AT SLOT %d !!!", pThis->m_iBuffer); + return; + } + + pThis->m_iBuffer++; + if(pThis->m_iBuffer >= pThis->Main().Settings().m_FingerPrints.count()) + { + debugf("InitFinger_VerifyTemplates: DONE! Verified %d templates.", pThis->m_iBuffer); + pThis->SetState(STATE_READY); + return; + } + + uint16_t position = pThis->Main().Settings().m_FingerPrints.keyAt(pThis->m_iBuffer); + pThis->FingerPrint().AsyncLoadTemplate((LoadTemplateCallback)InitFinger_OnLoadTemplate, pThis, position, 0x01); + } + else + pThis->SetState(STATE_ERROR); +} + + +void CFingerLogic::VerifyFinger() +{ + if(m_State != STATE_READY) + return; + + SetState(STATE_VERIFY_READIMAGE); + FingerPrint().AsyncReadImage((ReadImageCallback)VerifyFinger_OnReadImage, this); +} + +void CFingerLogic::VerifyFinger_OnReadImage(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("VerifyFinger_OnReadImage: (%d) %s", error, errorStr); + + if(error == ERROR_OK) + { + pThis->SetState(STATE_VERIFY_CONVERTIMAGE); + pThis->FingerPrint().AsyncConvertImage((ConvertImageCallback)VerifyFinger_OnConvertImage, pThis, 0x01); + } + else + { + pThis->SetState(STATE_READY); + if(pThis->m_Finger) + pThis->VerifyFinger(); + } +} + +void CFingerLogic::VerifyFinger_OnConvertImage(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("VerifyFinger_OnConvertImage: (%d) %s", error, errorStr); + + if(error == ERROR_OK) + { + pThis->SetState(STATE_VERIFY_SEARCHTEMPLATE); + pThis->FingerPrint().AsyncSearchTemplate((SearchTemplateCallback)VerifyFinger_OnSearchTemplate, pThis, 0x01, 0, pThis->m_FingerParams.storageCapacity); + } + else + { + pThis->SetState(STATE_READY); + if(pThis->m_Finger) + pThis->VerifyFinger(); + } +} + +void CFingerLogic::VerifyFinger_OnSearchTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int16_t position, int16_t score) +{ + debugf("VerifyFinger_OnSearchTemplate: (%d) %s (%d, %d)", error, errorStr, position, score); + + if(error == ERROR_OK) + { + pThis->m_iBuffer = position; + pThis->SetState(STATE_VERIFY_LOADTEMPLATE); + pThis->FingerPrint().AsyncLoadTemplate((LoadTemplateCallback)VerifyFinger_OnLoadTemplate, pThis, position, 0x01); + } + else + pThis->SetState(STATE_READY); +} + +void CFingerLogic::VerifyFinger_OnLoadTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("VerifyFinger_OnLoadTemplate: (%d) %s", error, errorStr); + + if(error == ERROR_OK) + { + pThis->SetState(STATE_VERIFY_DOWNLOADCHARACTERISTICS); + pThis->FingerPrint().AsyncDownloadCharacteristics((DownloadCharacteristicsCallback)VerifyFinger_OnDownloadCharacteristics, pThis, 0x01); + } + else + pThis->SetState(STATE_READY); +} + +void CFingerLogic::VerifyFinger_OnDownloadCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int8_t *pChar, uint16_t charLen) +{ + debugf("VerifyFinger_OnDownloadCharacteristics: (%d) %s (%p, %d)", error, errorStr, pChar, charLen); + + if(error == ERROR_OK) + { + Crypto::Sha256 ctx; + ctx.update(pChar, charLen); + uint8_t *digest = ctx.getHash().data(); + + char aHexDigest[SHA256_SIZE*2+1]; + bytes2hex(digest, sizeof(digest), aHexDigest, sizeof(aHexDigest)); + + debugf("Finger hexdigest: %s", aHexDigest); + + pThis->Main().OnFingerVerified(pThis->m_iBuffer, digest); + } + + pThis->SetState(STATE_READY); +} + + +bool CFingerLogic::EnrollFinger(bool cancel) +{ + if(cancel) + { + if(m_State < STATE_ENROLL_WAITFINGER1 || m_State > STATE_ENROLL_DOWNLOADCHARACTERISTICS) + return false; + + SetState(STATE_READY); + return true; + } + + if(m_State != STATE_READY) + return false; + + SetState(STATE_ENROLL_WAITFINGER1); + if(m_Finger) + EnrollFinger_OnFinger(); + + return true; +} + +void CFingerLogic::EnrollFinger_OnFinger() +{ + if(m_State == STATE_ENROLL_WAITFINGER1) + { + SetState(STATE_ENROLL_READIMAGE1); + FingerPrint().AsyncReadImage((ReadImageCallback)EnrollFinger_OnReadImage1, this); + } + else if(m_State == STATE_ENROLL_WAITFINGER2) + { + SetState(STATE_ENROLL_READIMAGE2); + FingerPrint().AsyncReadImage((ReadImageCallback)EnrollFinger_OnReadImage2, this); + } +} + +void CFingerLogic::EnrollFinger_OnReadImage1(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("EnrollFinger_OnReadImage1: (%d) %s", error, errorStr); + if(pThis->m_State != STATE_ENROLL_READIMAGE1) return; + + if(error == ERROR_OK) + { + pThis->SetState(STATE_ENROLL_CONVERTIMAGE1); + pThis->FingerPrint().AsyncConvertImage((ConvertImageCallback)EnrollFinger_OnConvertImage1, pThis, 0x01); + } + else + { + pThis->SetState(STATE_ENROLL_WAITFINGER1); + char aBuf[512]; + m_snprintf(aBuf, sizeof(aBuf), "Error while scanning finger: %s
Lift your finger and try again.", errorStr); + pThis->Main().EnrollMessage(aBuf); + } +} + +void CFingerLogic::EnrollFinger_OnConvertImage1(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("EnrollFinger_OnConvertImage1: (%d) %s", error, errorStr); + if(pThis->m_State != STATE_ENROLL_CONVERTIMAGE1) return; + + if(error == ERROR_OK) + { + pThis->SetState(STATE_ENROLL_SEARCHTEMPLATE); + pThis->FingerPrint().AsyncSearchTemplate((SearchTemplateCallback)EnrollFinger_OnSearchTemplate, pThis, 0x01, 0, pThis->m_FingerParams.storageCapacity); + } + else + { + pThis->SetState(STATE_ENROLL_WAITFINGER1); + char aBuf[512]; + m_snprintf(aBuf, sizeof(aBuf), "Error while analyzing finger: %s
Lift your finger and try again.", errorStr); + pThis->Main().EnrollMessage(aBuf); + } +} + +void CFingerLogic::EnrollFinger_OnSearchTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int16_t position, int16_t score) +{ + debugf("EnrollFinger_OnSearchTemplate: (%d) %s (%d, %d)", error, errorStr, position, score); + if(pThis->m_State != STATE_ENROLL_SEARCHTEMPLATE) return; + + if(error == ERROR_OK) + { + pThis->SetState(STATE_READY); + pThis->Main().EnrollMessage("Aborting: This finger is already enrolled!", true); + } + else + { + pThis->SetState(STATE_ENROLL_WAITFINGER2); + pThis->Main().EnrollMessage("Finger scanned. Lift finger and place it on the sensor again to enroll."); + } +} + +void CFingerLogic::EnrollFinger_OnReadImage2(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("EnrollFinger_OnReadImage2: (%d) %s", error, errorStr); + if(pThis->m_State != STATE_ENROLL_READIMAGE2) return; + + if(error == ERROR_OK) + { + pThis->SetState(STATE_ENROLL_CONVERTIMAGE2); + pThis->FingerPrint().AsyncConvertImage((ConvertImageCallback)EnrollFinger_OnConvertImage2, pThis, 0x02); + } + else + { + pThis->SetState(STATE_ENROLL_WAITFINGER2); + char aBuf[512]; + m_snprintf(aBuf, sizeof(aBuf), "Error while scanning finger: %s
Lift your finger and try again.", errorStr); + pThis->Main().EnrollMessage(aBuf); + } +} + +void CFingerLogic::EnrollFinger_OnConvertImage2(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("EnrollFinger_OnConvertImage2: (%d) %s", error, errorStr); + if(pThis->m_State != STATE_ENROLL_CONVERTIMAGE2) return; + + if(error == ERROR_OK) + { + pThis->SetState(STATE_ENROLL_COMPARE); + pThis->FingerPrint().AsyncCompareCharacteristics((CompareCharacteristicsCallback)EnrollFinger_OnCompareCharacteristics, pThis); + } + else + { + pThis->SetState(STATE_ENROLL_WAITFINGER2); + char aBuf[512]; + m_snprintf(aBuf, sizeof(aBuf), "Error while analyzing finger: %s
Lift your finger and try again.", errorStr); + pThis->Main().EnrollMessage(aBuf); + } +} + +void CFingerLogic::EnrollFinger_OnCompareCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int16_t score) +{ + debugf("EnrollFinger_OnCompareCharacteristics: (%d) %s (%d)", error, errorStr, score); + if(pThis->m_State != STATE_ENROLL_COMPARE) return; + + if(error == ERROR_OK) + { + pThis->SetState(STATE_ENROLL_CREATETEMPLATE); + pThis->FingerPrint().AsyncCreateTemplate((CreateTemplateCallback)EnrollFinger_OnCreateTemplate, pThis); + } + else + { + pThis->SetState(STATE_ENROLL_WAITFINGER1); + char aBuf[512]; + m_snprintf(aBuf, sizeof(aBuf), "Error while comparing fingers: %s
Lift your finger and try again.", errorStr); + pThis->Main().EnrollMessage(aBuf); + } +} + +void CFingerLogic::EnrollFinger_OnCreateTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("EnrollFinger_OnCreateTemplate: (%d) %s", error, errorStr); + if(pThis->m_State != STATE_ENROLL_CREATETEMPLATE) return; + + if(error == ERROR_OK) + { + pThis->SetState(STATE_ENROLL_STORETEMPLATE); + pThis->m_iBuffer = pThis->FirstFreeFingerSlot(); + pThis->FingerPrint().AsyncStoreTemplate((StoreTemplateCallback)EnrollFinger_OnStoreTemplate, pThis, pThis->m_iBuffer, 0x01); + } + else + { + pThis->SetState(STATE_READY); + } +} + +void CFingerLogic::EnrollFinger_OnStoreTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, uint16_t positionNumber) +{ + debugf("EnrollFinger_OnStoreTemplate: (%d) %s (%d)", error, errorStr, positionNumber); + if(pThis->m_State != STATE_ENROLL_STORETEMPLATE) return; + + if(error == ERROR_OK) + { + pThis->FingerSlot(positionNumber, true); + pThis->SetState(STATE_ENROLL_LOADTEMPLATE); + pThis->FingerPrint().AsyncLoadTemplate((LoadTemplateCallback)EnrollFinger_OnLoadTemplate, pThis, positionNumber, 0x01); + } + else + { + pThis->SetState(STATE_READY); + } +} + +void CFingerLogic::EnrollFinger_OnLoadTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("EnrollFinger_OnLoadTemplate: (%d) %s", error, errorStr); + if(pThis->m_State != STATE_ENROLL_LOADTEMPLATE) return; + + if(error == ERROR_OK) + { + pThis->SetState(STATE_ENROLL_DOWNLOADCHARACTERISTICS); + pThis->FingerPrint().AsyncDownloadCharacteristics((DownloadCharacteristicsCallback)EnrollFinger_OnDownloadCharacteristics, pThis, 0x01); + } + else + { + pThis->SetState(STATE_READY); + } +} + +void CFingerLogic::EnrollFinger_OnDownloadCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int8_t *pChar, uint16_t charLen) +{ + debugf("EnrollFinger_OnDownloadCharacteristics: (%d) %s (%p, %d)", error, errorStr, pChar, charLen); + if(pThis->m_State != STATE_ENROLL_DOWNLOADCHARACTERISTICS) return; + + if(error == ERROR_OK) + { + Crypto::Sha256 ctx; + ctx.update(pChar, charLen); + uint8_t *digest = ctx.getHash().data(); + + Serial1.printf("NEW Finger %d hexdigest: ", pThis->m_iBuffer); + for(uint8_t i = 0; i < sizeof(digest); i++) + Serial1.printf("%x", digest[i]); + Serial1.printf("\n"); + + pThis->Main().OnFingerEnrolled(pThis->m_iBuffer, digest); + } + + pThis->SetState(STATE_READY); +} diff --git a/app/FingerLogic.h b/app/FingerLogic.h new file mode 100644 index 0000000..ddc5081 --- /dev/null +++ b/app/FingerLogic.h @@ -0,0 +1,105 @@ +#ifndef FINGERLOGIC_H +#define FINGERLOGIC_H + +#include +#include "FingerPrint.h" + +enum FingerLogicState +{ + STATE_ERROR = -1, + STATE_INITIAL = 0, + + STATE_INIT_VERIFYPASSWORD, + STATE_INIT_READSYSTEMPARAMETERS, + STATE_INIT_READTEMPLATEMAP, + STATE_INIT_VERIFYTEMPLATES, + + STATE_READY, + + STATE_VERIFY_READIMAGE, + STATE_VERIFY_CONVERTIMAGE, + STATE_VERIFY_SEARCHTEMPLATE, + STATE_VERIFY_LOADTEMPLATE, + STATE_VERIFY_DOWNLOADCHARACTERISTICS, + + STATE_ENROLL_WAITFINGER1, + STATE_ENROLL_READIMAGE1, + STATE_ENROLL_CONVERTIMAGE1, + STATE_ENROLL_SEARCHTEMPLATE, + STATE_ENROLL_WAITFINGER2, + STATE_ENROLL_READIMAGE2, + STATE_ENROLL_CONVERTIMAGE2, + STATE_ENROLL_COMPARE, + STATE_ENROLL_CREATETEMPLATE, + STATE_ENROLL_STORETEMPLATE, + STATE_ENROLL_LOADTEMPLATE, + STATE_ENROLL_DOWNLOADCHARACTERISTICS +}; + +class CFingerLogic +{ +public: + CFingerLogic(class CMain *pMain); + void Init(CFingerPrint *pFingerPrint); + void OnFingerInterrupt(bool finger); + + bool FingerSlot(uint16_t index); + bool FingerSlot(uint16_t index, bool value); + int FirstFreeFingerSlot(); + + void InitFinger(); + static void InitFinger_OnVerifyPassword(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void InitFinger_OnReadSystemParameters(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, CFingerSystemParameters *param); + static void InitFinger_OnGetTemplates(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void InitFinger_OnReadTemplateMap(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, uint8_t *pData, uint16_t dataLen); + void InitFinger_VerifyTemplates(); + static void InitFinger_OnLoadTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void InitFinger_OnDownloadCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int8_t *pChar, uint16_t charLen); + + void VerifyFinger(); + static void VerifyFinger_OnReadImage(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void VerifyFinger_OnConvertImage(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void VerifyFinger_OnSearchTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int16_t position, int16_t score); + static void VerifyFinger_OnLoadTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void VerifyFinger_OnDownloadCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int8_t *pChar, uint16_t charLen); + + bool EnrollFinger(bool cancel=false); + static void EnrollFinger_OnReadImage1(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void EnrollFinger_OnConvertImage1(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void EnrollFinger_OnSearchTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int16_t position, int16_t score); + void EnrollFinger_OnFinger(); + static void EnrollFinger_OnReadImage2(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void EnrollFinger_OnConvertImage2(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void EnrollFinger_OnCompareCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int16_t score); + static void EnrollFinger_OnCreateTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void EnrollFinger_OnStoreTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, uint16_t positionNumber); + static void EnrollFinger_OnLoadTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + static void EnrollFinger_OnDownloadCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int8_t *pChar, uint16_t charLen); + + CMain &Main() { return *m_pMain; } + CFingerPrint &FingerPrint() { return *m_pFingerPrint; } + +private: + bool m_Power; + void PowerOff(); + void PowerOn(); + void SetState(FingerLogicState state); + Timer m_PowerOffTimer; + + CMain *m_pMain; + CFingerPrint *m_pFingerPrint; + FingerLogicState m_State; + bool m_Finger; + + CFingerSystemParameters m_FingerParams; + uint8_t m_aFingerSlots[1792/8]; + uint16_t m_FingerSlotsAdded; + + int32_t m_iBuffer; + + Timer m_Timer; + void *m_fnUserCallback; + void *m_pUserData; +}; + +#endif diff --git a/app/FingerPrint.cpp b/app/FingerPrint.cpp new file mode 100644 index 0000000..5509565 --- /dev/null +++ b/app/FingerPrint.cpp @@ -0,0 +1,267 @@ +#include +#include "FingerPrint.h" + +static struct CFingerErrorsExplain +{ + uint8_t error; + const char *str; +} gs_FingerErrors[] = { + {ERROR_OK, "ERROR_OK: command execution complete"}, + {ERROR_COMMUNICATION, "ERROR_COMMUNICATION: error when receiving data package"}, + {ERROR_NOFINGER, "ERROR_NOFINGER: no finger on the sensor"}, + {ERROR_READIMAGE, "ERROR_READIMAGE: fail to enroll the finger"}, + {ERROR_MESSYIMAGE, "ERROR_MESSYIMAGE: fail to generate character file due to the over-disorderly fingerprint image"}, + {ERROR_FEWFEATUREPOINTS, "ERROR_FEWFEATUREPOINTS: fail to generate character file due to lackness of character point or over-smallness of fingerprint image"}, + {ERROR_NOTMATCHING, "ERROR_NOTMATCHING: finger doesn't match"}, + {ERROR_NOTEMPLATEFOUND, "ERROR_NOTEMPLATEFOUND: fail to find the matching finger"}, + {ERROR_CHARACTERISTICSMISMATCH, "ERROR_CHARACTERISTICSMISMATCH: fail to combine the character files"}, + {ERROR_INVALIDPOSITION, "ERROR_INVALIDPOSITION: addressing PageID is beyond the finger library"}, + {ERROR_LOADTEMPLATE, "ERROR_LOADTEMPLATE: error when reading template from library or the template is invalid"}, + {ERROR_UPLOADTEMPLATE, "ERROR_UPLOADTEMPLATE: error when uploading template"}, + {ERROR_PACKETRESPONSEFAIL, "ERROR_PACKETRESPONSEFAIL: Module can't receive the following data packages"}, + {ERROR_UPLOADIMAGE, "ERROR_UPLOADIMAGE: error when uploading image"}, + {ERROR_DELETETEMPLATE, "ERROR_DELETETEMPLATE: fail to delete the template"}, + {ERROR_CLEARDATABASE, "ERROR_CLEARDATABASE: fail to clear finger library"}, + {ERROR_WRONGPASSWORD, "ERROR_WRONGPASSWORD: wrong password"}, + {ERROR_INVALIDIMAGE, "ERROR_INVALIDIMAGE: fail to generate the image for the lackness of valid primary image"}, + {ERROR_FLASH, "ERROR_FLASH: error when writing flash"}, + {ERROR_NODEF, "ERROR_NODEF: No definition error"}, + {ERROR_INVALIDREGISTER, "ERROR_INVALIDREGISTER: invalid register number"}, + {ERROR_INCORRECTREGISTERCONF, "ERROR_INCORRECTREGISTERCONF: incorrect configuration of register"}, + {ERROR_INVALIDNOTEPADPAGE, "ERROR_INVALIDNOTEPADPAGE: wrong notepad page number"}, + {ERROR_COMMUNICATIONPORT, "ERROR_COMMUNICATIONPORT: fail to operate the communication port"} +}; + +const char *CFingerPrint::ExplainFingerError(uint8_t error) +{ + CFingerErrorsExplain *pFound = NULL; + for(int i = 0; i < sizeof(gs_FingerErrors) / sizeof(*gs_FingerErrors); i++) + { + if(error == gs_FingerErrors[i].error) + { + pFound = &gs_FingerErrors[i]; + break; + } + } + + if(pFound) + return pFound->str; + + return "Unknown error."; +} + +CFingerPrint::CFingerPrint() +{ + m_RecvState = RECV_DROP; +} + +void CFingerPrint::Init(HardwareSerial &serial, uint32_t address, uint32_t password) +{ + if(m_pSerial) + return; + + m_pSerial = &serial; + m_Address = address; + m_Password = password; + + m_pSerial->onDataReceived(StreamDataReceivedDelegate(&CFingerPrint::OnData, this)); +} + +void CFingerPrint::OnData(Stream &stream, char arrivedChar, unsigned short availableCharsCount) +{ + if(m_RecvState == RECV_DONE) + return; + + if(m_RecvState == RECV_DROP) + { + while(stream.available()) + stream.read(); + + return; + } + + // RECV_WAITING + while(stream.available()) + { + uint8_t cur = stream.read(); + + if(m_RecvIndex == 0 && cur != 0xEF || m_RecvIndex == 1 && cur != 0x01) + { + debugf("skip garbage header at %d: %X", m_RecvIndex, cur); + m_RecvIndex = 0; + continue; + } + + m_aRecvBuffer[m_RecvIndex++] = cur; + + // Packet could be complete (the minimal packet size is 12 bytes) + if(m_RecvIndex >= 12) + { + // Check the packet header (redundant) + uint16_t header = m_aRecvBuffer[0] << 8 | m_aRecvBuffer[1]; + if(header != 0xEF01) + { + debugf("wrong header: %X", header); + m_RecvIndex = 0; + continue; + } + + // Calculate packet payload length + uint16_t length = m_aRecvBuffer[7] << 8 | m_aRecvBuffer[8]; + + // Check if the packet is still fully received + // Condition: index counter < packet payload length + packet frame + if(m_RecvIndex < length + 9) + continue; + + // At this point the packet should be fully received + uint8_t ident = m_aRecvBuffer[6]; + + // Calculate checksum: + // Checksum = packet type (1 byte) + packet length RAW!! (2 bytes) + packet payload (n bytes) + uint16_t calcChecksum = ident; + const uint16_t tmp = 7 + length; + for(uint16_t i = 7; i < tmp; i++) + calcChecksum += m_aRecvBuffer[i]; + + // Checksum in received data package + uint16_t recvChecksum = m_aRecvBuffer[m_RecvIndex - 2] << 8 | m_aRecvBuffer[m_RecvIndex - 1]; + + if(calcChecksum != recvChecksum) + { + debugf("checksum!!: %X != %X", calcChecksum, recvChecksum); + m_RecvIndex = 0; + continue; + } + + m_RecvIndex = 0; + m_RecvState = RECV_DONE; + + (this->*m_fnRecvCallback)((FingerPrintIdent)ident, &m_aRecvBuffer[9], length - 2); + } + } +} + +int CFingerPrint::Recv(FingerPrintIdent *pIdent, uint8_t *pData, uint16_t maxLength, const int maxTime) +{ + m_RecvState = RECV_DONE; + m_RecvIndex = 0; + + debugf("Start manual recv:"); + int Ret = -9999; + + int timeout = maxTime; + while(true) + { + while(!m_pSerial->available()) + { + if(--timeout == 0) + return Ret; + delay(1); + } + + uint8_t cur = m_pSerial->read(); + m_aRecvBuffer[m_RecvIndex++] = cur; + + // Packet could be complete (the minimal packet size is 12 bytes) + if(m_RecvIndex >= 12) + { + // Check the packet header + uint16_t header = m_aRecvBuffer[0] << 8 | m_aRecvBuffer[1]; + if(header != 0xEF01) + { + debugf("wrong header: %X", header); + m_RecvIndex = 0; + return Ret; + } + + // Calculate packet payload length + uint16_t length = m_aRecvBuffer[7] << 8 | m_aRecvBuffer[8]; + + // Check if the packet is still fully received + // Condition: index counter < packet payload length + packet frame + if(m_RecvIndex < length + 9) + continue; + + // At this point the packet should be fully received + uint8_t ident = m_aRecvBuffer[6]; + + // Calculate checksum: + // Checksum = packet type (1 byte) + packet length RAW! (2 bytes) + packet payload (n bytes) + uint16_t calcChecksum = ident + length; + uint16_t tmp = 9 + length - 2; + for(uint16_t i = 9; i < tmp; i++) + calcChecksum += m_aRecvBuffer[i]; + + // Checksum in received data package + uint16_t recvChecksum = m_aRecvBuffer[m_RecvIndex - 2] << 8 | m_aRecvBuffer[m_RecvIndex - 1]; + + if(calcChecksum != recvChecksum) + { + debugf("checksum!!: %X != %X", calcChecksum, recvChecksum); + m_RecvIndex = 0; + return Ret; + } + + length -= 2; + Ret = 0; + + if(pIdent) + *(uint8_t *)pIdent = ident; + + if(pData) + { + memcpy(pData, &m_aRecvBuffer[9], min(length, maxLength)); + Ret = (int)maxLength - length; // >= 0 = OK + } + + break; + } + } + + debugf("End recv. (%dms)", maxTime - timeout); + m_RecvIndex = 0; + + return Ret; +} + +int CFingerPrint::Write(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_WAITING; + + // Header + const uint16_t Header = 0xEF01; + m_pSerial->write(Header >> 8 & 0xFF); + m_pSerial->write(Header >> 0 & 0xFF); + + // Address + m_pSerial->write(m_Address >> 24 & 0xFF); + m_pSerial->write(m_Address >> 16 & 0xFF); + m_pSerial->write(m_Address >> 8 & 0xFF); + m_pSerial->write(m_Address >> 0 & 0xFF); + + // Package identifier + m_pSerial->write(ident); + + // Package length + length += 2; // Checksum + m_pSerial->write(length >> 8 & 0xFF); + m_pSerial->write(length >> 0 & 0xFF); + + // Checksum + uint16_t Checksum = ident + length; + + // Data + length -= 2; + for(uint16_t i = 0; i < length; i++) + { + m_pSerial->write(pData[i]); + Checksum += pData[i]; + } + + // Checksum + m_pSerial->write(Checksum >> 8 & 0xFF); + m_pSerial->write(Checksum >> 0 & 0xFF); + + return length; +} diff --git a/app/FingerPrint.h b/app/FingerPrint.h new file mode 100644 index 0000000..2bccf4b --- /dev/null +++ b/app/FingerPrint.h @@ -0,0 +1,163 @@ +#ifndef FINGERPRINT_H +#define FINGERPRINT_H + +// Data package identifier +enum FingerPrintIdent +{ + IDENT_COMMAND = 0x01, // Command packet + IDENT_DATA = 0x02, // Data packet; + // Data packet shall not appear alone in executing processs, + // must follow command packet or acknowledge packet. + IDENT_ACK = 0x07, // Acknowledge packet + IDENT_ENDOFDATA = 0x08, // End of Data packet +}; + +enum FingerPrintCommand +{ + COMMAND_GENIMG = 0x01, // Collect finger image + COMMAND_IMG2TZ = 0x02, // To generate character file from image + COMMAND_MATCH = 0x03, // Carry out precise matching of two templates + COMMAND_SEARCH = 0x04, // Search library finger + COMMAND_REGMODEL = 0x05, // To combine character files and generate template + COMMAND_STORE = 0x06, // To store template; + COMMAND_LOADCHAR = 0x07, // to read/load template + COMMAND_UPCHAR = 0x08, // to upload template + COMMAND_DOWNCHR = 0x09, // to download template + COMMAND_UPIMAGE = 0x0A, // To upload image + COMMAND_DOWNIMAGE = 0x0B, // To download image + COMMAND_DELETCHAR = 0x0C, // to delete tempates + COMMAND_EMPTY = 0x0D, // to empty the library + COMMAND_SETSYSPARA = 0x0E, // To set system Parameter + COMMAND_READSYSPARA = 0x0F, // To read Parameter + COMMAND_SETPWD = 0x12, // To set password + COMMAND_VFYPWD = 0x13, // To verify password + COMMAND_GETRANDOMCODE = 0x14, // to get random code + COMMAND_SETADDER = 0x15, // To set device address + COMMAND_CONTROL = 0x17, // Port control + COMMAND_WRITENOTEPAD = 0x18, // to write note pad + COMMAND_READNOTEPAD = 0x19, // To read note pad + COMMAND_HISPEEDSEARCH = 0x1B, // Search the library fastly + COMMAND_TEMPLATENUM = 0x1D, // To read finger template numbers + COMMAND_READCONLIST = 0x1F, // To read finger template index table +}; + +enum FingerPrintError +{ + ERROR_OK = 0x00, // command execution complete + ERROR_COMMUNICATION = 0x01, // error when receiving data package + ERROR_NOFINGER = 0x02, // no finger on the sensor + ERROR_READIMAGE = 0x03, // fail to enroll the finger + ERROR_MESSYIMAGE = 0x06, // fail to generate character file due to the over-disorderly fingerprint image + ERROR_FEWFEATUREPOINTS = 0x07, // fail to generate character file due to lackness of character point or over-smallness of fingerprint image + ERROR_NOTMATCHING = 0x08, // finger doesn't match + ERROR_NOTEMPLATEFOUND = 0x09, // fail to find the matching finger + ERROR_CHARACTERISTICSMISMATCH = 0x0A, // fail to combine the character files + ERROR_INVALIDPOSITION = 0x0B, // addressing PageID is beyond the finger library + ERROR_LOADTEMPLATE = 0x0C, // error when reading template from library or the template is invalid + ERROR_UPLOADTEMPLATE = 0x0D, // error when uploading template + ERROR_PACKETRESPONSEFAIL = 0x0E, // Module can't receive the following data packages + ERROR_UPLOADIMAGE = 0x0F, // error when uploading image + ERROR_DELETETEMPLATE = 0x10, // fail to delete the template + ERROR_CLEARDATABASE = 0x11, // fail to clear finger library + ERROR_WRONGPASSWORD = 0x13, // wrong password + ERROR_INVALIDIMAGE = 0x15, // fail to generate the image for the lackness of valid primary image + ERROR_FLASH = 0x18, // error when writing flash + ERROR_NODEF = 0x19, // No definition error + ERROR_INVALIDREGISTER = 0x1A, // invalid register number + ERROR_INCORRECTREGISTERCONF = 0x1B, // incorrect configuration of register + ERROR_INVALIDNOTEPADPAGE = 0x1C, // wrong notepad page number + ERROR_COMMUNICATIONPORT = 0x1D, // fail to operate the communication port +}; + +enum RecvStates +{ + RECV_DONE = 0, + RECV_WAITING = 1, + RECV_DROP = 2 +}; + +struct CFingerSystemParameters +{ + uint16_t statusRegister; + uint16_t systemID; + uint16_t storageCapacity; + uint16_t securityLevel; + uint32_t deviceAddress; + uint16_t packetLength; + uint16_t baudRate; +}; + +class CFingerPrint; +typedef void (CFingerPrint::*RecvCallback)(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + +typedef void (*VerifyPasswordCallback)(void *pUser, FingerPrintError error, const char *errorStr); +typedef void (*ReadSystemParametersCallback)(void *pUser, FingerPrintError error, const char *errorStr, CFingerSystemParameters *param); +typedef void (*ReadImageCallback)(void *pUser, FingerPrintError error, const char *errorStr); +typedef void (*ConvertImageCallback)(void *pUser, FingerPrintError error, const char *errorStr); +typedef void (*SearchTemplateCallback)(void *pUser, FingerPrintError error, const char *errorStr, int16_t position, int16_t score); +typedef void (*CompareCharacteristicsCallback)(void *pUser, FingerPrintError error, const char *errorStr, int16_t score); +typedef void (*CreateTemplateCallback)(void *pUser, FingerPrintError error, const char *errorStr); +typedef void (*DownloadCharacteristicsCallback)(void *pUser, FingerPrintError error, const char *errorStr, int8_t *pChar, uint16_t charLen); +typedef void (*LoadTemplateCallback)(void *pUser, FingerPrintError error, const char *errorStr); +typedef void (*StoreTemplateCallback)(void *pUser, FingerPrintError error, const char *errorStr, uint16_t positionNumber); +typedef void (*ReadTemplateMapCallback)(void *pUser, FingerPrintError error, const char *errorStr, uint8_t *pData, uint16_t dataLen); + +class CFingerPrint +{ +public: + CFingerPrint(); + void Init(HardwareSerial &serial, uint32_t address, uint32_t password); + + static const char *ExplainFingerError(uint8_t error); + + int VerifyPassword(); + int ReadSystemParameters(uint8_t aResponse[17]); + int DeleteTemplate(uint16_t positionStart, uint16_t count); + int EmptyDatabase(); + + int AsyncVerifyPassword(VerifyPasswordCallback fnCallback, void *pUser); + int AsyncReadSystemParameters(ReadSystemParametersCallback fnCallback, void *pUser); + int AsyncReadImage(ReadImageCallback fnCallback, void *pUser); + int AsyncConvertImage(ConvertImageCallback fnCallback, void *pUser, uint8_t numCharBuffer); + int AsyncSearchTemplate(SearchTemplateCallback fnCallback, void *pUser, uint8_t numCharBuffer, uint16_t positionStart, uint16_t numTemplates); + int AsyncCompareCharacteristics(CompareCharacteristicsCallback fnCallback, void *pUser); + int AsyncCreateTemplate(CreateTemplateCallback fnCallback, void *pUser); + int AsyncDownloadCharacteristics(DownloadCharacteristicsCallback fnCallback, void *pUser, uint8_t numCharBuffer); + int AsyncLoadTemplate(LoadTemplateCallback fnCallback, void *pUser, uint16_t positionNumber, uint8_t numCharBuffer); + int AsyncStoreTemplate(StoreTemplateCallback fnCallback, void *pUser, uint16_t positionNumber, uint8_t numCharBuffer); + int AsyncReadTemplateMap(ReadTemplateMapCallback fnCallback, void *pUser, uint8_t numPage); + +private: + void OnAsyncVerifyPassword(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncReadSystemParameters(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncReadImage(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncConvertImage(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncSearchTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncCompareCharacteristics(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncCreateTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncDownloadCharacteristics(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncLoadTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncStoreTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncReadTemplateMap(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + + void OnData(Stream &stream, char arrivedChar, unsigned short availableCharsCount); + int Write(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + int Recv(FingerPrintIdent *pIdent, uint8_t *pData, uint16_t maxLength, const int maxTime=100); + + uint32_t m_Address; + uint32_t m_Password; + HardwareSerial *m_pSerial; + + uint8_t m_aRecvBuffer[2+4+1+2+256+2]; + uint16_t m_RecvIndex; + + volatile RecvStates m_RecvState; + RecvCallback m_fnRecvCallback; + void *m_fnUserCallback; + void *m_pUserData; + + uint8_t m_aBuffer[1024]; + int32_t m_iBuffer; +}; + +#endif diff --git a/app/FingerPrint_API.cpp b/app/FingerPrint_API.cpp new file mode 100644 index 0000000..a2ab40b --- /dev/null +++ b/app/FingerPrint_API.cpp @@ -0,0 +1,101 @@ +#include +#include "FingerPrint.h" + +int CFingerPrint::VerifyPassword() +{ + uint8_t aPayload[] = { + COMMAND_VFYPWD, + (uint8_t)(m_Password >> 24 & 0xFF), + (uint8_t)(m_Password >> 16 & 0xFF), + (uint8_t)(m_Password >> 8 & 0xFF), + (uint8_t)(m_Password >> 0 & 0xFF), + }; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + FingerPrintIdent ident; + uint8_t aResponse[1]; + int ret = Recv(&ident, aResponse, sizeof(aResponse)); + m_RecvState = RECV_DROP; + + if(ret < 0) + return ret; + + if(ident != IDENT_ACK) + return ERROR_COMMUNICATION; + + uint8_t error = aResponse[0]; + return error; +} + +int CFingerPrint::ReadSystemParameters(uint8_t aResponse[17]) +{ + uint8_t aPayload[] = { + COMMAND_READSYSPARA, + }; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + FingerPrintIdent ident; + int ret = Recv(&ident, aResponse, 17); + m_RecvState = RECV_DROP; + + if(ret < 0) + return ret; + + if(ident != IDENT_ACK) + return ERROR_COMMUNICATION; + + uint8_t error = aResponse[0]; + return error; +} + +int CFingerPrint::DeleteTemplate(uint16_t positionStart, uint16_t count) +{ + uint8_t aPayload[] = { + COMMAND_DELETCHAR, + (uint8_t)(positionStart >> 8 & 0xFF), + (uint8_t)(positionStart >> 0 & 0xFF), + (uint8_t)(count >> 8 & 0xFF), + (uint8_t)(count >> 0 & 0xFF), + }; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + FingerPrintIdent ident; + uint8_t aResponse[1]; + int ret = Recv(&ident, aResponse, sizeof(aResponse)); + m_RecvState = RECV_DROP; + + if(ret < 0) + return ret; + + if(ident != IDENT_ACK) + return ERROR_COMMUNICATION; + + uint8_t error = aResponse[0]; + return error; +} + +int CFingerPrint::EmptyDatabase() +{ + uint8_t aPayload[] = { + COMMAND_EMPTY, + }; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + FingerPrintIdent ident; + uint8_t aResponse[1]; + int ret = Recv(&ident, aResponse, sizeof(aResponse)); + m_RecvState = RECV_DROP; + + if(ret < 0) + return ret; + + if(ident != IDENT_ACK) + return ERROR_COMMUNICATION; + + uint8_t error = aResponse[0]; + return error; +} diff --git a/app/FingerPrint_AsyncAPI.cpp b/app/FingerPrint_AsyncAPI.cpp new file mode 100644 index 0000000..8fcbe50 --- /dev/null +++ b/app/FingerPrint_AsyncAPI.cpp @@ -0,0 +1,510 @@ +#include +#include "FingerPrint.h" + +int CFingerPrint::AsyncVerifyPassword(VerifyPasswordCallback fnCallback, void *pUser) +{ + uint8_t aPayload[] = { + COMMAND_VFYPWD, + (uint8_t)(m_Password >> 24 & 0xFF), + (uint8_t)(m_Password >> 16 & 0xFF), + (uint8_t)(m_Password >> 8 & 0xFF), + (uint8_t)(m_Password >> 0 & 0xFF), + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncVerifyPassword; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncVerifyPassword(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((VerifyPasswordCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!"); + return; + } + + if(length != 1) + { + ((VerifyPasswordCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!"); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + ((VerifyPasswordCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr); +} + + +int CFingerPrint::AsyncReadSystemParameters(ReadSystemParametersCallback fnCallback, void *pUser) +{ + uint8_t aPayload[] = { + COMMAND_READSYSPARA, + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncReadSystemParameters; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncReadSystemParameters(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((ReadSystemParametersCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!", NULL); + return; + } + + if(length != 1 + sizeof(CFingerSystemParameters)) + { + ((ReadSystemParametersCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!", NULL); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + CFingerSystemParameters *param = (CFingerSystemParameters *)m_aBuffer; + param->statusRegister = pData[1] << 8 | pData[2]; + param->systemID = pData[3] << 8 | pData[4]; + param->storageCapacity = pData[5] << 8 | pData[6]; + param->securityLevel = pData[7] << 8 | pData[8]; + param->deviceAddress = pData[9] << 24 | pData[10] << 16 | pData[11] << 8 | pData[12]; + param->packetLength = pData[13] << 8 | pData[14]; + param->baudRate = pData[15] << 8 | pData[16]; + + ((ReadSystemParametersCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr, param); +} + + +int CFingerPrint::AsyncReadImage(ReadImageCallback fnCallback, void *pUser) +{ + uint8_t aPayload[] = { + COMMAND_GENIMG, + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncReadImage; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncReadImage(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((ReadImageCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!"); + return; + } + + if(length != 1) + { + ((ReadImageCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!"); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + ((ReadImageCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr); +} + + +int CFingerPrint::AsyncConvertImage(ConvertImageCallback fnCallback, void *pUser, uint8_t numCharBuffer) +{ + if(numCharBuffer != 0x01 && numCharBuffer != 0x02) + return -1; + + uint8_t aPayload[] = { + COMMAND_IMG2TZ, + numCharBuffer, + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncConvertImage; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncConvertImage(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((ConvertImageCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!"); + return; + } + + if(length != 1) + { + ((ConvertImageCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!"); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + ((ConvertImageCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr); +} + + +int CFingerPrint::AsyncSearchTemplate(SearchTemplateCallback fnCallback, void *pUser, uint8_t numCharBuffer, uint16_t positionStart, uint16_t numTemplates) +{ + if(numCharBuffer != 0x01 && numCharBuffer != 0x02) + return -1; + + uint8_t aPayload[] = { + COMMAND_SEARCH, + numCharBuffer, + (uint8_t)(positionStart >> 8 & 0xFF), + (uint8_t)(positionStart >> 0 & 0xFF), + (uint8_t)(numTemplates >> 8 & 0xFF), + (uint8_t)(numTemplates >> 0 & 0xFF), + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncSearchTemplate; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncSearchTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + int16_t position = -1; + int16_t score = -1; + + if(ident != IDENT_ACK) + { + ((SearchTemplateCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!", position, score); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + if(error == ERROR_OK) + { + if(length != 5) + { + ((SearchTemplateCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!", position, score); + return; + } + + position = pData[1] << 8 | pData[2]; + score = pData[3] << 8 | pData[4]; + } + + ((SearchTemplateCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr, position, score); +} + + +int CFingerPrint::AsyncCompareCharacteristics(CompareCharacteristicsCallback fnCallback, void *pUser) +{ + uint8_t aPayload[] = { + COMMAND_MATCH, + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncCompareCharacteristics; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncCompareCharacteristics(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + int16_t score = -1; + + if(ident != IDENT_ACK) + { + ((CompareCharacteristicsCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!", score); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + if(error == ERROR_OK) + { + if(length != 3) + { + ((CompareCharacteristicsCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!", score); + return; + } + + score = pData[1] << 8 | pData[2]; + } + + ((CompareCharacteristicsCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr, score); +} + + +int CFingerPrint::AsyncCreateTemplate(CreateTemplateCallback fnCallback, void *pUser) +{ + uint8_t aPayload[] = { + COMMAND_REGMODEL, + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncCreateTemplate; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncCreateTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((CreateTemplateCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!"); + return; + } + + if(length != 1) + { + ((CreateTemplateCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!"); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + ((CreateTemplateCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr); +} + + +int CFingerPrint::AsyncDownloadCharacteristics(DownloadCharacteristicsCallback fnCallback, void *pUser, uint8_t numCharBuffer) +{ + if(numCharBuffer != 0x01 && numCharBuffer != 0x02) + return -1; + + uint8_t aPayload[] = { + COMMAND_UPCHAR, + numCharBuffer, + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncDownloadCharacteristics; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + m_iBuffer = -1; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncDownloadCharacteristics(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + if(m_iBuffer == -1) + { + if(ident != IDENT_ACK) + { + m_RecvState = RECV_DROP; + ((DownloadCharacteristicsCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!", NULL, 0); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + if(error != ERROR_OK) + { + m_RecvState = RECV_DROP; + ((DownloadCharacteristicsCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr, NULL, 0); + return; + } + + m_iBuffer = 0; + m_RecvState = RECV_WAITING; + return; + } + + if(ident != IDENT_DATA && ident != IDENT_ENDOFDATA) + { + m_RecvState = RECV_DROP; + ((DownloadCharacteristicsCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no data packet", NULL, 0); + return; + } + + if(sizeof(m_aBuffer) - m_iBuffer < length) + { + m_RecvState = RECV_DROP; + ((DownloadCharacteristicsCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Not enough space to store received data", NULL, 0); + return; + } + + memcpy(&m_aBuffer[m_iBuffer], pData, length); + m_iBuffer += length; + + if(ident == IDENT_ENDOFDATA) + { + m_RecvState = RECV_DROP; + ((DownloadCharacteristicsCallback)m_fnUserCallback)(m_pUserData, ERROR_OK, ExplainFingerError(ERROR_OK), (int8_t *)m_aBuffer, m_iBuffer); + return; + } + + m_RecvState = RECV_WAITING; +} + + +int CFingerPrint::AsyncLoadTemplate(LoadTemplateCallback fnCallback, void *pUser, uint16_t positionNumber, uint8_t numCharBuffer) +{ + if(numCharBuffer != 0x01 && numCharBuffer != 0x02) + return -1; + + uint8_t aPayload[] = { + COMMAND_LOADCHAR, + numCharBuffer, + (uint8_t)(positionNumber >> 8 & 0xFF), + (uint8_t)(positionNumber >> 0 & 0xFF), + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncLoadTemplate; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncLoadTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((LoadTemplateCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!"); + return; + } + + if(length != 1) + { + ((LoadTemplateCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!"); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + ((LoadTemplateCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr); +} + + +int CFingerPrint::AsyncStoreTemplate(StoreTemplateCallback fnCallback, void *pUser, uint16_t positionNumber, uint8_t numCharBuffer) +{ + if(numCharBuffer != 0x01 && numCharBuffer != 0x02) + return -1; + + uint8_t aPayload[] = { + COMMAND_STORE, + numCharBuffer, + (uint8_t)(positionNumber >> 8 & 0xFF), + (uint8_t)(positionNumber >> 0 & 0xFF), + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncStoreTemplate; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + m_iBuffer = positionNumber; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncStoreTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((StoreTemplateCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!", m_iBuffer); + return; + } + + if(length != 1) + { + ((StoreTemplateCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "Incorrect data length!", m_iBuffer); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + ((StoreTemplateCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr, m_iBuffer); +} + + +int CFingerPrint::AsyncReadTemplateMap(ReadTemplateMapCallback fnCallback, void *pUser, uint8_t numPage) +{ + uint8_t aPayload[] = { + COMMAND_READCONLIST, + numPage + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncReadTemplateMap; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncReadTemplateMap(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((ReadTemplateMapCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!", NULL, 0); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + if(error != ERROR_OK) + { + ((ReadTemplateMapCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr, NULL, 0); + return; + } + + ((ReadTemplateMapCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr, &pData[1], length - 1); +} diff --git a/app/Settings.cpp b/app/Settings.cpp new file mode 100644 index 0000000..4a7fa0d --- /dev/null +++ b/app/Settings.cpp @@ -0,0 +1,118 @@ +#include +#include +#include "utils.h" +#include "Settings.h" + +CSettings::CSettings() +{ + strcpy(m_aUsername, "admin"); + strcpy(m_aPassword, "admin"); + strcpy(m_aSSID, ""); + strcpy(m_aPSK, ""); + m_DHCP = true; + strcpy(m_aHostname, "safeweb"); + m_Address = IPAddress(0, 0, 0, 0); + m_Netmask = IPAddress(0, 0, 0, 0); + m_Gateway = IPAddress(0, 0, 0, 0); + memset(m_aLockCode, 0, sizeof(m_aLockCode)); +} + +bool CSettings::Exists() +{ + return fileExist(APP_SETTINGS_FILE); +} + +bool CSettings::Load() +{ + if(!Exists()) + { + Save(); + return true; + } + + uint32_t size = fileGetSize(APP_SETTINGS_FILE); + char *pData = new char[size + 1]; + fileGetContent(APP_SETTINGS_FILE, pData, size + 1); + + DynamicJsonDocument doc(1024); + if(deserializeJson(doc, pData)) + { + delete[] pData; + return false; + } + + strncpy(m_aUsername, doc["username"], sizeof(m_aUsername)); + strncpy(m_aPassword, doc["password"], sizeof(m_aPassword)); + + JsonObject network = doc["network"]; + strncpy(m_aSSID, network["ssid"], sizeof(m_aSSID)); + strncpy(m_aPSK, network["passphrase"], sizeof(m_aPSK)); + m_DHCP = network["dhcp"]; + strncpy(m_aHostname, network["hostname"], sizeof(m_aHostname)); + m_Address = network["address"].as(); + m_Netmask = network["netmask"].as(); + m_Gateway = network["gateway"].as(); + + JsonArray lockCode = doc["lock_code"]; + uint8_t tmp = min(lockCode.size(), sizeof(m_aLockCode)); + for(uint8_t i = 0; i < tmp; i++) + m_aLockCode[i] = lockCode[i]; + + JsonArray fingerprints = doc["fingerprints"]; + uint16_t sz = fingerprints.size(); + m_FingerPrints.allocate(sz); + for(uint16_t i = 0; i < sz; i++) + { + JsonObject obj = fingerprints[i]; + CFingerPrint finger; + + finger.m_FingerNum = obj["num"]; + strncpy(finger.m_aLabel, obj["label"], sizeof(finger.m_aLabel)); + hex2bytes(obj["digest"].as(), finger.m_aDigest, sizeof(finger.m_aDigest)); + + m_FingerPrints[finger.m_FingerNum] = finger; + } + + delete[] pData; + return true; +} + +void CSettings::Save() +{ + DynamicJsonDocument doc(1024); + + doc["username"] = m_aUsername; + doc["password"] = m_aPassword; + + JsonObject network = doc.createNestedObject("network"); + network["ssid"] = m_aSSID; + network["passphrase"] = m_aPSK; + network["dhcp"] = m_DHCP; + network["hostname"] = m_aHostname; + network["address"] = m_Address.toString(); + network["netmask"] = m_Netmask.toString(); + network["gateway"] = m_Gateway.toString(); + + JsonArray lockCode = doc.createNestedArray("lock_code"); + for(uint8_t i = 0; i < sizeof(m_aLockCode); i++) + lockCode.add(m_aLockCode[i]); + + JsonArray fingerprints = doc.createNestedArray("fingerprints"); + uint16_t tmp = m_FingerPrints.count(); + for(uint16_t i = 0; i < tmp; i++) + { + JsonObject obj = fingerprints.createNestedObject(); + const CFingerPrint &finger = m_FingerPrints.valueAt(i); + + obj["num"] = finger.m_FingerNum; + obj["label"] = String(finger.m_aLabel); + + char aHexDigest[SHA256_SIZE*2+1]; + bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); + obj["digest"] = String(aHexDigest); + } + + String docString; + serializeJsonPretty(doc, docString); + fileSetContent(APP_SETTINGS_FILE, docString); +} diff --git a/app/Settings.h b/app/Settings.h new file mode 100644 index 0000000..d5a71fb --- /dev/null +++ b/app/Settings.h @@ -0,0 +1,38 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#define APP_SETTINGS_FILE "settings.conf" + +class CSettings +{ +public: + CSettings(); + + bool Exists(); + bool Load(); + void Save(); + + char m_aUsername[32]; + char m_aPassword[32]; + + char m_aSSID[32+1]; + char m_aPSK[64+1]; + + bool m_DHCP; + char m_aHostname[64]; + IpAddress m_Address; + IpAddress m_Netmask; + IpAddress m_Gateway; + + uint8_t m_aLockCode[8]; + + struct CFingerPrint + { + uint16_t m_FingerNum; + char m_aLabel[32]; + uint8_t m_aDigest[SHA256_SIZE]; + }; + HashMap m_FingerPrints; +}; + +#endif diff --git a/app/application.cpp b/app/application.cpp new file mode 100644 index 0000000..7dc4bbb --- /dev/null +++ b/app/application.cpp @@ -0,0 +1,54 @@ +#include +#include "FingerPrint.h" +#include "main.h" + +HardwareSerial Serial1(UART_ID_1); +NtpClient ntpClient("at.pool.ntp.org", 3600); + +void IRAM_ATTR OnFingerInterrupt() +{ + // LOW = FINGER, HIGH = NO FINGER + bool status = digitalRead(FINGER_DETECT_PIN); + + g_Main.OnFingerInterrupt(!status); +} + +void ready() +{ + debugf("READY!"); + + gpio_pin_wakeup_enable(GPIO_ID_PIN(FINGER_DETECT_PIN), GPIO_PIN_INTR_LOLEVEL); + + g_Main.Init(Serial); + + attachInterrupt(FINGER_DETECT_PIN, OnFingerInterrupt, CHANGE); +} + +void init() +{ + // for fingerprint module + Serial.systemDebugOutput(false); + Serial.begin(115200, SERIAL_8N1, SERIAL_FULL); + Serial.flush(); // TODO: Full flush? + + // for debug messages + Serial1.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY); + Serial1.systemDebugOutput(true); + + // p-channel FET to turn on fingerprint module + pinMode(FINGER_ENABLE_PIN, OUTPUT_OPEN_DRAIN); + digitalWrite(FINGER_ENABLE_PIN, 1); + + // communication with safe lock + pinMode(SAFELOCK_DATA_PIN, OUTPUT_OPEN_DRAIN); + digitalWrite(SAFELOCK_DATA_PIN, 1); + + pinMode(FINGER_DETECT_PIN, INPUT); + pinMode(SAFELOCK_DETECT_PIN, INPUT); + pinMode(DOOR_DETECT_PIN, INPUT); + + // mount spiffs + spiffs_mount(); + + System.onReady(ready); +} diff --git a/app/main.cpp b/app/main.cpp new file mode 100644 index 0000000..3fd98fd --- /dev/null +++ b/app/main.cpp @@ -0,0 +1,462 @@ +#include +#include +#include +#include "utils.h" +#include "main.h" + +CMain g_Main; + +CMain::CMain() : m_FingerLogic(this) +{ +} + +static void STAGotIP(IpAddress ip, IpAddress mask, IpAddress gateway) +{ + debugf("GOTIP - IP: %s, MASK: %s, GW: %s\n", ip.toString().c_str(), mask.toString().c_str(), + gateway.toString().c_str()); +} + +static void STADisconnect(const String& ssid, MacAddress bssid, WifiDisconnectReason reason) +{ + debugf("DISCONNECT - SSID: %s, REASON: %d", ssid.c_str(), WifiEvents.getDisconnectReasonDesc(reason).c_str()); +} + +void CMain::Init(HardwareSerial &serial) +{ + m_Settings.Load(); + //m_Settings.Save(); + + WifiAccessPoint.enable(false); + if(m_Settings.m_aSSID[0]) + { + debugf("Station: %s", m_Settings.m_aSSID); + WifiStation.enable(true); + WifiStation.config(m_Settings.m_aSSID, m_Settings.m_aPSK); + if(m_Settings.m_DHCP && m_Settings.m_aHostname[0]) + WifiStation.setHostname(m_Settings.m_aHostname); + if(!m_Settings.m_DHCP && !m_Settings.m_Address.isNull()) + WifiStation.setIP(m_Settings.m_Address, m_Settings.m_Netmask, m_Settings.m_Gateway); + WifiStation.connect(); + } + else + { + debugf("Access Point 'admin': %s", m_Settings.m_aPassword); + WifiAccessPoint.config("safeweb", m_Settings.m_aPassword, AUTH_WPA2_PSK); + WifiAccessPoint.enable(true); + } + + WifiEvents.onStationGotIP(STAGotIP); + WifiEvents.onStationDisconnect(STADisconnect); + + m_FTP.listen(21); + m_FTP.addUser(m_Settings.m_aUsername, m_Settings.m_aPassword); + + m_HttpServer.listen(80); + m_HttpServer.setBodyParser("application/json", bodyToStringParser); + + m_HttpServer.paths.set("/api", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/dashboard", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/fingerprint", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/fingerprint/label", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/fingerprint/delete", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/fingerprint/enroll", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/unlock", HttpPathDelegate(&CMain::HttpOnApi, this)); + + m_HttpServer.paths.setDefault(HttpPathDelegate(&CMain::HttpOnFile, this)); + + m_FingerPrint.Init(serial, 0xFFFFFFFF, 0x00000000); + m_FingerLogic.Init(&m_FingerPrint); +} + +bool CMain::HttpAuthorized(HttpRequest &request, HttpResponse &response) +{ + String auth = request.getHeader("Authorization"); + if(auth.startsWith("Basic ")) + { + int headerLength = auth.length() - 6; + if(headerLength <= 64) + { + auth = base64_decode(auth.c_str() + 6, headerLength); + if(auth) + { + int sep = auth.indexOf(':'); + if(sep != -1) + { + String username = auth.substring(0, sep); + String password = auth.substring(sep + 1); + if(username == m_Settings.m_aUsername && password == m_Settings.m_aPassword) + return true; + } + } + } + } + + response.code = HTTP_STATUS_UNAUTHORIZED; + response.setHeader("WWW-Authenticate", "Basic realm=\"safeweb\""); + response.setHeader("401 Wrong credentials", "Authentication required"); + response.setHeader("Connection", "close"); + return false; +} + +void CMain::HttpOnApi(HttpRequest &request, HttpResponse &response) +{ + if(!HttpAuthorized(request, response)) + return; + + String path = request.uri.Path; + if(path.length() < 6) + { + response.code = HTTP_STATUS_NOT_FOUND; + return; + } + String endpoint = path.substring(5); + + DynamicJsonDocument jsonReq(1024); + deserializeJson(jsonReq, request.getBody()); + DynamicJsonDocument jsonResp(1024); + + response.setAllowCrossDomainOrigin("*"); + response.setHeader("Access-Control-Allow-Headers", "Content-Type"); + + if(request.method == HTTP_OPTIONS) + { + response.code = HTTP_STATUS_OK; + return; + } + + + HttpStatus status = HandleApi(request.method, endpoint, jsonReq, jsonResp); + if(status != HTTP_STATUS_OK) + { + response.code = status; + return; + } + + String respString; + serializeJson(jsonResp, respString); + response.setContentType(MIME_JSON); + response.sendString(respString); +} + +HttpStatus CMain::HandleApi(HttpMethod method, String endpoint, JsonDocument &req, JsonDocument &resp) +{ + String test; + serializeJsonPretty(req, test); + + debugf("request: %s\n", test.c_str()); + + if(endpoint == "dashboard") + { + if(method != HTTP_GET) return HTTP_STATUS_METHOD_NOT_ALLOWED; + + JsonObject lock = resp.createNestedObject("lock"); + lock["locked"] = true; + + JsonObject door = resp.createNestedObject("door"); + door["closed"] = true; + + JsonObject battery = resp.createNestedObject("battery"); + debugf("adc: %d\n", system_adc_read()); + battery["voltage"] = (system_adc_read() / 1024.f) / 0.23; + battery["color"] = "success"; + if(battery["voltage"] < 3.6f) + battery["color"] = "warning"; + + JsonObject network = resp.createNestedObject("network"); + if(WifiStation.isEnabled()) + { + network["type"] = "Station"; + network["ssid"] = WifiStation.getSSID(); + network["channel"] = WifiStation.getChannel(); + network["dhcp"] = WifiStation.isEnabledDHCP(); + network["rssi"] = WifiStation.getRssi(); + network["signal"] = Rssi2Quality(WifiStation.getRssi()); + network["address"] = WifiStation.getIP().toString(); + } + else + { + network["type"] = "Access Point"; + network["ssid"] = WifiAccessPoint.getSSID(); + network["channel"] = WifiStation.getChannel(); + network["dhcp"] = true; + network["address"] = WifiAccessPoint.getIP().toString(); + } + } + else if(endpoint == "unlock") + { + if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; + + LockUnlock(); + resp["success"] = "true"; + } + else if(endpoint == "fingerprint") + { + if(method != HTTP_GET) return HTTP_STATUS_METHOD_NOT_ALLOWED; + + JsonArray fingerprints = resp.createNestedArray("fingerprints"); + uint16_t tmp = Settings().m_FingerPrints.count(); + for(uint16_t i = 0; i < tmp; i++) + { + JsonObject obj = fingerprints.createNestedObject(); + const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(i); + + obj["num"] = finger.m_FingerNum; + obj["label"] = String(finger.m_aLabel); + + char aHexDigest[SHA256_SIZE*2+1]; + bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); + obj["digest"] = String(aHexDigest); + } + } + else if(endpoint == "fingerprint/label") + { + if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; + + if(!req.containsKey("index") || !req.containsKey("label")) + return HTTP_STATUS_BAD_REQUEST; + + int index = req["index"].as() - 1; + String label = req["label"]; + + if(index < 0 || index >= Settings().m_FingerPrints.count()) + return HTTP_STATUS_BAD_REQUEST; + + CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(index); + strncpy(finger.m_aLabel, label.c_str(), sizeof(finger.m_aLabel)); + Settings().Save(); + + resp["success"] = true; + } + else if(endpoint == "fingerprint/delete") + { + if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; + + if(!req.containsKey("index")) + return HTTP_STATUS_BAD_REQUEST; + + int index = req["index"].as() - 1; + + if(index < 0 || index >= Settings().m_FingerPrints.count()) + return HTTP_STATUS_BAD_REQUEST; + + const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(index); + FingerPrint().DeleteTemplate(finger.m_FingerNum, 1); + Settings().m_FingerPrints.removeAt(index); + Settings().Save(); + + resp["success"] = true; + } + else if(endpoint == "fingerprint/enroll") + { + if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; + + if(req.containsKey("cancel")) + { + if(req["cancel"].as() && m_Enrolling) + { + resp["success"] = FingerLogic().EnrollFinger(false); + m_Enrolling = false; + m_Enrolled = false; + } + } + else + { + if(!req.containsKey("enrolling")) + return HTTP_STATUS_BAD_REQUEST; + + bool enrolling = req["enrolling"]; + if(enrolling && !m_Enrolling) + { + if(m_Enrolled) + { + resp["done"] = true; + resp["message"] = m_EnrollMessage; + m_Enrolled = false; + } + else + { + resp["error"] = m_EnrollMessage; + } + } + else if(enrolling && m_Enrolling) + { + resp["done"] = false; + resp["message"] = m_EnrollMessage; + } + else // if(!enrolling) + { + if(!req.containsKey("label")) + return HTTP_STATUS_BAD_REQUEST; + + if(!m_Enrolling) + { + FingerLogic().EnrollFinger(); + m_Enrolling = true; + m_Enrolled = false; + m_EnrollLabel = String(req["label"].as()); + resp["done"] = false; + m_EnrollMessage = "Started enroll process. Please place your finger on the sensor."; + resp["message"] = m_EnrollMessage; + } + else + { + resp["done"] = true; + m_Enrolled = false; + resp["message"] = m_EnrollMessage; + } + } + } + } + else + { + return HTTP_STATUS_NOT_FOUND; + } + + return HTTP_STATUS_OK; +} + +void CMain::HttpOnFile(HttpRequest &request, HttpResponse &response) +{ + if(!HttpAuthorized(request, response)) + return; + + String file = request.uri.Path; + + if(file == "/") + file = "index.html"; + if(file[0] == '/') + file = file.substring(1); + if(file[0] == '.') + { + response.code = HTTP_STATUS_FORBIDDEN; + return; + } + + response.setCache(86400, true); // It's important to use cache for better performance. + response.sendFile(file); +} + +void CMain::OnFingerInterrupt(bool finger) +{ + m_FingerLogic.OnFingerInterrupt(finger); +} + +void CMain::FingerEnable(bool enable) +{ + const int pin = FINGER_ENABLE_PIN; + digitalWrite(pin, !enable); +} + +void CMain::LockSendBytes(uint8_t *pBytes, uint8_t len) +{ + const int pin = SAFELOCK_DATA_PIN; + + // Init + digitalWrite(pin, 0); + delayMicroseconds(10 * 1000); + digitalWrite(pin, 1); + delayMicroseconds(10 * 1000); + + // Send data, calculate checksum and send checksum + uint8_t chk = 0x00; + for(uint8_t i = 0; i <= len; i++) + { + uint8_t byte; + if(i == len) + { + byte = chk; + } + else + { + byte = pBytes[i]; + chk ^= byte; + } + + for(int8_t j = 0; j < 8; j++) + { + digitalWrite(pin, 0); + delayMicroseconds(100); + digitalWrite(pin, 1); + + uint8_t val = byte & (1 << j); + if(val) + delayMicroseconds(300); + else + delayMicroseconds(100); + } + + digitalWrite(pin, 0); + delayMicroseconds(100); + digitalWrite(pin, 1); + delayMicroseconds(8); + } +} + +void CMain::LockSendCode(uint8_t pass[8]) +{ + uint8_t packet[9] = {0x51}; + memcpy(&packet[1], pass, 8); + LockSendBytes(packet, sizeof(packet)); +} + +void CMain::LockUnlock() +{ + LockSendCode(Settings().m_aLockCode); +} + +void CMain::EnrollMessage(const char *msg, bool error) +{ + m_EnrollMessage = msg; + if(error) + m_Enrolling = false; +} + +void CMain::OnFingerVerified(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]) +{ + int fingerIndex = Settings().m_FingerPrints.indexOf(fingerNum); + if(fingerIndex == -1) + { + debugf("OnFingerVerified: fingerIndex == -1"); + return; + } + + const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(fingerIndex); + if(memcmp(digest, finger.m_aDigest, SHA256_SIZE) != 0) + { + debugf("OnFingerVerified: SHA256 mismatch"); + return; + } + + LockUnlock(); + + debugf("OnFingerVerified: OK!!!"); +} + +void CMain::OnFingerEnrolled(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]) +{ + CSettings::CFingerPrint finger; + finger.m_FingerNum = fingerNum; + strncpy(finger.m_aLabel, m_EnrollLabel.c_str(), sizeof(finger.m_aLabel)); + memcpy(finger.m_aDigest, digest, SHA256_SIZE); + + char aHexDigest[SHA256_SIZE*2+1]; + bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); + debugf("OnFingerEnrolled: \"%s\"", aHexDigest); + + Serial1.printf("(sz: %d) Finger hexdigest: ", sizeof(finger.m_aDigest)); + for(uint8_t i = 0; i < sizeof(finger.m_aDigest); i++) + Serial1.printf("%x", finger.m_aDigest[i]); + Serial1.printf("\n"); + + Settings().m_FingerPrints[fingerNum] = finger; + Settings().Save(); + m_Enrolled = true; + m_Enrolling = false; + m_EnrolledFinger = finger; + + char aBuf[512]; + m_snprintf(aBuf, sizeof(aBuf), "Successfully enrolled new finger \"%s\".", finger.m_aLabel); + EnrollMessage(aBuf); + + debugf("OnFingerEnrolled: OK!!!"); +} diff --git a/app/main.h b/app/main.h new file mode 100644 index 0000000..3e6143a --- /dev/null +++ b/app/main.h @@ -0,0 +1,65 @@ +#ifndef MAIN_H +#define MAIN_H + +#define FINGER_DETECT_PIN 4 +#define FINGER_ENABLE_PIN 5 + +#define SAFELOCK_DATA_PIN 12 +#define SAFELOCK_DETECT_PIN 14 + +#define DOOR_DETECT_PIN 13 + +#include +#include "Settings.h" +#include "FingerPrint.h" +#include "FingerLogic.h" + +class CMain +{ +public: + CMain(); + void Init(HardwareSerial &serial); + + void OnFingerInterrupt(bool finger); + + void FingerEnable(bool enable); + + void LockUnlock(); + void EnrollMessage(const char *msg, bool error=false); + + void OnFingerVerified(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]); + void OnFingerEnrolled(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]); + + CSettings &Settings() { return m_Settings; } + CFingerPrint &FingerPrint() { return m_FingerPrint; } + CFingerLogic &FingerLogic() { return m_FingerLogic; } + +private: + bool HttpAuthorized(HttpRequest &request, HttpResponse &response); + void HttpOnApi(HttpRequest &request, HttpResponse &response); + void HttpOnFile(HttpRequest &request, HttpResponse &response); + + HttpStatus HandleApi(HttpMethod method, String endpoint, JsonDocument &req, JsonDocument &resp); + +private: + void LockSendBytes(uint8_t *pBytes, uint8_t len); + void LockSendCode(uint8_t code[8]); + + CSettings m_Settings; + CFingerPrint m_FingerPrint; + CFingerLogic m_FingerLogic; + + FtpServer m_FTP; + HttpServer m_HttpServer; + + String m_EnrollMessage; + String m_EnrollLabel; + CSettings::CFingerPrint m_EnrolledFinger; + bool m_Enrolling; + bool m_Enrolled; +}; + +extern CMain g_Main; +extern HardwareSerial Serial1; + +#endif diff --git a/app/utils.cpp b/app/utils.cpp new file mode 100644 index 0000000..d082b36 --- /dev/null +++ b/app/utils.cpp @@ -0,0 +1,75 @@ +#include +#include "utils.h" + +static inline uint8_t _char2byte(char c) +{ + if('0' <= c && c <= '9') return (uint8_t)(c - '0'); + if('A' <= c && c <= 'F') return (uint8_t)(c - 'A' + 10); + if('a' <= c && c <= 'f') return (uint8_t)(c - 'a' + 10); + return 0xFF; +} + +int hex2bytes(const char *str, uint8_t *bytes, int32_t length) +{ + int result; + if(!str || !bytes || length <= 0) + return -1; + + for(result = 0; *str; result++) + { + uint8_t msn = _char2byte(*str++); + if(msn == 0xFF) return -1; + + uint8_t lsn = _char2byte(*str++); + if(lsn == 0xFF) return -1; + + uint8_t bin = (msn << 4) + lsn; + + if(length-- <= 0) + return -1; + + *bytes++ = bin; + } + + return result; +} + +void bytes2hex(const uint8_t *bytes, int32_t length, char *str, int32_t strLength) +{ + const char binHex[] = "0123456789ABCDEF"; + + if(!str || strLength < 3) + return; + *str = 0; + + if(!bytes || length <= 0 || strLength <= 2 * length) + { + strncpy(str, "ERR", strLength); + return; + } + + for(; length > 0; length--, strLength -= 2) + { + uint8_t byte = *bytes++; + + *str++ = binHex[(byte >> 4) & 0x0F]; + *str++ = binHex[byte & 0x0F]; + } + + if(strLength-- <= 0) + return; + + *str++ = 0; +} + +int Rssi2Quality(sint8 rssi) +{ + if(rssi >= -100 && rssi <= -80) + return 1; + else if(rssi > -80 && rssi <= -65) + return 2; + else if(rssi > -65 && rssi < -50) + return 3; + else + return 4; +} diff --git a/app/utils.h b/app/utils.h new file mode 100644 index 0000000..d0ed894 --- /dev/null +++ b/app/utils.h @@ -0,0 +1,8 @@ +#ifndef UTILS_H +#define UTILS_H + +int hex2bytes(const char *str, uint8_t *bytes, int32_t length); +void bytes2hex(const uint8_t *bytes, int32_t length, char *str, int32_t strLength); +int Rssi2Quality(sint8 rssi); + +#endif diff --git a/component.mk b/component.mk new file mode 100644 index 0000000..dcb4953 --- /dev/null +++ b/component.mk @@ -0,0 +1,4 @@ +ARDUINO_LIBRARIES := ArduinoJson6 + +HWCONFIG = spiffs +SPIFF_FILES = files diff --git a/files/.gitkeep b/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/files/bootstrap.min.css.gz b/files/bootstrap.min.css.gz new file mode 100644 index 0000000000000000000000000000000000000000..71634cbfdeba63409e3c25fa5ca092c3541fafb3 GIT binary patch literal 18466 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 DWey7_ literal 0 HcmV?d00001 diff --git a/files/favicon.ico b/files/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..23ceec5c4cd0158aafbd691c5c778a7da013ab6a GIT binary patch literal 1150 zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?&U;z=R_~FBcvR$1WyM6#Tt}>aP7b!e7cN{dg6aSM{X3Ve zjMOV)^@G$kH8!-t^q)L=(u#wf^)E;q8RPOlHy6jR1@q^n!}K3Ne$1YqkLMe*UJx6X z{+#TrIWWC2x~r?R4x|Se((HcJ{W!J;zeU*y&yI&{TtS=3y0~0(Pz(|wE*cs z#<=uHM1-yX`}Z#+OfQHoEh(OYtQW+_rJs|7?N45A&di%PZ))GYdsnTgv7rMLrXW4Y z7?*yS8c;d_VQg^+QUlUoR9G+p#3l#V)z!A#zI|Jzs + + + + + + Safe control panel + + + + + + + +
+ + + + + + + + + + diff --git a/files/jquery-3.2.1.min.js.gz b/files/jquery-3.2.1.min.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..269d2de4f5eef069a9295ae87775c8c6548cacdb GIT binary patch literal 29995 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 AX8-^I literal 0 HcmV?d00001 diff --git a/files/main.css.gz b/files/main.css.gz new file mode 100644 index 0000000000000000000000000000000000000000..772ab62f41e4f23ee429ec74b99f68e1037d9858 GIT binary patch literal 653 zcmb2|=HNIGcrlWRIX5vgPcON+m|xsNe(W@rC+I!|EA?T8g~RJX9Gh84*Qm~n8Q zY!}Mf>&hBtH}w*;u1X`H!Rb8Pbqz}l3u-4%^RqpiJ!?ylX0Ej3I^Az}{V(-=^VcOV z&gOMA50mQfkeTW#?LKu=l-a$tYqb*Io((nWzEI^WvFV!G{R37HH@bTEXD%wq=&jLn zh+eGQb!kVIWTcgaVSBxR>+2@9w>gZKPh|F8>=I+WbN06Gd$AR>Sw#)rZ(}}|BjS8y zhyP=rwPcVd73e%gH@?BTWs_J6V`W|slH)wb&nF*l+5YwV<>tOHyQsc(@5-m4FFWq6ivMkArpLKmWy!-7(SVN$3(}L% zIx98R9Xzpp{dtA=>>7VdLYAG9e9paN#@V#R;rm~g^qG{->foK`eKu%K+{u6M5;d7B zmt-w8n3g`tm-+bBTb~XvoNAaO^6*+=#6SHv529us@7iTox7?+~Vk7HjPm3p3Ru*ov zYg?xEFMO(g;o0+xpWZw)iK;K?Df=yW>#xME+V*e79kGv!-#+C!ynBzaW8KfW(o_B? zX&=z$aeT6rckAaneG4bC)yF@*`A7QArIcTd^3T9*p_F>Gs`#=7?0D@VsN! zmoDjxXItnUZ$352FC+CLi*1=T>(n56)Fz$o|9sT+_{xyig{k*PDACC9lk%#F^JC!&0gU5Y}XAw zW}fP1@Adav!z+?y@&2v^(e0XP_F}bRV&EoflDOG+?kE@*76TUHHL3;X%=^-vfTT51lOi%7w`cPG+ z$M)cxRRYpSRJN8~>i_q(P1$cCuE!bJ4bSH^1=n@TR}p^R1!JR9xM3 zzF(Hz{3|t?GuqPGGFz*Z^%8w+8ml%sZImhP?JeV(l~*;iXzE>^$vLG<~nY#b)X zMJ`2kq^4TjdAKW6EbUnP{^y@%Y?ZRMcRhW0MN#7W4e0{sjKDh_FCXdFJz(E28@kqg zMeU7CD$R2&Y9F613|YKfy+lxhFS~E;$2v?k`{hvxINK9_r I!Op+{02@qdNdN!< literal 0 HcmV?d00001 diff --git a/files/nunjucks.min.js.gz b/files/nunjucks.min.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..ff6749fbe9f1f06e4ec5b8624ef14ff949665c21 GIT binary patch literal 22603 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 literal 0 HcmV?d00001 diff --git a/files/popper.min.js.gz b/files/popper.min.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..78a60d71dd6b6d9fab2fcd4c10a2bc73966b22ce GIT binary patch literal 6963 zcmb2|=HMu=UK+{7T##Q-kXoddo0+GVRm?EQI=lF`*|z(?wV&Kv=iw%xo4UL>eMN$4 z=VOzbrH^tRndFN4dUsD~ZByEzkaB$Lm;3K6D<&*(QQup>Y-`8&%KZ&UWu4>JbGW%?|1m=vIq^m zt_gyoxy*%s*`rr91eL}cZG5v)f9Zr(uf8+e)Sl#?#;TrIpu5TU+_Got+k#heEmkSG zZDu|5>fe1odoP!ona*F>B_y_AH1(v4-aYq)Q6IamOkj+VKETh(xWG52zQ0xHme(>f zp?!>pW-{*ke#mde;fE!=`=?g$dw0*SnE1?c&CTo98qKEhZI6Y$B4avwVi|W#yjf~C z|IV^a>MvyVjS9u@8&(&x~ijJVChiKlm{z8EaP#Ts`wyJIZ?y5on#5Xz zenz)%Pm3hYe!GkH&uhK!+qd4eY<=KkhXOG{&q|%VnK92E^-YO;oE}uUHeP1Y^2A+% zN#VLn7{2%?AJq~)zA8HceYn=_Gih~nyR(WVbP7+bmRp|9$ zuuN>b{!sU9)=Sr)7MVxHrMUjvPkDaN{KM8u=N_?oSkC!zhj0JZ7G~eK$~+%GpWe*B z{`~!zzu&%n`n!5{`1Jg`>cYyx!k-_n&X%7zpJ9s<>z`FE6Q^7|uB+lLT0LFNT&G7Z zlgH^p+|r=dIXy~?Za;Sw6T5I}!4nhLfNP5?qI4XNwAcu@dL2wV!I^Nj;aTPW^&ir= zKQ;*E4@(T>u9JLr(ZpZn&kNbbH_{tU+n|I`KayR#y6f4z3aHEBkLuNQJ`=Ktzd30TC_vEY|DYl@V_-LB@@!yX_wbo~XyZZaJ1lPVUM4efA8I(;l&yI1B^$FLhutTiwFj<+ zq@D|vm=fQVVK$4u>YvHK3tQN>m^QhwJluNgzs#!0gq$RUgkmP$!-{u>HYZFhHu<>i z=~h)mjeuSLSpyXQrt@iq3hwl(=u{@d;@&Qkg^anko_li3V^jTsAtGOo=`yJ^kg ze&|G1l!4;DC&ubOg%efha;V?@5bAAHV=Z}KKWOWP8*d`{1TStsH>IRoS}iS}X}QWT z9dD!EA20AlGwoj7UiC`uP~*B2tP=AX6t$zIE+xK6I`U<4rq}MBCbQEGF3MauQVq3u(DMn<_`G;uf#c@_MxUM&iypo%eY}5v?j*BY)eYSY zr{8#+ZuG8X6zrJ#Sum^E_&i5O_9EfWf~SNk_HeWuFRu~38Z*u9A=@Iwj;C8gFI2H~ zCR?79UElq8$+YLECO@xkH?P+yu;tNSrk?C^|A)B8my!o({v4>>zT2MX-TN)}kB@Zu zXKs*`yy2dqMusWymQq^l)iw^JD*&X(qe{;F8{fMesIz6m{DOiZD%D8z#9(UISwdaavN*peW zb}+rQ)VO7#cGRNs#Fz7Odjr{CrmvfkGxy3fQ~PIM?GK)>bX$M_x$k-J*8vAS`{ym0 zcKx!5kA2d#lyA&23dQRZIp!tWl<$7tTwvL{@1PMU)A_C%L#L-Xp<+pgb#Ezr-g|Po z`>{VejD#B{ocW?RitqFgz41Ki^&&_2{hY-YpOy10`c=B_lA)m$gErgR1E~Sh&&$3T z32a@fCpAY;YL3qZF%RLVQi^8h4xL9X*f*;_m}8TpIr*c?^0-dmHN_BP?@ zOWv0gtXV%jJ-+k3tToGqd&y5S@|q)49GlJawYk@qdId$V-FQO6WKR{Zm9yp59~Ls& zZ%+8GTK4hGhQn_p7N}kitm~Tl%(}4IG&23^9IYS50h@E`epQ_K%CDJrbxzWU0AJA> zr*bR*D{q)Ygf=Z_`@MQvob6Pjz5U*rQw{{Fy$G4;d$wOQHcB=4twqRLPg3s2ne;JP2I>PPBMQx5= zjCFjQp02pJv17W^yyX+xuP`vI4S5sw*RCaN>WPDgmI&---M`-T*z`FktB$?5jcZ)0 zt=e4Emb#?*_A=VCOj5(niA- z)hf|O9eQGGOxM42-F@)jJ%=j{Y3GWo=Q{^y9lll4Gu2fg_v@TcudeE8J54IM6<2vB z&Ud-KLEFSlG|g+}lt=GsFNbVfvp)Lt9OfC3MMi333NP;ZN3dUrKg7nc$ey?B*=^_M zqqle#Y+kxOZK8(Y^4$&8Ju|#m|miCb?`4$1;Z7 z2f9t_qnl4#DQ>w_EB}k3O!0wkf8Vv^dNY2Po?&OfuxbP9{cjYnT*u8U2( zEEcrrM?l&IUac7|v6o+-?2uVwplsEa*YY~~^rMFNBH3k6wi@a=mtOI^oz)nw=YIH_ zR+!D<)&*-87HS($SeFpnDERNhI{V@+$JQU6|KYpQjMp>rH+nq{7VT5&m}jT3_S_{d zqX&$e#S(kO>P8*As581Db2xV)|Vz*8f(*t~-)_MYD6 zv%2g4rY9*ZZ6WW<&L39N`8%7Zn(txZ&Xr+_FFW6zX5$jf-_rL|OZH{IaH=?~h|rpZM8$=aAy@ieQae}2#YyqkjFT8DqKuUJ|$rBO9^PJgZK43TSl z7b_=KAFsXgZo!K4?Z+?N-1DyJ$kxw%FRaz>t-Z5){RQzYcNXn;D4%1m`@`yJsJ@Cv z!3|gU{&QbS)t$f0@PA)^@2_6iq75nsR-3<^ec`lN0;@{kRqnWg+#j=_%Jc00b0@&{ zxXJp zJ(>Prxo5}WCn;+ddzPidva+3d(6?7^(ZP@XH5dKZ_?u(zi(8p)JI60`h4Y$`wN1v` z=t4=hlNPHxRg+_TY7d=VuQ_wx71q87-xC(FeJC#yxbteZ;t%OBbG9FR5Vq-m-j0;W z_rLau^Q2^pBuxD!+y3G%=g*m6)&(6DAr%X|zIXn*{^eoDqq}{7fB!F>`7-`VuVLJY*;kqp@6Fk5;K*}Ka9XzF4*u8Y z1jSNjmkY_TJQkZH*&@!F&+t3{_D_)mPqyy+WWjql^Hb)I-zFW)=5KPJpY%t%_2c3f zBCA(5GbL(e8dlp}@0slTmt)q9TT^^nU%qeDy%{?BRo~-psas+l)(HQR-E^U=>!j(C zjQQ%zv^~zO=Mm~ix#YVpfouMo!sec&tydRLb^KOpCD$u=a__p*brb$P%@e=#>QJ)e zO{L{?=LMOk{wn?vp!sS2l&O>R_5MeFwc4t1y)gW2uRQ-wL+ObZ|ID_uJwE5&)Ox?e z{>^unD{gqXaFRHS+obg$Z}#u=E!rK^n(E$WZSuZEYns;-u1&u~<{mng5#(fd|NZMI zr8=`SY7!r|PW^UCg=KG7y&nKpG%Fi+@ zxcp|dudXo%7q2*t|3D^^A=2!#uZD9;@2z+93JN|4Zqru#dCEpO}B%d8T#3 zll0s3>lFVpZq!uZ+MT+pNl7SgUQzd3+-=?*ruiq=e|dOiN)3O(ymWJp2UDL&rZuTlFv`_x?qe|N z3G{gV@Vv`L_50B)+uk$Ex+nasfBW6tKV#y(PqRfH^dBrcvD`ePSC-@cd6C)8!cD!$ zj@`^CKh&8Z688Ue>$>(3M*Gt%5-vZkvJ2~D(5U-yDSj=wt|aOC+K?HS7aaOr*1|5i)y?L>b*W~?eP=y(8LXI}TAITt z9rw{xSo-PVRUg?Jz8?@;vwT8RVE&DF&ri*cY2S0J%Jhuq=}+n$E(wm0jpO#$m)?`^ zSMX7&@n5n%{qF<46x;BZ%o_s(mxKk~`8r|8j|cB!ye@P-(9z{uSoPzb$X<88l^Oc2 z|Fc9CHz=oar~NP0mV36vTjok!$y3)Ei?~x3vKquD>3IJ=me*d?_^tG0zFC?~hvc1Q zKigO(x?cTyIrqt_KG!2`zuZjkc`;_Kd-pN4W#0|KQk7dPVqY5yw_RPBm38+-@w(pz zSHpkXc7$JDrs4KW&*;dH8NTvOjuY3i*$OUk6nZk@heXot#3iXF7gGx(HmOS%ix)<4%TIyFFzbL*sgM6xw^=yz^aIKQ(N6dF$bWHjZvV3=a=} zPdIe+x$o9G{~)$)?C%}g)tc0wIbHd~V>s#h&N$nwPj+t%lb=nx+`CKQ?3Y=R-;^%r zo9xY7SZ0{kwR5VWZCZt&Ls*dbt^dqD42y4zi?6?@#wb?KTvS$A`R#rG`g`-$4=Raw zpD&kD$|x%P`S9%3pVsvOIb26mjMM$E-MzKINuTfP($b&TWMkDZ>{3`@f}lU|3K zeoR}SrQ^Iyl6a%T?6Gi=&3f6;fg+K*m!Q+6BnCI4x3vSoenXU%_snDzU# zX1~{XwMJ6(`0w*K=Gg5~?|D~d@GRGM>e|GFCF}IIdhK0X{Vi|LzN(*nM;Z6jzHj^f zTKvPdHiM9LZ3PUsnZ?uZ)rzgr(Q}#ASXwY&A;niDa>s*B8ClIAa!&=lvr#Vl#+l$5 z?UFI|gxXSFF=>unr_abPHI2Ha?{{I%`7hnA2V|rLk{9%czSz9Ab_K_Sm1h^7;rMZQ z^}VvSubRF;)SXu*cPBKezM?;Tt?jcHCl1}pS|9nx@#CIF|8m)L+oxQf9TT&B=U;L5 ziGQE#RY}g+qc(fRfptmk!5q8Rw!iu?XUmSpckdqXIowyi)vYE!)3#7N?CrfMr^&fn zAN^S6%3WY2=lJQ`oJ~5D*0rnns<5lb7t9F?~zMCXLy- z*ZR0*<6bj5vSpUN-s{jM%kIzY;kW%(rRr2OOUa3wd5mW&Wq;*)qBED@`-kw)@9VD> zNEL@pl9^lf*GI19)S2Rv>Kd2r_IG;(b*9g%pZ-n$>JHccKe#r|-u~v*k9k-BI17vS z?#iCqdHreyYwR~3)}!_RAFaFbzAt?8x}yop-mcZPJNuDwVNH^(F^gS{!<*9e-VV91 z9v;?}h+F>2&QkCBw1l|1P7l8e^VR?QE4-oqs*>(a_jO_KWi+JP=R41FFNkX~Tv~a9 zmr<+6*=9!R{L4>HuhBJSZBU3(`14s>LFCJFf$JFpZ438ruC)!bXY9`6Q0!yp{xtQt z$kw!%T$j$Y#^-KKzi6^zBMXmCY_n(N=?qrK_4`uo_Ay`2v@TrZ^~yNqwcf!jnI-?) zUZ3QD;_+?Lx8}39KmY8X8xxs-+fAVE{Q3n!4ob>F+b%6@f3j7OB{T4fZ{v=<01l@$ z(L5>>1zf$Imi)i>Gp&5*Y3EI|)o1TMY`dfQ?HTh~K9BY(lA;`y`#&A4_lbUhI23DqBhV7qJR@74*Whc*f&?{=D}B%;cCG3o!uzI(1MY>M}zGTNq{2wCT?y?4gL9fxyt zf4+FLe7n5;pMMXx_kBKKec^R=!L-b#Egi;AT(=LtGV4@!FBJ*oJ-KF`ncni5Pa_$v zkNR5PJW+Vrq386(tE^IMA4%1p+HwDO#_EqE|AbV`4)?l{UzyKxn#7_ou}xvBFX!2ck5)5c_k3%2 zsGsiR9Hz15gV74bOC5Fz+|?7=K3{5)3p2l>v_<_a?@`aPvS;sYr~G5w?3KPYc>lts z8+R~=PjnLI)qLEQ{X(Vu{Zx)=4nYnVeYdQ8^_J<~D|3}6UfnnQ{b$5Idg8CXq-V0` zwups#8<&2F@z9nM5EBwtT@xH9w>aPMn5bUkae;-wfA$M)pK#-==A<(Djln{_aq;RW zGWt%|eDGj%PCGT{<&vY~bxK@+L+_uOvG8=v$K;RfXG7oUu`$g!wLUh>X>LY$>&zY5 z3GY;AcUD+tYfZZGq@W|zf}!wD+HI@Z>h@go4=s4JOUFg@LXKBhy5=T}+KBc3cN<%# zS6>efKlH`+*NmCz3p{rGFJv7CBn?FYNH_y^(x+ksyC6 z$G1fx7pJ@bIx5xT?Qdf*`t#)CoHvPivlkzl_iQKcnXc8op89jPpFPqS9jNTjJ?S(5 zMkDt~p`f_x$m46h+$yq6R0ZZeq$I9^?KAfn1c7sr0i}OzBM~aW+ z40$5o>e{Y7DqjBfY2%c{%dy5awY)!e)jw(qNSeRfYo%R(%omBN5i3j8_VsM`eE#l5 zw&2Bw+c)z?hcy*{iLA_2{V{nh%kBGRH;v0mRh{+*9+~}S_ub0fQk6B&h1(*%1jM+X z&E35CiRVOfzqAi+)m(BiT;av(m9N7NI3JanCfj$zM#lfk_Mbo3uveesKCH2&{m|U? zFWPgpu6naP@xHp^>5b4#rq1iqI_mD{jrceC?P_@1l`6L)tL9MlQt=NyXRk+n`gle* z+-v@aJpIawH)r2C+ZFHIo;t~8?=iiOOTGfW%$s}Ibd0S#ZK8gk$#|RI8ra^}{F6VP_|W(5qAsZS$!Q8%tbPop`!=cTgJF zQJsn;>m{dRKCI!LpwsNwy(L~{!J$n?6W1wT+`j9|u9xjIX9ev08t}w$`-JX|rg#3R zT}Z9*jXi$r;jOSBzneQWS45mP3H8i&pSJT?b5-pN-D>AY#q&}PZ}{w!-`$&bNhSPd zxzyQ>f1Y2;{&D)=9(}!+{-^4rEH;-H|M6(sa4ln%%J-kG+pKdp&Hc9MgEE)Riyv3x z_-609^^AR=QD7tatB! zu>N`Gjdh|sds)iU{VLY?Pu4p-b4O<7|EFt?g;nobZ*sco?EkQD&&+e;qhFO;f1hyQ zciQ}2<)8IK-$WhzYyFdFWo+)wV#el~3~66I^k%iXIBu4=jh!htSL^r*fvTlE`wkxY zt+L?zyJO~)a@4N2q?)Nf3SBKPJI(c!=clrF*E`FWEqAL}a RWW@bpPptd3LdBbb0RY`8dV&A| literal 0 HcmV?d00001 diff --git a/files/settings.conf b/files/settings.conf new file mode 100644 index 0000000..99c40d7 --- /dev/null +++ b/files/settings.conf @@ -0,0 +1,25 @@ +{ + "username": "admin", + "password": "password", + "network": { + "ssid": "", + "passphrase": "", + "dhcp": true, + "hostname": "safeweb", + "address": "0.0.0.0", + "netmask": "0.0.0.0", + "gateway": "0.0.0.0" + }, + "lock_code": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "fingerprints": [ + ] +} \ No newline at end of file diff --git a/files/templates.js.gz b/files/templates.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..ff7a6ba78b3d1a23c1ff88e12b6c6580a73216ae GIT binary patch literal 4065 zcmb2|=HQrk*)WQUxg<5WASbaTwOB8!nBlEVcJXbqPrK_`SEV>*DV^#QUcNT<_p;u} zneXqY6`y-*cVr%qi<3}x*MkWQ` z%2_UQvrlzXSH=t5j1LUSvx@^JGtWA;KT>AH7v6;UJQD@&LkXJ#&pC@<-MhTkxvTKn z+>?5CqW@ZU2q`;WGVS@-a?J5a+;UAHHHVO6*Mr5bhbm}U%hg@lR4tG^E#TqGO^a2| ztF7DS<-MWWvBK|9N7~JQp3m3USUpjC`qA_G0~I^LFy%|$jSE7|ojyN_yeZWp>2q-E zcc!TRmmL#*0w$(c&z_v_nlBu2N}r?mP~n@_CST+8>>DK}NUu=Nzn$T7#{LUT4>WYv+Mk@I#u4Yqa7zih7IayM%JcD`GzGo1b7HZbTVa_K0} z4tbx-+3UTl%|yajseW(6vE(U?oXi;~lUbghR8#olap>OdBNy4lu1^vTF+8V6P`l3e=|BHX?&O{~&-MVyv({_Gil zef8HoQU6;#Yrh%U^9n0Q%TKr7%(6Z0>VxC!KA$+nD9d56_=0`*>z=zuRnF}CtMat- zwaCe(!EdID-Bt2yHjtB7S6m~qQd^pf<8Mp)qxr}4|Ch}5oz*g%%erAl*_Ws5TFi{i z?lWnvaCBvEyz_d^h4~!?E>9Np_nm8<6P&&0tOb|Mk7K>T*EP+!9yp#__GixCLpKX$ zjT@WIW)!bpcpy0_Fz58zmhTyi*MusVa~=CTx9*rD>i@O%4|`R}DwaGCvvQvId%{vpOPZnsKMu;$BG62D2I#tNleSyC(0CQ%lfHaSSvvV3}FD zsa^777w2L}R<`$h4BF19C6|?GU1Bai+TNJDrAm-3X_I%|9Z$W)GmH278m|0qYq7{# zZ0pu53*WraFgU*d@20(_?=|V5F zIKvMsG&S3LscA|tR5y-EYMJ(2)u!=>b#BF>Tg9G?Hvft&_n+Rf!0^iyhp;I|uJuAD zs=O=SdATXr-tH<<*k|UoigoF}@QpjFRvYP5&h!nBm17n&JowK*dOurj;FMF5`?;5z zhsxb~G=V95%FM4pB4?tPoqD?}vexub=k0fA6ZUMJa!_{N=hZHnMXMCkIU@x1Z*1mi z=(7Aa@1SjqOICB2o6KXaGl!<+D0<4q-F)0%VN)m1)ngHI+*5)@=a>U?)5&c+JSIJ! zdVxFV&iUmF9)xbVBC~k$W7pV%x7)veRV;Njb2a{hP{R zma4FXr^+@*!%PHEOk6W1{M&|yErxmyQC~XF3hY_Jd{yNrQ~S}TW3D{x%)Rcjn2JqJ zR;z74{Y(G#3GdZmU8TDZ$MZkuA_~wPk5H?`dciu^XM|`Z`bxDmABVT2@^7Ku?boC^2w6N4q=>YYv;yV z-0hEE{@0*kIiJ~ltI&^2)~;G4nCQ!TA;x-AQhIpcJ>|Y7;_sgRpDA~7#X*UA&M!Ay zo~-bEdV-jEg2?^akn-?Dt7qxNb!%T|e133F)n2D!yX^(3x=!8P;l(M-m#DX@rhA5N zpAa72ed*mzz2`R*@=8xnS))|GGGlu9cBOa0GB^Dt%S?ByoL=K&c`}uG35&$soJ0D* z4zJPoJ-ISNQO}yaKPwVJH_o)-2=RK#r?&g+i_J5|5ab)@K>cd|caxO{yuA&HR!r`%7XV#OP7AGn6k4b=cS?3j}_0qgj61MfA%Zv^Nb(iLif`zyTokW zqPfiQ*orN6s-J@oha2o)RdHs+g9{q6=Y{L$GKXHjJKN@Z_v{{16^|cRYMs!hYb3QNY3+|>*9qkW$1P2;`Zb+B_QXHJWT>WNJXTfJp=KKRllw#7(w!l#KBM1x;s zyB@!qRunjUX4}T)uZ_=d-!{qnle5LqPu538Hl46$eLj&xSm)l13mL1*9)?!jZd>7H zD*ol-&1vrx>$RVyC~@WNX_JW5t?w&|x^<^gCsb;IsFn8RQ$9P?)%hH>o^X^;^?Ar9 zDm-d9rkks5^4Z1943w+ScBXR4^Da6t#X&X5tJ7fahYt%6 zcKe8ieB$(9?&D*@SMmMZ58FGREl#&z7Wh74bqtGUbHKeGr(MjwA-f;+$!<`eCOU>^CVdxZ-KV1%3BvA*C$EjR6Uq>+x^DM1&?a#*4+x3(41Tq z_ttW&LO?WsF7Ju|jh9PUd5_1G_iHiu1^xU#p*KU0^DyW1i={pHwtY~tHF|C`cP97E zl}|c;S3WEIE$Py~uORx-f#Z1_xv#QrpO>!Vzv{5>8nH7|{k+$n7QNN@@_W6;?yrZ> zq}GOBcri)cUyI={r|GPcp8?YqeP6F)+oE1+`v11Y+;6$_eU1A)Y7ZX2bmr8ob+bxJ zI_IaQOMdxX!IkhKN|r&xv;3p!QpK<5C+s@LxpBrk{>&(A_AqPnWy|ha$+W3om?q1i z{Xy&M#Pn-V9xi$(W7RDxr4kuXvsdv{v)`u)le1;cYx!MnESxquQQ#+2K;CqheHxuL z8z-E2tLm~$huJ=PV#btbDUT1Zty;M7iGIVxl9v2l&l_wz@4nW5%X0hlJge=?j(p!F za{u&B^&4fc*w>nAyZ)`4lI!b!b6=u=&aOr4j|elLOr5agg!-%b$MqB+aPG+ruxXI_ z*~4@E)&831E5bIKT(Mi&j!7>wkYUn&T;L;mBl%azt^#|1-NRWdOMd)iu@O}gym~Gy zBj=Tf>n)+5x7&3b$^?B^Nrjf4bMm<{Unp#%?28Fu^Dk_^J*8lV&CB+XYuVB7Rqdw4 zSWMGQ4&AjmDSCF5{+Fw>KZnjet>bCZZ>$y9#c5PAd1kX?X`@a5s}+Ha%T~5O*F1B{ zOJtVp)76$o>hyEBZdU%Zr(8VA>a(%sR`H7(GmctjS4N&#7Uoosi!A?RSHbW_)whZX1&) zPd8nVohtJs`+jlChV3)mw^k`6{L9&R=-0Ii?9sZcr;mK!T68YzXYJYFM%Qef*5*CP zKBL-tqbGgk@y8t7UAreNkFI<3h^_8h+L1fgX5Z&Ity}x_|HtRs#OjNlMeVz)xu40n zIq_B4Mw94ojZ;$dZBDO!dMj^Z?S&U;8~0#rytWF4D@M z_(gO|_D^2UE%$CZC2%h*jpZ@5+h1JVrjt5LLFlM_9q)1j(XTG`zG>eif6o29bw;#r z@@w;CyW=uu_AiQx>u&8YUX&iBG{e+R`;%nF+FFkk-CJCqOPAZf4t%9;$E{YkY0|oP YxoT_Qy_Y^`V*Zc4(D03BOcVnH0D}YA8~^|S literal 0 HcmV?d00001 diff --git a/files/wifi-sprites.png b/files/wifi-sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..863d0d7f28d60e96bcc36f55a461e79816598c21 GIT binary patch literal 1815 zcmeAS@N?(olHy`uVBq!ia0y~yU}RumV9?=UV_;zD%1)clz`($k|H*Y zfq{Xuz$3Dlfq`2Xgc%uT&5>YWU|=ut^mS!_$id5RV7={lW+4LugKTCtgVoOt;%;l1C_ow>W_?4&t=Ywwl)do%O>9OL_z z-~ZGV_X#NT$1Ylu8?g4?`bOTjn#=O5LXxKJ|GIJh>+RR(p7gmXduzI<)53WR8pYTD zD4p_acm2O@rz~#^uAH`fRZF@2^5Wya17F$K++)49tEl>IU8rjP8_sh-9^`s1Hfgw~ z^7fjFqw~{uN)<7yo!87U+t;YB_ct~9@x5a5TZ&{tB z{Cj<^g4f@@>Fv@585EF8n0jp#NIC zUiZRm$Ee_prziF~d^oc!Y|E#w58{sH)$QBJSR%hq)vcE{hL-6|8sW6Iy!DEZc&W4JZDGpxSsYyXU0 z>wiA|_Vc@be?wGBr|W)#w|y4w%G3o5)zsaaFMx)rEtsSip{?` z80VOoPDu5AmN;h-+X4+mmQ|OmZMtuJ^9tV2JF5TfYune?i~c!m2+Le^&-!FuV?_S9mA`kg+rn|FD62Yrg3-(|e=`N3ST?4*yA?^DI-`%z- zTBhWb@MxXCa%}B$r`)$Y=IuIv#dX2<*YoH6yJy#Vi6QE(rbkqU>m4&juh3h1iv3ru zZU>j#dabEFX_in+p2mwFU)MkHJOs?M7$;Sv7kQntoL#ydT%PXSZcE4bI)0B~A5qZ_# z)L;Hvso9dXNyqoEj_H5zHnwQi*omAfk3R4!Uq75En^|)xWXrc%tL4&ScYRuD8&p4^ z)!gPr-J2i3iVitgetmf7fB(ZZs|@uHuPEJMt9$y+{&{t!MR%_2<*~}t?yBLu@5?kfJNM9&m&{z_U8nw-a9xMzk;0TMY+7or8cz8u zH-=AXo}~Ks=>oa;)gi8WER6yuHht9JzOsG7^(hY>Y}al%8xt?Nc#B90oAdI84z+xV z6^Rp$s7~4Ep8Y|(`u#`G&$ik8Q;IiKx1658J6!xwL7emP)Ge+SAtn2#2>p3K(P!>_FwQ;Y vtP%h6di=gUvtO=Wx8Byh;nUZ>41f5Kh&OOmXHNbKs-Qew{an^LB{Ts5UFCSb literal 0 HcmV?d00001 diff --git a/out/.gitignore b/out/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/out/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore