From 3c94b047709b91fdb878c6fd3a23daf7bc7b2d32 Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 15 Oct 2024 21:08:45 +0200 Subject: [PATCH 1/5] Check keypad codes --- src/NukiOpenerWrapper.cpp | 643 +++++++++++++++++++++----------------- src/NukiOpenerWrapper.h | 4 + src/NukiWrapper.cpp | 643 +++++++++++++++++++++----------------- src/NukiWrapper.h | 4 + src/PreferencesKeys.h | 5 +- src/WebCfgServer.cpp | 19 +- 6 files changed, 726 insertions(+), 592 deletions(-) diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index 3ae17a7..14c5a34 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -89,6 +89,7 @@ void NukiOpenerWrapper::readSettings() _retryDelay = _preferences->getInt(preference_command_retry_delay); _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; _disableNonJSON = _preferences->getBool(preference_disable_non_json, false); + _checkKeypadCodes = _preferences->getBool(preference_keypad_check_code_enabled, false); _preferences->getBytes(preference_conf_opener_basic_acl, &_basicOpenerConfigAclPrefs, sizeof(_basicOpenerConfigAclPrefs)); _preferences->getBytes(preference_conf_opener_advanced_acl, &_advancedOpenerConfigAclPrefs, sizeof(_advancedOpenerConfigAclPrefs)); @@ -253,6 +254,11 @@ void NukiOpenerWrapper::update() _nextKeypadUpdateTs = ts + _intervalKeypad * 1000; updateKeypad(false); } + + if(_checkKeypadCodes && _invalidCount > 0 && ts - 120000 < _lastCodeCheck) + { + _invalidCount--; + } if(_nextLockAction != (NukiOpener::LockAction)0xff) { @@ -714,10 +720,13 @@ void NukiOpenerWrapper::updateKeypad(bool retrieved) _network->publishKeypad(entries, _maxKeypadCodeCount); _keypadCodeIds.clear(); + _keypadCodes.clear(); _keypadCodeIds.reserve(entries.size()); + _keypadCodes.reserve(entries.size()); for(const auto& entry : entries) { _keypadCodeIds.push_back(entry.codeId); + _keypadCodes.push_back(entry.code); } } @@ -1797,372 +1806,420 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value) idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId) != _keypadCodeIds.end(); } - Nuki::CmdResult result = (Nuki::CmdResult)-1; - int retryCount = 0; + if(strcmp(action, "check") == 0) { + if(!_preferences->getBool(preference_keypad_check_code_enabled, false)) + { + _network->publishKeypadJsonCommandResult("checkingKeypadCodesDisabled"); + return; + } - while(retryCount < _nrOfRetries + 1) - { - if(strcmp(action, "delete") == 0) { - if(idExists) + if(pow(_invalidCount, 5) + _lastCodeCheck > (esp_timer_get_time() / 1000)) + { + _network->publishKeypadJsonCommandResult("checkingCodesBlockedTooManyInvalid"); + _lastCodeCheck = (esp_timer_get_time() / 1000); + return; + } + + _lastCodeCheck = (esp_timer_get_time() / 1000); + + if(idExists) + { + auto it1 = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId); + int index = it1 - _keypadCodeIds.begin(); + Log->print(F("Check keypad code: ")); + + if(code == _keypadCodes[index]) { - result = _nukiOpener.deleteKeypadEntry(codeId); - Log->print(F("Delete keypad code: ")); - Log->println((int)result); + _invalidCount = 0; + _network->publishKeypadJsonCommandResult("codeValid"); + Log->println("Valid"); + return; } else { - _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + _invalidCount++; + _network->publishKeypadJsonCommandResult("codeInvalid"); + Log->println("Invalid"); return; } } - else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0) + else { - if(name.length() < 1) - { - if (strcmp(action, "update") != 0) + _invalidCount++; + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + return; + } + } + else + { + + Nuki::CmdResult result = (Nuki::CmdResult)-1; + int retryCount = 0; + + while(retryCount < _nrOfRetries + 1) + { + if(strcmp(action, "delete") == 0) { + if(idExists) { - _network->publishKeypadJsonCommandResult("noNameSet"); + result = _nukiOpener.deleteKeypadEntry(codeId); + Log->print(F("Delete keypad code: ")); + Log->println((int)result); + } + else + { + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); return; } } - - if(code != 12) + else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0) { - String codeStr = json["code"].as(); - bool codeValid = code > 100000 && code < 1000000 && (codeStr.indexOf('0') == -1); - - if (!codeValid) + if(name.length() < 1) { - _network->publishKeypadJsonCommandResult("noValidCodeSet"); - return; - } - } - else if (strcmp(action, "update") != 0) - { - _network->publishKeypadJsonCommandResult("noCodeSet"); - return; - } - - unsigned int allowedFromAr[6]; - unsigned int allowedUntilAr[6]; - unsigned int allowedFromTimeAr[2]; - unsigned int allowedUntilTimeAr[2]; - uint8_t allowedWeekdaysInt = 0; - - if(timeLimited == 1) - { - if(allowedFrom.length() > 0) - { - if(allowedFrom.length() == 19) + if (strcmp(action, "update") != 0) { - allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt(); - allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt(); - allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt(); - allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt(); - allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt(); - allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt(); + _network->publishKeypadJsonCommandResult("noNameSet"); + return; + } + } - if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59) + if(code != 12) + { + String codeStr = json["code"].as(); + bool codeValid = code > 100000 && code < 1000000 && (codeStr.indexOf('0') == -1); + + if (!codeValid) + { + _network->publishKeypadJsonCommandResult("noValidCodeSet"); + return; + } + } + else if (strcmp(action, "update") != 0) + { + _network->publishKeypadJsonCommandResult("noCodeSet"); + return; + } + + unsigned int allowedFromAr[6]; + unsigned int allowedUntilAr[6]; + unsigned int allowedFromTimeAr[2]; + unsigned int allowedUntilTimeAr[2]; + uint8_t allowedWeekdaysInt = 0; + + if(timeLimited == 1) + { + if(allowedFrom.length() > 0) + { + if(allowedFrom.length() == 19) + { + allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt(); + allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt(); + allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt(); + allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt(); + allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt(); + allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt(); + + if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); - return; - } - } - if(allowedUntil.length() > 0) - { - if(allowedUntil.length() > 0 == 19) + if(allowedUntil.length() > 0) { - allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); - allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt(); - allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt(); - allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt(); - allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt(); - allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt(); + if(allowedUntil.length() > 0 == 19) + { + allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); + allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt(); + allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt(); + allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt(); + allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt(); + allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt(); - if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59) + if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); - return; - } - } - if(allowedFromTime.length() > 0) - { - if(allowedFromTime.length() == 5) + if(allowedFromTime.length() > 0) { - allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt(); - allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt(); + if(allowedFromTime.length() == 5) + { + allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt(); + allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt(); - if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59) + if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); - return; - } - } - if(allowedUntilTime.length() > 0) - { - if(allowedUntilTime.length() == 5) + if(allowedUntilTime.length() > 0) { - allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt(); - allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt(); + if(allowedUntilTime.length() == 5) + { + allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt(); + allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt(); - if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59) + if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); return; } } - else + + if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64; + if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32; + if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16; + if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8; + if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4; + if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2; + if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1; + } + + if(strcmp(action, "add") == 0) + { + NukiOpener::NewKeypadEntry entry; + memset(&entry, 0, sizeof(entry)); + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + entry.code = code; + entry.timeLimited = timeLimited == 1 ? 1 : 0; + + if(allowedFrom.length() > 0) { - _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); + entry.allowedFromYear = allowedFromAr[0]; + entry.allowedFromMonth = allowedFromAr[1]; + entry.allowedFromDay = allowedFromAr[2]; + entry.allowedFromHour = allowedFromAr[3]; + entry.allowedFromMin = allowedFromAr[4]; + entry.allowedFromSec = allowedFromAr[5]; + } + + if(allowedUntil.length() > 0) + { + entry.allowedUntilYear = allowedUntilAr[0]; + entry.allowedUntilMonth = allowedUntilAr[1]; + entry.allowedUntilDay = allowedUntilAr[2]; + entry.allowedUntilHour = allowedUntilAr[3]; + entry.allowedUntilMin = allowedUntilAr[4]; + entry.allowedUntilSec = allowedUntilAr[5]; + } + + entry.allowedWeekdays = allowedWeekdaysInt; + + if(allowedFromTime.length() > 0) + { + entry.allowedFromTimeHour = allowedFromTimeAr[0]; + entry.allowedFromTimeMin = allowedFromTimeAr[1]; + } + + if(allowedUntilTime.length() > 0) + { + entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; + entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + } + + result = _nukiOpener.addKeypadEntry(entry); + Log->print(F("Add keypad code: ")); + Log->println((int)result); + } + else if (strcmp(action, "update") == 0) + { + if(!codeId) + { + _network->publishKeypadJsonCommandResult("noCodeIdSet"); return; } - } - if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64; - if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32; - if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16; - if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8; - if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4; - if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2; - if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1; - } - - if(strcmp(action, "add") == 0) - { - NukiOpener::NewKeypadEntry entry; - memset(&entry, 0, sizeof(entry)); - size_t nameLen = name.length(); - memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - entry.code = code; - entry.timeLimited = timeLimited == 1 ? 1 : 0; - - if(allowedFrom.length() > 0) - { - entry.allowedFromYear = allowedFromAr[0]; - entry.allowedFromMonth = allowedFromAr[1]; - entry.allowedFromDay = allowedFromAr[2]; - entry.allowedFromHour = allowedFromAr[3]; - entry.allowedFromMin = allowedFromAr[4]; - entry.allowedFromSec = allowedFromAr[5]; - } - - if(allowedUntil.length() > 0) - { - entry.allowedUntilYear = allowedUntilAr[0]; - entry.allowedUntilMonth = allowedUntilAr[1]; - entry.allowedUntilDay = allowedUntilAr[2]; - entry.allowedUntilHour = allowedUntilAr[3]; - entry.allowedUntilMin = allowedUntilAr[4]; - entry.allowedUntilSec = allowedUntilAr[5]; - } - - entry.allowedWeekdays = allowedWeekdaysInt; - - if(allowedFromTime.length() > 0) - { - entry.allowedFromTimeHour = allowedFromTimeAr[0]; - entry.allowedFromTimeMin = allowedFromTimeAr[1]; - } - - if(allowedUntilTime.length() > 0) - { - entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; - entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; - } - - result = _nukiOpener.addKeypadEntry(entry); - Log->print(F("Add keypad code: ")); - Log->println((int)result); - } - else if (strcmp(action, "update") == 0) - { - if(!codeId) - { - _network->publishKeypadJsonCommandResult("noCodeIdSet"); - return; - } - - if(!idExists) - { - _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); - return; - } - - Nuki::CmdResult resultKp = _nukiOpener.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); - bool foundExisting = false; - - if(resultKp == Nuki::CmdResult::Success) - { - delay(250); - std::list entries; - _nukiOpener.getKeypadEntries(&entries); - - for(const auto& entry : entries) + if(!idExists) { - if (codeId != entry.codeId) continue; - else foundExisting = true; - - if(name.length() < 1) - { - memset(oldName, 0, sizeof(oldName)); - memcpy(oldName, entry.name, sizeof(entry.name)); - } - if(code == 12) code = entry.code; - if(enabled == 2) enabled = entry.enabled; - if(timeLimited == 2) timeLimited = entry.timeLimited; - if(allowedFrom.length() < 1) - { - allowedFrom = "old"; - allowedFromAr[0] = entry.allowedFromYear; - allowedFromAr[1] = entry.allowedFromMonth; - allowedFromAr[2] = entry.allowedFromDay; - allowedFromAr[3] = entry.allowedFromHour; - allowedFromAr[4] = entry.allowedFromMin; - allowedFromAr[5] = entry.allowedFromSec; - } - if(allowedUntil.length() < 1) - { - allowedUntil = "old"; - allowedUntilAr[0] = entry.allowedUntilYear; - allowedUntilAr[1] = entry.allowedUntilMonth; - allowedUntilAr[2] = entry.allowedUntilDay; - allowedUntilAr[3] = entry.allowedUntilHour; - allowedUntilAr[4] = entry.allowedUntilMin; - allowedUntilAr[5] = entry.allowedUntilSec; - } - if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays; - if(allowedFromTime.length() < 1) - { - allowedFromTime = "old"; - allowedFromTimeAr[0] = entry.allowedFromTimeHour; - allowedFromTimeAr[1] = entry.allowedFromTimeMin; - } - - if(allowedUntilTime.length() < 1) - { - allowedUntilTime = "old"; - allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; - allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; - } + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + return; } - if(!foundExisting) + Nuki::CmdResult resultKp = _nukiOpener.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); + bool foundExisting = false; + + if(resultKp == Nuki::CmdResult::Success) + { + delay(250); + std::list entries; + _nukiOpener.getKeypadEntries(&entries); + + for(const auto& entry : entries) + { + if (codeId != entry.codeId) continue; + else foundExisting = true; + + if(name.length() < 1) + { + memset(oldName, 0, sizeof(oldName)); + memcpy(oldName, entry.name, sizeof(entry.name)); + } + if(code == 12) code = entry.code; + if(enabled == 2) enabled = entry.enabled; + if(timeLimited == 2) timeLimited = entry.timeLimited; + if(allowedFrom.length() < 1) + { + allowedFrom = "old"; + allowedFromAr[0] = entry.allowedFromYear; + allowedFromAr[1] = entry.allowedFromMonth; + allowedFromAr[2] = entry.allowedFromDay; + allowedFromAr[3] = entry.allowedFromHour; + allowedFromAr[4] = entry.allowedFromMin; + allowedFromAr[5] = entry.allowedFromSec; + } + if(allowedUntil.length() < 1) + { + allowedUntil = "old"; + allowedUntilAr[0] = entry.allowedUntilYear; + allowedUntilAr[1] = entry.allowedUntilMonth; + allowedUntilAr[2] = entry.allowedUntilDay; + allowedUntilAr[3] = entry.allowedUntilHour; + allowedUntilAr[4] = entry.allowedUntilMin; + allowedUntilAr[5] = entry.allowedUntilSec; + } + if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays; + if(allowedFromTime.length() < 1) + { + allowedFromTime = "old"; + allowedFromTimeAr[0] = entry.allowedFromTimeHour; + allowedFromTimeAr[1] = entry.allowedFromTimeMin; + } + + if(allowedUntilTime.length() < 1) + { + allowedUntilTime = "old"; + allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; + allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; + } + } + + if(!foundExisting) + { + _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); return; } - } - else - { - _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); - return; - } - NukiOpener::UpdatedKeypadEntry entry; + NukiOpener::UpdatedKeypadEntry entry; - memset(&entry, 0, sizeof(entry)); - entry.codeId = codeId; - entry.code = code; + memset(&entry, 0, sizeof(entry)); + entry.codeId = codeId; + entry.code = code; - if(name.length() < 1) - { - size_t nameLen = strlen(oldName); - memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen); - } - else - { - size_t nameLen = name.length(); - memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - } - entry.enabled = enabled; - entry.timeLimited = timeLimited; - - if(enabled == 1) - { - if(timeLimited == 1) + if(name.length() < 1) { - if(allowedFrom.length() > 0) - { - entry.allowedFromYear = allowedFromAr[0]; - entry.allowedFromMonth = allowedFromAr[1]; - entry.allowedFromDay = allowedFromAr[2]; - entry.allowedFromHour = allowedFromAr[3]; - entry.allowedFromMin = allowedFromAr[4]; - entry.allowedFromSec = allowedFromAr[5]; - } + size_t nameLen = strlen(oldName); + memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen); + } + else + { + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + } + entry.enabled = enabled; + entry.timeLimited = timeLimited; - if(allowedUntil.length() > 0) + if(enabled == 1) + { + if(timeLimited == 1) { - entry.allowedUntilYear = allowedUntilAr[0]; - entry.allowedUntilMonth = allowedUntilAr[1]; - entry.allowedUntilDay = allowedUntilAr[2]; - entry.allowedUntilHour = allowedUntilAr[3]; - entry.allowedUntilMin = allowedUntilAr[4]; - entry.allowedUntilSec = allowedUntilAr[5]; - } + if(allowedFrom.length() > 0) + { + entry.allowedFromYear = allowedFromAr[0]; + entry.allowedFromMonth = allowedFromAr[1]; + entry.allowedFromDay = allowedFromAr[2]; + entry.allowedFromHour = allowedFromAr[3]; + entry.allowedFromMin = allowedFromAr[4]; + entry.allowedFromSec = allowedFromAr[5]; + } - entry.allowedWeekdays = allowedWeekdaysInt; + if(allowedUntil.length() > 0) + { + entry.allowedUntilYear = allowedUntilAr[0]; + entry.allowedUntilMonth = allowedUntilAr[1]; + entry.allowedUntilDay = allowedUntilAr[2]; + entry.allowedUntilHour = allowedUntilAr[3]; + entry.allowedUntilMin = allowedUntilAr[4]; + entry.allowedUntilSec = allowedUntilAr[5]; + } - if(allowedFromTime.length() > 0) - { - entry.allowedFromTimeHour = allowedFromTimeAr[0]; - entry.allowedFromTimeMin = allowedFromTimeAr[1]; - } + entry.allowedWeekdays = allowedWeekdaysInt; - if(allowedUntilTime.length() > 0) - { - entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; - entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + if(allowedFromTime.length() > 0) + { + entry.allowedFromTimeHour = allowedFromTimeAr[0]; + entry.allowedFromTimeMin = allowedFromTimeAr[1]; + } + + if(allowedUntilTime.length() > 0) + { + entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; + entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + } } } + + result = _nukiOpener.updateKeypadEntry(entry); + Log->print(F("Update keypad code: ")); + Log->println((int)result); } - - result = _nukiOpener.updateKeypadEntry(entry); - Log->print(F("Update keypad code: ")); - Log->println((int)result); } + else + { + _network->publishKeypadJsonCommandResult("invalidAction"); + return; + } + + if(result != Nuki::CmdResult::Success) { + ++retryCount; + } + else break; } - else + + updateKeypad(false); + + if((int)result != -1) { - _network->publishKeypadJsonCommandResult("invalidAction"); - return; + char resultStr[15]; + memset(&resultStr, 0, sizeof(resultStr)); + NukiOpener::cmdResultToString(result, resultStr); + _network->publishKeypadJsonCommandResult(resultStr); } - - if(result != Nuki::CmdResult::Success) { - ++retryCount; - } - else break; - } - - updateKeypad(false); - - if((int)result != -1) - { - char resultStr[15]; - memset(&resultStr, 0, sizeof(resultStr)); - NukiOpener::cmdResultToString(result, resultStr); - _network->publishKeypadJsonCommandResult(resultStr); } } else diff --git a/src/NukiOpenerWrapper.h b/src/NukiOpenerWrapper.h index 1d5eb28..6853711 100644 --- a/src/NukiOpenerWrapper.h +++ b/src/NukiOpenerWrapper.h @@ -106,12 +106,16 @@ private: bool _publishAuthData = false; bool _clearAuthData = false; bool _disableNonJSON = false; + bool _checkKeypadCodes = false; int _nrOfRetries = 0; int _retryDelay = 0; int _retryConfigCount = 0; int _retryLockstateCount = 0; int64_t _nextRetryTs = 0; + int64_t _invalidCount = 0; + int64_t _lastCodeCheck = 0; std::vector _keypadCodeIds; + std::vector _keypadCodes; std::vector _timeControlIds; std::vector _authIds; diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index 7cc1092..da04bfb 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -137,7 +137,7 @@ void NukiWrapper::readSettings() _retryDelay = _preferences->getInt(preference_command_retry_delay); _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; _disableNonJSON = _preferences->getBool(preference_disable_non_json, false); - + _checkKeypadCodes = _preferences->getBool(preference_keypad_check_code_enabled, false); _preferences->getBytes(preference_conf_lock_basic_acl, &_basicLockConfigaclPrefs, sizeof(_basicLockConfigaclPrefs)); _preferences->getBytes(preference_conf_lock_advanced_acl, &_advancedLockConfigaclPrefs, sizeof(_advancedLockConfigaclPrefs)); @@ -369,6 +369,10 @@ void NukiWrapper::update() _nextKeypadUpdateTs = ts + _intervalKeypad * 1000; updateKeypad(false); } + if(_checkKeypadCodes && _invalidCount > 0 && ts - 120000 < _lastCodeCheck) + { + _invalidCount--; + } } if(_clearAuthData) { @@ -773,10 +777,13 @@ void NukiWrapper::updateKeypad(bool retrieved) _network->publishKeypad(entries, _maxKeypadCodeCount); _keypadCodeIds.clear(); + _keypadCodes.clear(); _keypadCodeIds.reserve(entries.size()); + _keypadCodes.reserve(entries.size()); for(const auto& entry : entries) { _keypadCodeIds.push_back(entry.codeId); + _keypadCodes.push_back(entry.code); } } @@ -1918,373 +1925,421 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value) { idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId) != _keypadCodeIds.end(); } + + if(strcmp(action, "check") == 0) { + if(!_preferences->getBool(preference_keypad_check_code_enabled, false)) + { + _network->publishKeypadJsonCommandResult("checkingKeypadCodesDisabled"); + return; + } - Nuki::CmdResult result = (Nuki::CmdResult)-1; - int retryCount = 0; + if(pow(_invalidCount, 5) + _lastCodeCheck > (esp_timer_get_time() / 1000)) + { + _network->publishKeypadJsonCommandResult("checkingCodesBlockedTooManyInvalid"); + _lastCodeCheck = (esp_timer_get_time() / 1000); + return; + } - while(retryCount < _nrOfRetries + 1) - { - if(strcmp(action, "delete") == 0) { - if(idExists) + _lastCodeCheck = (esp_timer_get_time() / 1000); + + if(idExists) + { + auto it1 = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId); + int index = it1 - _keypadCodeIds.begin(); + Log->print(F("Check keypad code: ")); + + if(code == _keypadCodes[index]) { - result = _nukiLock.deleteKeypadEntry(codeId); - Log->print(F("Delete keypad code: ")); - Log->println((int)result); + _invalidCount = 0; + _network->publishKeypadJsonCommandResult("codeValid"); + Log->println("Valid"); + return; } else { - _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + _invalidCount++; + _network->publishKeypadJsonCommandResult("codeInvalid"); + Log->println("Invalid"); return; } } - else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0) + else { - if(name.length() < 1) - { - if (strcmp(action, "update") != 0) + _invalidCount++; + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + return; + } + } + else + { + + Nuki::CmdResult result = (Nuki::CmdResult)-1; + int retryCount = 0; + + while(retryCount < _nrOfRetries + 1) + { + if(strcmp(action, "delete") == 0) { + if(idExists) { - _network->publishKeypadJsonCommandResult("noNameSet"); + result = _nukiLock.deleteKeypadEntry(codeId); + Log->print(F("Delete keypad code: ")); + Log->println((int)result); + } + else + { + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); return; } } - - if(code != 12) + else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0) { - String codeStr = json["code"].as(); - bool codeValid = code > 100000 && code < 1000000 && (codeStr.indexOf('0') == -1); - - if (!codeValid) + if(name.length() < 1) { - _network->publishKeypadJsonCommandResult("noValidCodeSet"); - return; - } - } - else if (strcmp(action, "update") != 0) - { - _network->publishKeypadJsonCommandResult("noCodeSet"); - return; - } - - unsigned int allowedFromAr[6]; - unsigned int allowedUntilAr[6]; - unsigned int allowedFromTimeAr[2]; - unsigned int allowedUntilTimeAr[2]; - uint8_t allowedWeekdaysInt = 0; - - if(timeLimited == 1) - { - if(allowedFrom.length() > 0) - { - if(allowedFrom.length() == 19) + if (strcmp(action, "update") != 0) { - allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt(); - allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt(); - allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt(); - allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt(); - allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt(); - allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt(); + _network->publishKeypadJsonCommandResult("noNameSet"); + return; + } + } - if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59) + if(code != 12) + { + String codeStr = json["code"].as(); + bool codeValid = code > 100000 && code < 1000000 && (codeStr.indexOf('0') == -1); + + if (!codeValid) + { + _network->publishKeypadJsonCommandResult("noValidCodeSet"); + return; + } + } + else if (strcmp(action, "update") != 0) + { + _network->publishKeypadJsonCommandResult("noCodeSet"); + return; + } + + unsigned int allowedFromAr[6]; + unsigned int allowedUntilAr[6]; + unsigned int allowedFromTimeAr[2]; + unsigned int allowedUntilTimeAr[2]; + uint8_t allowedWeekdaysInt = 0; + + if(timeLimited == 1) + { + if(allowedFrom.length() > 0) + { + if(allowedFrom.length() == 19) + { + allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt(); + allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt(); + allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt(); + allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt(); + allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt(); + allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt(); + + if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); - return; - } - } - if(allowedUntil.length() > 0) - { - if(allowedUntil.length() > 0 == 19) + if(allowedUntil.length() > 0) { - allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); - allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt(); - allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt(); - allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt(); - allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt(); - allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt(); + if(allowedUntil.length() > 0 == 19) + { + allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); + allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt(); + allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt(); + allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt(); + allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt(); + allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt(); - if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59) + if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); - return; - } - } - if(allowedFromTime.length() > 0) - { - if(allowedFromTime.length() == 5) + if(allowedFromTime.length() > 0) { - allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt(); - allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt(); + if(allowedFromTime.length() == 5) + { + allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt(); + allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt(); - if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59) + if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); - return; - } - } - if(allowedUntilTime.length() > 0) - { - if(allowedUntilTime.length() == 5) + if(allowedUntilTime.length() > 0) { - allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt(); - allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt(); + if(allowedUntilTime.length() == 5) + { + allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt(); + allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt(); - if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59) + if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); return; } } - else + + if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64; + if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32; + if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16; + if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8; + if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4; + if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2; + if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1; + } + + if(strcmp(action, "add") == 0) + { + NukiLock::NewKeypadEntry entry; + memset(&entry, 0, sizeof(entry)); + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + entry.code = code; + entry.timeLimited = timeLimited == 1 ? 1 : 0; + + if(allowedFrom.length() > 0) { - _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); + entry.allowedFromYear = allowedFromAr[0]; + entry.allowedFromMonth = allowedFromAr[1]; + entry.allowedFromDay = allowedFromAr[2]; + entry.allowedFromHour = allowedFromAr[3]; + entry.allowedFromMin = allowedFromAr[4]; + entry.allowedFromSec = allowedFromAr[5]; + } + + if(allowedUntil.length() > 0) + { + entry.allowedUntilYear = allowedUntilAr[0]; + entry.allowedUntilMonth = allowedUntilAr[1]; + entry.allowedUntilDay = allowedUntilAr[2]; + entry.allowedUntilHour = allowedUntilAr[3]; + entry.allowedUntilMin = allowedUntilAr[4]; + entry.allowedUntilSec = allowedUntilAr[5]; + } + + entry.allowedWeekdays = allowedWeekdaysInt; + + if(allowedFromTime.length() > 0) + { + entry.allowedFromTimeHour = allowedFromTimeAr[0]; + entry.allowedFromTimeMin = allowedFromTimeAr[1]; + } + + if(allowedUntilTime.length() > 0) + { + entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; + entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + } + + result = _nukiLock.addKeypadEntry(entry); + Log->print(F("Add keypad code: ")); + Log->println((int)result); + } + else if (strcmp(action, "update") == 0) + { + if(!codeId) + { + _network->publishKeypadJsonCommandResult("noCodeIdSet"); return; } - } - if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64; - if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32; - if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16; - if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8; - if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4; - if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2; - if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1; - } - - if(strcmp(action, "add") == 0) - { - NukiLock::NewKeypadEntry entry; - memset(&entry, 0, sizeof(entry)); - size_t nameLen = name.length(); - memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - entry.code = code; - entry.timeLimited = timeLimited == 1 ? 1 : 0; - - if(allowedFrom.length() > 0) - { - entry.allowedFromYear = allowedFromAr[0]; - entry.allowedFromMonth = allowedFromAr[1]; - entry.allowedFromDay = allowedFromAr[2]; - entry.allowedFromHour = allowedFromAr[3]; - entry.allowedFromMin = allowedFromAr[4]; - entry.allowedFromSec = allowedFromAr[5]; - } - - if(allowedUntil.length() > 0) - { - entry.allowedUntilYear = allowedUntilAr[0]; - entry.allowedUntilMonth = allowedUntilAr[1]; - entry.allowedUntilDay = allowedUntilAr[2]; - entry.allowedUntilHour = allowedUntilAr[3]; - entry.allowedUntilMin = allowedUntilAr[4]; - entry.allowedUntilSec = allowedUntilAr[5]; - } - - entry.allowedWeekdays = allowedWeekdaysInt; - - if(allowedFromTime.length() > 0) - { - entry.allowedFromTimeHour = allowedFromTimeAr[0]; - entry.allowedFromTimeMin = allowedFromTimeAr[1]; - } - - if(allowedUntilTime.length() > 0) - { - entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; - entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; - } - - result = _nukiLock.addKeypadEntry(entry); - Log->print(F("Add keypad code: ")); - Log->println((int)result); - } - else if (strcmp(action, "update") == 0) - { - if(!codeId) - { - _network->publishKeypadJsonCommandResult("noCodeIdSet"); - return; - } - - if(!idExists) - { - _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); - return; - } - - Nuki::CmdResult resultKp = _nukiLock.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); - bool foundExisting = false; - - if(resultKp == Nuki::CmdResult::Success) - { - delay(250); - std::list entries; - _nukiLock.getKeypadEntries(&entries); - - for(const auto& entry : entries) + if(!idExists) { - if (codeId != entry.codeId) continue; - else foundExisting = true; - - if(name.length() < 1) - { - memset(oldName, 0, sizeof(oldName)); - memcpy(oldName, entry.name, sizeof(entry.name)); - } - if(code == 12) code = entry.code; - if(enabled == 2) enabled = entry.enabled; - if(timeLimited == 2) timeLimited = entry.timeLimited; - if(allowedFrom.length() < 1) - { - allowedFrom = "old"; - allowedFromAr[0] = entry.allowedFromYear; - allowedFromAr[1] = entry.allowedFromMonth; - allowedFromAr[2] = entry.allowedFromDay; - allowedFromAr[3] = entry.allowedFromHour; - allowedFromAr[4] = entry.allowedFromMin; - allowedFromAr[5] = entry.allowedFromSec; - } - if(allowedUntil.length() < 1) - { - allowedUntil = "old"; - allowedUntilAr[0] = entry.allowedUntilYear; - allowedUntilAr[1] = entry.allowedUntilMonth; - allowedUntilAr[2] = entry.allowedUntilDay; - allowedUntilAr[3] = entry.allowedUntilHour; - allowedUntilAr[4] = entry.allowedUntilMin; - allowedUntilAr[5] = entry.allowedUntilSec; - } - if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays; - if(allowedFromTime.length() < 1) - { - allowedFromTime = "old"; - allowedFromTimeAr[0] = entry.allowedFromTimeHour; - allowedFromTimeAr[1] = entry.allowedFromTimeMin; - } - - if(allowedUntilTime.length() < 1) - { - allowedUntilTime = "old"; - allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; - allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; - } + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + return; } - if(!foundExisting) + Nuki::CmdResult resultKp = _nukiLock.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); + bool foundExisting = false; + + if(resultKp == Nuki::CmdResult::Success) + { + delay(250); + std::list entries; + _nukiLock.getKeypadEntries(&entries); + + for(const auto& entry : entries) + { + if (codeId != entry.codeId) continue; + else foundExisting = true; + + if(name.length() < 1) + { + memset(oldName, 0, sizeof(oldName)); + memcpy(oldName, entry.name, sizeof(entry.name)); + } + if(code == 12) code = entry.code; + if(enabled == 2) enabled = entry.enabled; + if(timeLimited == 2) timeLimited = entry.timeLimited; + if(allowedFrom.length() < 1) + { + allowedFrom = "old"; + allowedFromAr[0] = entry.allowedFromYear; + allowedFromAr[1] = entry.allowedFromMonth; + allowedFromAr[2] = entry.allowedFromDay; + allowedFromAr[3] = entry.allowedFromHour; + allowedFromAr[4] = entry.allowedFromMin; + allowedFromAr[5] = entry.allowedFromSec; + } + if(allowedUntil.length() < 1) + { + allowedUntil = "old"; + allowedUntilAr[0] = entry.allowedUntilYear; + allowedUntilAr[1] = entry.allowedUntilMonth; + allowedUntilAr[2] = entry.allowedUntilDay; + allowedUntilAr[3] = entry.allowedUntilHour; + allowedUntilAr[4] = entry.allowedUntilMin; + allowedUntilAr[5] = entry.allowedUntilSec; + } + if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays; + if(allowedFromTime.length() < 1) + { + allowedFromTime = "old"; + allowedFromTimeAr[0] = entry.allowedFromTimeHour; + allowedFromTimeAr[1] = entry.allowedFromTimeMin; + } + + if(allowedUntilTime.length() < 1) + { + allowedUntilTime = "old"; + allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; + allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; + } + } + + if(!foundExisting) + { + _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); return; } - } - else - { - _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); - return; - } - NukiLock::UpdatedKeypadEntry entry; + NukiLock::UpdatedKeypadEntry entry; - memset(&entry, 0, sizeof(entry)); - entry.codeId = codeId; - entry.code = code; + memset(&entry, 0, sizeof(entry)); + entry.codeId = codeId; + entry.code = code; - if(name.length() < 1) - { - size_t nameLen = strlen(oldName); - memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen); - } - else - { - size_t nameLen = name.length(); - memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - } - entry.enabled = enabled; - entry.timeLimited = timeLimited; - - if(enabled == 1) - { - if(timeLimited == 1) + if(name.length() < 1) { - if(allowedFrom.length() > 0) - { - entry.allowedFromYear = allowedFromAr[0]; - entry.allowedFromMonth = allowedFromAr[1]; - entry.allowedFromDay = allowedFromAr[2]; - entry.allowedFromHour = allowedFromAr[3]; - entry.allowedFromMin = allowedFromAr[4]; - entry.allowedFromSec = allowedFromAr[5]; - } + size_t nameLen = strlen(oldName); + memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen); + } + else + { + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + } + entry.enabled = enabled; + entry.timeLimited = timeLimited; - if(allowedUntil.length() > 0) + if(enabled == 1) + { + if(timeLimited == 1) { - entry.allowedUntilYear = allowedUntilAr[0]; - entry.allowedUntilMonth = allowedUntilAr[1]; - entry.allowedUntilDay = allowedUntilAr[2]; - entry.allowedUntilHour = allowedUntilAr[3]; - entry.allowedUntilMin = allowedUntilAr[4]; - entry.allowedUntilSec = allowedUntilAr[5]; - } + if(allowedFrom.length() > 0) + { + entry.allowedFromYear = allowedFromAr[0]; + entry.allowedFromMonth = allowedFromAr[1]; + entry.allowedFromDay = allowedFromAr[2]; + entry.allowedFromHour = allowedFromAr[3]; + entry.allowedFromMin = allowedFromAr[4]; + entry.allowedFromSec = allowedFromAr[5]; + } - entry.allowedWeekdays = allowedWeekdaysInt; + if(allowedUntil.length() > 0) + { + entry.allowedUntilYear = allowedUntilAr[0]; + entry.allowedUntilMonth = allowedUntilAr[1]; + entry.allowedUntilDay = allowedUntilAr[2]; + entry.allowedUntilHour = allowedUntilAr[3]; + entry.allowedUntilMin = allowedUntilAr[4]; + entry.allowedUntilSec = allowedUntilAr[5]; + } - if(allowedFromTime.length() > 0) - { - entry.allowedFromTimeHour = allowedFromTimeAr[0]; - entry.allowedFromTimeMin = allowedFromTimeAr[1]; - } + entry.allowedWeekdays = allowedWeekdaysInt; - if(allowedUntilTime.length() > 0) - { - entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; - entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + if(allowedFromTime.length() > 0) + { + entry.allowedFromTimeHour = allowedFromTimeAr[0]; + entry.allowedFromTimeMin = allowedFromTimeAr[1]; + } + + if(allowedUntilTime.length() > 0) + { + entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; + entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + } } } + + result = _nukiLock.updateKeypadEntry(entry); + Log->print(F("Update keypad code: ")); + Log->println((int)result); } - - result = _nukiLock.updateKeypadEntry(entry); - Log->print(F("Update keypad code: ")); - Log->println((int)result); } + else + { + _network->publishKeypadJsonCommandResult("invalidAction"); + return; + } + + if(result != Nuki::CmdResult::Success) { + ++retryCount; + } + else break; } - else + + updateKeypad(false); + + if((int)result != -1) { - _network->publishKeypadJsonCommandResult("invalidAction"); - return; + char resultStr[15]; + memset(&resultStr, 0, sizeof(resultStr)); + NukiLock::cmdResultToString(result, resultStr); + _network->publishKeypadJsonCommandResult(resultStr); } - - if(result != Nuki::CmdResult::Success) { - ++retryCount; - } - else break; - } - - updateKeypad(false); - - if((int)result != -1) - { - char resultStr[15]; - memset(&resultStr, 0, sizeof(resultStr)); - NukiLock::cmdResultToString(result, resultStr); - _network->publishKeypadJsonCommandResult(resultStr); } } else diff --git a/src/NukiWrapper.h b/src/NukiWrapper.h index ee695f9..0cf886b 100644 --- a/src/NukiWrapper.h +++ b/src/NukiWrapper.h @@ -107,7 +107,11 @@ private: int _restartBeaconTimeout = 0; // seconds bool _publishAuthData = false; bool _clearAuthData = false; + bool _checkKeypadCodes = false; + int64_t _invalidCount = 0; + int64_t _lastCodeCheck = 0; std::vector _keypadCodeIds; + std::vector _keypadCodes; std::vector _timeControlIds; std::vector _authIds; diff --git a/src/PreferencesKeys.h b/src/PreferencesKeys.h index fc0cb16..7edc997 100644 --- a/src/PreferencesKeys.h +++ b/src/PreferencesKeys.h @@ -98,6 +98,7 @@ #define preference_recon_netw_on_mqtt_discon (char*)"recNtwMqttDis" #define preference_official_hybrid_actions (char*)"hybridAct" #define preference_official_hybrid_retry (char*)"hybridRtry" +#define preference_keypad_check_code_enabled (char*)"kpChkEna" //NOT USER CHANGABLE #define preference_updater_version (char*)"updVer" @@ -284,7 +285,7 @@ private: preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, preference_network_custom_irq, preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi, preference_network_custom_pwr, preference_network_custom_mdio, preference_ntw_reconfigure, preference_lock_max_auth_entry_count, preference_opener_max_auth_entry_count, - preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, + preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_keypad_check_code_enabled }; std::vector _redact = { @@ -300,7 +301,7 @@ private: preference_publish_authdata, preference_publish_debug_info, preference_network_wifi_fallback_disabled, preference_official_hybrid_enabled, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_recon_netw_on_mqtt_discon, preference_webserial_enabled, - preference_ntw_reconfigure + preference_ntw_reconfigure, preference_keypad_check_code_enabled }; std::vector _bytePrefs = { diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 208124e..4d3e975 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -1561,6 +1561,16 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message) //configChanged = true; } } + else if(key == "KPCHECK") + { + if(_preferences->getBool(preference_keypad_check_code_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_keypad_check_code_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + //configChanged = true; + } + } else if(key == "KPENA") { if(_preferences->getBool(preference_keypad_control_enabled, false) != (value == "1")) @@ -2639,15 +2649,15 @@ void WebCfgServer::buildCredHtml(AsyncWebServerRequest *request) { _response = ""; buildHtmlHeader(); - _response.concat("
"); + _response.concat(""); _response.concat("

Credentials

"); _response.concat(""); printInputField("CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "", false, true); - printInputField("CREDPASS", "Password", "*", 30, "", true, true); + printInputField("CREDPASS", "Password", "*", 30, "id=\"inputpass\"", true, true); printInputField("CREDPASSRE", "Retype password", "*", 30, "", true); _response.concat("
"); _response.concat("
"); - _response.concat("
"); + _response.concat(""); if(_nuki != nullptr) { _response.concat("

"); @@ -2919,6 +2929,7 @@ void WebCfgServer::buildAccLvlHtml(AsyncWebServerRequest *request) printCheckBox("KPPUB", "Publish keypad entries information", _preferences->getBool(preference_keypad_info_enabled), ""); printCheckBox("KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), ""); printCheckBox("KPCODE", "Also publish keypad codes (Disadvised for security reasons)", _preferences->getBool(preference_keypad_publish_code, false), ""); + printCheckBox("KPCHECK", "Allow checking if keypad codes are valid (Disadvised for security reasons)", _preferences->getBool(preference_keypad_check_code_enabled, false), ""); printCheckBox("KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), ""); } printCheckBox("TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), ""); @@ -3386,6 +3397,8 @@ void WebCfgServer::buildInfoHtml(AsyncWebServerRequest *request) _response.concat(_preferences->getBool(preference_keypad_topic_per_entry, false) ? "Yes" : "No"); _response.concat("\nPublish Keypad codes: "); _response.concat(_preferences->getBool(preference_keypad_publish_code, false) ? "Yes" : "No"); + _response.concat("\nAllow checking Keypad codes: "); + _response.concat(_preferences->getBool(preference_keypad_check_code_enabled, false) ? "Yes" : "No"); _response.concat("\nMax keypad entries to retrieve: "); _response.concat(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); _response.concat("\nPublish timecontrol info: "); From 0b9b3da38036e990f32d54d9e62969b014329683 Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 15 Oct 2024 21:08:45 +0200 Subject: [PATCH 2/5] Check keypad codes --- src/Config.h | 2 +- src/NukiOpenerWrapper.cpp | 643 +++++++++++++++++++++----------------- src/NukiOpenerWrapper.h | 4 + src/NukiWrapper.cpp | 643 +++++++++++++++++++++----------------- src/NukiWrapper.h | 4 + src/PreferencesKeys.h | 5 +- src/WebCfgServer.cpp | 21 +- 7 files changed, 728 insertions(+), 594 deletions(-) diff --git a/src/Config.h b/src/Config.h index 630b1a1..c90f772 100644 --- a/src/Config.h +++ b/src/Config.h @@ -4,7 +4,7 @@ #define NUKI_HUB_VERSION "9.02" #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2024-10-13" +#define NUKI_HUB_DATE "2024-10-15" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index 3ae17a7..14c5a34 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -89,6 +89,7 @@ void NukiOpenerWrapper::readSettings() _retryDelay = _preferences->getInt(preference_command_retry_delay); _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; _disableNonJSON = _preferences->getBool(preference_disable_non_json, false); + _checkKeypadCodes = _preferences->getBool(preference_keypad_check_code_enabled, false); _preferences->getBytes(preference_conf_opener_basic_acl, &_basicOpenerConfigAclPrefs, sizeof(_basicOpenerConfigAclPrefs)); _preferences->getBytes(preference_conf_opener_advanced_acl, &_advancedOpenerConfigAclPrefs, sizeof(_advancedOpenerConfigAclPrefs)); @@ -253,6 +254,11 @@ void NukiOpenerWrapper::update() _nextKeypadUpdateTs = ts + _intervalKeypad * 1000; updateKeypad(false); } + + if(_checkKeypadCodes && _invalidCount > 0 && ts - 120000 < _lastCodeCheck) + { + _invalidCount--; + } if(_nextLockAction != (NukiOpener::LockAction)0xff) { @@ -714,10 +720,13 @@ void NukiOpenerWrapper::updateKeypad(bool retrieved) _network->publishKeypad(entries, _maxKeypadCodeCount); _keypadCodeIds.clear(); + _keypadCodes.clear(); _keypadCodeIds.reserve(entries.size()); + _keypadCodes.reserve(entries.size()); for(const auto& entry : entries) { _keypadCodeIds.push_back(entry.codeId); + _keypadCodes.push_back(entry.code); } } @@ -1797,372 +1806,420 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value) idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId) != _keypadCodeIds.end(); } - Nuki::CmdResult result = (Nuki::CmdResult)-1; - int retryCount = 0; + if(strcmp(action, "check") == 0) { + if(!_preferences->getBool(preference_keypad_check_code_enabled, false)) + { + _network->publishKeypadJsonCommandResult("checkingKeypadCodesDisabled"); + return; + } - while(retryCount < _nrOfRetries + 1) - { - if(strcmp(action, "delete") == 0) { - if(idExists) + if(pow(_invalidCount, 5) + _lastCodeCheck > (esp_timer_get_time() / 1000)) + { + _network->publishKeypadJsonCommandResult("checkingCodesBlockedTooManyInvalid"); + _lastCodeCheck = (esp_timer_get_time() / 1000); + return; + } + + _lastCodeCheck = (esp_timer_get_time() / 1000); + + if(idExists) + { + auto it1 = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId); + int index = it1 - _keypadCodeIds.begin(); + Log->print(F("Check keypad code: ")); + + if(code == _keypadCodes[index]) { - result = _nukiOpener.deleteKeypadEntry(codeId); - Log->print(F("Delete keypad code: ")); - Log->println((int)result); + _invalidCount = 0; + _network->publishKeypadJsonCommandResult("codeValid"); + Log->println("Valid"); + return; } else { - _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + _invalidCount++; + _network->publishKeypadJsonCommandResult("codeInvalid"); + Log->println("Invalid"); return; } } - else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0) + else { - if(name.length() < 1) - { - if (strcmp(action, "update") != 0) + _invalidCount++; + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + return; + } + } + else + { + + Nuki::CmdResult result = (Nuki::CmdResult)-1; + int retryCount = 0; + + while(retryCount < _nrOfRetries + 1) + { + if(strcmp(action, "delete") == 0) { + if(idExists) { - _network->publishKeypadJsonCommandResult("noNameSet"); + result = _nukiOpener.deleteKeypadEntry(codeId); + Log->print(F("Delete keypad code: ")); + Log->println((int)result); + } + else + { + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); return; } } - - if(code != 12) + else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0) { - String codeStr = json["code"].as(); - bool codeValid = code > 100000 && code < 1000000 && (codeStr.indexOf('0') == -1); - - if (!codeValid) + if(name.length() < 1) { - _network->publishKeypadJsonCommandResult("noValidCodeSet"); - return; - } - } - else if (strcmp(action, "update") != 0) - { - _network->publishKeypadJsonCommandResult("noCodeSet"); - return; - } - - unsigned int allowedFromAr[6]; - unsigned int allowedUntilAr[6]; - unsigned int allowedFromTimeAr[2]; - unsigned int allowedUntilTimeAr[2]; - uint8_t allowedWeekdaysInt = 0; - - if(timeLimited == 1) - { - if(allowedFrom.length() > 0) - { - if(allowedFrom.length() == 19) + if (strcmp(action, "update") != 0) { - allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt(); - allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt(); - allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt(); - allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt(); - allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt(); - allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt(); + _network->publishKeypadJsonCommandResult("noNameSet"); + return; + } + } - if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59) + if(code != 12) + { + String codeStr = json["code"].as(); + bool codeValid = code > 100000 && code < 1000000 && (codeStr.indexOf('0') == -1); + + if (!codeValid) + { + _network->publishKeypadJsonCommandResult("noValidCodeSet"); + return; + } + } + else if (strcmp(action, "update") != 0) + { + _network->publishKeypadJsonCommandResult("noCodeSet"); + return; + } + + unsigned int allowedFromAr[6]; + unsigned int allowedUntilAr[6]; + unsigned int allowedFromTimeAr[2]; + unsigned int allowedUntilTimeAr[2]; + uint8_t allowedWeekdaysInt = 0; + + if(timeLimited == 1) + { + if(allowedFrom.length() > 0) + { + if(allowedFrom.length() == 19) + { + allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt(); + allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt(); + allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt(); + allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt(); + allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt(); + allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt(); + + if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); - return; - } - } - if(allowedUntil.length() > 0) - { - if(allowedUntil.length() > 0 == 19) + if(allowedUntil.length() > 0) { - allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); - allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt(); - allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt(); - allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt(); - allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt(); - allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt(); + if(allowedUntil.length() > 0 == 19) + { + allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); + allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt(); + allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt(); + allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt(); + allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt(); + allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt(); - if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59) + if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); - return; - } - } - if(allowedFromTime.length() > 0) - { - if(allowedFromTime.length() == 5) + if(allowedFromTime.length() > 0) { - allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt(); - allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt(); + if(allowedFromTime.length() == 5) + { + allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt(); + allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt(); - if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59) + if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); - return; - } - } - if(allowedUntilTime.length() > 0) - { - if(allowedUntilTime.length() == 5) + if(allowedUntilTime.length() > 0) { - allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt(); - allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt(); + if(allowedUntilTime.length() == 5) + { + allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt(); + allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt(); - if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59) + if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); return; } } - else + + if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64; + if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32; + if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16; + if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8; + if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4; + if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2; + if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1; + } + + if(strcmp(action, "add") == 0) + { + NukiOpener::NewKeypadEntry entry; + memset(&entry, 0, sizeof(entry)); + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + entry.code = code; + entry.timeLimited = timeLimited == 1 ? 1 : 0; + + if(allowedFrom.length() > 0) { - _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); + entry.allowedFromYear = allowedFromAr[0]; + entry.allowedFromMonth = allowedFromAr[1]; + entry.allowedFromDay = allowedFromAr[2]; + entry.allowedFromHour = allowedFromAr[3]; + entry.allowedFromMin = allowedFromAr[4]; + entry.allowedFromSec = allowedFromAr[5]; + } + + if(allowedUntil.length() > 0) + { + entry.allowedUntilYear = allowedUntilAr[0]; + entry.allowedUntilMonth = allowedUntilAr[1]; + entry.allowedUntilDay = allowedUntilAr[2]; + entry.allowedUntilHour = allowedUntilAr[3]; + entry.allowedUntilMin = allowedUntilAr[4]; + entry.allowedUntilSec = allowedUntilAr[5]; + } + + entry.allowedWeekdays = allowedWeekdaysInt; + + if(allowedFromTime.length() > 0) + { + entry.allowedFromTimeHour = allowedFromTimeAr[0]; + entry.allowedFromTimeMin = allowedFromTimeAr[1]; + } + + if(allowedUntilTime.length() > 0) + { + entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; + entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + } + + result = _nukiOpener.addKeypadEntry(entry); + Log->print(F("Add keypad code: ")); + Log->println((int)result); + } + else if (strcmp(action, "update") == 0) + { + if(!codeId) + { + _network->publishKeypadJsonCommandResult("noCodeIdSet"); return; } - } - if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64; - if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32; - if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16; - if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8; - if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4; - if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2; - if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1; - } - - if(strcmp(action, "add") == 0) - { - NukiOpener::NewKeypadEntry entry; - memset(&entry, 0, sizeof(entry)); - size_t nameLen = name.length(); - memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - entry.code = code; - entry.timeLimited = timeLimited == 1 ? 1 : 0; - - if(allowedFrom.length() > 0) - { - entry.allowedFromYear = allowedFromAr[0]; - entry.allowedFromMonth = allowedFromAr[1]; - entry.allowedFromDay = allowedFromAr[2]; - entry.allowedFromHour = allowedFromAr[3]; - entry.allowedFromMin = allowedFromAr[4]; - entry.allowedFromSec = allowedFromAr[5]; - } - - if(allowedUntil.length() > 0) - { - entry.allowedUntilYear = allowedUntilAr[0]; - entry.allowedUntilMonth = allowedUntilAr[1]; - entry.allowedUntilDay = allowedUntilAr[2]; - entry.allowedUntilHour = allowedUntilAr[3]; - entry.allowedUntilMin = allowedUntilAr[4]; - entry.allowedUntilSec = allowedUntilAr[5]; - } - - entry.allowedWeekdays = allowedWeekdaysInt; - - if(allowedFromTime.length() > 0) - { - entry.allowedFromTimeHour = allowedFromTimeAr[0]; - entry.allowedFromTimeMin = allowedFromTimeAr[1]; - } - - if(allowedUntilTime.length() > 0) - { - entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; - entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; - } - - result = _nukiOpener.addKeypadEntry(entry); - Log->print(F("Add keypad code: ")); - Log->println((int)result); - } - else if (strcmp(action, "update") == 0) - { - if(!codeId) - { - _network->publishKeypadJsonCommandResult("noCodeIdSet"); - return; - } - - if(!idExists) - { - _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); - return; - } - - Nuki::CmdResult resultKp = _nukiOpener.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); - bool foundExisting = false; - - if(resultKp == Nuki::CmdResult::Success) - { - delay(250); - std::list entries; - _nukiOpener.getKeypadEntries(&entries); - - for(const auto& entry : entries) + if(!idExists) { - if (codeId != entry.codeId) continue; - else foundExisting = true; - - if(name.length() < 1) - { - memset(oldName, 0, sizeof(oldName)); - memcpy(oldName, entry.name, sizeof(entry.name)); - } - if(code == 12) code = entry.code; - if(enabled == 2) enabled = entry.enabled; - if(timeLimited == 2) timeLimited = entry.timeLimited; - if(allowedFrom.length() < 1) - { - allowedFrom = "old"; - allowedFromAr[0] = entry.allowedFromYear; - allowedFromAr[1] = entry.allowedFromMonth; - allowedFromAr[2] = entry.allowedFromDay; - allowedFromAr[3] = entry.allowedFromHour; - allowedFromAr[4] = entry.allowedFromMin; - allowedFromAr[5] = entry.allowedFromSec; - } - if(allowedUntil.length() < 1) - { - allowedUntil = "old"; - allowedUntilAr[0] = entry.allowedUntilYear; - allowedUntilAr[1] = entry.allowedUntilMonth; - allowedUntilAr[2] = entry.allowedUntilDay; - allowedUntilAr[3] = entry.allowedUntilHour; - allowedUntilAr[4] = entry.allowedUntilMin; - allowedUntilAr[5] = entry.allowedUntilSec; - } - if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays; - if(allowedFromTime.length() < 1) - { - allowedFromTime = "old"; - allowedFromTimeAr[0] = entry.allowedFromTimeHour; - allowedFromTimeAr[1] = entry.allowedFromTimeMin; - } - - if(allowedUntilTime.length() < 1) - { - allowedUntilTime = "old"; - allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; - allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; - } + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + return; } - if(!foundExisting) + Nuki::CmdResult resultKp = _nukiOpener.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); + bool foundExisting = false; + + if(resultKp == Nuki::CmdResult::Success) + { + delay(250); + std::list entries; + _nukiOpener.getKeypadEntries(&entries); + + for(const auto& entry : entries) + { + if (codeId != entry.codeId) continue; + else foundExisting = true; + + if(name.length() < 1) + { + memset(oldName, 0, sizeof(oldName)); + memcpy(oldName, entry.name, sizeof(entry.name)); + } + if(code == 12) code = entry.code; + if(enabled == 2) enabled = entry.enabled; + if(timeLimited == 2) timeLimited = entry.timeLimited; + if(allowedFrom.length() < 1) + { + allowedFrom = "old"; + allowedFromAr[0] = entry.allowedFromYear; + allowedFromAr[1] = entry.allowedFromMonth; + allowedFromAr[2] = entry.allowedFromDay; + allowedFromAr[3] = entry.allowedFromHour; + allowedFromAr[4] = entry.allowedFromMin; + allowedFromAr[5] = entry.allowedFromSec; + } + if(allowedUntil.length() < 1) + { + allowedUntil = "old"; + allowedUntilAr[0] = entry.allowedUntilYear; + allowedUntilAr[1] = entry.allowedUntilMonth; + allowedUntilAr[2] = entry.allowedUntilDay; + allowedUntilAr[3] = entry.allowedUntilHour; + allowedUntilAr[4] = entry.allowedUntilMin; + allowedUntilAr[5] = entry.allowedUntilSec; + } + if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays; + if(allowedFromTime.length() < 1) + { + allowedFromTime = "old"; + allowedFromTimeAr[0] = entry.allowedFromTimeHour; + allowedFromTimeAr[1] = entry.allowedFromTimeMin; + } + + if(allowedUntilTime.length() < 1) + { + allowedUntilTime = "old"; + allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; + allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; + } + } + + if(!foundExisting) + { + _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); return; } - } - else - { - _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); - return; - } - NukiOpener::UpdatedKeypadEntry entry; + NukiOpener::UpdatedKeypadEntry entry; - memset(&entry, 0, sizeof(entry)); - entry.codeId = codeId; - entry.code = code; + memset(&entry, 0, sizeof(entry)); + entry.codeId = codeId; + entry.code = code; - if(name.length() < 1) - { - size_t nameLen = strlen(oldName); - memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen); - } - else - { - size_t nameLen = name.length(); - memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - } - entry.enabled = enabled; - entry.timeLimited = timeLimited; - - if(enabled == 1) - { - if(timeLimited == 1) + if(name.length() < 1) { - if(allowedFrom.length() > 0) - { - entry.allowedFromYear = allowedFromAr[0]; - entry.allowedFromMonth = allowedFromAr[1]; - entry.allowedFromDay = allowedFromAr[2]; - entry.allowedFromHour = allowedFromAr[3]; - entry.allowedFromMin = allowedFromAr[4]; - entry.allowedFromSec = allowedFromAr[5]; - } + size_t nameLen = strlen(oldName); + memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen); + } + else + { + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + } + entry.enabled = enabled; + entry.timeLimited = timeLimited; - if(allowedUntil.length() > 0) + if(enabled == 1) + { + if(timeLimited == 1) { - entry.allowedUntilYear = allowedUntilAr[0]; - entry.allowedUntilMonth = allowedUntilAr[1]; - entry.allowedUntilDay = allowedUntilAr[2]; - entry.allowedUntilHour = allowedUntilAr[3]; - entry.allowedUntilMin = allowedUntilAr[4]; - entry.allowedUntilSec = allowedUntilAr[5]; - } + if(allowedFrom.length() > 0) + { + entry.allowedFromYear = allowedFromAr[0]; + entry.allowedFromMonth = allowedFromAr[1]; + entry.allowedFromDay = allowedFromAr[2]; + entry.allowedFromHour = allowedFromAr[3]; + entry.allowedFromMin = allowedFromAr[4]; + entry.allowedFromSec = allowedFromAr[5]; + } - entry.allowedWeekdays = allowedWeekdaysInt; + if(allowedUntil.length() > 0) + { + entry.allowedUntilYear = allowedUntilAr[0]; + entry.allowedUntilMonth = allowedUntilAr[1]; + entry.allowedUntilDay = allowedUntilAr[2]; + entry.allowedUntilHour = allowedUntilAr[3]; + entry.allowedUntilMin = allowedUntilAr[4]; + entry.allowedUntilSec = allowedUntilAr[5]; + } - if(allowedFromTime.length() > 0) - { - entry.allowedFromTimeHour = allowedFromTimeAr[0]; - entry.allowedFromTimeMin = allowedFromTimeAr[1]; - } + entry.allowedWeekdays = allowedWeekdaysInt; - if(allowedUntilTime.length() > 0) - { - entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; - entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + if(allowedFromTime.length() > 0) + { + entry.allowedFromTimeHour = allowedFromTimeAr[0]; + entry.allowedFromTimeMin = allowedFromTimeAr[1]; + } + + if(allowedUntilTime.length() > 0) + { + entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; + entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + } } } + + result = _nukiOpener.updateKeypadEntry(entry); + Log->print(F("Update keypad code: ")); + Log->println((int)result); } - - result = _nukiOpener.updateKeypadEntry(entry); - Log->print(F("Update keypad code: ")); - Log->println((int)result); } + else + { + _network->publishKeypadJsonCommandResult("invalidAction"); + return; + } + + if(result != Nuki::CmdResult::Success) { + ++retryCount; + } + else break; } - else + + updateKeypad(false); + + if((int)result != -1) { - _network->publishKeypadJsonCommandResult("invalidAction"); - return; + char resultStr[15]; + memset(&resultStr, 0, sizeof(resultStr)); + NukiOpener::cmdResultToString(result, resultStr); + _network->publishKeypadJsonCommandResult(resultStr); } - - if(result != Nuki::CmdResult::Success) { - ++retryCount; - } - else break; - } - - updateKeypad(false); - - if((int)result != -1) - { - char resultStr[15]; - memset(&resultStr, 0, sizeof(resultStr)); - NukiOpener::cmdResultToString(result, resultStr); - _network->publishKeypadJsonCommandResult(resultStr); } } else diff --git a/src/NukiOpenerWrapper.h b/src/NukiOpenerWrapper.h index 1d5eb28..6853711 100644 --- a/src/NukiOpenerWrapper.h +++ b/src/NukiOpenerWrapper.h @@ -106,12 +106,16 @@ private: bool _publishAuthData = false; bool _clearAuthData = false; bool _disableNonJSON = false; + bool _checkKeypadCodes = false; int _nrOfRetries = 0; int _retryDelay = 0; int _retryConfigCount = 0; int _retryLockstateCount = 0; int64_t _nextRetryTs = 0; + int64_t _invalidCount = 0; + int64_t _lastCodeCheck = 0; std::vector _keypadCodeIds; + std::vector _keypadCodes; std::vector _timeControlIds; std::vector _authIds; diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index 7cc1092..da04bfb 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -137,7 +137,7 @@ void NukiWrapper::readSettings() _retryDelay = _preferences->getInt(preference_command_retry_delay); _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; _disableNonJSON = _preferences->getBool(preference_disable_non_json, false); - + _checkKeypadCodes = _preferences->getBool(preference_keypad_check_code_enabled, false); _preferences->getBytes(preference_conf_lock_basic_acl, &_basicLockConfigaclPrefs, sizeof(_basicLockConfigaclPrefs)); _preferences->getBytes(preference_conf_lock_advanced_acl, &_advancedLockConfigaclPrefs, sizeof(_advancedLockConfigaclPrefs)); @@ -369,6 +369,10 @@ void NukiWrapper::update() _nextKeypadUpdateTs = ts + _intervalKeypad * 1000; updateKeypad(false); } + if(_checkKeypadCodes && _invalidCount > 0 && ts - 120000 < _lastCodeCheck) + { + _invalidCount--; + } } if(_clearAuthData) { @@ -773,10 +777,13 @@ void NukiWrapper::updateKeypad(bool retrieved) _network->publishKeypad(entries, _maxKeypadCodeCount); _keypadCodeIds.clear(); + _keypadCodes.clear(); _keypadCodeIds.reserve(entries.size()); + _keypadCodes.reserve(entries.size()); for(const auto& entry : entries) { _keypadCodeIds.push_back(entry.codeId); + _keypadCodes.push_back(entry.code); } } @@ -1918,373 +1925,421 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value) { idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId) != _keypadCodeIds.end(); } + + if(strcmp(action, "check") == 0) { + if(!_preferences->getBool(preference_keypad_check_code_enabled, false)) + { + _network->publishKeypadJsonCommandResult("checkingKeypadCodesDisabled"); + return; + } - Nuki::CmdResult result = (Nuki::CmdResult)-1; - int retryCount = 0; + if(pow(_invalidCount, 5) + _lastCodeCheck > (esp_timer_get_time() / 1000)) + { + _network->publishKeypadJsonCommandResult("checkingCodesBlockedTooManyInvalid"); + _lastCodeCheck = (esp_timer_get_time() / 1000); + return; + } - while(retryCount < _nrOfRetries + 1) - { - if(strcmp(action, "delete") == 0) { - if(idExists) + _lastCodeCheck = (esp_timer_get_time() / 1000); + + if(idExists) + { + auto it1 = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId); + int index = it1 - _keypadCodeIds.begin(); + Log->print(F("Check keypad code: ")); + + if(code == _keypadCodes[index]) { - result = _nukiLock.deleteKeypadEntry(codeId); - Log->print(F("Delete keypad code: ")); - Log->println((int)result); + _invalidCount = 0; + _network->publishKeypadJsonCommandResult("codeValid"); + Log->println("Valid"); + return; } else { - _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + _invalidCount++; + _network->publishKeypadJsonCommandResult("codeInvalid"); + Log->println("Invalid"); return; } } - else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0) + else { - if(name.length() < 1) - { - if (strcmp(action, "update") != 0) + _invalidCount++; + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + return; + } + } + else + { + + Nuki::CmdResult result = (Nuki::CmdResult)-1; + int retryCount = 0; + + while(retryCount < _nrOfRetries + 1) + { + if(strcmp(action, "delete") == 0) { + if(idExists) { - _network->publishKeypadJsonCommandResult("noNameSet"); + result = _nukiLock.deleteKeypadEntry(codeId); + Log->print(F("Delete keypad code: ")); + Log->println((int)result); + } + else + { + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); return; } } - - if(code != 12) + else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0) { - String codeStr = json["code"].as(); - bool codeValid = code > 100000 && code < 1000000 && (codeStr.indexOf('0') == -1); - - if (!codeValid) + if(name.length() < 1) { - _network->publishKeypadJsonCommandResult("noValidCodeSet"); - return; - } - } - else if (strcmp(action, "update") != 0) - { - _network->publishKeypadJsonCommandResult("noCodeSet"); - return; - } - - unsigned int allowedFromAr[6]; - unsigned int allowedUntilAr[6]; - unsigned int allowedFromTimeAr[2]; - unsigned int allowedUntilTimeAr[2]; - uint8_t allowedWeekdaysInt = 0; - - if(timeLimited == 1) - { - if(allowedFrom.length() > 0) - { - if(allowedFrom.length() == 19) + if (strcmp(action, "update") != 0) { - allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt(); - allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt(); - allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt(); - allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt(); - allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt(); - allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt(); + _network->publishKeypadJsonCommandResult("noNameSet"); + return; + } + } - if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59) + if(code != 12) + { + String codeStr = json["code"].as(); + bool codeValid = code > 100000 && code < 1000000 && (codeStr.indexOf('0') == -1); + + if (!codeValid) + { + _network->publishKeypadJsonCommandResult("noValidCodeSet"); + return; + } + } + else if (strcmp(action, "update") != 0) + { + _network->publishKeypadJsonCommandResult("noCodeSet"); + return; + } + + unsigned int allowedFromAr[6]; + unsigned int allowedUntilAr[6]; + unsigned int allowedFromTimeAr[2]; + unsigned int allowedUntilTimeAr[2]; + uint8_t allowedWeekdaysInt = 0; + + if(timeLimited == 1) + { + if(allowedFrom.length() > 0) + { + if(allowedFrom.length() == 19) + { + allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt(); + allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt(); + allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt(); + allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt(); + allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt(); + allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt(); + + if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedFrom"); - return; - } - } - if(allowedUntil.length() > 0) - { - if(allowedUntil.length() > 0 == 19) + if(allowedUntil.length() > 0) { - allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); - allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt(); - allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt(); - allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt(); - allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt(); - allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt(); + if(allowedUntil.length() > 0 == 19) + { + allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); + allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt(); + allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt(); + allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt(); + allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt(); + allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt(); - if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59) + if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedUntil"); - return; - } - } - if(allowedFromTime.length() > 0) - { - if(allowedFromTime.length() == 5) + if(allowedFromTime.length() > 0) { - allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt(); - allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt(); + if(allowedFromTime.length() == 5) + { + allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt(); + allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt(); - if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59) + if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); return; } } - else - { - _network->publishKeypadJsonCommandResult("invalidAllowedFromTime"); - return; - } - } - if(allowedUntilTime.length() > 0) - { - if(allowedUntilTime.length() == 5) + if(allowedUntilTime.length() > 0) { - allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt(); - allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt(); + if(allowedUntilTime.length() == 5) + { + allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt(); + allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt(); - if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59) + if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59) + { + _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); return; } } - else + + if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64; + if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32; + if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16; + if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8; + if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4; + if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2; + if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1; + } + + if(strcmp(action, "add") == 0) + { + NukiLock::NewKeypadEntry entry; + memset(&entry, 0, sizeof(entry)); + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + entry.code = code; + entry.timeLimited = timeLimited == 1 ? 1 : 0; + + if(allowedFrom.length() > 0) { - _network->publishKeypadJsonCommandResult("invalidAllowedUntilTime"); + entry.allowedFromYear = allowedFromAr[0]; + entry.allowedFromMonth = allowedFromAr[1]; + entry.allowedFromDay = allowedFromAr[2]; + entry.allowedFromHour = allowedFromAr[3]; + entry.allowedFromMin = allowedFromAr[4]; + entry.allowedFromSec = allowedFromAr[5]; + } + + if(allowedUntil.length() > 0) + { + entry.allowedUntilYear = allowedUntilAr[0]; + entry.allowedUntilMonth = allowedUntilAr[1]; + entry.allowedUntilDay = allowedUntilAr[2]; + entry.allowedUntilHour = allowedUntilAr[3]; + entry.allowedUntilMin = allowedUntilAr[4]; + entry.allowedUntilSec = allowedUntilAr[5]; + } + + entry.allowedWeekdays = allowedWeekdaysInt; + + if(allowedFromTime.length() > 0) + { + entry.allowedFromTimeHour = allowedFromTimeAr[0]; + entry.allowedFromTimeMin = allowedFromTimeAr[1]; + } + + if(allowedUntilTime.length() > 0) + { + entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; + entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + } + + result = _nukiLock.addKeypadEntry(entry); + Log->print(F("Add keypad code: ")); + Log->println((int)result); + } + else if (strcmp(action, "update") == 0) + { + if(!codeId) + { + _network->publishKeypadJsonCommandResult("noCodeIdSet"); return; } - } - if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64; - if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32; - if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16; - if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8; - if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4; - if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2; - if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1; - } - - if(strcmp(action, "add") == 0) - { - NukiLock::NewKeypadEntry entry; - memset(&entry, 0, sizeof(entry)); - size_t nameLen = name.length(); - memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - entry.code = code; - entry.timeLimited = timeLimited == 1 ? 1 : 0; - - if(allowedFrom.length() > 0) - { - entry.allowedFromYear = allowedFromAr[0]; - entry.allowedFromMonth = allowedFromAr[1]; - entry.allowedFromDay = allowedFromAr[2]; - entry.allowedFromHour = allowedFromAr[3]; - entry.allowedFromMin = allowedFromAr[4]; - entry.allowedFromSec = allowedFromAr[5]; - } - - if(allowedUntil.length() > 0) - { - entry.allowedUntilYear = allowedUntilAr[0]; - entry.allowedUntilMonth = allowedUntilAr[1]; - entry.allowedUntilDay = allowedUntilAr[2]; - entry.allowedUntilHour = allowedUntilAr[3]; - entry.allowedUntilMin = allowedUntilAr[4]; - entry.allowedUntilSec = allowedUntilAr[5]; - } - - entry.allowedWeekdays = allowedWeekdaysInt; - - if(allowedFromTime.length() > 0) - { - entry.allowedFromTimeHour = allowedFromTimeAr[0]; - entry.allowedFromTimeMin = allowedFromTimeAr[1]; - } - - if(allowedUntilTime.length() > 0) - { - entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; - entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; - } - - result = _nukiLock.addKeypadEntry(entry); - Log->print(F("Add keypad code: ")); - Log->println((int)result); - } - else if (strcmp(action, "update") == 0) - { - if(!codeId) - { - _network->publishKeypadJsonCommandResult("noCodeIdSet"); - return; - } - - if(!idExists) - { - _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); - return; - } - - Nuki::CmdResult resultKp = _nukiLock.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); - bool foundExisting = false; - - if(resultKp == Nuki::CmdResult::Success) - { - delay(250); - std::list entries; - _nukiLock.getKeypadEntries(&entries); - - for(const auto& entry : entries) + if(!idExists) { - if (codeId != entry.codeId) continue; - else foundExisting = true; - - if(name.length() < 1) - { - memset(oldName, 0, sizeof(oldName)); - memcpy(oldName, entry.name, sizeof(entry.name)); - } - if(code == 12) code = entry.code; - if(enabled == 2) enabled = entry.enabled; - if(timeLimited == 2) timeLimited = entry.timeLimited; - if(allowedFrom.length() < 1) - { - allowedFrom = "old"; - allowedFromAr[0] = entry.allowedFromYear; - allowedFromAr[1] = entry.allowedFromMonth; - allowedFromAr[2] = entry.allowedFromDay; - allowedFromAr[3] = entry.allowedFromHour; - allowedFromAr[4] = entry.allowedFromMin; - allowedFromAr[5] = entry.allowedFromSec; - } - if(allowedUntil.length() < 1) - { - allowedUntil = "old"; - allowedUntilAr[0] = entry.allowedUntilYear; - allowedUntilAr[1] = entry.allowedUntilMonth; - allowedUntilAr[2] = entry.allowedUntilDay; - allowedUntilAr[3] = entry.allowedUntilHour; - allowedUntilAr[4] = entry.allowedUntilMin; - allowedUntilAr[5] = entry.allowedUntilSec; - } - if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays; - if(allowedFromTime.length() < 1) - { - allowedFromTime = "old"; - allowedFromTimeAr[0] = entry.allowedFromTimeHour; - allowedFromTimeAr[1] = entry.allowedFromTimeMin; - } - - if(allowedUntilTime.length() < 1) - { - allowedUntilTime = "old"; - allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; - allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; - } + _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + return; } - if(!foundExisting) + Nuki::CmdResult resultKp = _nukiLock.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); + bool foundExisting = false; + + if(resultKp == Nuki::CmdResult::Success) + { + delay(250); + std::list entries; + _nukiLock.getKeypadEntries(&entries); + + for(const auto& entry : entries) + { + if (codeId != entry.codeId) continue; + else foundExisting = true; + + if(name.length() < 1) + { + memset(oldName, 0, sizeof(oldName)); + memcpy(oldName, entry.name, sizeof(entry.name)); + } + if(code == 12) code = entry.code; + if(enabled == 2) enabled = entry.enabled; + if(timeLimited == 2) timeLimited = entry.timeLimited; + if(allowedFrom.length() < 1) + { + allowedFrom = "old"; + allowedFromAr[0] = entry.allowedFromYear; + allowedFromAr[1] = entry.allowedFromMonth; + allowedFromAr[2] = entry.allowedFromDay; + allowedFromAr[3] = entry.allowedFromHour; + allowedFromAr[4] = entry.allowedFromMin; + allowedFromAr[5] = entry.allowedFromSec; + } + if(allowedUntil.length() < 1) + { + allowedUntil = "old"; + allowedUntilAr[0] = entry.allowedUntilYear; + allowedUntilAr[1] = entry.allowedUntilMonth; + allowedUntilAr[2] = entry.allowedUntilDay; + allowedUntilAr[3] = entry.allowedUntilHour; + allowedUntilAr[4] = entry.allowedUntilMin; + allowedUntilAr[5] = entry.allowedUntilSec; + } + if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays; + if(allowedFromTime.length() < 1) + { + allowedFromTime = "old"; + allowedFromTimeAr[0] = entry.allowedFromTimeHour; + allowedFromTimeAr[1] = entry.allowedFromTimeMin; + } + + if(allowedUntilTime.length() < 1) + { + allowedUntilTime = "old"; + allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; + allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; + } + } + + if(!foundExisting) + { + _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); + return; + } + } + else { _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); return; } - } - else - { - _network->publishKeypadJsonCommandResult("failedToRetrieveExistingKeypadEntry"); - return; - } - NukiLock::UpdatedKeypadEntry entry; + NukiLock::UpdatedKeypadEntry entry; - memset(&entry, 0, sizeof(entry)); - entry.codeId = codeId; - entry.code = code; + memset(&entry, 0, sizeof(entry)); + entry.codeId = codeId; + entry.code = code; - if(name.length() < 1) - { - size_t nameLen = strlen(oldName); - memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen); - } - else - { - size_t nameLen = name.length(); - memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - } - entry.enabled = enabled; - entry.timeLimited = timeLimited; - - if(enabled == 1) - { - if(timeLimited == 1) + if(name.length() < 1) { - if(allowedFrom.length() > 0) - { - entry.allowedFromYear = allowedFromAr[0]; - entry.allowedFromMonth = allowedFromAr[1]; - entry.allowedFromDay = allowedFromAr[2]; - entry.allowedFromHour = allowedFromAr[3]; - entry.allowedFromMin = allowedFromAr[4]; - entry.allowedFromSec = allowedFromAr[5]; - } + size_t nameLen = strlen(oldName); + memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen); + } + else + { + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + } + entry.enabled = enabled; + entry.timeLimited = timeLimited; - if(allowedUntil.length() > 0) + if(enabled == 1) + { + if(timeLimited == 1) { - entry.allowedUntilYear = allowedUntilAr[0]; - entry.allowedUntilMonth = allowedUntilAr[1]; - entry.allowedUntilDay = allowedUntilAr[2]; - entry.allowedUntilHour = allowedUntilAr[3]; - entry.allowedUntilMin = allowedUntilAr[4]; - entry.allowedUntilSec = allowedUntilAr[5]; - } + if(allowedFrom.length() > 0) + { + entry.allowedFromYear = allowedFromAr[0]; + entry.allowedFromMonth = allowedFromAr[1]; + entry.allowedFromDay = allowedFromAr[2]; + entry.allowedFromHour = allowedFromAr[3]; + entry.allowedFromMin = allowedFromAr[4]; + entry.allowedFromSec = allowedFromAr[5]; + } - entry.allowedWeekdays = allowedWeekdaysInt; + if(allowedUntil.length() > 0) + { + entry.allowedUntilYear = allowedUntilAr[0]; + entry.allowedUntilMonth = allowedUntilAr[1]; + entry.allowedUntilDay = allowedUntilAr[2]; + entry.allowedUntilHour = allowedUntilAr[3]; + entry.allowedUntilMin = allowedUntilAr[4]; + entry.allowedUntilSec = allowedUntilAr[5]; + } - if(allowedFromTime.length() > 0) - { - entry.allowedFromTimeHour = allowedFromTimeAr[0]; - entry.allowedFromTimeMin = allowedFromTimeAr[1]; - } + entry.allowedWeekdays = allowedWeekdaysInt; - if(allowedUntilTime.length() > 0) - { - entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; - entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + if(allowedFromTime.length() > 0) + { + entry.allowedFromTimeHour = allowedFromTimeAr[0]; + entry.allowedFromTimeMin = allowedFromTimeAr[1]; + } + + if(allowedUntilTime.length() > 0) + { + entry.allowedUntilTimeHour = allowedUntilTimeAr[0]; + entry.allowedUntilTimeMin = allowedUntilTimeAr[1]; + } } } + + result = _nukiLock.updateKeypadEntry(entry); + Log->print(F("Update keypad code: ")); + Log->println((int)result); } - - result = _nukiLock.updateKeypadEntry(entry); - Log->print(F("Update keypad code: ")); - Log->println((int)result); } + else + { + _network->publishKeypadJsonCommandResult("invalidAction"); + return; + } + + if(result != Nuki::CmdResult::Success) { + ++retryCount; + } + else break; } - else + + updateKeypad(false); + + if((int)result != -1) { - _network->publishKeypadJsonCommandResult("invalidAction"); - return; + char resultStr[15]; + memset(&resultStr, 0, sizeof(resultStr)); + NukiLock::cmdResultToString(result, resultStr); + _network->publishKeypadJsonCommandResult(resultStr); } - - if(result != Nuki::CmdResult::Success) { - ++retryCount; - } - else break; - } - - updateKeypad(false); - - if((int)result != -1) - { - char resultStr[15]; - memset(&resultStr, 0, sizeof(resultStr)); - NukiLock::cmdResultToString(result, resultStr); - _network->publishKeypadJsonCommandResult(resultStr); } } else diff --git a/src/NukiWrapper.h b/src/NukiWrapper.h index ee695f9..0cf886b 100644 --- a/src/NukiWrapper.h +++ b/src/NukiWrapper.h @@ -107,7 +107,11 @@ private: int _restartBeaconTimeout = 0; // seconds bool _publishAuthData = false; bool _clearAuthData = false; + bool _checkKeypadCodes = false; + int64_t _invalidCount = 0; + int64_t _lastCodeCheck = 0; std::vector _keypadCodeIds; + std::vector _keypadCodes; std::vector _timeControlIds; std::vector _authIds; diff --git a/src/PreferencesKeys.h b/src/PreferencesKeys.h index fc0cb16..7edc997 100644 --- a/src/PreferencesKeys.h +++ b/src/PreferencesKeys.h @@ -98,6 +98,7 @@ #define preference_recon_netw_on_mqtt_discon (char*)"recNtwMqttDis" #define preference_official_hybrid_actions (char*)"hybridAct" #define preference_official_hybrid_retry (char*)"hybridRtry" +#define preference_keypad_check_code_enabled (char*)"kpChkEna" //NOT USER CHANGABLE #define preference_updater_version (char*)"updVer" @@ -284,7 +285,7 @@ private: preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, preference_network_custom_irq, preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi, preference_network_custom_pwr, preference_network_custom_mdio, preference_ntw_reconfigure, preference_lock_max_auth_entry_count, preference_opener_max_auth_entry_count, - preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, + preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_keypad_check_code_enabled }; std::vector _redact = { @@ -300,7 +301,7 @@ private: preference_publish_authdata, preference_publish_debug_info, preference_network_wifi_fallback_disabled, preference_official_hybrid_enabled, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_recon_netw_on_mqtt_discon, preference_webserial_enabled, - preference_ntw_reconfigure + preference_ntw_reconfigure, preference_keypad_check_code_enabled }; std::vector _bytePrefs = { diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 208124e..ea5d492 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -1561,6 +1561,16 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message) //configChanged = true; } } + else if(key == "KPCHECK") + { + if(_preferences->getBool(preference_keypad_check_code_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_keypad_check_code_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + //configChanged = true; + } + } else if(key == "KPENA") { if(_preferences->getBool(preference_keypad_control_enabled, false) != (value == "1")) @@ -2639,15 +2649,15 @@ void WebCfgServer::buildCredHtml(AsyncWebServerRequest *request) { _response = ""; buildHtmlHeader(); - _response.concat(""); + _response.concat(""); _response.concat("

Credentials

"); _response.concat(""); - printInputField("CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "", false, true); - printInputField("CREDPASS", "Password", "*", 30, "", true, true); + printInputField("CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "id=\"inputuser\"", false, true); + printInputField("CREDPASS", "Password", "*", 30, "id=\"inputpass\"", true, true); printInputField("CREDPASSRE", "Retype password", "*", 30, "", true); _response.concat("
"); _response.concat("
"); - _response.concat(""); + _response.concat(""); if(_nuki != nullptr) { _response.concat("

"); @@ -2919,6 +2929,7 @@ void WebCfgServer::buildAccLvlHtml(AsyncWebServerRequest *request) printCheckBox("KPPUB", "Publish keypad entries information", _preferences->getBool(preference_keypad_info_enabled), ""); printCheckBox("KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), ""); printCheckBox("KPCODE", "Also publish keypad codes (Disadvised for security reasons)", _preferences->getBool(preference_keypad_publish_code, false), ""); + printCheckBox("KPCHECK", "Allow checking if keypad codes are valid (Disadvised for security reasons)", _preferences->getBool(preference_keypad_check_code_enabled, false), ""); printCheckBox("KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), ""); } printCheckBox("TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), ""); @@ -3386,6 +3397,8 @@ void WebCfgServer::buildInfoHtml(AsyncWebServerRequest *request) _response.concat(_preferences->getBool(preference_keypad_topic_per_entry, false) ? "Yes" : "No"); _response.concat("\nPublish Keypad codes: "); _response.concat(_preferences->getBool(preference_keypad_publish_code, false) ? "Yes" : "No"); + _response.concat("\nAllow checking Keypad codes: "); + _response.concat(_preferences->getBool(preference_keypad_check_code_enabled, false) ? "Yes" : "No"); _response.concat("\nMax keypad entries to retrieve: "); _response.concat(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); _response.concat("\nPublish timecontrol info: "); From 7d34e43c335cdf364c4b98facbe74f6d4cffe05e Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 15 Oct 2024 22:33:43 +0200 Subject: [PATCH 3/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23449a8..e95a301 100644 --- a/README.md +++ b/README.md @@ -565,7 +565,7 @@ Examples: ### Result of attempted keypad code changes -The result of the last configuration change action will be published to the `configuration/commandResultJson` MQTT topic.
+The result of the last keypad change action will be published to the `keypad/commandResultJson` MQTT topic.
Possible values are "noValidPinSet", "keypadControlDisabled", "keypadNotAvailable", "keypadDisabled", "invalidConfig", "invalidJson", "noActionSet", "invalidAction", "noExistingCodeIdSet", "noNameSet", "noValidCodeSet", "noCodeSet", "invalidAllowedFrom", "invalidAllowedUntil", "invalidAllowedFromTime", "invalidAllowedUntilTime", "success", "failed", "timeOut", "working", "notPaired", "error" and "undefined".
## Keypad control (alternative, optional) From b813faf771c5775edd438330e468b0d0f2462950 Mon Sep 17 00:00:00 2001 From: iranl Date: Sat, 2 Nov 2024 19:56:36 +0100 Subject: [PATCH 4/5] Fix --- README.md | 29 +++++++++++++++-------------- src/Config.h | 2 +- src/NukiNetwork.cpp | 10 +++++----- src/NukiOpenerWrapper.cpp | 29 ++++++++++++++++------------- src/NukiWrapper.cpp | 22 +++++++++++++--------- src/WebCfgServer.cpp | 10 +++++----- 6 files changed, 55 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index dedf3c8..e251f85 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,7 @@ In a browser navigate to the IP address assigned to the ESP32. - Publish keypad entries information (Only available when a Keypad is detected): Enable to publish information about keypad codes through MQTT, see the "[Keypad control](#keypad-control-optional)" section of this README - Also publish keypad codes (Only available when a Keypad is detected): Enable to publish the actual keypad codes through MQTT, note that is could be considered a security risk - Add, modify and delete keypad codes (Only available when a Keypad is detected): Enable to allow configuration of keypad codes through MQTT, see the "[Keypad control](#keypad-control-optional)" section of this README +- Allow checking if keypad codes are valid (Only available when a Keypad is detected): Enable to allow checking if a given codeId and code combination is valid through MQTT, note that is could be considered a security risk - Publish timecontrol information: Enable to publish information about timecontrol entries through MQTT, see the "[Timecontrol](#timecontrol)" section of this README - Add, modify and delete timecontrol entries: Enable to allow configuration of timecontrol entries through MQTT, see the "[Timecontrol](#timecontrol)" section of this README - Publish authorization information: Enable to publish information about authorization entries through MQTT, see the "[Authorization](#authorization)" section of this README @@ -588,19 +589,19 @@ By default a maximum of 10 entries are published. To change Nuki Lock/Opener keypad settings set the `[lock/opener]/keypad/actionJson` topic to a JSON formatted value containing the following nodes. -| Node | Delete | Add | Update | Usage | Possible values | -|------------------|----------|----------|----------|------------------------------------------------------------------------------------------------------------------|----------------------------------------| -| action | Required | Required | Required | The action to execute | "delete", "add", "update" | -| codeId | Required | Not used | Required | The code ID of the existing code to delete or update | Integer | -| code | Not used | Required | Optional | The code to create or update | 6-digit Integer without zero's, can't start with "12"| -| enabled | Not used | Not used | Optional | Enable or disable the code, always enabled on add | 1 = enabled, 0 = disabled | -| name | Not used | Required | Optional | The name of the code to create or update | String, max 20 chars | -| timeLimited | Not used | Optional | Optional | If this authorization is restricted to access only at certain times, requires enabled = 1 | 1 = enabled, 0 = disabled | -| allowedFrom | Not used | Optional | Optional | The start timestamp from which access should be allowed (requires enabled = 1 and timeLimited = 1) | "YYYY-MM-DD HH:MM:SS" | -| allowedUntil | Not used | Optional | Optional | The end timestamp until access should be allowed (requires enabled = 1 and timeLimited = 1) | "YYYY-MM-DD HH:MM:SS" | -| allowedWeekdays | Not used | Optional | Optional | Weekdays on which access should be allowed (requires enabled = 1 and timeLimited = 1) | Array of days: "mon", "tue", "wed", "thu" , "fri" "sat", "sun"| -| allowedFromTime | Not used | Optional | Optional | The start time per day from which access should be allowed (requires enabled = 1 and timeLimited = 1) | "HH:MM" | -| allowedUntilTime | Not used | Optional | Optional | The end time per day until access should be allowed (requires enabled = 1 and timeLimited = 1) | "HH:MM" | +| Node | Delete | Add | Update | Check | Usage | Possible values | +|------------------|----------|----------|----------|----------|------------------------------------------------------------------------------------------------------------------|----------------------------------------| +| action | Required | Required | Required | Required | The action to execute | "delete", "add", "update", "check" | +| codeId | Required | Not used | Required | Required | The code ID of the existing code to delete or update | Integer | +| code | Not used | Required | Optional | Required | The code to create or update | 6-digit Integer without zero's, can't start with "12"| +| enabled | Not used | Not used | Optional | Not used | Enable or disable the code, always enabled on add | 1 = enabled, 0 = disabled | +| name | Not used | Required | Optional | Not used | The name of the code to create or update | String, max 20 chars | +| timeLimited | Not used | Optional | Optional | Not used | If this authorization is restricted to access only at certain times, requires enabled = 1 | 1 = enabled, 0 = disabled | +| allowedFrom | Not used | Optional | Optional | Not used | The start timestamp from which access should be allowed (requires enabled = 1 and timeLimited = 1) | "YYYY-MM-DD HH:MM:SS" | +| allowedUntil | Not used | Optional | Optional | Not used | The end timestamp until access should be allowed (requires enabled = 1 and timeLimited = 1) | "YYYY-MM-DD HH:MM:SS" | +| allowedWeekdays | Not used | Optional | Optional | Not used | Weekdays on which access should be allowed (requires enabled = 1 and timeLimited = 1) | Array of days: "mon", "tue", "wed", "thu" , "fri" "sat", "sun"| +| allowedFromTime | Not used | Optional | Optional | Not used | The start time per day from which access should be allowed (requires enabled = 1 and timeLimited = 1) | "HH:MM" | +| allowedUntilTime | Not used | Optional | Optional | Not used | The end time per day until access should be allowed (requires enabled = 1 and timeLimited = 1) | "HH:MM" | Examples: - Delete: `{ "action": "delete", "codeId": "1234" }` @@ -664,7 +665,7 @@ To change Nuki Lock/Opener timecontrol settings set the `[lock/opener]/timecontr | enabled | Not used | Not used | Optional | Enable or disable the entry, always enabled on add | 1 = enabled, 0 = disabled | | weekdays | Not used | Optional | Optional | Weekdays on which the chosen lock action should be exectued (requires enabled = 1) | Array of days: "mon", "tue", "wed", "thu" , "fri" "sat", "sun" | | time | Not used | Required | Optional | The time on which the chosen lock action should be executed (requires enabled = 1) | "HH:MM" | -| lockAction | Not used | Required | Optional | The lock action that should be executed on the chosen weekdays at the chosen time (requires enabled = 1) | For the Nuki lock: "Unlock", "Lock", "Unlatch", "LockNgo", "LockNgoUnlatch", "FullLock". For the Nuki Opener: "ActivateRTO", "DeactivateRTO", "ElectricStrikeActuation", "ActivateCM", "DeactivateCM | +| lockAction | Not used | Required | Optional | The lock action that should be executed on the chosen weekdays at the chosen time (requires enabled = 1) | For the Nuki lock: "Unlock", "Lock", "Unlatch", "LockNgo", "LockNgoUnlatch", "FullLock". For the Nuki Opener: "ActivateRTO", "DeactivateRTO", "ElectricStrikeActuation", "ActivateCM", "DeactivateCM | Examples: - Delete: `{ "action": "delete", "entryId": "1234" }` diff --git a/src/Config.h b/src/Config.h index 29bb237..b82a95b 100644 --- a/src/Config.h +++ b/src/Config.h @@ -4,7 +4,7 @@ #define NUKI_HUB_VERSION "9.02" #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2024-11-01" +#define NUKI_HUB_DATE "2024-11-02" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index a297e4f..9ae5d7a 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -607,12 +607,12 @@ bool NukiNetwork::reconnect() { _mqttConnectionState = 0; - while (!_device->mqttConnected() && (esp_timer_get_time() / 1000) > _nextReconnect) + while (!_device->mqttConnected() && espMillis() > _nextReconnect) { if(strcmp(_mqttBrokerAddr, "") == 0) { Log->println(F("MQTT Broker not configured, aborting connection attempt.")); - _nextReconnect = (esp_timer_get_time() / 1000) + 5000; + _nextReconnect = espMillis() + 5000; return false; } @@ -634,9 +634,9 @@ bool NukiNetwork::reconnect() _device->mqttSetServer(_mqttBrokerAddr, _mqttPort); _device->mqttConnect(); - int64_t timeout = (esp_timer_get_time() / 1000) + 60000; + int64_t timeout = espMillis() + 60000; - while(!_connectReplyReceived && (esp_timer_get_time() / 1000) < timeout) + while(!_connectReplyReceived && espMillis() < timeout) { delay(50); _device->update(); @@ -696,7 +696,7 @@ bool NukiNetwork::reconnect() Log->print(F("MQTT connect failed, rc=")); _device->printError(); _mqttConnectionState = 0; - _nextReconnect = (esp_timer_get_time() / 1000) + 5000; + _nextReconnect = espMillis() + 5000; //_device->mqttDisconnect(true); } } diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index 5ae4d50..4fa6762 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -278,8 +278,8 @@ void NukiOpenerWrapper::update() updateKeypad(false); } } - - if(_checkKeypadCodes && _invalidCount > 0 && ts - 120000 < _lastCodeCheck) + + if(_checkKeypadCodes && _invalidCount > 0 && (ts - (120000 * _invalidCount)) > _lastCodeCheck) { _invalidCount--; } @@ -893,8 +893,8 @@ void NukiOpenerWrapper::updateAuth(bool retrieved) { Log->println(F("No valid Nuki Lock PIN set")); return; - } - + } + if(!_preferences->getBool(preference_auth_info_enabled)) { return; @@ -2613,14 +2613,14 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value) return; } - if(pow(_invalidCount, 5) + _lastCodeCheck > (esp_timer_get_time() / 1000)) + if((pow(_invalidCount, 5) + _lastCodeCheck) > espMillis()) { _network->publishKeypadJsonCommandResult("checkingCodesBlockedTooManyInvalid"); - _lastCodeCheck = (esp_timer_get_time() / 1000); + _lastCodeCheck = espMillis(); return; } - _lastCodeCheck = (esp_timer_get_time() / 1000); + _lastCodeCheck = espMillis(); if(idExists) { @@ -2639,7 +2639,8 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value) { _invalidCount++; _network->publishKeypadJsonCommandResult("codeInvalid"); - Log->println("Invalid"); + Log->print("Invalid\nInvalid count: "); + Log->println(_invalidCount); return; } } @@ -2647,12 +2648,13 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value) { _invalidCount++; _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + Log->print("Invalid count: "); + Log->println(_invalidCount); return; } } else { - Nuki::CmdResult result = (Nuki::CmdResult)-1; int retryCount = 0; @@ -2732,6 +2734,7 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value) } if(allowedUntil.length() > 0) + { if(allowedUntil.length() == 19) { allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); @@ -2869,7 +2872,7 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value) if(resultKp == Nuki::CmdResult::Success) { - delay(250); + delay(5000); std::list entries; _nukiOpener.getKeypadEntries(&entries); @@ -3213,7 +3216,7 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value) if(resultTc == Nuki::CmdResult::Success) { - delay(250); + delay(5000); std::list timeControlEntries; _nukiOpener.getTimeControlEntries(&timeControlEntries); @@ -3342,7 +3345,7 @@ void NukiOpenerWrapper::onAuthCommandReceived(const char *value) char oldName[33]; const char *action = json["action"].as(); - uint16_t authId = json["authId"].as(); + uint32_t authId = json["authId"].as(); //uint8_t idType = json["idType"].as(); //unsigned char secretKeyK[32] = {0x00}; uint8_t remoteAllowed; @@ -3669,7 +3672,7 @@ void NukiOpenerWrapper::onAuthCommandReceived(const char *value) if(resultAuth == Nuki::CmdResult::Success) { - delay(250); + delay(5000); std::list entries; _nukiOpener.getAuthorizationEntries(&entries); diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index aa419a4..799eb0c 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -431,7 +431,7 @@ void NukiWrapper::update() _network->clearAuthorizationInfo(); _clearAuthData = false; } - if(_checkKeypadCodes && _invalidCount > 0 && ts - 120000 < _lastCodeCheck) + if(_checkKeypadCodes && _invalidCount > 0 && (ts - (120000 * _invalidCount)) > _lastCodeCheck) { _invalidCount--; } @@ -2727,14 +2727,14 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value) return; } - if(pow(_invalidCount, 5) + _lastCodeCheck > (esp_timer_get_time() / 1000)) + if((pow(_invalidCount, 5) + _lastCodeCheck) > espMillis()) { _network->publishKeypadJsonCommandResult("checkingCodesBlockedTooManyInvalid"); - _lastCodeCheck = (esp_timer_get_time() / 1000); + _lastCodeCheck = espMillis(); return; } - _lastCodeCheck = (esp_timer_get_time() / 1000); + _lastCodeCheck = espMillis(); if(idExists) { @@ -2753,7 +2753,8 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value) { _invalidCount++; _network->publishKeypadJsonCommandResult("codeInvalid"); - Log->println("Invalid"); + Log->print("Invalid\nInvalid count: "); + Log->println(_invalidCount); return; } } @@ -2761,6 +2762,8 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value) { _invalidCount++; _network->publishKeypadJsonCommandResult("noExistingCodeIdSet"); + Log->print("Invalid count: "); + Log->println(_invalidCount); return; } } @@ -2846,6 +2849,7 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value) } if(allowedUntil.length() > 0) + { if(allowedUntil.length() == 19) { allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt(); @@ -2983,7 +2987,7 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value) if(resultKp == Nuki::CmdResult::Success) { - delay(250); + delay(5000); std::list entries; _nukiLock.getKeypadEntries(&entries); @@ -3328,7 +3332,7 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value) if(resultTc == Nuki::CmdResult::Success) { - delay(250); + delay(5000); std::list timeControlEntries; _nukiLock.getTimeControlEntries(&timeControlEntries); @@ -3458,7 +3462,7 @@ void NukiWrapper::onAuthCommandReceived(const char *value) char oldName[33]; const char *action = json["action"].as(); - uint16_t authId = json["authId"].as(); + uint32_t authId = json["authId"].as(); //uint8_t idType = json["idType"].as(); //unsigned char secretKeyK[32] = {0x00}; uint8_t remoteAllowed; @@ -3783,11 +3787,11 @@ void NukiWrapper::onAuthCommandReceived(const char *value) } Nuki::CmdResult resultAuth = _nukiLock.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH)); - delay(250); bool foundExisting = false; if(resultAuth == Nuki::CmdResult::Success) { + delay(5000); std::list entries; _nukiLock.getAuthorizationEntries(&entries); diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 02d44fd..7bbaa7f 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -3389,15 +3389,15 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request) PsychicStreamResponse response(request, "text/plain"); response.beginSend(); buildHtmlHeader(&response); - response.print(""); + response.print(""); response.print("

Credentials

"); response.print(""); - printInputField(&response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "", false, true); - printInputField(&response, "CREDPASS", "Password", "*", 30, "", true, true); - printInputField(&response, "CREDPASSRE", "Retype password", "*", 30, "", true); + printInputField(&response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "id=\"inputuser\"", false, true); + printInputField(&response, "CREDPASS", "Password", "*", 30, "id=\"inputpass\"", true, true); + printInputField(&response, "CREDPASSRE", "Retype password", "*", 30, "id=\"inputpass2\"", true); response.print("
"); response.print("
"); - response.print(""); + response.print(""); if(_nuki != nullptr) { response.print("

"); From 751186a7d0868ab1f1f202499e2e5f8618f38d73 Mon Sep 17 00:00:00 2001 From: iranl Date: Sat, 2 Nov 2024 21:22:05 +0100 Subject: [PATCH 5/5] Update nuki_ble --- lib/nuki_ble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nuki_ble b/lib/nuki_ble index fcca247..0b4e590 160000 --- a/lib/nuki_ble +++ b/lib/nuki_ble @@ -1 +1 @@ -Subproject commit fcca24773874d43385fee33f0ea9510bd4c63c3e +Subproject commit 0b4e5901c712a77417fda9a8a6ba54436e343e4a