merge master

This commit is contained in:
technyon
2023-03-24 18:39:41 +01:00
19 changed files with 251 additions and 160 deletions

View File

@@ -43,6 +43,7 @@ include_directories(${PROJECT_NAME}
set(SRCFILES set(SRCFILES
Pins.h Pins.h
Config.h Config.h
CharBuffer.cpp
Network.cpp Network.cpp
MqttReceiver.h MqttReceiver.h
NetworkLock.cpp NetworkLock.cpp

13
CharBuffer.cpp Normal file
View File

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

13
CharBuffer.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#define CHAR_BUFFER_SIZE 4096
class CharBuffer
{
public:
static void initialize();
static char* get();
private:
static char* _buffer;
};

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#define NUKI_HUB_VERSION "8.18-lilygo-1" #define NUKI_HUB_VERSION "8.19-pre-3"
#define MQTT_QOS_LEVEL 1 #define MQTT_QOS_LEVEL 1
#define MQTT_CLEAN_SESSIONS false #define MQTT_CLEAN_SESSIONS false

View File

@@ -1,6 +0,0 @@
#pragma once
#define NUKI_HUB_VERSION "8.13"
#define MQTT_QOS_LEVEL 1
#define MQTT_CLEAN_SESSIONS false

View File

@@ -36,10 +36,11 @@
#define mqtt_topic_config_auto_lock "/configuration/autoLock" #define mqtt_topic_config_auto_lock "/configuration/autoLock"
#define mqtt_topic_config_single_lock "/configuration/singleLock" #define mqtt_topic_config_single_lock "/configuration/singleLock"
#define mqtt_topic_config_sound_level "/configuration/soundLevel" #define mqtt_topic_config_sound_level "/configuration/soundLevel"
#define mqtt_topic_config_last_action_authorization "/configuration/lastActionAuthorizaton"
#define mqtt_topic_info_hardware_version "/info/hardwareVersion" #define mqtt_topic_info_hardware_version "/info/hardwareVersion"
#define mqtt_topic_info_firmware_version "/info/firmwareVersion" #define mqtt_topic_info_firmware_version "/info/firmwareVersion"
#define mqtt_topic_info_nuki_hub_version "/info/nukiHubVersion"
#define mqtt_topic_keypad "/keypad" #define mqtt_topic_keypad "/keypad"
#define mqtt_topic_keypad_command_action "/keypad/command/action" #define mqtt_topic_keypad_command_action "/keypad/command/action"
@@ -57,4 +58,5 @@
#define mqtt_topic_log "/maintenance/log" #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_fw "/maintenance/restartReasonNukiHub"
#define mqtt_topic_restart_reason_esp "/maintenance/restartReasonNukiEsp" #define mqtt_topic_restart_reason_esp "/maintenance/restartReasonNukiEsp"
#define mqtt_topic_network_device "/maintenance/networkDevice"

View File

@@ -11,11 +11,14 @@
Network* Network::_inst = nullptr; Network* Network::_inst = nullptr;
unsigned long Network::_ignoreSubscriptionsTs = 0; unsigned long Network::_ignoreSubscriptionsTs = 0;
bool _versionPublished = false;
RTC_NOINIT_ATTR char WiFi_fallbackDetect[14]; RTC_NOINIT_ATTR char WiFi_fallbackDetect[14];
Network::Network(Preferences *preferences, const String& maintenancePathPrefix) Network::Network(Preferences *preferences, const String& maintenancePathPrefix, char* buffer, size_t bufferSize)
: _preferences(preferences) : _preferences(preferences),
_buffer(buffer),
_bufferSize(bufferSize)
{ {
_inst = this; _inst = this;
_hostname = _preferences->getString(preference_hostname); _hostname = _preferences->getString(preference_hostname);
@@ -296,6 +299,10 @@ bool Network::update()
publishString(_maintenancePathPrefix, mqtt_topic_restart_reason_fw, getRestartReason().c_str()); publishString(_maintenancePathPrefix, mqtt_topic_restart_reason_fw, getRestartReason().c_str());
publishString(_maintenancePathPrefix, mqtt_topic_restart_reason_esp, getEspRestartReason().c_str()); publishString(_maintenancePathPrefix, mqtt_topic_restart_reason_esp, getEspRestartReason().c_str());
} }
if (!_versionPublished) {
publishString(_maintenancePathPrefix, mqtt_topic_info_nuki_hub_version, NUKI_HUB_VERSION);
_versionPublished = true;
}
_lastMaintenanceTs = ts; _lastMaintenanceTs = ts;
} }
@@ -403,6 +410,7 @@ bool Network::reconnect()
if(_firstConnect) if(_firstConnect)
{ {
_firstConnect = false; _firstConnect = false;
publishString(_maintenancePathPrefix, mqtt_topic_network_device, _device->deviceName().c_str());
for(const auto& it : _initTopics) for(const auto& it : _initTopics)
{ {
_device->mqttPublish(it.first.c_str(), MQTT_QOS_LEVEL, true, it.second.c_str()); _device->mqttPublish(it.first.c_str(), MQTT_QOS_LEVEL, true, it.second.c_str());
@@ -585,7 +593,6 @@ void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* n
if (discoveryTopic != "") if (discoveryTopic != "")
{ {
char* jsonOut = new char[JSON_BUFFER_SIZE];
DynamicJsonDocument json(JSON_BUFFER_SIZE); DynamicJsonDocument json(JSON_BUFFER_SIZE);
auto dev = json.createNestedObject("dev"); auto dev = json.createNestedObject("dev");
@@ -606,16 +613,14 @@ void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* n
json["stat_unlocked"] = unlockedState; json["stat_unlocked"] = unlockedState;
json["opt"] = "false"; json["opt"] = "false";
serializeJson(json, reinterpret_cast<char(&)[JSON_BUFFER_SIZE]>(*jsonOut)); serializeJson(json, _buffer, _bufferSize);
String path = discoveryTopic; String path = discoveryTopic;
path.concat("/lock/"); path.concat("/lock/");
path.concat(uidString); path.concat(uidString);
path.concat("/smartlock/config"); path.concat("/smartlock/config");
_device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, jsonOut); _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer);
delete jsonOut;
// Battery critical // Battery critical
publishHassTopic("binary_sensor", publishHassTopic("binary_sensor",
@@ -724,6 +729,23 @@ void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* n
{ { "enabled_by_default", "true" }, { { "enabled_by_default", "true" },
{"ic", "mdi:counter"}}); {"ic", "mdi:counter"}});
// NUKI Hub version
publishHassTopic("sensor",
"nuki_hub_version",
uidString,
"_nuki_hub__version",
"NUKI Hub version",
name,
baseTopic,
mqtt_topic_info_nuki_hub_version,
deviceType,
"",
"",
"diagnostic",
"",
{ { "enabled_by_default", "true" },
{"ic", "mdi:counter"}});
// LED enabled // LED enabled
publishHassTopic("switch", publishHassTopic("switch",
"led_enabled", "led_enabled",
@@ -863,7 +885,7 @@ void Network::publishHASSConfigLedBrightness(char *deviceType, const char *baseT
void Network::publishHASSConfigSoundLevel(char *deviceType, const char *baseTopic, char *name, char *uidString) void Network::publishHASSConfigSoundLevel(char *deviceType, const char *baseTopic, char *name, char *uidString)
{ {
publishHassTopic("number", publishHassTopic("sensor",
"sound_level", "sound_level",
uidString, uidString,
"_sound_level", "_sound_level",
@@ -881,6 +903,45 @@ void Network::publishHASSConfigSoundLevel(char *deviceType, const char *baseTopi
{ "max", "255" }}); { "max", "255" }});
} }
void Network::publishHASSConfigAccessLog(char *deviceType, const char *baseTopic, char *name, char *uidString)
{
publishHassTopic("sensor",
"last_action_authorization",
uidString,
"_last_action_authorization",
"Last action authorization",
name,
baseTopic,
mqtt_topic_lock_log,
deviceType,
"",
"",
"diagnostic",
"",
{ { "ic", "mdi:format-list-bulleted" },
{ "value_template", "{{ (value_json|selectattr('type', 'eq', 'LockAction')|selectattr('action', 'in', ['Lock', 'Unlock', 'Unlatch'])|first).authorizationName }}" }});
}
void Network::publishHASSConfigKeypadAttemptInfo(char *deviceType, const char *baseTopic, char *name, char *uidString)
{
publishHassTopic("sensor",
"keypad_status",
uidString,
"_keypad_stats",
"Keypad status",
name,
baseTopic,
mqtt_topic_lock_log,
deviceType,
"",
"",
"diagnostic",
"",
{ { "ic", "mdi:drag-vertical" },
{ "value_template", "{{ (value_json|selectattr('type', 'eq', 'KeypadAction')|first).completionStatus }}" }});
}
void Network::publishHASSWifiRssiConfig(char *deviceType, const char *baseTopic, char *name, char *uidString) void Network::publishHASSWifiRssiConfig(char *deviceType, const char *baseTopic, char *name, char *uidString)
{ {
if(_device->signalStrength() == 127) if(_device->signalStrength() == 127)
@@ -952,8 +1013,7 @@ void Network::publishHassTopic(const String& mqttDeviceType,
if (discoveryTopic != "") if (discoveryTopic != "")
{ {
char *jsonOut = new char[JSON_BUFFER_SIZE]; DynamicJsonDocument json(_bufferSize);
DynamicJsonDocument json(JSON_BUFFER_SIZE);
// Battery level // Battery level
json.clear(); json.clear();
@@ -990,7 +1050,7 @@ void Network::publishHassTopic(const String& mqttDeviceType,
json[entry.first] = entry.second; json[entry.first] = entry.second;
} }
serializeJson(json, reinterpret_cast<char (&)[JSON_BUFFER_SIZE]>(*jsonOut)); serializeJson(json, _buffer, _bufferSize);
String path = discoveryTopic; String path = discoveryTopic;
path.concat("/"); path.concat("/");
@@ -1001,9 +1061,7 @@ void Network::publishHassTopic(const String& mqttDeviceType,
path.concat(mattDeviceName); path.concat(mattDeviceName);
path.concat("/config"); path.concat("/config");
_device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, jsonOut); _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer);
delete jsonOut;
} }
} }
@@ -1090,9 +1148,9 @@ void Network::removeHASSConfig(char* uidString)
} }
} }
void Network::removeHASSConfigDoorSensor(char *deviceType, const char *baseTopic, char *name, char *uidString) void Network::removeHASSConfigTopic(char *deviceType, char *name, char *uidString)
{ {
removeHassTopic("binary_sensor", "door_sensor", uidString); removeHassTopic(deviceType, name, uidString);
} }
void Network::publishPresenceDetection(char *csv) void Network::publishPresenceDetection(char *csv)

View File

@@ -22,7 +22,7 @@ enum class NetworkDeviceType
class Network class Network
{ {
public: public:
explicit Network(Preferences* preferences, const String& maintenancePathPrefix); explicit Network(Preferences* preferences, const String& maintenancePathPrefix, char* buffer, size_t bufferSize);
void initialize(); void initialize();
bool update(); bool update();
@@ -47,10 +47,12 @@ public:
void publishHASSConfigRingDetect(char* deviceType, const char* baseTopic, char* name, char* uidString); void publishHASSConfigRingDetect(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigLedBrightness(char* deviceType, const char* baseTopic, char* name, char* uidString); void publishHASSConfigLedBrightness(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigSoundLevel(char* deviceType, const char* baseTopic, char* name, char* uidString); void publishHASSConfigSoundLevel(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigAccessLog(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigKeypadAttemptInfo(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSWifiRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString); void publishHASSWifiRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSBleRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString); void publishHASSBleRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString);
void removeHASSConfig(char* uidString); void removeHASSConfig(char* uidString);
void removeHASSConfigDoorSensor(char* deviceType, const char* baseTopic, char* name, char* uidString); void removeHASSConfigTopic(char* deviceType, char* name, char* uidString);
void clearWifiFallback(); void clearWifiFallback();
@@ -126,6 +128,10 @@ private:
bool _mqttEnabled = true; bool _mqttEnabled = true;
static unsigned long _ignoreSubscriptionsTs; static unsigned long _ignoreSubscriptionsTs;
long _rssiPublishInterval = 0; long _rssiPublishInterval = 0;
char* _buffer;
const size_t _bufferSize;
std::function<void()> _keepAliveCallback = nullptr; std::function<void()> _keepAliveCallback = nullptr;
std::vector<std::function<void()>> _reconnectedCallbacks; std::vector<std::function<void()>> _reconnectedCallbacks;

View File

@@ -5,10 +5,13 @@
#include "PreferencesKeys.h" #include "PreferencesKeys.h"
#include "Logger.h" #include "Logger.h"
#include "RestartReason.h" #include "RestartReason.h"
#include <ArduinoJson.h>
NetworkLock::NetworkLock(Network* network, Preferences* preferences) NetworkLock::NetworkLock(Network* network, Preferences* preferences, char* buffer, size_t bufferSize)
: _network(network), : _network(network),
_preferences(preferences) _preferences(preferences),
_buffer(buffer),
_bufferSize(bufferSize)
{ {
_configTopics.reserve(5); _configTopics.reserve(5);
_configTopics.push_back(mqtt_topic_config_button_enabled); _configTopics.push_back(mqtt_topic_config_button_enabled);
@@ -273,7 +276,6 @@ void NetworkLock::publishBinaryState(NukiLock::LockState lockState)
} }
} }
void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>& logEntries) void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>& logEntries)
{ {
char str[50]; char str[50];
@@ -283,7 +285,7 @@ void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>&
char authName[33]; char authName[33];
memset(authName, 0, sizeof(authName)); memset(authName, 0, sizeof(authName));
String json = "[\n"; DynamicJsonDocument json(_bufferSize);
for(const auto& log : logEntries) for(const auto& log : logEntries)
{ {
@@ -294,90 +296,75 @@ void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>&
memcpy(authName, log.name, sizeof(log.name)); memcpy(authName, log.name, sizeof(log.name));
} }
json.concat("{\n"); auto entry = json.add();
json.concat("\"index\": "); json.concat(log.index); json.concat(",\n"); entry["index"] = log.index;
json.concat("\"authorizationId\": "); json.concat(log.authId); json.concat(",\n"); entry["authorizationId"] = log.authId;
entry["authorizationName"] = log.name;
memset(str, 0, sizeof(str)); entry["timeYear"] = log.timeStampYear;
memcpy(str, log.name, sizeof(log.name)); entry["timeMonth"] = log.timeStampMonth;
json.concat("\"authorizationName\": \""); json.concat(str); json.concat("\",\n"); entry["timeDay"] = log.timeStampDay;
entry["timeHour"] = log.timeStampHour;
json.concat("\"timeYear\": "); json.concat(log.timeStampYear); json.concat(",\n"); entry["timeMinute"] = log.timeStampMinute;
json.concat("\"timeMonth\": "); json.concat(log.timeStampMonth); json.concat(",\n"); entry["timeSecond"] = log.timeStampSecond;
json.concat("\"timeDay\": "); json.concat(log.timeStampDay); json.concat(",\n");
json.concat("\"timeHour\": "); json.concat(log.timeStampHour); json.concat(",\n");
json.concat("\"timeMinute\": "); json.concat(log.timeStampMinute); json.concat(",\n");
json.concat("\"timeSecond\": "); json.concat(log.timeStampSecond); json.concat(",\n");
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
loggingTypeToString(log.loggingType, str); loggingTypeToString(log.loggingType, str);
json.concat("\"type\": \""); json.concat(str); json.concat("\",\n"); entry["type"] = str;
switch(log.loggingType) switch(log.loggingType)
{ {
case NukiLock::LoggingType::LockAction: case NukiLock::LoggingType::LockAction:
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str); NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
json.concat("\"action\": \""); json.concat(str); json.concat("\",\n"); entry["action"] = str;
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
NukiLock::triggerToString((NukiLock::Trigger)log.data[1], str); NukiLock::triggerToString((NukiLock::Trigger)log.data[1], str);
json.concat("\"trigger\": \""); json.concat(str); json.concat("\",\n"); entry["trigger"] = str;
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[3], str); NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[3], str);
json.concat("\"completionStatus\": \""); json.concat(str); json.concat("\"\n"); entry["completionStatus"] = str;
break; break;
case NukiLock::LoggingType::KeypadAction: case NukiLock::LoggingType::KeypadAction:
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str); NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
json.concat("\"action\": \""); json.concat(str); json.concat("\",\n"); entry["action"] = str;
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str); NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
json.concat("\"completionStatus\": \""); json.concat(str); json.concat("\"\n"); entry["completionStatus"] = str;
break; break;
case NukiLock::LoggingType::DoorSensor: case NukiLock::LoggingType::DoorSensor:
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str); NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
json.concat("\"action\": \"");
switch(log.data[0]) switch(log.data[0])
{ {
case 0: case 0:
json.concat("DoorOpened"); entry["action"] = "DoorOpened";
break; break;
case 1: case 1:
json.concat("DoorClosed"); entry["action"] = "DoorClosed";
break; break;
case 2: case 2:
json.concat("SensorJammed"); entry["action"] = "SensorJammed";
break; break;
default: default:
json.concat("Unknown"); entry["action"] = "Unknown";
break; break;
} }
json.concat("\",\n");
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str); NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
json.concat("\"completionStatus\": \""); json.concat(str); json.concat("\"\n"); entry["completionStatus"] = str;
break; break;
} }
json.concat("}");
if(&log == &logEntries.back())
{
json.concat("\n");
}
else
{
json.concat(",\n");
}
} }
json.concat("]"); serializeJson(json, _buffer, _bufferSize);
publishString(mqtt_topic_lock_log, json); publishString(mqtt_topic_lock_log, _buffer);
if(authFound) if(authFound)
{ {
@@ -515,7 +502,7 @@ bool NetworkLock::comparePrefixedPath(const char *fullPath, const char *subPath)
return strcmp(fullPath, prefixedPath) == 0; return strcmp(fullPath, prefixedPath) == 0;
} }
void NetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const bool& hasDoorSensor, const bool& hasKeypad, char *lockAction, void NetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction,
char *unlockAction, char *openAction, char *lockedState, char *unlockedState) char *unlockAction, char *openAction, char *lockedState, char *unlockedState)
{ {
_network->publishHASSConfig(deviceType, baseTopic, name, uidString, hasKeypad, lockAction, unlockAction, openAction, lockedState, unlockedState); _network->publishHASSConfig(deviceType, baseTopic, name, uidString, hasKeypad, lockAction, unlockAction, openAction, lockedState, unlockedState);
@@ -527,10 +514,28 @@ void NetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, cha
} }
else else
{ {
_network->removeHASSConfigDoorSensor(deviceType, baseTopic, name, uidString); _network->removeHASSConfigTopic("binary_sensor", "door_sensor", uidString);
} }
_network->publishHASSWifiRssiConfig(deviceType, baseTopic, name, uidString); _network->publishHASSWifiRssiConfig(deviceType, baseTopic, name, uidString);
_network->publishHASSBleRssiConfig(deviceType, baseTopic, name, uidString); _network->publishHASSBleRssiConfig(deviceType, baseTopic, name, uidString);
if(publishAuthData)
{
_network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString);
}
else
{
_network->removeHASSConfigTopic("sensor", "last_action_authorization", uidString);
}
if(hasKeypad)
{
_network->publishHASSConfigKeypadAttemptInfo(deviceType, baseTopic, name, uidString);
}
else
{
_network->removeHASSConfigTopic("sensor", "keypad_status", uidString);
}
} }
void NetworkLock::removeHASSConfig(char *uidString) void NetworkLock::removeHASSConfig(char *uidString)

View File

@@ -11,10 +11,12 @@
#include "Network.h" #include "Network.h"
#include "QueryCommand.h" #include "QueryCommand.h"
#define LOCK_LOG_JSON_BUFFER_SIZE 2048
class NetworkLock : public MqttReceiver class NetworkLock : public MqttReceiver
{ {
public: public:
explicit NetworkLock(Network* network, Preferences* preferences); explicit NetworkLock(Network* network, Preferences* preferences, char* buffer, size_t bufferSize);
virtual ~NetworkLock(); virtual ~NetworkLock();
void initialize(); void initialize();
@@ -31,7 +33,7 @@ public:
void publishRssi(const int& rssi); void publishRssi(const int& rssi);
void publishRetry(const std::string& message); void publishRetry(const std::string& message);
void publishBleAddress(const std::string& address); void publishBleAddress(const std::string& address);
void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const bool& hasDoorSensor, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState); void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState);
void removeHASSConfig(char* uidString); void removeHASSConfig(char* uidString);
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount); void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
void publishKeypadCommandResult(const char* result); void publishKeypadCommandResult(const char* result);
@@ -79,6 +81,9 @@ private:
int _keypadCommandEnabled = 1; int _keypadCommandEnabled = 1;
uint8_t _queryCommands = 0; uint8_t _queryCommands = 0;
char* _buffer;
size_t _bufferSize;
bool (*_lockActionReceivedCallback)(const char* value) = nullptr; bool (*_lockActionReceivedCallback)(const char* value) = nullptr;
void (*_configUpdateReceivedCallback)(const char* path, 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; void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;

View File

@@ -4,10 +4,13 @@
#include "PreferencesKeys.h" #include "PreferencesKeys.h"
#include "Logger.h" #include "Logger.h"
#include "Config.h" #include "Config.h"
#include <ArduinoJson.h>
NetworkOpener::NetworkOpener(Network* network, Preferences* preferences) NetworkOpener::NetworkOpener(Network* network, Preferences* preferences, char* buffer, size_t bufferSize)
: _preferences(preferences), : _preferences(preferences),
_network(network) _network(network),
_buffer(buffer),
_bufferSize(bufferSize)
{ {
_configTopics.reserve(5); _configTopics.reserve(5);
_configTopics.push_back(mqtt_topic_config_button_enabled); _configTopics.push_back(mqtt_topic_config_button_enabled);
@@ -268,130 +271,111 @@ void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntr
char authName[33]; char authName[33];
memset(authName, 0, sizeof(authName)); memset(authName, 0, sizeof(authName));
String json = "[\n"; DynamicJsonDocument json(_bufferSize);
for(const auto& log : logEntries) for(const auto& log : logEntries)
{ {
if((log.loggingType == NukiOpener::LoggingType::LockAction || log.loggingType == NukiOpener::LoggingType::KeypadAction || log.loggingType == NukiOpener::LoggingType::DoorbellRecognition) && ! authFound) if((log.loggingType == NukiOpener::LoggingType::LockAction || log.loggingType == NukiOpener::LoggingType::KeypadAction) && ! authFound)
{ {
authFound = true; authFound = true;
authId = log.authId; authId = log.authId;
memcpy(authName, log.name, sizeof(log.name)); memcpy(authName, log.name, sizeof(log.name));
} }
json.concat("{\n"); auto entry = json.add();
json.concat("\"index\": "); json.concat(log.index); json.concat(",\n"); entry["index"] = log.index;
json.concat("\"authorizationId\": "); json.concat(log.authId); json.concat(",\n"); entry["authorizationId"] = log.authId;
entry["authorizationName"] = log.name;
memset(str, 0, sizeof(str)); entry["timeYear"] = log.timeStampYear;
memcpy(str, log.name, sizeof(log.name)); entry["timeMonth"] = log.timeStampMonth;
json.concat("\"authorizationName\": \""); json.concat(str); json.concat("\",\n"); entry["timeDay"] = log.timeStampDay;
entry["timeHour"] = log.timeStampHour;
json.concat("\"timeYear\": "); json.concat(log.timeStampYear); json.concat(",\n"); entry["timeMinute"] = log.timeStampMinute;
json.concat("\"timeMonth\": "); json.concat(log.timeStampMonth); json.concat(",\n"); entry["timeSecond"] = log.timeStampSecond;
json.concat("\"timeDay\": "); json.concat(log.timeStampDay); json.concat(",\n");
json.concat("\"timeHour\": "); json.concat(log.timeStampHour); json.concat(",\n");
json.concat("\"timeMinute\": "); json.concat(log.timeStampMinute); json.concat(",\n");
json.concat("\"timeSecond\": "); json.concat(log.timeStampSecond); json.concat(",\n");
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
loggingTypeToString(log.loggingType, str); loggingTypeToString(log.loggingType, str);
json.concat("\"type\": \""); json.concat(str); json.concat("\",\n"); entry["type"] = str;
switch(log.loggingType) switch(log.loggingType)
{ {
case NukiOpener::LoggingType::LockAction: case NukiOpener::LoggingType::LockAction:
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
lockactionToString((NukiOpener::LockAction)log.data[0], str); NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
json.concat("\"action\": \""); json.concat(str); json.concat("\",\n"); entry["action"] = str;
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
triggerToString((NukiOpener::Trigger)log.data[1], str); NukiLock::triggerToString((NukiLock::Trigger)log.data[1], str);
json.concat("\"trigger\": \""); json.concat(str); json.concat("\",\n"); entry["trigger"] = str;
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
logactionCompletionStatusToString(log.data[3], str); NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[3], str);
json.concat("\"completionStatus\": \""); json.concat(str); json.concat("\"\n"); entry["completionStatus"] = str;
break; break;
case NukiOpener::LoggingType::KeypadAction: case NukiOpener::LoggingType::KeypadAction:
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
lockactionToString((NukiOpener::LockAction)log.data[0], str); NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
json.concat("\"action\": \""); json.concat(str); json.concat("\",\n"); entry["action"] = str;
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
completionStatusToString((NukiOpener::CompletionStatus)log.data[2], str); NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
json.concat("\"completionStatus\": \""); json.concat(str); json.concat("\"\n"); entry["completionStatus"] = str;
break; break;
case NukiOpener::LoggingType::DoorbellRecognition: case NukiOpener::LoggingType::DoorbellRecognition:
json.concat("\"mode\": \"");
switch(log.data[0] & 3) switch(log.data[0] & 3)
{ {
case 0: case 0:
json.concat("None"); entry["mode"] = "None";
break; break;
case 1: case 1:
json.concat("RTO"); entry["mode"] = "RTO";
break; break;
case 2: case 2:
json.concat("CM"); entry["mode"] = "CM";
break; break;
default: default:
json.concat("Unknown"); entry["mode"] = "Unknown";
break; break;
} }
json.concat("\",\n");
json.concat("\"source\": \"");
switch(log.data[1]) switch(log.data[1])
{ {
case 0: case 0:
json.concat("Doorbell"); entry["source"] = "Doorbell";
break; break;
case 1: case 1:
json.concat("Timecontrol"); entry["source"] = "Timecontrol";
break; break;
case 2: case 2:
json.concat("App"); entry["source"] = "App";
break; break;
case 3: case 3:
json.concat("Button"); entry["source"] = "Button";
break; break;
case 4: case 4:
json.concat("Fob"); entry["source"] = "Fob";
break; break;
case 5: case 5:
json.concat("Bridge"); entry["source"] = "Bridge";
break; break;
case 6: case 6:
json.concat("Keypad"); entry["source"] = "Keypad";
break; break;
} default:
json.concat("\",\n"); entry["source"] = "Unknown";
break; }
json.concat("\"geofence\": \""); json.concat(log.data[2] == 1 ? "active" : "inactive"); json.concat("\",\n"); entry["geofence"] = log.data[2] == 1 ? "active" : "inactive";
json.concat("\"doorbellSuppression\": \""); json.concat(log.data[3] == 1 ? "active" : "inactive"); json.concat("\",\n"); entry["doorbellSuppression"] = log.data[3] == 1 ? "active" : "inactive";
entry["completionStatus"] = str;
memset(str, 0, sizeof(str));
logactionCompletionStatusToString(log.data[5], str);
json.concat("\"completionStatus\": \""); json.concat(str); json.concat("\"\n");
break; break;
} }
json.concat("}");
if(&log == &logEntries.back())
{
json.concat("\n");
}
else
{
json.concat(",\n");
}
} }
json.concat("]"); serializeJson(json, _buffer, _bufferSize);
publishString(mqtt_topic_lock_log, json); publishString(mqtt_topic_lock_log, _buffer);
if(authFound) if(authFound)
{ {

View File

@@ -12,7 +12,7 @@
class NetworkOpener : public MqttReceiver class NetworkOpener : public MqttReceiver
{ {
public: public:
explicit NetworkOpener(Network* network, Preferences* preferences); explicit NetworkOpener(Network* network, Preferences* preferences, char* buffer, size_t bufferSize);
virtual ~NetworkOpener() = default; virtual ~NetworkOpener() = default;
void initialize(); void initialize();
@@ -83,6 +83,9 @@ private:
unsigned long _resetLockStateTs = 0; unsigned long _resetLockStateTs = 0;
uint8_t _queryCommands = 0; uint8_t _queryCommands = 0;
char* _buffer;
const size_t _bufferSize;
bool (*_lockActionReceivedCallback)(const char* value) = nullptr; bool (*_lockActionReceivedCallback)(const char* value) = nullptr;
void (*_configUpdateReceivedCallback)(const char* path, 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; void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;

View File

@@ -646,7 +646,7 @@ void NukiOpenerWrapper::setupHASS()
String baseTopic = _preferences->getString(preference_mqtt_opener_path); String baseTopic = _preferences->getString(preference_mqtt_opener_path);
char uidString[20]; char uidString[20];
itoa(_nukiConfig.nukiId, uidString, 16); itoa(_nukiConfig.nukiId, uidString, 16);
_network->publishHASSConfig("Opener",baseTopic.c_str(),(char*)_nukiConfig.name,uidString,"deactivateRTO","activateRTO","electricStrikeActuation","locked","unlocked"); _network->publishHASSConfig("Opener",baseTopic.c_str(),(char*)_nukiConfig.name,uidString, "deactivateRTO","activateRTO","electricStrikeActuation","locked","unlocked");
_hassSetupCompleted = true; _hassSetupCompleted = true;
Log->println("HASS setup for opener completed."); Log->println("HASS setup for opener completed.");

View File

@@ -661,7 +661,7 @@ void NukiWrapper::setupHASS()
char uidString[20]; char uidString[20];
itoa(_nukiConfig.nukiId, uidString, 16); itoa(_nukiConfig.nukiId, uidString, 16);
_network->publishHASSConfig("SmartLock", baseTopic.c_str(),(char*)_nukiConfig.name, uidString, hasDoorSensor(), _hasKeypad, "lock", "unlock", "unlatch", "locked", "unlocked"); _network->publishHASSConfig("SmartLock", baseTopic.c_str(),(char*)_nukiConfig.name, uidString, hasDoorSensor(), _hasKeypad, _publishAuthData,"lock", "unlock", "unlatch", "locked", "unlocked");
_hassSetupCompleted = true; _hassSetupCompleted = true;
Log->println("HASS setup for lock completed."); Log->println("HASS setup for lock completed.");

View File

@@ -1,14 +1,15 @@
#include "PresenceDetection.h" #include "PresenceDetection.h"
#include "PreferencesKeys.h" #include "PreferencesKeys.h"
#include "Logger.h" #include "Logger.h"
#include "CharBuffer.h"
PresenceDetection::PresenceDetection(Preferences* preferences, BleScanner::Scanner *bleScanner, Network* network) PresenceDetection::PresenceDetection(Preferences* preferences, BleScanner::Scanner *bleScanner, Network* network, char* buffer, size_t bufferSize)
: _preferences(preferences), : _preferences(preferences),
_bleScanner(bleScanner), _bleScanner(bleScanner),
_network(network) _network(network),
_csv(buffer),
_bufferSize(bufferSize)
{ {
_csv = new char[presence_detection_buffer_size];
_timeout = _preferences->getInt(preference_presence_detection_timeout) * 1000; _timeout = _preferences->getInt(preference_presence_detection_timeout) * 1000;
if(_timeout == 0) if(_timeout == 0)
{ {
@@ -41,7 +42,7 @@ void PresenceDetection::update()
delay(3000); delay(3000);
if(_timeout < 0) return; if(_timeout < 0) return;
memset(_csv, 0, presence_detection_buffer_size); memset(_csv, 0, _bufferSize);
if(_devices.size() == 0) if(_devices.size() == 0)
{ {
@@ -60,7 +61,7 @@ void PresenceDetection::update()
} }
// Prevent csv buffer overflow // Prevent csv buffer overflow
if(_csvIndex > presence_detection_buffer_size - (sizeof(it.second.name) + sizeof(it.second.address) + 10)) if(_csvIndex > _bufferSize - (sizeof(it.second.name) + sizeof(it.second.address) + 10))
{ {
break; break;
} }

View File

@@ -13,12 +13,10 @@ struct PdDevice
bool hasRssi = false; bool hasRssi = false;
}; };
#define presence_detection_buffer_size 4096
class PresenceDetection : public BleScanner::Subscriber class PresenceDetection : public BleScanner::Subscriber
{ {
public: public:
PresenceDetection(Preferences* preferences, BleScanner::Scanner* bleScanner, Network* network); PresenceDetection(Preferences* preferences, BleScanner::Scanner* bleScanner, Network* network, char* buffer, size_t bufferSize);
virtual ~PresenceDetection(); virtual ~PresenceDetection();
void initialize(); void initialize();
@@ -33,6 +31,7 @@ private:
BleScanner::Scanner* _bleScanner; BleScanner::Scanner* _bleScanner;
Network* _network; Network* _network;
char* _csv = {0}; char* _csv = {0};
size_t _bufferSize = 0;
std::map<long long, PdDevice> _devices; std::map<long long, PdDevice> _devices;
int _timeout = 20000; int _timeout = 20000;
int _csvIndex = 0; int _csvIndex = 0;

View File

@@ -51,7 +51,7 @@ Note: It is possible to run NUKI Hub alongside a NUKI Bridge. This is not recomm
## Support ## Support
If you haven't ordered your NUKI product yet, you can support me by using my referrer code when placing your order:<br> If you haven't ordered your NUKI product yet, you can support me by using my referrer code when placing your order:<br>
REFPGVT4XJ5JW<br> REFXQ847A4ZDG<br>
This will also give you a 30€ discount for your order. This will also give you a 30€ discount for your order.
This project is free to use for everyone. However if you feel like donating, you can buy me a coffee at ko-fi.com: This project is free to use for everyone. However if you feel like donating, you can buy me a coffee at ko-fi.com:
@@ -187,8 +187,11 @@ The Pin configuration is:<br>
## Connecting via LAN (Optional) ## Connecting via LAN (Optional)
If you prefer to connect to the MQTT Broker via LAN instead of WiFi, you can use a Wiznet W5x00 Module (W5100, W5200, W5500 are supported). If you prefer to connect to the MQTT Broker via LAN instead of WiFi, you either use one of the supported ESP32 modules (see about section above),
To connect, just wire the module and connect the LAN cable: or wire a seperate Wiznet W5x00 Module (W5100, W5200, W5500 are supported). To use a supported module, flash the firmware, connect via Wifi and
select the correct network hardware in the MQTT and network settings section.<br>
To wire an external W5x00 module to the ESP, use this wiring scheme:<br>
- Connect W5x00 to ESP32 SPI0:<br> - Connect W5x00 to ESP32 SPI0:<br>
W5x00 SCK to GPIO18<br> W5x00 SCK to GPIO18<br>
@@ -199,10 +202,10 @@ W5x00 CS/SS to GPIO5
W5x00 reset to GPIO33 W5x00 reset to GPIO33
- Last but not least, on the ESP32 bridge GPIO26 and GND. This let's the firmware know that a LAN Module is connected - Last but not least, on the ESP32 bridge GPIO26 and GND. This let's the firmware know that a LAN Module is connected
Wifi is now disabled, and the module doesn't boot into WifiManager anymore. Wifi is now disabled, and the module doesn't boot into WifiManager anymore.<br>
Note: Encrypted MQTT is only available for Wifi and LAN8720 modules, W5x00 modules don't support encryption
As an alternative to do the wiring yourself, you can use an [M5Stack Atom POE](https://docs.m5stack.com/en/atom/atom_poe). (that leaves Olimex, WT32-ETH01 and M5Stack PoESP32 Unit if encryption is desired). If encryption is needed, Olimex
After flashing, go to MQTT and network settings and select the module under "Network hardware". is the easiest option, since it has USB for flashing onboard.
## Troubleshooting ## Troubleshooting

View File

@@ -13,6 +13,7 @@
#include "Logger.h" #include "Logger.h"
#include "Config.h" #include "Config.h"
#include "RestartReason.h" #include "RestartReason.h"
#include "CharBuffer.h"
Network* network = nullptr; Network* network = nullptr;
NetworkLock* networkLock = nullptr; NetworkLock* networkLock = nullptr;
@@ -170,6 +171,8 @@ void setup()
bool firstStart = initPreferences(); bool firstStart = initPreferences();
initializeRestartReason(); initializeRestartReason();
CharBuffer::initialize();
if(preferences->getInt(preference_restart_timer) > 0) if(preferences->getInt(preference_restart_timer) > 0)
{ {
restartTs = preferences->getInt(preference_restart_timer) * 60 * 1000; restartTs = preferences->getInt(preference_restart_timer) * 60 * 1000;
@@ -179,15 +182,15 @@ void setup()
openerEnabled = preferences->getBool(preference_opener_enabled); openerEnabled = preferences->getBool(preference_opener_enabled);
const String mqttLockPath = preferences->getString(preference_mqtt_lock_path); const String mqttLockPath = preferences->getString(preference_mqtt_lock_path);
network = new Network(preferences, mqttLockPath); network = new Network(preferences, mqttLockPath, CharBuffer::get(), CHAR_BUFFER_SIZE);
network->initialize(); network->initialize();
networkLock = new NetworkLock(network, preferences); networkLock = new NetworkLock(network, preferences, CharBuffer::get(), CHAR_BUFFER_SIZE);
networkLock->initialize(); networkLock->initialize();
if(openerEnabled) if(openerEnabled)
{ {
networkOpener = new NetworkOpener(network, preferences); networkOpener = new NetworkOpener(network, preferences, CharBuffer::get(), CHAR_BUFFER_SIZE);
networkOpener->initialize(); networkOpener->initialize();
} }
@@ -226,7 +229,7 @@ void setup()
webCfgServer = new WebCfgServer(nuki, nukiOpener, network, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi); webCfgServer = new WebCfgServer(nuki, nukiOpener, network, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi);
webCfgServer->initialize(); webCfgServer->initialize();
presenceDetection = new PresenceDetection(preferences, bleScanner, network); presenceDetection = new PresenceDetection(preferences, bleScanner, network, CharBuffer::get(), CHAR_BUFFER_SIZE);
presenceDetection->initialize(); presenceDetection->initialize();
setupTasks(); setupTasks();

View File

@@ -115,6 +115,7 @@ ReconnectStatus W5500Device::reconnect()
} }
else else
{ {
hardwareFound = true;
_hasDHCPAddress = true; _hasDHCPAddress = true;
dhcpRetryCnt = 1000; dhcpRetryCnt = 1000;
if(_ipConfiguration->dhcpEnabled()) if(_ipConfiguration->dhcpEnabled())