From fa856dc656df3cad33f8b79fde7d2b4159a91892 Mon Sep 17 00:00:00 2001 From: technyon Date: Wed, 10 Aug 2022 22:12:23 +0200 Subject: [PATCH 01/13] publish keypad code info --- MqttTopics.h | 4 +++- NetworkLock.cpp | 41 ++++++++++++++++++++++++++++++++++++++++- NetworkLock.h | 4 ++++ NetworkOpener.cpp | 2 +- NukiWrapper.cpp | 26 ++++++++++++++++++++++++++ NukiWrapper.h | 4 ++++ PreferencesKeys.h | 1 + 7 files changed, 79 insertions(+), 3 deletions(-) diff --git a/MqttTopics.h b/MqttTopics.h index 04c089a..04ba9a5 100644 --- a/MqttTopics.h +++ b/MqttTopics.h @@ -15,7 +15,7 @@ #define mqtt_topic_lock_auth_name "/lock/authorizationName" #define mqtt_topic_lock_completionStatus "/lock/completionStatus" #define mqtt_topic_lock_action_command_result "/lock/commandResult" -#define mqtt_topic_door_sensor_state "/lock/doorSensorState" +#define mqtt_topic_lock_door_sensor_state "/lock/doorSensorState" #define mqtt_topic_lock_action "/lock/action" #define mqtt_topic_config_button_enabled "/configuration/buttonEnabled" @@ -26,6 +26,8 @@ #define mqtt_topic_config_single_lock "/configuration/singleLock" #define mqtt_topic_config_sound_level "/configuration/soundLevel" +#define mqtt_topic_keypad "/keypad" + #define mqtt_topic_presence "/presence/devices" #define mqtt_topic_reset "/maintenance/reset" diff --git a/NetworkLock.cpp b/NetworkLock.cpp index 24acd4a..c8e09b7 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -144,7 +144,7 @@ void NetworkLock::publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurne { memset(&str, 0, sizeof(str)); NukiLock::doorSensorStateToString(keyTurnerState.doorSensorState, str); - publishString(mqtt_topic_door_sensor_state, str); + publishString(mqtt_topic_lock_door_sensor_state, str); } if(_firstTunerStatePublish || keyTurnerState.criticalBatteryState != lastKeyTurnerState.criticalBatteryState) @@ -216,6 +216,38 @@ void NetworkLock::publishAdvancedConfig(const NukiLock::AdvancedConfig &config) publishBool(mqtt_topic_config_auto_lock, config.autoLockEnabled == 1); } +void NetworkLock::publishKeypad(const std::list& 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()); + + char codeName[sizeof(entry.name) + 1]; + memset(codeName, 0, sizeof(codeName)); + memcpy(codeName, entry.name, sizeof(entry.name)); + + publishInt(concat(basePath, "/id").c_str(), entry.codeId); + publishBool(concat(basePath, "/enabled").c_str(), entry.enabled); + publishString(concat(basePath, "/name").c_str(), codeName); + publishInt(concat(basePath, "/createdYear").c_str(), entry.dateCreatedYear); + publishInt(concat(basePath, "/createdMonth").c_str(), entry.dateCreatedMonth); + publishInt(concat(basePath, "/createdDay").c_str(), entry.dateCreatedDay); + publishInt(concat(basePath, "/createdHour").c_str(), entry.dateCreatedHour); + publishInt(concat(basePath, "/createdMin").c_str(), entry.dateCreatedMin); + publishInt(concat(basePath, "/createdSec").c_str(), entry.dateCreatedSec); + publishInt(concat(basePath, "/lockCount").c_str(), entry.lockCount); + + ++index; + } + while(index < maxKeypadCodeCount) + { + ++index; + } +} + void NetworkLock::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; @@ -298,3 +330,10 @@ void NetworkLock::publishULong(const char *topic, const unsigned long value) return _network->publishULong(_mqttPath, topic, value); } +String NetworkLock::concat(String a, String b) +{ + String c = a; + c.concat(b); + return c; +} + diff --git a/NetworkLock.h b/NetworkLock.h index 45b3234..1b22706 100644 --- a/NetworkLock.h +++ b/NetworkLock.h @@ -6,6 +6,7 @@ #include "networkDevices/W5500Device.h" #include #include +#include #include "NukiConstants.h" #include "NukiLockConstants.h" #include "Network.h" @@ -26,6 +27,7 @@ public: void publishBatteryReport(const NukiLock::BatteryReport& batteryReport); void publishConfig(const NukiLock::Config& config); void publishAdvancedConfig(const NukiLock::AdvancedConfig& config); + void publishKeypad(const std::list& entries, uint maxKeypadCodeCount); 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); @@ -43,6 +45,8 @@ private: bool publishString(const char* topic, const char* value); bool comparePrefixedPath(const char* fullPath, const char* subPath); + String concat(String a, String b); + void buildMqttPath(const char* path, char* outPath); Network* _network; diff --git a/NetworkOpener.cpp b/NetworkOpener.cpp index 3b2dab9..09de990 100644 --- a/NetworkOpener.cpp +++ b/NetworkOpener.cpp @@ -113,7 +113,7 @@ void NetworkOpener::publishKeyTurnerState(const NukiOpener::OpenerState& keyTurn { memset(&str, 0, sizeof(str)); NukiLock::doorSensorStateToString(keyTurnerState.doorSensorState, str); - publishString(mqtt_topic_door_sensor_state, str); + publishString(mqtt_topic_lock_door_sensor_state, str); } if(_firstTunerStatePublish || keyTurnerState.criticalBatteryState != lastKeyTurnerState.criticalBatteryState) diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 6e6b378..219a026 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -41,6 +41,7 @@ void NukiWrapper::initialize() _intervalLockstate = _preferences->getInt(preference_query_interval_lockstate); _intervalBattery = _preferences->getInt(preference_query_interval_battery); _publishAuthData = _preferences->getBool(preference_publish_authdata); + _maxKeypadCodeCount = _preferences->getUInt(preference_max_keypad_code_count); if(_intervalLockstate == 0) { @@ -105,6 +106,11 @@ void NukiWrapper::update() _nextConfigUpdateTs = ts + _intervalConfig * 1000; updateConfig(); } + if(_hasKeypad && _nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs) + { + _nextKeypadUpdateTs = ts + 60 * 60 * 1000; + updateKeypad(); + } if(_nextLockAction != (NukiLock::LockAction)0xff) { @@ -189,6 +195,7 @@ void NukiWrapper::updateConfig() { readConfig(); readAdvancedConfig(); + _hasKeypad = _nukiConfig.hasKeypad > 0; _network->publishConfig(_nukiConfig); _network->publishAdvancedConfig(_nukiAdvancedConfig); } @@ -231,6 +238,25 @@ void NukiWrapper::updateAuthData() } } +void NukiWrapper::updateKeypad() +{ + Nuki::CmdResult result = _nukiLock.retrieveKeypadEntries(0, 0xffff); + if(result == 1) + { + std::list entries; + _nukiLock.getKeypadEntries(&entries); + + uint keypadCount = entries.size(); + if(keypadCount > _maxKeypadCodeCount) + { + _maxKeypadCodeCount = keypadCount; + _preferences->putUInt(preference_max_keypad_code_count, _maxKeypadCodeCount); + } + + _network->publishKeypad(entries, _maxKeypadCodeCount); + } +} + NukiLock::LockAction NukiWrapper::lockActionToEnum(const char *str) { if(strcmp(str, "unlock") == 0) return NukiLock::LockAction::Unlock; diff --git a/NukiWrapper.h b/NukiWrapper.h index 6e809df..785d24d 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -38,6 +38,7 @@ private: void updateBatteryState(); void updateConfig(); void updateAuthData(); + void updateKeypad(); void readConfig(); void readAdvancedConfig(); @@ -72,9 +73,12 @@ private: bool _paired = false; bool _statusUpdated = false; + bool _hasKeypad = false; + uint _maxKeypadCodeCount = 0; unsigned long _nextLockStateUpdateTs = 0; unsigned long _nextBatteryReportTs = 0; unsigned long _nextConfigUpdateTs = 0; + unsigned long _nextKeypadUpdateTs = 0; unsigned long _nextPairTs = 0; NukiLock::LockAction _nextLockAction = (NukiLock::LockAction)0xff; }; diff --git a/PreferencesKeys.h b/PreferencesKeys.h index c220810..3b72102 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -10,6 +10,7 @@ #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_mqtt_ca "mqttca" #define preference_mqtt_crt "mqttcrt" #define preference_mqtt_key "mqttkey" From da1a2ebcfe96c19a6a730e11430b556fb52ae931 Mon Sep 17 00:00:00 2001 From: technyon Date: Wed, 10 Aug 2022 22:20:41 +0200 Subject: [PATCH 02/13] clear mqtt nodes for deleted keypad codes --- NetworkLock.cpp | 43 ++++++++++++++++++++++++++++--------------- NetworkLock.h | 1 + 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/NetworkLock.cpp b/NetworkLock.cpp index c8e09b7..0277618 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -224,26 +224,19 @@ void NetworkLock::publishKeypad(const std::list& entries, String basePath = mqtt_topic_keypad; basePath.concat("/code_"); basePath.concat(std::to_string(index).c_str()); - - char codeName[sizeof(entry.name) + 1]; - memset(codeName, 0, sizeof(codeName)); - memcpy(codeName, entry.name, sizeof(entry.name)); - - publishInt(concat(basePath, "/id").c_str(), entry.codeId); - publishBool(concat(basePath, "/enabled").c_str(), entry.enabled); - publishString(concat(basePath, "/name").c_str(), codeName); - publishInt(concat(basePath, "/createdYear").c_str(), entry.dateCreatedYear); - publishInt(concat(basePath, "/createdMonth").c_str(), entry.dateCreatedMonth); - publishInt(concat(basePath, "/createdDay").c_str(), entry.dateCreatedDay); - publishInt(concat(basePath, "/createdHour").c_str(), entry.dateCreatedHour); - publishInt(concat(basePath, "/createdMin").c_str(), entry.dateCreatedMin); - publishInt(concat(basePath, "/createdSec").c_str(), entry.dateCreatedSec); - publishInt(concat(basePath, "/lockCount").c_str(), entry.lockCount); + 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; } } @@ -325,6 +318,26 @@ bool NetworkLock::publishString(const char *topic, const char *value) return _network->publishString(_mqttPath, topic, value); } +void NetworkLock::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 NetworkLock::publishULong(const char *topic, const unsigned long value) { return _network->publishULong(_mqttPath, topic, value); diff --git a/NetworkLock.h b/NetworkLock.h index 1b22706..2c87bfb 100644 --- a/NetworkLock.h +++ b/NetworkLock.h @@ -43,6 +43,7 @@ private: void publishULong(const char* topic, const unsigned long value); void publishBool(const char* topic, const bool 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); From 9fed6c0ce889c3f5632b1434b70c24fdc2389222 Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 18:29:30 +0200 Subject: [PATCH 03/13] implement add and delete keypad code --- MqttTopics.h | 4 ++++ NetworkLock.cpp | 39 ++++++++++++++++++++++++++++++++++++++- NetworkLock.h | 8 +++++++- NukiWrapper.cpp | 31 ++++++++++++++++++++++++++++++- NukiWrapper.h | 2 ++ 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/MqttTopics.h b/MqttTopics.h index 04ba9a5..8917aa4 100644 --- a/MqttTopics.h +++ b/MqttTopics.h @@ -27,6 +27,10 @@ #define mqtt_topic_config_sound_level "/configuration/soundLevel" #define mqtt_topic_keypad "/keypad" +#define mqtt_topic_keypad_command_action "/keypad/command/action" +#define mqtt_topic_keypad_command_id "/keypad/command/id" +#define mqtt_topic_keypad_command_name "/keypad/command/name" +#define mqtt_topic_keypad_command_code "/keypad/command/code" #define mqtt_topic_presence "/presence/devices" diff --git a/NetworkLock.cpp b/NetworkLock.cpp index 0277618..606af6c 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -53,6 +53,15 @@ void NetworkLock::initialize() _network->subscribe(_mqttPath, mqtt_topic_reset); _network->initTopic(_mqttPath, mqtt_topic_reset, "0"); + + _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->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"); } void NetworkLock::update() @@ -98,6 +107,30 @@ void NetworkLock::onMqttDataReceived(char *&topic, byte *&payload, unsigned int publishString(mqtt_topic_lock_action, success ? "ack" : "unknown_action"); } + if(comparePrefixedPath(topic, mqtt_topic_keypad_command_action)) + { + if(_keypadCommandReceivedReceivedCallback != nullptr) + { + _keypadCommandReceivedReceivedCallback(value, _keypadCommandId, _keypadCommandName, _keypadCommandCode); + publishString(mqtt_topic_keypad_command_action, "--"); + publishInt(mqtt_topic_keypad_command_id, 0); + publishString(mqtt_topic_keypad_command_name, "--"); + publishString(mqtt_topic_keypad_command_code, "000000"); + } + } + 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; + } + for(auto configTopic : _configTopics) { if(comparePrefixedPath(topic, configTopic)) @@ -251,6 +284,11 @@ void NetworkLock::setConfigUpdateReceivedCallback(void (*configUpdateReceivedCal _configUpdateReceivedCallback = configUpdateReceivedCallback; } +void NetworkLock::setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code)) +{ + _keypadCommandReceivedReceivedCallback = keypadCommandReceivedReceivedCallback; +} + void NetworkLock::buildMqttPath(const char* path, char* outPath) { int offset = 0; @@ -349,4 +387,3 @@ String NetworkLock::concat(String a, String b) c.concat(b); return c; } - diff --git a/NetworkLock.h b/NetworkLock.h index 2c87bfb..629e302 100644 --- a/NetworkLock.h +++ b/NetworkLock.h @@ -27,12 +27,13 @@ public: void publishBatteryReport(const NukiLock::BatteryReport& batteryReport); void publishConfig(const NukiLock::Config& config); void publishAdvancedConfig(const NukiLock::AdvancedConfig& config); - void publishKeypad(const std::list& entries, uint maxKeypadCodeCount); 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& entries, uint maxKeypadCodeCount); 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)); void onMqttDataReceived(char*& topic, byte*& payload, unsigned int& length) override; @@ -60,6 +61,11 @@ private: unsigned long _lastMaintenanceTs = 0; bool _haEnabled= false; + String _keypadCommandName = ""; + String _keypadCommandCode = ""; + uint _keypadCommandId = 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) = nullptr; }; diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 219a026..392be65 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -23,6 +23,7 @@ NukiWrapper::NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner: network->setLockActionReceivedCallback(nukiInst->onLockActionReceivedCallback); network->setConfigUpdateReceivedCallback(nukiInst->onConfigUpdateReceivedCallback); + network->setKeypadCommandReceivedCallback(nukiInst->onKeypadCommandReceivedCallback); } @@ -106,7 +107,7 @@ void NukiWrapper::update() _nextConfigUpdateTs = ts + _intervalConfig * 1000; updateConfig(); } - if(_hasKeypad && _nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs) + if(_hasKeypad && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs)) { _nextKeypadUpdateTs = ts + 60 * 60 * 1000; updateKeypad(); @@ -284,6 +285,13 @@ void NukiWrapper::onConfigUpdateReceivedCallback(const char *topic, const char * } +void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uint &id, const String &name, + const String &code) +{ + nukiInst->onKeypadCommandReceived(command, id, name, code); +} + + void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) { if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) @@ -337,6 +345,27 @@ void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) } } +void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code) +{ + if(strcmp(command, "add") == 0) + { + NukiLock::NewKeypadEntry entry; + memset(&entry, 0, sizeof(entry)); + size_t nameLen = name.length(); + memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); + entry.code = code.toInt(); + const auto r = _nukiLock.addKeypadEntry(entry); + Serial.print("Add keypad code: "); Serial.println((int)r); + updateKeypad(); + } + else if(strcmp(command, "delete") == 0) + { + const auto r = _nukiLock.deleteKeypadEntry(id); + Serial.print("Delete keypad code: "); Serial.println((int)r); + updateKeypad(); + } +} + const NukiLock::KeyTurnerState &NukiWrapper::keyTurnerState() { return _keyTurnerState; diff --git a/NukiWrapper.h b/NukiWrapper.h index 785d24d..358fccc 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -32,7 +32,9 @@ 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); void onConfigUpdateReceived(const char* topic, const char* value); + void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code); void updateKeyTurnerState(); void updateBatteryState(); From ac751fd5268f46859a99847e5f70a520cf34410a Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 19:33:56 +0200 Subject: [PATCH 04/13] add keypad update command --- MqttTopics.h | 1 + NetworkLock.cpp | 23 +++++++++++++++++------ NetworkLock.h | 5 +++-- NukiWrapper.cpp | 25 +++++++++++++++++++++---- NukiWrapper.h | 4 ++-- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/MqttTopics.h b/MqttTopics.h index 8917aa4..010412e 100644 --- a/MqttTopics.h +++ b/MqttTopics.h @@ -31,6 +31,7 @@ #define mqtt_topic_keypad_command_id "/keypad/command/id" #define mqtt_topic_keypad_command_name "/keypad/command/name" #define mqtt_topic_keypad_command_code "/keypad/command/code" +#define mqtt_topic_keypad_command_enabled "/keypad/command/enabled" #define mqtt_topic_presence "/presence/devices" diff --git a/NetworkLock.cpp b/NetworkLock.cpp index 606af6c..a9a350c 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -58,10 +58,12 @@ void NetworkLock::initialize() _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"); } void NetworkLock::update() @@ -111,11 +113,17 @@ void NetworkLock::onMqttDataReceived(char *&topic, byte *&payload, unsigned int { if(_keypadCommandReceivedReceivedCallback != nullptr) { - _keypadCommandReceivedReceivedCallback(value, _keypadCommandId, _keypadCommandName, _keypadCommandCode); + _keypadCommandReceivedReceivedCallback(value, _keypadCommandId, _keypadCommandName, _keypadCommandCode, _keypadCommandEnabled); + + _keypadCommandId = 0; + _keypadCommandName = "--"; + _keypadCommandCode = "000000"; + _keypadCommandEnabled = 1; publishString(mqtt_topic_keypad_command_action, "--"); - publishInt(mqtt_topic_keypad_command_id, 0); - publishString(mqtt_topic_keypad_command_name, "--"); - publishString(mqtt_topic_keypad_command_code, "000000"); + publishInt(mqtt_topic_keypad_command_id, _keypadCommandId); + publishString(mqtt_topic_keypad_command_name, _keypadCommandName.c_str()); + publishString(mqtt_topic_keypad_command_code, _keypadCommandCode.c_str()); + publishInt(mqtt_topic_keypad_command_enabled, _keypadCommandEnabled); } } else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_id)) @@ -130,6 +138,10 @@ void NetworkLock::onMqttDataReceived(char *&topic, byte *&payload, unsigned int { _keypadCommandCode = value; } + else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_enabled)) + { + _keypadCommandEnabled = atoi(value); + } for(auto configTopic : _configTopics) { @@ -284,7 +296,7 @@ void NetworkLock::setConfigUpdateReceivedCallback(void (*configUpdateReceivedCal _configUpdateReceivedCallback = configUpdateReceivedCallback; } -void NetworkLock::setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code)) +void NetworkLock::setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled)) { _keypadCommandReceivedReceivedCallback = keypadCommandReceivedReceivedCallback; } @@ -358,7 +370,6 @@ bool NetworkLock::publishString(const char *topic, const char *value) void NetworkLock::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)); diff --git a/NetworkLock.h b/NetworkLock.h index 629e302..ff4a03f 100644 --- a/NetworkLock.h +++ b/NetworkLock.h @@ -33,7 +33,7 @@ public: 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)); + void setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled)); void onMqttDataReceived(char*& topic, byte*& payload, unsigned int& length) override; @@ -64,8 +64,9 @@ private: String _keypadCommandName = ""; String _keypadCommandCode = ""; uint _keypadCommandId = 0; + int _keypadCommandEnabled = 1; 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) = nullptr; + void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr; }; diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 392be65..7feff6b 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -285,10 +285,9 @@ void NukiWrapper::onConfigUpdateReceivedCallback(const char *topic, const char * } -void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uint &id, const String &name, - const String &code) +void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uint &id, const String &name, const String &code, const int& enabled) { - nukiInst->onKeypadCommandReceived(command, id, name, code); + nukiInst->onKeypadCommandReceived(command, id, name, code, enabled); } @@ -345,8 +344,13 @@ void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) } } -void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code) +void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code, const int& enabled) { + if(!_hasKeypad) + { + return; + } + if(strcmp(command, "add") == 0) { NukiLock::NewKeypadEntry entry; @@ -364,6 +368,19 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c Serial.print("Delete keypad code: "); Serial.println((int)r); updateKeypad(); } + if(strcmp(command, "update") == 0) + { + 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 = code.toInt(); + entry.enabled = enabled == 0 ? 0 : 1; + const auto r = _nukiLock.updateKeypadEntry(entry); + Serial.print("Update keypad code: "); Serial.println((int)r); + updateKeypad(); + } } const NukiLock::KeyTurnerState &NukiWrapper::keyTurnerState() diff --git a/NukiWrapper.h b/NukiWrapper.h index 358fccc..48addc6 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -32,9 +32,9 @@ 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); + 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); + void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled); void updateKeyTurnerState(); void updateBatteryState(); From fc794429a4387c91a9772a6780ba4443efc51181 Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 19:43:54 +0200 Subject: [PATCH 05/13] update readme --- README.md | 33 ++++++++++++++++++++++++++++++--- WebCfgServer.cpp | 2 +- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 11c2e50..35e33fd 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Just enable pairing mode on the NUKI lock (press button for a few seconds) and p ## Support If you haven't ordered your NUKI product yet, you can support me by using my referrer code when placing your order:
-REFT6MTTJMBJ6
+REFETQHSFVW33
This will also give you a 30€ discount for your order. ## MQTT Interface @@ -91,7 +91,7 @@ This will also give you a 30€ discount for your order. ## Over-the-air Update (OTA) After initially flashing the firmware via serial connection, further updates can be deployed via OTA update from a Web Browser. In the configuration portal, scroll down to "Firmware update" and click "Open". Then Click "Browse" and select the new "nuki_hub.bin" file and select "Upload file". After about a minute the new firmware should be installed. -## MQTT Encryption +## MQTT Encryption (optional; WiFi only) The communication via MQTT can be SSL encrypted. To enable SSL encryption, supply the necessary information in the MQTT Configuration page. The following configurations are supported:
@@ -100,7 +100,7 @@ CA, CERT and KEY are empty -> No encryption
CA is filled but CERT and KEY are empty -> Encrypted MQTT
CA, CERT and KEY are filled -> Encrypted MQTT with client vaildation
-## Home Assistant Discovery +## Home Assistant Discovery (optional) Home Assistant can be setup manually using the [MQTT Lock integration](https://www.home-assistant.io/integrations/lock.mqtt/). @@ -115,6 +115,33 @@ The following mapping between Home Assistant services and Nuki commands is setup NOTE: MQTT Discovery uses retained MQTT messages to store devices configurations. In order to avoid orphan configurations on your broker please disable autodiscovery first if you no longer want to use this SW. Retained messages are automatically cleared when unpairing and when changing/disabling autodiscovery topic in MQTT Configuration page. +## Keypad control (optional) + +If a keypad is connected to the lock, keypad codes can be added, updated and removed. +Information about codes is published under "keypad/code_x", x starting from 0 up the number of configured codes. +
+For security reasons, the code itself is not published. To modify keypad codes, a command +structure is setup under keypad/command: + +- keypad/command/id: The id of an existing code, found under keypad_code_x +- keypad/command/name: Display name of the code +- keypad/command/code: The actual 6-digit keypad code +- keypad/command/enabled: Set to 1 to enable the code, 0 to disable +- keypad/command/action: The action to execute. Possible values are add, delete and update + +To modify keypad codes, the first four parameter nodes have to be set depending on the command: + +- To add a code, set name, code, enabled. +- To delete a code, set id +- To update a code, set id, name, code, enabled + +After setting the necessary parameters, write the action to be executed to the command node. +For example, to add a code: +- write "John Doe" to name +- write 123456 to code +- write 1 to enabled +- write "add" to action + ## GPIO lock control (optional) The lock can be controlled via GPIO. For security reasons, this has to be enabled in diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 80e8b14..993eca6 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -557,7 +557,7 @@ void WebCfgServer::buildMqttConfigHtml(String &response) printTextarea(response, "MQTTCA", "MQTT SSL CA Certificate (*, optional)", _preferences->getString(preference_mqtt_ca).c_str(), TLS_CA_MAX_SIZE); printTextarea(response, "MQTTCRT", "MQTT SSL Client Certificate (*, optional)", _preferences->getString(preference_mqtt_crt).c_str(), TLS_CERT_MAX_SIZE); printTextarea(response, "MQTTKEY", "MQTT SSL Client Key (*, optional)", _preferences->getString(preference_mqtt_key).c_str(), TLS_KEY_MAX_SIZE); - printInputField(response, "HASSDISCOVERY", "Home Assistant discovery topic (empty to disable)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30); + printInputField(response, "HASSDISCOVERY", "Home Assistant discovery topic (empty to disable; usually homeassistant)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30); printInputField(response, "NETTIMEOUT", "Network Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5); printCheckBox(response, "RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect)); printInputField(response, "RSTTMR", "Restart timer (minutes; -1 to disable)", _preferences->getInt(preference_restart_timer), 10); From 2f2825eeedd3dc74d2d570383adc29ff684b975a Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 20:12:21 +0200 Subject: [PATCH 06/13] add config entries for keypad --- NukiWrapper.cpp | 30 ++++++++++++++++++++++++++---- NukiWrapper.h | 3 +++ PreferencesKeys.h | 2 ++ WebCfgServer.cpp | 15 +++++++++++++++ main.cpp | 1 + 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 7feff6b..5d11b5d 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -41,12 +41,14 @@ void NukiWrapper::initialize() _intervalLockstate = _preferences->getInt(preference_query_interval_lockstate); _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); if(_intervalLockstate == 0) { - _intervalLockstate = 60 * 5; + _intervalLockstate = 60 * 30; _preferences->putInt(preference_query_interval_lockstate, _intervalLockstate); } if(_intervalBattery == 0) @@ -54,6 +56,11 @@ void NukiWrapper::initialize() _intervalBattery = 60 * 30; _preferences->putInt(preference_query_interval_battery, _intervalBattery); } + if(_intervalKeypad == 0) + { + _intervalKeypad = 60 * 30; + _preferences->putInt(preference_query_interval_keypad, _intervalKeypad); + } _nukiLock.setEventHandler(this); @@ -107,9 +114,9 @@ void NukiWrapper::update() _nextConfigUpdateTs = ts + _intervalConfig * 1000; updateConfig(); } - if(_hasKeypad && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs)) + if(_hasKeypad && _keypadEnabled && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs)) { - _nextKeypadUpdateTs = ts + 60 * 60 * 1000; + _nextKeypadUpdateTs = ts + _intervalKeypad * 1000; updateKeypad(); } @@ -346,13 +353,18 @@ void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code, const int& enabled) { - if(!_hasKeypad) + if(!_hasKeypad || !_keypadEnabled) { return; } if(strcmp(command, "add") == 0) { + if(name == "") + { + return; + } + NukiLock::NewKeypadEntry entry; memset(&entry, 0, sizeof(entry)); size_t nameLen = name.length(); @@ -370,6 +382,11 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c } if(strcmp(command, "update") == 0) { + if(name == "") + { + return; + } + NukiLock::UpdatedKeypadEntry entry; memset(&entry, 0, sizeof(entry)); entry.codeId = id; @@ -393,6 +410,11 @@ const bool NukiWrapper::isPaired() return _paired; } +const bool NukiWrapper::hasKeypad() +{ + return _hasKeypad; +} + void NukiWrapper::notify(Nuki::EventType eventType) { if(eventType == Nuki::EventType::KeyTurnerStatusUpdated) diff --git a/NukiWrapper.h b/NukiWrapper.h index 48addc6..b4b7fb5 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -26,6 +26,7 @@ public: const NukiLock::KeyTurnerState& keyTurnerState(); const bool isPaired(); + const bool hasKeypad(); void notify(Nuki::EventType eventType) override; @@ -57,6 +58,7 @@ private: int _intervalLockstate = 0; // seconds int _intervalBattery = 0; // seconds int _intervalConfig = 60 * 60; // seconds + int _intervalKeypad = 0; // seconds bool _publishAuthData = false; bool _clearAuthData = false; @@ -76,6 +78,7 @@ private: bool _paired = false; bool _statusUpdated = false; bool _hasKeypad = false; + bool _keypadEnabled = false; uint _maxKeypadCodeCount = 0; unsigned long _nextLockStateUpdateTs = 0; unsigned long _nextBatteryReportTs = 0; diff --git a/PreferencesKeys.h b/PreferencesKeys.h index 3b72102..1f00e7c 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -21,6 +21,8 @@ #define preference_restart_timer "resttmr" #define preference_query_interval_lockstate "lockStInterval" #define preference_query_interval_battery "batInterval" +#define preference_query_interval_keypad "kpInterval" +#define preference_keypad_control_enabled "kpEnabled" #define preference_cred_user "crdusr" #define preference_cred_password "crdpass" #define preference_publish_authdata "pubauth" diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 993eca6..c223f98 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -286,6 +286,16 @@ bool WebCfgServer::processArgs(String& message) _preferences->putInt(preference_query_interval_battery, value.toInt()); configChanged = true; } + else if(key == "KPINT") + { + _preferences->putInt(preference_query_interval_keypad, value.toInt()); + configChanged = true; + } + else if(key == "KPENA") + { + _preferences->putBool(preference_keypad_control_enabled, (value == "1")); + configChanged = true; + } else if(key == "PRDTMO") { _preferences->putInt(preference_presence_detection_timeout, value.toInt()); @@ -589,6 +599,11 @@ void WebCfgServer::buildNukiConfigHtml(String &response) } printInputField(response, "LSTINT", "Query interval lock state (seconds)", _preferences->getInt(preference_query_interval_lockstate), 10); printInputField(response, "BATINT", "Query interval battery (seconds)", _preferences->getInt(preference_query_interval_battery), 10); + if(_nuki->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)); + } printCheckBox(response, "PUBAUTH", "Publish auth data (May reduce battery life)", _preferences->getBool(preference_publish_authdata)); printCheckBox(response, "GPLCK", "Enable control via GPIO", _preferences->getBool(preference_gpio_locking_enabled)); printInputField(response, "PRDTMO", "Presence detection timeout (seconds; -1 to disable)", _preferences->getInt(preference_presence_detection_timeout), 10); diff --git a/main.cpp b/main.cpp index 69e8d25..63b68bc 100644 --- a/main.cpp +++ b/main.cpp @@ -148,6 +148,7 @@ void initPreferences() { preferences->putBool(preference_started_befores, true); preferences->putBool(preference_lock_enabled, true); + preferences->putBool(preference_keypad_control_enabled, true); } if(preferences->getInt(preference_restart_timer) == 0) From 54d4d8b3418351a1e4ae45db5a09c3adf38ef8f2 Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 23:04:04 +0200 Subject: [PATCH 07/13] publish keypad command result --- MqttTopics.h | 1 + NetworkLock.cpp | 5 +++++ NetworkLock.h | 1 + NukiWrapper.cpp | 25 +++++++++++++++++++------ lib/nuki_ble | 2 +- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/MqttTopics.h b/MqttTopics.h index 010412e..a342aac 100644 --- a/MqttTopics.h +++ b/MqttTopics.h @@ -32,6 +32,7 @@ #define mqtt_topic_keypad_command_name "/keypad/command/name" #define mqtt_topic_keypad_command_code "/keypad/command/code" #define mqtt_topic_keypad_command_enabled "/keypad/command/enabled" +#define mqtt_topic_keypad_command_enabled "/keypad/command/commandResult" #define mqtt_topic_presence "/presence/devices" diff --git a/NetworkLock.cpp b/NetworkLock.cpp index a9a350c..c54a63e 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -286,6 +286,11 @@ void NetworkLock::publishKeypad(const std::list& entries, } } +void NetworkLock::publishKeypadCommandResult(const char* result) +{ + publishString(mqtt_topic_keypad_command_enabled, result); +} + void NetworkLock::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; diff --git a/NetworkLock.h b/NetworkLock.h index ff4a03f..0457bc0 100644 --- a/NetworkLock.h +++ b/NetworkLock.h @@ -30,6 +30,7 @@ public: 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& 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)); diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 5d11b5d..101f556 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -358,6 +358,8 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c return; } + NukiLock::CmdResult result = (NukiLock::CmdResult)-1; + if(strcmp(command, "add") == 0) { if(name == "") @@ -370,14 +372,14 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c size_t nameLen = name.length(); memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); entry.code = code.toInt(); - const auto r = _nukiLock.addKeypadEntry(entry); - Serial.print("Add keypad code: "); Serial.println((int)r); + result = _nukiLock.addKeypadEntry(entry); + Serial.print("Add keypad code: "); Serial.println((int)result); updateKeypad(); } else if(strcmp(command, "delete") == 0) { - const auto r = _nukiLock.deleteKeypadEntry(id); - Serial.print("Delete keypad code: "); Serial.println((int)r); + result = _nukiLock.deleteKeypadEntry(id); + Serial.print("Delete keypad code: "); Serial.println((int)result); updateKeypad(); } if(strcmp(command, "update") == 0) @@ -394,10 +396,21 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); entry.code = code.toInt(); entry.enabled = enabled == 0 ? 0 : 1; - const auto r = _nukiLock.updateKeypadEntry(entry); - Serial.print("Update keypad code: "); Serial.println((int)r); + result = _nukiLock.updateKeypadEntry(entry); + Serial.print("Update keypad code: "); Serial.println((int)result); updateKeypad(); } + + if((int)result != -1) + { + char resultStr[15]; + memset(&resultStr, 0, sizeof(resultStr)); + NukiLock::cmdResultToString(result, resultStr); + _network->publishKeypadCommandResult(resultStr); + } else + { + _network->publishKeypadCommandResult("UnknownCommand"); + } } const NukiLock::KeyTurnerState &NukiWrapper::keyTurnerState() diff --git a/lib/nuki_ble b/lib/nuki_ble index 822fd64..1d78e91 160000 --- a/lib/nuki_ble +++ b/lib/nuki_ble @@ -1 +1 @@ -Subproject commit 822fd645af574613d0c19a5ef38738d1052ce1ec +Subproject commit 1d78e91750ed88f5a0fb136043ad8b5573bbbccd From 0060acefce1265ab2317b01eb0909f7c423cd81f Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 23:17:03 +0200 Subject: [PATCH 08/13] add keypad command sanity checks --- MqttTopics.h | 2 +- NetworkLock.cpp | 2 +- NukiWrapper.cpp | 30 ++++++++++++++++++++++++++---- NukiWrapper.h | 2 +- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/MqttTopics.h b/MqttTopics.h index a342aac..a00474a 100644 --- a/MqttTopics.h +++ b/MqttTopics.h @@ -32,7 +32,7 @@ #define mqtt_topic_keypad_command_name "/keypad/command/name" #define mqtt_topic_keypad_command_code "/keypad/command/code" #define mqtt_topic_keypad_command_enabled "/keypad/command/enabled" -#define mqtt_topic_keypad_command_enabled "/keypad/command/commandResult" +#define mqtt_topic_keypad_command_result "/keypad/command/commandResult" #define mqtt_topic_presence "/presence/devices" diff --git a/NetworkLock.cpp b/NetworkLock.cpp index c54a63e..5fb4c55 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -288,7 +288,7 @@ void NetworkLock::publishKeypad(const std::list& entries, void NetworkLock::publishKeypadCommandResult(const char* result) { - publishString(mqtt_topic_keypad_command_enabled, result); + publishString(mqtt_topic_keypad_command_result, result); } void NetworkLock::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 101f556..30176b5 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -262,6 +262,13 @@ void NukiWrapper::updateKeypad() } _network->publishKeypad(entries, _maxKeypadCodeCount); + + _keypadCodeIds.clear(); + _keypadCodeIds.reserve(entries.size()); + for(const auto& entry : entries) + { + _keypadCodeIds.push_back(entry.codeId); + } } } @@ -358,12 +365,14 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c return; } + bool idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), id) != _keypadCodeIds.end(); NukiLock::CmdResult result = (NukiLock::CmdResult)-1; if(strcmp(command, "add") == 0) { if(name == "") { + _network->publishKeypadCommandResult("MissingParameterName"); return; } @@ -378,14 +387,25 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c } else if(strcmp(command, "delete") == 0) { + if(!idExists) + { + _network->publishKeypadCommandResult("UnknownId"); + return; + } result = _nukiLock.deleteKeypadEntry(id); Serial.print("Delete keypad code: "); Serial.println((int)result); updateKeypad(); } - if(strcmp(command, "update") == 0) + else if(strcmp(command, "update") == 0) { if(name == "") { + _network->publishKeypadCommandResult("MissingParameterName"); + return; + } + if(!idExists) + { + _network->publishKeypadCommandResult("UnknownId"); return; } @@ -400,6 +420,11 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c Serial.print("Update keypad code: "); Serial.println((int)result); updateKeypad(); } + else + { + _network->publishKeypadCommandResult("UnknownCommand"); + return; + } if((int)result != -1) { @@ -407,9 +432,6 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c memset(&resultStr, 0, sizeof(resultStr)); NukiLock::cmdResultToString(result, resultStr); _network->publishKeypadCommandResult(resultStr); - } else - { - _network->publishKeypadCommandResult("UnknownCommand"); } } diff --git a/NukiWrapper.h b/NukiWrapper.h index b4b7fb5..4e7f047 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -61,6 +61,7 @@ private: int _intervalKeypad = 0; // seconds bool _publishAuthData = false; bool _clearAuthData = false; + std::vector _keypadCodeIds; NukiLock::KeyTurnerState _lastKeyTurnerState; NukiLock::KeyTurnerState _keyTurnerState; @@ -84,6 +85,5 @@ private: unsigned long _nextBatteryReportTs = 0; unsigned long _nextConfigUpdateTs = 0; unsigned long _nextKeypadUpdateTs = 0; - unsigned long _nextPairTs = 0; NukiLock::LockAction _nextLockAction = (NukiLock::LockAction)0xff; }; From 3de080241924384e9b3f21ee360c9f746199006d Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 23:28:18 +0200 Subject: [PATCH 09/13] add more sanity checks --- NukiWrapper.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 30176b5..2539373 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -366,21 +366,27 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c } bool idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), id) != _keypadCodeIds.end(); + int codeInt = code.toInt(); NukiLock::CmdResult result = (NukiLock::CmdResult)-1; if(strcmp(command, "add") == 0) { - if(name == "") + if(name == "" || name == "--") { _network->publishKeypadCommandResult("MissingParameterName"); return; } + if(codeInt == 0) + { + _network->publishKeypadCommandResult("MissingParameterCode"); + 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 = code.toInt(); + entry.code = codeInt; result = _nukiLock.addKeypadEntry(entry); Serial.print("Add keypad code: "); Serial.println((int)result); updateKeypad(); @@ -398,11 +404,16 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c } else if(strcmp(command, "update") == 0) { - if(name == "") + if(name == "" || name == "--") { _network->publishKeypadCommandResult("MissingParameterName"); return; } + if(codeInt == 0) + { + _network->publishKeypadCommandResult("MissingParameterCode"); + return; + } if(!idExists) { _network->publishKeypadCommandResult("UnknownId"); @@ -414,7 +425,7 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c entry.codeId = id; size_t nameLen = name.length(); memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen); - entry.code = code.toInt(); + entry.code = codeInt; entry.enabled = enabled == 0 ? 0 : 1; result = _nukiLock.updateKeypadEntry(entry); Serial.print("Update keypad code: "); Serial.println((int)result); From d1b8b5377f328cab6c399bd2e4524177c4b3bcfd Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 23:38:11 +0200 Subject: [PATCH 10/13] add sanity checks for code --- NukiWrapper.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 2539373..ce3151e 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -367,6 +367,7 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c bool idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), id) != _keypadCodeIds.end(); int codeInt = code.toInt(); + bool codeValid = codeInt > 100000 && (code.indexOf('0') == -1); NukiLock::CmdResult result = (NukiLock::CmdResult)-1; if(strcmp(command, "add") == 0) @@ -381,6 +382,11 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c _network->publishKeypadCommandResult("MissingParameterCode"); return; } + if(!codeValid) + { + _network->publishKeypadCommandResult("CodeInvalid"); + return; + } NukiLock::NewKeypadEntry entry; memset(&entry, 0, sizeof(entry)); @@ -414,6 +420,11 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c _network->publishKeypadCommandResult("MissingParameterCode"); return; } + if(!codeValid) + { + _network->publishKeypadCommandResult("CodeInvalid"); + return; + } if(!idExists) { _network->publishKeypadCommandResult("UnknownId"); From 8c53e93e9d4e34b68238a4e4233092781ac8a2f5 Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 12 Aug 2022 23:50:50 +0200 Subject: [PATCH 11/13] sort keypad codes --- NukiWrapper.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index ce3151e..4ddcb3e 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -254,6 +254,8 @@ void NukiWrapper::updateKeypad() std::list entries; _nukiLock.getKeypadEntries(&entries); + entries.sort([](const NukiLock::KeypadEntry& a, const NukiLock::KeypadEntry& b) { return a.codeId < b.codeId; }); + uint keypadCount = entries.size(); if(keypadCount > _maxKeypadCodeCount) { @@ -367,7 +369,7 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c bool idExists = std::find(_keypadCodeIds.begin(), _keypadCodeIds.end(), id) != _keypadCodeIds.end(); int codeInt = code.toInt(); - bool codeValid = codeInt > 100000 && (code.indexOf('0') == -1); + bool codeValid = codeInt > 100000 && codeInt < 1000000 && (code.indexOf('0') == -1); NukiLock::CmdResult result = (NukiLock::CmdResult)-1; if(strcmp(command, "add") == 0) From 7ccaffec7aebbd8dc6547ca20554e599c57ac3dd Mon Sep 17 00:00:00 2001 From: technyon Date: Sat, 13 Aug 2022 08:49:46 +0200 Subject: [PATCH 12/13] disable keypad control by default; add more sanity checks --- NetworkLock.cpp | 23 +++++++++++++---------- NukiWrapper.cpp | 15 ++++++++++++++- NukiWrapper.h | 1 + README.md | 3 ++- main.cpp | 1 - 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/NetworkLock.cpp b/NetworkLock.cpp index 5fb4c55..74d5292 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -54,16 +54,19 @@ void NetworkLock::initialize() _network->subscribe(_mqttPath, mqtt_topic_reset); _network->initTopic(_mqttPath, mqtt_topic_reset, "0"); - _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"); + 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"); + } } void NetworkLock::update() diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 4ddcb3e..8618958 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -203,6 +203,7 @@ void NukiWrapper::updateConfig() { readConfig(); readAdvancedConfig(); + _configRead = true; _hasKeypad = _nukiConfig.hasKeypad > 0; _network->publishConfig(_nukiConfig); _network->publishAdvancedConfig(_nukiAdvancedConfig); @@ -362,7 +363,15 @@ void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code, const int& enabled) { - if(!_hasKeypad || !_keypadEnabled) + if(!_hasKeypad) + { + if(_configRead) + { + _network->publishKeypadCommandResult("KeypadNotAvailable"); + } + return; + } + if(!_keypadEnabled) { return; } @@ -444,6 +453,10 @@ void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, c Serial.print("Update keypad code: "); Serial.println((int)result); updateKeypad(); } + else if(command == "--") + { + return; + } else { _network->publishKeypadCommandResult("UnknownCommand"); diff --git a/NukiWrapper.h b/NukiWrapper.h index 4e7f047..f27e28e 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -80,6 +80,7 @@ private: bool _statusUpdated = false; bool _hasKeypad = false; bool _keypadEnabled = false; + bool _configRead = false; uint _maxKeypadCodeCount = 0; unsigned long _nextLockStateUpdateTs = 0; unsigned long _nextBatteryReportTs = 0; diff --git a/README.md b/README.md index 35e33fd..533eb3a 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,8 @@ NOTE: MQTT Discovery uses retained MQTT messages to store devices configurations ## Keypad control (optional) If a keypad is connected to the lock, keypad codes can be added, updated and removed. -Information about codes is published under "keypad/code_x", x starting from 0 up the number of configured codes. +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.
For security reasons, the code itself is not published. To modify keypad codes, a command structure is setup under keypad/command: diff --git a/main.cpp b/main.cpp index 63b68bc..69e8d25 100644 --- a/main.cpp +++ b/main.cpp @@ -148,7 +148,6 @@ void initPreferences() { preferences->putBool(preference_started_befores, true); preferences->putBool(preference_lock_enabled, true); - preferences->putBool(preference_keypad_control_enabled, true); } if(preferences->getInt(preference_restart_timer) == 0) From 3710d4ca6bfa2089046edc649290b1f420b0153e Mon Sep 17 00:00:00 2001 From: technyon Date: Sat, 13 Aug 2022 08:57:32 +0200 Subject: [PATCH 13/13] add encryption note --- Version.h | 2 +- WebCfgServer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Version.h b/Version.h index 90f8788..1efb477 100644 --- a/Version.h +++ b/Version.h @@ -1,3 +1,3 @@ #pragma once -#define nuki_hub_version "5.7" \ No newline at end of file +#define nuki_hub_version "5.8" \ No newline at end of file diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index c223f98..30d11d2 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -572,7 +572,7 @@ void WebCfgServer::buildMqttConfigHtml(String &response) printCheckBox(response, "RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect)); printInputField(response, "RSTTMR", "Restart timer (minutes; -1 to disable)", _preferences->getInt(preference_restart_timer), 10); response.concat(""); - response.concat("* If no encryption is configured for the MQTT broker, leave empty.
"); + response.concat("* If no encryption is configured for the MQTT broker, leave empty. Only supported for WiFi connections.
"); response.concat("
"); response.concat("");