From 9f68e67ce8a9f65745217e240b8f7032d12c9f5f Mon Sep 17 00:00:00 2001 From: rodriguezst <2828844+rodriguezst@users.noreply.github.com> Date: Sun, 19 Jun 2022 09:51:07 +0000 Subject: [PATCH 1/6] Initial HASS autodiscovery support --- Network.cpp | 30 ++++++++++++++++++++++++++++++ Network.h | 1 + NetworkOpener.cpp | 5 +++++ NetworkOpener.h | 1 + NukiOpenerWrapper.cpp | 21 +++++++++++++++++++++ NukiOpenerWrapper.h | 2 ++ NukiWrapper.cpp | 21 +++++++++++++++++++++ NukiWrapper.h | 2 ++ 8 files changed, 83 insertions(+) diff --git a/Network.cpp b/Network.cpp index 91fb69b..3b0dadf 100644 --- a/Network.cpp +++ b/Network.cpp @@ -343,6 +343,36 @@ void Network::publishPresenceDetection(char *csv) _presenceCsv = csv; } +void Network::publishHASSConfig(const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) +{ + String configJSON = "{\"~\": \""; + configJSON.concat(baseTopic); + configJSON.concat("\", \"name\": \""); + configJSON.concat(name); + configJSON.concat("\", \"unique_id\": \""); + configJSON.concat(uidString); + configJSON.concat("\", \"cmd_t\": \"~/lock/action\", \"pl_lock\": \""); + configJSON.concat(lockAction); + configJSON.concat("\", \"pl_unlk\": \""); + configJSON.concat(unlockAction); + configJSON.concat("\", \"pl_open\": \""); + configJSON.concat(openAction); + configJSON.concat("\", \"stat_t\": \"~/lock/state\", \"stat_locked\": \""); + configJSON.concat(lockedState); + configJSON.concat("\", \"stat_unlocked\": \""); + configJSON.concat(unlockedState); + configJSON.concat("\", \"opt\": \"false\"}"); + + String path = "homeassistant/lock/"; + path.concat(uidString); + path.concat("/config"); + + Serial.println("HASS Config:"); + Serial.println(configJSON); + + _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); +} + void Network::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; diff --git a/Network.h b/Network.h index ff5890f..ff3a7f8 100644 --- a/Network.h +++ b/Network.h @@ -36,6 +36,7 @@ public: void publishConfig(const NukiLock::Config& config); void publishAdvancedConfig(const NukiLock::AdvancedConfig& config); void publishPresenceDetection(char* csv); + void publishHASSConfig(const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState); void setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); diff --git a/NetworkOpener.cpp b/NetworkOpener.cpp index d2bf43e..f92f10d 100644 --- a/NetworkOpener.cpp +++ b/NetworkOpener.cpp @@ -166,6 +166,11 @@ void NetworkOpener::publishAdvancedConfig(const NukiOpener::AdvancedConfig &conf // publishBool(mqtt_topic_config_auto_lock, config.autoLockEnabled == 1); } +void NetworkOpener::publishHASSConfig(const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) +{ + _network->publishHASSConfig(baseTopic, name, uidString, lockAction, unlockAction, openAction, lockedState, unlockedState); +} + void NetworkOpener::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; diff --git a/NetworkOpener.h b/NetworkOpener.h index 5371ab7..97329c7 100644 --- a/NetworkOpener.h +++ b/NetworkOpener.h @@ -26,6 +26,7 @@ public: void publishBatteryReport(const NukiOpener::BatteryReport& batteryReport); void publishConfig(const NukiOpener::Config& config); void publishAdvancedConfig(const NukiOpener::AdvancedConfig& config); + void publishHASSConfig(const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState); void setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index bf517c2..3e01567 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -75,6 +75,7 @@ void NukiOpenerWrapper::update() if (_nukiOpener.pairNuki() == NukiOpener::PairingResult::Success) { Serial.println(F("Nuki opener paired")); _paired = true; + setupHASS(); } else { @@ -299,3 +300,23 @@ void NukiOpenerWrapper::readAdvancedConfig() _nukiAdvancedConfigValid = result == Nuki::CmdResult::Success; Serial.println(result); } + +void NukiOpenerWrapper::setupHASS() +{ + if(!_nukiConfigValid) // only ask for config once to save battery life + { + Nuki::CmdResult result = _nukiOpener.requestConfig(&_nukiConfig); + _nukiConfigValid = result == Nuki::CmdResult::Success; + } + if (_nukiConfigValid) + { + String baseTopic = _preferences->getString(preference_mqtt_opener_path); + char uidString[20]; + itoa(_nukiConfig.nukiId, uidString, 16); + _network->publishHASSConfig(baseTopic.c_str(),(char*)_nukiConfig.name,uidString,"deactivateRTO","activateRTO","electricStrikeActuation","locked","RTOactive"); + } + else + { + Serial.println(F("Unable to setup HASS. Invalid config received.")); + } +} diff --git a/NukiOpenerWrapper.h b/NukiOpenerWrapper.h index b76af46..250e6f2 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -38,6 +38,8 @@ private: void readConfig(); void readAdvancedConfig(); + + void setupHASS(); NukiOpener::LockAction lockActionToEnum(const char* str); // char array at least 14 characters diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index b1e8493..f0f8fa9 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -76,6 +76,7 @@ void NukiWrapper::update() if (_nukiLock.pairNuki() == Nuki::PairingResult::Success) { Serial.println(F("Nuki paired")); _paired = true; + setupHASS(); } else { @@ -321,3 +322,23 @@ void NukiWrapper::readAdvancedConfig() _nukiAdvancedConfigValid = result == Nuki::CmdResult::Success; Serial.println(result); } + +void NukiWrapper::setupHASS() +{ + if(!_nukiConfigValid) // only ask for config once to save battery life + { + Nuki::CmdResult result = _nukiLock.requestConfig(&_nukiConfig); + _nukiConfigValid = result == Nuki::CmdResult::Success; + } + if (_nukiConfigValid) + { + String baseTopic = _preferences->getString(preference_mqtt_lock_path); + char uidString[20]; + itoa(_nukiConfig.nukiId, uidString, 16); + _network->publishHASSConfig(baseTopic.c_str(),(char*)_nukiConfig.name,uidString,"lock","unlock","unlatch","locked","unlocked"); + } + else + { + Serial.println(F("Unable to setup HASS. Invalid config received.")); + } +} diff --git a/NukiWrapper.h b/NukiWrapper.h index 48110cd..8f8eaa6 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -36,6 +36,8 @@ private: void readConfig(); void readAdvancedConfig(); + + void setupHASS(); NukiLock::LockAction lockActionToEnum(const char* str); // char array at least 14 characters From 6f9b482bfbf19b20aaaffa8952d1d62592c3b281 Mon Sep 17 00:00:00 2001 From: rodriguezst <2828844+rodriguezst@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:43:21 +0000 Subject: [PATCH 2/6] Add battery low entity to HASS autodiscovery --- Network.cpp | 58 +++++++++++++++++++++++++++++++++---------- Network.h | 2 +- NetworkOpener.cpp | 4 +-- NetworkOpener.h | 2 +- NukiOpenerWrapper.cpp | 2 +- NukiWrapper.cpp | 2 +- 6 files changed, 51 insertions(+), 19 deletions(-) diff --git a/Network.cpp b/Network.cpp index 3b0dadf..dd5452a 100644 --- a/Network.cpp +++ b/Network.cpp @@ -343,34 +343,66 @@ void Network::publishPresenceDetection(char *csv) _presenceCsv = csv; } -void Network::publishHASSConfig(const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) +void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) { - String configJSON = "{\"~\": \""; - configJSON.concat(baseTopic); - configJSON.concat("\", \"name\": \""); - configJSON.concat(name); - configJSON.concat("\", \"unique_id\": \""); + String configJSON = "{\"dev\":{\"ids\":[\"nuki_"; configJSON.concat(uidString); - configJSON.concat("\", \"cmd_t\": \"~/lock/action\", \"pl_lock\": \""); + configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); + configJSON.concat(deviceType); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\"},\"~\":\""); + configJSON.concat(baseTopic); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\",\"unique_id\":\""); + configJSON.concat(uidString); + configJSON.concat("_lock\",\"cmd_t\":\"~"); + configJSON.concat(mqtt_topic_lock_action); + configJSON.concat("\",\"pl_lock\":\""); configJSON.concat(lockAction); - configJSON.concat("\", \"pl_unlk\": \""); + configJSON.concat("\",\"pl_unlk\":\""); configJSON.concat(unlockAction); - configJSON.concat("\", \"pl_open\": \""); + configJSON.concat("\",\"pl_open\":\""); configJSON.concat(openAction); - configJSON.concat("\", \"stat_t\": \"~/lock/state\", \"stat_locked\": \""); + configJSON.concat("\",\"stat_t\":\"~"); + configJSON.concat(mqtt_topic_lock_state); + configJSON.concat("\",\"stat_locked\":\""); configJSON.concat(lockedState); - configJSON.concat("\", \"stat_unlocked\": \""); + configJSON.concat("\",\"stat_unlocked\":\""); configJSON.concat(unlockedState); - configJSON.concat("\", \"opt\": \"false\"}"); + configJSON.concat("\",\"opt\":\"false\"}"); String path = "homeassistant/lock/"; path.concat(uidString); - path.concat("/config"); + path.concat("/smartlock/config"); Serial.println("HASS Config:"); Serial.println(configJSON); _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); + + configJSON = "{\"dev\":{\"ids\":[\"nuki_"; + configJSON.concat(uidString); + configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); + configJSON.concat(deviceType); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\"},\"~\":\""); + configJSON.concat(baseTopic); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat(" battery low\",\"unique_id\":\""); + configJSON.concat(uidString); + configJSON.concat("_battery_low\",\"dev_cla\":\"battery\",\"ent_cat\":\"diagnostic\",\"pl_off\":\"0\",\"pl_on\":\"1\",\"stat_t\":\"~"); + configJSON.concat(mqtt_topic_battery_critical); + configJSON.concat("\"}"); + + path = "homeassistant/binary_sensor/"; + path.concat(uidString); + path.concat("/battery_low/config"); + + _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); } void Network::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) diff --git a/Network.h b/Network.h index ff3a7f8..bef677a 100644 --- a/Network.h +++ b/Network.h @@ -36,7 +36,7 @@ public: void publishConfig(const NukiLock::Config& config); void publishAdvancedConfig(const NukiLock::AdvancedConfig& config); void publishPresenceDetection(char* csv); - void publishHASSConfig(const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState); + void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState); void setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); diff --git a/NetworkOpener.cpp b/NetworkOpener.cpp index f92f10d..ac90b88 100644 --- a/NetworkOpener.cpp +++ b/NetworkOpener.cpp @@ -166,9 +166,9 @@ void NetworkOpener::publishAdvancedConfig(const NukiOpener::AdvancedConfig &conf // publishBool(mqtt_topic_config_auto_lock, config.autoLockEnabled == 1); } -void NetworkOpener::publishHASSConfig(const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) +void NetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) { - _network->publishHASSConfig(baseTopic, name, uidString, lockAction, unlockAction, openAction, lockedState, unlockedState); + _network->publishHASSConfig(deviceType, baseTopic, name, uidString, lockAction, unlockAction, openAction, lockedState, unlockedState); } void NetworkOpener::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) diff --git a/NetworkOpener.h b/NetworkOpener.h index 97329c7..5136786 100644 --- a/NetworkOpener.h +++ b/NetworkOpener.h @@ -26,7 +26,7 @@ public: void publishBatteryReport(const NukiOpener::BatteryReport& batteryReport); void publishConfig(const NukiOpener::Config& config); void publishAdvancedConfig(const NukiOpener::AdvancedConfig& config); - void publishHASSConfig(const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState); + void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState); void setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index 3e01567..b88ed4e 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -313,7 +313,7 @@ void NukiOpenerWrapper::setupHASS() String baseTopic = _preferences->getString(preference_mqtt_opener_path); char uidString[20]; itoa(_nukiConfig.nukiId, uidString, 16); - _network->publishHASSConfig(baseTopic.c_str(),(char*)_nukiConfig.name,uidString,"deactivateRTO","activateRTO","electricStrikeActuation","locked","RTOactive"); + _network->publishHASSConfig("Opener",baseTopic.c_str(),(char*)_nukiConfig.name,uidString,"deactivateRTO","activateRTO","electricStrikeActuation","locked","RTOactive"); } else { diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index f0f8fa9..8478b03 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -335,7 +335,7 @@ void NukiWrapper::setupHASS() String baseTopic = _preferences->getString(preference_mqtt_lock_path); char uidString[20]; itoa(_nukiConfig.nukiId, uidString, 16); - _network->publishHASSConfig(baseTopic.c_str(),(char*)_nukiConfig.name,uidString,"lock","unlock","unlatch","locked","unlocked"); + _network->publishHASSConfig("SmartLock",baseTopic.c_str(),(char*)_nukiConfig.name,uidString,"lock","unlock","unlatch","locked","unlocked"); } else { From 5d254d463b5c07e7bd71dd1aed06acad82a29851 Mon Sep 17 00:00:00 2001 From: rodriguezst <2828844+rodriguezst@users.noreply.github.com> Date: Tue, 21 Jun 2022 12:01:31 +0000 Subject: [PATCH 3/6] Add input form for HASS discovery topic --- Network.cpp | 111 ++++++++++++++++++++++++---------------------- PreferencesKeys.h | 1 + WebCfgServer.cpp | 6 +++ 3 files changed, 66 insertions(+), 52 deletions(-) diff --git a/Network.cpp b/Network.cpp index dd5452a..46a328b 100644 --- a/Network.cpp +++ b/Network.cpp @@ -345,64 +345,71 @@ void Network::publishPresenceDetection(char *csv) void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) { - String configJSON = "{\"dev\":{\"ids\":[\"nuki_"; - configJSON.concat(uidString); - configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); - configJSON.concat(deviceType); - configJSON.concat("\",\"name\":\""); - configJSON.concat(name); - configJSON.concat("\"},\"~\":\""); - configJSON.concat(baseTopic); - configJSON.concat("\",\"name\":\""); - configJSON.concat(name); - configJSON.concat("\",\"unique_id\":\""); - configJSON.concat(uidString); - configJSON.concat("_lock\",\"cmd_t\":\"~"); - configJSON.concat(mqtt_topic_lock_action); - configJSON.concat("\",\"pl_lock\":\""); - configJSON.concat(lockAction); - configJSON.concat("\",\"pl_unlk\":\""); - configJSON.concat(unlockAction); - configJSON.concat("\",\"pl_open\":\""); - configJSON.concat(openAction); - configJSON.concat("\",\"stat_t\":\"~"); - configJSON.concat(mqtt_topic_lock_state); - configJSON.concat("\",\"stat_locked\":\""); - configJSON.concat(lockedState); - configJSON.concat("\",\"stat_unlocked\":\""); - configJSON.concat(unlockedState); - configJSON.concat("\",\"opt\":\"false\"}"); + String discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery); - String path = "homeassistant/lock/"; - path.concat(uidString); - path.concat("/smartlock/config"); + if(discoveryTopic != "") + { + String configJSON = "{\"dev\":{\"ids\":[\"nuki_"; + configJSON.concat(uidString); + configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); + configJSON.concat(deviceType); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\"},\"~\":\""); + configJSON.concat(baseTopic); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\",\"unique_id\":\""); + configJSON.concat(uidString); + configJSON.concat("_lock\",\"cmd_t\":\"~"); + configJSON.concat(mqtt_topic_lock_action); + configJSON.concat("\",\"pl_lock\":\""); + configJSON.concat(lockAction); + configJSON.concat("\",\"pl_unlk\":\""); + configJSON.concat(unlockAction); + configJSON.concat("\",\"pl_open\":\""); + configJSON.concat(openAction); + configJSON.concat("\",\"stat_t\":\"~"); + configJSON.concat(mqtt_topic_lock_state); + configJSON.concat("\",\"stat_locked\":\""); + configJSON.concat(lockedState); + configJSON.concat("\",\"stat_unlocked\":\""); + configJSON.concat(unlockedState); + configJSON.concat("\",\"opt\":\"false\"}"); - Serial.println("HASS Config:"); - Serial.println(configJSON); + String path = discoveryTopic; + path.concat("/lock/"); + path.concat(uidString); + path.concat("/smartlock/config"); - _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); + Serial.println("HASS Config:"); + Serial.println(configJSON); - configJSON = "{\"dev\":{\"ids\":[\"nuki_"; - configJSON.concat(uidString); - configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); - configJSON.concat(deviceType); - configJSON.concat("\",\"name\":\""); - configJSON.concat(name); - configJSON.concat("\"},\"~\":\""); - configJSON.concat(baseTopic); - configJSON.concat("\",\"name\":\""); - configJSON.concat(name); - configJSON.concat(" battery low\",\"unique_id\":\""); - configJSON.concat(uidString); - configJSON.concat("_battery_low\",\"dev_cla\":\"battery\",\"ent_cat\":\"diagnostic\",\"pl_off\":\"0\",\"pl_on\":\"1\",\"stat_t\":\"~"); - configJSON.concat(mqtt_topic_battery_critical); - configJSON.concat("\"}"); + _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); - path = "homeassistant/binary_sensor/"; - path.concat(uidString); - path.concat("/battery_low/config"); + configJSON = "{\"dev\":{\"ids\":[\"nuki_"; + configJSON.concat(uidString); + configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); + configJSON.concat(deviceType); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\"},\"~\":\""); + configJSON.concat(baseTopic); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat(" battery low\",\"unique_id\":\""); + configJSON.concat(uidString); + configJSON.concat("_battery_low\",\"dev_cla\":\"battery\",\"ent_cat\":\"diagnostic\",\"pl_off\":\"0\",\"pl_on\":\"1\",\"stat_t\":\"~"); + configJSON.concat(mqtt_topic_battery_critical); + configJSON.concat("\"}"); - _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); + path = discoveryTopic; + path.concat("/binary_sensor/"); + path.concat(uidString); + path.concat("/battery_low/config"); + + _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); + } } void Network::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) diff --git a/PreferencesKeys.h b/PreferencesKeys.h index 56466db..a471121 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -13,6 +13,7 @@ #define preference_mqtt_ca "mqttca" #define preference_mqtt_crt "mqttcrt" #define preference_mqtt_key "mqttkey" +#define preference_mqtt_hass_discovery "hassdiscovery" #define preference_hostname "hostname" #define preference_network_timeout "nettmout" #define preference_query_interval_lockstate "lockStInterval" diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 6c91c0c..1ae7afc 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -225,6 +225,11 @@ bool WebCfgServer::processArgs(String& message) _preferences->putString(preference_mqtt_key, value); configChanged = true; } + else if(key == "HASSDISCOVERY") + { + _preferences->putString(preference_mqtt_hass_discovery, value); + configChanged = true; + } else if(key == "HOSTNAME") { _preferences->putString(preference_hostname, value); @@ -493,6 +498,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, "NETTIMEOUT", "Network Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5); response.concat(""); response.concat("* If no encryption is configured for the MQTT broker, leave empty.
"); From 1b0402cfe7a71e6b1cf0e4d907d61942eabcd130 Mon Sep 17 00:00:00 2001 From: rodriguezst <2828844+rodriguezst@users.noreply.github.com> Date: Tue, 21 Jun 2022 22:19:19 +0000 Subject: [PATCH 4/6] Remove retained MQTT messages when changing HASS config --- Network.cpp | 22 ++++++++++++++++++++++ Network.h | 1 + NetworkOpener.cpp | 5 +++++ NetworkOpener.h | 1 + NukiOpenerWrapper.cpp | 20 ++++++++++++++++++++ NukiOpenerWrapper.h | 2 ++ NukiWrapper.cpp | 20 ++++++++++++++++++++ NukiWrapper.h | 2 ++ WebCfgServer.cpp | 9 +++++++++ 9 files changed, 82 insertions(+) diff --git a/Network.cpp b/Network.cpp index 46a328b..a51776f 100644 --- a/Network.cpp +++ b/Network.cpp @@ -412,6 +412,28 @@ void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* n } } +void Network::removeHASSConfig(char* uidString) +{ + String discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery); + + if(discoveryTopic != "") + { + String path = discoveryTopic; + path.concat("/lock/"); + path.concat(uidString); + path.concat("/smartlock/config"); + + _device->mqttClient()->publish(path.c_str(), NULL, 0U, true); + + path = discoveryTopic; + path.concat("/binary_sensor/"); + path.concat(uidString); + path.concat("/battery_low/config"); + + _device->mqttClient()->publish(path.c_str(), NULL, 0U, true); + } +} + void Network::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; diff --git a/Network.h b/Network.h index bef677a..3703e93 100644 --- a/Network.h +++ b/Network.h @@ -37,6 +37,7 @@ public: void publishAdvancedConfig(const NukiLock::AdvancedConfig& config); void publishPresenceDetection(char* csv); 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 setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); diff --git a/NetworkOpener.cpp b/NetworkOpener.cpp index ac90b88..6de71c1 100644 --- a/NetworkOpener.cpp +++ b/NetworkOpener.cpp @@ -171,6 +171,11 @@ void NetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, c _network->publishHASSConfig(deviceType, baseTopic, name, uidString, lockAction, unlockAction, openAction, lockedState, unlockedState); } +void NetworkOpener::removeHASSConfig(char* uidString) +{ + _network->removeHASSConfig(uidString); +} + void NetworkOpener::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; diff --git a/NetworkOpener.h b/NetworkOpener.h index 5136786..ee15822 100644 --- a/NetworkOpener.h +++ b/NetworkOpener.h @@ -27,6 +27,7 @@ public: void publishConfig(const NukiOpener::Config& config); void publishAdvancedConfig(const NukiOpener::AdvancedConfig& config); 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 setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index b88ed4e..dc3eb49 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -320,3 +320,23 @@ void NukiOpenerWrapper::setupHASS() Serial.println(F("Unable to setup HASS. Invalid config received.")); } } + +void NukiOpenerWrapper::disableHASS() +{ + if(!_nukiConfigValid) // only ask for config once to save battery life + { + Nuki::CmdResult result = _nukiOpener.requestConfig(&_nukiConfig); + _nukiConfigValid = result == Nuki::CmdResult::Success; + } + if (_nukiConfigValid) + { + String baseTopic = _preferences->getString(preference_mqtt_opener_path); + char uidString[20]; + itoa(_nukiConfig.nukiId, uidString, 16); + _network->removeHASSConfig(uidString); + } + else + { + Serial.println(F("Unable to disable HASS. Invalid config received.")); + } +} diff --git a/NukiOpenerWrapper.h b/NukiOpenerWrapper.h index 250e6f2..43b8c29 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -18,6 +18,8 @@ public: void setPin(const uint16_t pin); void unpair(); + + void disableHASS(); const NukiOpener::OpenerState& keyTurnerState(); const bool isPaired(); diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 8478b03..32fec91 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -342,3 +342,23 @@ void NukiWrapper::setupHASS() Serial.println(F("Unable to setup HASS. Invalid config received.")); } } + +void NukiWrapper::disableHASS() +{ + if(!_nukiConfigValid) // only ask for config once to save battery life + { + Nuki::CmdResult result = _nukiLock.requestConfig(&_nukiConfig); + _nukiConfigValid = result == Nuki::CmdResult::Success; + } + if (_nukiConfigValid) + { + String baseTopic = _preferences->getString(preference_mqtt_lock_path); + char uidString[20]; + itoa(_nukiConfig.nukiId, uidString, 16); + _network->removeHASSConfig(uidString); + } + else + { + Serial.println(F("Unable to disable HASS. Invalid config received.")); + } +} diff --git a/NukiWrapper.h b/NukiWrapper.h index 8f8eaa6..5133db2 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -18,6 +18,8 @@ public: void setPin(const uint16_t pin); void unpair(); + + void disableHASS(); const NukiLock::KeyTurnerState& keyTurnerState(); const bool isPaired(); diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 1ae7afc..4ec332c 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -227,6 +227,15 @@ bool WebCfgServer::processArgs(String& message) } else if(key == "HASSDISCOVERY") { + // Previous HASS config has to be disabled first (remove retained MQTT messages) + if ( _nuki != nullptr ) + { + _nuki->disableHASS(); + } + if ( _nukiOpener != nullptr ) + { + _nukiOpener->disableHASS(); + } _preferences->putString(preference_mqtt_hass_discovery, value); configChanged = true; } From 319877596da2661dda9e38ca9b2a5af1e6572216 Mon Sep 17 00:00:00 2001 From: rodriguezst <2828844+rodriguezst@users.noreply.github.com> Date: Tue, 21 Jun 2022 22:22:50 +0000 Subject: [PATCH 5/6] Remove HASS config also when unpairing lock/opener --- WebCfgServer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 4ec332c..3cf6224 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -599,10 +599,12 @@ void WebCfgServer::processUnpair(bool opener) _server.send(200, "text/html", response); if(!opener && _nuki != nullptr) { + _nuki->disableHASS(); _nuki->unpair(); } if(opener && _nukiOpener != nullptr) { + _nukiOpener->disableHASS(); _nukiOpener->unpair(); } waitAndProcess(false, 1000); From 240fa6c020b6847c3bb6dca63d3797cd4772eba9 Mon Sep 17 00:00:00 2001 From: rodriguezst <2828844+rodriguezst@users.noreply.github.com> Date: Tue, 21 Jun 2022 22:23:01 +0000 Subject: [PATCH 6/6] Add Home Asssitant setup options to README.md --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 76cc13d..781a3f2 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,21 @@ 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 can be setup manually using the [MQTT Lock integration](https://www.home-assistant.io/integrations/lock.mqtt/). + +For a simpler integration, this software supports [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/). To enable autodiscovery, supply the discovery topic that is configured in your Home Assistant instance (typically "homeassistant") in the MQTT Configuration page. Once enabled, Smartlock and/or Opener should automatically appear on Home Assistant. + +The following mapping between Home Assistant services and Nuki commands is setup when enabling autodiscovery: +| | Smartlock | Opener | +|-------------|-----------|---------------------------| +| lock.lock | Lock | Disable Ring To Open | +| lock.unlock | Unlock | Enable Ring To Open | +| lock.open | Unlatch | Electric Strike Actuation | + +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. + ## 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).