Merge pull request #371 from iranl/update-tasks

Update tasks
This commit is contained in:
Jan-Ole Schümann
2024-05-27 21:10:46 +07:00
committed by GitHub
18 changed files with 422 additions and 165 deletions

View File

@@ -200,6 +200,7 @@ In a browser navigate to the IP address assigned to the ESP32.
- lock/trigger: The trigger of the last action: autoLock, automatic, button, manual, system.
- lock/lastLockAction: Reports the last lock action as a string. Possible values are: Unlock, Lock, Unlatch, LockNgo, LockNgoUnlatch, FullLock, FobAction1, FobAction2, FobAction3, Unknown.
- lock/log: If "Publish auth data" is enabled in the web interface, this topic will be filled with the log of authorization data.
- lock/shortLog: If "Publish auth data" is enabled in the web interface, this topic will be filled with the 3 most recent entries in the log of authorization data, updates faster than lock/log.
- lock/completionStatus: Status of the last action as reported by Nuki Lock: success, motorBlocked, canceled, tooRecent, busy, lowMotorVoltage, clutchFailure, motorPowerFailure, incompleteFailure, invalidCode, otherError, unknown.
- lock/authorizationId: If enabled in the web interface, this node returns the authorization id of the last lock action.
- lock/authorizationName: If enabled in the web interface, this node returns the authorization name of the last lock action.

View File

@@ -1,8 +1,8 @@
#include "CharBuffer.h"
void CharBuffer::initialize()
void CharBuffer::initialize(char16_t buffer_size)
{
_buffer = new char[CHAR_BUFFER_SIZE];
_buffer = new char[buffer_size];
}
char *CharBuffer::get()

View File

@@ -1,11 +1,9 @@
#pragma once
#define CHAR_BUFFER_SIZE 4096
class CharBuffer
{
public:
static void initialize();
static void initialize(char16_t buffer_size);
static char* get();
private:

View File

@@ -3,6 +3,7 @@
#include "sdkconfig.h"
#define NUKI_HUB_VERSION "8.34"
#define NUKI_HUB_BUILD "unknownbuildnr"
#define GITHUB_LATEST_RELEASE_URL "https://github.com/technyon/nuki_hub/releases/latest"
#define GITHUB_LATEST_RELEASE_API_URL "https://api.github.com/repos/technyon/nuki_hub/releases/latest"
@@ -24,4 +25,10 @@
#define GPIO_DEBOUNCE_TIME 200
#define NUKI_HUB_BUILD "unknownbuildnr"
#define CHAR_BUFFER_SIZE 4096
#define NETWORK_TASK_SIZE 12288
#define NUKI_TASK_SIZE 8192
#define PD_TASK_SIZE 1024
#define MAX_AUTHLOG 5
#define MAX_KEYPAD 10
#define MAX_TIMECONTROL 10

View File

@@ -12,6 +12,7 @@
#define mqtt_topic_lock_trigger "/lock/trigger"
#define mqtt_topic_lock_last_lock_action "/lock/lastLockAction"
#define mqtt_topic_lock_log "/lock/log"
#define mqtt_topic_lock_log_latest "/lock/shortLog"
#define mqtt_topic_lock_auth_id "/lock/authorizationId"
#define mqtt_topic_lock_auth_name "/lock/authorizationName"
#define mqtt_topic_lock_completionStatus "/lock/completionStatus"
@@ -71,6 +72,8 @@
#define mqtt_topic_info_nuki_hub_ip "/info/nukiHubIp"
#define mqtt_topic_reset "/maintenance/reset"
#define mqtt_topic_webserver_state "/maintenance/webserver/state"
#define mqtt_topic_webserver_action "/maintenance/webserver/enable"
#define mqtt_topic_uptime "/maintenance/uptime"
#define mqtt_topic_wifi_rssi "/maintenance/wifiRssi"
#define mqtt_topic_log "/maintenance/log"

View File

@@ -264,7 +264,13 @@ void Network::initialize()
bool Network::update()
{
unsigned long ts = millis();
if(ts > 120000 && ts < 125000 && _preferences->getInt(preference_bootloop_counter, 0) > 0)
{
_preferences->putInt(preference_bootloop_counter, 0);
Log->println(F("Bootloop counter reset"));
}
_device->update();
if(!_mqttEnabled)

View File

@@ -52,6 +52,10 @@ void NetworkLock::initialize()
_network->subscribe(_mqttPath, mqtt_topic_reset);
_network->initTopic(_mqttPath, mqtt_topic_reset, "0");
_network->subscribe(_mqttPath, mqtt_topic_webserver_action);
_network->initTopic(_mqttPath, mqtt_topic_webserver_action, "--");
_network->initTopic(_mqttPath, mqtt_topic_webserver_state, (_preferences->getBool(preference_webserver_enabled, true) ? "1" : "0"));
_network->initTopic(_mqttPath, mqtt_topic_query_config, "0");
_network->initTopic(_mqttPath, mqtt_topic_query_lockstate, "0");
_network->initTopic(_mqttPath, mqtt_topic_query_battery, "0");
@@ -101,6 +105,31 @@ void NetworkLock::onMqttDataReceived(const char* topic, byte* payload, const uns
restartEsp(RestartReason::RequestedViaMqtt);
}
if(comparePrefixedPath(topic, mqtt_topic_webserver_action))
{
if(strcmp(value, "") == 0 ||
strcmp(value, "--") == 0) return;
if(strcmp(value, "1") == 0)
{
if(_preferences->getBool(preference_webserver_enabled, true)) return;
Log->println(F("Webserver enabled, restarting."));
_preferences->putBool(preference_webserver_enabled, true);
}
else if (strcmp(value, "0") == 0)
{
if(!_preferences->getBool(preference_webserver_enabled, true)) return;
Log->println(F("Webserver disabled, restarting."));
_preferences->putBool(preference_webserver_enabled, false);
}
publishString(mqtt_topic_webserver_action, "--");
_network->clearWifiFallback();
delay(200);
restartEsp(RestartReason::RequestedViaMqtt);
}
if(comparePrefixedPath(topic, mqtt_topic_lock_action))
{
if(strcmp(value, "") == 0 ||
@@ -371,7 +400,7 @@ void NetworkLock::publishState(NukiLock::LockState lockState)
}
}
void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>& logEntries)
void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>& logEntries, bool latest)
{
char str[50];
char authName[33];
@@ -379,15 +408,8 @@ void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>&
JsonDocument json;
int i = 5;
for(const auto& log : logEntries)
{
if(i <= 0)
{
break;
}
--i;
memset(authName, 0, sizeof(authName));
authName[0] = '\0';
@@ -437,6 +459,7 @@ void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>&
memset(str, 0, sizeof(str));
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[3], str);
entry["completionStatus"] = str;
entry["completionStatusVal"] = log.data[3];
break;
case NukiLock::LoggingType::KeypadAction:
memset(str, 0, sizeof(str));
@@ -446,6 +469,7 @@ void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>&
memset(str, 0, sizeof(str));
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
entry["completionStatus"] = str;
entry["completionStatusVal"] = log.data[2];
break;
case NukiLock::LoggingType::DoorSensor:
memset(str, 0, sizeof(str));
@@ -475,7 +499,9 @@ void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>&
}
serializeJson(json, _buffer, _bufferSize);
publishString(mqtt_topic_lock_log, _buffer);
if(latest) publishString(mqtt_topic_lock_log_latest, _buffer);
else publishString(mqtt_topic_lock_log, _buffer);
if(authFound)
{

View File

@@ -24,7 +24,7 @@ public:
void publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurnerState, const NukiLock::KeyTurnerState& lastKeyTurnerState);
void publishState(NukiLock::LockState lockState);
void publishAuthorizationInfo(const std::list<NukiLock::LogEntry>& logEntries);
void publishAuthorizationInfo(const std::list<NukiLock::LogEntry>& logEntries, bool latest);
void clearAuthorizationInfo();
void publishCommandResult(const char* resultStr);
void publishLockstateCommandResult(const char* resultStr);

View File

@@ -369,7 +369,7 @@ void NetworkOpener::publishState(NukiOpener::OpenerState lockState)
}
}
void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntry>& logEntries)
void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntry>& logEntries, bool latest)
{
char str[50];
char authName[33];
@@ -377,15 +377,8 @@ void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntr
JsonDocument json;
int i = 5;
for(const auto& log : logEntries)
{
if(i <= 0)
{
break;
}
--i;
memset(authName, 0, sizeof(authName));
authName[0] = '\0';
@@ -498,8 +491,10 @@ void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntr
}
serializeJson(json, _buffer, _bufferSize);
publishString(mqtt_topic_lock_log, _buffer);
if(latest) publishString(mqtt_topic_lock_log_latest, _buffer);
else publishString(mqtt_topic_lock_log, _buffer);
if(authFound)
{
publishUInt(mqtt_topic_lock_auth_id, _authId);
@@ -507,37 +502,6 @@ void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntr
}
}
void NetworkOpener::logactionCompletionStatusToString(uint8_t value, char* out)
{
switch (value)
{
case 0x00:
strcpy(out, "success");
break;
case 0x02:
strcpy(out, "cancelled");
break;
case 0x03:
strcpy(out, "tooRecent");
break;
case 0x04:
strcpy(out, "busy");
break;
case 0x08:
strcpy(out, "incomplete");
break;
case 0xfe:
strcpy(out, "otherError");
break;
case 0xff:
strcpy(out, "unknown");
break;
default:
strcpy(out, "undefined");
break;
}
}
void NetworkOpener::clearAuthorizationInfo()
{
publishString(mqtt_topic_lock_log, "--");

View File

@@ -21,7 +21,7 @@ public:
void publishKeyTurnerState(const NukiOpener::OpenerState& keyTurnerState, const NukiOpener::OpenerState& lastKeyTurnerState);
void publishRing(const bool locked);
void publishState(NukiOpener::OpenerState lockState);
void publishAuthorizationInfo(const std::list<NukiOpener::LogEntry>& logEntries);
void publishAuthorizationInfo(const std::list<NukiOpener::LogEntry>& logEntries, bool latest);
void clearAuthorizationInfo();
void publishCommandResult(const char* resultStr);
void publishLockstateCommandResult(const char* resultStr);
@@ -65,7 +65,6 @@ private:
void buildMqttPath(const char* path, char* outPath);
void subscribe(const char* path);
void logactionCompletionStatusToString(uint8_t value, char* out);
void buttonPressActionToString(const NukiOpener::ButtonPressAction btnPressAction, char* str);
void fobActionToString(const int fobact, char* str);
void operatingModeToString(const int opmode, char* str);

View File

@@ -5,6 +5,7 @@
#include "Logger.h"
#include "RestartReason.h"
#include <NukiOpenerUtils.h>
#include "Config.h"
NukiOpenerWrapper* nukiOpenerInst;
Preferences* nukiOpenerPreferences = nullptr;
@@ -173,9 +174,19 @@ void NukiOpenerWrapper::update()
setupHASS();
}
}
if(_nextTimeControlUpdateTs != 0 && ts > _nextTimeControlUpdateTs)
if(_waitAuthLogUpdateTs != 0 && ts > _waitAuthLogUpdateTs)
{
_nextTimeControlUpdateTs = 0;
_waitAuthLogUpdateTs = 0;
updateAuthData(true);
}
if(_waitKeypadUpdateTs != 0 && ts > _waitKeypadUpdateTs)
{
_waitKeypadUpdateTs = 0;
updateKeypad(true);
}
if(_waitTimeControlUpdateTs != 0 && ts > _waitTimeControlUpdateTs)
{
_waitTimeControlUpdateTs = 0;
updateTimeControl(true);
}
if(_hassEnabled && _configRead && _network->reconnected())
@@ -197,7 +208,7 @@ void NukiOpenerWrapper::update()
if(_hasKeypad && _keypadEnabled && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs || (queryCommands & QUERY_COMMAND_KEYPAD) > 0))
{
_nextKeypadUpdateTs = ts + _intervalKeypad * 1000;
updateKeypad();
updateKeypad(false);
}
if(_nextLockAction != (NukiOpener::LockAction)0xff && ts > _nextRetryTs)
@@ -380,7 +391,7 @@ void NukiOpenerWrapper::updateKeyTurnerState()
if(_publishAuthData)
{
Log->println(F("Publishing auth data"));
updateAuthData();
updateAuthData(false);
Log->println(F("Done publishing auth data"));
}
@@ -478,7 +489,7 @@ void NukiOpenerWrapper::updateConfig()
}
}
void NukiOpenerWrapper::updateAuthData()
void NukiOpenerWrapper::updateAuthData(bool retrieved)
{
if(!isPinValid())
{
@@ -486,35 +497,59 @@ void NukiOpenerWrapper::updateAuthData()
return;
}
Nuki::CmdResult result = _nukiOpener.retrieveLogEntries(0, 5, 1, false);
Log->print(F("Retrieve log entries: "));
Log->println(result);
if(result != Nuki::CmdResult::Success)
if(!retrieved)
{
return;
delay(250);
Nuki::CmdResult result = _nukiOpener.retrieveLogEntries(0, _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 1, false);
Log->print(F("Retrieve log entries: "));
Log->println(result);
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
{
_waitAuthLogUpdateTs = millis() + 5000;
delay(100);
std::list<NukiOpener::LogEntry> log;
_nukiOpener.getLogEntries(&log);
if(log.size() > _preferences->getInt(preference_authlog_max_entries, 3))
{
log.resize(_preferences->getInt(preference_authlog_max_entries, 3));
}
if(log.size() > 0)
{
_network->publishAuthorizationInfo(log, true);
}
}
}
delay(100);
std::list<NukiOpener::LogEntry> log;
_nukiOpener.getLogEntries(&log);
Log->print(F("Log size: "));
Log->println(log.size());
if(log.size() > 0)
else
{
_network->publishAuthorizationInfo(log);
std::list<NukiOpener::LogEntry> log;
_nukiOpener.getLogEntries(&log);
if(log.size() > _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG))
{
log.resize(_preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG));
}
Log->print(F("Log size: "));
Log->println(log.size());
if(log.size() > 0)
{
_network->publishAuthorizationInfo(log, false);
}
}
postponeBleWatchdog();
}
void NukiOpenerWrapper::updateKeypad()
void NukiOpenerWrapper::updateKeypad(bool retrieved)
{
if(!_preferences->getBool(preference_keypad_info_enabled)) return;
Log->print(F("Querying opener keypad: "));
Nuki::CmdResult result = _nukiOpener.retrieveKeypadEntries(0, 0xffff);
Nuki::CmdResult result = _nukiOpener.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
{
@@ -526,6 +561,11 @@ void NukiOpenerWrapper::updateKeypad()
entries.sort([](const NukiLock::KeypadEntry& a, const NukiLock::KeypadEntry& b) { return a.codeId < b.codeId; });
if(entries.size() > _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD))
{
entries.resize(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
}
uint keypadCount = entries.size();
if(keypadCount > _maxKeypadCodeCount)
{
@@ -560,7 +600,7 @@ void NukiOpenerWrapper::updateTimeControl(bool retrieved)
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
{
_nextTimeControlUpdateTs = millis() + 5000;
_waitTimeControlUpdateTs = millis() + 5000;
}
}
else
@@ -573,6 +613,11 @@ void NukiOpenerWrapper::updateTimeControl(bool retrieved)
timeControlEntries.sort([](const NukiOpener::TimeControlEntry& a, const NukiOpener::TimeControlEntry& b) { return a.entryId < b.entryId; });
if(timeControlEntries.size() > _preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL))
{
timeControlEntries.resize(_preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL));
}
_network->publishTimeControl(timeControlEntries);
_timeControlIds.clear();
@@ -1358,7 +1403,7 @@ void NukiOpenerWrapper::onKeypadCommandReceived(const char *command, const uint
entry.code = codeInt;
result = _nukiOpener.addKeypadEntry(entry);
Log->print("Add keypad code: "); Log->println((int)result);
updateKeypad();
updateKeypad(false);
}
else if(strcmp(command, "delete") == 0)
{
@@ -1369,7 +1414,7 @@ void NukiOpenerWrapper::onKeypadCommandReceived(const char *command, const uint
}
result = _nukiOpener.deleteKeypadEntry(id);
Log->print("Delete keypad code: "); Log->println((int)result);
updateKeypad();
updateKeypad(false);
}
else if(strcmp(command, "update") == 0)
{
@@ -1403,7 +1448,7 @@ void NukiOpenerWrapper::onKeypadCommandReceived(const char *command, const uint
entry.enabled = enabled == 0 ? 0 : 1;
result = _nukiOpener.updateKeypadEntry(entry);
Log->print("Update keypad code: "); Log->println((int)result);
updateKeypad();
updateKeypad(false);
}
else if(command == "--")
{
@@ -1695,7 +1740,7 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
_network->publishKeypadJsonCommandResult("noExistingCodeIdSet");
return;
}
NukiOpener::UpdatedKeypadEntry entry;
memset(&entry, 0, sizeof(entry));
entry.codeId = codeId;
@@ -1708,7 +1753,7 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
auto it = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), codeId);
entry.code = _keypadCodes[(it - _keypadCodeIds.begin())];
}
entry.enabled = enabled == 0 ? 0 : 1;
entry.timeLimited = timeLimited == 1 ? 1 : 0;
@@ -1757,7 +1802,7 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
return;
}
updateKeypad();
updateKeypad(false);
if((int)result != -1)
{

View File

@@ -62,8 +62,8 @@ private:
void updateKeyTurnerState();
void updateBatteryState();
void updateConfig();
void updateAuthData();
void updateKeypad();
void updateAuthData(bool retrieved);
void updateKeypad(bool retrieved);
void updateTimeControl(bool retrieved);
void postponeBleWatchdog();
@@ -133,7 +133,9 @@ private:
unsigned long _nextLockStateUpdateTs = 0;
unsigned long _nextBatteryReportTs = 0;
unsigned long _nextConfigUpdateTs = 0;
unsigned long _nextTimeControlUpdateTs = 0;
unsigned long _waitAuthLogUpdateTs = 0;
unsigned long _waitKeypadUpdateTs = 0;
unsigned long _waitTimeControlUpdateTs = 0;
unsigned long _nextKeypadUpdateTs = 0;
unsigned long _nextPairTs = 0;
long _nextRssiTs = 0;

View File

@@ -5,6 +5,7 @@
#include "Logger.h"
#include "RestartReason.h"
#include <NukiLockUtils.h>
#include "Config.h"
NukiWrapper* nukiInst;
Preferences* nukiLockPreferences = nullptr;
@@ -192,9 +193,19 @@ void NukiWrapper::update()
setupHASS();
}
}
if(_nextTimeControlUpdateTs != 0 && ts > _nextTimeControlUpdateTs)
if(_waitAuthLogUpdateTs != 0 && ts > _waitAuthLogUpdateTs)
{
_nextTimeControlUpdateTs = 0;
_waitAuthLogUpdateTs = 0;
updateAuthData(true);
}
if(_waitKeypadUpdateTs != 0 && ts > _waitKeypadUpdateTs)
{
_waitKeypadUpdateTs = 0;
updateKeypad(true);
}
if(_waitTimeControlUpdateTs != 0 && ts > _waitTimeControlUpdateTs)
{
_waitTimeControlUpdateTs = 0;
updateTimeControl(true);
}
if(_hassEnabled && _configRead && _network->reconnected())
@@ -216,7 +227,7 @@ void NukiWrapper::update()
if(_hasKeypad && _keypadEnabled && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs || (queryCommands & QUERY_COMMAND_KEYPAD) > 0))
{
_nextKeypadUpdateTs = ts + _intervalKeypad * 1000;
updateKeypad();
updateKeypad(false);
}
if(_nextLockAction != (NukiLock::LockAction)0xff && ts > _nextRetryTs)
@@ -354,7 +365,7 @@ void NukiWrapper::updateKeyTurnerState()
if(_publishAuthData)
{
Log->println(F("Publishing auth data"));
updateAuthData();
updateAuthData(false);
Log->println(F("Done publishing auth data"));
}
@@ -459,7 +470,7 @@ void NukiWrapper::updateConfig()
}
}
void NukiWrapper::updateAuthData()
void NukiWrapper::updateAuthData(bool retrieved)
{
if(!isPinValid())
{
@@ -467,37 +478,69 @@ void NukiWrapper::updateAuthData()
return;
}
Nuki::CmdResult result = _nukiLock.retrieveLogEntries(0, 5, 1, false);
Log->print(F("Retrieve log entries: "));
Log->println(result);
if(result != Nuki::CmdResult::Success)
if(!retrieved)
{
return;
delay(250);
Nuki::CmdResult result = _nukiLock.retrieveLogEntries(0, _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 1, false);
Log->print(F("Retrieve log entries: "));
Log->println(result);
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
{
_waitAuthLogUpdateTs = millis() + 5000;
delay(100);
std::list<NukiLock::LogEntry> log;
_nukiLock.getLogEntries(&log);
if(log.size() > _preferences->getInt(preference_authlog_max_entries, 3))
{
log.resize(_preferences->getInt(preference_authlog_max_entries, 3));
}
if(log.size() > 0)
{
_network->publishAuthorizationInfo(log, true);
}
}
}
delay(100);
std::list<NukiLock::LogEntry> log;
_nukiLock.getLogEntries(&log);
Log->print(F("Log size: "));
Log->println(log.size());
if(log.size() > 0)
else
{
_network->publishAuthorizationInfo(log);
std::list<NukiLock::LogEntry> log;
_nukiLock.getLogEntries(&log);
if(log.size() > _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG))
{
log.resize(_preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG));
}
Log->print(F("Log size: "));
Log->println(log.size());
if(log.size() > 0)
{
_network->publishAuthorizationInfo(log, false);
}
}
postponeBleWatchdog();
}
void NukiWrapper::updateKeypad()
void NukiWrapper::updateKeypad(bool retrieved)
{
if(!_preferences->getBool(preference_keypad_info_enabled)) return;
Log->print(F("Querying lock keypad: "));
Nuki::CmdResult result = _nukiLock.retrieveKeypadEntries(0, 0xffff);
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
if(!retrieved)
{
Log->print(F("Querying lock keypad: "));
Nuki::CmdResult result = _nukiLock.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
{
_waitKeypadUpdateTs = millis() + 5000;
}
}
else
{
std::list<NukiLock::KeypadEntry> entries;
_nukiLock.getKeypadEntries(&entries);
@@ -506,6 +549,11 @@ void NukiWrapper::updateKeypad()
Log->println(entries.size());
entries.sort([](const NukiLock::KeypadEntry& a, const NukiLock::KeypadEntry& b) { return a.codeId < b.codeId; });
if(entries.size() > _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD))
{
entries.resize(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
}
uint keypadCount = entries.size();
if(keypadCount > _maxKeypadCodeCount)
@@ -541,7 +589,7 @@ void NukiWrapper::updateTimeControl(bool retrieved)
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
{
_nextTimeControlUpdateTs = millis() + 5000;
_waitTimeControlUpdateTs = millis() + 5000;
}
}
else
@@ -553,7 +601,12 @@ void NukiWrapper::updateTimeControl(bool retrieved)
Log->println(timeControlEntries.size());
timeControlEntries.sort([](const NukiLock::TimeControlEntry& a, const NukiLock::TimeControlEntry& b) { return a.entryId < b.entryId; });
if(timeControlEntries.size() > _preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL))
{
timeControlEntries.resize(_preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL));
}
_network->publishTimeControl(timeControlEntries);
_timeControlIds.clear();
@@ -1344,7 +1397,7 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c
entry.code = codeInt;
result = _nukiLock.addKeypadEntry(entry);
Log->print("Add keypad code: "); Log->println((int)result);
updateKeypad();
updateKeypad(false);
}
else if(strcmp(command, "delete") == 0)
{
@@ -1355,7 +1408,7 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c
}
result = _nukiLock.deleteKeypadEntry(id);
Log->print("Delete keypad code: "); Log->println((int)result);
updateKeypad();
updateKeypad(false);
}
else if(strcmp(command, "update") == 0)
{
@@ -1389,7 +1442,7 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c
entry.enabled = enabled == 0 ? 0 : 1;
result = _nukiLock.updateKeypadEntry(entry);
Log->print("Update keypad code: "); Log->println((int)result);
updateKeypad();
updateKeypad(false);
}
else if(command == "--")
{
@@ -1743,7 +1796,7 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
return;
}
updateKeypad();
updateKeypad(false);
if((int)result != -1)
{

View File

@@ -59,8 +59,8 @@ private:
void updateKeyTurnerState();
void updateBatteryState();
void updateConfig();
void updateAuthData();
void updateKeypad();
void updateAuthData(bool retrieved);
void updateKeypad(bool retrieved);
void updateTimeControl(bool retrieved);
void postponeBleWatchdog();
@@ -127,7 +127,9 @@ private:
unsigned long _nextLockStateUpdateTs = 0;
unsigned long _nextBatteryReportTs = 0;
unsigned long _nextConfigUpdateTs = 0;
unsigned long _nextTimeControlUpdateTs = 0;
unsigned long _waitAuthLogUpdateTs = 0;
unsigned long _waitKeypadUpdateTs = 0;
unsigned long _waitTimeControlUpdateTs = 0;
unsigned long _nextKeypadUpdateTs = 0;
unsigned long _nextRssiTs = 0;
unsigned long _lastRssi = 0;

View File

@@ -13,6 +13,7 @@
#define preference_mqtt_user (char*)"mqttuser"
#define preference_mqtt_password (char*)"mqttpass"
#define preference_mqtt_log_enabled (char*)"mqttlog"
#define preference_webserver_enabled (char*)"websrvena"
#define preference_lock_enabled (char*)"lockena"
#define preference_lock_pin_status (char*)"lockpin"
#define preference_mqtt_lock_path (char*)"mqttpath"
@@ -72,28 +73,35 @@
#define preference_has_mac_byte_1 (char*)"macb1"
#define preference_has_mac_byte_2 (char*)"macb2"
#define preference_latest_version (char*)"latest"
#define preference_task_size_network (char*)"tsksznetw"
#define preference_task_size_nuki (char*)"tsksznuki"
#define preference_task_size_pd (char*)"tskszpd"
#define preference_authlog_max_entries (char*)"authmaxentry"
#define preference_keypad_max_entries (char*)"kpmaxentry"
#define preference_timecontrol_max_entries (char*)"tcmaxentry"
#define preference_bootloop_counter (char*)"btlpcounter"
#define preference_enable_bootloop_reset (char*)"enabtlprst"
#define preference_buffer_size (char*)"buffsize"
class DebugPreferences
{
private:
std::vector<char*> _keys =
{
preference_started_before, preference_config_version, preference_device_id_lock, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_opener, preference_mqtt_broker, preference_mqtt_broker_port, preference_mqtt_user, preference_mqtt_password, preference_mqtt_log_enabled, preference_check_updates,
preference_started_before, preference_config_version, preference_device_id_lock, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_opener, preference_mqtt_broker, preference_mqtt_broker_port, preference_mqtt_user, preference_mqtt_password, preference_mqtt_log_enabled, preference_check_updates, preference_webserver_enabled,
preference_lock_enabled, preference_lock_pin_status, preference_mqtt_lock_path, preference_opener_enabled, preference_opener_pin_status,
preference_opener_continuous_mode, preference_mqtt_opener_path, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count,
preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key, preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url,
preference_ip_dhcp_enabled, preference_ip_address, preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server,
preference_network_hardware, preference_network_wifi_fallback_disabled, preference_rssi_publish_interval,
preference_find_best_rssi, preference_hostname, preference_network_timeout, preference_restart_on_disconnect,
preference_restart_ble_beacon_lost, preference_query_interval_lockstate,
preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad,
preference_keypad_control_enabled, preference_keypad_info_enabled, preference_acl,
preference_timecontrol_control_enabled, preference_timecontrol_info_enabled,
preference_enable_bootloop_reset, preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key, preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url,
preference_buffer_size, preference_ip_dhcp_enabled, preference_ip_address, preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server,
preference_network_hardware, preference_network_wifi_fallback_disabled, preference_rssi_publish_interval, preference_hostname, preference_find_best_rssi,
preference_network_timeout, preference_restart_on_disconnect, preference_restart_ble_beacon_lost, preference_query_interval_lockstate,
preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_keypad_control_enabled,
preference_keypad_info_enabled, preference_acl, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled,
preference_conf_lock_basic_acl, preference_conf_lock_advanced_acl, preference_conf_opener_basic_acl, preference_conf_opener_advanced_acl,
preference_access_level, preference_register_as_app, preference_command_nr_of_retries,
preference_command_retry_delay, preference_cred_user, preference_cred_password, preference_publish_authdata,
preference_publish_debug_info, preference_presence_detection_timeout,
preference_access_level, preference_register_as_app, preference_command_nr_of_retries, preference_command_retry_delay, preference_cred_user,
preference_cred_password, preference_publish_authdata, preference_publish_debug_info, preference_presence_detection_timeout,
preference_has_mac_saved, preference_has_mac_byte_0, preference_has_mac_byte_1, preference_has_mac_byte_2, preference_latest_version,
preference_task_size_network, preference_task_size_nuki, preference_task_size_pd, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries
};
std::vector<char*> _redact =
{
@@ -105,8 +113,8 @@ private:
std::vector<char*> _boolPrefs =
{
preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_opener_enabled, preference_opener_continuous_mode,
preference_find_best_rssi, preference_restart_on_disconnect, preference_keypad_control_enabled, preference_keypad_info_enabled,
preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_ip_dhcp_enabled,
preference_enable_bootloop_reset, preference_webserver_enabled, preference_find_best_rssi, preference_restart_on_disconnect, preference_keypad_control_enabled,
preference_keypad_info_enabled, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_ip_dhcp_enabled,
preference_publish_authdata, preference_has_mac_saved, preference_publish_debug_info, preference_network_wifi_fallback_disabled
};

View File

@@ -78,6 +78,14 @@ void WebCfgServer::initialize()
buildAccLvlHtml(response);
_server.send(200, "text/html", response);
});
_server.on("/advanced", [&]() {
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
return _server.requestAuthentication();
}
String response = "";
buildAdvancedConfigHtml(response);
_server.send(200, "text/html", response);
});
_server.on("/cred", [&]() {
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
return _server.requestAuthentication();
@@ -470,6 +478,67 @@ bool WebCfgServer::processArgs(String& message)
_preferences->putInt(preference_restart_ble_beacon_lost, value.toInt());
configChanged = true;
}
else if(key == "TSKNTWK")
{
if(value.toInt() > 12287 && value.toInt() < 32769)
{
_preferences->putInt(preference_task_size_network, value.toInt());
configChanged = true;
}
}
else if(key == "TSKNUKI")
{
if(value.toInt() > 8191 && value.toInt() < 32769)
{
_preferences->putInt(preference_task_size_nuki, value.toInt());
configChanged = true;
}
}
else if(key == "TSKPD")
{
if(value.toInt() > 1023 && value.toInt() < 4049)
{
_preferences->putInt(preference_task_size_pd, value.toInt());
configChanged = true;
}
}
else if(key == "ALMAX")
{
if(value.toInt() > 0 && value.toInt() < 51)
{
_preferences->putInt(preference_authlog_max_entries, value.toInt());
configChanged = true;
}
}
else if(key == "KPMAX")
{
if(value.toInt() > 0 && value.toInt() < 101)
{
_preferences->putInt(preference_keypad_max_entries, value.toInt());
configChanged = true;
}
}
else if(key == "TCMAX")
{
if(value.toInt() > 0 && value.toInt() < 51)
{
_preferences->putInt(preference_timecontrol_max_entries, value.toInt());
configChanged = true;
}
}
else if(key == "BUFFSIZE")
{
if(value.toInt() > 4095 && value.toInt() < 32769)
{
_preferences->putInt(preference_buffer_size, value.toInt());
configChanged = true;
}
}
else if(key == "BTLPRST")
{
_preferences->putBool(preference_enable_bootloop_reset, (value == "1"));
configChanged = true;
}
else if(key == "ACLLVLCHANGED")
{
aclLvlChanged = true;
@@ -493,7 +562,7 @@ bool WebCfgServer::processArgs(String& message)
{
_preferences->putBool(preference_timecontrol_control_enabled, (value == "1"));
configChanged = true;
}
}
else if(key == "PUBAUTH")
{
_preferences->putBool(preference_publish_authdata, (value == "1"));
@@ -1073,23 +1142,30 @@ void WebCfgServer::buildHtml(String& response)
if(_preferences->getBool(preference_check_updates)) printParameter(response, "Latest Firmware", _preferences->getString(preference_latest_version).c_str(), "/ota");
response.concat("</table><br><table id=\"tblnav\"><tbody>");
response.concat("<tr><td><h3>MQTT and Network Configuration</h3></td><td class=\"tdbtn\">");
response.concat("<tr><td><h5>MQTT and Network Configuration</h5></td><td class=\"tdbtn\">");
buildNavigationButton(response, "Edit", "/mqttconfig", _brokerConfigured ? "" : "<font color=\"#f07000\"><em>(!) Please configure MQTT broker</em></font>");
response.concat("</td></tr><tr><td><h3>Nuki Configuration</h3></td><td class=\"tdbtn\">");
response.concat("</td></tr><tr><td><h5>Nuki Configuration</h5></td><td class=\"tdbtn\">");
buildNavigationButton(response, "Edit", "/nukicfg");
response.concat("</td></tr><tr><td><h3>Access Level Configuration</h3></td><td class=\"tdbtn\">");
response.concat("</td></tr><tr><td><h5>Access Level Configuration</h5></td><td class=\"tdbtn\">");
buildNavigationButton(response, "Edit", "/acclvl");
response.concat("</td></tr><tr><td><h3>Credentials</h3></td><td class=\"tdbtn\">");
response.concat("</td></tr><tr><td><h5>Credentials</h5></td><td class=\"tdbtn\">");
buildNavigationButton(response, "Edit", "/cred", _pinsConfigured ? "" : "<font color=\"#f07000\"><em>(!) Please configure PIN</em></font>");
response.concat("</td></tr><tr><td><h3>GPIO Configuration</h3></td><td class=\"tdbtn\">");
response.concat("</td></tr><tr><td><h5>GPIO Configuration</h5></td><td class=\"tdbtn\">");
buildNavigationButton(response, "Edit", "/gpiocfg");
response.concat("</td></tr><tr><td><h3>Firmware update</h3></td><td class=\"tdbtn\">");
response.concat("</td></tr><tr><td><h5>Firmware update</h5></td><td class=\"tdbtn\">");
buildNavigationButton(response, "Open", "/ota");
if(_preferences->getBool(preference_publish_debug_info, false))
{
response.concat("</td></tr><tr><td><h5>Advanced Configuration</h5></td><td class=\"tdbtn\">");
buildNavigationButton(response, "Edit", "/advanced");
}
response.concat("</td></tr>");
if(_allowRestartToPortal)
{
response.concat("<tr><td><h3>Wi-Fi</h3></td><td class=\"tdbtn\">");
response.concat("<tr><td><h5>Wi-Fi</h5></td><td class=\"tdbtn\">");
buildNavigationButton(response, "Restart and configure Wi-Fi", "/wifi");
response.concat("</td></tr>");
}
@@ -1260,6 +1336,31 @@ void WebCfgServer::buildMqttConfigHtml(String &response)
response.concat("</body></html>");
}
void WebCfgServer::buildAdvancedConfigHtml(String &response)
{
buildHtmlHeader(response);
response.concat("<form method=\"post\" action=\"savecfg\">");
response.concat("<h3>Advanced Configuration</h3>");
response.concat("<h4 style=\"color: #ff0000\">Warning: Changing these settings can lead to bootloops that might require you to erase the ESP32 and reflash nukihub using USB/serial</h4>");
response.concat("<table>");
response.concat("<tr><td>Current bootloop prevention state</td><td>");
response.concat(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Enabled" : "Disabled");
response.concat("</td></tr>");
printCheckBox(response, "BTLPRST", "Enable Bootloop prevention (Try to reset these settings to default on bootloop)", true, "");
printInputField(response, "BUFFSIZE", "Char buffer size (min 4096, max 32768)", _preferences->getInt(preference_buffer_size, CHAR_BUFFER_SIZE), 6);
printInputField(response, "TSKNTWK", "Task size Network (min 12288, max 32768)", _preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), 6);
printInputField(response, "TSKNUKI", "Task size Nuki (min 8192, max 32768)", _preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), 6);
printInputField(response, "TSKPD", "Task size Presence Detection (min 1024, max 4048)", _preferences->getInt(preference_task_size_pd, PD_TASK_SIZE), 6);
printInputField(response, "ALMAX", "Max auth log entries (min 1, max 50)", _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 3);
printInputField(response, "KPMAX", "Max keypad entries (min 1, max 100)", _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD), 3);
printInputField(response, "TCMAX", "Max timecontrol entries (min 1, max 50)", _preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL), 3);
response.concat("</table>");
response.concat("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.concat("</form>");
response.concat("</body></html>");
}
void WebCfgServer::buildAccLvlHtml(String &response)
{
buildHtmlHeader(response);
@@ -1781,8 +1882,12 @@ void WebCfgServer::buildInfoHtml(String &response)
response.concat(uxTaskGetStackHighWaterMark(networkTaskHandle));
response.concat(", nuki: ");
response.concat(uxTaskGetStackHighWaterMark(nukiTaskHandle));
response.concat(", pd: ");
response.concat(uxTaskGetStackHighWaterMark(presenceDetectionTaskHandle));
if(_preferences->getInt(preference_presence_detection_timeout) >= 0)
{
response.concat(", pd: ");
response.concat(uxTaskGetStackHighWaterMark(presenceDetectionTaskHandle));
}
response.concat("\n");
_gpio->getConfigurationText(response, _gpio->pinConfiguration());

View File

@@ -42,6 +42,7 @@ private:
void buildOtaHtml(String& response, bool errored);
void buildOtaCompletedHtml(String& response);
void buildMqttConfigHtml(String& response);
void buildAdvancedConfigHtml(String& response);
void buildNukiConfigHtml(String& response);
void buildGpioConfigHtml(String& response);
void buildConfirmHtml(String& response, const String &message, uint32_t redirectDelay = 5);

View File

@@ -52,7 +52,11 @@ void networkTask(void *pvParameters)
{
networkOpener->update();
}
webCfgServer->update();
if(preferences->getBool(preference_webserver_enabled, true))
{
webCfgServer->update();
}
// millis() is about to overflow. Restart device to prevent problems with overflow
if(millis() > restartTs)
@@ -112,9 +116,13 @@ void setupTasks()
{
// configMAX_PRIORITIES is 25
xTaskCreatePinnedToCore(networkTask, "ntw", 12288, NULL, 3, &networkTaskHandle, 1);
xTaskCreatePinnedToCore(nukiTask, "nuki", 8192, NULL, 2, &nukiTaskHandle, 1);
xTaskCreatePinnedToCore(presenceDetectionTask, "prdet", 1024, NULL, 5, &presenceDetectionTaskHandle, 1);
xTaskCreatePinnedToCore(networkTask, "ntw", preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), NULL, 3, &networkTaskHandle, 1);
xTaskCreatePinnedToCore(nukiTask, "nuki", preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), NULL, 2, &nukiTaskHandle, 1);
if(preferences->getInt(preference_presence_detection_timeout) >= 0)
{
xTaskCreatePinnedToCore(presenceDetectionTask, "prdet", preferences->getInt(preference_task_size_pd, PD_TASK_SIZE), NULL, 5, &presenceDetectionTaskHandle, 1);
}
}
void initEthServer(const NetworkDeviceType device)
@@ -259,6 +267,27 @@ void setup()
initializeRestartReason();
if(preferences->getBool(preference_enable_bootloop_reset, false) &&
(esp_reset_reason() == esp_reset_reason_t::ESP_RST_PANIC ||
esp_reset_reason() == esp_reset_reason_t::ESP_RST_INT_WDT ||
esp_reset_reason() == esp_reset_reason_t::ESP_RST_TASK_WDT ||
esp_reset_reason() == esp_reset_reason_t::ESP_RST_WDT))
{
preferences->putInt(preference_bootloop_counter, preferences->getInt(preference_bootloop_counter, 0) + 1);
Log->println(F("Bootloop counter incremented"));
if(preferences->getInt(preference_bootloop_counter) == 10)
{
preferences->putInt(preference_buffer_size, CHAR_BUFFER_SIZE);
preferences->putInt(preference_task_size_network, NETWORK_TASK_SIZE);
preferences->putInt(preference_task_size_nuki, NUKI_TASK_SIZE);
preferences->putInt(preference_task_size_pd, PD_TASK_SIZE);
preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG);
preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD);
preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL);
preferences->putInt(preference_bootloop_counter, 0);
}
}
uint32_t devIdOpener = preferences->getUInt(preference_device_id_opener);
@@ -269,8 +298,10 @@ void setup()
{
deviceIdOpener->assignId(deviceIdLock->get());
}
char16_t buffer_size = preferences->getInt(preference_buffer_size, 4096);
CharBuffer::initialize();
CharBuffer::initialize(buffer_size);
if(preferences->getInt(preference_restart_timer) != 0)
{
@@ -286,15 +317,15 @@ void setup()
openerEnabled = preferences->getBool(preference_opener_enabled);
const String mqttLockPath = preferences->getString(preference_mqtt_lock_path);
network = new Network(preferences, gpio, mqttLockPath, CharBuffer::get(), CHAR_BUFFER_SIZE);
network = new Network(preferences, gpio, mqttLockPath, CharBuffer::get(), buffer_size);
network->initialize();
networkLock = new NetworkLock(network, preferences, CharBuffer::get(), CHAR_BUFFER_SIZE);
networkLock = new NetworkLock(network, preferences, CharBuffer::get(), buffer_size);
networkLock->initialize();
if(openerEnabled)
{
networkOpener = new NetworkOpener(network, preferences, CharBuffer::get(), CHAR_BUFFER_SIZE);
networkOpener = new NetworkOpener(network, preferences, CharBuffer::get(), buffer_size);
networkOpener->initialize();
}
@@ -318,16 +349,22 @@ void setup()
nukiOpener->initialize();
}
webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi);
webCfgServer->initialize();
if(preferences->getBool(preference_webserver_enabled, true))
{
webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi);
webCfgServer->initialize();
}
presenceDetection = new PresenceDetection(preferences, bleScanner, network, CharBuffer::get(), CHAR_BUFFER_SIZE);
presenceDetection->initialize();
if(preferences->getInt(preference_presence_detection_timeout) >= 0)
{
presenceDetection = new PresenceDetection(preferences, bleScanner, network, CharBuffer::get(), CHAR_BUFFER_SIZE);
presenceDetection->initialize();
}
setupTasks();
}
void loop()
{
delay(60000);
vTaskDelete(NULL);
}