Merge pull request #488 from iranl/check-keypad-codes

Check keypad codes + unicode check for user/password
This commit is contained in:
iranl
2024-11-02 21:30:27 +01:00
committed by GitHub
10 changed files with 769 additions and 714 deletions

View File

@@ -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 - 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 - 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 - 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 - 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 - 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 - 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. 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 | | Node | Delete | Add | Update | Check | Usage | Possible values |
|------------------|----------|----------|----------|------------------------------------------------------------------------------------------------------------------|----------------------------------------| |------------------|----------|----------|----------|----------|------------------------------------------------------------------------------------------------------------------|----------------------------------------|
| action | Required | Required | Required | The action to execute | "delete", "add", "update" | | action | Required | Required | Required | Required | The action to execute | "delete", "add", "update", "check" |
| codeId | Required | Not used | Required | The code ID of the existing code to delete or update | Integer | | codeId | Required | Not used | Required | 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"| | 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 | Enable or disable the code, always enabled on add | 1 = enabled, 0 = disabled | | 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 | The name of the code to create or update | String, max 20 chars | | 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 | If this authorization is restricted to access only at certain times, requires enabled = 1 | 1 = enabled, 0 = disabled | | 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 | The start timestamp from which access should be allowed (requires enabled = 1 and timeLimited = 1) | "YYYY-MM-DD HH:MM:SS" | | 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 | The end timestamp until 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 | Weekdays on which access should be allowed (requires enabled = 1 and timeLimited = 1) | Array of days: "mon", "tue", "wed", "thu" , "fri" "sat", "sun"| | 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 | The start time per day from which access should be allowed (requires enabled = 1 and timeLimited = 1) | "HH:MM" | | 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 | The end time per day until 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: Examples:
- Delete: `{ "action": "delete", "codeId": "1234" }` - Delete: `{ "action": "delete", "codeId": "1234" }`
@@ -609,7 +610,7 @@ Examples:
### Result of attempted keypad code changes ### Result of attempted keypad code changes
The result of the last configuration change action will be published to the `[lock/opener]/configuration/commandResultJson` MQTT topic.<br> The result of the last keypad change action will be published to the `[lock/opener]/configuration/commandResultJson` MQTT topic.<br>
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".<br> 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".<br>
## Keypad control (alternative, optional) ## Keypad control (alternative, optional)

View File

@@ -4,7 +4,7 @@
#define NUKI_HUB_VERSION "9.02" #define NUKI_HUB_VERSION "9.02"
#define NUKI_HUB_BUILD "unknownbuildnr" #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_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" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"

View File

@@ -607,12 +607,12 @@ bool NukiNetwork::reconnect()
{ {
_mqttConnectionState = 0; _mqttConnectionState = 0;
while (!_device->mqttConnected() && (esp_timer_get_time() / 1000) > _nextReconnect) while (!_device->mqttConnected() && espMillis() > _nextReconnect)
{ {
if(strcmp(_mqttBrokerAddr, "") == 0) if(strcmp(_mqttBrokerAddr, "") == 0)
{ {
Log->println(F("MQTT Broker not configured, aborting connection attempt.")); Log->println(F("MQTT Broker not configured, aborting connection attempt."));
_nextReconnect = (esp_timer_get_time() / 1000) + 5000; _nextReconnect = espMillis() + 5000;
return false; return false;
} }
@@ -634,9 +634,9 @@ bool NukiNetwork::reconnect()
_device->mqttSetServer(_mqttBrokerAddr, _mqttPort); _device->mqttSetServer(_mqttBrokerAddr, _mqttPort);
_device->mqttConnect(); _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); delay(50);
_device->update(); _device->update();
@@ -696,7 +696,7 @@ bool NukiNetwork::reconnect()
Log->print(F("MQTT connect failed, rc=")); Log->print(F("MQTT connect failed, rc="));
_device->printError(); _device->printError();
_mqttConnectionState = 0; _mqttConnectionState = 0;
_nextReconnect = (esp_timer_get_time() / 1000) + 5000; _nextReconnect = espMillis() + 5000;
//_device->mqttDisconnect(true); //_device->mqttDisconnect(true);
} }
} }

View File

@@ -113,6 +113,7 @@ void NukiOpenerWrapper::readSettings()
_retryDelay = _preferences->getInt(preference_command_retry_delay); _retryDelay = _preferences->getInt(preference_command_retry_delay);
_rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000;
_disableNonJSON = _preferences->getBool(preference_disable_non_json, false); _disableNonJSON = _preferences->getBool(preference_disable_non_json, false);
_checkKeypadCodes = _preferences->getBool(preference_keypad_check_code_enabled, false);
_pairedAsApp = _preferences->getBool(preference_register_opener_as_app, false); _pairedAsApp = _preferences->getBool(preference_register_opener_as_app, false);
_preferences->getBytes(preference_conf_opener_basic_acl, &_basicOpenerConfigAclPrefs, sizeof(_basicOpenerConfigAclPrefs)); _preferences->getBytes(preference_conf_opener_basic_acl, &_basicOpenerConfigAclPrefs, sizeof(_basicOpenerConfigAclPrefs));
@@ -278,6 +279,11 @@ void NukiOpenerWrapper::update()
} }
} }
if(_checkKeypadCodes && _invalidCount > 0 && (ts - (120000 * _invalidCount)) > _lastCodeCheck)
{
_invalidCount--;
}
if(_nextLockAction != (NukiOpener::LockAction)0xff) if(_nextLockAction != (NukiOpener::LockAction)0xff)
{ {
int retryCount = 0; int retryCount = 0;
@@ -791,10 +797,13 @@ void NukiOpenerWrapper::updateKeypad(bool retrieved)
_network->publishKeypad(entries, _maxKeypadCodeCount); _network->publishKeypad(entries, _maxKeypadCodeCount);
_keypadCodeIds.clear(); _keypadCodeIds.clear();
_keypadCodes.clear();
_keypadCodeIds.reserve(entries.size()); _keypadCodeIds.reserve(entries.size());
_keypadCodes.reserve(entries.size());
for(const auto& entry : entries) for(const auto& entry : entries)
{ {
_keypadCodeIds.push_back(entry.codeId); _keypadCodeIds.push_back(entry.codeId);
_keypadCodes.push_back(entry.code);
} }
} }
@@ -2597,13 +2606,61 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId) != _keypadCodeIds.end(); 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;
}
if((pow(_invalidCount, 5) + _lastCodeCheck) > espMillis())
{
_network->publishKeypadJsonCommandResult("checkingCodesBlockedTooManyInvalid");
_lastCodeCheck = espMillis();
return;
}
_lastCodeCheck = espMillis();
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])
{
_invalidCount = 0;
_network->publishKeypadJsonCommandResult("codeValid");
Log->println("Valid");
return;
}
else
{
_invalidCount++;
_network->publishKeypadJsonCommandResult("codeInvalid");
Log->print("Invalid\nInvalid count: ");
Log->println(_invalidCount);
return;
}
}
else
{
_invalidCount++;
_network->publishKeypadJsonCommandResult("noExistingCodeIdSet");
Log->print("Invalid count: ");
Log->println(_invalidCount);
return;
}
}
else
{
Nuki::CmdResult result = (Nuki::CmdResult)-1; Nuki::CmdResult result = (Nuki::CmdResult)-1;
int retryCount = 0; int retryCount = 0;
while(retryCount < _nrOfRetries + 1) while(retryCount < _nrOfRetries + 1)
{ {
if(strcmp(action, "delete") == 0) if(strcmp(action, "delete") == 0) {
{
if(idExists) if(idExists)
{ {
result = _nukiOpener.deleteKeypadEntry(codeId); result = _nukiOpener.deleteKeypadEntry(codeId);
@@ -2740,34 +2797,13 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
} }
} }
if(allowedWeekdays.indexOf("mon") >= 0) if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64;
{ if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32;
allowedWeekdaysInt += 64; if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16;
} if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8;
if(allowedWeekdays.indexOf("tue") >= 0) if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4;
{ if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2;
allowedWeekdaysInt += 32; if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1;
}
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) if(strcmp(action, "add") == 0)
@@ -2836,38 +2872,23 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
if(resultKp == Nuki::CmdResult::Success) if(resultKp == Nuki::CmdResult::Success)
{ {
delay(250); delay(5000);
std::list<NukiOpener::KeypadEntry> entries; std::list<NukiOpener::KeypadEntry> entries;
_nukiOpener.getKeypadEntries(&entries); _nukiOpener.getKeypadEntries(&entries);
for(const auto& entry : entries) for(const auto& entry : entries)
{ {
if (codeId != entry.codeId) if (codeId != entry.codeId) continue;
{ else foundExisting = true;
continue;
}
else
{
foundExisting = true;
}
if(name.length() < 1) if(name.length() < 1)
{ {
memset(oldName, 0, sizeof(oldName)); memset(oldName, 0, sizeof(oldName));
memcpy(oldName, entry.name, sizeof(entry.name)); memcpy(oldName, entry.name, sizeof(entry.name));
} }
if(code == 12) if(code == 12) code = entry.code;
{ if(enabled == 2) enabled = entry.enabled;
code = entry.code; if(timeLimited == 2) timeLimited = entry.timeLimited;
}
if(enabled == 2)
{
enabled = entry.enabled;
}
if(timeLimited == 2)
{
timeLimited = entry.timeLimited;
}
if(allowedFrom.length() < 1) if(allowedFrom.length() < 1)
{ {
allowedFrom = "old"; allowedFrom = "old";
@@ -2888,10 +2909,7 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
allowedUntilAr[4] = entry.allowedUntilMin; allowedUntilAr[4] = entry.allowedUntilMin;
allowedUntilAr[5] = entry.allowedUntilSec; allowedUntilAr[5] = entry.allowedUntilSec;
} }
if(allowedWeekdays.length() < 1) if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays;
{
allowedWeekdaysInt = entry.allowedWeekdays;
}
if(allowedFromTime.length() < 1) if(allowedFromTime.length() < 1)
{ {
allowedFromTime = "old"; allowedFromTime = "old";
@@ -2905,6 +2923,7 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; allowedUntilTimeAr[0] = entry.allowedUntilTimeHour;
allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; allowedUntilTimeAr[1] = entry.allowedUntilTimeMin;
} }
} }
if(!foundExisting) if(!foundExisting)
@@ -2989,14 +3008,10 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
return; return;
} }
if(result != Nuki::CmdResult::Success) if(result != Nuki::CmdResult::Success) {
{
++retryCount; ++retryCount;
} }
else else break;
{
break;
}
} }
updateKeypad(false); updateKeypad(false);
@@ -3009,6 +3024,7 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
_network->publishKeypadJsonCommandResult(resultStr); _network->publishKeypadJsonCommandResult(resultStr);
} }
} }
}
else else
{ {
_network->publishKeypadJsonCommandResult("noActionSet"); _network->publishKeypadJsonCommandResult("noActionSet");
@@ -3200,7 +3216,7 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
if(resultTc == Nuki::CmdResult::Success) if(resultTc == Nuki::CmdResult::Success)
{ {
delay(250); delay(5000);
std::list<NukiOpener::TimeControlEntry> timeControlEntries; std::list<NukiOpener::TimeControlEntry> timeControlEntries;
_nukiOpener.getTimeControlEntries(&timeControlEntries); _nukiOpener.getTimeControlEntries(&timeControlEntries);
@@ -3329,7 +3345,7 @@ void NukiOpenerWrapper::onAuthCommandReceived(const char *value)
char oldName[33]; char oldName[33];
const char *action = json["action"].as<const char*>(); const char *action = json["action"].as<const char*>();
uint16_t authId = json["authId"].as<unsigned int>(); uint32_t authId = json["authId"].as<unsigned int>();
//uint8_t idType = json["idType"].as<unsigned int>(); //uint8_t idType = json["idType"].as<unsigned int>();
//unsigned char secretKeyK[32] = {0x00}; //unsigned char secretKeyK[32] = {0x00};
uint8_t remoteAllowed; uint8_t remoteAllowed;
@@ -3656,7 +3672,7 @@ void NukiOpenerWrapper::onAuthCommandReceived(const char *value)
if(resultAuth == Nuki::CmdResult::Success) if(resultAuth == Nuki::CmdResult::Success)
{ {
delay(250); delay(5000);
std::list<NukiOpener::AuthorizationEntry> entries; std::list<NukiOpener::AuthorizationEntry> entries;
_nukiOpener.getAuthorizationEntries(&entries); _nukiOpener.getAuthorizationEntries(&entries);

View File

@@ -106,13 +106,17 @@ private:
bool _publishAuthData = false; bool _publishAuthData = false;
bool _clearAuthData = false; bool _clearAuthData = false;
bool _disableNonJSON = false; bool _disableNonJSON = false;
bool _checkKeypadCodes = false;
bool _pairedAsApp = false; bool _pairedAsApp = false;
int _nrOfRetries = 0; int _nrOfRetries = 0;
int _retryDelay = 0; int _retryDelay = 0;
int _retryConfigCount = 0; int _retryConfigCount = 0;
int _retryLockstateCount = 0; int _retryLockstateCount = 0;
int64_t _nextRetryTs = 0; int64_t _nextRetryTs = 0;
int64_t _invalidCount = 0;
int64_t _lastCodeCheck = 0;
std::vector<uint16_t> _keypadCodeIds; std::vector<uint16_t> _keypadCodeIds;
std::vector<uint32_t> _keypadCodes;
std::vector<uint8_t> _timeControlIds; std::vector<uint8_t> _timeControlIds;
std::vector<uint32_t> _authIds; std::vector<uint32_t> _authIds;

View File

@@ -185,6 +185,7 @@ void NukiWrapper::readSettings()
_retryDelay = _preferences->getInt(preference_command_retry_delay); _retryDelay = _preferences->getInt(preference_command_retry_delay);
_rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000;
_disableNonJSON = _preferences->getBool(preference_disable_non_json, false); _disableNonJSON = _preferences->getBool(preference_disable_non_json, false);
_checkKeypadCodes = _preferences->getBool(preference_keypad_check_code_enabled, false);
_pairedAsApp = _preferences->getBool(preference_register_as_app, false); _pairedAsApp = _preferences->getBool(preference_register_as_app, false);
_preferences->getBytes(preference_conf_lock_basic_acl, &_basicLockConfigaclPrefs, sizeof(_basicLockConfigaclPrefs)); _preferences->getBytes(preference_conf_lock_basic_acl, &_basicLockConfigaclPrefs, sizeof(_basicLockConfigaclPrefs));
@@ -430,6 +431,10 @@ void NukiWrapper::update()
_network->clearAuthorizationInfo(); _network->clearAuthorizationInfo();
_clearAuthData = false; _clearAuthData = false;
} }
if(_checkKeypadCodes && _invalidCount > 0 && (ts - (120000 * _invalidCount)) > _lastCodeCheck)
{
_invalidCount--;
}
} }
memcpy(&_lastKeyTurnerState, &_keyTurnerState, sizeof(NukiLock::KeyTurnerState)); memcpy(&_lastKeyTurnerState, &_keyTurnerState, sizeof(NukiLock::KeyTurnerState));
@@ -875,10 +880,13 @@ void NukiWrapper::updateKeypad(bool retrieved)
_network->publishKeypad(entries, _maxKeypadCodeCount); _network->publishKeypad(entries, _maxKeypadCodeCount);
_keypadCodeIds.clear(); _keypadCodeIds.clear();
_keypadCodes.clear();
_keypadCodeIds.reserve(entries.size()); _keypadCodeIds.reserve(entries.size());
_keypadCodes.reserve(entries.size());
for(const auto& entry : entries) for(const auto& entry : entries)
{ {
_keypadCodeIds.push_back(entry.codeId); _keypadCodeIds.push_back(entry.codeId);
_keypadCodes.push_back(entry.code);
} }
} }
@@ -2712,13 +2720,62 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId) != _keypadCodeIds.end(); 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;
}
if((pow(_invalidCount, 5) + _lastCodeCheck) > espMillis())
{
_network->publishKeypadJsonCommandResult("checkingCodesBlockedTooManyInvalid");
_lastCodeCheck = espMillis();
return;
}
_lastCodeCheck = espMillis();
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])
{
_invalidCount = 0;
_network->publishKeypadJsonCommandResult("codeValid");
Log->println("Valid");
return;
}
else
{
_invalidCount++;
_network->publishKeypadJsonCommandResult("codeInvalid");
Log->print("Invalid\nInvalid count: ");
Log->println(_invalidCount);
return;
}
}
else
{
_invalidCount++;
_network->publishKeypadJsonCommandResult("noExistingCodeIdSet");
Log->print("Invalid count: ");
Log->println(_invalidCount);
return;
}
}
else
{
Nuki::CmdResult result = (Nuki::CmdResult)-1; Nuki::CmdResult result = (Nuki::CmdResult)-1;
int retryCount = 0; int retryCount = 0;
while(retryCount < _nrOfRetries + 1) while(retryCount < _nrOfRetries + 1)
{ {
if(strcmp(action, "delete") == 0) if(strcmp(action, "delete") == 0) {
{
if(idExists) if(idExists)
{ {
result = _nukiLock.deleteKeypadEntry(codeId); result = _nukiLock.deleteKeypadEntry(codeId);
@@ -2855,34 +2912,13 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
} }
} }
if(allowedWeekdays.indexOf("mon") >= 0) if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64;
{ if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32;
allowedWeekdaysInt += 64; if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16;
} if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8;
if(allowedWeekdays.indexOf("tue") >= 0) if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4;
{ if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2;
allowedWeekdaysInt += 32; if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1;
}
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) if(strcmp(action, "add") == 0)
@@ -2951,38 +2987,23 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
if(resultKp == Nuki::CmdResult::Success) if(resultKp == Nuki::CmdResult::Success)
{ {
delay(250); delay(5000);
std::list<NukiLock::KeypadEntry> entries; std::list<NukiLock::KeypadEntry> entries;
_nukiLock.getKeypadEntries(&entries); _nukiLock.getKeypadEntries(&entries);
for(const auto& entry : entries) for(const auto& entry : entries)
{ {
if (codeId != entry.codeId) if (codeId != entry.codeId) continue;
{ else foundExisting = true;
continue;
}
else
{
foundExisting = true;
}
if(name.length() < 1) if(name.length() < 1)
{ {
memset(oldName, 0, sizeof(oldName)); memset(oldName, 0, sizeof(oldName));
memcpy(oldName, entry.name, sizeof(entry.name)); memcpy(oldName, entry.name, sizeof(entry.name));
} }
if(code == 12) if(code == 12) code = entry.code;
{ if(enabled == 2) enabled = entry.enabled;
code = entry.code; if(timeLimited == 2) timeLimited = entry.timeLimited;
}
if(enabled == 2)
{
enabled = entry.enabled;
}
if(timeLimited == 2)
{
timeLimited = entry.timeLimited;
}
if(allowedFrom.length() < 1) if(allowedFrom.length() < 1)
{ {
allowedFrom = "old"; allowedFrom = "old";
@@ -3003,10 +3024,7 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
allowedUntilAr[4] = entry.allowedUntilMin; allowedUntilAr[4] = entry.allowedUntilMin;
allowedUntilAr[5] = entry.allowedUntilSec; allowedUntilAr[5] = entry.allowedUntilSec;
} }
if(allowedWeekdays.length() < 1) if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays;
{
allowedWeekdaysInt = entry.allowedWeekdays;
}
if(allowedFromTime.length() < 1) if(allowedFromTime.length() < 1)
{ {
allowedFromTime = "old"; allowedFromTime = "old";
@@ -3020,6 +3038,7 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
allowedUntilTimeAr[0] = entry.allowedUntilTimeHour; allowedUntilTimeAr[0] = entry.allowedUntilTimeHour;
allowedUntilTimeAr[1] = entry.allowedUntilTimeMin; allowedUntilTimeAr[1] = entry.allowedUntilTimeMin;
} }
} }
if(!foundExisting) if(!foundExisting)
@@ -3104,14 +3123,10 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
return; return;
} }
if(result != Nuki::CmdResult::Success) if(result != Nuki::CmdResult::Success) {
{
++retryCount; ++retryCount;
} }
else else break;
{
break;
}
} }
updateKeypad(false); updateKeypad(false);
@@ -3124,6 +3139,7 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
_network->publishKeypadJsonCommandResult(resultStr); _network->publishKeypadJsonCommandResult(resultStr);
} }
} }
}
else else
{ {
_network->publishKeypadJsonCommandResult("noActionSet"); _network->publishKeypadJsonCommandResult("noActionSet");
@@ -3316,7 +3332,7 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
if(resultTc == Nuki::CmdResult::Success) if(resultTc == Nuki::CmdResult::Success)
{ {
delay(250); delay(5000);
std::list<NukiLock::TimeControlEntry> timeControlEntries; std::list<NukiLock::TimeControlEntry> timeControlEntries;
_nukiLock.getTimeControlEntries(&timeControlEntries); _nukiLock.getTimeControlEntries(&timeControlEntries);
@@ -3446,7 +3462,7 @@ void NukiWrapper::onAuthCommandReceived(const char *value)
char oldName[33]; char oldName[33];
const char *action = json["action"].as<const char*>(); const char *action = json["action"].as<const char*>();
uint16_t authId = json["authId"].as<unsigned int>(); uint32_t authId = json["authId"].as<unsigned int>();
//uint8_t idType = json["idType"].as<unsigned int>(); //uint8_t idType = json["idType"].as<unsigned int>();
//unsigned char secretKeyK[32] = {0x00}; //unsigned char secretKeyK[32] = {0x00};
uint8_t remoteAllowed; uint8_t remoteAllowed;
@@ -3771,11 +3787,11 @@ void NukiWrapper::onAuthCommandReceived(const char *value)
} }
Nuki::CmdResult resultAuth = _nukiLock.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH)); Nuki::CmdResult resultAuth = _nukiLock.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH));
delay(250);
bool foundExisting = false; bool foundExisting = false;
if(resultAuth == Nuki::CmdResult::Success) if(resultAuth == Nuki::CmdResult::Success)
{ {
delay(5000);
std::list<NukiLock::AuthorizationEntry> entries; std::list<NukiLock::AuthorizationEntry> entries;
_nukiLock.getAuthorizationEntries(&entries); _nukiLock.getAuthorizationEntries(&entries);

View File

@@ -108,7 +108,11 @@ private:
int _restartBeaconTimeout = 0; // seconds int _restartBeaconTimeout = 0; // seconds
bool _publishAuthData = false; bool _publishAuthData = false;
bool _clearAuthData = false; bool _clearAuthData = false;
bool _checkKeypadCodes = false;
int64_t _invalidCount = 0;
int64_t _lastCodeCheck = 0;
std::vector<uint16_t> _keypadCodeIds; std::vector<uint16_t> _keypadCodeIds;
std::vector<uint32_t> _keypadCodes;
std::vector<uint8_t> _timeControlIds; std::vector<uint8_t> _timeControlIds;
std::vector<uint32_t> _authIds; std::vector<uint32_t> _authIds;

View File

@@ -97,6 +97,7 @@
#define preference_publish_debug_info (char*)"pubdbg" #define preference_publish_debug_info (char*)"pubdbg"
#define preference_official_hybrid_actions (char*)"hybridAct" #define preference_official_hybrid_actions (char*)"hybridAct"
#define preference_official_hybrid_retry (char*)"hybridRtry" #define preference_official_hybrid_retry (char*)"hybridRtry"
#define preference_keypad_check_code_enabled (char*)"kpChkEna"
//NOT USER CHANGABLE //NOT USER CHANGABLE
#define preference_updater_version (char*)"updVer" #define preference_updater_version (char*)"updVer"
@@ -292,7 +293,7 @@ private:
preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi, 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_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_wifi_ssid, preference_wifi_pass, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_wifi_ssid, preference_wifi_pass,
preference_disable_network_not_connected preference_keypad_check_code_enabled, preference_disable_network_not_connected
}; };
std::vector<char*> _redact = std::vector<char*> _redact =
{ {
@@ -308,7 +309,7 @@ private:
preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_enabled, preference_publish_authdata, preference_publish_debug_info, 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_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_webserial_enabled, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled,
preference_ntw_reconfigure, preference_disable_network_not_connected preference_ntw_reconfigure, preference_keypad_check_code_enabled, preference_disable_network_not_connected
}; };
std::vector<char*> _bytePrefs = std::vector<char*> _bytePrefs =
{ {

View File

@@ -2200,6 +2200,16 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message)
//configChanged = true; //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") else if(key == "KPENA")
{ {
if(_preferences->getBool(preference_keypad_control_enabled, false) != (value == "1")) if(_preferences->getBool(preference_keypad_control_enabled, false) != (value == "1"))
@@ -3379,15 +3389,15 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request)
PsychicStreamResponse response(request, "text/plain"); PsychicStreamResponse response(request, "text/plain");
response.beginSend(); response.beginSend();
buildHtmlHeader(&response); buildHtmlHeader(&response);
response.print("<form class=\"adapt\" method=\"post\" action=\"savecfg\">"); response.print("<form id=\"credfrm\" class=\"adapt\" onsubmit=\"return testcreds();\" method=\"post\" action=\"savecfg\">");
response.print("<h3>Credentials</h3>"); response.print("<h3>Credentials</h3>");
response.print("<table>"); response.print("<table>");
printInputField(&response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "", false, true); printInputField(&response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "id=\"inputuser\"", false, true);
printInputField(&response, "CREDPASS", "Password", "*", 30, "", true, true); printInputField(&response, "CREDPASS", "Password", "*", 30, "id=\"inputpass\"", true, true);
printInputField(&response, "CREDPASSRE", "Retype password", "*", 30, "", true); printInputField(&response, "CREDPASSRE", "Retype password", "*", 30, "id=\"inputpass2\"", true);
response.print("</table>"); response.print("</table>");
response.print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">"); response.print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.print("</form>"); response.print("</form><script>function testcreds() { var input_user = document.getElementById(\"inputuser\").value; var input_pass = document.getElementById(\"inputpass\").value; var input_pass2 = document.getElementById(\"inputpass2\").value; var pattern = /^[ -~]*$/; if(input_user == '#' || input_user == '') { return true; } if (input_pass != input_pass2) { alert('Passwords do not match'); return false;} if(!pattern.test(input_user) || !pattern.test(input_pass)) { alert('Only non unicode characters are allowed in username and password'); return false;} else { return true; } }</script>");
if(_nuki != nullptr) if(_nuki != nullptr)
{ {
response.print("<br><br><form class=\"adapt\" method=\"post\" action=\"savecfg\">"); response.print("<br><br><form class=\"adapt\" method=\"post\" action=\"savecfg\">");
@@ -3711,6 +3721,7 @@ esp_err_t WebCfgServer::buildAccLvlHtml(PsychicRequest *request)
printCheckBox(&response, "KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), ""); printCheckBox(&response, "KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), "");
printCheckBox(&response, "KPCODE", "Also publish keypad codes (<span class=\"warning\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_publish_code, false), ""); printCheckBox(&response, "KPCODE", "Also publish keypad codes (<span class=\"warning\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_publish_code, false), "");
printCheckBox(&response, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), ""); printCheckBox(&response, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), "");
printCheckBox(&response, "KPCHECK", "Allow checking if keypad codes are valid (<span class=\"warning\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_check_code_enabled, false), "");
} }
printCheckBox(&response, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), ""); printCheckBox(&response, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), "");
printCheckBox(&response, "TCPER", "Publish a topic per time control entry and create HA sensor", _preferences->getBool(preference_timecontrol_topic_per_entry), ""); printCheckBox(&response, "TCPER", "Publish a topic per time control entry and create HA sensor", _preferences->getBool(preference_timecontrol_topic_per_entry), "");
@@ -4186,6 +4197,8 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request)
response.print(_preferences->getBool(preference_keypad_topic_per_entry, false) ? "Yes" : "No"); response.print(_preferences->getBool(preference_keypad_topic_per_entry, false) ? "Yes" : "No");
response.print("\nPublish Keypad codes: "); response.print("\nPublish Keypad codes: ");
response.print(_preferences->getBool(preference_keypad_publish_code, false) ? "Yes" : "No"); response.print(_preferences->getBool(preference_keypad_publish_code, false) ? "Yes" : "No");
response.print("\nAllow checking Keypad codes: ");
response.print(_preferences->getBool(preference_keypad_check_code_enabled, false) ? "Yes" : "No");
response.print("\nMax keypad entries to retrieve: "); response.print("\nMax keypad entries to retrieve: ");
response.print(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); response.print(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
response.print("\nPublish timecontrol info: "); response.print("\nPublish timecontrol info: ");