diff --git a/tasmota/xsns_79_as608.ino b/tasmota/xsns_79_as608.ino new file mode 100644 index 000000000..ada626632 --- /dev/null +++ b/tasmota/xsns_79_as608.ino @@ -0,0 +1,301 @@ +/* + xsns_79_as608.ino - AS608 and R503 fingerprint sensor support for Tasmota + + Copyright (C) 2020 boaschti and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_AS608 +/*********************************************************************************************\ + * AS608 optical and R503 capacitive Fingerprint sensor + * + * Currently supports: + * - English only as there are no tanslations supported + * - AS608 only as R503 fails to read stored fingerprints +\*********************************************************************************************/ + +#define XSNS_79 79 + +#define D_JSON_FPRINT "FPrint" + +#define D_PRFX_FP "Fp" +#define D_CMND_FP_ENROLL "Enroll" +#define D_CMND_FP_DELETE "Delete" +#define D_CMND_FP_COUNT "Count" + +const char kAs608Commands[] PROGMEM = D_PRFX_FP "|" D_CMND_FP_ENROLL "|" D_CMND_FP_DELETE "|" D_CMND_FP_COUNT; + +void (*const As608Commands[])(void) PROGMEM = { &CmndFpEnroll, &CmndFpDelete, &CmndFpCount }; + +const char kAs608Messages[] PROGMEM = + "Done|Comms error||Imaging error|Unknown error|Image too messy|Could not find fingerprint features|No match|Did not find a match|Fingerprint did not match|" + "Bad location|DB range error|Upload feature error|Packet response error|Upload error|Delete error|DB Clear error|Password error|Image invalid|" + "Error writing to flash|Invalid reg|Address code|Password verify"; + +const uint8_t As608Reference[] PROGMEM = { 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 4, 17, 4, 18, 4, 4, 19, 4, 20, 4, 4, 4, 4, 4, 21, 22 }; + +#include +#include + +Adafruit_Fingerprint *As608Finger; +TasmotaSerial *As608Serial; + +struct AS608 { + bool selected = false; + uint8_t enroll_step = 0; + uint8_t model_number = 0; +} As608; + +char* As608Message(char* response, uint32_t index) { + if (index > sizeof(As608Reference)) { index = 4; } + uint32_t i = pgm_read_byte(&As608Reference[index]); + return GetTextIndexed(response, TOPSZ, i, kAs608Messages); +} + +void As608PublishMessage(const char* message) { + char romram[TOPSZ]; + snprintf_P(romram, sizeof(romram), message); + if (strlen(romram) > 0) { + char json_name[20]; + if (As608.enroll_step) { + strcpy_P(json_name, PSTR(D_PRFX_FP D_CMND_FP_ENROLL)); + } else { + strcpy_P(json_name, PSTR(D_JSON_FPRINT)); + } + Response_P(S_JSON_COMMAND_SVALUE, json_name, romram); + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, json_name); + } +} + +void As608Init(void) { + if (PinUsed(GPIO_AS608_RX) && PinUsed(GPIO_AS608_TX)) { + As608Serial = new TasmotaSerial(Pin(GPIO_AS608_RX), Pin(GPIO_AS608_TX), 0); + As608Finger = new Adafruit_Fingerprint(As608Serial, 0); + + As608Finger->begin(57600); + if (As608Serial->hardwareSerial()) { ClaimSerial(); } + + if (As608Finger->verifyPassword()) { + As608Finger->getTemplateCount(); + AddLog_P(LOG_LEVEL_INFO, PSTR("AS6: Detected with %d fingerprint(s) stored"), As608Finger->templateCount); + As608.selected = true; + } + } +} + +int As608GetFingerImage(void) { + int p = As608Finger->getImage(); + if (p != FINGERPRINT_OK) { + char response[TOPSZ]; + As608PublishMessage(As608Message(response, p)); + } + return p; +} + +int As608ConvertFingerImage(uint8_t slot) { + int p = As608Finger->image2Tz(slot); + if (p != FINGERPRINT_OK) { + char response[TOPSZ]; + As608PublishMessage(As608Message(response, p)); + } + return p; +} + +void As608Loop(void) { + uint32_t p = 0; + + if (!As608.enroll_step) { + // Search for Finger + +// As608Finger->LEDcontrol(FINGERPRINT_LED_OFF, 0, FINGERPRINT_LED_RED); +// As608Finger->LEDcontrol(FINGERPRINT_LED_OFF, 0, FINGERPRINT_LED_BLUE); +// As608Finger->LEDcontrol(FINGERPRINT_LED_OFF, 0, FINGERPRINT_LED_PURPLE); +// As608Finger->LEDcontrol(0); + + p = As608Finger->getImage(); // Take image + if (p != FINGERPRINT_OK) { return; } + + p = As608Finger->image2Tz(); // Convert image + if (p != FINGERPRINT_OK) { return; } + + p = As608Finger->fingerFastSearch(); // Match found + if (p != FINGERPRINT_OK) { +// AddLog_P(LOG_LEVEL_DEBUG, PSTR("AS6: No matching finger")); + return; + } + + // Found a match + Response_P(PSTR("{\"" D_JSON_FPRINT "\":{\"Id\":%d,\"Confidence\":%d}}"), As608Finger->fingerID, As608Finger->confidence); + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_JSON_FPRINT)); + return; + } else { + // enroll is active + switch (As608.enroll_step) { + case 1: + As608PublishMessage(PSTR("Place finger")); +// As608Finger->LEDcontrol(FINGERPRINT_LED_ON, 0, FINGERPRINT_LED_BLUE); + As608.enroll_step++; + break; + case 2: + // get first image + if (As608GetFingerImage() == FINGERPRINT_OK) { + As608.enroll_step++; + } + break; + case 3: + // convert image + if (As608ConvertFingerImage(1) == FINGERPRINT_OK) { + As608.enroll_step++; + } else { + As608.enroll_step--; + } + break; + case 4: + As608PublishMessage(PSTR("Remove finger")); + As608.enroll_step++; + break; + case 5: + // Remove finger + p = As608Finger->getImage(); + if (p == FINGERPRINT_NOFINGER) { + As608.enroll_step++; + } + break; + case 6: + As608PublishMessage(PSTR("Place same finger again")); +// As608Finger->LEDcontrol(FINGERPRINT_LED_OFF, 0, FINGERPRINT_LED_PURPLE); + As608.enroll_step++; + break; + case 7: + // get second image of finger + if (As608GetFingerImage() == FINGERPRINT_OK) { + As608.enroll_step++; + } + break; + case 8: + // convert second image + if (As608ConvertFingerImage(2) != FINGERPRINT_OK) { + As608PublishMessage(PSTR("Not Ok so try again")); + As608.enroll_step -= 2; + break; + } + // Create model + p = As608Finger->createModel(); + if (p != FINGERPRINT_OK) { + char response[TOPSZ]; + As608PublishMessage(As608Message(response, p)); + As608.enroll_step = 99; + break; + } + // Store model + p = As608Finger->storeModel(As608.model_number); + char response[TOPSZ]; + As608PublishMessage(As608Message(response, p)); + if (p == FINGERPRINT_OK) { + As608.enroll_step = 0; + As608.model_number = 0; + } else { + As608.enroll_step = 99; + } + break; + case 99: + As608PublishMessage(PSTR("Restart")); + As608.enroll_step = 1; + break; + default: + As608PublishMessage(PSTR("Error")); + As608.enroll_step = 0; + As608.model_number = 0; + break; + } + } +} + +/*********************************************************************************************\ +* Commands +\*********************************************************************************************/ + +void CmndFpEnroll(void) { + if (As608.enroll_step) { + if (0 == XdrvMailbox.payload) { + // FpEnroll 0 - Stop enrollement + As608.enroll_step = 0; + ResponseCmndChar_P(PSTR("Reset")); + } else { + // FpEnroll - Enrollement state + ResponseCmndChar_P(PSTR("Active")); + } + } else { + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 128)) { + // FpEnroll 1..128 - Start enrollement into slot x + As608.enroll_step = 1; + As608.model_number = XdrvMailbox.payload; + ResponseClear(); // Will use loop start message + } else { + // FpEnroll - Enrollement state + ResponseCmndChar_P(PSTR("Inactive")); + } + } +} + +void CmndFpDelete(void) { + if (0 == XdrvMailbox.payload) { + // FpDelete 0 - Clear database + As608Finger->emptyDatabase(); + As608Finger->getTemplateCount(); + if (As608Finger->templateCount) { + ResponseCmndChar_P(PSTR("Error")); + } else { + ResponseCmndDone(); + } + } + else if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 128)) { + // FpDelete 1..128 - Delete single entry from database + int p = As608Finger->deleteModel(XdrvMailbox.payload); + char response[TOPSZ]; + ResponseCmndChar(As608Message(response, p)); + } +} + +void CmndFpCount(void) { + // FpCount - Show number of slots used + As608Finger->getTemplateCount(); + ResponseCmndNumber(As608Finger->templateCount); +} + +/*********************************************************************************************\ +* Interface +\*********************************************************************************************/ + +bool Xsns79(uint8_t function) { + bool result = false; + + if (FUNC_INIT == function) { + As608Init(); + } + else if (As608.selected) { + switch (function) { + case FUNC_EVERY_250_MSECOND: + As608Loop(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kAs608Commands, As608Commands); + break; + } + } + return result; +} + +#endif // USE_AS608