merge master

This commit is contained in:
technyon
2023-02-14 18:56:36 +01:00
36 changed files with 1061 additions and 141 deletions

View File

@@ -50,8 +50,8 @@ file(GLOB SRCFILES
networkDevices/NetworkDevice.h
networkDevices/WifiDevice.cpp
networkDevices/W5500Device.cpp
networkDevices/ClientSyncEthernet.cpp
networkDevices/espMqttClientEthernet.cpp
networkDevices/ClientSyncW5500.cpp
networkDevices/espMqttClientW5500.cpp
NukiWrapper.cpp
NukiOpenerWrapper.cpp
MqttTopics.h
@@ -62,6 +62,7 @@ file(GLOB SRCFILES
PreferencesKeys.h
Gpio.cpp
Logger.cpp
RestartReason.h
# include/RTOS.h
lib/WiFiManager/WiFiManager.cpp
lib/WiFiManager/wm_consts_en.h
@@ -122,15 +123,9 @@ target_link_arduino_libraries(${PROJECT_NAME}
WiFi
WiFiClientSecure
Update
# WebServer
DNSServer
Preferences
SPIFFS
SPI
# Ethernet
# esp32
# Wire
# FS
)
target_link_arduino_libraries(DNSServer PUBLIC WiFi)

View File

@@ -1,6 +1,6 @@
#pragma once
#define NUKI_HUB_VERSION "8.3"
#define NUKI_HUB_VERSION "8.8"
#define MQTT_QOS_LEVEL 1
#define MQTT_CLEAN_SESSIONS false

View File

@@ -27,7 +27,6 @@ void Gpio::isrLock()
if(millis() < _lockedTs) return;
_nuki->lock();
_lockedTs = millis() + _debounceTime;
Log->println(F("Lock via GPIO"));;
}
void Gpio::isrUnlock()
@@ -35,7 +34,6 @@ void Gpio::isrUnlock()
if(millis() < _lockedTs) return;
_nuki->unlock();
_lockedTs = millis() + _debounceTime;
Log->println(F("Unlock via GPIO"));;
}
void Gpio::isrUnlatch()
@@ -43,5 +41,4 @@ void Gpio::isrUnlatch()
if(millis() < _lockedTs) return;
_nuki->unlatch();
_lockedTs = millis() + _debounceTime;
Log->println(F("Unlatch via GPIO"));;
}

View File

@@ -45,4 +45,6 @@
#define mqtt_topic_uptime "/maintenance/uptime"
#define mqtt_topic_wifi_rssi "/maintenance/wifiRssi"
#define mqtt_topic_log "/maintenance/log"
#define mqtt_topic_freeheap "/maintenance/freeHeap"
#define mqtt_topic_freeheap "/maintenance/freeHeap"
#define mqtt_topic_restart_reason_fw "/maintenance/restartReasonNukiHub"
#define mqtt_topic_restart_reason_esp "/maintenance/restartReasonNukiEsp"

View File

@@ -6,6 +6,7 @@
#include "Logger.h"
#include "Config.h"
#include <ArduinoJson.h>
#include "RestartReason.h"
Network* Network::_inst = nullptr;
@@ -65,8 +66,7 @@ void Network::setupDevice()
Log->println(F(" for network device selection"));
pinMode(hardwareDetectGpio, INPUT_PULLUP);
_networkDeviceType = NetworkDeviceType::W5500;
// digitalRead(hardwareDetectGpio) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500;
_networkDeviceType = digitalRead(hardwareDetectGpio) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500;
}
else if(hardwareDetect == 3)
{
@@ -173,6 +173,8 @@ void Network::initialize()
_networkTimeout = -1;
_preferences->putInt(preference_network_timeout, _networkTimeout);
}
_publishDebugInfo = _preferences->getBool(preference_publish_debug_info);
}
bool Network::update()
@@ -185,7 +187,7 @@ bool Network::update()
{
if(_restartOnDisconnect && millis() > 60000)
{
ESP.restart();
restartEsp(RestartReason::RestartOnDisconnectWatchdog);
}
Log->println(F("Network not connected. Trying reconnect."));
@@ -197,7 +199,7 @@ bool Network::update()
strcpy(WiFi_fallbackDetect, "wifi_fallback");
Log->println("Network device has a critical failure, enable fallback to Wifi and reboot.");
delay(200);
ESP.restart();
restartEsp(RestartReason::NetworkDeviceCriticalFailure);
break;
case ReconnectStatus::Success:
memset(WiFi_fallbackDetect, 0, sizeof(WiFi_fallbackDetect));
@@ -216,7 +218,7 @@ bool Network::update()
{
Log->println("Network timeout has been reached, restarting ...");
delay(200);
ESP.restart();
restartEsp(RestartReason::NetworkTimeoutWatchdog);
}
bool success = reconnect();
@@ -254,7 +256,12 @@ bool Network::update()
if(_lastMaintenanceTs == 0 || (ts - _lastMaintenanceTs) > 30000)
{
publishULong(_maintenancePathPrefix, mqtt_topic_uptime, ts / 1000 / 60);
// publishUInt(_maintenancePathPrefix, mqtt_topic_freeheap, esp_get_free_heap_size());
if(_publishDebugInfo)
{
publishUInt(_maintenancePathPrefix, mqtt_topic_freeheap, esp_get_free_heap_size());
publishString(_maintenancePathPrefix, mqtt_topic_restart_reason_fw, getRestartReason().c_str());
publishString(_maintenancePathPrefix, mqtt_topic_restart_reason_esp, getEspRestartReason().c_str());
}
_lastMaintenanceTs = ts;
}
@@ -367,6 +374,10 @@ bool Network::reconnect()
}
delay(1000);
_mqttConnectionState = 2;
for(const auto& callback : _reconnectedCallbacks)
{
callback();
}
}
else
{
@@ -471,6 +482,11 @@ bool Network::encryptionSupported()
return _device->supportsEncryption();
}
const String Network::networkDeviceName() const
{
return _device->deviceName();
}
void Network::publishFloat(const char* prefix, const char* topic, const float value, const uint8_t precision)
{
char str[30];
@@ -924,3 +940,8 @@ void Network::setKeepAliveCallback(std::function<void()> reconnectTick)
{
_keepAliveCallback = reconnectTick;
}
void Network::addReconnectedCallback(std::function<void()> reconnectedCallback)
{
_reconnectedCallbacks.push_back(reconnectedCallback);
}

View File

@@ -45,12 +45,14 @@ public:
int mqttConnectionState(); // 0 = not connected; 1 = connected; 2 = connected and mqtt processed
bool encryptionSupported();
const String networkDeviceName() const;
const NetworkDeviceType networkDeviceType();
uint16_t subscribe(const char* topic, uint8_t qos);
void setKeepAliveCallback(std::function<void()> reconnectTick);
void addReconnectedCallback(std::function<void()> reconnectedCallback);
private:
static void onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
@@ -82,6 +84,7 @@ private:
char* _presenceCsv = nullptr;
bool _restartOnDisconnect = false;
bool _firstConnect = true;
bool _publishDebugInfo = false;
std::vector<String> _subscribedTopics;
std::map<String, String> _initTopics;
@@ -89,7 +92,8 @@ private:
unsigned long _lastMaintenanceTs = 0;
unsigned long _lastRssiTs = 0;
long _rssiPublishInterval = 0;
std::function<void()> _keepAliveCallback;
std::function<void()> _keepAliveCallback = nullptr;
std::vector<std::function<void()>> _reconnectedCallbacks;
NetworkDeviceType _networkDeviceType = (NetworkDeviceType)-1;

View File

@@ -5,6 +5,7 @@
#include "PreferencesKeys.h"
#include "Pins.h"
#include "Logger.h"
#include "RestartReason.h"
NetworkLock::NetworkLock(Network* network, Preferences* preferences)
: _network(network),
@@ -69,6 +70,11 @@ void NetworkLock::initialize()
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_code, "000000");
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_enabled, "1");
}
_network->addReconnectedCallback([&]()
{
_reconnected = true;
});
}
void NetworkLock::onMqttDataReceived(const char* topic, byte* payload, const unsigned int length)
@@ -81,7 +87,7 @@ void NetworkLock::onMqttDataReceived(const char* topic, byte* payload, const uns
{
Log->println(F("Restart requested via MQTT."));
delay(200);
ESP.restart();
restartEsp(RestartReason::RequestedViaMqtt);
}
if(processActions && comparePrefixedPath(topic, mqtt_topic_lock_action))
@@ -545,7 +551,6 @@ void NetworkLock::publishKeypadEntry(const String topic, NukiLock::KeypadEntry e
publishInt(concat(topic, "/lockCount").c_str(), entry.lockCount);
}
void NetworkLock::publishULong(const char *topic, const unsigned long value)
{
return _network->publishULong(_mqttPath, topic, value);
@@ -557,3 +562,10 @@ String NetworkLock::concat(String a, String b)
c.concat(b);
return c;
}
bool NetworkLock::reconnected()
{
bool r = _reconnected;
_reconnected = false;
return r;
}

View File

@@ -40,7 +40,11 @@ public:
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
bool reconnected();
private:
bool comparePrefixedPath(const char* fullPath, const char* subPath);
void publishFloat(const char* topic, const float value, const uint8_t precision = 2);
void publishInt(const char* topic, const int value);
void publishUInt(const char* topic, const unsigned int value);
@@ -50,7 +54,6 @@ private:
bool publishString(const char* topic, const std::string& value);
bool publishString(const char* topic, const char* value);
void publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry);
bool comparePrefixedPath(const char* fullPath, const char* subPath);
String concat(String a, String b);
@@ -65,6 +68,7 @@ private:
bool _firstTunerStatePublish = true;
unsigned long _lastMaintenanceTs = 0;
bool _haEnabled= false;
bool _reconnected = false;
String _keypadCommandName = "";
String _keypadCommandCode = "";

View File

@@ -42,6 +42,25 @@ void NetworkOpener::initialize()
{
_network->subscribe(_mqttPath, topic);
}
if(_preferences->getBool(preference_keypad_control_enabled))
{
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_action);
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_id);
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_name);
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_code);
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_enabled);
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_action, "--");
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_id, "0");
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_name, "--");
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_code, "000000");
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_enabled, "1");
}
_network->addReconnectedCallback([&]()
{
_reconnected = true;
});
}
void NetworkOpener::update()
@@ -76,6 +95,46 @@ void NetworkOpener::onMqttDataReceived(const char* topic, byte* payload, const u
publishString(mqtt_topic_lock_action, success ? "ack" : "unknown_action");
}
if(processActions && comparePrefixedPath(topic, mqtt_topic_keypad_command_action))
{
if(_keypadCommandReceivedReceivedCallback != nullptr)
{
if(strcmp(value, "--") == 0) return;
_keypadCommandReceivedReceivedCallback(value, _keypadCommandId, _keypadCommandName, _keypadCommandCode, _keypadCommandEnabled);
_keypadCommandId = 0;
_keypadCommandName = "--";
_keypadCommandCode = "000000";
_keypadCommandEnabled = 1;
if(strcmp(value, "--") != 0)
{
publishString(mqtt_topic_keypad_command_action, "--");
}
publishInt(mqtt_topic_keypad_command_id, _keypadCommandId);
publishString(mqtt_topic_keypad_command_name, _keypadCommandName);
publishString(mqtt_topic_keypad_command_code, _keypadCommandCode);
publishInt(mqtt_topic_keypad_command_enabled, _keypadCommandEnabled);
}
}
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_id))
{
_keypadCommandId = atoi(value);
}
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_name))
{
_keypadCommandName = value;
}
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_code))
{
_keypadCommandCode = value;
}
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_enabled))
{
_keypadCommandEnabled = atoi(value);
}
for(auto configTopic : _configTopics)
{
if(comparePrefixedPath(topic, configTopic))
@@ -399,6 +458,36 @@ void NetworkOpener::removeHASSConfig(char* uidString)
_network->removeHASSConfig(uidString);
}
void NetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount)
{
uint index = 0;
for(const auto& entry : entries)
{
String basePath = mqtt_topic_keypad;
basePath.concat("/code_");
basePath.concat(std::to_string(index).c_str());
publishKeypadEntry(basePath, entry);
++index;
}
while(index < maxKeypadCodeCount)
{
NukiLock::KeypadEntry entry;
memset(&entry, 0, sizeof(entry));
String basePath = mqtt_topic_keypad;
basePath.concat("/code_");
basePath.concat(std::to_string(index).c_str());
publishKeypadEntry(basePath, entry);
++index;
}
}
void NetworkOpener::publishKeypadCommandResult(const char* result)
{
publishString(mqtt_topic_keypad_command_result, result);
}
void NetworkOpener::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *))
{
_lockActionReceivedCallback = lockActionReceivedCallback;
@@ -409,6 +498,11 @@ void NetworkOpener::setConfigUpdateReceivedCallback(void (*configUpdateReceivedC
_configUpdateReceivedCallback = configUpdateReceivedCallback;
}
void NetworkOpener::setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled))
{
_keypadCommandReceivedReceivedCallback = keypadCommandReceivedReceivedCallback;
}
void NetworkOpener::publishFloat(const char *topic, const float value, const uint8_t precision)
{
_network->publishFloat(_mqttPath, topic, value, precision);
@@ -450,6 +544,24 @@ void NetworkOpener::publishString(const char* topic, const char* value)
_network->publishString(_mqttPath, topic, value);
}
void NetworkOpener::publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry)
{
char codeName[sizeof(entry.name) + 1];
memset(codeName, 0, sizeof(codeName));
memcpy(codeName, entry.name, sizeof(entry.name));
publishInt(concat(topic, "/id").c_str(), entry.codeId);
publishBool(concat(topic, "/enabled").c_str(), entry.enabled);
publishString(concat(topic, "/name").c_str(), codeName);
publishInt(concat(topic, "/createdYear").c_str(), entry.dateCreatedYear);
publishInt(concat(topic, "/createdMonth").c_str(), entry.dateCreatedMonth);
publishInt(concat(topic, "/createdDay").c_str(), entry.dateCreatedDay);
publishInt(concat(topic, "/createdHour").c_str(), entry.dateCreatedHour);
publishInt(concat(topic, "/createdMin").c_str(), entry.dateCreatedMin);
publishInt(concat(topic, "/createdSec").c_str(), entry.dateCreatedSec);
publishInt(concat(topic, "/lockCount").c_str(), entry.lockCount);
}
void NetworkOpener::buildMqttPath(const char* path, char* outPath)
{
int offset = 0;
@@ -486,3 +598,17 @@ bool NetworkOpener::comparePrefixedPath(const char *fullPath, const char *subPat
return strcmp(fullPath, prefixedPath) == 0;
}
String NetworkOpener::concat(String a, String b)
{
String c = a;
c.concat(b);
return c;
}
bool NetworkOpener::reconnected()
{
bool r = _reconnected;
_reconnected = false;
return r;
}

View File

@@ -32,12 +32,17 @@ public:
void publishBleAddress(const std::string& address);
void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState);
void removeHASSConfig(char* uidString);
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
void publishKeypadCommandResult(const char* result);
void setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value));
void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value));
void setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled));
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
bool reconnected();
private:
bool comparePrefixedPath(const char* fullPath, const char* subPath);
@@ -48,11 +53,14 @@ private:
void publishString(const char* topic, const String& value);
void publishString(const char* topic, const std::string& value);
void publishString(const char* topic, const char* value);
void publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry);
void buildMqttPath(const char* path, char* outPath);
void subscribe(const char* path);
void logactionCompletionStatusToString(uint8_t value, char* out);
String concat(String a, String b);
Preferences* _preferences;
Network* _network = nullptr;
@@ -64,8 +72,15 @@ private:
bool _firstTunerStatePublish = true;
bool _haEnabled= false;
bool _reconnected = false;
String _keypadCommandName = "";
String _keypadCommandCode = "";
uint _keypadCommandId = 0;
int _keypadCommandEnabled = 1;
unsigned long _resetLockStateTs = 0;
bool (*_lockActionReceivedCallback)(const char* value) = nullptr;
void (*_configUpdateReceivedCallback)(const char* path, const char* value) = nullptr;
void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;
};

View File

@@ -3,6 +3,7 @@
#include "PreferencesKeys.h"
#include "MqttTopics.h"
#include "Logger.h"
#include "RestartReason.h"
#include <NukiOpenerUtils.h>
NukiOpenerWrapper* nukiOpenerInst;
@@ -24,6 +25,7 @@ NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, uint32_t id,
network->setLockActionReceivedCallback(nukiOpenerInst->onLockActionReceivedCallback);
network->setConfigUpdateReceivedCallback(nukiOpenerInst->onConfigUpdateReceivedCallback);
network->setKeypadCommandReceivedCallback(nukiOpenerInst->onKeypadCommandReceivedCallback);
}
@@ -39,24 +41,56 @@ void NukiOpenerWrapper::initialize()
_nukiOpener.registerBleScanner(_bleScanner);
_intervalLockstate = _preferences->getInt(preference_query_interval_lockstate);
_intervalConfig = _preferences->getInt(preference_query_interval_configuration);
_intervalBattery = _preferences->getInt(preference_query_interval_battery);
_intervalKeypad = _preferences->getInt(preference_query_interval_keypad);
_keypadEnabled = _preferences->getBool(preference_keypad_control_enabled);
_publishAuthData = _preferences->getBool(preference_publish_authdata);
_maxKeypadCodeCount = _preferences->getUInt(preference_opener_max_keypad_code_count);
_restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost);
_hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
_nrOfRetries = _preferences->getInt(preference_command_nr_of_retries);
_retryDelay = _preferences->getInt(preference_command_retry_delay);
_rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000;
if(_retryDelay <= 100)
{
_retryDelay = 100;
_preferences->putInt(preference_command_retry_delay, _retryDelay);
}
if(_intervalLockstate == 0)
{
_intervalLockstate = 60 * 5;
_intervalLockstate = 60 * 30;
_preferences->putInt(preference_query_interval_lockstate, _intervalLockstate);
}
if(_intervalConfig == 0)
{
_intervalConfig = 60 * 60;
_preferences->putInt(preference_query_interval_configuration, _intervalConfig);
}
if(_intervalBattery == 0)
{
_intervalBattery = 60 * 30;
_preferences->putInt(preference_query_interval_battery, _intervalBattery);
}
if(_intervalKeypad == 0)
{
_intervalKeypad = 60 * 30;
_preferences->putInt(preference_query_interval_keypad, _intervalKeypad);
}
/*
if(_intervalKeypad == 0)
{
_intervalKeypad = 60 * 30;
_preferences->putInt(preference_query_interval_keypad, _intervalKeypad);
}
*/
if(_restartBeaconTimeout < 10)
{
_restartBeaconTimeout = -1;
_preferences->putInt(preference_restart_ble_beacon_lost, _restartBeaconTimeout);
}
_nukiOpener.setEventHandler(this);
@@ -108,7 +142,7 @@ void NukiOpenerWrapper::update()
Log->print((millis() - _nukiOpener.getLastReceivedBeaconTs()) / 1000);
Log->println(" seconds, restarting device.");
delay(200);
ESP.restart();
restartEsp(RestartReason::BLEBeaconWatchdog);
}
_nukiOpener.updateConnectionState();
@@ -128,12 +162,15 @@ void NukiOpenerWrapper::update()
{
_nextConfigUpdateTs = ts + _intervalConfig * 1000;
updateConfig();
if(_hassEnabled)
if(_hassEnabled && !_hassSetupCompleted)
{
setupHASS();
}
}
if(_hassEnabled && _configRead && _network->reconnected())
{
setupHASS();
}
if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs))
{
_nextRssiTs = ts + _rssiPublishInterval;
@@ -146,6 +183,12 @@ void NukiOpenerWrapper::update()
}
}
if(_hasKeypad && _keypadEnabled && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs))
{
_nextKeypadUpdateTs = ts + _intervalKeypad * 1000;
updateKeypad();
}
if(_nextLockAction != (NukiOpener::LockAction)0xff && ts > _nextRetryTs)
{
Nuki::CmdResult cmdResult = _nukiOpener.lockAction(_nextLockAction, 0, 0);
@@ -194,6 +237,7 @@ void NukiOpenerWrapper::update()
_nextLockAction = (NukiOpener::LockAction) 0xff;
}
}
postponeBleWatchdog();
}
if(_clearAuthData)
@@ -205,6 +249,11 @@ void NukiOpenerWrapper::update()
memcpy(&_lastKeyTurnerState, &_keyTurnerState, sizeof(NukiOpener::OpenerState));
}
bool NukiOpenerWrapper::isPinSet()
{
return _nukiOpener.getSecurityPincode() != 0;
}
void NukiOpenerWrapper::setPin(const uint16_t pin)
{
_nukiOpener.saveSecurityPincode(pin);
@@ -218,7 +267,18 @@ void NukiOpenerWrapper::unpair()
void NukiOpenerWrapper::updateKeyTurnerState()
{
_nukiOpener.requestOpenerState(&_keyTurnerState);
Nuki::CmdResult result =_nukiOpener.requestOpenerState(&_keyTurnerState);
if(result != Nuki::CmdResult::Success)
{
_retryLockstateCount++;
postponeBleWatchdog();
if(_retryLockstateCount < _nrOfRetries)
{
_nextLockStateUpdateTs = millis() + _retryDelay;
}
return;
}
_retryLockstateCount = 0;
if(_statusUpdated &&
_keyTurnerState.lockState == NukiOpener::LockState::Locked &&
@@ -249,20 +309,34 @@ void NukiOpenerWrapper::updateKeyTurnerState()
{
updateAuthData();
}
postponeBleWatchdog();
}
void NukiOpenerWrapper::updateBatteryState()
{
_nukiOpener.requestBatteryReport(&_batteryReport);
_network->publishBatteryReport(_batteryReport);
Nuki::CmdResult result = _nukiOpener.requestBatteryReport(&_batteryReport);
if(result == Nuki::CmdResult::Success)
{
_network->publishBatteryReport(_batteryReport);
}
postponeBleWatchdog();
}
void NukiOpenerWrapper::updateConfig()
{
readConfig();
readAdvancedConfig();
_network->publishConfig(_nukiConfig);
_network->publishAdvancedConfig(_nukiAdvancedConfig);
_configRead = true;
_hasKeypad = _nukiConfig.hasKeypad > 0;
if(_nukiConfigValid)
{
_network->publishConfig(_nukiConfig);
}
if(_nukiAdvancedConfigValid)
{
_network->publishAdvancedConfig(_nukiAdvancedConfig);
}
}
void NukiOpenerWrapper::updateAuthData()
@@ -290,6 +364,41 @@ void NukiOpenerWrapper::updateAuthData()
{
_network->publishAuthorizationInfo(log);
}
postponeBleWatchdog();
}
void NukiOpenerWrapper::updateKeypad()
{
Nuki::CmdResult result = _nukiOpener.retrieveKeypadEntries(0, 0xffff);
if(result == 1)
{
std::list<NukiLock::KeypadEntry> entries;
_nukiOpener.getKeypadEntries(&entries);
entries.sort([](const NukiLock::KeypadEntry& a, const NukiLock::KeypadEntry& b) { return a.codeId < b.codeId; });
uint keypadCount = entries.size();
if(keypadCount > _maxKeypadCodeCount)
{
_maxKeypadCodeCount = keypadCount;
_preferences->putUInt(preference_opener_max_keypad_code_count, _maxKeypadCodeCount);
}
_network->publishKeypad(entries, _maxKeypadCodeCount);
_keypadCodeIds.clear();
_keypadCodeIds.reserve(entries.size());
for(const auto& entry : entries)
{
_keypadCodeIds.push_back(entry.codeId);
}
}
postponeBleWatchdog();
}
void NukiOpenerWrapper::postponeBleWatchdog()
{
_disableBleWatchdogTs = millis() + 15000;
}
NukiOpener::LockAction NukiOpenerWrapper::lockActionToEnum(const char *str)
@@ -317,6 +426,10 @@ void NukiOpenerWrapper::onConfigUpdateReceivedCallback(const char *topic, const
nukiOpenerInst->onConfigUpdateReceived(topic, value);
}
void NukiOpenerWrapper::onKeypadCommandReceivedCallback(const char *command, const uint &id, const String &name, const String &code, const int& enabled)
{
nukiOpenerInst->onKeypadCommandReceived(command, id, name, code, enabled);
}
void NukiOpenerWrapper::onConfigUpdateReceived(const char *topic, const char *value)
{
@@ -343,6 +456,117 @@ void NukiOpenerWrapper::onConfigUpdateReceived(const char *topic, const char *va
}
}
void NukiOpenerWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code, const int& enabled)
{
if(!_hasKeypad)
{
if(_configRead)
{
_network->publishKeypadCommandResult("KeypadNotAvailable");
}
return;
}
if(!_keypadEnabled)
{
return;
}
bool idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), id) != _keypadCodeIds.end();
int codeInt = code.toInt();
bool codeValid = codeInt > 100000 && codeInt < 1000000 && (code.indexOf('0') == -1);
NukiLock::CmdResult result = (NukiLock::CmdResult)-1;
if(strcmp(command, "add") == 0)
{
if(name == "" || name == "--")
{
_network->publishKeypadCommandResult("MissingParameterName");
return;
}
if(codeInt == 0)
{
_network->publishKeypadCommandResult("MissingParameterCode");
return;
}
if(!codeValid)
{
_network->publishKeypadCommandResult("CodeInvalid");
return;
}
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 = codeInt;
result = _nukiOpener.addKeypadEntry(entry);
Log->print("Add keypad code: "); Log->println((int)result);
updateKeypad();
}
else if(strcmp(command, "delete") == 0)
{
if(!idExists)
{
_network->publishKeypadCommandResult("UnknownId");
return;
}
result = _nukiOpener.deleteKeypadEntry(id);
Log->print("Delete keypad code: "); Log->println((int)result);
updateKeypad();
}
else if(strcmp(command, "update") == 0)
{
if(name == "" || name == "--")
{
_network->publishKeypadCommandResult("MissingParameterName");
return;
}
if(codeInt == 0)
{
_network->publishKeypadCommandResult("MissingParameterCode");
return;
}
if(!codeValid)
{
_network->publishKeypadCommandResult("CodeInvalid");
return;
}
if(!idExists)
{
_network->publishKeypadCommandResult("UnknownId");
return;
}
NukiLock::UpdatedKeypadEntry entry;
memset(&entry, 0, sizeof(entry));
entry.codeId = id;
size_t nameLen = name.length();
memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen);
entry.code = codeInt;
entry.enabled = enabled == 0 ? 0 : 1;
result = _nukiOpener.updateKeypadEntry(entry);
Log->print("Update keypad code: "); Log->println((int)result);
updateKeypad();
}
else if(command == "--")
{
return;
}
else
{
_network->publishKeypadCommandResult("UnknownCommand");
return;
}
if((int)result != -1)
{
char resultStr[15];
memset(&resultStr, 0, sizeof(resultStr));
NukiOpener::cmdResultToString(result, resultStr);
_network->publishKeypadCommandResult(resultStr);
}
}
const NukiOpener::OpenerState &NukiOpenerWrapper::keyTurnerState()
{
return _keyTurnerState;
@@ -353,6 +577,11 @@ const bool NukiOpenerWrapper::isPaired()
return _paired;
}
const bool NukiOpenerWrapper::hasKeypad()
{
return _hasKeypad;
}
const BLEAddress NukiOpenerWrapper::getBleAddress() const
{
return _nukiOpener.getBleAddress();
@@ -379,6 +608,7 @@ void NukiOpenerWrapper::readConfig()
char resultStr[20];
NukiOpener::cmdResultToString(result, resultStr);
Log->println(resultStr);
postponeBleWatchdog();
}
void NukiOpenerWrapper::readAdvancedConfig()
@@ -389,11 +619,12 @@ void NukiOpenerWrapper::readAdvancedConfig()
char resultStr[20];
NukiOpener::cmdResultToString(result, resultStr);
Log->println(resultStr);
postponeBleWatchdog();
}
void NukiOpenerWrapper::setupHASS()
{
if(!_nukiConfigValid || _hassSetupCompleted) return;
if(!_nukiConfigValid) return;
String baseTopic = _preferences->getString(preference_mqtt_opener_path);
char uidString[20];

View File

@@ -15,6 +15,7 @@ public:
void initialize();
void update();
bool isPinSet();
void setPin(const uint16_t pin);
void unpair();
@@ -23,6 +24,7 @@ public:
const NukiOpener::OpenerState& keyTurnerState();
const bool isPaired();
const bool hasKeypad();
const BLEAddress getBleAddress() const;
BleScanner::Scanner* bleScanner();
@@ -32,12 +34,16 @@ public:
private:
static bool onLockActionReceivedCallback(const char* value);
static void onConfigUpdateReceivedCallback(const char* topic, const char* value);
static void onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
void onConfigUpdateReceived(const char* topic, const char* value);
void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
void updateKeyTurnerState();
void updateBatteryState();
void updateConfig();
void updateAuthData();
void updateKeypad();
void postponeBleWatchdog();
void readConfig();
void readAdvancedConfig();
@@ -54,13 +60,16 @@ private:
int _intervalLockstate = 0; // seconds
int _intervalBattery = 0; // seconds
int _intervalConfig = 60 * 60; // seconds
int _intervalKeypad = 0; // seconds
int _restartBeaconTimeout = 0; // seconds
bool _publishAuthData = false;
bool _clearAuthData = false;
int _nrOfRetries = 0;
int _retryDelay = 0;
int _retryCount = 0;
int _retryLockstateCount = 0;
unsigned long _nextRetryTs = 0;
std::vector<uint16_t> _keypadCodeIds;
NukiOpener::OpenerState _lastKeyTurnerState;
NukiOpener::OpenerState _keyTurnerState;
@@ -77,12 +86,18 @@ private:
bool _paired = false;
bool _statusUpdated = false;
bool _hasKeypad = false;
bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0;
bool _configRead = false;
long _rssiPublishInterval = 0;
unsigned long _nextLockStateUpdateTs = 0;
unsigned long _nextBatteryReportTs = 0;
unsigned long _nextConfigUpdateTs = 0;
unsigned long _nextKeypadUpdateTs = 0;
unsigned long _nextPairTs = 0;
long _nextRssiTs = 0;
unsigned long _lastRssi = 0;
unsigned long _disableBleWatchdogTs = 0;
NukiOpener::LockAction _nextLockAction = (NukiOpener::LockAction)0xff;
};

View File

@@ -3,6 +3,7 @@
#include "PreferencesKeys.h"
#include "MqttTopics.h"
#include "Logger.h"
#include "RestartReason.h"
#include <NukiLockUtils.h>
NukiWrapper* nukiInst;
@@ -41,11 +42,12 @@ void NukiWrapper::initialize(const bool& firstStart)
_nukiLock.registerBleScanner(_bleScanner);
_intervalLockstate = _preferences->getInt(preference_query_interval_lockstate);
_intervalConfig = _preferences->getInt(preference_query_interval_battery);
_intervalBattery = _preferences->getInt(preference_query_interval_battery);
_intervalKeypad = _preferences->getInt(preference_query_interval_keypad);
_keypadEnabled = _preferences->getBool(preference_keypad_control_enabled);
_publishAuthData = _preferences->getBool(preference_publish_authdata);
_maxKeypadCodeCount = _preferences->getUInt(preference_max_keypad_code_count);
_maxKeypadCodeCount = _preferences->getUInt(preference_lock_max_keypad_code_count);
_restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost);
_hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
_nrOfRetries = _preferences->getInt(preference_command_nr_of_retries);
@@ -57,6 +59,7 @@ void NukiWrapper::initialize(const bool& firstStart)
_preferences->putInt(preference_command_nr_of_retries, 3);
_preferences->putInt(preference_command_retry_delay, 1000);
}
if(_retryDelay <= 100)
{
_retryDelay = 100;
@@ -68,6 +71,11 @@ void NukiWrapper::initialize(const bool& firstStart)
_intervalLockstate = 60 * 30;
_preferences->putInt(preference_query_interval_lockstate, _intervalLockstate);
}
if(_intervalConfig == 0)
{
_intervalConfig = 60 * 60;
_preferences->putInt(preference_query_interval_configuration, _intervalConfig);
}
if(_intervalBattery == 0)
{
_intervalBattery = 60 * 30;
@@ -128,13 +136,14 @@ void NukiWrapper::update()
if(_restartBeaconTimeout > 0 &&
ts > 60000 &&
lastReceivedBeaconTs > 0 &&
_disableBleWatchdogTs < ts &&
(ts - lastReceivedBeaconTs > _restartBeaconTimeout * 1000))
{
Log->print("No BLE beacon received from the lock for ");
Log->print((millis() - _nukiLock.getLastReceivedBeaconTs()) / 1000);
Log->println(" seconds, restarting device.");
delay(200);
ESP.restart();
restartEsp(RestartReason::BLEBeaconWatchdog);
}
_nukiLock.updateConnectionState();
@@ -154,11 +163,15 @@ void NukiWrapper::update()
{
_nextConfigUpdateTs = ts + _intervalConfig * 1000;
updateConfig();
if(_hassEnabled)
if(_hassEnabled && !_hassSetupCompleted)
{
setupHASS();
}
}
if(_hassEnabled && _configRead && _network->reconnected())
{
setupHASS();
}
if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs))
{
_nextRssiTs = ts + _rssiPublishInterval;
@@ -225,6 +238,7 @@ void NukiWrapper::update()
_nextLockAction = (NukiLock::LockAction) 0xff;
}
}
postponeBleWatchdog();
}
if(_clearAuthData)
@@ -251,6 +265,11 @@ void NukiWrapper::unlatch()
_nextLockAction = NukiLock::LockAction::Unlatch;
}
bool NukiWrapper::isPinSet()
{
return _nukiLock.getSecurityPincode() != 0;
}
void NukiWrapper::setPin(const uint16_t pin)
{
_nukiLock.saveSecurityPincode(pin);
@@ -264,7 +283,19 @@ void NukiWrapper::unpair()
void NukiWrapper::updateKeyTurnerState()
{
_nukiLock.requestKeyTurnerState(&_keyTurnerState);
Nuki::CmdResult result =_nukiLock.requestKeyTurnerState(&_keyTurnerState);
if(result != Nuki::CmdResult::Success)
{
_retryLockstateCount++;
postponeBleWatchdog();
if(_retryLockstateCount < _nrOfRetries)
{
_nextLockStateUpdateTs = millis() + _retryDelay;
}
return;
}
_retryLockstateCount = 0;
_network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState);
if(_keyTurnerState.lockState != _lastKeyTurnerState.lockState)
@@ -279,12 +310,18 @@ void NukiWrapper::updateKeyTurnerState()
{
updateAuthData();
}
postponeBleWatchdog();
}
void NukiWrapper::updateBatteryState()
{
_nukiLock.requestBatteryReport(&_batteryReport);
_network->publishBatteryReport(_batteryReport);
Nuki::CmdResult result = _nukiLock.requestBatteryReport(&_batteryReport);
if(result == Nuki::CmdResult::Success)
{
_network->publishBatteryReport(_batteryReport);
}
postponeBleWatchdog();
}
void NukiWrapper::updateConfig()
@@ -293,8 +330,14 @@ void NukiWrapper::updateConfig()
readAdvancedConfig();
_configRead = true;
_hasKeypad = _nukiConfig.hasKeypad > 0;
_network->publishConfig(_nukiConfig);
_network->publishAdvancedConfig(_nukiAdvancedConfig);
if(_nukiConfigValid)
{
_network->publishConfig(_nukiConfig);
}
if(_nukiAdvancedConfigValid)
{
_network->publishAdvancedConfig(_nukiAdvancedConfig);
}
}
void NukiWrapper::updateAuthData()
@@ -322,6 +365,7 @@ void NukiWrapper::updateAuthData()
{
_network->publishAuthorizationInfo(log);
}
postponeBleWatchdog();
}
void NukiWrapper::updateKeypad()
@@ -338,7 +382,7 @@ void NukiWrapper::updateKeypad()
if(keypadCount > _maxKeypadCodeCount)
{
_maxKeypadCodeCount = keypadCount;
_preferences->putUInt(preference_max_keypad_code_count, _maxKeypadCodeCount);
_preferences->putUInt(preference_lock_max_keypad_code_count, _maxKeypadCodeCount);
}
_network->publishKeypad(entries, _maxKeypadCodeCount);
@@ -350,6 +394,12 @@ void NukiWrapper::updateKeypad()
_keypadCodeIds.push_back(entry.codeId);
}
}
postponeBleWatchdog();
}
void NukiWrapper::postponeBleWatchdog()
{
_disableBleWatchdogTs = millis() + 15000;
}
NukiLock::LockAction NukiWrapper::lockActionToEnum(const char *str)
@@ -378,13 +428,11 @@ void NukiWrapper::onConfigUpdateReceivedCallback(const char *topic, const char *
nukiInst->onConfigUpdateReceived(topic, value);
}
void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uint &id, const String &name, const String &code, const int& enabled)
{
nukiInst->onKeypadCommandReceived(command, id, name, code, enabled);
}
void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value)
{
if(strcmp(topic, mqtt_topic_config_button_enabled) == 0)
@@ -594,7 +642,7 @@ void NukiWrapper::readAdvancedConfig()
void NukiWrapper::setupHASS()
{
if(!_nukiConfigValid || _hassSetupCompleted) return;
if(!_nukiConfigValid) return;
String baseTopic = _preferences->getString(preference_mqtt_lock_path);
char uidString[20];

View File

@@ -19,6 +19,7 @@ public:
void unlock();
void unlatch();
bool isPinSet();
void setPin(const uint16_t pin);
void unpair();
@@ -43,12 +44,12 @@ private:
void updateConfig();
void updateAuthData();
void updateKeypad();
void postponeBleWatchdog();
void readConfig();
void readAdvancedConfig();
void setupHASS();
bool hasDoorSensor();
NukiLock::LockAction lockActionToEnum(const char* str); // char array at least 14 characters
@@ -84,11 +85,12 @@ private:
bool _statusUpdated = false;
bool _hasKeypad = false;
bool _keypadEnabled = false;
bool _configRead = false;
uint _maxKeypadCodeCount = 0;
bool _configRead = false;
int _nrOfRetries = 0;
int _retryDelay = 0;
int _retryCount = 0;
int _retryLockstateCount = 0;
long _rssiPublishInterval = 0;
unsigned long _nextRetryTs = 0;
unsigned long _nextLockStateUpdateTs = 0;
@@ -97,5 +99,6 @@ private:
unsigned long _nextKeypadUpdateTs = 0;
unsigned long _nextRssiTs = 0;
unsigned long _lastRssi = 0;
NukiLock::LockAction _nextLockAction = (NukiLock::LockAction)0xff;
unsigned long _disableBleWatchdogTs = 0;
volatile NukiLock::LockAction _nextLockAction = (NukiLock::LockAction)0xff;
};

View File

@@ -1,6 +1,7 @@
#include <Arduino.h>
#include "Ota.h"
#include "Logger.h"
#include "RestartReason.h"
#define FULL_PACKET 1436 // HTTP_UPLOAD_BUFLEN in WebServer,h
@@ -26,7 +27,7 @@ void Ota::updateFirmware(uint8_t* buf, size_t size)
if (ESP_OK == esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)))
{
delay(2000);
esp_restart();
restartEsp(RestartReason::OTACompleted);
}
else
{

View File

@@ -1,5 +1,7 @@
#pragma once
#include <vector>
#define preference_started_before "run"
#define preference_deviceId "deviceId"
#define preference_mqtt_broker "mqttbroker"
@@ -11,7 +13,8 @@
#define preference_mqtt_lock_path "mqttpath"
#define preference_opener_enabled "openerena"
#define preference_mqtt_opener_path "mqttoppath"
#define preference_max_keypad_code_count "maxkpad"
#define preference_lock_max_keypad_code_count "maxkpad"
#define preference_opener_max_keypad_code_count "opmaxkpad"
#define preference_mqtt_ca "mqttca"
#define preference_mqtt_crt "mqttcrt"
#define preference_mqtt_key "mqttkey"
@@ -25,6 +28,7 @@
#define preference_restart_timer "resttmr"
#define preference_restart_ble_beacon_lost "rstbcn"
#define preference_query_interval_lockstate "lockStInterval"
#define preference_query_interval_configuration "configInterval"
#define preference_query_interval_battery "batInterval"
#define preference_query_interval_keypad "kpInterval"
#define preference_keypad_control_enabled "kpEnabled"
@@ -35,11 +39,183 @@
#define preference_cred_password "crdpass"
#define preference_publish_authdata "pubauth"
#define preference_gpio_locking_enabled "gpiolck"
#define preference_publish_debug_info "pubdbg"
#define preference_presence_detection_timeout "prdtimeout"
#define preference_has_mac_saved "hasmac"
#define preference_has_mac_byte_0 "macb0"
#define preference_has_mac_byte_1 "macb1"
#define preference_has_mac_byte_2 "macb2"
class DebugPreferences
{
private:
std::vector<char*> _keys =
{
preference_started_before, preference_deviceId, preference_mqtt_broker, preference_mqtt_broker_port, preference_mqtt_user, preference_mqtt_password, preference_mqtt_log_enabled, preference_lock_enabled, preference_mqtt_lock_path, preference_opener_enabled, 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_network_hardware, preference_network_hardware_gpio, preference_rssi_publish_interval, preference_hostname, preference_network_timeout, preference_restart_on_disconnect, preference_restart_timer, 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_register_as_app, preference_command_nr_of_retries, preference_command_retry_delay, preference_cred_user, preference_cred_password, preference_publish_authdata, preference_gpio_locking_enabled, 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,
};
std::vector<char*> _redact =
{
preference_mqtt_user, preference_mqtt_password,
preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key,
preference_cred_user, preference_cred_password,
};
std::vector<char*> _boolPrefs =
{
preference_started_before, preference_mqtt_log_enabled, preference_lock_enabled, preference_opener_enabled,
preference_restart_on_disconnect, preference_keypad_control_enabled, preference_register_as_app,
preference_publish_authdata, preference_gpio_locking_enabled, preference_has_mac_saved, preference_publish_debug_info
};
const bool isRedacted(const char* key) const
{
return std::find(_redact.begin(), _redact.end(), key) != _redact.end();
}
const String redact(const String s) const
{
return s == "" ? "" : "***";
}
const String redact(const int i) const
{
return i == 0 ? "" : "***";
}
const String redact(const uint i) const
{
return i == 0 ? "" : "***";
}
const String redact(const int64_t i) const
{
return i == 0 ? "" : "***";
}
const String redact(const uint64_t i) const
{
return i == 0 ? "" : "***";
}
const void appendPreferenceInt8(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getChar(key)) : String(preferences->getChar(key)));
s.concat("\n");
}
const void appendPreferenceUInt8(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getUChar(key)) : String(preferences->getUChar(key)));
s.concat("\n");
}
const void appendPreferenceInt16(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getShort(key)) : String(preferences->getShort(key)));
s.concat("\n");
}
const void appendPreferenceUInt16(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getUShort(key)) : String(preferences->getUShort(key)));
s.concat("\n");
}
const void appendPreferenceInt32(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getInt(key)) : String(preferences->getInt(key)));
s.concat("\n");
}
const void appendPreferenceUInt32(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getUInt(key)) : String(preferences->getUInt(key)));
s.concat("\n");
}
const void appendPreferenceInt64(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getLong64(key)) : String(preferences->getLong64(key)));
s.concat("\n");
}
const void appendPreferenceUInt64(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getULong64(key)) : String(preferences->getULong64(key)));
s.concat("\n");
}
const void appendPreferenceBool(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(preferences->getBool(key) ? "true" : "false");
s.concat("\n");
}
const void appendPreferenceString(Preferences *preferences, String& s, const char* description, const char* key)
{
s.concat(description);
s.concat(": ");
s.concat(isRedacted(key) ? redact(preferences->getString(key)) : preferences->getString(key));
s.concat("\n");
}
const void appendPreference(Preferences *preferences, String& s, const char* key)
{
if(std::find(_boolPrefs.begin(), _boolPrefs.end(), key) != _boolPrefs.end())
{
appendPreferenceBool(preferences, s, key, key);
return;
}
switch(preferences->getType(key))
{
case PT_I8:
appendPreferenceInt8(preferences, s, key, key);
break;
case PT_I16:
appendPreferenceInt16(preferences, s, key, key);
break;
case PT_I32:
appendPreferenceInt32(preferences, s, key, key);
break;
case PT_I64:
appendPreferenceInt64(preferences, s, key, key);
break;
case PT_U8:
appendPreferenceUInt8(preferences, s, key, key);
break;
case PT_U16:
appendPreferenceUInt16(preferences, s, key, key);
break;
case PT_U32:
appendPreferenceUInt32(preferences, s, key, key);
break;
case PT_U64:
appendPreferenceUInt64(preferences, s, key, key);
break;
case PT_STR:
appendPreferenceString(preferences, s, key, key);
break;
default:
appendPreferenceString(preferences, s, key, key);
break;
}
}
public:
const String preferencesToString(Preferences *preferences)
{
String s = "";
for(const auto& key : _keys)
{
appendPreference(preferences, s, key);
}
return s;
}
};

View File

@@ -12,7 +12,7 @@ NUKI Smart Lock 2.0<br>
NUKI Smart Lock 3.0<br>
NUKI Smart Lock 3.0 Pro<br>
NUKI Opener<br>
NUKI Keypad 1.0
NUKI Keypad 1.0 (2.0 not supported yet due to current [NUKI limitations](https://developer.nuki.io/t/keypad-2-not-setting-has-keypad-flag-in-config-of-nuki-lock/19539))
## Installation
@@ -124,6 +124,8 @@ NOTE: MQTT Discovery uses retained MQTT messages to store devices configurations
## Keypad control (optional)
Note: Only devices listed in the [About section](#about) are currently supported.
If a keypad is connected to the lock, keypad codes can be added, updated and removed.
This has to enabled first in the configuration portal. Check "Enabled keypad control via MQTT" and save the configuration.
After enabling keypad control, information about codes is published under "keypad/code_x", x starting from 0 up the number of configured codes.

109
RestartReason.h Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
enum class RestartReason
{
RequestedViaMqtt,
BLEBeaconWatchdog,
RestartOnDisconnectWatchdog,
RestartIntervalWatchdog,
NetworkTimeoutWatchdog,
WifiInitFailed,
ReconfigureWifi,
NetworkDeviceCriticalFailure,
ConfigurationUpdated,
RestartTimer,
OTACompleted,
OTATimeout,
OTAAborted,
OTAUnknownState,
DeviceUnpaired,
};
#define RESTART_REASON_VALID_DETECT 0xa00ab00bc00bd00d;
extern int restartReason;
extern uint64_t restartReasonValid;
inline static void restartEsp(RestartReason reason)
{
restartReason = (int)reason;
restartReasonValid = RESTART_REASON_VALID_DETECT;
ESP.restart();
}
inline static String getRestartReason()
{
uint64_t cmp = RESTART_REASON_VALID_DETECT;
if(restartReasonValid != cmp)
{
return "UnknownNoRestartRegistered";
}
switch((RestartReason)restartReason)
{
case RestartReason::RequestedViaMqtt:
return "RequestedViaMqtt";
case RestartReason::BLEBeaconWatchdog:
return "BLEBeaconWatchdog";
case RestartReason::RestartOnDisconnectWatchdog:
return "RestartOnDisconnectWatchdog";
case RestartReason::RestartIntervalWatchdog:
return "RestartIntervalWatchdog";
case RestartReason::NetworkTimeoutWatchdog:
return "NetworkTimeoutWatchdog";
case RestartReason::WifiInitFailed:
return "WifiInitFailed";
case RestartReason::ReconfigureWifi:
return "ReconfigureWifi";
case RestartReason::NetworkDeviceCriticalFailure:
return "NetworkDeviceCriticalFailure";
case RestartReason::ConfigurationUpdated:
return "ConfigurationUpdated";
case RestartReason::RestartTimer:
return "RestartTimer";
case RestartReason::OTACompleted:
return "OTACompleted";
case RestartReason::OTATimeout:
return "OTATimeout";
case RestartReason::OTAAborted:
return "OTAAborted";
case RestartReason::OTAUnknownState:
return "OTAUnknownState";
case RestartReason::DeviceUnpaired:
return "DeviceUnpaired";
default:
return "Unknown: " + restartReason;
}
}
inline static String getEspRestartReason()
{
esp_reset_reason_t reason = esp_reset_reason();
switch(reason)
{
case esp_reset_reason_t::ESP_RST_UNKNOWN:
return "ESP_RST_UNKNOWN: Reset reason can not be determined.";
case esp_reset_reason_t::ESP_RST_POWERON:
return "ESP_RST_POWERON: Reset due to power-on event.";
case esp_reset_reason_t::ESP_RST_EXT:
return "ESP_RST_EXT: Reset by external pin";
case esp_reset_reason_t::ESP_RST_SW:
return "ESP_RST_SW: Software reset via esp_restart.";
case esp_reset_reason_t::ESP_RST_PANIC:
return "ESP_RST_PANIC: Software reset due to exception/panic.";
case esp_reset_reason_t::ESP_RST_INT_WDT:
return "ESP_RST_INT_WDT: Reset (software or hardware) due to interrupt watchdog";
case esp_reset_reason_t::ESP_RST_TASK_WDT:
return "ESP_RST_TASK_WDT: Reset due to task watchdog.";
case esp_reset_reason_t::ESP_RST_WDT:
return "ESP_RST_WDT: Reset due to other watchdogs.";
case esp_reset_reason_t::ESP_RST_DEEPSLEEP:
return "ESP_RST_DEEPSLEEP: Reset after exiting deep sleep mode.";
case esp_reset_reason_t::ESP_RST_BROWNOUT:
return "ESP_RST_BROWNOUT: Brownout reset (software or hardware)";
case esp_reset_reason_t::ESP_RST_SDIO:
return "ESP_RST_SDIO: Reset over SDIO.";
default:
return "Unknown: " + (int)reason;
}
}

View File

@@ -4,6 +4,7 @@
#include "hardware/WifiEthServer.h"
#include "Logger.h"
#include "Config.h"
#include "RestartReason.h"
#include <esp_task_wdt.h>
WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal)
@@ -119,8 +120,8 @@ void WebCfgServer::initialize()
return _server.requestAuthentication();
}
String message = "";
bool restartEsp = processArgs(message);
if(restartEsp)
bool restart = processArgs(message);
if(restart)
{
String response = "";
buildConfirmHtml(response, message);
@@ -128,7 +129,7 @@ void WebCfgServer::initialize()
Log->println(F("Restarting"));
waitAndProcess(true, 1000);
ESP.restart();
restartEsp(RestartReason::ConfigurationUpdated);
}
else
{
@@ -159,13 +160,43 @@ void WebCfgServer::initialize()
handleOtaUpload();
});
_server.on("/info", [&]() {
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
return _server.requestAuthentication();
}
String response = "";
buildInfoHtml(response);
_server.send(200, "text/html", response);
});
_server.on("/debugon", [&]() {
_preferences->putBool(preference_publish_debug_info, true);
String response = "";
buildConfirmHtml(response, "OK");
_server.send(200, "text/html", response);
Log->println(F("Restarting"));
waitAndProcess(true, 1000);
restartEsp(RestartReason::ConfigurationUpdated);
});
_server.on("/debugoff", [&]() {
_preferences->putBool(preference_publish_debug_info, false);
String response = "";
buildConfirmHtml(response, "OK");
_server.send(200, "text/html", response);
Log->println(F("Restarting"));
waitAndProcess(true, 1000);
restartEsp(RestartReason::ConfigurationUpdated);
});
_server.begin();
_network->setKeepAliveCallback([&]()
{
update();
});
{
update();
});
}
bool WebCfgServer::processArgs(String& message)
@@ -301,6 +332,11 @@ bool WebCfgServer::processArgs(String& message)
_preferences->putInt(preference_query_interval_lockstate, value.toInt());
configChanged = true;
}
else if(key == "CFGINT")
{
_preferences->putInt(preference_query_interval_configuration, value.toInt());
configChanged = true;
}
else if(key == "BATINT")
{
_preferences->putInt(preference_query_interval_battery, value.toInt());
@@ -445,7 +481,7 @@ void WebCfgServer::update()
{
Log->println(F("OTA time out, restarting"));
delay(200);
ESP.restart();
restartEsp(RestartReason::OTATimeout);
}
if(!_enabled) return;
@@ -479,7 +515,7 @@ void WebCfgServer::buildHtml(String& response)
printParameter(response, "NUKI Opener paired", _nukiOpener->isPaired() ? ("Yes (BLE Address " + _nukiOpener->getBleAddress().toString() + ")").c_str() : "No");
printParameter(response, "NUKI Opener state", lockstateArr);
}
printParameter(response, "Firmware", version.c_str());
printParameter(response, "Firmware", version.c_str(), "/info");
response.concat("</table><br><br>");
response.concat("<h3>MQTT and Network Configuration</h3>");
@@ -511,8 +547,8 @@ void WebCfgServer::buildCredHtml(String &response)
response.concat("<FORM ACTION=method=get >");
response.concat("<h3>Credentials</h3>");
response.concat("<table>");
printInputField(response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30);
printInputField(response, "CREDPASS", "Password (max 30 characters)", "*", 30, true);
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);
response.concat("</table>");
response.concat("<br><INPUT TYPE=SUBMIT NAME=\"submit\" VALUE=\"Save\">");
@@ -602,16 +638,16 @@ void WebCfgServer::buildMqttConfigHtml(String &response)
printInputField(response, "HOSTNAME", "Host name", _preferences->getString(preference_hostname).c_str(), 100);
printInputField(response, "MQTTSERVER", "MQTT Broker", _preferences->getString(preference_mqtt_broker).c_str(), 100);
printInputField(response, "MQTTPORT", "MQTT Broker port", _preferences->getInt(preference_mqtt_broker_port), 5);
printInputField(response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30);
printInputField(response, "MQTTPASS", "MQTT Password", "*", 30, true);
printInputField(response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, false, true);
printInputField(response, "MQTTPASS", "MQTT Password", "*", 30, true, true);
response.concat("</table><br>");
response.concat("<h3>Advanced MQTT and Network Configuration</h3>");
response.concat("<table>");
printInputField(response, "HASSDISCOVERY", "Home Assistant discovery topic (empty to disable; usually homeassistant)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30);
printTextarea(response, "MQTTCA", "MQTT SSL CA Certificate (*, optional)", _preferences->getString(preference_mqtt_ca).c_str(), TLS_CA_MAX_SIZE, _network->encryptionSupported());
printTextarea(response, "MQTTCRT", "MQTT SSL Client Certificate (*, optional)", _preferences->getString(preference_mqtt_crt).c_str(), TLS_CERT_MAX_SIZE, _network->encryptionSupported());
printTextarea(response, "MQTTKEY", "MQTT SSL Client Key (*, optional)", _preferences->getString(preference_mqtt_key).c_str(), TLS_KEY_MAX_SIZE, _network->encryptionSupported());
printTextarea(response, "MQTTCA", "MQTT SSL CA Certificate (*, optional)", _preferences->getString(preference_mqtt_ca).c_str(), TLS_CA_MAX_SIZE, _network->encryptionSupported(), true);
printTextarea(response, "MQTTCRT", "MQTT SSL Client Certificate (*, optional)", _preferences->getString(preference_mqtt_crt).c_str(), TLS_CERT_MAX_SIZE, _network->encryptionSupported(), true);
printTextarea(response, "MQTTKEY", "MQTT SSL Client Key (*, optional)", _preferences->getString(preference_mqtt_key).c_str(), TLS_KEY_MAX_SIZE, _network->encryptionSupported(), true);
printDropDown(response, "NWHW", "Network hardware", String(_preferences->getInt(preference_network_hardware)), getNetworkDetectionOptions());
printDropDown(response, "NWHWDT", "Network hardware detection", String(_preferences->getInt(preference_network_hardware_gpio)), getNetworkGpioOptions());
printInputField(response, "RSSI", "RSSI Publish interval (seconds; -1 to disable)", _preferences->getInt(preference_rssi_publish_interval), 6);
@@ -652,8 +688,9 @@ void WebCfgServer::buildNukiConfigHtml(String &response)
printCheckBox(response, "REGAPP", "Register as app (on: register as app, off: register as bridge; needs re-pairing if changed)", _preferences->getBool(preference_register_as_app));
printInputField(response, "LSTINT", "Query interval lock state (seconds)", _preferences->getInt(preference_query_interval_lockstate), 10);
printInputField(response, "CFGINT", "Query interval configuration (seconds)", _preferences->getInt(preference_query_interval_configuration), 10);
printInputField(response, "BATINT", "Query interval battery (seconds)", _preferences->getInt(preference_query_interval_battery), 10);
if(_nuki != nullptr && _nuki->hasKeypad())
if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad()))
{
printInputField(response, "KPINT", "Query interval keypad (seconds)", _preferences->getInt(preference_query_interval_keypad), 10);
printCheckBox(response, "KPENA", "Enabled keypad control via MQTT", _preferences->getBool(preference_keypad_control_enabled));
@@ -698,6 +735,60 @@ void WebCfgServer::buildConfigureWifiHtml(String &response)
response.concat("</BODY></HTML>");
}
void WebCfgServer::buildInfoHtml(String &response)
{
DebugPreferences debugPreferences;
buildHtmlHeader(response);
response.concat("<h3>System Information</h3> <pre>");
response.concat("Firmware version: ");
response.concat(NUKI_HUB_VERSION);
response.concat("\n");
response.concat(debugPreferences.preferencesToString(_preferences));
response.concat("MQTT connected: ");
response.concat(_network->mqttConnectionState() > 0 ? "Yes\n" : "No\n");
if(_nuki != nullptr)
{
response.concat("Lock paired: ");
response.concat(_nuki->isPaired() ? "Yes\n" : "No\n");
response.concat("Lock PIN set: ");
response.concat(_nuki->isPaired() ? _nuki->isPinSet() ? "Yes\n" : "No\n" : "-\n");
}
if(_nukiOpener != nullptr)
{
response.concat("Opener paired: ");
response.concat(_nukiOpener->isPaired() ? "Yes\n" : "No\n");
response.concat("Opener PIN set: ");
response.concat(_nukiOpener->isPaired() ? _nukiOpener->isPinSet() ? "Yes\n" : "No\n" : "-\n");
}
response.concat("Network device: ");
response.concat(_network->networkDeviceName());
response.concat("\n");
response.concat("Uptime: ");
response.concat(millis() / 1000 / 60);
response.concat(" minutes\n");
response.concat("Heap: ");
response.concat(esp_get_free_heap_size());
response.concat("\n");
response.concat("Restart reason FW: ");
response.concat(getRestartReason());
response.concat("\n");
response.concat("Restart reason ESP: ");
response.concat(getEspRestartReason());
response.concat("\n");
response.concat("</pre> </BODY></HTML>");
}
void WebCfgServer::processUnpair(bool opener)
{
String response = "";
@@ -733,7 +824,7 @@ void WebCfgServer::processUnpair(bool opener)
_nukiOpener->unpair();
}
waitAndProcess(false, 1000);
ESP.restart();
restartEsp(RestartReason::DeviceUnpaired);
}
void WebCfgServer::buildHtmlHeader(String &response)
@@ -751,8 +842,9 @@ void WebCfgServer::printInputField(String& response,
const char *token,
const char *description,
const char *value,
const size_t maxLength,
const bool isPassword)
const size_t& maxLength,
const bool& isPassword,
const bool& showLengthRestriction)
{
char maxLengthStr[20];
@@ -760,6 +852,14 @@ void WebCfgServer::printInputField(String& response,
response.concat("<tr><td>");
response.concat(description);
if(showLengthRestriction)
{
response.concat(" (Max. ");
response.concat(maxLength);
response.concat(" characters)");
}
response.concat("</td><td>");
response.concat("<INPUT TYPE=");
response.concat(isPassword ? "PASSWORD" : "TEXT");
@@ -806,8 +906,9 @@ void WebCfgServer::printTextarea(String& response,
const char *token,
const char *description,
const char *value,
const size_t maxLength,
const bool enabled)
const size_t& maxLength,
const bool& enabled,
const bool& showLengthRestriction)
{
char maxLengthStr[20];
@@ -815,6 +916,12 @@ void WebCfgServer::printTextarea(String& response,
response.concat("<tr><td>");
response.concat(description);
if(showLengthRestriction)
{
response.concat(" (Max. ");
response.concat(maxLength);
response.concat(" characters)");
}
response.concat("</td><td>");
response.concat(" <TEXTAREA ");
if(!enabled)
@@ -872,14 +979,25 @@ void WebCfgServer::buildNavigationButton(String &response, const char *caption,
response.concat("</form>");
}
void WebCfgServer::printParameter(String& response, const char *description, const char *value)
void WebCfgServer::printParameter(String& response, const char *description, const char *value, const char *link)
{
response.concat("<tr>");
response.concat("<td>");
response.concat(description);
response.concat("</td>");
response.concat("<td>");
response.concat(value);
if(strcmp(link, "") == 0)
{
response.concat(value);
}
else
{
response.concat("<a href=\"");
response.concat(link);
response.concat("\"> ");
response.concat(value);
response.concat("</a>");
}
response.concat("</td>");
response.concat("</tr>");
@@ -928,23 +1046,41 @@ void WebCfgServer::handleOtaUpload()
return;
}
if (upload.status == UPLOAD_FILE_START) {
if (upload.status == UPLOAD_FILE_START)
{
String filename = upload.filename;
if (!filename.startsWith("/")) {
if (!filename.startsWith("/"))
{
filename = "/" + filename;
}
_otaStartTs = millis();
esp_task_wdt_init(30, false);
_network->disableAutoRestarts();
Log->print("handleFileUpload Name: "); Log->println(filename);
} else if (upload.status == UPLOAD_FILE_WRITE) {
}
else if (upload.status == UPLOAD_FILE_WRITE)
{
_transferredSize = _transferredSize + upload.currentSize;
Log->println(_transferredSize);
_ota.updateFirmware(upload.buf, upload.currentSize);
} else if (upload.status == UPLOAD_FILE_END) {
} else if (upload.status == UPLOAD_FILE_END)
{
Log->println();
Log->print("handleFileUpload Size: "); Log->println(upload.totalSize);
}
else if(upload.status == UPLOAD_FILE_ABORTED)
{
Log->println();
Log->println("OTA aborted, restarting ESP.");
restartEsp(RestartReason::OTAAborted);
}
else
{
Log->println();
Log->print("OTA unknown state: ");
Log->println((int)upload.status);
restartEsp(RestartReason::OTAUnknownState);
}
}
void WebCfgServer::sendCss()
@@ -981,4 +1117,4 @@ const std::vector<std::pair<String, String>> WebCfgServer::getNetworkGpioOptions
}
return options;
}
}

View File

@@ -37,22 +37,23 @@ private:
void buildNukiConfigHtml(String& response);
void buildConfirmHtml(String& response, const String &message, uint32_t redirectDelay = 5);
void buildConfigureWifiHtml(String& response);
void buildInfoHtml(String& response);
void sendCss();
void sendFavicon();
void processUnpair(bool opener);
void buildHtmlHeader(String& response);
void printInputField(String& response, const char* token, const char* description, const char* value, const size_t maxLength, const bool isPassword = false);
void printInputField(String& response, const char* token, const char* description, const char* value, const size_t& maxLength, const bool& isPassword = false, const bool& showLengthRestriction = false);
void printInputField(String& response, const char* token, const char* description, const int value, size_t maxLength);
void printCheckBox(String& response, const char* token, const char* description, const bool value);
void printTextarea(String& response, const char *token, const char *description, const char *value, const size_t maxLength, const bool enabled = true);
void printTextarea(String& response, const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false);
void printDropDown(String &response, const char *token, const char *description, const String preselectedValue, std::vector<std::pair<String, String>> options);
void buildNavigationButton(String& response, const char* caption, const char* targetPath);
const std::vector<std::pair<String, String>> getNetworkDetectionOptions() const;
const std::vector<std::pair<String, String>> getNetworkGpioOptions() const;
void printParameter(String& response, const char* description, const char* value);
void printParameter(String& response, const char* description, const char* value, const char *link = "");
String generateConfirmCode();
void waitAndProcess(const bool blocking, const uint32_t duration);

View File

@@ -26,7 +26,9 @@
#include <libb64/cencode.h>
#include "WiFiClient.h"
#include "WebServer.h"
#include "FS.h"
#ifdef WEBSERVER_ENABLE_STATIC_CONTENT
#include "FS.h"
#endif
#include "detail/RequestHandlersImpl.h"
#include "mbedtls/md5.h"
@@ -258,9 +260,11 @@ void WebServer::_addRequestHandler(RequestHandler* handler) {
}
}
#ifdef WEBSERVER_ENABLE_STATIC_CONTENT
void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
}
#endif
void WebServer::handleClient() {
if (_currentStatus == HC_NONE) {

View File

@@ -90,7 +90,9 @@ public:
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
void addHandler(RequestHandler* handler);
#ifdef WEBSERVER_ENABLE_STATIC_CONTENT
void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );
#endif
void onNotFound(THandlerFunction fn); //called when handler is not assigned
void onFileUpload(THandlerFunction fn); //handle file uploads

View File

@@ -60,6 +60,7 @@ protected:
HTTPMethod _method;
};
#ifdef WEBSERVER_ENABLE_STATIC_CONTENT
class StaticRequestHandler : public RequestHandler {
public:
StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
@@ -146,6 +147,6 @@ protected:
bool _isFile;
size_t _baseUriLength;
};
#endif
#endif //REQUESTHANDLERSIMPL_H

View File

@@ -11,9 +11,15 @@ the LICENSE file.
#if defined(ARDUINO_ARCH_ESP32)
// Logging is en/disabled by Arduino framework macros
#include <esp32-hal-log.h>
#define emc_log_i(...) log_i(__VA_ARGS__)
#define emc_log_e(...) log_e(__VA_ARGS__)
#define emc_log_w(...) log_w(__VA_ARGS__)
#if defined(DEBUG_ESP_MQTT_CLIENT)
#define emc_log_i(...) log_i(__VA_ARGS__)
#define emc_log_e(...) log_e(__VA_ARGS__)
#define emc_log_w(...) log_w(__VA_ARGS__)
#else
#define emc_log_i(...)
#define emc_log_e(...)
#define emc_log_w(...)
#endif
#elif defined(ARDUINO_ARCH_ESP8266)
#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MQTT_CLIENT)
#include <Arduino.h>

View File

@@ -12,6 +12,7 @@
#include "Gpio.h"
#include "Logger.h"
#include "Config.h"
#include "RestartReason.h"
Network* network = nullptr;
NetworkLock* networkLock = nullptr;
@@ -28,6 +29,9 @@ bool lockEnabled = false;
bool openerEnabled = false;
unsigned long restartTs = (2^32) - 5 * 60000;
RTC_NOINIT_ATTR int restartReason;
RTC_NOINIT_ATTR uint64_t restartReasonValid;
void networkTask(void *pvParameters)
{
while(true)
@@ -39,7 +43,17 @@ void networkTask(void *pvParameters)
}
webCfgServer->update();
// millis() is about to overflow. Restart device to prevent problems with overflow
if(millis() > restartTs)
{
Log->println(F("Restart timer expired, restarting device."));
delay(200);
restartEsp(RestartReason::RestartTimer);
}
delay(200);
// Serial.println(uxTaskGetStackHighWaterMark(NULL));
}
}
@@ -65,14 +79,10 @@ void nukiTask(void *pvParameters)
{
nukiOpener->update();
}
}
}
void webServerTask(void *pvParameters)
{
}
void presenceDetectionTask(void *pvParameters)
{
while(true)
@@ -81,22 +91,6 @@ void presenceDetectionTask(void *pvParameters)
}
}
void checkMillisTask(void *pvParameters)
{
while(true)
{
delay(30000);
// millis() is about to overflow. Restart device to prevent problems with overflow
if(millis() > restartTs)
{
Log->println(F("Restart timer expired, restarting device."));
delay(200);
ESP.restart();
}
}
}
void setupTasks()
{
@@ -105,7 +99,6 @@ void setupTasks()
xTaskCreatePinnedToCore(networkTask, "ntw", 8192, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(nukiTask, "nuki", 4096, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(presenceDetectionTask, "prdet", 768, NULL, 5, NULL, 1);
xTaskCreatePinnedToCore(checkMillisTask, "mlchk", 1024, NULL, 1, NULL, 1);
}
uint32_t getRandomId()

View File

@@ -8,17 +8,17 @@ the LICENSE file.
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#include "ClientSyncEthernet.h"
#include "ClientSyncW5500.h"
#include <lwip/sockets.h> // socket options
namespace espMqttClientInternals {
ClientSyncEthernet::ClientSyncEthernet()
ClientSyncW5500::ClientSyncW5500()
: client() {
// empty
}
bool ClientSyncEthernet::connect(IPAddress ip, uint16_t port) {
bool ClientSyncW5500::connect(IPAddress ip, uint16_t port) {
bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool
if (ret) {
#if defined(ARDUINO_ARCH_ESP8266)
@@ -34,7 +34,7 @@ namespace espMqttClientInternals {
return ret;
}
bool ClientSyncEthernet::connect(const char* host, uint16_t port) {
bool ClientSyncW5500::connect(const char* host, uint16_t port) {
bool ret = client.connect(host, port); // implicit conversion of return code int --> bool
if (ret) {
#if defined(ARDUINO_ARCH_ESP8266)
@@ -50,27 +50,27 @@ namespace espMqttClientInternals {
return ret;
}
size_t ClientSyncEthernet::write(const uint8_t* buf, size_t size) {
size_t ClientSyncW5500::write(const uint8_t* buf, size_t size) {
return client.write(buf, size);
}
int ClientSyncEthernet::available() {
int ClientSyncW5500::available() {
return client.available();
}
int ClientSyncEthernet::read(uint8_t* buf, size_t size) {
int ClientSyncW5500::read(uint8_t* buf, size_t size) {
return client.read(buf, size);
}
void ClientSyncEthernet::stop() {
void ClientSyncW5500::stop() {
client.stop();
}
bool ClientSyncEthernet::connected() {
bool ClientSyncW5500::connected() {
return client.connected();
}
bool ClientSyncEthernet::disconnected() {
bool ClientSyncW5500::disconnected() {
return !client.connected();
}

View File

@@ -7,9 +7,9 @@
namespace espMqttClientInternals {
class ClientSyncEthernet : public Transport {
class ClientSyncW5500 : public Transport {
public:
ClientSyncEthernet();
ClientSyncW5500();
bool connect(IPAddress ip, uint16_t port) override;
bool connect(const char* host, uint16_t port) override;
size_t write(const uint8_t* buf, size_t size) override;

View File

@@ -17,6 +17,8 @@ public:
: _hostname(hostname)
{}
virtual const String deviceName() const = 0;
virtual void initialize() = 0;
virtual ReconnectStatus reconnect() = 0;
virtual void reconfigure() = 0;

View File

@@ -32,6 +32,11 @@ W5500Device::W5500Device(const String &hostname, Preferences* preferences, int v
W5500Device::~W5500Device()
{}
const String W5500Device::deviceName() const
{
return "Wiznet W5500";
}
void W5500Device::initialize()
{
WiFi.mode(WIFI_OFF);
@@ -52,11 +57,10 @@ void W5500Device::initialize()
if(_preferences->getBool(preference_mqtt_log_enabled))
{
_path = new char[200];
memset(_path, 0, sizeof(_path));
String pathStr = _preferences->getString(preference_mqtt_lock_path);
pathStr.concat(mqtt_topic_log);
_path = new char[pathStr.length() + 1];
memset(_path, 0, sizeof(_path));
strcpy(_path, pathStr.c_str());
Log = new MqttLogger(this, _path, MqttLoggerMode::MqttAndSerial);
}

View File

@@ -2,7 +2,7 @@
#include "NetworkDevice.h"
#include "espMqttClient.h"
#include "espMqttClientEthernet.h"
#include "espMqttClientW5500.h"
#include <Ethernet.h>
#include <Preferences.h>
@@ -18,6 +18,8 @@ public:
explicit W5500Device(const String& hostname, Preferences* _preferences, int variant);
~W5500Device();
const String deviceName() const override;
virtual void initialize();
virtual ReconnectStatus reconnect();
virtual void reconfigure();
@@ -61,7 +63,7 @@ private:
void resetDevice();
void initializeMacAddress(byte* mac);
espMqttClientEthernet _mqttClient;
espMqttClientW5500 _mqttClient;
Preferences* _preferences = nullptr;
int _maintainResult = 0;

View File

@@ -4,6 +4,7 @@
#include "../Logger.h"
#include "../MqttTopics.h"
#include "espMqttClient.h"
#include "../RestartReason.h"
RTC_NOINIT_ATTR char WiFiDevice_reconfdetect[17];
@@ -52,6 +53,11 @@ WifiDevice::WifiDevice(const String& hostname, Preferences* _preferences)
}
}
const String WifiDevice::deviceName() const
{
return "Built-in Wifi";
}
void WifiDevice::initialize()
{
std::vector<const char *> wm_menu;
@@ -80,7 +86,7 @@ void WifiDevice::initialize()
if(!res) {
Log->println(F("Failed to connect. Wait for ESP restart."));
delay(1000);
ESP.restart();
restartEsp(RestartReason::WifiInitFailed);
}
else {
Log->print(F("WiFi connected: "));
@@ -103,7 +109,7 @@ void WifiDevice::reconfigure()
{
strcpy(WiFiDevice_reconfdetect, "reconfigure_wifi");
delay(200);
ESP.restart();
restartEsp(RestartReason::ReconfigureWifi);
}
void WifiDevice::printError()
@@ -143,7 +149,7 @@ void WifiDevice::onDisconnected()
{
if(millis() > 60000)
{
ESP.restart();
restartEsp(RestartReason::RestartOnDisconnectWatchdog);
}
}

View File

@@ -12,6 +12,8 @@ class WifiDevice : public NetworkDevice
public:
WifiDevice(const String& hostname, Preferences* _preferences);
const String deviceName() const override;
virtual void initialize();
virtual void reconfigure();
virtual ReconnectStatus reconnect();

View File

@@ -1,8 +0,0 @@
#include "espMqttClientEthernet.h"
espMqttClientEthernet::espMqttClientEthernet(uint8_t priority, uint8_t core)
: MqttClientSetup(true, priority, core),
_client()
{
_transport = &_client;
}

View File

@@ -0,0 +1,8 @@
#include "espMqttClientW5500.h"
espMqttClientW5500::espMqttClientW5500(uint8_t priority, uint8_t core)
: MqttClientSetup(true, priority, core),
_client()
{
_transport = &_client;
}

View File

@@ -1,19 +1,19 @@
#pragma once
#include "MqttClientSetup.h"
#include "ClientSyncEthernet.h"
#include "ClientSyncW5500.h"
class espMqttClientEthernet : public MqttClientSetup<espMqttClientEthernet> {
class espMqttClientW5500 : public MqttClientSetup<espMqttClientW5500> {
public:
#if defined(ARDUINO_ARCH_ESP32)
explicit espMqttClientEthernet(uint8_t priority = 1, uint8_t core = 1);
explicit espMqttClientW5500(uint8_t priority = 1, uint8_t core = 1);
#else
espMqttClient();
#endif
protected:
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
espMqttClientInternals::ClientSyncEthernet _client;
espMqttClientInternals::ClientSyncW5500 _client;
#elif defined(__linux__)
espMqttClientInternals::ClientPosix _client;
#endif

Binary file not shown.