From 62742549a79d2fc7742f32bc0a7028b75fcb4535 Mon Sep 17 00:00:00 2001 From: iranl Date: Sat, 2 Nov 2024 21:54:06 +0100 Subject: [PATCH 01/17] Enable/Disable HA Discovery using a checkbox --- README.md | 3 ++- src/NukiNetwork.cpp | 6 ++++++ src/PreferencesKeys.h | 5 +++-- src/WebCfgServer.cpp | 44 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e251f85..a0202a0 100644 --- a/README.md +++ b/README.md @@ -203,10 +203,11 @@ In a browser navigate to the IP address assigned to the ESP32. - MQTT User: If using authentication on the MQTT broker set to a username with read/write rights on the MQTT broker, set to # to clear - MQTT Password : If using authentication on the MQTT broker set to the password belonging to a username with read/write rights on the MQTT broker, set to # to clear - MQTT NukiHub Path: Set to the preferred MQTT root topic for NukiHub, defaults to "nukihub". Make sure this topic is unique when using multiple ESP32 NukiHub devices +- Enable Home Assistant auto discovery: Enable Home Assistant MQTT auto discovery. Will automatically create entities in Home Assistant for NukiHub and connected Nuki Lock and/or Opener when enabled. #### Advanced MQTT Configuration -- Home Assistant discovery topic: Set to the Home Assistant auto discovery topic, leave empty to disable auto discovery. Usually "homeassistant" unless you manually changed this setting on the Home Assistant side. +- Home Assistant discovery topic: Set to the Home Assistant auto discovery topic. Usually "homeassistant" unless you manually changed this setting on the Home Assistant side. - Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode (Opener only): By default the lock entity in Home Assistant will enable Ring-to-Open (RTO) when unlocking and disable RTO when locking. By enabling this setting this behaviour will change and now unlocking will enable Continuous Mode and locking will disable Continuous Mode, for more information see the "[Home Assistant Discovery](#home-assistant-discovery-optional)" section of this README. - MQTT SSL CA Certificate: Optionally set to the CA SSL certificate of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional)" section of this README. - MQTT SSL Client Certificate: Optionally set to the Client SSL certificate of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional)" section of this README. diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 9ae5d7a..88c7b08 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -61,6 +61,12 @@ NukiNetwork::NukiNetwork(Preferences *preferences) { _mqttConnectionStateTopic[i] = connectionStateTopic.charAt(i); } + + if(_preferences->getString(preference_mqtt_hass_discovery, "") != "" && !_preferences->getBool(preference_mqtt_hass_enabled, false)) + { + _preferences->putBool(preference_mqtt_hass_enabled, true); + } + #endif setupDevice(); diff --git a/src/PreferencesKeys.h b/src/PreferencesKeys.h index b01a959..8390e0f 100644 --- a/src/PreferencesKeys.h +++ b/src/PreferencesKeys.h @@ -46,6 +46,7 @@ #define preference_cred_user (char*)"crdusr" #define preference_cred_password (char*)"crdpass" #define preference_gpio_configuration (char*)"gpiocfg" +#define preference_mqtt_hass_enabled (char*)"hassena" #define preference_mqtt_hass_discovery (char*)"hassdiscovery" #define preference_webserver_enabled (char*)"websrvena" #define preference_update_from_mqtt (char*)"updMqtt" @@ -293,7 +294,7 @@ private: preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi, preference_network_custom_pwr, preference_network_custom_mdio, preference_ntw_reconfigure, preference_lock_max_auth_entry_count, preference_opener_max_auth_entry_count, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_wifi_ssid, preference_wifi_pass, - preference_keypad_check_code_enabled, preference_disable_network_not_connected + preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_mqtt_hass_enabled }; std::vector _redact = { @@ -306,7 +307,7 @@ private: preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_enable_bootloop_reset, preference_webserver_enabled, preference_restart_on_disconnect, preference_keypad_control_enabled, preference_keypad_info_enabled, preference_keypad_publish_code, preference_show_secrets, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_register_opener_as_app, preference_ip_dhcp_enabled, - preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_enabled, + preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_enabled, preference_mqtt_hass_enabled, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled, preference_ntw_reconfigure, preference_keypad_check_code_enabled, preference_disable_network_not_connected diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 7bbaa7f..a94ce81 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -1726,6 +1726,33 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) //configChanged = true; } } + else if(key == "ENHADISC") + { + if(_preferences->getBool(preference_mqtt_hass_enabled, false) != (value == "1")) + { + if(!_preferences->getBool(preference_mqtt_hass_enabled, false)) + { + if (_nuki != nullptr) + { + _nuki->disableHASS(); + } + if (_nukiOpener != nullptr) + { + _nukiOpener->disableHASS(); + } + + _preferences->putString(preference_mqtt_hass_discovery, ""); + } + else if(_preferences->getString(preference_mqtt_hass_discovery, "") == "") + { + _preferences->putString(preference_mqtt_hass_discovery, "homeassistant"); + } + _preferences->putBool(preference_mqtt_hass_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } else if(key == "HASSDISCOVERY") { if(_preferences->getString(preference_mqtt_hass_discovery, "") != value) @@ -1739,6 +1766,16 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) _nukiOpener->disableHASS(); } _preferences->putString(preference_mqtt_hass_discovery, value); + + if(value != "" && !_preferences->getBool(preference_mqtt_hass_enabled, false)) + { + _preferences->putBool(preference_mqtt_hass_enabled, true); + } + else if(value == "" && _preferences->getBool(preference_mqtt_hass_enabled, false)) + { + _preferences->putBool(preference_mqtt_hass_enabled, false); + } + Log->print(F("Setting changed: ")); Log->println(key); configChanged = true; @@ -3507,11 +3544,12 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request) printInputField(&response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true); printInputField(&response, "MQTTPASS", "MQTT Password", "*", 30, "", true, true); printInputField(&response, "MQTTPATH", "MQTT NukiHub Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, ""); + printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), ""); response.print("
"); response.print("

Advanced MQTT Configuration

"); response.print(""); - printInputField(&response, "HASSDISCOVERY", "Home Assistant discovery topic (empty to disable; usually homeassistant)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, ""); + printInputField(&response, "HASSDISCOVERY", "Home Assistant discovery topic (usually \"homeassistant\")", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, ""); if(_preferences->getBool(preference_opener_enabled, false)) { printCheckBox(&response, "OPENERCONT", "Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode", _preferences->getBool(preference_opener_continuous_mode), ""); @@ -3547,7 +3585,7 @@ esp_err_t WebCfgServer::buildAdvancedConfigHtml(PsychicRequest *request) response.print(""); - printCheckBox(&response, "DISNTWNOCON", "Disable Network if not connected within 60s", _preferences->getBool(preference_disable_network_not_connected, false), ""); + printCheckBox(&response, "DISNTWNOCON", "Disable Network if not connected within 60s", _preferences->getBool(preference_disable_network_not_connected, false), ""); printCheckBox(&response, "WEBLOG", "Enable WebSerial logging", _preferences->getBool(preference_webserial_enabled), ""); printCheckBox(&response, "BTLPRST", "Enable Bootloop prevention (Try to reset these settings to default on bootloop)", true, ""); printInputField(&response, "BUFFSIZE", "Char buffer size (min 4096, max 32768)", _preferences->getInt(preference_buffer_size, CHAR_BUFFER_SIZE), 6, ""); @@ -3721,7 +3759,7 @@ esp_err_t WebCfgServer::buildAccLvlHtml(PsychicRequest *request) printCheckBox(&response, "KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), ""); printCheckBox(&response, "KPCODE", "Also publish keypad codes (Disadvised for security reasons)", _preferences->getBool(preference_keypad_publish_code, false), ""); printCheckBox(&response, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), ""); - printCheckBox(&response, "KPCHECK", "Allow checking if keypad codes are valid (Disadvised for security reasons)", _preferences->getBool(preference_keypad_check_code_enabled, false), ""); + printCheckBox(&response, "KPCHECK", "Allow checking if keypad codes are valid (Disadvised for security reasons)", _preferences->getBool(preference_keypad_check_code_enabled, false), ""); } printCheckBox(&response, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), ""); printCheckBox(&response, "TCPER", "Publish a topic per time control entry and create HA sensor", _preferences->getBool(preference_timecontrol_topic_per_entry), ""); From be646bc6a8259ee027151c381adddc410174b415 Mon Sep 17 00:00:00 2001 From: iranl Date: Sat, 2 Nov 2024 23:54:03 +0100 Subject: [PATCH 02/17] Update main.cpp --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 4fb617f..ded388c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,7 +46,7 @@ bool wifiConnected = false; TaskHandle_t nukiTaskHandle = nullptr; -int64_t restartTs = ((2^64) - (5 * 1000 * 60000)) / 1000; +int64_t restartTs = (pow(2,64) - (5 * 1000 * 60000)) / 1000; #else #include "../../src/WebCfgServer.h" From 020b16bfea18388286b7b5381c01af9bb9054e47 Mon Sep 17 00:00:00 2001 From: iranl Date: Sun, 3 Nov 2024 14:11:08 +0100 Subject: [PATCH 03/17] Minor fixes --- lib/nuki_ble | 2 +- src/Config.h | 2 +- src/NukiOpenerWrapper.cpp | 19 ++- src/NukiOpenerWrapper.h | 2 + src/NukiWrapper.cpp | 20 ++- src/NukiWrapper.h | 1 + src/WebCfgServer.cpp | 285 +++++++++++++++++++------------------- src/main.cpp | 6 +- 8 files changed, 177 insertions(+), 160 deletions(-) diff --git a/lib/nuki_ble b/lib/nuki_ble index 0b4e590..19e82ce 160000 --- a/lib/nuki_ble +++ b/lib/nuki_ble @@ -1 +1 @@ -Subproject commit 0b4e5901c712a77417fda9a8a6ba54436e343e4a +Subproject commit 19e82cea9739cadeebfbfac46c189e7cfdad2896 diff --git a/src/Config.h b/src/Config.h index b82a95b..d032352 100644 --- a/src/Config.h +++ b/src/Config.h @@ -4,7 +4,7 @@ #define NUKI_HUB_VERSION "9.02" #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2024-11-02" +#define NUKI_HUB_DATE "2024-11-03" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index 4fa6762..6c0a86d 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -540,7 +540,7 @@ void NukiOpenerWrapper::updateConfig() if(_preferences->getUInt(preference_nuki_id_opener, 0) == _nukiConfig.nukiId) { - _hasKeypad = _nukiConfig.hasKeypad == 1 || _nukiConfig.hasKeypadV2 == 1; + _hasKeypad = _nukiConfig.hasKeypad == 1 || (_nukiConfig.hasKeypadV2 > 0 && _nukiConfig.hasKeypadV2 != 252); _firmwareVersion = std::to_string(_nukiConfig.firmwareVersion[0]) + "." + std::to_string(_nukiConfig.firmwareVersion[1]) + "." + std::to_string(_nukiConfig.firmwareVersion[2]); _hardwareVersion = std::to_string(_nukiConfig.hardwareRevision[0]) + "." + std::to_string(_nukiConfig.hardwareRevision[1]); if(_preferences->getBool(preference_conf_info_enabled, true)) @@ -3879,11 +3879,20 @@ BleScanner::Scanner *NukiOpenerWrapper::bleScanner() void NukiOpenerWrapper::notify(Nuki::EventType eventType) { - if(!_pairedAsApp && eventType == Nuki::EventType::KeyTurnerStatusUpdated && !_statusUpdated) + if(eventType == Nuki::EventType::KeyTurnerStatusReset) { - Log->println("KeyTurnerStatusUpdated"); - _statusUpdated = true; - _network->publishStatusUpdated(_statusUpdated); + _newSignal = false; + } + else if(eventType == Nuki::EventType::KeyTurnerStatusUpdated) + { + if(!_statusUpdated && !_newSignal) + { + _newSignal = true; + Log->println("KeyTurnerStatusUpdated"); + _statusUpdated = true; + _statusUpdatedTs = espMillis(); + _network->publishStatusUpdated(_statusUpdated); + } } } diff --git a/src/NukiOpenerWrapper.h b/src/NukiOpenerWrapper.h index 2dab4aa..177efa9 100644 --- a/src/NukiOpenerWrapper.h +++ b/src/NukiOpenerWrapper.h @@ -135,12 +135,14 @@ private: bool _paired = false; bool _statusUpdated = false; + bool _newSignal = false; bool _hasKeypad = false; bool _keypadEnabled = false; uint _maxKeypadCodeCount = 0; uint _maxTimeControlEntryCount = 0; uint _maxAuthEntryCount = 0; int _rssiPublishInterval = 0; + int64_t _statusUpdatedTs = 0; int64_t _nextLockStateUpdateTs = 0; int64_t _nextBatteryReportTs = 0; int64_t _nextConfigUpdateTs = 0; diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index 799eb0c..3c88bee 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -628,7 +628,7 @@ void NukiWrapper::updateConfig() if(_preferences->getUInt(preference_nuki_id_lock, 0) == _nukiConfig.nukiId) { - _hasKeypad = _nukiConfig.hasKeypad == 1 || _nukiConfig.hasKeypadV2 == 1; + _hasKeypad = _nukiConfig.hasKeypad == 1 || (_nukiConfig.hasKeypadV2 > 0 && _nukiConfig.hasKeypadV2 != 252); _firmwareVersion = std::to_string(_nukiConfig.firmwareVersion[0]) + "." + std::to_string(_nukiConfig.firmwareVersion[1]) + "." + std::to_string(_nukiConfig.firmwareVersion[2]); _hardwareVersion = std::to_string(_nukiConfig.hardwareRevision[0]) + "." + std::to_string(_nukiConfig.hardwareRevision[1]); if(_preferences->getBool(preference_conf_info_enabled, true)) @@ -3998,12 +3998,20 @@ void NukiWrapper::notify(Nuki::EventType eventType) } else { - if(!_pairedAsApp && eventType == Nuki::EventType::KeyTurnerStatusUpdated && !_statusUpdated) + if(eventType == Nuki::EventType::KeyTurnerStatusReset) { - Log->println("KeyTurnerStatusUpdated"); - _statusUpdated = true; - _statusUpdatedTs = espMillis(); - _network->publishStatusUpdated(_statusUpdated); + _newSignal = false; + } + else if(eventType == Nuki::EventType::KeyTurnerStatusUpdated) + { + if(!_statusUpdated && !_newSignal) + { + _newSignal = true; + Log->println("KeyTurnerStatusUpdated"); + _statusUpdated = true; + _statusUpdatedTs = espMillis(); + _network->publishStatusUpdated(_statusUpdated); + } } } } diff --git a/src/NukiWrapper.h b/src/NukiWrapper.h index 4cdb937..890f7b4 100644 --- a/src/NukiWrapper.h +++ b/src/NukiWrapper.h @@ -134,6 +134,7 @@ private: bool _pairedAsApp = false; bool _paired = false; bool _statusUpdated = false; + bool _newSignal = false; bool _hasKeypad = false; bool _keypadEnabled = false; uint _maxKeypadCodeCount = 0; diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 7bbaa7f..87a3ccb 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -78,10 +78,10 @@ void WebCfgServer::initialize() { _psychicServer->on("/", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } if(!_network->isApOpen()) { #ifndef NUKI_HUB_UPDATER @@ -100,26 +100,26 @@ void WebCfgServer::initialize() _psychicServer->on("/style.css", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return sendCss(request); }); _psychicServer->on("/favicon.ico", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return sendFavicon(request); }); _psychicServer->on("/reboot", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } String value = ""; if(request->hasParam("CONFIRMTOKEN")) @@ -150,15 +150,15 @@ void WebCfgServer::initialize() #ifndef CONFIG_IDF_TARGET_ESP32H2 _psychicServer->on("/ssidlist", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildSSIDListHtml(request); }); _psychicServer->on("/savewifi", HTTP_POST, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); } @@ -181,117 +181,117 @@ void WebCfgServer::initialize() #ifndef NUKI_HUB_UPDATER _psychicServer->on("/import", HTTP_POST, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } String message = ""; bool restart = processImport(request, message); return buildConfirmHtml(request, message, 3, true); }); _psychicServer->on("/export", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return sendSettings(request); }); _psychicServer->on("/impexpcfg", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildImportExportHtml(request); }); _psychicServer->on("/status", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildStatusHtml(request); }); _psychicServer->on("/acclvl", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildAccLvlHtml(request); }); _psychicServer->on("/custntw", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildCustomNetworkConfigHtml(request); }); _psychicServer->on("/advanced", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildAdvancedConfigHtml(request); }); _psychicServer->on("/cred", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildCredHtml(request); }); _psychicServer->on("/ntwconfig", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildNetworkConfigHtml(request); }); _psychicServer->on("/mqttconfig", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildMqttConfigHtml(request); }); _psychicServer->on("/nukicfg", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildNukiConfigHtml(request); }); _psychicServer->on("/gpiocfg", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildGpioConfigHtml(request); }); #ifndef CONFIG_IDF_TARGET_ESP32H2 _psychicServer->on("/wifi", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildConfigureWifiHtml(request); }); _psychicServer->on("/wifimanager", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } if(_allowRestartToPortal) { esp_err_t res = buildConfirmHtml(request, "Restarting. Connect to ESP access point (\"NukiHub\" with password \"NukiHubESP32\") to reconfigure Wi-Fi.", 0); @@ -304,70 +304,70 @@ void WebCfgServer::initialize() #endif _psychicServer->on("/unpairlock", HTTP_POST, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return processUnpair(request, false); }); _psychicServer->on("/unpairopener", HTTP_POST, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return processUnpair(request, true); }); _psychicServer->on("/factoryreset", HTTP_POST, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return processFactoryReset(request); }); - _psychicServer->on("/infopg", HTTP_GET, [&](PsychicRequest *request) + _psychicServer->on("/info", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildInfoHtml(request); }); _psychicServer->on("/debugon", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } _preferences->putBool(preference_publish_debug_info, true); return buildConfirmHtml(request, "Debug On", 3, true); }); _psychicServer->on("/debugoff", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } _preferences->putBool(preference_publish_debug_info, false); return buildConfirmHtml(request, "Debug Off", 3, true); }); _psychicServer->on("/savecfg", HTTP_POST, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } String message = ""; bool restart = processArgs(request, message); return buildConfirmHtml(request, message, 3, true); }); _psychicServer->on("/savegpiocfg", HTTP_POST, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } processGpioArgs(request); esp_err_t res = buildConfirmHtml(request, "Saving GPIO configuration. Restarting.", 3, true); Log->println(F("Restarting")); @@ -378,26 +378,26 @@ void WebCfgServer::initialize() #endif _psychicServer->on("/ota", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildOtaHtml(request); }); _psychicServer->on("/otadebug", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return buildOtaHtml(request, true); }); _psychicServer->on("/reboottoota", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } String value = ""; if(request->hasParam("CONFIRMTOKEN")) { @@ -424,10 +424,10 @@ void WebCfgServer::initialize() }); _psychicServer->on("/autoupdate", HTTP_GET, [&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } #ifndef NUKI_HUB_UPDATER return processUpdate(request); #else @@ -438,20 +438,20 @@ void WebCfgServer::initialize() PsychicUploadHandler *updateHandler = new PsychicUploadHandler(); updateHandler->onUpload([&](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } return handleOtaUpload(request, filename, index, data, len, final); } ); updateHandler->onRequest([&](PsychicRequest *request) { - if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) - { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) + { + return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + } String result; if (!Update.hasError()) @@ -586,9 +586,8 @@ esp_err_t WebCfgServer::buildWifiConnectHtml(PsychicRequest *request) response.print("
Current bootloop prevention state"); response.print(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Enabled" : "Disabled"); response.print("
"); response.print("
"); response.print(""); - response.print("

"); + response.print("

"); + response.print("
"); response.print(""); return response.endSend(); } @@ -882,9 +881,8 @@ esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, bool debug) response.print("

Manually update Nuki Hub

"); response.print("

Reboot to Nuki Hub Updater

"); response.print("Click on the button to reboot to the Nuki Hub updater, where you can select the latest Nuki Hub binary to update"); - response.print("



"); + response.print("

"); + response.print("


"); response.print("

Update Nuki Hub Updater

"); response.print("Select the latest Nuki Hub updater binary to update the Nuki Hub updater"); response.print("
Choose the nuki_hub_updater.bin file to upload:
"); @@ -894,9 +892,8 @@ esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, bool debug) response.print("
"); response.print("

Reboot to Nuki Hub

"); response.print("Click on the button to reboot to Nuki Hub"); - response.print("


"); + response.print("

"); + response.print("


"); response.print("

Update Nuki Hub

"); response.print("Select the latest Nuki Hub binary to update Nuki Hub"); response.print("
Choose the nuki_hub.bin file to upload:
"); @@ -3345,7 +3342,7 @@ esp_err_t WebCfgServer::buildHtml(PsychicRequest *request) printParameter(&response, "Nuki Opener PIN status", openerState.c_str(), "", "openerPin"); } } - printParameter(&response, "Firmware", NUKI_HUB_VERSION, "/infopg", "firmware"); + printParameter(&response, "Firmware", NUKI_HUB_VERSION, "/info", "firmware"); if(_preferences->getBool(preference_check_updates)) { printParameter(&response, "Latest Firmware", _preferences->getString(preference_latest_version).c_str(), "/ota", "ota"); diff --git a/src/main.cpp b/src/main.cpp index 4fb617f..0210382 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -500,9 +500,11 @@ void setup() if(!doOta) { psychicServer = new PsychicHttpServer; + psychicServer->config.max_uri_handlers = 40; + psychicServer->config.stack_size = HTTPD_TASK_SIZE; + psychicServer->listen(80); webCfgServer = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer); webCfgServer->initialize(); - psychicServer->listen(80); psychicServer->onNotFound([](PsychicRequest* request) { return request->redirect("/"); @@ -591,8 +593,6 @@ void setup() psychicServer = new PsychicHttpServer; psychicServer->config.max_uri_handlers = 40; psychicServer->config.stack_size = HTTPD_TASK_SIZE; - psychicServer->maxUploadSize = 8192; - psychicServer->maxRequestBodySize = 8192; psychicServer->listen(80); if(forceEnableWebServer || preferences->getBool(preference_webserver_enabled, true)) From 481d18cc288b2d59ff0b68d24967743cb9a6bc9b Mon Sep 17 00:00:00 2001 From: iranl Date: Sun, 3 Nov 2024 15:30:21 +0100 Subject: [PATCH 04/17] Minor fixes --- src/NukiOpenerWrapper.cpp | 158 +++++++++++++++++++------------------- src/NukiWrapper.cpp | 17 ++-- src/main.cpp | 2 +- 3 files changed, 90 insertions(+), 87 deletions(-) diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index 6c0a86d..05a0983 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -218,72 +218,6 @@ void NukiOpenerWrapper::update() _nukiOpener.updateConnectionState(); - if(_network->mqttConnectionState() == 2) - { - if(_statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0) - { - _statusUpdated = false; - _nextLockStateUpdateTs = ts + _intervalLockstate * 1000; - updateKeyTurnerState(); - _network->publishStatusUpdated(_statusUpdated); - } - if(_nextBatteryReportTs == 0 || ts > _nextBatteryReportTs || (queryCommands & QUERY_COMMAND_BATTERY) > 0) - { - _nextBatteryReportTs = ts + _intervalBattery * 1000; - updateBatteryState(); - } - if(_nextConfigUpdateTs == 0 || ts > _nextConfigUpdateTs || (queryCommands & QUERY_COMMAND_CONFIG) > 0) - { - _nextConfigUpdateTs = ts + _intervalConfig * 1000; - updateConfig(); - } - if(_waitAuthLogUpdateTs != 0 && ts > _waitAuthLogUpdateTs) - { - _waitAuthLogUpdateTs = 0; - updateAuthData(true); - } - if(_waitKeypadUpdateTs != 0 && ts > _waitKeypadUpdateTs) - { - _waitKeypadUpdateTs = 0; - updateKeypad(true); - } - if(_waitTimeControlUpdateTs != 0 && ts > _waitTimeControlUpdateTs) - { - _waitTimeControlUpdateTs = 0; - updateTimeControl(true); - } - if(_waitAuthUpdateTs != 0 && ts > _waitAuthUpdateTs) - { - _waitAuthUpdateTs = 0; - updateAuth(true); - } - if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted) - { - setupHASS(); - } - if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs)) - { - _nextRssiTs = ts + _rssiPublishInterval; - - int rssi = _nukiOpener.getRssi(); - if(rssi != _lastRssi) - { - _network->publishRssi(rssi); - _lastRssi = rssi; - } - } - if(_hasKeypad && _keypadEnabled && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs || (queryCommands & QUERY_COMMAND_KEYPAD) > 0)) - { - _nextKeypadUpdateTs = ts + _intervalKeypad * 1000; - updateKeypad(false); - } - } - - if(_checkKeypadCodes && _invalidCount > 0 && (ts - (120000 * _invalidCount)) > _lastCodeCheck) - { - _invalidCount--; - } - if(_nextLockAction != (NukiOpener::LockAction)0xff) { int retryCount = 0; @@ -336,11 +270,78 @@ void NukiOpenerWrapper::update() _nextLockAction = (NukiOpener::LockAction) 0xff; } } - - if(_clearAuthData) + if(_statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0) { - _network->clearAuthorizationInfo(); - _clearAuthData = false; + _statusUpdated = false; + _nextLockStateUpdateTs = ts + _intervalLockstate * 1000; + updateKeyTurnerState(); + _network->publishStatusUpdated(_statusUpdated); + } + if(_network->mqttConnectionState() == 2) + { + if(!_statusUpdated) + { + if(_nextBatteryReportTs == 0 || ts > _nextBatteryReportTs || (queryCommands & QUERY_COMMAND_BATTERY) > 0) + { + _nextBatteryReportTs = ts + _intervalBattery * 1000; + updateBatteryState(); + } + if(_nextConfigUpdateTs == 0 || ts > _nextConfigUpdateTs || (queryCommands & QUERY_COMMAND_CONFIG) > 0) + { + _nextConfigUpdateTs = ts + _intervalConfig * 1000; + updateConfig(); + } + if(_waitAuthLogUpdateTs != 0 && ts > _waitAuthLogUpdateTs) + { + _waitAuthLogUpdateTs = 0; + updateAuthData(true); + } + if(_waitKeypadUpdateTs != 0 && ts > _waitKeypadUpdateTs) + { + _waitKeypadUpdateTs = 0; + updateKeypad(true); + } + if(_waitTimeControlUpdateTs != 0 && ts > _waitTimeControlUpdateTs) + { + _waitTimeControlUpdateTs = 0; + updateTimeControl(true); + } + if(_waitAuthUpdateTs != 0 && ts > _waitAuthUpdateTs) + { + _waitAuthUpdateTs = 0; + updateAuth(true); + } + if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted) + { + setupHASS(); + } + if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs)) + { + _nextRssiTs = ts + _rssiPublishInterval; + + int rssi = _nukiOpener.getRssi(); + if(rssi != _lastRssi) + { + _network->publishRssi(rssi); + _lastRssi = rssi; + } + } + if(_hasKeypad && _keypadEnabled && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs || (queryCommands & QUERY_COMMAND_KEYPAD) > 0)) + { + _nextKeypadUpdateTs = ts + _intervalKeypad * 1000; + updateKeypad(false); + } + } + + if(_clearAuthData) + { + _network->clearAuthorizationInfo(); + _clearAuthData = false; + } + if(_checkKeypadCodes && _invalidCount > 0 && (ts - (120000 * _invalidCount)) > _lastCodeCheck) + { + _invalidCount--; + } } memcpy(&_lastKeyTurnerState, &_keyTurnerState, sizeof(NukiOpener::OpenerState)); @@ -465,8 +466,15 @@ void NukiOpenerWrapper::updateKeyTurnerState() _network->publishRing(false); } - _network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState); + if(_publishAuthData) + { + Log->println(F("Publishing auth data")); + updateAuthData(false); + Log->println(F("Done publishing auth data")); + } + updateGpioOutputs(); + _network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState); if(_keyTurnerState.nukiState == NukiOpener::State::ContinuousMode) { @@ -478,13 +486,6 @@ void NukiOpenerWrapper::updateKeyTurnerState() Log->println(lockStateStr); } - if(_publishAuthData) - { - Log->println(F("Publishing auth data")); - updateAuthData(false); - Log->println(F("Done publishing auth data")); - } - postponeBleWatchdog(); Log->println(F("Done querying opener state")); } @@ -3882,6 +3883,7 @@ void NukiOpenerWrapper::notify(Nuki::EventType eventType) if(eventType == Nuki::EventType::KeyTurnerStatusReset) { _newSignal = false; + Log->println("KeyTurnerStatusReset"); } else if(eventType == Nuki::EventType::KeyTurnerStatusUpdated) { diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index 3c88bee..e2ca06b 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -359,16 +359,16 @@ void NukiWrapper::update() _nextLockAction = (NukiLock::LockAction) 0xff; } } + if(_nukiOfficial->getStatusUpdated() || _statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0) + { + Log->println("Updating Lock state based on status, timer or query"); + _statusUpdated = false; + _nextLockStateUpdateTs = ts + _intervalLockstate * 1000; + updateKeyTurnerState(); + _network->publishStatusUpdated(_statusUpdated); + } if(_network->mqttConnectionState() == 2) { - if(_nukiOfficial->getStatusUpdated() || _statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0) - { - Log->println("Updating Lock state based on status, timer or query"); - _statusUpdated = false; - _nextLockStateUpdateTs = ts + _intervalLockstate * 1000; - updateKeyTurnerState(); - _network->publishStatusUpdated(_statusUpdated); - } if(!_statusUpdated) { if(_nextBatteryReportTs == 0 || ts > _nextBatteryReportTs || (queryCommands & QUERY_COMMAND_BATTERY) > 0) @@ -4001,6 +4001,7 @@ void NukiWrapper::notify(Nuki::EventType eventType) if(eventType == Nuki::EventType::KeyTurnerStatusReset) { _newSignal = false; + Log->println("KeyTurnerStatusReset"); } else if(eventType == Nuki::EventType::KeyTurnerStatusUpdated) { diff --git a/src/main.cpp b/src/main.cpp index 0210382..4e42ac5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -201,7 +201,7 @@ void nukiTask(void *pvParameters) if (needsPairing) { - delay(5000); + delay(2500); } else if (!whiteListed) { From 7ea04e6366c97d04ce68c9fff7903e6cf1f96905 Mon Sep 17 00:00:00 2001 From: iranl Date: Mon, 4 Nov 2024 21:14:53 +0100 Subject: [PATCH 05/17] Fix Ring detection --- src/Config.h | 2 +- src/NukiNetwork.cpp | 1 + src/NukiNetworkOpener.cpp | 21 +++++++++++++++++++-- src/NukiOpenerWrapper.cpp | 16 +++++++++------- src/NukiOpenerWrapper.h | 2 +- src/NukiWrapper.cpp | 8 ++++---- src/NukiWrapper.h | 2 +- src/WebCfgServer.cpp | 24 +++++------------------- 8 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/Config.h b/src/Config.h index d032352..7ef96ef 100644 --- a/src/Config.h +++ b/src/Config.h @@ -4,7 +4,7 @@ #define NUKI_HUB_VERSION "9.02" #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2024-11-03" +#define NUKI_HUB_DATE "2024-11-04" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 88c7b08..5755cf6 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -2700,6 +2700,7 @@ void NukiNetwork::publishHASSConfigAdditionalOpenerEntities(char *deviceType, co json = createHassJson(uidString, "_ring_event", "Ring", name, baseTopic, String("~") + mqtt_topic_lock_ring, deviceType, "doorbell", "", "", "", {{(char*)"val_tpl", (char*)"{ \"event_type\": \"{{ value }}\" }"}}); json["event_types"][0] = "ring"; json["event_types"][1] = "ringlocked"; + json["event_types"][2] = "standby"; serializeJson(json, _buffer, _bufferSize); String path = createHassTopicPath("event", "ring", uidString); _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); diff --git a/src/NukiNetworkOpener.cpp b/src/NukiNetworkOpener.cpp index b1e2692..3dbf265 100644 --- a/src/NukiNetworkOpener.cpp +++ b/src/NukiNetworkOpener.cpp @@ -43,6 +43,7 @@ void NukiNetworkOpener::initialize() _network->initTopic(_mqttPath, mqtt_topic_query_lockstate, "0"); _network->initTopic(_mqttPath, mqtt_topic_query_battery, "0"); _network->initTopic(_mqttPath, mqtt_topic_lock_binary_ring, "standby"); + _network->initTopic(_mqttPath, mqtt_topic_lock_ring, "standby"); _network->subscribe(_mqttPath, mqtt_topic_query_config); _network->subscribe(_mqttPath, mqtt_topic_query_lockstate); _network->subscribe(_mqttPath, mqtt_topic_query_battery); @@ -128,6 +129,7 @@ void NukiNetworkOpener::update() { _resetRingStateTs = 0; publishString(mqtt_topic_lock_binary_ring, "standby", true); + publishString(mqtt_topic_lock_ring, "standby", true); } } @@ -647,10 +649,25 @@ void NukiNetworkOpener::publishAuthorizationInfo(const std::list _lastRollingLog) { - _lastRollingLog = log.index; serializeJson(entry, _buffer, _bufferSize); publishString(mqtt_topic_lock_log_rolling, _buffer, true); publishInt(mqtt_topic_lock_log_rolling_last, log.index, true); + + if(log.loggingType == NukiOpener::LoggingType::DoorbellRecognition && _lastRollingLog > 0) + { + if((log.data[0] & 3) == 0) + { + Log->println(F("Nuki opener: Ring detected (Locked)")); + publishRing(true); + } + else + { + Log->println(F("Nuki opener: Ring detected (Open)")); + publishRing(false); + } + } + + _lastRollingLog = log.index; } } @@ -658,7 +675,7 @@ void NukiNetworkOpener::publishAuthorizationInfo(const std::list= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0) { - _statusUpdated = false; - _nextLockStateUpdateTs = ts + _intervalLockstate * 1000; updateKeyTurnerState(); + _nextLockStateUpdateTs = ts + _intervalLockstate * 1000; + _statusUpdated = false; _network->publishStatusUpdated(_statusUpdated); } if(_network->mqttConnectionState() == 2) @@ -448,7 +448,8 @@ void NukiOpenerWrapper::updateKeyTurnerState() } _retryLockstateCount = 0; - if(_statusUpdated && + if((!isPinValid() || !_publishAuthData) && + _statusUpdated && _keyTurnerState.lockState == NukiOpener::LockState::Locked && _lastKeyTurnerState.lockState == NukiOpener::LockState::Locked && _lastKeyTurnerState.nukiState == _keyTurnerState.nukiState) @@ -458,7 +459,8 @@ void NukiOpenerWrapper::updateKeyTurnerState() } else { - if(_keyTurnerState.lockState != _lastKeyTurnerState.lockState && + if((!isPinValid() || !_publishAuthData) && + _keyTurnerState.lockState != _lastKeyTurnerState.lockState && _keyTurnerState.lockState == NukiOpener::LockState::Open && _keyTurnerState.trigger == NukiOpener::Trigger::Manual) { @@ -3882,14 +3884,14 @@ void NukiOpenerWrapper::notify(Nuki::EventType eventType) { if(eventType == Nuki::EventType::KeyTurnerStatusReset) { - _newSignal = false; + _newSignal = 0; Log->println("KeyTurnerStatusReset"); } else if(eventType == Nuki::EventType::KeyTurnerStatusUpdated) { - if(!_statusUpdated && !_newSignal) + if(!_statusUpdated && _newSignal < 5) { - _newSignal = true; + _newSignal++; Log->println("KeyTurnerStatusUpdated"); _statusUpdated = true; _statusUpdatedTs = espMillis(); diff --git a/src/NukiOpenerWrapper.h b/src/NukiOpenerWrapper.h index 177efa9..f0c892f 100644 --- a/src/NukiOpenerWrapper.h +++ b/src/NukiOpenerWrapper.h @@ -135,7 +135,7 @@ private: bool _paired = false; bool _statusUpdated = false; - bool _newSignal = false; + int _newSignal = 0; bool _hasKeypad = false; bool _keypadEnabled = false; uint _maxKeypadCodeCount = 0; diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index e2ca06b..49aba9a 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -362,9 +362,9 @@ void NukiWrapper::update() if(_nukiOfficial->getStatusUpdated() || _statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0) { Log->println("Updating Lock state based on status, timer or query"); + updateKeyTurnerState(); _statusUpdated = false; _nextLockStateUpdateTs = ts + _intervalLockstate * 1000; - updateKeyTurnerState(); _network->publishStatusUpdated(_statusUpdated); } if(_network->mqttConnectionState() == 2) @@ -4000,14 +4000,14 @@ void NukiWrapper::notify(Nuki::EventType eventType) { if(eventType == Nuki::EventType::KeyTurnerStatusReset) { - _newSignal = false; + _newSignal = 0; Log->println("KeyTurnerStatusReset"); } else if(eventType == Nuki::EventType::KeyTurnerStatusUpdated) { - if(!_statusUpdated && !_newSignal) + if(!_statusUpdated && _newSignal < 5) { - _newSignal = true; + _newSignal++; Log->println("KeyTurnerStatusUpdated"); _statusUpdated = true; _statusUpdatedTs = espMillis(); diff --git a/src/NukiWrapper.h b/src/NukiWrapper.h index 890f7b4..2358e02 100644 --- a/src/NukiWrapper.h +++ b/src/NukiWrapper.h @@ -134,7 +134,7 @@ private: bool _pairedAsApp = false; bool _paired = false; bool _statusUpdated = false; - bool _newSignal = false; + int _newSignal = 0; bool _hasKeypad = false; bool _keypadEnabled = false; uint _maxKeypadCodeCount = 0; diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 9d6f3eb..790d387 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -1737,12 +1737,6 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) { _nukiOpener->disableHASS(); } - - _preferences->putString(preference_mqtt_hass_discovery, ""); - } - else if(_preferences->getString(preference_mqtt_hass_discovery, "") == "") - { - _preferences->putString(preference_mqtt_hass_discovery, "homeassistant"); } _preferences->putBool(preference_mqtt_hass_enabled, (value == "1")); Log->print(F("Setting changed: ")); @@ -1763,16 +1757,6 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) _nukiOpener->disableHASS(); } _preferences->putString(preference_mqtt_hass_discovery, value); - - if(value != "" && !_preferences->getBool(preference_mqtt_hass_enabled, false)) - { - _preferences->putBool(preference_mqtt_hass_enabled, true); - } - else if(value == "" && _preferences->getBool(preference_mqtt_hass_enabled, false)) - { - _preferences->putBool(preference_mqtt_hass_enabled, false); - } - Log->print(F("Setting changed: ")); Log->println(key); configChanged = true; @@ -3541,12 +3525,12 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request) printInputField(&response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true); printInputField(&response, "MQTTPASS", "MQTT Password", "*", 30, "", true, true); printInputField(&response, "MQTTPATH", "MQTT NukiHub Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, ""); - printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), ""); + printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), "chkHass"); response.print("
"); response.print("

Advanced MQTT Configuration

"); response.print(""); - printInputField(&response, "HASSDISCOVERY", "Home Assistant discovery topic (usually \"homeassistant\")", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, ""); + printInputField(&response, "HASSDISCOVERY", "Home Assistant discovery topic (usually \"homeassistant\")", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, "class=\"chkHass\""); if(_preferences->getBool(preference_opener_enabled, false)) { printCheckBox(&response, "OPENERCONT", "Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode", _preferences->getBool(preference_opener_continuous_mode), ""); @@ -3566,7 +3550,9 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request) response.print("* If no encryption is configured for the MQTT broker, leave empty.

"); response.print("
"); response.print(""); - response.print(""); + response.print(""); + response.print(""); + response.print(""); return response.endSend(); } From 7632a8cbcc5d621d9672e7dd4662228a956a4851 Mon Sep 17 00:00:00 2001 From: iranl Date: Mon, 4 Nov 2024 21:32:57 +0100 Subject: [PATCH 06/17] HTTP, MQTT and PSRAM fixes --- boards/nuki-esp32dev.json | 37 ++++++++++++++++++++++++ platformio.ini | 2 +- src/NukiNetwork.cpp | 9 ++++-- src/NukiNetwork.h | 1 + src/WebCfgServer.cpp | 60 +++++++++++++++++++++++++-------------- updater/platformio.ini | 2 +- 6 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 boards/nuki-esp32dev.json diff --git a/boards/nuki-esp32dev.json b/boards/nuki-esp32dev.json new file mode 100644 index 0000000..5b04731 --- /dev/null +++ b/boards/nuki-esp32dev.json @@ -0,0 +1,37 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32_out.ld" + }, + "core": "esp32", + "extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue", + "f_cpu": "240000000L", + "f_flash": "40000000L", + "flash_mode": "dio", + "mcu": "esp32", + "variant": "esp32" + }, + "connectivity": [ + "wifi", + "bluetooth", + "ethernet", + "can" + ], + "debug": { + "openocd_board": "esp-wroom-32.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif ESP32 Dev Module", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://en.wikipedia.org/wiki/ESP32", + "vendor": "AI Thinker" +} diff --git a/platformio.ini b/platformio.ini index fd5dd54..1785ee6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -69,7 +69,7 @@ monitor_filters = time [env:esp32] -board = esp32dev +board = nuki-esp32dev board_build.cmake_extra_args = -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release.defaults;sdkconfig.defaults.esp32" extra_scripts = diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 5755cf6..d15dad6 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -461,8 +461,13 @@ bool NukiNetwork::update() if(_lastMaintenanceTs == 0 || (ts - _lastMaintenanceTs) > 30000) { - publishULong(_maintenancePathPrefix, mqtt_topic_uptime, ts / 1000 / 60, true); - publishString(_maintenancePathPrefix, mqtt_topic_mqtt_connection_state, "online", true); + int64_t curUptime = ts / 1000 / 60; + if(curUptime > _publishedUpTime) + { + publishULong(_maintenancePathPrefix, mqtt_topic_uptime, curUptime, true); + _publishedUpTime = curUptime; + } + //publishString(_maintenancePathPrefix, mqtt_topic_mqtt_connection_state, "online", true); if(_lastMaintenanceTs == 0) { diff --git a/src/NukiNetwork.h b/src/NukiNetwork.h index 517d670..a867311 100644 --- a/src/NukiNetwork.h +++ b/src/NukiNetwork.h @@ -155,6 +155,7 @@ private: bool _connectReplyReceived = false; bool _firstDisconnected = true; + int64_t _publishedUpTime = 0; int64_t _nextReconnect = 0; char _mqttBrokerAddr[101] = {0}; char _mqttUser[31] = {0}; diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 790d387..61255a7 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -292,14 +292,31 @@ void WebCfgServer::initialize() { return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); } - if(_allowRestartToPortal) + String value = ""; + if(request->hasParam("CONFIRMTOKEN")) { - esp_err_t res = buildConfirmHtml(request, "Restarting. Connect to ESP access point (\"NukiHub\" with password \"NukiHubESP32\") to reconfigure Wi-Fi.", 0); - waitAndProcess(false, 1000); - _network->reconfigureDevice(); - return res; + const PsychicWebParameter* p = request->getParam("CONFIRMTOKEN"); + if(p->value() != "") + { + value = p->value(); + } } - return(ESP_OK); + else + { + return buildConfirmHtml(request, "No confirm code set.", 3, true); + } + if(value != _confirmCode) + { + return request->redirect("/"); + } + if(!_allowRestartToPortal) + { + return buildConfirmHtml(request, "Can't reset WiFi when network device is Ethernet", 3, true); + } + esp_err_t res = buildConfirmHtml(request, "Restarting. Connect to ESP access point (\"NukiHub\" with password \"NukiHubESP32\") to reconfigure Wi-Fi.", 0); + waitAndProcess(false, 1000); + _network->reconfigureDevice(); + return res; }); #endif _psychicServer->on("/unpairlock", HTTP_POST, [&](PsychicRequest *request) @@ -3363,37 +3380,37 @@ esp_err_t WebCfgServer::buildHtml(PsychicRequest *request) printParameter(&response, "Nuki Opener PIN status", openerState.c_str(), "", "openerPin"); } } - printParameter(&response, "Firmware", NUKI_HUB_VERSION, "/info", "firmware"); + printParameter(&response, "Firmware", NUKI_HUB_VERSION, "/info?", "firmware"); if(_preferences->getBool(preference_check_updates)) { - printParameter(&response, "Latest Firmware", _preferences->getString(preference_latest_version).c_str(), "/ota", "ota"); + printParameter(&response, "Latest Firmware", _preferences->getString(preference_latest_version).c_str(), "/ota?", "ota"); } response.print("

"); response.print("
    "); - buildNavigationMenuEntry(&response, "Network Configuration", "/ntwconfig"); - buildNavigationMenuEntry(&response, "MQTT Configuration", "/mqttconfig", _brokerConfigured ? "" : "Please configure MQTT broker"); - buildNavigationMenuEntry(&response, "Nuki Configuration", "/nukicfg"); - buildNavigationMenuEntry(&response, "Access Level Configuration", "/acclvl"); - buildNavigationMenuEntry(&response, "Credentials", "/cred", _pinsConfigured ? "" : "Please configure PIN"); - buildNavigationMenuEntry(&response, "GPIO Configuration", "/gpiocfg"); - buildNavigationMenuEntry(&response, "Firmware update", "/ota"); - buildNavigationMenuEntry(&response, "Import/Export Configuration", "/impexpcfg"); + buildNavigationMenuEntry(&response, "Network Configuration", "/ntwconfig?"); + buildNavigationMenuEntry(&response, "MQTT Configuration", "/mqttconfig?", _brokerConfigured ? "" : "Please configure MQTT broker"); + buildNavigationMenuEntry(&response, "Nuki Configuration", "/nukicfg?"); + buildNavigationMenuEntry(&response, "Access Level Configuration", "/acclvl?"); + buildNavigationMenuEntry(&response, "Credentials", "/cred?", _pinsConfigured ? "" : "Please configure PIN"); + buildNavigationMenuEntry(&response, "GPIO Configuration", "/gpiocfg?"); + buildNavigationMenuEntry(&response, "Firmware update", "/ota?"); + buildNavigationMenuEntry(&response, "Import/Export Configuration", "/impexpcfg?"); if(_preferences->getInt(preference_network_hardware, 0) == 11) { - buildNavigationMenuEntry(&response, "Custom Ethernet Configuration", "/custntw"); + buildNavigationMenuEntry(&response, "Custom Ethernet Configuration", "/custntw?"); } if (_preferences->getBool(preference_publish_debug_info, false)) { - buildNavigationMenuEntry(&response, "Advanced Configuration", "/advanced"); + buildNavigationMenuEntry(&response, "Advanced Configuration", "/advanced?"); } if(_preferences->getBool(preference_webserial_enabled, false)) { - buildNavigationMenuEntry(&response, "Open Webserial", "/webserial"); + buildNavigationMenuEntry(&response, "Open Webserial", "/webserial?"); } #ifndef CONFIG_IDF_TARGET_ESP32H2 if(_allowRestartToPortal) { - buildNavigationMenuEntry(&response, "Configure Wi-Fi", "/wifi"); + buildNavigationMenuEntry(&response, "Configure Wi-Fi", "/wifi?"); } #endif String rebooturl = "/reboot?CONFIRMTOKEN=" + _confirmCode; @@ -4002,7 +4019,8 @@ esp_err_t WebCfgServer::buildConfigureWifiHtml(PsychicRequest *request) buildHtmlHeader(&response); response.print("

    Wi-Fi

    "); response.print("Click confirm to remove saved WiFi settings and restart ESP into Wi-Fi configuration mode. After restart, connect to ESP access point to reconfigure Wi-Fi.

    "); - buildNavigationButton(&response, "Confirm", "/wifimanager"); + String wifiMgrUrl = "/wifimanager?CONFIRMTOKEN=" + _confirmCode; + buildNavigationButton(&response, "Confirm", wifiMgrUrl.c_str()); response.print(""); return response.endSend(); } diff --git a/updater/platformio.ini b/updater/platformio.ini index 4b98f2a..959c43e 100644 --- a/updater/platformio.ini +++ b/updater/platformio.ini @@ -63,7 +63,7 @@ monitor_filters = time [env:updater_esp32] -board = esp32dev +board = nuki-esp32dev extra_scripts = pre:pio_package_pre.py post:pio_package_post.py From b46d42ddd549ba3c4f67c7be5d387ba1f246a4be Mon Sep 17 00:00:00 2001 From: iranl Date: Mon, 4 Nov 2024 22:27:05 +0100 Subject: [PATCH 07/17] Update NukiNetwork.cpp --- src/NukiNetwork.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index d15dad6..3b80b22 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -1061,12 +1061,12 @@ void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, cha "Restart Nuki Hub", name, baseTopic, - String("~") + mqtt_topic_reset, + _lockPath + mqtt_topic_reset, deviceType, "", "", "diagnostic", - String("~") + mqtt_topic_reset, + _lockPath + mqtt_topic_reset, { { (char*)"ic", (char*)"mdi:restart" }, { (char*)"pl_on", (char*)"1" }, @@ -4070,4 +4070,4 @@ String NukiNetwork::localIP() { return _device->localIP(); } -#endif \ No newline at end of file +#endif From 1e7cefa77072f60875486f3679acef043090a0dc Mon Sep 17 00:00:00 2001 From: iranl Date: Mon, 4 Nov 2024 23:30:10 +0100 Subject: [PATCH 08/17] Update NukiNetwork.cpp --- src/NukiNetwork.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 3b80b22..100dac1 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -690,7 +690,7 @@ bool NukiNetwork::reconnect() for(const String& topic : _subscribedTopics) { - _device->mqttSubscribe(topic.c_str(), MQTT_QOS_LEVEL); + subscribe(topic.c_str(), MQTT_QOS_LEVEL); } publishString(_maintenancePathPrefix, mqtt_topic_mqtt_connection_state, "online", true); @@ -4052,6 +4052,8 @@ void NukiNetwork::timeZoneIdToString(const Nuki::TimeZoneId timeZoneId, char* st uint16_t NukiNetwork::subscribe(const char *topic, uint8_t qos) { + Log->print("Subscribing to MQTT topic: "); + Log->println(topic); return _device->mqttSubscribe(topic, qos); } From 67bf20eaa5e611185da0b0eeb4f167a7b01739dd Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 5 Nov 2024 21:25:07 +0100 Subject: [PATCH 09/17] MQTT Device refractor --- src/networkDevices/EthernetDevice.cpp | 59 ++---------- src/networkDevices/EthernetDevice.h | 7 -- src/networkDevices/NetworkDevice.cpp | 132 +++++++++++--------------- src/networkDevices/NetworkDevice.h | 10 +- src/networkDevices/WifiDevice.cpp | 50 +--------- src/networkDevices/WifiDevice.h | 6 -- 6 files changed, 72 insertions(+), 192 deletions(-) diff --git a/src/networkDevices/EthernetDevice.cpp b/src/networkDevices/EthernetDevice.cpp index f75277c..4dec1a0 100644 --- a/src/networkDevices/EthernetDevice.cpp +++ b/src/networkDevices/EthernetDevice.cpp @@ -1,17 +1,13 @@ #include "EthernetDevice.h" #include "../PreferencesKeys.h" #include "../Logger.h" -#ifndef NUKI_HUB_UPDATER -#include "../MqttTopics.h" -#include "espMqttClient.h" -#endif #include "../RestartReason.h" extern bool ethCriticalFailure; extern bool wifiFallback; EthernetDevice::EthernetDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration, const std::string& deviceName, uint8_t phy_addr, int power, int mdc, int mdio, eth_phy_type_t ethtype, eth_clock_mode_t clock_mode) - : NetworkDevice(hostname, ipConfiguration), + : NetworkDevice(hostname, preferences, ipConfiguration), _deviceName(deviceName), _phy_addr(phy_addr), _power(power), @@ -22,7 +18,9 @@ EthernetDevice::EthernetDevice(const String& hostname, Preferences* preferences, _useSpi(false), _preferences(preferences) { - init(); +#ifndef NUKI_HUB_UPDATER + NetworkDevice::init(); +#endif } EthernetDevice::EthernetDevice(const String &hostname, @@ -37,7 +35,7 @@ EthernetDevice::EthernetDevice(const String &hostname, int spi_miso, int spi_mosi, eth_phy_type_t ethtype) - : NetworkDevice(hostname, ipConfiguration), + : NetworkDevice(hostname, preferences, ipConfiguration), _deviceName(deviceName), _phy_addr(phy_addr), _cs(cs), @@ -50,52 +48,7 @@ EthernetDevice::EthernetDevice(const String &hostname, _useSpi(true), _preferences(preferences) { - init(); -} - -void EthernetDevice::init() -{ -#ifndef NUKI_HUB_UPDATER - size_t caLength = _preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE); - size_t crtLength = _preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE); - size_t keyLength = _preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE); - - _useEncryption = caLength > 1; // length is 1 when empty - - if(_useEncryption) - { - Log->println(F("MQTT over TLS.")); - _mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO); - _mqttClientSecure->setCACert(_ca); - if(crtLength > 1 && keyLength > 1) // length is 1 when empty - { - Log->println(F("MQTT with client certificate.")); - _mqttClientSecure->setCertificate(_cert); - _mqttClientSecure->setPrivateKey(_key); - } - } else - { - Log->println(F("MQTT without TLS.")); - _mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO); - } - - if(_preferences->getBool(preference_mqtt_log_enabled, false) || _preferences->getBool(preference_webserial_enabled, false)) - { - MqttLoggerMode mode; - - if(_preferences->getBool(preference_mqtt_log_enabled, false) && _preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb; - else if (_preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb; - else mode = MqttLoggerMode::MqttAndSerial; - - _path = new char[200]; - memset(_path, 0, sizeof(_path)); - - String pathStr = _preferences->getString(preference_mqtt_lock_path); - pathStr.concat(mqtt_topic_log); - strcpy(_path, pathStr.c_str()); - Log = new MqttLogger(*getMqttClient(), _path, mode); - } -#endif + NetworkDevice::init(); } const String EthernetDevice::deviceName() const diff --git a/src/networkDevices/EthernetDevice.h b/src/networkDevices/EthernetDevice.h index d5503c8..6d5251d 100644 --- a/src/networkDevices/EthernetDevice.h +++ b/src/networkDevices/EthernetDevice.h @@ -61,7 +61,6 @@ public: private: Preferences* _preferences; - void init(); void onDisconnected(); void onNetworkEvent(arduino_event_id_t event, arduino_event_info_t info); @@ -90,10 +89,4 @@ private: eth_phy_type_t _type; eth_clock_mode_t _clock_mode; bool _useSpi = false; - - #ifndef NUKI_HUB_UPDATER - char _ca[TLS_CA_MAX_SIZE] = {0}; - char _cert[TLS_CERT_MAX_SIZE] = {0}; - char _key[TLS_KEY_MAX_SIZE] = {0}; - #endif }; \ No newline at end of file diff --git a/src/networkDevices/NetworkDevice.cpp b/src/networkDevices/NetworkDevice.cpp index 9e65acd..4ad1d84 100644 --- a/src/networkDevices/NetworkDevice.cpp +++ b/src/networkDevices/NetworkDevice.cpp @@ -2,13 +2,52 @@ #include "NetworkDevice.h" #include "../Logger.h" -void NetworkDevice::printError() -{ - Log->print(F("Free Heap: ")); - Log->println(ESP.getFreeHeap()); -} - #ifndef NUKI_HUB_UPDATER +#include "../MqttTopics.h" +#include "espMqttClient.h" + +void NetworkDevice::init() +{ + size_t caLength = _preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE); + size_t crtLength = _preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE); + size_t keyLength = _preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE); + + _useEncryption = caLength > 1; // length is 1 when empty + + if(_useEncryption) + { + Log->println(F("MQTT over TLS.")); + _mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO); + _mqttClientSecure->setCACert(_ca); + if(crtLength > 1 && keyLength > 1) // length is 1 when empty + { + Log->println(F("MQTT with client certificate.")); + _mqttClientSecure->setCertificate(_cert); + _mqttClientSecure->setPrivateKey(_key); + } + } else + { + Log->println(F("MQTT without TLS.")); + _mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO); + } + + if(_preferences->getBool(preference_mqtt_log_enabled, false) || _preferences->getBool(preference_webserial_enabled, false)) + { + MqttLoggerMode mode; + + if(_preferences->getBool(preference_mqtt_log_enabled, false) && _preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb; + else if (_preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb; + else mode = MqttLoggerMode::MqttAndSerial; + + _path = new char[200]; + memset(_path, 0, sizeof(_path)); + + String pathStr = _preferences->getString(preference_mqtt_lock_path); + pathStr.concat(mqtt_topic_log); + strcpy(_path, pathStr.c_str()); + Log = new MqttLogger(*getMqttClient(), _path, mode); + } +} void NetworkDevice::update() { if (_mqttEnabled) @@ -19,38 +58,17 @@ void NetworkDevice::update() void NetworkDevice::mqttSetClientId(const char *clientId) { - if (_useEncryption) - { - _mqttClientSecure->setClientId(clientId); - } - else - { - _mqttClient->setClientId(clientId); - } + getMqttClient()->setClientId(clientId); } void NetworkDevice::mqttSetCleanSession(bool cleanSession) { - if (_useEncryption) - { - _mqttClientSecure->setCleanSession(cleanSession); - } - else - { - _mqttClient->setCleanSession(cleanSession); - } + getMqttClient()->setCleanSession(cleanSession); } void NetworkDevice::mqttSetKeepAlive(uint16_t keepAlive) { - if (_useEncryption) - { - _mqttClientSecure->setKeepAlive(keepAlive); - } - else - { - _mqttClient->setKeepAlive(keepAlive); - } + getMqttClient()->setKeepAlive(keepAlive); } uint16_t NetworkDevice::mqttPublish(const char *topic, uint8_t qos, bool retain, const char *payload) @@ -70,14 +88,7 @@ bool NetworkDevice::mqttConnected() const void NetworkDevice::mqttSetServer(const char *host, uint16_t port) { - if (_useEncryption) - { - _mqttClientSecure->setServer(host, port); - } - else - { - _mqttClient->setServer(host, port); - } + getMqttClient()->setServer(host, port); } bool NetworkDevice::mqttConnect() @@ -92,62 +103,27 @@ bool NetworkDevice::mqttDisconnect(bool force) void NetworkDevice::setWill(const char *topic, uint8_t qos, bool retain, const char *payload) { - if (_useEncryption) - { - _mqttClientSecure->setWill(topic, qos, retain, payload); - } - else - { - _mqttClient->setWill(topic, qos, retain, payload); - } + getMqttClient()->setWill(topic, qos, retain, payload); } void NetworkDevice::mqttSetCredentials(const char *username, const char *password) { - if (_useEncryption) - { - _mqttClientSecure->setCredentials(username, password); - } - else - { - _mqttClient->setCredentials(username, password); - } + getMqttClient()->setCredentials(username, password); } void NetworkDevice::mqttOnMessage(espMqttClientTypes::OnMessageCallback callback) { - if (_useEncryption) - { - _mqttClientSecure->onMessage(callback); - } - else - { - _mqttClient->onMessage(callback); - } + getMqttClient()->onMessage(callback); } void NetworkDevice::mqttOnConnect(espMqttClientTypes::OnConnectCallback callback) { - if(_useEncryption) - { - _mqttClientSecure->onConnect(callback); - } - else - { - _mqttClient->onConnect(callback); - } + getMqttClient()->onConnect(callback); } void NetworkDevice::mqttOnDisconnect(espMqttClientTypes::OnDisconnectCallback callback) { - if (_useEncryption) - { - _mqttClientSecure->onDisconnect(callback); - } - else - { - _mqttClient->onDisconnect(callback); - } + getMqttClient()->onDisconnect(callback); } uint16_t NetworkDevice::mqttSubscribe(const char *topic, uint8_t qos) diff --git a/src/networkDevices/NetworkDevice.h b/src/networkDevices/NetworkDevice.h index f5b2b11..2705245 100644 --- a/src/networkDevices/NetworkDevice.h +++ b/src/networkDevices/NetworkDevice.h @@ -10,8 +10,9 @@ class NetworkDevice { public: - explicit NetworkDevice(const String& hostname, const IPConfiguration* ipConfiguration) + explicit NetworkDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration) : _hostname(hostname), + _preferences(preferences), _ipConfiguration(ipConfiguration) {} @@ -19,7 +20,6 @@ public: virtual void initialize() = 0; virtual void reconfigure() = 0; - virtual void printError(); virtual void update(); virtual void scan(bool passive = false, bool async = true) = 0; @@ -57,8 +57,14 @@ protected: bool _useEncryption = false; bool _mqttEnabled = true; + + void init(); MqttClient *getMqttClient() const; + + char _ca[TLS_CA_MAX_SIZE] = {0}; + char _cert[TLS_CERT_MAX_SIZE] = {0}; + char _key[TLS_KEY_MAX_SIZE] = {0}; #endif const String _hostname; diff --git a/src/networkDevices/WifiDevice.cpp b/src/networkDevices/WifiDevice.cpp index bbacc52..460e566 100644 --- a/src/networkDevices/WifiDevice.cpp +++ b/src/networkDevices/WifiDevice.cpp @@ -3,59 +3,17 @@ #include "WifiDevice.h" #include "../PreferencesKeys.h" #include "../Logger.h" -#ifndef NUKI_HUB_UPDATER -#include "../MqttTopics.h" -#include "espMqttClient.h" -#endif #include "../RestartReason.h" WifiDevice::WifiDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration) - : NetworkDevice(hostname, ipConfiguration), + : NetworkDevice(hostname, preferences, ipConfiguration), _preferences(preferences) { - #ifndef NUKI_HUB_UPDATER - size_t caLength = preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE); - size_t crtLength = preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE); - size_t keyLength = preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE); - - _useEncryption = caLength > 1; // length is 1 when empty - - if(_useEncryption) - { - Log->println(F("MQTT over TLS.")); - _mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO); - _mqttClientSecure->setCACert(_ca); - if(crtLength > 1 && keyLength > 1) // length is 1 when empty - { - Log->println(F("MQTT with client certificate.")); - _mqttClientSecure->setCertificate(_cert); - _mqttClientSecure->setPrivateKey(_key); - } - } - else - { - Log->println(F("MQTT without TLS.")); - _mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO); - } - - if(preferences->getBool(preference_mqtt_log_enabled, false) || preferences->getBool(preference_webserial_enabled, false)) - { - MqttLoggerMode mode; - - if(preferences->getBool(preference_mqtt_log_enabled, false) && preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb; - else if (preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb; - else mode = MqttLoggerMode::MqttAndSerial; - _path = new char[200]; - memset(_path, 0, sizeof(_path)); - String pathStr = preferences->getString(preference_mqtt_lock_path); - pathStr.concat(mqtt_topic_log); - strcpy(_path, pathStr.c_str()); - Log = new MqttLogger(*getMqttClient(), _path, mode); - } - #endif +#ifndef NUKI_HUB_UPDATER + NetworkDevice::init(); +#endif } - const String WifiDevice::deviceName() const { return "Built-in Wi-Fi"; diff --git a/src/networkDevices/WifiDevice.h b/src/networkDevices/WifiDevice.h index e49d540..b08f6f6 100644 --- a/src/networkDevices/WifiDevice.h +++ b/src/networkDevices/WifiDevice.h @@ -45,10 +45,4 @@ private: uint8_t _connectedChannel = 0; uint8_t* _connectedBSSID; int64_t _disconnectTs = 0; - - #ifndef NUKI_HUB_UPDATER - char _ca[TLS_CA_MAX_SIZE] = {0}; - char _cert[TLS_CERT_MAX_SIZE] = {0}; - char _key[TLS_KEY_MAX_SIZE] = {0}; - #endif }; From 0813347d0f9c59b3328f7fd13006d394aaf67a69 Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 5 Nov 2024 15:35:55 +0100 Subject: [PATCH 10/17] Refractor HA discovery --- README.md | 1 + src/HomeAssistantDiscovery.cpp | 3054 ++++++++++++++++++++++++++++++++ src/HomeAssistantDiscovery.h | 43 + src/NukiNetwork.cpp | 2926 +----------------------------- src/NukiNetwork.h | 59 +- src/NukiNetworkLock.cpp | 47 - src/NukiNetworkLock.h | 2 - src/NukiNetworkOpener.cpp | 32 - src/NukiNetworkOpener.h | 2 - src/NukiOfficial.cpp | 2 +- src/NukiOpenerWrapper.cpp | 48 +- src/NukiOpenerWrapper.h | 5 - src/NukiWrapper.cpp | 40 +- src/NukiWrapper.h | 4 - src/PreferencesKeys.h | 5 +- src/WebCfgServer.cpp | 44 +- 16 files changed, 3169 insertions(+), 3145 deletions(-) create mode 100644 src/HomeAssistantDiscovery.cpp create mode 100644 src/HomeAssistantDiscovery.h diff --git a/README.md b/README.md index a0202a0..9110acf 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,7 @@ In a browser navigate to the IP address assigned to the ESP32. - MQTT Password : If using authentication on the MQTT broker set to the password belonging to a username with read/write rights on the MQTT broker, set to # to clear - MQTT NukiHub Path: Set to the preferred MQTT root topic for NukiHub, defaults to "nukihub". Make sure this topic is unique when using multiple ESP32 NukiHub devices - Enable Home Assistant auto discovery: Enable Home Assistant MQTT auto discovery. Will automatically create entities in Home Assistant for NukiHub and connected Nuki Lock and/or Opener when enabled. +- Use Home Assistant device based discovery: Use Home Assistant Device discovery instead of single component discovery. Recommended, but requires Home Assistant 2024.11 or newer. #### Advanced MQTT Configuration diff --git a/src/HomeAssistantDiscovery.cpp b/src/HomeAssistantDiscovery.cpp new file mode 100644 index 0000000..cd86bd3 --- /dev/null +++ b/src/HomeAssistantDiscovery.cpp @@ -0,0 +1,3054 @@ +void NukiWrapper::setupHASS(int type) +{ + if(_preferences->getUInt(preference_nuki_id_lock, 0) != _nukiConfig.nukiId) + { + return; + } + + String baseTopic = _preferences->getString(preference_mqtt_lock_path); + baseTopic.concat("/lock"); + char uidString[20]; + itoa(_nukiConfig.nukiId, uidString, 16); + + _network->publishHASSConfig((char*)"SmartLock", baseTopic.c_str(),(char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), hasDoorSensor(), _hasKeypad, _publishAuthData, (char*)"lock", (char*)"unlock", (char*)"unlatch"); + Log->println("HASS setup for lock completed."); +} + +void NukiWrapper::disableHASS() +{ + char uidString[20]; + itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16); + _network->removeHASSConfig(uidString); +} + +void NukiOpenerWrapper::setupHASS() +{ + if(_preferences->getUInt(preference_nuki_id_opener, 0) != _nukiConfig.nukiId) + { + return; + } + + String baseTopic = _preferences->getString(preference_mqtt_lock_path); + baseTopic.concat("/opener"); + char uidString[20]; + itoa(_nukiConfig.nukiId, uidString, 16); + + if(_preferences->getBool(preference_opener_continuous_mode, false)) + { + _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), _publishAuthData, _hasKeypad, (char*)"deactivateCM", (char*)"activateCM", (char*)"electricStrikeActuation"); + } + else + { + _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), _publishAuthData, _hasKeypad, (char*)"deactivateRTO", (char*)"activateRTO", (char*)"electricStrikeActuation"); + } + + _hassSetupCompleted = true; + + Log->println("HASS setup for opener completed."); +} + +void NukiOpenerWrapper::disableHASS() +{ + char uidString[20]; + itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16); + _network->removeHASSConfig(uidString); +} + +void NukiNetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction) +{ + String availabilityTopic = _preferences->getString(preference_mqtt_lock_path); + availabilityTopic.concat("/maintenance/mqttConnectionState"); + + _network->publishHASSConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, lockAction, unlockAction, openAction); + _network->publishHASSConfigAdditionalOpenerEntities(deviceType, baseTopic, name, uidString); + if(publishAuthData) + { + _network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString); + } + else + { + _network->removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); + _network->removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); + } + if(hasKeypad) + { + _network->publishHASSConfigKeypad(deviceType, baseTopic, name, uidString); + } + else + { + _network->removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString); + _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); + } +} + +void NukiNetworkOpener::removeHASSConfig(char* uidString) +{ + _network->removeHASSConfig(uidString); +} + +void NukiNetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction, + char *unlockAction, char *openAction) +{ + String availabilityTopic = _preferences->getString(preference_mqtt_lock_path); + availabilityTopic.concat("/maintenance/mqttConnectionState"); + _network->publishHASSConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, lockAction, unlockAction, openAction); + _network->publishHASSConfigAdditionalLockEntities(deviceType, baseTopic, name, uidString); + + if(hasDoorSensor) + { + _network->publishHASSConfigDoorSensor(deviceType, baseTopic, name, uidString); + } + else + { + _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"door_sensor", uidString); + } + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + _network->publishHASSWifiRssiConfig(deviceType, baseTopic, name, uidString); +#endif + + if(publishAuthData) + { + _network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString); + } + else + { + _network->removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); + _network->removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); + } + + if(hasKeypad) + { + _network->publishHASSConfigKeypad(deviceType, baseTopic, name, uidString); + } + else + { + _network->removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString); + _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); + } +} + +void NukiNetworkLock::removeHASSConfig(char *uidString) +{ + _network->removeHASSConfig(uidString); +} + +void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction) +{ + JsonDocument json; + json.clear(); + JsonObject dev = json["dev"].to(); + JsonArray ids = dev["ids"].to(); + ids.add(String("nuki_") + uidString); + json["dev"]["mf"] = "Nuki"; + json["dev"]["mdl"] = deviceType; + json["dev"]["name"] = name; + json["dev"]["sw"] = softwareVersion; + json["dev"]["hw"] = hardwareVersion; + + String cuUrl = _preferences->getString(preference_mqtt_hass_cu_url, ""); + + if (cuUrl != "") + { + json["dev"]["cu"] = cuUrl; + } + else + { + json["dev"]["cu"] = "http://" + _device->localIP(); + } + + json["~"] = baseTopic; + json["name"] = nullptr; + json["unique_id"] = String(uidString) + "_lock"; + json["cmd_t"] = String("~") + String(mqtt_topic_lock_action); + json["avty"][0]["t"] = availabilityTopic; + json["pl_lock"] = lockAction; + json["pl_unlk"] = unlockAction; + + uint32_t aclPrefs[17]; + _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); + + if((strcmp(deviceType, "SmartLock") == 0 && (int)aclPrefs[2]) || (strcmp(deviceType, "SmartLock") != 0 && (int)aclPrefs[11])) + { + json["pl_open"] = openAction; + } + + json["stat_t"] = String("~") + mqtt_topic_lock_ha_state; + json["stat_jam"] = "jammed"; + json["stat_locked"] = "locked"; + json["stat_locking"] = "locking"; + json["stat_unlocked"] = "unlocked"; + json["stat_unlocking"] = "unlocking"; + json["stat_open"] = "open"; + json["stat_opening"] = "opening"; + json["opt"] = "false"; + + serializeJson(json, _buffer, _bufferSize); + + String path = _preferences->getString(preference_mqtt_hass_discovery, "homeassistant"); + path.concat("/lock/"); + path.concat(uidString); + path.concat("/smartlock/config"); + + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + + // Battery critical + publishHassTopic("binary_sensor", + "battery_low", + uidString, + "_battery_low", + "Battery low", + name, + baseTopic, + String("~") + mqtt_topic_battery_basic_json, + deviceType, + "battery", + "", + "diagnostic", + "", + { + {(char*)"pl_on", (char*)"1"}, + {(char*)"pl_off", (char*)"0"}, + {(char*)"val_tpl", (char*)"{{value_json.critical}}" } + }); + + // Battery voltage + publishHassTopic("sensor", + "battery_voltage", + uidString, + "_battery_voltage", + "Battery voltage", + name, + baseTopic, + String("~") + mqtt_topic_battery_advanced_json, + deviceType, + "voltage", + "measurement", + "diagnostic", + "", + { + {(char*)"unit_of_meas", (char*)"V"}, + {(char*)"val_tpl", (char*)"{{value_json.batteryVoltage}}" } + }); + + // Trigger + publishHassTopic("sensor", + "trigger", + uidString, + "_trigger", + "Trigger", + name, + baseTopic, + String("~") + mqtt_topic_lock_trigger, + deviceType, + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" } }); + + // MQTT Connected + publishHassTopic("binary_sensor", + "mqtt_connected", + uidString, + "_mqtt_connected", + "MQTT connected", + name, + baseTopic, + _lockPath + mqtt_topic_mqtt_connection_state, + deviceType, + "", + "", + "diagnostic", + "", + { + {(char*)"pl_on", (char*)"online"}, + {(char*)"pl_off", (char*)"offline"}, + {(char*)"ic", (char*)"mdi:lan-connect"} + }); + + // Reset + publishHassTopic("switch", + "reset", + uidString, + "_reset", + "Restart Nuki Hub", + name, + baseTopic, + String("~") + mqtt_topic_reset, + deviceType, + "", + "", + "diagnostic", + String("~") + mqtt_topic_reset, + { + { (char*)"ic", (char*)"mdi:restart" }, + { (char*)"pl_on", (char*)"1" }, + { (char*)"pl_off", (char*)"0" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + + // Network device + publishHassTopic("sensor", + "network_device", + uidString, + "_network_device", + "Network device", + name, + baseTopic, + _lockPath + mqtt_topic_network_device, + deviceType, + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" }}); + + // Nuki Hub Webserver enabled + publishHassTopic("switch", + "webserver", + uidString, + "_webserver", + "Nuki Hub webserver enabled", + name, + baseTopic, + _lockPath + mqtt_topic_webserver_state, + deviceType, + "", + "", + "diagnostic", + _lockPath + mqtt_topic_webserver_action, + { + { (char*)"pl_on", (char*)"1" }, + { (char*)"pl_off", (char*)"0" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + + // Uptime + publishHassTopic("sensor", + "uptime", + uidString, + "_uptime", + "Uptime", + name, + baseTopic, + _lockPath + mqtt_topic_uptime, + deviceType, + "duration", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + { (char*)"unit_of_meas", (char*)"min"} + }); + + if(_preferences->getBool(preference_mqtt_log_enabled, false)) + { + // MQTT Log + publishHassTopic("sensor", + "mqtt_log", + uidString, + "_mqtt_log", + "MQTT Log", + name, + baseTopic, + _lockPath + mqtt_topic_log, + deviceType, + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" }}); + } + else + { + removeHassTopic((char*)"sensor", (char*)"mqtt_log", uidString); + } + + if(_offEnabled) + { + // Hybrid connected + String hybridPath = _lockPath; + hybridPath.concat("/lock"); + hybridPath.concat(mqtt_hybrid_state); + publishHassTopic("binary_sensor", + "hybrid_connected", + uidString, + "_hybrid_connected", + "Hybrid connected", + name, + baseTopic, + hybridPath, + deviceType, + "", + "", + "diagnostic", + "", + { + {(char*)"pl_on", (char*)"1"}, + {(char*)"pl_off", (char*)"0"}, + { (char*)"en", (char*)"true" } + }); + } + else + { + removeHassTopic((char*)"binary_sensor", (char*)"hybrid_connected", uidString); + } + + // Firmware version + publishHassTopic("sensor", + "firmware_version", + uidString, + "_firmware_version", + "Firmware version", + name, + baseTopic, + String("~") + mqtt_topic_info_firmware_version, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // Hardware version + publishHassTopic("sensor", + "hardware_version", + uidString, + "_hardware_version", + "Hardware version", + name, + baseTopic, + String("~") + mqtt_topic_info_hardware_version, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // Nuki Hub version + publishHassTopic("sensor", + "nuki_hub_version", + uidString, + "_nuki_hub_version", + "Nuki Hub version", + name, + baseTopic, + _lockPath + mqtt_topic_info_nuki_hub_version, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // Nuki Hub build + publishHassTopic("sensor", + "nuki_hub_build", + uidString, + "_nuki_hub_build", + "Nuki Hub build", + name, + baseTopic, + _lockPath + mqtt_topic_info_nuki_hub_build, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // Nuki Hub restart reason + publishHassTopic("sensor", + "nuki_hub_restart_reason", + uidString, + "_nuki_hub_restart_reason", + "Nuki Hub restart reason", + name, + baseTopic, + _lockPath + mqtt_topic_restart_reason_fw, + deviceType, + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" }}); + + // Nuki Hub restart reason ESP + publishHassTopic("sensor", + "nuki_hub_restart_reason_esp", + uidString, + "_nuki_hub_restart_reason_esp", + "Nuki Hub restart reason ESP", + name, + baseTopic, + _lockPath + mqtt_topic_restart_reason_esp, + deviceType, + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" }}); + + if(_checkUpdates) + { + // NUKI Hub latest + publishHassTopic("sensor", + "nuki_hub_latest", + uidString, + "_nuki_hub_latest", + "NUKI Hub latest", + name, + baseTopic, + _lockPath + mqtt_topic_info_nuki_hub_latest, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // NUKI Hub update + char latest_version_topic[250]; + _lockPath.toCharArray(latest_version_topic,_lockPath.length() + 1); + strcat(latest_version_topic, mqtt_topic_info_nuki_hub_latest); + + if(!_updateFromMQTT) + { + publishHassTopic("update", + "nuki_hub_update", + uidString, + "_nuki_hub_update", + "NUKI Hub firmware update", + name, + baseTopic, + _lockPath + mqtt_topic_info_nuki_hub_version, + deviceType, + "firmware", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + { (char*)"ent_pic", (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/master/icon/favicon-32x32.png" }, + { (char*)"rel_u", (char*)GITHUB_LATEST_RELEASE_URL }, + { (char*)"l_ver_t", (char*)latest_version_topic } + }); + } + else + { + publishHassTopic("update", + "nuki_hub_update", + uidString, + "_nuki_hub_update", + "NUKI Hub firmware update", + name, + baseTopic, + _lockPath + mqtt_topic_info_nuki_hub_version, + deviceType, + "firmware", + "", + "diagnostic", + _lockPath + mqtt_topic_update, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_inst", (char*)"1" }, + { (char*)"ent_pic", (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/master/icon/favicon-32x32.png" }, + { (char*)"rel_u", (char*)GITHUB_LATEST_RELEASE_URL }, + { (char*)"l_ver_t", (char*)latest_version_topic } + }); + } + } + else + { + removeHassTopic((char*)"sensor", (char*)"nuki_hub_latest", uidString); + removeHassTopic((char*)"update", (char*)"nuki_hub_update", uidString); + } + + // Nuki Hub IP Address + publishHassTopic("sensor", + "nuki_hub_ip", + uidString, + "_nuki_hub_ip", + "Nuki Hub IP", + name, + baseTopic, + _lockPath + mqtt_topic_info_nuki_hub_ip, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:ip"} + }); + + // Query Lock State + publishHassTopic("button", + "query_lockstate", + uidString, + "_query_lockstate", + "Query lock state", + name, + baseTopic, + "", + deviceType, + "", + "", + "diagnostic", + String("~") + mqtt_topic_query_lockstate, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"1" } + }); + + // Query Config + publishHassTopic("button", + "query_config", + uidString, + "_query_config", + "Query config", + name, + baseTopic, + "", + deviceType, + "", + "", + "diagnostic", + String("~") + mqtt_topic_query_config, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"1" } + }); + + // Query Lock State Command result + publishHassTopic("button", + "query_commandresult", + uidString, + "_query_commandresult", + "Query lock state command result", + name, + baseTopic, + "", + deviceType, + "", + "", + "diagnostic", + String("~") + mqtt_topic_query_lockstate_command_result, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"1" } + }); + + publishHassTopic("sensor", + "bluetooth_signal_strength", + uidString, + "_bluetooth_signal_strength", + "Bluetooth signal strength", + name, + baseTopic, + String("~") + mqtt_topic_lock_rssi, + deviceType, + "signal_strength", + "measurement", + "diagnostic", + "", + { {(char*)"unit_of_meas", (char*)"dBm"} }); +} + +void NukiNetwork::publishHASSConfigAdditionalLockEntities(char *deviceType, const char *baseTopic, char *name, char *uidString) +{ + uint32_t aclPrefs[17]; + _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); + + uint32_t basicLockConfigAclPrefs[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t advancedLockConfigAclPrefs[22] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + if(_preferences->getBool(preference_conf_info_enabled, true)) + { + _preferences->getBytes(preference_conf_lock_basic_acl, &basicLockConfigAclPrefs, sizeof(basicLockConfigAclPrefs)); + _preferences->getBytes(preference_conf_lock_advanced_acl, &advancedLockConfigAclPrefs, sizeof(advancedLockConfigAclPrefs)); + } + + if((int)aclPrefs[2]) + { + // Unlatch + publishHassTopic("button", + "unlatch", + uidString, + "_unlatch", + "Open", + name, + baseTopic, + "", + deviceType, + "", + "", + "", + String("~") + mqtt_topic_lock_action, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"unlatch" } + }); + } + else + { + removeHassTopic((char*)"button", (char*)"unlatch", uidString); + } + + if((int)aclPrefs[3]) + { + // Lock 'n' Go + publishHassTopic("button", + "lockngo", + uidString, + "_lockngo", + "Lock 'n' Go", + name, + baseTopic, + "", + deviceType, + "", + "", + "", + String("~") + mqtt_topic_lock_action, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"lockNgo" } + }); + } + else + { + removeHassTopic((char*)"button", (char*)"lockngo", uidString); + } + + if((int)aclPrefs[4]) + { + // Lock 'n' Go with unlatch + publishHassTopic("button", + "lockngounlatch", + uidString, + "_lockngounlatch", + "Lock 'n' Go with unlatch", + name, + baseTopic, + "", + deviceType, + "", + "", + "", + String("~") + mqtt_topic_lock_action, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"lockNgoUnlatch" } + }); + } + else + { + removeHassTopic((char*)"button", (char*)"lockngounlatch", uidString); + } + + // Query Battery + publishHassTopic("button", + "query_battery", + uidString, + "_query_battery", + "Query battery", + name, + baseTopic, + "", + deviceType, + "", + "", + "diagnostic", + String("~") + mqtt_topic_query_battery, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"1" } + }); + + if((int)basicLockConfigAclPrefs[6] == 1) + { + // LED enabled + publishHassTopic("switch", + "led_enabled", + uidString, + "_led_enabled", + "LED enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"ic", (char*)"mdi:led-variant-on" }, + { (char*)"pl_on", (char*)"{ \"ledEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"ledEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.ledEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"led_enabled", uidString); + } + + if((int)basicLockConfigAclPrefs[5] == 1) + { + // Button enabled + publishHassTopic("switch", + "button_enabled", + uidString, + "_button_enabled", + "Button enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"ic", (char*)"mdi:radiobox-marked" }, + { (char*)"pl_on", (char*)"{ \"buttonEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"buttonEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.buttonEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"button_enabled", uidString); + } + + if((int)advancedLockConfigAclPrefs[19] == 1) + { + // Auto Lock + publishHassTopic("switch", + "auto_lock", + uidString, + "_auto_lock", + "Auto lock", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"autoLockEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"autoLockEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.autoLockEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"auto_lock", uidString); + } + + if((int)advancedLockConfigAclPrefs[12] == 1) + { + // Auto Unlock + publishHassTopic("switch", + "auto_unlock", + uidString, + "_auto_unlock", + "Auto unlock", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"autoUnLockDisabled\": \"0\"}" }, + { (char*)"pl_off", (char*)"{ \"autoUnLockDisabled\": \"1\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.autoUnLockDisabled}}" }, + { (char*)"stat_on", (char*)"0" }, + { (char*)"stat_off", (char*)"1" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"auto_unlock", uidString); + } + + if((int)basicLockConfigAclPrefs[13] == 1) + { + // Double lock + publishHassTopic("switch", + "double_lock", + uidString, + "_double_lock", + "Double lock", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"singleLock\": \"0\"}" }, + { (char*)"pl_off", (char*)"{ \"singleLock\": \"1\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.singleLock}}" }, + { (char*)"stat_on", (char*)"0" }, + { (char*)"stat_off", (char*)"1" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"double_lock", uidString); + } + + publishHassTopic("sensor", + "battery_level", + uidString, + "_battery_level", + "Battery level", + name, + baseTopic, + String("~") + mqtt_topic_battery_basic_json, + deviceType, + "battery", + "measurement", + "diagnostic", + "", + { + {(char*)"unit_of_meas", (char*)"%"}, + {(char*)"val_tpl", (char*)"{{value_json.level}}" } + }); + + if((int)basicLockConfigAclPrefs[7] == 1) + { + publishHassTopic("number", + "led_brightness", + uidString, + "_led_brightness", + "LED brightness", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"ic", (char*)"mdi:brightness-6" }, + { (char*)"cmd_tpl", (char*)"{ \"ledBrightness\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.ledBrightness}}" }, + { (char*)"min", (char*)"0" }, + { (char*)"max", (char*)"5" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"led_brightness", uidString); + } + + if((int)basicLockConfigAclPrefs[3] == 1) + { + // Auto Unlatch + publishHassTopic("switch", + "auto_unlatch", + uidString, + "_auto_unlatch", + "Auto unlatch", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"autoUnlatch\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"autoUnlatch\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.autoUnlatch}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"auto_unlatch", uidString); + } + + if((int)basicLockConfigAclPrefs[4] == 1) + { + // Pairing enabled + publishHassTopic("switch", + "pairing_enabled", + uidString, + "_pairing_enabled", + "Pairing enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"pairingEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"pairingEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.pairingEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"pairing_enabled", uidString); + } + + if((int)basicLockConfigAclPrefs[8] == 1) + { + publishHassTopic("number", + "timezone_offset", + uidString, + "_timezone_offset", + "Timezone offset", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"ic", (char*)"mdi:timer-cog-outline" }, + { (char*)"cmd_tpl", (char*)"{ \"timeZoneOffset\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.timeZoneOffset}}" }, + { (char*)"min", (char*)"0" }, + { (char*)"max", (char*)"60" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"timezone_offset", uidString); + } + + if((int)basicLockConfigAclPrefs[9] == 1) + { + // DST Mode + publishHassTopic("switch", + "dst_mode", + uidString, + "_dst_mode", + "DST mode European", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"dstMode\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"dstMode\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.dstMode}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"dst_mode", uidString); + } + + if((int)basicLockConfigAclPrefs[10] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_fob_action_1", "Fob action 1", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction1}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction1\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Unlock"; + json["options"][2] = "Lock"; + json["options"][3] = "Lock n Go"; + json["options"][4] = "Intelligent"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "fob_action_1", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"fob_action_1", uidString); + } + + if((int)basicLockConfigAclPrefs[11] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_fob_action_2", "Fob action 2", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction2}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction2\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Unlock"; + json["options"][2] = "Lock"; + json["options"][3] = "Lock n Go"; + json["options"][4] = "Intelligent"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "fob_action_2", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"fob_action_2", uidString); + } + + if((int)basicLockConfigAclPrefs[12] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_fob_action_3", "Fob action 3", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction3}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction3\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Unlock"; + json["options"][2] = "Lock"; + json["options"][3] = "Lock n Go"; + json["options"][4] = "Intelligent"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "fob_action_3", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"fob_action_3", uidString); + } + + if((int)basicLockConfigAclPrefs[14] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_advertising_mode", "Advertising mode", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.advertisingMode}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"advertisingMode\": \"{{ value }}\" }" }}); + json["options"][0] = "Automatic"; + json["options"][1] = "Normal"; + json["options"][2] = "Slow"; + json["options"][3] = "Slowest"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "advertising_mode", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"advertising_mode", uidString); + } + + if((int)basicLockConfigAclPrefs[15] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_timezone", "Timezone", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.timeZone}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"timeZone\": \"{{ value }}\" }" }}); + json["options"][0] = "Africa/Cairo"; + json["options"][1] = "Africa/Lagos"; + json["options"][2] = "Africa/Maputo"; + json["options"][3] = "Africa/Nairobi"; + json["options"][4] = "America/Anchorage"; + json["options"][5] = "America/Argentina/Buenos_Aires"; + json["options"][6] = "America/Chicago"; + json["options"][7] = "America/Denver"; + json["options"][8] = "America/Halifax"; + json["options"][9] = "America/Los_Angeles"; + json["options"][10] = "America/Manaus"; + json["options"][11] = "America/Mexico_City"; + json["options"][12] = "America/New_York"; + json["options"][13] = "America/Phoenix"; + json["options"][14] = "America/Regina"; + json["options"][15] = "America/Santiago"; + json["options"][16] = "America/Sao_Paulo"; + json["options"][17] = "America/St_Johns"; + json["options"][18] = "Asia/Bangkok"; + json["options"][19] = "Asia/Dubai"; + json["options"][20] = "Asia/Hong_Kong"; + json["options"][21] = "Asia/Jerusalem"; + json["options"][22] = "Asia/Karachi"; + json["options"][23] = "Asia/Kathmandu"; + json["options"][24] = "Asia/Kolkata"; + json["options"][25] = "Asia/Riyadh"; + json["options"][26] = "Asia/Seoul"; + json["options"][27] = "Asia/Shanghai"; + json["options"][28] = "Asia/Tehran"; + json["options"][29] = "Asia/Tokyo"; + json["options"][30] = "Asia/Yangon"; + json["options"][31] = "Australia/Adelaide"; + json["options"][32] = "Australia/Brisbane"; + json["options"][33] = "Australia/Darwin"; + json["options"][34] = "Australia/Hobart"; + json["options"][35] = "Australia/Perth"; + json["options"][36] = "Australia/Sydney"; + json["options"][37] = "Europe/Berlin"; + json["options"][38] = "Europe/Helsinki"; + json["options"][39] = "Europe/Istanbul"; + json["options"][40] = "Europe/London"; + json["options"][41] = "Europe/Moscow"; + json["options"][42] = "Pacific/Auckland"; + json["options"][43] = "Pacific/Guam"; + json["options"][44] = "Pacific/Honolulu"; + json["options"][45] = "Pacific/Pago_Pago"; + json["options"][46] = "None"; + + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "timezone", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"timezone", uidString); + } + + if((int)advancedLockConfigAclPrefs[0] == 1) + { + publishHassTopic("number", + "unlocked_position_offset_degrees", + uidString, + "_unlocked_position_offset_degrees", + "Unlocked position offset degrees", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"unlockedPositionOffsetDegrees\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.unlockedPositionOffsetDegrees}}" }, + { (char*)"min", (char*)"-90" }, + { (char*)"max", (char*)"180" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"unlocked_position_offset_degrees", uidString); + } + + if((int)advancedLockConfigAclPrefs[1] == 1) + { + publishHassTopic("number", + "locked_position_offset_degrees", + uidString, + "_locked_position_offset_degrees", + "Locked position offset degrees", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"lockedPositionOffsetDegrees\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.lockedPositionOffsetDegrees}}" }, + { (char*)"min", (char*)"-180" }, + { (char*)"max", (char*)"90" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"locked_position_offset_degrees", uidString); + } + + if((int)advancedLockConfigAclPrefs[2] == 1) + { + publishHassTopic("number", + "single_locked_position_offset_degrees", + uidString, + "_single_locked_position_offset_degrees", + "Single locked position offset degrees", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"singleLockedPositionOffsetDegrees\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.singleLockedPositionOffsetDegrees}}" }, + { (char*)"min", (char*)"-180" }, + { (char*)"max", (char*)"180" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"single_locked_position_offset_degrees", uidString); + } + + if((int)advancedLockConfigAclPrefs[3] == 1) + { + publishHassTopic("number", + "unlocked_locked_transition_offset_degrees", + uidString, + "_unlocked_locked_transition_offset_degrees", + "Unlocked to locked transition offset degrees", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"unlockedToLockedTransitionOffsetDegrees\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.unlockedToLockedTransitionOffsetDegrees}}" }, + { (char*)"min", (char*)"-180" }, + { (char*)"max", (char*)"180" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"unlocked_locked_transition_offset_degrees", uidString); + } + + if((int)advancedLockConfigAclPrefs[4] == 1) + { + publishHassTopic("number", + "lockngo_timeout", + uidString, + "_lockngo_timeout", + "Lock n Go timeout", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"lockNgoTimeout\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.lockNgoTimeout}}" }, + { (char*)"min", (char*)"5" }, + { (char*)"max", (char*)"60" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"lockngo_timeout", uidString); + } + + if((int)advancedLockConfigAclPrefs[5] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_single_button_press_action", "Single button press action", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.singleButtonPressAction}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"singleButtonPressAction\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Intelligent"; + json["options"][2] = "Unlock"; + json["options"][3] = "Lock"; + json["options"][4] = "Unlatch"; + json["options"][5] = "Lock n Go"; + json["options"][6] = "Show Status"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "single_button_press_action", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"single_button_press_action", uidString); + } + + if((int)advancedLockConfigAclPrefs[6] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_double_button_press_action", "Double button press action", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.doubleButtonPressAction}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"doubleButtonPressAction\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Intelligent"; + json["options"][2] = "Unlock"; + json["options"][3] = "Lock"; + json["options"][4] = "Unlatch"; + json["options"][5] = "Lock n Go"; + json["options"][6] = "Show Status"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "double_button_press_action", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"double_button_press_action", uidString); + } + + if((int)advancedLockConfigAclPrefs[7] == 1) + { + // Detached cylinder + publishHassTopic("switch", + "detached_cylinder", + uidString, + "_detached_cylinder", + "Detached cylinder", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"detachedCylinder\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"detachedCylinder\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.detachedCylinder}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"detached_cylinder", uidString); + } + + if((int)advancedLockConfigAclPrefs[8] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_battery_type", "Battery type", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.batteryType}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"batteryType\": \"{{ value }}\" }" }}); + json["options"][0] = "Alkali"; + json["options"][1] = "Accumulators"; + json["options"][2] = "Lithium"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "battery_type", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"battery_type", uidString); + } + + if((int)advancedLockConfigAclPrefs[9] == 1) + { + // Automatic battery type detection + publishHassTopic("switch", + "automatic_battery_type_detection", + uidString, + "_automatic_battery_type_detection", + "Automatic battery type detection", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"automaticBatteryTypeDetection\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"automaticBatteryTypeDetection\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.automaticBatteryTypeDetection}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"automatic_battery_type_detection", uidString); + } + + if((int)advancedLockConfigAclPrefs[10] == 1) + { + publishHassTopic("number", + "unlatch_duration", + uidString, + "_unlatch_duration", + "Unlatch duration", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"unlatchDuration\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.unlatchDuration}}" }, + { (char*)"min", (char*)"1" }, + { (char*)"max", (char*)"30" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"unlatch_duration", uidString); + } + + if((int)advancedLockConfigAclPrefs[11] == 1) + { + publishHassTopic("number", + "auto_lock_timeout", + uidString, + "_auto_lock_timeout", + "Auto lock timeout", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"autoLockTimeOut\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.autoLockTimeOut}}" }, + { (char*)"min", (char*)"30" }, + { (char*)"max", (char*)"1800" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"auto_lock_timeout", uidString); + } + + if((int)advancedLockConfigAclPrefs[13] == 1) + { + // Nightmode enabled + publishHassTopic("switch", + "nightmode_enabled", + uidString, + "_nightmode_enabled", + "Nightmode enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"nightModeEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"nightModeEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.nightModeEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"nightmode_enabled", uidString); + } + + if((int)advancedLockConfigAclPrefs[14] == 1) + { + // Nightmode start time + publishHassTopic("text", + "nightmode_start_time", + uidString, + "_nightmode_start_time", + "Nightmode start time", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pattern", (char*)"([0-1][0-9]|2[0-3]):[0-5][0-9]" }, + { (char*)"cmd_tpl", (char*)"{ \"nightModeStartTime\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.nightModeStartTime}}" }, + { (char*)"min", (char*)"5" }, + { (char*)"max", (char*)"5" } + }); + } + else + { + removeHassTopic((char*)"text", (char*)"nightmode_start_time", uidString); + } + + if((int)advancedLockConfigAclPrefs[15] == 1) + { + // Nightmode end time + publishHassTopic("text", + "nightmode_end_time", + uidString, + "_nightmode_end_time", + "Nightmode end time", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pattern", (char*)"([0-1][0-9]|2[0-3]):[0-5][0-9]" }, + { (char*)"cmd_tpl", (char*)"{ \"nightModeEndTime\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.nightModeEndTime}}" }, + { (char*)"min", (char*)"5" }, + { (char*)"max", (char*)"5" } + }); + } + else + { + removeHassTopic((char*)"text", (char*)"nightmode_end_time", uidString); + } + + if((int)advancedLockConfigAclPrefs[16] == 1) + { + // Nightmode Auto Lock + publishHassTopic("switch", + "nightmode_auto_lock", + uidString, + "_nightmode_auto_lock", + "Nightmode auto lock", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"nightModeAutoLockEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"nightModeAutoLockEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.nightModeAutoLockEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"nightmode_auto_lock", uidString); + } + + if((int)advancedLockConfigAclPrefs[17] == 1) + { + // Nightmode Auto Unlock + publishHassTopic("switch", + "nightmode_auto_unlock", + uidString, + "_nightmode_auto_unlock", + "Nightmode auto unlock", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"nightModeAutoUnlockDisabled\": \"0\"}" }, + { (char*)"pl_off", (char*)"{ \"nightModeAutoUnlockDisabled\": \"1\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.nightModeAutoUnlockDisabled}}" }, + { (char*)"stat_on", (char*)"0" }, + { (char*)"stat_off", (char*)"1" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"nightmode_auto_unlock", uidString); + } + + if((int)advancedLockConfigAclPrefs[18] == 1) + { + // Nightmode immediate lock on start + publishHassTopic("switch", + "nightmode_immediate_lock_start", + uidString, + "_nightmode_immediate_lock_start", + "Nightmode immediate lock on start", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"nightModeImmediateLockOnStart\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"nightModeImmediateLockOnStart\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.nightModeImmediateLockOnStart}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"nightmode_immediate_lock_start", uidString); + } + + if((int)advancedLockConfigAclPrefs[20] == 1) + { + // Immediate auto lock enabled + publishHassTopic("switch", + "immediate_auto_lock_enabled", + uidString, + "_immediate_auto_lock_enabled", + "Immediate auto lock enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"immediateAutoLockEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"immediateAutoLockEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.immediateAutoLockEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"immediate_auto_lock_enabled", uidString); + } + + if((int)advancedLockConfigAclPrefs[21] == 1) + { + // Auto update enabled + publishHassTopic("switch", + "auto_update_enabled", + uidString, + "_auto_update_enabled", + "Auto update enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"autoUpdateEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"autoUpdateEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.autoUpdateEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"auto_update_enabled", uidString); + } +} + +void NukiNetwork::publishHASSConfigDoorSensor(char *deviceType, const char *baseTopic, char *name, char *uidString) +{ + publishHassTopic("binary_sensor", + "door_sensor", + uidString, + "_door_sensor", + "Door sensor", + name, + baseTopic, + String("~") + mqtt_topic_lock_door_sensor_state, + deviceType, + "door", + "", + "", + "", + { + {(char*)"pl_on", (char*)"doorOpened"}, + {(char*)"pl_off", (char*)"doorClosed"}, + {(char*)"pl_not_avail", (char*)"unavailable"} + }); +} + +void NukiNetwork::publishHASSConfigAdditionalOpenerEntities(char *deviceType, const char *baseTopic, char *name, char *uidString) +{ + uint32_t aclPrefs[17]; + _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); + uint32_t basicOpenerConfigAclPrefs[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t advancedOpenerConfigAclPrefs[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + if(_preferences->getBool(preference_conf_info_enabled, true)) + { + _preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs)); + _preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs)); + } + + if((int)aclPrefs[11]) + { + // Unlatch + publishHassTopic("button", + "unlatch", + uidString, + "_unlatch", + "Open", + name, + baseTopic, + "", + deviceType, + "", + "", + "", + String("~") + mqtt_topic_lock_action, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"electricStrikeActuation" } + }); + } + else + { + removeHassTopic((char*)"button", (char*)"unlatch", uidString); + } + + publishHassTopic("binary_sensor", + "continuous_mode", + uidString, + "_continuous_mode", + "Continuous mode", + name, + baseTopic, + String("~") + mqtt_topic_lock_continuous_mode, + deviceType, + "lock", + "", + "", + "", + { + {(char*)"pl_on", (char*)"on"}, + {(char*)"pl_off", (char*)"off"} + }); + + if((int)aclPrefs[12] == 1 && (int)aclPrefs[13] == 1) + { + publishHassTopic("switch", + "continuous_mode", + uidString, + "_continuous_mode", + "Continuous mode", + name, + baseTopic, + String("~") + mqtt_topic_lock_continuous_mode, + deviceType, + "", + "", + "", + String("~") + mqtt_topic_lock_action, + { + { (char*)"en", (char*)"true" }, + {(char*)"stat_on", (char*)"on"}, + {(char*)"stat_off", (char*)"off"}, + {(char*)"pl_on", (char*)"activateCM"}, + {(char*)"pl_off", (char*)"deactivateCM"} + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"continuous_mode", uidString); + } + + publishHassTopic("binary_sensor", + "ring_detect", + uidString, + "_ring_detect", + "Ring detect", + name, + baseTopic, + String("~") + mqtt_topic_lock_binary_ring, + deviceType, + "sound", + "", + "", + "", + { + {(char*)"pl_on", (char*)"ring"}, + {(char*)"pl_off", (char*)"standby"} + }); + + JsonDocument json; + json = createHassJson(uidString, "_ring_event", "Ring", name, baseTopic, String("~") + mqtt_topic_lock_ring, deviceType, "doorbell", "", "", "", {{(char*)"val_tpl", (char*)"{ \"event_type\": \"{{ value }}\" }"}}); + json["event_types"][0] = "ring"; + json["event_types"][1] = "ringlocked"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("event", "ring", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + + if((int)basicOpenerConfigAclPrefs[5] == 1) + { + // LED enabled + publishHassTopic("switch", + "led_enabled", + uidString, + "_led_enabled", + "LED enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"ic", (char*)"mdi:led-variant-on" }, + { (char*)"pl_on", (char*)"{ \"ledEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"ledEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.ledEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"led_enabled", uidString); + } + + if((int)basicOpenerConfigAclPrefs[4] == 1) + { + // Button enabled + publishHassTopic("switch", + "button_enabled", + uidString, + "_button_enabled", + "Button enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"ic", (char*)"mdi:radiobox-marked" }, + { (char*)"pl_on", (char*)"{ \"buttonEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"buttonEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.buttonEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"button_enabled", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[15] == 1) + { + publishHassTopic("number", + "sound_level", + uidString, + "_sound_level", + "Sound level", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"ic", (char*)"mdi:volume-source" }, + { (char*)"cmd_tpl", (char*)"{ \"soundLevel\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.soundLevel}}" }, + { (char*)"min", (char*)"0" }, + { (char*)"max", (char*)"255" }, + { (char*)"mode", (char*)"slider" }, + { (char*)"step", (char*)"25.5" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"sound_level", uidString); + } + + if((int)basicOpenerConfigAclPrefs[3] == 1) + { + // Pairing enabled + publishHassTopic("switch", + "pairing_enabled", + uidString, + "_pairing_enabled", + "Pairing enabled", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"pairingEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"pairingEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.pairingEnabled}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"pairing_enabled", uidString); + } + + if((int)basicOpenerConfigAclPrefs[6] == 1) + { + publishHassTopic("number", + "timezone_offset", + uidString, + "_timezone_offset", + "Timezone offset", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"ic", (char*)"mdi:timer-cog-outline" }, + { (char*)"cmd_tpl", (char*)"{ \"timeZoneOffset\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.timeZoneOffset}}" }, + { (char*)"min", (char*)"0" }, + { (char*)"max", (char*)"60" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"timezone_offset", uidString); + } + + if((int)basicOpenerConfigAclPrefs[7] == 1) + { + // DST Mode + publishHassTopic("switch", + "dst_mode", + uidString, + "_dst_mode", + "DST mode European", + name, + baseTopic, + String("~") + mqtt_topic_config_basic_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"dstMode\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"dstMode\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.dstMode}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"dst_mode", uidString); + } + + if((int)basicOpenerConfigAclPrefs[8] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_fob_action_1", "Fob action 1", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction1}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction1\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Toggle RTO"; + json["options"][2] = "Activate RTO"; + json["options"][3] = "Deactivate RTO"; + json["options"][4] = "Open"; + json["options"][5] = "Ring"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "fob_action_1", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"fob_action_1", uidString); + } + + if((int)basicOpenerConfigAclPrefs[9] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_fob_action_2", "Fob action 2", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction2}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction2\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Toggle RTO"; + json["options"][2] = "Activate RTO"; + json["options"][3] = "Deactivate RTO"; + json["options"][4] = "Open"; + json["options"][5] = "Ring"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "fob_action_2", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"fob_action_2", uidString); + } + + if((int)basicOpenerConfigAclPrefs[10] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_fob_action_3", "Fob action 3", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction3}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction3\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Toggle RTO"; + json["options"][2] = "Activate RTO"; + json["options"][3] = "Deactivate RTO"; + json["options"][4] = "Open"; + json["options"][5] = "Ring"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "fob_action_3", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"fob_action_3", uidString); + } + + if((int)basicOpenerConfigAclPrefs[12] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_advertising_mode", "Advertising mode", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.advertisingMode}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"advertisingMode\": \"{{ value }}\" }" }}); + json["options"][0] = "Automatic"; + json["options"][1] = "Normal"; + json["options"][2] = "Slow"; + json["options"][3] = "Slowest"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "advertising_mode", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"advertising_mode", uidString); + } + + if((int)basicOpenerConfigAclPrefs[13] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_timezone", "Timezone", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.timeZone}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"timeZone\": \"{{ value }}\" }" }}); + json["options"][0] = "Africa/Cairo"; + json["options"][1] = "Africa/Lagos"; + json["options"][2] = "Africa/Maputo"; + json["options"][3] = "Africa/Nairobi"; + json["options"][4] = "America/Anchorage"; + json["options"][5] = "America/Argentina/Buenos_Aires"; + json["options"][6] = "America/Chicago"; + json["options"][7] = "America/Denver"; + json["options"][8] = "America/Halifax"; + json["options"][9] = "America/Los_Angeles"; + json["options"][10] = "America/Manaus"; + json["options"][11] = "America/Mexico_City"; + json["options"][12] = "America/New_York"; + json["options"][13] = "America/Phoenix"; + json["options"][14] = "America/Regina"; + json["options"][15] = "America/Santiago"; + json["options"][16] = "America/Sao_Paulo"; + json["options"][17] = "America/St_Johns"; + json["options"][18] = "Asia/Bangkok"; + json["options"][19] = "Asia/Dubai"; + json["options"][20] = "Asia/Hong_Kong"; + json["options"][21] = "Asia/Jerusalem"; + json["options"][22] = "Asia/Karachi"; + json["options"][23] = "Asia/Kathmandu"; + json["options"][24] = "Asia/Kolkata"; + json["options"][25] = "Asia/Riyadh"; + json["options"][26] = "Asia/Seoul"; + json["options"][27] = "Asia/Shanghai"; + json["options"][28] = "Asia/Tehran"; + json["options"][29] = "Asia/Tokyo"; + json["options"][30] = "Asia/Yangon"; + json["options"][31] = "Australia/Adelaide"; + json["options"][32] = "Australia/Brisbane"; + json["options"][33] = "Australia/Darwin"; + json["options"][34] = "Australia/Hobart"; + json["options"][35] = "Australia/Perth"; + json["options"][36] = "Australia/Sydney"; + json["options"][37] = "Europe/Berlin"; + json["options"][38] = "Europe/Helsinki"; + json["options"][39] = "Europe/Istanbul"; + json["options"][40] = "Europe/London"; + json["options"][41] = "Europe/Moscow"; + json["options"][42] = "Pacific/Auckland"; + json["options"][43] = "Pacific/Guam"; + json["options"][44] = "Pacific/Honolulu"; + json["options"][45] = "Pacific/Pago_Pago"; + json["options"][46] = "None"; + + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "timezone", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"timezone", uidString); + } + + if((int)basicOpenerConfigAclPrefs[11] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_operating_mode", "Operating mode", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.operatingMode}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"operatingMode\": \"{{ value }}\" }" }}); + json["options"][0] = "Generic door opener"; + json["options"][1] = "Analogue intercom"; + json["options"][2] = "Digital intercom"; + json["options"][3] = "Siedle"; + json["options"][4] = "TCS"; + json["options"][5] = "Bticino"; + json["options"][6] = "Siedle HTS"; + json["options"][7] = "STR"; + json["options"][8] = "Ritto"; + json["options"][9] = "Fermax"; + json["options"][10] = "Comelit"; + json["options"][11] = "Urmet BiBus"; + json["options"][12] = "Urmet 2Voice"; + json["options"][13] = "Golmar"; + json["options"][14] = "SKS"; + json["options"][15] = "Spare"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "operating_mode", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"operating_mode", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[1] == 1) + { + // BUS mode switch analogue + publishHassTopic("switch", + "bus_mode_switch", + uidString, + "_bus_mode_switch", + "BUS mode switch analogue", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"busModeSwitch\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"busModeSwitch\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.busModeSwitch}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"bus_mode_switch", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[2] == 1) + { + publishHassTopic("number", + "short_circuit_duration", + uidString, + "_short_circuit_duration", + "Short circuit duration", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"shortCircuitDuration\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.shortCircuitDuration}}" }, + { (char*)"min", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"short_circuit_duration", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[3] == 1) + { + publishHassTopic("number", + "electric_strike_delay", + uidString, + "_electric_strike_delay", + "Electric strike delay", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"electricStrikeDelay\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.electricStrikeDelay}}" }, + { (char*)"min", (char*)"0" }, + { (char*)"max", (char*)"30000" }, + { (char*)"step", (char*)"3000" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"electric_strike_delay", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[4] == 1) + { + // Random Electric Strike Delay + publishHassTopic("switch", + "random_electric_strike_delay", + uidString, + "_random_electric_strike_delay", + "Random electric strike delay", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"randomElectricStrikeDelay\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"randomElectricStrikeDelay\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.randomElectricStrikeDelay}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"random_electric_strike_delay", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[5] == 1) + { + publishHassTopic("number", + "electric_strike_duration", + uidString, + "_electric_strike_duration", + "Electric strike duration", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"electricStrikeDuration\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.electricStrikeDuration}}" }, + { (char*)"min", (char*)"1000" }, + { (char*)"max", (char*)"30000" }, + { (char*)"step", (char*)"3000" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"electric_strike_duration", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[6] == 1) + { + // Disable RTO after ring + publishHassTopic("switch", + "disable_rto_after_ring", + uidString, + "_disable_rto_after_ring", + "Disable RTO after ring", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"disableRtoAfterRing\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"disableRtoAfterRing\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.disableRtoAfterRing}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"disable_rto_after_ring", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[7] == 1) + { + publishHassTopic("number", + "rto_timeout", + uidString, + "_rto_timeout", + "RTO timeout", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"rtoTimeout\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.rtoTimeout}}" }, + { (char*)"min", (char*)"5" }, + { (char*)"max", (char*)"60" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"rto_timeout", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[8] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_doorbell_suppression", "Doorbell suppression", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.doorbellSuppression}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"doorbellSuppression\": \"{{ value }}\" }" }}); + json["options"][0] = "Off"; + json["options"][1] = "CM"; + json["options"][2] = "RTO"; + json["options"][3] = "CM & RTO"; + json["options"][4] = "Ring"; + json["options"][5] = "CM & Ring"; + json["options"][6] = "RTO & Ring"; + json["options"][7] = "CM & RTO & Ring"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "doorbell_suppression", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"doorbell_suppression", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[9] == 1) + { + publishHassTopic("number", + "doorbell_suppression_duration", + uidString, + "_doorbell_suppression_duration", + "Doorbell suppression duration", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"cmd_tpl", (char*)"{ \"doorbellSuppressionDuration\": \"{{ value }}\" }" }, + { (char*)"val_tpl", (char*)"{{value_json.doorbellSuppressionDuration}}" }, + { (char*)"min", (char*)"500" }, + { (char*)"max", (char*)"10000" }, + { (char*)"step", (char*)"1000" } + }); + } + else + { + removeHassTopic((char*)"number", (char*)"doorbell_suppression_duration", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[10] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_sound_ring", "Sound ring", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.soundRing}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"soundRing\": \"{{ value }}\" }" }}); + json["options"][0] = "No Sound"; + json["options"][1] = "Sound 1"; + json["options"][2] = "Sound 2"; + json["options"][3] = "Sound 3"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "sound_ring", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"sound_ring", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[11] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_sound_open", "Sound open", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.soundOpen}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"soundOpen\": \"{{ value }}\" }" }}); + json["options"][0] = "No Sound"; + json["options"][1] = "Sound 1"; + json["options"][2] = "Sound 2"; + json["options"][3] = "Sound 3"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "sound_open", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"sound_open", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[12] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_sound_rto", "Sound RTO", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.soundRto}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"soundRto\": \"{{ value }}\" }" }}); + json["options"][0] = "No Sound"; + json["options"][1] = "Sound 1"; + json["options"][2] = "Sound 2"; + json["options"][3] = "Sound 3"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "sound_rto", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"sound_rto", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[13] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_sound_cm", "Sound CM", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.soundCm}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"soundCm\": \"{{ value }}\" }" }}); + json["options"][0] = "No Sound"; + json["options"][1] = "Sound 1"; + json["options"][2] = "Sound 2"; + json["options"][3] = "Sound 3"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "sound_cm", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"sound_cm", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[14] == 1) + { + // Sound confirmation + publishHassTopic("switch", + "sound_confirmation", + uidString, + "_sound_confirmation", + "Sound confirmation", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"soundConfirmation\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"soundConfirmation\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.soundConfirmation}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"sound_confirmation", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[16] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_single_button_press_action", "Single button press action", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.singleButtonPressAction}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"singleButtonPressAction\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Toggle RTO"; + json["options"][2] = "Activate RTO"; + json["options"][3] = "Deactivate RTO"; + json["options"][4] = "Toggle CM"; + json["options"][5] = "Activate CM"; + json["options"][6] = "Deactivate CM"; + json["options"][7] = "Open"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "single_button_press_action", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"single_button_press_action", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[17] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_double_button_press_action", "Double button press action", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.doubleButtonPressAction}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"doubleButtonPressAction\": \"{{ value }}\" }" }}); + json["options"][0] = "No Action"; + json["options"][1] = "Toggle RTO"; + json["options"][2] = "Activate RTO"; + json["options"][3] = "Deactivate RTO"; + json["options"][4] = "Toggle CM"; + json["options"][5] = "Activate CM"; + json["options"][6] = "Deactivate CM"; + json["options"][7] = "Open"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "double_button_press_action", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"double_button_press_action", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[18] == 1) + { + JsonDocument json; + json = createHassJson(uidString, "_battery_type", "Battery type", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.batteryType}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"batteryType\": \"{{ value }}\" }" }}); + json["options"][0] = "Alkali"; + json["options"][1] = "Accumulators"; + json["options"][2] = "Lithium"; + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath("select", "battery_type", uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } + else + { + removeHassTopic((char*)"select", (char*)"battery_type", uidString); + } + + if((int)advancedOpenerConfigAclPrefs[19] == 1) + { + // Automatic battery type detection + publishHassTopic("switch", + "automatic_battery_type_detection", + uidString, + "_automatic_battery_type_detection", + "Automatic battery type detection", + name, + baseTopic, + String("~") + mqtt_topic_config_advanced_json, + deviceType, + "", + "", + "config", + String("~") + mqtt_topic_config_action, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_on", (char*)"{ \"automaticBatteryTypeDetection\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"automaticBatteryTypeDetection\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.automaticBatteryTypeDetection}}" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + } + else + { + removeHassTopic((char*)"switch", (char*)"automatic_battery_type_detection", uidString); + } +} + +void NukiNetwork::publishHASSConfigAccessLog(char *deviceType, const char *baseTopic, char *name, char *uidString) +{ + publishHassTopic("sensor", + "last_action_authorization", + uidString, + "_last_action_authorization", + "Last action authorization", + name, + baseTopic, + String("~") + mqtt_topic_lock_log, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"ic", (char*)"mdi:format-list-bulleted" }, + { (char*)"val_tpl", (char*)"{{ (value_json|selectattr('type', 'eq', 'LockAction')|selectattr('action', 'in', ['Lock', 'Unlock', 'Unlatch'])|first|default).authorizationName|default }}" } + }); + + String rollingSate = "~"; + rollingSate.concat(mqtt_topic_lock_log_rolling); + const char *rollingStateChr = rollingSate.c_str(); + + publishHassTopic("sensor", + "rolling_log", + uidString, + "_rolling_log", + "Rolling authorization log", + name, + baseTopic, + String("~") + mqtt_topic_lock_log_rolling, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"ic", (char*)"mdi:format-list-bulleted" }, + { (char*)"json_attr_t", (char*)rollingStateChr }, + { (char*)"val_tpl", (char*)"{{value_json.index}}" } + }); +} + +void NukiNetwork::publishHASSConfigKeypad(char *deviceType, const char *baseTopic, char *name, char *uidString) +{ + // Keypad battery critical + publishHassTopic("binary_sensor", + "keypad_battery_low", + uidString, + "_keypad_battery_low", + "Keypad battery low", + name, + baseTopic, + String("~") + mqtt_topic_battery_basic_json, + deviceType, + "battery", + "", + "diagnostic", + "", + { + {(char*)"pl_on", (char*)"1"}, + {(char*)"pl_off", (char*)"0"}, + {(char*)"val_tpl", (char*)"{{value_json.keypadCritical}}" } + }); + + // Query Keypad + publishHassTopic("button", + "query_keypad", + uidString, + "_query_keypad", + "Query keypad", + name, + baseTopic, + "", + deviceType, + "", + "", + "diagnostic", + String("~") + mqtt_topic_query_keypad, + { + { (char*)"en", (char*)"false" }, + { (char*)"pl_prs", (char*)"1" } + }); + + publishHassTopic("sensor", + "keypad_status", + uidString, + "_keypad_stats", + "Keypad status", + name, + baseTopic, + String("~") + mqtt_topic_lock_log, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"ic", (char*)"mdi:drag-vertical" }, + { (char*)"val_tpl", (char*)"{{ (value_json|selectattr('type', 'eq', 'KeypadAction')|first|default).completionStatus|default }}" } + }); +} + +void NukiNetwork::publishHASSWifiRssiConfig(char *deviceType, const char *baseTopic, char *name, char *uidString) +{ + publishHassTopic("sensor", + "wifi_signal_strength", + uidString, + "_wifi_signal_strength", + "WIFI signal strength", + name, + baseTopic, + _lockPath + mqtt_topic_wifi_rssi, + deviceType, + "signal_strength", + "measurement", + "diagnostic", + "", + { {(char*)"unit_of_meas", (char*)"dBm"} }); +} + +void NukiNetwork::publishHassTopic(const String& mqttDeviceType, + const String& mqttDeviceName, + const String& uidString, + const String& uidStringPostfix, + const String& displayName, + const String& name, + const String& baseTopic, + const String& stateTopic, + const String& deviceType, + const String& deviceClass, + const String& stateClass, + const String& entityCat, + const String& commandTopic, + std::vector> additionalEntries + ) +{ + if (_discoveryTopic != "") + { + JsonDocument json; + json = createHassJson(uidString, uidStringPostfix, displayName, name, baseTopic, stateTopic, deviceType, deviceClass, stateClass, entityCat, commandTopic, additionalEntries); + serializeJson(json, _buffer, _bufferSize); + String path = createHassTopicPath(mqttDeviceType, mqttDeviceName, uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + } +} + +String NukiNetwork::createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString) +{ + String path = _discoveryTopic; + path.concat("/"); + path.concat(mqttDeviceType); + path.concat("/"); + path.concat(uidString); + path.concat("/"); + path.concat(mqttDeviceName); + path.concat("/config"); + + return path; +} + +void NukiNetwork::removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString) +{ + if (_discoveryTopic != "") + { + String path = createHassTopicPath(mqttDeviceType, mqttDeviceName, uidString); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, ""); + } +} + +void NukiNetwork::removeHASSConfig(char* uidString) +{ + removeHassTopic((char*)"lock", (char*)"smartlock", uidString); + removeHassTopic((char*)"binary_sensor", (char*)"battery_low", uidString); + removeHassTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); + removeHassTopic((char*)"sensor", (char*)"battery_voltage", uidString); + removeHassTopic((char*)"sensor", (char*)"trigger", uidString); + removeHassTopic((char*)"binary_sensor", (char*)"mqtt_connected", uidString); + removeHassTopic((char*)"switch", (char*)"reset", uidString); + removeHassTopic((char*)"sensor", (char*)"firmware_version", uidString); + removeHassTopic((char*)"sensor", (char*)"hardware_version", uidString); + removeHassTopic((char*)"sensor", (char*)"nuki_hub_version", uidString); + removeHassTopic((char*)"sensor", (char*)"nuki_hub_build", uidString); + removeHassTopic((char*)"sensor", (char*)"nuki_hub_latest", uidString); + removeHassTopic((char*)"update", (char*)"nuki_hub_update", uidString); + removeHassTopic((char*)"sensor", (char*)"nuki_hub_ip", uidString); + removeHassTopic((char*)"button", (char*)"unlatch", uidString); + removeHassTopic((char*)"button", (char*)"lockngo", uidString); + removeHassTopic((char*)"button", (char*)"lockngounlatch", uidString); + removeHassTopic((char*)"sensor", (char*)"battery_level", uidString); + removeHassTopic((char*)"binary_sensor", (char*)"door_sensor", uidString); + removeHassTopic((char*)"binary_sensor", (char*)"ring_detect", uidString); + removeHassTopic((char*)"sensor", (char*)"sound_level", uidString); + removeHassTopic((char*)"sensor", (char*)"last_action_authorization", uidString); + removeHassTopic((char*)"sensor", (char*)"keypad_status", uidString); + removeHassTopic((char*)"sensor", (char*)"rolling_log", uidString); + removeHassTopic((char*)"sensor", (char*)"wifi_signal_strength", uidString); + removeHassTopic((char*)"sensor", (char*)"bluetooth_signal_strength", uidString); + removeHassTopic((char*)"binary_sensor", (char*)"continuous_mode", uidString); + removeHassTopic((char*)"switch", (char*)"continuous_mode", uidString); + removeHassTopic((char*)"button", (char*)"query_lockstate", uidString); + removeHassTopic((char*)"button", (char*)"query_config", uidString); + removeHassTopic((char*)"button", (char*)"query_keypad", uidString); + removeHassTopic((char*)"button", (char*)"query_battery", uidString); + removeHassTopic((char*)"button", (char*)"query_commandresult", uidString); + removeHassTopic((char*)"switch", (char*)"auto_lock", uidString); + removeHassTopic((char*)"switch", (char*)"auto_unlock", uidString); + removeHassTopic((char*)"switch", (char*)"double_lock", uidString); + removeHassTopic((char*)"switch", (char*)"automatic_battery_type_detection", uidString); + removeHassTopic((char*)"select", (char*)"battery_type", uidString); + removeHassTopic((char*)"select", (char*)"double_button_press_action", uidString); + removeHassTopic((char*)"select", (char*)"single_button_press_action", uidString); + removeHassTopic((char*)"switch", (char*)"sound_confirmation", uidString); + removeHassTopic((char*)"select", (char*)"sound_cm", uidString); + removeHassTopic((char*)"select", (char*)"sound_rto", uidString); + removeHassTopic((char*)"select", (char*)"sound_open", uidString); + removeHassTopic((char*)"select", (char*)"sound_ring", uidString); + removeHassTopic((char*)"number", (char*)"doorbell_suppression_duration", uidString); + removeHassTopic((char*)"select", (char*)"doorbell_suppression", uidString); + removeHassTopic((char*)"number", (char*)"rto_timeout", uidString); + removeHassTopic((char*)"switch", (char*)"disable_rto_after_ring", uidString); + removeHassTopic((char*)"number", (char*)"electric_strike_duration", uidString); + removeHassTopic((char*)"switch", (char*)"random_electric_strike_delay", uidString); + removeHassTopic((char*)"number", (char*)"electric_strike_delay", uidString); + removeHassTopic((char*)"number", (char*)"short_circuit_duration", uidString); + removeHassTopic((char*)"switch", (char*)"bus_mode_switch", uidString); + removeHassTopic((char*)"select", (char*)"operating_mode", uidString); + removeHassTopic((char*)"select", (char*)"timezone", uidString); + removeHassTopic((char*)"select", (char*)"advertising_mode", uidString); + removeHassTopic((char*)"select", (char*)"fob_action_3", uidString); + removeHassTopic((char*)"select", (char*)"fob_action_2", uidString); + removeHassTopic((char*)"select", (char*)"fob_action_1", uidString); + removeHassTopic((char*)"switch", (char*)"dst_mode", uidString); + removeHassTopic((char*)"number", (char*)"timezone_offset", uidString); + removeHassTopic((char*)"switch", (char*)"pairing_enabled", uidString); + removeHassTopic((char*)"number", (char*)"sound_level", uidString); + removeHassTopic((char*)"switch", (char*)"button_enabled", uidString); + removeHassTopic((char*)"switch", (char*)"led_enabled", uidString); + removeHassTopic((char*)"number", (char*)"led_brightness", uidString); + removeHassTopic((char*)"switch", (char*)"auto_update_enabled", uidString); + removeHassTopic((char*)"switch", (char*)"immediate_auto_lock_enabled", uidString); + removeHassTopic((char*)"switch", (char*)"nightmode_immediate_lock_start", uidString); + removeHassTopic((char*)"switch", (char*)"nightmode_auto_unlock", uidString); + removeHassTopic((char*)"switch", (char*)"nightmode_auto_lock", uidString); + removeHassTopic((char*)"text", (char*)"nightmode_end_time", uidString); + removeHassTopic((char*)"text", (char*)"nightmode_start_time", uidString); + removeHassTopic((char*)"switch", (char*)"nightmode_enabled", uidString); + removeHassTopic((char*)"number", (char*)"auto_lock_timeout", uidString); + removeHassTopic((char*)"number", (char*)"unlatch_duration", uidString); + removeHassTopic((char*)"switch", (char*)"detached_cylinder", uidString); + removeHassTopic((char*)"number", (char*)"lockngo_timeout", uidString); + removeHassTopic((char*)"number", (char*)"unlocked_locked_transition_offset_degrees", uidString); + removeHassTopic((char*)"number", (char*)"single_locked_position_offset_degrees", uidString); + removeHassTopic((char*)"number", (char*)"locked_position_offset_degrees", uidString); + removeHassTopic((char*)"number", (char*)"unlocked_position_offset_degrees", uidString); + removeHassTopic((char*)"switch", (char*)"pairing_enabled", uidString); + removeHassTopic((char*)"switch", (char*)"auto_unlatch", uidString); + removeHassTopic((char*)"sensor", (char*)"network_device", uidString); + removeHassTopic((char*)"switch", (char*)"webserver", uidString); + removeHassTopic((char*)"sensor", (char*)"uptime", uidString); + removeHassTopic((char*)"sensor", (char*)"mqtt_log", uidString); + removeHassTopic((char*)"binary_sensor", (char*)"hybrid_connected", uidString); + removeHassTopic((char*)"sensor", (char*)"nuki_hub_restart_reason", uidString); + removeHassTopic((char*)"sensor", (char*)"nuki_hub_restart_reason_esp", uidString); +} + +void NukiNetwork::removeHASSConfigTopic(char *deviceType, char *name, char *uidString) +{ + removeHassTopic(deviceType, name, uidString); +} + +JsonDocument NukiNetwork::createHassJson(const String& uidString, + const String& uidStringPostfix, + const String& displayName, + const String& name, + const String& baseTopic, + const String& stateTopic, + const String& deviceType, + const String& deviceClass, + const String& stateClass, + const String& entityCat, + const String& commandTopic, + std::vector> additionalEntries + ) +{ + JsonDocument json; + json.clear(); + JsonObject dev = json["dev"].to(); + JsonArray ids = dev["ids"].to(); + ids.add(String("nuki_") + uidString); + json["dev"]["mf"] = "Nuki"; + json["dev"]["mdl"] = deviceType; + json["dev"]["name"] = name; + json["~"] = baseTopic; + json["name"] = displayName; + json["unique_id"] = String(uidString) + uidStringPostfix; + + if(deviceClass != "") + { + json["dev_cla"] = deviceClass; + } + + if(stateTopic != "") + { + json["stat_t"] = stateTopic; + } + + if(stateClass != "") + { + json["stat_cla"] = stateClass; + } + + if(entityCat != "") + { + json["ent_cat"] = entityCat; + } + + if(commandTopic != "") + { + json["cmd_t"] = commandTopic; + } + + json["avty"]["t"] = _lockPath + mqtt_topic_mqtt_connection_state; + + for(const auto& entry : additionalEntries) + { + if(strcmp(entry.second, "true") == 0) + { + json[entry.first] = true; + } + else if(strcmp(entry.second, "false") == 0) + { + json[entry.first] = false; + } + else + { + json[entry.first] = entry.second; + } + } + + return json; +} \ No newline at end of file diff --git a/src/HomeAssistantDiscovery.h b/src/HomeAssistantDiscovery.h new file mode 100644 index 0000000..aeb72c9 --- /dev/null +++ b/src/HomeAssistantDiscovery.h @@ -0,0 +1,43 @@ + void setupHASS(int type=0); + void disableHASS(); + void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction); + void removeHASSConfig(char* uidString); + void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction); + void publishHASSConfigAdditionalLockEntities(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigDoorSensor(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigAdditionalOpenerEntities(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigAccessLog(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigKeypad(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSWifiRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString); + void removeHASSConfig(char* uidString); + void removeHASSConfigTopic(char* deviceType, char* name, char* uidString); + void publishHassTopic(const String& mqttDeviceType, + const String& mqttDeviceName, + const String& uidString, + const String& uidStringPostfix, + const String& displayName, + const String& name, + const String& baseTopic, + const String& stateTopic, + const String& deviceType, + const String& deviceClass, + const String& stateClass = "", + const String& entityCat = "", + const String& commandTopic = "", + std::vector> additionalEntries = {} + ); + void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); + String createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); + JsonDocument createHassJson(const String& uidString, + const String& uidStringPostfix, + const String& displayName, + const String& name, + const String& baseTopic, + const String& stateTopic, + const String& deviceType, + const String& deviceClass, + const String& stateClass = "", + const String& entityCat = "", + const String& commandTopic = "", + std::vector> additionalEntries = {} + ); \ No newline at end of file diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 100dac1..2edf3d0 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -16,6 +16,7 @@ #endif NukiNetwork* NukiNetwork::_inst = nullptr; +HomeAssistantDiscovery* HomeAssistantDiscovery::_hadiscovery = nullptr; extern bool wifiFallback; extern bool disableNetwork; @@ -41,6 +42,7 @@ NukiNetwork::NukiNetwork(Preferences *preferences) } _inst = this; + _hadiscovery = new HomeAssistantDiscovery(_inst, _preferences); _webEnabled = _preferences->getBool(preference_webserver_enabled, true); _updateFromMQTT = _preferences->getBool(preference_update_from_mqtt, false); @@ -704,8 +706,7 @@ bool NukiNetwork::reconnect() } else { - Log->print(F("MQTT connect failed, rc=")); - _device->printError(); + Log->print(F("MQTT connect failed")); _mqttConnectionState = 0; _nextReconnect = espMillis() + 5000; //_device->mqttDisconnect(true); @@ -919,2708 +920,26 @@ void NukiNetwork::publishString(const char* prefix, const char *topic, const cha _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, value); } -void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction) +void NukiNetwork::removeTopic(const String& mqttPath, const String& mqttTopic) { - JsonDocument json; - json.clear(); - JsonObject dev = json["dev"].to(); - JsonArray ids = dev["ids"].to(); - ids.add(String("nuki_") + uidString); - json["dev"]["mf"] = "Nuki"; - json["dev"]["mdl"] = deviceType; - json["dev"]["name"] = name; - json["dev"]["sw"] = softwareVersion; - json["dev"]["hw"] = hardwareVersion; + String path = mqttPath; + path.concat(mqttTopic); + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, ""); - String cuUrl = _preferences->getString(preference_mqtt_hass_cu_url, ""); - - if (cuUrl != "") - { - json["dev"]["cu"] = cuUrl; - } - else - { - json["dev"]["cu"] = "http://" + _device->localIP(); - } - - json["~"] = baseTopic; - json["name"] = nullptr; - json["unique_id"] = String(uidString) + "_lock"; - json["cmd_t"] = String("~") + String(mqtt_topic_lock_action); - json["avty"][0]["t"] = availabilityTopic; - json["pl_lock"] = lockAction; - json["pl_unlk"] = unlockAction; - - uint32_t aclPrefs[17]; - _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); - - if((strcmp(deviceType, "SmartLock") == 0 && (int)aclPrefs[2]) || (strcmp(deviceType, "SmartLock") != 0 && (int)aclPrefs[11])) - { - json["pl_open"] = openAction; - } - - json["stat_t"] = String("~") + mqtt_topic_lock_ha_state; - json["stat_jam"] = "jammed"; - json["stat_locked"] = "locked"; - json["stat_locking"] = "locking"; - json["stat_unlocked"] = "unlocked"; - json["stat_unlocking"] = "unlocking"; - json["stat_open"] = "open"; - json["stat_opening"] = "opening"; - json["opt"] = "false"; - - serializeJson(json, _buffer, _bufferSize); - - String path = _preferences->getString(preference_mqtt_hass_discovery, "homeassistant"); - path.concat("/lock/"); - path.concat(uidString); - path.concat("/smartlock/config"); - - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - - // Battery critical - publishHassTopic("binary_sensor", - "battery_low", - uidString, - "_battery_low", - "Battery low", - name, - baseTopic, - String("~") + mqtt_topic_battery_basic_json, - deviceType, - "battery", - "", - "diagnostic", - "", - { - {(char*)"pl_on", (char*)"1"}, - {(char*)"pl_off", (char*)"0"}, - {(char*)"val_tpl", (char*)"{{value_json.critical}}" } - }); - - // Battery voltage - publishHassTopic("sensor", - "battery_voltage", - uidString, - "_battery_voltage", - "Battery voltage", - name, - baseTopic, - String("~") + mqtt_topic_battery_advanced_json, - deviceType, - "voltage", - "measurement", - "diagnostic", - "", - { - {(char*)"unit_of_meas", (char*)"V"}, - {(char*)"val_tpl", (char*)"{{value_json.batteryVoltage}}" } - }); - - // Trigger - publishHassTopic("sensor", - "trigger", - uidString, - "_trigger", - "Trigger", - name, - baseTopic, - String("~") + mqtt_topic_lock_trigger, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" } }); - - // MQTT Connected - publishHassTopic("binary_sensor", - "mqtt_connected", - uidString, - "_mqtt_connected", - "MQTT connected", - name, - baseTopic, - _lockPath + mqtt_topic_mqtt_connection_state, - deviceType, - "", - "", - "diagnostic", - "", - { - {(char*)"pl_on", (char*)"online"}, - {(char*)"pl_off", (char*)"offline"}, - {(char*)"ic", (char*)"mdi:lan-connect"} - }); - - // Reset - publishHassTopic("switch", - "reset", - uidString, - "_reset", - "Restart Nuki Hub", - name, - baseTopic, - _lockPath + mqtt_topic_reset, - deviceType, - "", - "", - "diagnostic", - _lockPath + mqtt_topic_reset, - { - { (char*)"ic", (char*)"mdi:restart" }, - { (char*)"pl_on", (char*)"1" }, - { (char*)"pl_off", (char*)"0" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - - // Network device - publishHassTopic("sensor", - "network_device", - uidString, - "_network_device", - "Network device", - name, - baseTopic, - _lockPath + mqtt_topic_network_device, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" }}); - - // Nuki Hub Webserver enabled - publishHassTopic("switch", - "webserver", - uidString, - "_webserver", - "Nuki Hub webserver enabled", - name, - baseTopic, - _lockPath + mqtt_topic_webserver_state, - deviceType, - "", - "", - "diagnostic", - _lockPath + mqtt_topic_webserver_action, - { - { (char*)"pl_on", (char*)"1" }, - { (char*)"pl_off", (char*)"0" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - - // Uptime - publishHassTopic("sensor", - "uptime", - uidString, - "_uptime", - "Uptime", - name, - baseTopic, - _lockPath + mqtt_topic_uptime, - deviceType, - "duration", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - { (char*)"unit_of_meas", (char*)"min"} - }); - - if(_preferences->getBool(preference_mqtt_log_enabled, false)) - { - // MQTT Log - publishHassTopic("sensor", - "mqtt_log", - uidString, - "_mqtt_log", - "MQTT Log", - name, - baseTopic, - _lockPath + mqtt_topic_log, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" }}); - } - else - { - removeHassTopic((char*)"sensor", (char*)"mqtt_log", uidString); - } - - if(_offEnabled) - { - // Hybrid connected - String hybridPath = _lockPath; - hybridPath.concat("/lock"); - hybridPath.concat(mqtt_hybrid_state); - publishHassTopic("binary_sensor", - "hybrid_connected", - uidString, - "_hybrid_connected", - "Hybrid connected", - name, - baseTopic, - hybridPath, - deviceType, - "", - "", - "diagnostic", - "", - { - {(char*)"pl_on", (char*)"1"}, - {(char*)"pl_off", (char*)"0"}, - { (char*)"en", (char*)"true" } - }); - } - else - { - removeHassTopic((char*)"binary_sensor", (char*)"hybrid_connected", uidString); - } - - // Firmware version - publishHassTopic("sensor", - "firmware_version", - uidString, - "_firmware_version", - "Firmware version", - name, - baseTopic, - String("~") + mqtt_topic_info_firmware_version, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // Hardware version - publishHassTopic("sensor", - "hardware_version", - uidString, - "_hardware_version", - "Hardware version", - name, - baseTopic, - String("~") + mqtt_topic_info_hardware_version, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // Nuki Hub version - publishHassTopic("sensor", - "nuki_hub_version", - uidString, - "_nuki_hub_version", - "Nuki Hub version", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_version, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // Nuki Hub build - publishHassTopic("sensor", - "nuki_hub_build", - uidString, - "_nuki_hub_build", - "Nuki Hub build", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_build, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // Nuki Hub restart reason - publishHassTopic("sensor", - "nuki_hub_restart_reason", - uidString, - "_nuki_hub_restart_reason", - "Nuki Hub restart reason", - name, - baseTopic, - _lockPath + mqtt_topic_restart_reason_fw, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" }}); - - // Nuki Hub restart reason ESP - publishHassTopic("sensor", - "nuki_hub_restart_reason_esp", - uidString, - "_nuki_hub_restart_reason_esp", - "Nuki Hub restart reason ESP", - name, - baseTopic, - _lockPath + mqtt_topic_restart_reason_esp, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" }}); - - if(_checkUpdates) - { - // NUKI Hub latest - publishHassTopic("sensor", - "nuki_hub_latest", - uidString, - "_nuki_hub_latest", - "NUKI Hub latest", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_latest, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // NUKI Hub update - char latest_version_topic[250]; - _lockPath.toCharArray(latest_version_topic,_lockPath.length() + 1); - strcat(latest_version_topic, mqtt_topic_info_nuki_hub_latest); - - if(!_updateFromMQTT) - { - publishHassTopic("update", - "nuki_hub_update", - uidString, - "_nuki_hub_update", - "NUKI Hub firmware update", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_version, - deviceType, - "firmware", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - { (char*)"ent_pic", (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/master/icon/favicon-32x32.png" }, - { (char*)"rel_u", (char*)GITHUB_LATEST_RELEASE_URL }, - { (char*)"l_ver_t", (char*)latest_version_topic } - }); - } - else - { - publishHassTopic("update", - "nuki_hub_update", - uidString, - "_nuki_hub_update", - "NUKI Hub firmware update", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_version, - deviceType, - "firmware", - "", - "diagnostic", - _lockPath + mqtt_topic_update, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_inst", (char*)"1" }, - { (char*)"ent_pic", (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/master/icon/favicon-32x32.png" }, - { (char*)"rel_u", (char*)GITHUB_LATEST_RELEASE_URL }, - { (char*)"l_ver_t", (char*)latest_version_topic } - }); - } - } - else - { - removeHassTopic((char*)"sensor", (char*)"nuki_hub_latest", uidString); - removeHassTopic((char*)"update", (char*)"nuki_hub_update", uidString); - } - - // Nuki Hub IP Address - publishHassTopic("sensor", - "nuki_hub_ip", - uidString, - "_nuki_hub_ip", - "Nuki Hub IP", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_ip, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:ip"} - }); - - // Query Lock State - publishHassTopic("button", - "query_lockstate", - uidString, - "_query_lockstate", - "Query lock state", - name, - baseTopic, - "", - deviceType, - "", - "", - "diagnostic", - String("~") + mqtt_topic_query_lockstate, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"1" } - }); - - // Query Config - publishHassTopic("button", - "query_config", - uidString, - "_query_config", - "Query config", - name, - baseTopic, - "", - deviceType, - "", - "", - "diagnostic", - String("~") + mqtt_topic_query_config, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"1" } - }); - - // Query Lock State Command result - publishHassTopic("button", - "query_commandresult", - uidString, - "_query_commandresult", - "Query lock state command result", - name, - baseTopic, - "", - deviceType, - "", - "", - "diagnostic", - String("~") + mqtt_topic_query_lockstate_command_result, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"1" } - }); - - publishHassTopic("sensor", - "bluetooth_signal_strength", - uidString, - "_bluetooth_signal_strength", - "Bluetooth signal strength", - name, - baseTopic, - String("~") + mqtt_topic_lock_rssi, - deviceType, - "signal_strength", - "measurement", - "diagnostic", - "", - { {(char*)"unit_of_meas", (char*)"dBm"} }); +#ifdef DEBUG_NUKIHUB + Log->print(F("Removing MQTT topic: ")); + Log->println(path.c_str()); +#endif } -void NukiNetwork::publishHASSConfigAdditionalLockEntities(char *deviceType, const char *baseTopic, char *name, char *uidString) +void NukiNetwork::setupHASS(int type) { - uint32_t aclPrefs[17]; - _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); - - uint32_t basicLockConfigAclPrefs[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - uint32_t advancedLockConfigAclPrefs[22] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - - if(_preferences->getBool(preference_conf_info_enabled, true)) - { - _preferences->getBytes(preference_conf_lock_basic_acl, &basicLockConfigAclPrefs, sizeof(basicLockConfigAclPrefs)); - _preferences->getBytes(preference_conf_lock_advanced_acl, &advancedLockConfigAclPrefs, sizeof(advancedLockConfigAclPrefs)); - } - - if((int)aclPrefs[2]) - { - // Unlatch - publishHassTopic("button", - "unlatch", - uidString, - "_unlatch", - "Open", - name, - baseTopic, - "", - deviceType, - "", - "", - "", - String("~") + mqtt_topic_lock_action, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"unlatch" } - }); - } - else - { - removeHassTopic((char*)"button", (char*)"unlatch", uidString); - } - - if((int)aclPrefs[3]) - { - // Lock 'n' Go - publishHassTopic("button", - "lockngo", - uidString, - "_lockngo", - "Lock 'n' Go", - name, - baseTopic, - "", - deviceType, - "", - "", - "", - String("~") + mqtt_topic_lock_action, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"lockNgo" } - }); - } - else - { - removeHassTopic((char*)"button", (char*)"lockngo", uidString); - } - - if((int)aclPrefs[4]) - { - // Lock 'n' Go with unlatch - publishHassTopic("button", - "lockngounlatch", - uidString, - "_lockngounlatch", - "Lock 'n' Go with unlatch", - name, - baseTopic, - "", - deviceType, - "", - "", - "", - String("~") + mqtt_topic_lock_action, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"lockNgoUnlatch" } - }); - } - else - { - removeHassTopic((char*)"button", (char*)"lockngounlatch", uidString); - } - - // Query Battery - publishHassTopic("button", - "query_battery", - uidString, - "_query_battery", - "Query battery", - name, - baseTopic, - "", - deviceType, - "", - "", - "diagnostic", - String("~") + mqtt_topic_query_battery, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"1" } - }); - - if((int)basicLockConfigAclPrefs[6] == 1) - { - // LED enabled - publishHassTopic("switch", - "led_enabled", - uidString, - "_led_enabled", - "LED enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"ic", (char*)"mdi:led-variant-on" }, - { (char*)"pl_on", (char*)"{ \"ledEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"ledEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.ledEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"led_enabled", uidString); - } - - if((int)basicLockConfigAclPrefs[5] == 1) - { - // Button enabled - publishHassTopic("switch", - "button_enabled", - uidString, - "_button_enabled", - "Button enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"ic", (char*)"mdi:radiobox-marked" }, - { (char*)"pl_on", (char*)"{ \"buttonEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"buttonEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.buttonEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"button_enabled", uidString); - } - - if((int)advancedLockConfigAclPrefs[19] == 1) - { - // Auto Lock - publishHassTopic("switch", - "auto_lock", - uidString, - "_auto_lock", - "Auto lock", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"autoLockEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"autoLockEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.autoLockEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"auto_lock", uidString); - } - - if((int)advancedLockConfigAclPrefs[12] == 1) - { - // Auto Unlock - publishHassTopic("switch", - "auto_unlock", - uidString, - "_auto_unlock", - "Auto unlock", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"autoUnLockDisabled\": \"0\"}" }, - { (char*)"pl_off", (char*)"{ \"autoUnLockDisabled\": \"1\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.autoUnLockDisabled}}" }, - { (char*)"stat_on", (char*)"0" }, - { (char*)"stat_off", (char*)"1" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"auto_unlock", uidString); - } - - if((int)basicLockConfigAclPrefs[13] == 1) - { - // Double lock - publishHassTopic("switch", - "double_lock", - uidString, - "_double_lock", - "Double lock", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"singleLock\": \"0\"}" }, - { (char*)"pl_off", (char*)"{ \"singleLock\": \"1\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.singleLock}}" }, - { (char*)"stat_on", (char*)"0" }, - { (char*)"stat_off", (char*)"1" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"double_lock", uidString); - } - - publishHassTopic("sensor", - "battery_level", - uidString, - "_battery_level", - "Battery level", - name, - baseTopic, - String("~") + mqtt_topic_battery_basic_json, - deviceType, - "battery", - "measurement", - "diagnostic", - "", - { - {(char*)"unit_of_meas", (char*)"%"}, - {(char*)"val_tpl", (char*)"{{value_json.level}}" } - }); - - if((int)basicLockConfigAclPrefs[7] == 1) - { - publishHassTopic("number", - "led_brightness", - uidString, - "_led_brightness", - "LED brightness", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"ic", (char*)"mdi:brightness-6" }, - { (char*)"cmd_tpl", (char*)"{ \"ledBrightness\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.ledBrightness}}" }, - { (char*)"min", (char*)"0" }, - { (char*)"max", (char*)"5" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"led_brightness", uidString); - } - - if((int)basicLockConfigAclPrefs[3] == 1) - { - // Auto Unlatch - publishHassTopic("switch", - "auto_unlatch", - uidString, - "_auto_unlatch", - "Auto unlatch", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"autoUnlatch\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"autoUnlatch\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.autoUnlatch}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"auto_unlatch", uidString); - } - - if((int)basicLockConfigAclPrefs[4] == 1) - { - // Pairing enabled - publishHassTopic("switch", - "pairing_enabled", - uidString, - "_pairing_enabled", - "Pairing enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"pairingEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"pairingEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.pairingEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"pairing_enabled", uidString); - } - - if((int)basicLockConfigAclPrefs[8] == 1) - { - publishHassTopic("number", - "timezone_offset", - uidString, - "_timezone_offset", - "Timezone offset", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"ic", (char*)"mdi:timer-cog-outline" }, - { (char*)"cmd_tpl", (char*)"{ \"timeZoneOffset\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.timeZoneOffset}}" }, - { (char*)"min", (char*)"0" }, - { (char*)"max", (char*)"60" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"timezone_offset", uidString); - } - - if((int)basicLockConfigAclPrefs[9] == 1) - { - // DST Mode - publishHassTopic("switch", - "dst_mode", - uidString, - "_dst_mode", - "DST mode European", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"dstMode\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"dstMode\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.dstMode}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"dst_mode", uidString); - } - - if((int)basicLockConfigAclPrefs[10] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_fob_action_1", "Fob action 1", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction1}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction1\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Unlock"; - json["options"][2] = "Lock"; - json["options"][3] = "Lock n Go"; - json["options"][4] = "Intelligent"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "fob_action_1", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"fob_action_1", uidString); - } - - if((int)basicLockConfigAclPrefs[11] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_fob_action_2", "Fob action 2", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction2}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction2\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Unlock"; - json["options"][2] = "Lock"; - json["options"][3] = "Lock n Go"; - json["options"][4] = "Intelligent"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "fob_action_2", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"fob_action_2", uidString); - } - - if((int)basicLockConfigAclPrefs[12] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_fob_action_3", "Fob action 3", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction3}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction3\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Unlock"; - json["options"][2] = "Lock"; - json["options"][3] = "Lock n Go"; - json["options"][4] = "Intelligent"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "fob_action_3", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"fob_action_3", uidString); - } - - if((int)basicLockConfigAclPrefs[14] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_advertising_mode", "Advertising mode", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.advertisingMode}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"advertisingMode\": \"{{ value }}\" }" }}); - json["options"][0] = "Automatic"; - json["options"][1] = "Normal"; - json["options"][2] = "Slow"; - json["options"][3] = "Slowest"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "advertising_mode", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"advertising_mode", uidString); - } - - if((int)basicLockConfigAclPrefs[15] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_timezone", "Timezone", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.timeZone}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"timeZone\": \"{{ value }}\" }" }}); - json["options"][0] = "Africa/Cairo"; - json["options"][1] = "Africa/Lagos"; - json["options"][2] = "Africa/Maputo"; - json["options"][3] = "Africa/Nairobi"; - json["options"][4] = "America/Anchorage"; - json["options"][5] = "America/Argentina/Buenos_Aires"; - json["options"][6] = "America/Chicago"; - json["options"][7] = "America/Denver"; - json["options"][8] = "America/Halifax"; - json["options"][9] = "America/Los_Angeles"; - json["options"][10] = "America/Manaus"; - json["options"][11] = "America/Mexico_City"; - json["options"][12] = "America/New_York"; - json["options"][13] = "America/Phoenix"; - json["options"][14] = "America/Regina"; - json["options"][15] = "America/Santiago"; - json["options"][16] = "America/Sao_Paulo"; - json["options"][17] = "America/St_Johns"; - json["options"][18] = "Asia/Bangkok"; - json["options"][19] = "Asia/Dubai"; - json["options"][20] = "Asia/Hong_Kong"; - json["options"][21] = "Asia/Jerusalem"; - json["options"][22] = "Asia/Karachi"; - json["options"][23] = "Asia/Kathmandu"; - json["options"][24] = "Asia/Kolkata"; - json["options"][25] = "Asia/Riyadh"; - json["options"][26] = "Asia/Seoul"; - json["options"][27] = "Asia/Shanghai"; - json["options"][28] = "Asia/Tehran"; - json["options"][29] = "Asia/Tokyo"; - json["options"][30] = "Asia/Yangon"; - json["options"][31] = "Australia/Adelaide"; - json["options"][32] = "Australia/Brisbane"; - json["options"][33] = "Australia/Darwin"; - json["options"][34] = "Australia/Hobart"; - json["options"][35] = "Australia/Perth"; - json["options"][36] = "Australia/Sydney"; - json["options"][37] = "Europe/Berlin"; - json["options"][38] = "Europe/Helsinki"; - json["options"][39] = "Europe/Istanbul"; - json["options"][40] = "Europe/London"; - json["options"][41] = "Europe/Moscow"; - json["options"][42] = "Pacific/Auckland"; - json["options"][43] = "Pacific/Guam"; - json["options"][44] = "Pacific/Honolulu"; - json["options"][45] = "Pacific/Pago_Pago"; - json["options"][46] = "None"; - - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "timezone", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"timezone", uidString); - } - - if((int)advancedLockConfigAclPrefs[0] == 1) - { - publishHassTopic("number", - "unlocked_position_offset_degrees", - uidString, - "_unlocked_position_offset_degrees", - "Unlocked position offset degrees", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"unlockedPositionOffsetDegrees\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.unlockedPositionOffsetDegrees}}" }, - { (char*)"min", (char*)"-90" }, - { (char*)"max", (char*)"180" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"unlocked_position_offset_degrees", uidString); - } - - if((int)advancedLockConfigAclPrefs[1] == 1) - { - publishHassTopic("number", - "locked_position_offset_degrees", - uidString, - "_locked_position_offset_degrees", - "Locked position offset degrees", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"lockedPositionOffsetDegrees\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.lockedPositionOffsetDegrees}}" }, - { (char*)"min", (char*)"-180" }, - { (char*)"max", (char*)"90" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"locked_position_offset_degrees", uidString); - } - - if((int)advancedLockConfigAclPrefs[2] == 1) - { - publishHassTopic("number", - "single_locked_position_offset_degrees", - uidString, - "_single_locked_position_offset_degrees", - "Single locked position offset degrees", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"singleLockedPositionOffsetDegrees\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.singleLockedPositionOffsetDegrees}}" }, - { (char*)"min", (char*)"-180" }, - { (char*)"max", (char*)"180" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"single_locked_position_offset_degrees", uidString); - } - - if((int)advancedLockConfigAclPrefs[3] == 1) - { - publishHassTopic("number", - "unlocked_locked_transition_offset_degrees", - uidString, - "_unlocked_locked_transition_offset_degrees", - "Unlocked to locked transition offset degrees", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"unlockedToLockedTransitionOffsetDegrees\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.unlockedToLockedTransitionOffsetDegrees}}" }, - { (char*)"min", (char*)"-180" }, - { (char*)"max", (char*)"180" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"unlocked_locked_transition_offset_degrees", uidString); - } - - if((int)advancedLockConfigAclPrefs[4] == 1) - { - publishHassTopic("number", - "lockngo_timeout", - uidString, - "_lockngo_timeout", - "Lock n Go timeout", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"lockNgoTimeout\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.lockNgoTimeout}}" }, - { (char*)"min", (char*)"5" }, - { (char*)"max", (char*)"60" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"lockngo_timeout", uidString); - } - - if((int)advancedLockConfigAclPrefs[5] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_single_button_press_action", "Single button press action", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.singleButtonPressAction}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"singleButtonPressAction\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Intelligent"; - json["options"][2] = "Unlock"; - json["options"][3] = "Lock"; - json["options"][4] = "Unlatch"; - json["options"][5] = "Lock n Go"; - json["options"][6] = "Show Status"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "single_button_press_action", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"single_button_press_action", uidString); - } - - if((int)advancedLockConfigAclPrefs[6] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_double_button_press_action", "Double button press action", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.doubleButtonPressAction}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"doubleButtonPressAction\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Intelligent"; - json["options"][2] = "Unlock"; - json["options"][3] = "Lock"; - json["options"][4] = "Unlatch"; - json["options"][5] = "Lock n Go"; - json["options"][6] = "Show Status"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "double_button_press_action", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"double_button_press_action", uidString); - } - - if((int)advancedLockConfigAclPrefs[7] == 1) - { - // Detached cylinder - publishHassTopic("switch", - "detached_cylinder", - uidString, - "_detached_cylinder", - "Detached cylinder", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"detachedCylinder\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"detachedCylinder\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.detachedCylinder}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"detached_cylinder", uidString); - } - - if((int)advancedLockConfigAclPrefs[8] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_battery_type", "Battery type", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.batteryType}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"batteryType\": \"{{ value }}\" }" }}); - json["options"][0] = "Alkali"; - json["options"][1] = "Accumulators"; - json["options"][2] = "Lithium"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "battery_type", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"battery_type", uidString); - } - - if((int)advancedLockConfigAclPrefs[9] == 1) - { - // Automatic battery type detection - publishHassTopic("switch", - "automatic_battery_type_detection", - uidString, - "_automatic_battery_type_detection", - "Automatic battery type detection", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"automaticBatteryTypeDetection\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"automaticBatteryTypeDetection\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.automaticBatteryTypeDetection}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"automatic_battery_type_detection", uidString); - } - - if((int)advancedLockConfigAclPrefs[10] == 1) - { - publishHassTopic("number", - "unlatch_duration", - uidString, - "_unlatch_duration", - "Unlatch duration", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"unlatchDuration\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.unlatchDuration}}" }, - { (char*)"min", (char*)"1" }, - { (char*)"max", (char*)"30" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"unlatch_duration", uidString); - } - - if((int)advancedLockConfigAclPrefs[11] == 1) - { - publishHassTopic("number", - "auto_lock_timeout", - uidString, - "_auto_lock_timeout", - "Auto lock timeout", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"autoLockTimeOut\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.autoLockTimeOut}}" }, - { (char*)"min", (char*)"30" }, - { (char*)"max", (char*)"1800" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"auto_lock_timeout", uidString); - } - - if((int)advancedLockConfigAclPrefs[13] == 1) - { - // Nightmode enabled - publishHassTopic("switch", - "nightmode_enabled", - uidString, - "_nightmode_enabled", - "Nightmode enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"nightModeEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"nightModeEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.nightModeEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"nightmode_enabled", uidString); - } - - if((int)advancedLockConfigAclPrefs[14] == 1) - { - // Nightmode start time - publishHassTopic("text", - "nightmode_start_time", - uidString, - "_nightmode_start_time", - "Nightmode start time", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pattern", (char*)"([0-1][0-9]|2[0-3]):[0-5][0-9]" }, - { (char*)"cmd_tpl", (char*)"{ \"nightModeStartTime\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.nightModeStartTime}}" }, - { (char*)"min", (char*)"5" }, - { (char*)"max", (char*)"5" } - }); - } - else - { - removeHassTopic((char*)"text", (char*)"nightmode_start_time", uidString); - } - - if((int)advancedLockConfigAclPrefs[15] == 1) - { - // Nightmode end time - publishHassTopic("text", - "nightmode_end_time", - uidString, - "_nightmode_end_time", - "Nightmode end time", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pattern", (char*)"([0-1][0-9]|2[0-3]):[0-5][0-9]" }, - { (char*)"cmd_tpl", (char*)"{ \"nightModeEndTime\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.nightModeEndTime}}" }, - { (char*)"min", (char*)"5" }, - { (char*)"max", (char*)"5" } - }); - } - else - { - removeHassTopic((char*)"text", (char*)"nightmode_end_time", uidString); - } - - if((int)advancedLockConfigAclPrefs[16] == 1) - { - // Nightmode Auto Lock - publishHassTopic("switch", - "nightmode_auto_lock", - uidString, - "_nightmode_auto_lock", - "Nightmode auto lock", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"nightModeAutoLockEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"nightModeAutoLockEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.nightModeAutoLockEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"nightmode_auto_lock", uidString); - } - - if((int)advancedLockConfigAclPrefs[17] == 1) - { - // Nightmode Auto Unlock - publishHassTopic("switch", - "nightmode_auto_unlock", - uidString, - "_nightmode_auto_unlock", - "Nightmode auto unlock", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"nightModeAutoUnlockDisabled\": \"0\"}" }, - { (char*)"pl_off", (char*)"{ \"nightModeAutoUnlockDisabled\": \"1\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.nightModeAutoUnlockDisabled}}" }, - { (char*)"stat_on", (char*)"0" }, - { (char*)"stat_off", (char*)"1" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"nightmode_auto_unlock", uidString); - } - - if((int)advancedLockConfigAclPrefs[18] == 1) - { - // Nightmode immediate lock on start - publishHassTopic("switch", - "nightmode_immediate_lock_start", - uidString, - "_nightmode_immediate_lock_start", - "Nightmode immediate lock on start", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"nightModeImmediateLockOnStart\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"nightModeImmediateLockOnStart\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.nightModeImmediateLockOnStart}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"nightmode_immediate_lock_start", uidString); - } - - if((int)advancedLockConfigAclPrefs[20] == 1) - { - // Immediate auto lock enabled - publishHassTopic("switch", - "immediate_auto_lock_enabled", - uidString, - "_immediate_auto_lock_enabled", - "Immediate auto lock enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"immediateAutoLockEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"immediateAutoLockEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.immediateAutoLockEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"immediate_auto_lock_enabled", uidString); - } - - if((int)advancedLockConfigAclPrefs[21] == 1) - { - // Auto update enabled - publishHassTopic("switch", - "auto_update_enabled", - uidString, - "_auto_update_enabled", - "Auto update enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"autoUpdateEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"autoUpdateEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.autoUpdateEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"auto_update_enabled", uidString); - } + _hadiscovery->setupHASS(type); } -void NukiNetwork::publishHASSConfigDoorSensor(char *deviceType, const char *baseTopic, char *name, char *uidString) +void NukiNetwork::disableHASS() { - publishHassTopic("binary_sensor", - "door_sensor", - uidString, - "_door_sensor", - "Door sensor", - name, - baseTopic, - String("~") + mqtt_topic_lock_door_sensor_state, - deviceType, - "door", - "", - "", - "", - { - {(char*)"pl_on", (char*)"doorOpened"}, - {(char*)"pl_off", (char*)"doorClosed"}, - {(char*)"pl_not_avail", (char*)"unavailable"} - }); -} - -void NukiNetwork::publishHASSConfigAdditionalOpenerEntities(char *deviceType, const char *baseTopic, char *name, char *uidString) -{ - uint32_t aclPrefs[17]; - _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); - uint32_t basicOpenerConfigAclPrefs[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - uint32_t advancedOpenerConfigAclPrefs[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - - if(_preferences->getBool(preference_conf_info_enabled, true)) - { - _preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs)); - _preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs)); - } - - if((int)aclPrefs[11]) - { - // Unlatch - publishHassTopic("button", - "unlatch", - uidString, - "_unlatch", - "Open", - name, - baseTopic, - "", - deviceType, - "", - "", - "", - String("~") + mqtt_topic_lock_action, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"electricStrikeActuation" } - }); - } - else - { - removeHassTopic((char*)"button", (char*)"unlatch", uidString); - } - - publishHassTopic("binary_sensor", - "continuous_mode", - uidString, - "_continuous_mode", - "Continuous mode", - name, - baseTopic, - String("~") + mqtt_topic_lock_continuous_mode, - deviceType, - "lock", - "", - "", - "", - { - {(char*)"pl_on", (char*)"on"}, - {(char*)"pl_off", (char*)"off"} - }); - - if((int)aclPrefs[12] == 1 && (int)aclPrefs[13] == 1) - { - publishHassTopic("switch", - "continuous_mode", - uidString, - "_continuous_mode", - "Continuous mode", - name, - baseTopic, - String("~") + mqtt_topic_lock_continuous_mode, - deviceType, - "", - "", - "", - String("~") + mqtt_topic_lock_action, - { - { (char*)"en", (char*)"true" }, - {(char*)"stat_on", (char*)"on"}, - {(char*)"stat_off", (char*)"off"}, - {(char*)"pl_on", (char*)"activateCM"}, - {(char*)"pl_off", (char*)"deactivateCM"} - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"continuous_mode", uidString); - } - - publishHassTopic("binary_sensor", - "ring_detect", - uidString, - "_ring_detect", - "Ring detect", - name, - baseTopic, - String("~") + mqtt_topic_lock_binary_ring, - deviceType, - "sound", - "", - "", - "", - { - {(char*)"pl_on", (char*)"ring"}, - {(char*)"pl_off", (char*)"standby"} - }); - - JsonDocument json; - json = createHassJson(uidString, "_ring_event", "Ring", name, baseTopic, String("~") + mqtt_topic_lock_ring, deviceType, "doorbell", "", "", "", {{(char*)"val_tpl", (char*)"{ \"event_type\": \"{{ value }}\" }"}}); - json["event_types"][0] = "ring"; - json["event_types"][1] = "ringlocked"; - json["event_types"][2] = "standby"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("event", "ring", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - - if((int)basicOpenerConfigAclPrefs[5] == 1) - { - // LED enabled - publishHassTopic("switch", - "led_enabled", - uidString, - "_led_enabled", - "LED enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"ic", (char*)"mdi:led-variant-on" }, - { (char*)"pl_on", (char*)"{ \"ledEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"ledEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.ledEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"led_enabled", uidString); - } - - if((int)basicOpenerConfigAclPrefs[4] == 1) - { - // Button enabled - publishHassTopic("switch", - "button_enabled", - uidString, - "_button_enabled", - "Button enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"ic", (char*)"mdi:radiobox-marked" }, - { (char*)"pl_on", (char*)"{ \"buttonEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"buttonEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.buttonEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"button_enabled", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[15] == 1) - { - publishHassTopic("number", - "sound_level", - uidString, - "_sound_level", - "Sound level", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"ic", (char*)"mdi:volume-source" }, - { (char*)"cmd_tpl", (char*)"{ \"soundLevel\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.soundLevel}}" }, - { (char*)"min", (char*)"0" }, - { (char*)"max", (char*)"255" }, - { (char*)"mode", (char*)"slider" }, - { (char*)"step", (char*)"25.5" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"sound_level", uidString); - } - - if((int)basicOpenerConfigAclPrefs[3] == 1) - { - // Pairing enabled - publishHassTopic("switch", - "pairing_enabled", - uidString, - "_pairing_enabled", - "Pairing enabled", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"pairingEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"pairingEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.pairingEnabled}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"pairing_enabled", uidString); - } - - if((int)basicOpenerConfigAclPrefs[6] == 1) - { - publishHassTopic("number", - "timezone_offset", - uidString, - "_timezone_offset", - "Timezone offset", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"ic", (char*)"mdi:timer-cog-outline" }, - { (char*)"cmd_tpl", (char*)"{ \"timeZoneOffset\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.timeZoneOffset}}" }, - { (char*)"min", (char*)"0" }, - { (char*)"max", (char*)"60" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"timezone_offset", uidString); - } - - if((int)basicOpenerConfigAclPrefs[7] == 1) - { - // DST Mode - publishHassTopic("switch", - "dst_mode", - uidString, - "_dst_mode", - "DST mode European", - name, - baseTopic, - String("~") + mqtt_topic_config_basic_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"dstMode\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"dstMode\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.dstMode}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"dst_mode", uidString); - } - - if((int)basicOpenerConfigAclPrefs[8] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_fob_action_1", "Fob action 1", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction1}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction1\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Toggle RTO"; - json["options"][2] = "Activate RTO"; - json["options"][3] = "Deactivate RTO"; - json["options"][4] = "Open"; - json["options"][5] = "Ring"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "fob_action_1", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"fob_action_1", uidString); - } - - if((int)basicOpenerConfigAclPrefs[9] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_fob_action_2", "Fob action 2", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction2}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction2\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Toggle RTO"; - json["options"][2] = "Activate RTO"; - json["options"][3] = "Deactivate RTO"; - json["options"][4] = "Open"; - json["options"][5] = "Ring"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "fob_action_2", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"fob_action_2", uidString); - } - - if((int)basicOpenerConfigAclPrefs[10] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_fob_action_3", "Fob action 3", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.fobAction3}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"fobAction3\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Toggle RTO"; - json["options"][2] = "Activate RTO"; - json["options"][3] = "Deactivate RTO"; - json["options"][4] = "Open"; - json["options"][5] = "Ring"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "fob_action_3", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"fob_action_3", uidString); - } - - if((int)basicOpenerConfigAclPrefs[12] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_advertising_mode", "Advertising mode", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.advertisingMode}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"advertisingMode\": \"{{ value }}\" }" }}); - json["options"][0] = "Automatic"; - json["options"][1] = "Normal"; - json["options"][2] = "Slow"; - json["options"][3] = "Slowest"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "advertising_mode", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"advertising_mode", uidString); - } - - if((int)basicOpenerConfigAclPrefs[13] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_timezone", "Timezone", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.timeZone}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"timeZone\": \"{{ value }}\" }" }}); - json["options"][0] = "Africa/Cairo"; - json["options"][1] = "Africa/Lagos"; - json["options"][2] = "Africa/Maputo"; - json["options"][3] = "Africa/Nairobi"; - json["options"][4] = "America/Anchorage"; - json["options"][5] = "America/Argentina/Buenos_Aires"; - json["options"][6] = "America/Chicago"; - json["options"][7] = "America/Denver"; - json["options"][8] = "America/Halifax"; - json["options"][9] = "America/Los_Angeles"; - json["options"][10] = "America/Manaus"; - json["options"][11] = "America/Mexico_City"; - json["options"][12] = "America/New_York"; - json["options"][13] = "America/Phoenix"; - json["options"][14] = "America/Regina"; - json["options"][15] = "America/Santiago"; - json["options"][16] = "America/Sao_Paulo"; - json["options"][17] = "America/St_Johns"; - json["options"][18] = "Asia/Bangkok"; - json["options"][19] = "Asia/Dubai"; - json["options"][20] = "Asia/Hong_Kong"; - json["options"][21] = "Asia/Jerusalem"; - json["options"][22] = "Asia/Karachi"; - json["options"][23] = "Asia/Kathmandu"; - json["options"][24] = "Asia/Kolkata"; - json["options"][25] = "Asia/Riyadh"; - json["options"][26] = "Asia/Seoul"; - json["options"][27] = "Asia/Shanghai"; - json["options"][28] = "Asia/Tehran"; - json["options"][29] = "Asia/Tokyo"; - json["options"][30] = "Asia/Yangon"; - json["options"][31] = "Australia/Adelaide"; - json["options"][32] = "Australia/Brisbane"; - json["options"][33] = "Australia/Darwin"; - json["options"][34] = "Australia/Hobart"; - json["options"][35] = "Australia/Perth"; - json["options"][36] = "Australia/Sydney"; - json["options"][37] = "Europe/Berlin"; - json["options"][38] = "Europe/Helsinki"; - json["options"][39] = "Europe/Istanbul"; - json["options"][40] = "Europe/London"; - json["options"][41] = "Europe/Moscow"; - json["options"][42] = "Pacific/Auckland"; - json["options"][43] = "Pacific/Guam"; - json["options"][44] = "Pacific/Honolulu"; - json["options"][45] = "Pacific/Pago_Pago"; - json["options"][46] = "None"; - - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "timezone", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"timezone", uidString); - } - - if((int)basicOpenerConfigAclPrefs[11] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_operating_mode", "Operating mode", name, baseTopic, String("~") + mqtt_topic_config_basic_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.operatingMode}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"operatingMode\": \"{{ value }}\" }" }}); - json["options"][0] = "Generic door opener"; - json["options"][1] = "Analogue intercom"; - json["options"][2] = "Digital intercom"; - json["options"][3] = "Siedle"; - json["options"][4] = "TCS"; - json["options"][5] = "Bticino"; - json["options"][6] = "Siedle HTS"; - json["options"][7] = "STR"; - json["options"][8] = "Ritto"; - json["options"][9] = "Fermax"; - json["options"][10] = "Comelit"; - json["options"][11] = "Urmet BiBus"; - json["options"][12] = "Urmet 2Voice"; - json["options"][13] = "Golmar"; - json["options"][14] = "SKS"; - json["options"][15] = "Spare"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "operating_mode", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"operating_mode", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[1] == 1) - { - // BUS mode switch analogue - publishHassTopic("switch", - "bus_mode_switch", - uidString, - "_bus_mode_switch", - "BUS mode switch analogue", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"busModeSwitch\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"busModeSwitch\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.busModeSwitch}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"bus_mode_switch", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[2] == 1) - { - publishHassTopic("number", - "short_circuit_duration", - uidString, - "_short_circuit_duration", - "Short circuit duration", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"shortCircuitDuration\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.shortCircuitDuration}}" }, - { (char*)"min", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"short_circuit_duration", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[3] == 1) - { - publishHassTopic("number", - "electric_strike_delay", - uidString, - "_electric_strike_delay", - "Electric strike delay", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"electricStrikeDelay\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.electricStrikeDelay}}" }, - { (char*)"min", (char*)"0" }, - { (char*)"max", (char*)"30000" }, - { (char*)"step", (char*)"3000" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"electric_strike_delay", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[4] == 1) - { - // Random Electric Strike Delay - publishHassTopic("switch", - "random_electric_strike_delay", - uidString, - "_random_electric_strike_delay", - "Random electric strike delay", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"randomElectricStrikeDelay\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"randomElectricStrikeDelay\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.randomElectricStrikeDelay}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"random_electric_strike_delay", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[5] == 1) - { - publishHassTopic("number", - "electric_strike_duration", - uidString, - "_electric_strike_duration", - "Electric strike duration", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"electricStrikeDuration\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.electricStrikeDuration}}" }, - { (char*)"min", (char*)"1000" }, - { (char*)"max", (char*)"30000" }, - { (char*)"step", (char*)"3000" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"electric_strike_duration", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[6] == 1) - { - // Disable RTO after ring - publishHassTopic("switch", - "disable_rto_after_ring", - uidString, - "_disable_rto_after_ring", - "Disable RTO after ring", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"disableRtoAfterRing\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"disableRtoAfterRing\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.disableRtoAfterRing}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"disable_rto_after_ring", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[7] == 1) - { - publishHassTopic("number", - "rto_timeout", - uidString, - "_rto_timeout", - "RTO timeout", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"rtoTimeout\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.rtoTimeout}}" }, - { (char*)"min", (char*)"5" }, - { (char*)"max", (char*)"60" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"rto_timeout", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[8] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_doorbell_suppression", "Doorbell suppression", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.doorbellSuppression}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"doorbellSuppression\": \"{{ value }}\" }" }}); - json["options"][0] = "Off"; - json["options"][1] = "CM"; - json["options"][2] = "RTO"; - json["options"][3] = "CM & RTO"; - json["options"][4] = "Ring"; - json["options"][5] = "CM & Ring"; - json["options"][6] = "RTO & Ring"; - json["options"][7] = "CM & RTO & Ring"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "doorbell_suppression", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"doorbell_suppression", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[9] == 1) - { - publishHassTopic("number", - "doorbell_suppression_duration", - uidString, - "_doorbell_suppression_duration", - "Doorbell suppression duration", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"cmd_tpl", (char*)"{ \"doorbellSuppressionDuration\": \"{{ value }}\" }" }, - { (char*)"val_tpl", (char*)"{{value_json.doorbellSuppressionDuration}}" }, - { (char*)"min", (char*)"500" }, - { (char*)"max", (char*)"10000" }, - { (char*)"step", (char*)"1000" } - }); - } - else - { - removeHassTopic((char*)"number", (char*)"doorbell_suppression_duration", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[10] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_sound_ring", "Sound ring", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.soundRing}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"soundRing\": \"{{ value }}\" }" }}); - json["options"][0] = "No Sound"; - json["options"][1] = "Sound 1"; - json["options"][2] = "Sound 2"; - json["options"][3] = "Sound 3"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "sound_ring", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"sound_ring", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[11] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_sound_open", "Sound open", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.soundOpen}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"soundOpen\": \"{{ value }}\" }" }}); - json["options"][0] = "No Sound"; - json["options"][1] = "Sound 1"; - json["options"][2] = "Sound 2"; - json["options"][3] = "Sound 3"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "sound_open", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"sound_open", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[12] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_sound_rto", "Sound RTO", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.soundRto}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"soundRto\": \"{{ value }}\" }" }}); - json["options"][0] = "No Sound"; - json["options"][1] = "Sound 1"; - json["options"][2] = "Sound 2"; - json["options"][3] = "Sound 3"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "sound_rto", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"sound_rto", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[13] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_sound_cm", "Sound CM", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.soundCm}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"soundCm\": \"{{ value }}\" }" }}); - json["options"][0] = "No Sound"; - json["options"][1] = "Sound 1"; - json["options"][2] = "Sound 2"; - json["options"][3] = "Sound 3"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "sound_cm", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"sound_cm", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[14] == 1) - { - // Sound confirmation - publishHassTopic("switch", - "sound_confirmation", - uidString, - "_sound_confirmation", - "Sound confirmation", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"soundConfirmation\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"soundConfirmation\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.soundConfirmation}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"sound_confirmation", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[16] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_single_button_press_action", "Single button press action", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.singleButtonPressAction}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"singleButtonPressAction\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Toggle RTO"; - json["options"][2] = "Activate RTO"; - json["options"][3] = "Deactivate RTO"; - json["options"][4] = "Toggle CM"; - json["options"][5] = "Activate CM"; - json["options"][6] = "Deactivate CM"; - json["options"][7] = "Open"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "single_button_press_action", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"single_button_press_action", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[17] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_double_button_press_action", "Double button press action", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.doubleButtonPressAction}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"doubleButtonPressAction\": \"{{ value }}\" }" }}); - json["options"][0] = "No Action"; - json["options"][1] = "Toggle RTO"; - json["options"][2] = "Activate RTO"; - json["options"][3] = "Deactivate RTO"; - json["options"][4] = "Toggle CM"; - json["options"][5] = "Activate CM"; - json["options"][6] = "Deactivate CM"; - json["options"][7] = "Open"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "double_button_press_action", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"double_button_press_action", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[18] == 1) - { - JsonDocument json; - json = createHassJson(uidString, "_battery_type", "Battery type", name, baseTopic, String("~") + mqtt_topic_config_advanced_json, deviceType, "", "", "config", String("~") + mqtt_topic_config_action, {{ (char*)"val_tpl", (char*)"{{value_json.batteryType}}" }, { (char*)"en", (char*)"true" }, { (char*)"cmd_tpl", (char*)"{ \"batteryType\": \"{{ value }}\" }" }}); - json["options"][0] = "Alkali"; - json["options"][1] = "Accumulators"; - json["options"][2] = "Lithium"; - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath("select", "battery_type", uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } - else - { - removeHassTopic((char*)"select", (char*)"battery_type", uidString); - } - - if((int)advancedOpenerConfigAclPrefs[19] == 1) - { - // Automatic battery type detection - publishHassTopic("switch", - "automatic_battery_type_detection", - uidString, - "_automatic_battery_type_detection", - "Automatic battery type detection", - name, - baseTopic, - String("~") + mqtt_topic_config_advanced_json, - deviceType, - "", - "", - "config", - String("~") + mqtt_topic_config_action, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_on", (char*)"{ \"automaticBatteryTypeDetection\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"automaticBatteryTypeDetection\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.automaticBatteryTypeDetection}}" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - } - else - { - removeHassTopic((char*)"switch", (char*)"automatic_battery_type_detection", uidString); - } -} - -void NukiNetwork::publishHASSConfigAccessLog(char *deviceType, const char *baseTopic, char *name, char *uidString) -{ - publishHassTopic("sensor", - "last_action_authorization", - uidString, - "_last_action_authorization", - "Last action authorization", - name, - baseTopic, - String("~") + mqtt_topic_lock_log, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"ic", (char*)"mdi:format-list-bulleted" }, - { (char*)"val_tpl", (char*)"{{ (value_json|selectattr('type', 'eq', 'LockAction')|selectattr('action', 'in', ['Lock', 'Unlock', 'Unlatch'])|first|default).authorizationName|default }}" } - }); - - String rollingSate = "~"; - rollingSate.concat(mqtt_topic_lock_log_rolling); - const char *rollingStateChr = rollingSate.c_str(); - - publishHassTopic("sensor", - "rolling_log", - uidString, - "_rolling_log", - "Rolling authorization log", - name, - baseTopic, - String("~") + mqtt_topic_lock_log_rolling, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"ic", (char*)"mdi:format-list-bulleted" }, - { (char*)"json_attr_t", (char*)rollingStateChr }, - { (char*)"val_tpl", (char*)"{{value_json.index}}" } - }); -} - -void NukiNetwork::publishHASSConfigKeypad(char *deviceType, const char *baseTopic, char *name, char *uidString) -{ - // Keypad battery critical - publishHassTopic("binary_sensor", - "keypad_battery_low", - uidString, - "_keypad_battery_low", - "Keypad battery low", - name, - baseTopic, - String("~") + mqtt_topic_battery_basic_json, - deviceType, - "battery", - "", - "diagnostic", - "", - { - {(char*)"pl_on", (char*)"1"}, - {(char*)"pl_off", (char*)"0"}, - {(char*)"val_tpl", (char*)"{{value_json.keypadCritical}}" } - }); - - // Query Keypad - publishHassTopic("button", - "query_keypad", - uidString, - "_query_keypad", - "Query keypad", - name, - baseTopic, - "", - deviceType, - "", - "", - "diagnostic", - String("~") + mqtt_topic_query_keypad, - { - { (char*)"en", (char*)"false" }, - { (char*)"pl_prs", (char*)"1" } - }); - - publishHassTopic("sensor", - "keypad_status", - uidString, - "_keypad_stats", - "Keypad status", - name, - baseTopic, - String("~") + mqtt_topic_lock_log, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"ic", (char*)"mdi:drag-vertical" }, - { (char*)"val_tpl", (char*)"{{ (value_json|selectattr('type', 'eq', 'KeypadAction')|first|default).completionStatus|default }}" } - }); -} - -void NukiNetwork::publishHASSWifiRssiConfig(char *deviceType, const char *baseTopic, char *name, char *uidString) -{ - if(_device->signalStrength() == 127) - { - return; - } - - publishHassTopic("sensor", - "wifi_signal_strength", - uidString, - "_wifi_signal_strength", - "WIFI signal strength", - name, - baseTopic, - _lockPath + mqtt_topic_wifi_rssi, - deviceType, - "signal_strength", - "measurement", - "diagnostic", - "", - { {(char*)"unit_of_meas", (char*)"dBm"} }); + _hadiscovery->disableHASS(); } void NukiNetwork::publishHassTopic(const String& mqttDeviceType, @@ -3639,223 +958,12 @@ void NukiNetwork::publishHassTopic(const String& mqttDeviceType, std::vector> additionalEntries ) { - if (_discoveryTopic != "") - { - JsonDocument json; - json = createHassJson(uidString, uidStringPostfix, displayName, name, baseTopic, stateTopic, deviceType, deviceClass, stateClass, entityCat, commandTopic, additionalEntries); - serializeJson(json, _buffer, _bufferSize); - String path = createHassTopicPath(mqttDeviceType, mqttDeviceName, uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); - } -} - -String NukiNetwork::createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString) -{ - String path = _discoveryTopic; - path.concat("/"); - path.concat(mqttDeviceType); - path.concat("/"); - path.concat(uidString); - path.concat("/"); - path.concat(mqttDeviceName); - path.concat("/config"); - - return path; + _hadiscovery->publishHassTopic(mqttDeviceType, mqttDeviceName, uidString, uidStringPostfix, displayName, name, baseTopic, stateTopic, deviceType, deviceClass, stateClass, entityCat, commandTopic, additionalEntries); } void NukiNetwork::removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString) { - if (_discoveryTopic != "") - { - String path = createHassTopicPath(mqttDeviceType, mqttDeviceName, uidString); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, ""); - } -} - -void NukiNetwork::removeTopic(const String& mqttPath, const String& mqttTopic) -{ - String path = mqttPath; - path.concat(mqttTopic); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, ""); - -#ifdef DEBUG_NUKIHUB - Log->print(F("Removing MQTT topic: ")); - Log->println(path.c_str()); -#endif -} - - -void NukiNetwork::removeHASSConfig(char* uidString) -{ - removeHassTopic((char*)"lock", (char*)"smartlock", uidString); - removeHassTopic((char*)"binary_sensor", (char*)"battery_low", uidString); - removeHassTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); - removeHassTopic((char*)"sensor", (char*)"battery_voltage", uidString); - removeHassTopic((char*)"sensor", (char*)"trigger", uidString); - removeHassTopic((char*)"binary_sensor", (char*)"mqtt_connected", uidString); - removeHassTopic((char*)"switch", (char*)"reset", uidString); - removeHassTopic((char*)"sensor", (char*)"firmware_version", uidString); - removeHassTopic((char*)"sensor", (char*)"hardware_version", uidString); - removeHassTopic((char*)"sensor", (char*)"nuki_hub_version", uidString); - removeHassTopic((char*)"sensor", (char*)"nuki_hub_build", uidString); - removeHassTopic((char*)"sensor", (char*)"nuki_hub_latest", uidString); - removeHassTopic((char*)"update", (char*)"nuki_hub_update", uidString); - removeHassTopic((char*)"sensor", (char*)"nuki_hub_ip", uidString); - removeHassTopic((char*)"button", (char*)"unlatch", uidString); - removeHassTopic((char*)"button", (char*)"lockngo", uidString); - removeHassTopic((char*)"button", (char*)"lockngounlatch", uidString); - removeHassTopic((char*)"sensor", (char*)"battery_level", uidString); - removeHassTopic((char*)"binary_sensor", (char*)"door_sensor", uidString); - removeHassTopic((char*)"binary_sensor", (char*)"ring_detect", uidString); - removeHassTopic((char*)"sensor", (char*)"sound_level", uidString); - removeHassTopic((char*)"sensor", (char*)"last_action_authorization", uidString); - removeHassTopic((char*)"sensor", (char*)"keypad_status", uidString); - removeHassTopic((char*)"sensor", (char*)"rolling_log", uidString); - removeHassTopic((char*)"sensor", (char*)"wifi_signal_strength", uidString); - removeHassTopic((char*)"sensor", (char*)"bluetooth_signal_strength", uidString); - removeHassTopic((char*)"binary_sensor", (char*)"continuous_mode", uidString); - removeHassTopic((char*)"switch", (char*)"continuous_mode", uidString); - removeHassTopic((char*)"button", (char*)"query_lockstate", uidString); - removeHassTopic((char*)"button", (char*)"query_config", uidString); - removeHassTopic((char*)"button", (char*)"query_keypad", uidString); - removeHassTopic((char*)"button", (char*)"query_battery", uidString); - removeHassTopic((char*)"button", (char*)"query_commandresult", uidString); - removeHassTopic((char*)"switch", (char*)"auto_lock", uidString); - removeHassTopic((char*)"switch", (char*)"auto_unlock", uidString); - removeHassTopic((char*)"switch", (char*)"double_lock", uidString); - removeHassTopic((char*)"switch", (char*)"automatic_battery_type_detection", uidString); - removeHassTopic((char*)"select", (char*)"battery_type", uidString); - removeHassTopic((char*)"select", (char*)"double_button_press_action", uidString); - removeHassTopic((char*)"select", (char*)"single_button_press_action", uidString); - removeHassTopic((char*)"switch", (char*)"sound_confirmation", uidString); - removeHassTopic((char*)"select", (char*)"sound_cm", uidString); - removeHassTopic((char*)"select", (char*)"sound_rto", uidString); - removeHassTopic((char*)"select", (char*)"sound_open", uidString); - removeHassTopic((char*)"select", (char*)"sound_ring", uidString); - removeHassTopic((char*)"number", (char*)"doorbell_suppression_duration", uidString); - removeHassTopic((char*)"select", (char*)"doorbell_suppression", uidString); - removeHassTopic((char*)"number", (char*)"rto_timeout", uidString); - removeHassTopic((char*)"switch", (char*)"disable_rto_after_ring", uidString); - removeHassTopic((char*)"number", (char*)"electric_strike_duration", uidString); - removeHassTopic((char*)"switch", (char*)"random_electric_strike_delay", uidString); - removeHassTopic((char*)"number", (char*)"electric_strike_delay", uidString); - removeHassTopic((char*)"number", (char*)"short_circuit_duration", uidString); - removeHassTopic((char*)"switch", (char*)"bus_mode_switch", uidString); - removeHassTopic((char*)"select", (char*)"operating_mode", uidString); - removeHassTopic((char*)"select", (char*)"timezone", uidString); - removeHassTopic((char*)"select", (char*)"advertising_mode", uidString); - removeHassTopic((char*)"select", (char*)"fob_action_3", uidString); - removeHassTopic((char*)"select", (char*)"fob_action_2", uidString); - removeHassTopic((char*)"select", (char*)"fob_action_1", uidString); - removeHassTopic((char*)"switch", (char*)"dst_mode", uidString); - removeHassTopic((char*)"number", (char*)"timezone_offset", uidString); - removeHassTopic((char*)"switch", (char*)"pairing_enabled", uidString); - removeHassTopic((char*)"number", (char*)"sound_level", uidString); - removeHassTopic((char*)"switch", (char*)"button_enabled", uidString); - removeHassTopic((char*)"switch", (char*)"led_enabled", uidString); - removeHassTopic((char*)"number", (char*)"led_brightness", uidString); - removeHassTopic((char*)"switch", (char*)"auto_update_enabled", uidString); - removeHassTopic((char*)"switch", (char*)"immediate_auto_lock_enabled", uidString); - removeHassTopic((char*)"switch", (char*)"nightmode_immediate_lock_start", uidString); - removeHassTopic((char*)"switch", (char*)"nightmode_auto_unlock", uidString); - removeHassTopic((char*)"switch", (char*)"nightmode_auto_lock", uidString); - removeHassTopic((char*)"text", (char*)"nightmode_end_time", uidString); - removeHassTopic((char*)"text", (char*)"nightmode_start_time", uidString); - removeHassTopic((char*)"switch", (char*)"nightmode_enabled", uidString); - removeHassTopic((char*)"number", (char*)"auto_lock_timeout", uidString); - removeHassTopic((char*)"number", (char*)"unlatch_duration", uidString); - removeHassTopic((char*)"switch", (char*)"detached_cylinder", uidString); - removeHassTopic((char*)"number", (char*)"lockngo_timeout", uidString); - removeHassTopic((char*)"number", (char*)"unlocked_locked_transition_offset_degrees", uidString); - removeHassTopic((char*)"number", (char*)"single_locked_position_offset_degrees", uidString); - removeHassTopic((char*)"number", (char*)"locked_position_offset_degrees", uidString); - removeHassTopic((char*)"number", (char*)"unlocked_position_offset_degrees", uidString); - removeHassTopic((char*)"switch", (char*)"pairing_enabled", uidString); - removeHassTopic((char*)"switch", (char*)"auto_unlatch", uidString); - removeHassTopic((char*)"sensor", (char*)"network_device", uidString); - removeHassTopic((char*)"switch", (char*)"webserver", uidString); - removeHassTopic((char*)"sensor", (char*)"uptime", uidString); - removeHassTopic((char*)"sensor", (char*)"mqtt_log", uidString); - removeHassTopic((char*)"binary_sensor", (char*)"hybrid_connected", uidString); - removeHassTopic((char*)"sensor", (char*)"nuki_hub_restart_reason", uidString); - removeHassTopic((char*)"sensor", (char*)"nuki_hub_restart_reason_esp", uidString); -} - -void NukiNetwork::removeHASSConfigTopic(char *deviceType, char *name, char *uidString) -{ - removeHassTopic(deviceType, name, uidString); -} - -JsonDocument NukiNetwork::createHassJson(const String& uidString, - const String& uidStringPostfix, - const String& displayName, - const String& name, - const String& baseTopic, - const String& stateTopic, - const String& deviceType, - const String& deviceClass, - const String& stateClass, - const String& entityCat, - const String& commandTopic, - std::vector> additionalEntries - ) -{ - JsonDocument json; - json.clear(); - JsonObject dev = json["dev"].to(); - JsonArray ids = dev["ids"].to(); - ids.add(String("nuki_") + uidString); - json["dev"]["mf"] = "Nuki"; - json["dev"]["mdl"] = deviceType; - json["dev"]["name"] = name; - json["~"] = baseTopic; - json["name"] = displayName; - json["unique_id"] = String(uidString) + uidStringPostfix; - - if(deviceClass != "") - { - json["dev_cla"] = deviceClass; - } - - if(stateTopic != "") - { - json["stat_t"] = stateTopic; - } - - if(stateClass != "") - { - json["stat_cla"] = stateClass; - } - - if(entityCat != "") - { - json["ent_cat"] = entityCat; - } - - if(commandTopic != "") - { - json["cmd_t"] = commandTopic; - } - - json["avty"]["t"] = _lockPath + mqtt_topic_mqtt_connection_state; - - for(const auto& entry : additionalEntries) - { - if(strcmp(entry.second, "true") == 0) - { - json[entry.first] = true; - } - else if(strcmp(entry.second, "false") == 0) - { - json[entry.first] = false; - } - else - { - json[entry.first] = entry.second; - } - } - - return json; + _hadiscovery->removeHassTopic(mqttDeviceType, mqttDeviceName, uidString); } void NukiNetwork::batteryTypeToString(const Nuki::BatteryType battype, char* str) diff --git a/src/NukiNetwork.h b/src/NukiNetwork.h index a867311..ad90ff0 100644 --- a/src/NukiNetwork.h +++ b/src/NukiNetwork.h @@ -56,32 +56,6 @@ public: void publishLongLong(const char* prefix, const char* topic, int64_t value, bool retain); void publishBool(const char* prefix, const char* topic, const bool value, bool retain); void publishString(const char* prefix, const char* topic, const char* value, bool retain); - - void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction); - void publishHASSConfigAdditionalLockEntities(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSConfigDoorSensor(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSConfigAdditionalOpenerEntities(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSConfigAccessLog(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSConfigKeypad(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSWifiRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString); - void removeHASSConfig(char* uidString); - void removeHASSConfigTopic(char* deviceType, char* name, char* uidString); - void publishHassTopic(const String& mqttDeviceType, - const String& mqttDeviceName, - const String& uidString, - const String& uidStringPostfix, - const String& displayName, - const String& name, - const String& baseTopic, - const String& stateTopic, - const String& deviceType, - const String& deviceClass, - const String& stateClass = "", - const String& entityCat = "", - const String& commandTopic = "", - std::vector> additionalEntries = {} - ); - void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); void removeTopic(const String& mqttPath, const String& mqttTopic); void batteryTypeToString(const Nuki::BatteryType battype, char* str); void advertisingModeToString(const Nuki::AdvertisingMode advmode, char* str); @@ -123,22 +97,25 @@ private: void onMqttDisconnect(const espMqttClientTypes::DisconnectReason& reason); void parseGpioTopics(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total); void gpioActionCallback(const GpioAction& action, const int& pin); - - String createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); - JsonDocument createHassJson(const String& uidString, - const String& uidStringPostfix, - const String& displayName, - const String& name, - const String& baseTopic, - const String& stateTopic, - const String& deviceType, - const String& deviceClass, - const String& stateClass = "", - const String& entityCat = "", - const String& commandTopic = "", - std::vector> additionalEntries = {} - ); void buildMqttPath(char* outPath, std::initializer_list paths); + void setupHASS(int type); + void disableHASS(); + void publishHassTopic(const String& mqttDeviceType, + const String& mqttDeviceName, + const String& uidString, + const String& uidStringPostfix, + const String& displayName, + const String& name, + const String& baseTopic, + const String& stateTopic, + const String& deviceType, + const String& deviceClass, + const String& stateClass, + const String& entityCat, + const String& commandTopic, + std::vector> additionalEntries + ); + void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); const char* _lastWillPayload = "offline"; char _mqttConnectionStateTopic[211] = {0}; diff --git a/src/NukiNetworkLock.cpp b/src/NukiNetworkLock.cpp index 9855787..0ae6768 100644 --- a/src/NukiNetworkLock.cpp +++ b/src/NukiNetworkLock.cpp @@ -1663,53 +1663,6 @@ bool NukiNetworkLock::comparePrefixedPath(const char *fullPath, const char *subP return strcmp(fullPath, prefixedPath) == 0; } -void NukiNetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction, - char *unlockAction, char *openAction) -{ - String availabilityTopic = _preferences->getString(preference_mqtt_lock_path); - availabilityTopic.concat("/maintenance/mqttConnectionState"); - _network->publishHASSConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, lockAction, unlockAction, openAction); - _network->publishHASSConfigAdditionalLockEntities(deviceType, baseTopic, name, uidString); - - if(hasDoorSensor) - { - _network->publishHASSConfigDoorSensor(deviceType, baseTopic, name, uidString); - } - else - { - _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"door_sensor", uidString); - } - -#ifndef CONFIG_IDF_TARGET_ESP32H2 - _network->publishHASSWifiRssiConfig(deviceType, baseTopic, name, uidString); -#endif - - if(publishAuthData) - { - _network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString); - } - else - { - _network->removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); - _network->removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); - } - - if(hasKeypad) - { - _network->publishHASSConfigKeypad(deviceType, baseTopic, name, uidString); - } - else - { - _network->removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString); - _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); - } -} - -void NukiNetworkLock::removeHASSConfig(char *uidString) -{ - _network->removeHASSConfig(uidString); -} - void NukiNetworkLock::publishOffAction(const int value) { _network->publishInt(_nukiOfficial->getMqttPath(), mqtt_topic_official_lock_action, value, false); diff --git a/src/NukiNetworkLock.h b/src/NukiNetworkLock.h index 1dbdb3d..d172784 100644 --- a/src/NukiNetworkLock.h +++ b/src/NukiNetworkLock.h @@ -37,8 +37,6 @@ public: void publishRssi(const int& rssi); void publishRetry(const std::string& message); void publishBleAddress(const std::string& address); - void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char* lockAction, char* unlockAction, char* openAction); - void removeHASSConfig(char* uidString); void publishKeypad(const std::list& entries, uint maxKeypadCodeCount); void publishTimeControl(const std::list& timeControlEntries, uint maxTimeControlEntryCount); void publishAuth(const std::list& authEntries, uint maxAuthEntryCount); diff --git a/src/NukiNetworkOpener.cpp b/src/NukiNetworkOpener.cpp index 3dbf265..ca286dc 100644 --- a/src/NukiNetworkOpener.cpp +++ b/src/NukiNetworkOpener.cpp @@ -859,38 +859,6 @@ void NukiNetworkOpener::publishBleAddress(const std::string &address) publishString(mqtt_topic_lock_address, address, true); } -void NukiNetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction) -{ - String availabilityTopic = _preferences->getString(preference_mqtt_lock_path); - availabilityTopic.concat("/maintenance/mqttConnectionState"); - - _network->publishHASSConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, lockAction, unlockAction, openAction); - _network->publishHASSConfigAdditionalOpenerEntities(deviceType, baseTopic, name, uidString); - if(publishAuthData) - { - _network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString); - } - else - { - _network->removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); - _network->removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); - } - if(hasKeypad) - { - _network->publishHASSConfigKeypad(deviceType, baseTopic, name, uidString); - } - else - { - _network->removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString); - _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); - } -} - -void NukiNetworkOpener::removeHASSConfig(char* uidString) -{ - _network->removeHASSConfig(uidString); -} - void NukiNetworkOpener::publishKeypad(const std::list& entries, uint maxKeypadCodeCount) { bool publishCode = _preferences->getBool(preference_keypad_publish_code, false); diff --git a/src/NukiNetworkOpener.h b/src/NukiNetworkOpener.h index 3facab4..6db0a38 100644 --- a/src/NukiNetworkOpener.h +++ b/src/NukiNetworkOpener.h @@ -30,8 +30,6 @@ public: void publishRssi(const int& rssi); void publishRetry(const std::string& message); void publishBleAddress(const std::string& address); - void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction); - void removeHASSConfig(char* uidString); void publishKeypad(const std::list& entries, uint maxKeypadCodeCount); void publishTimeControl(const std::list& timeControlEntries, uint maxTimeControlEntryCount); void publishAuth(const std::list& authEntries, uint maxAuthEntryCount); diff --git a/src/NukiOfficial.cpp b/src/NukiOfficial.cpp index af10b63..64960b3 100644 --- a/src/NukiOfficial.cpp +++ b/src/NukiOfficial.cpp @@ -88,7 +88,7 @@ void NukiOfficial::onOfficialUpdateReceived(const char *topic, const char *value bool publishBatteryJson = false; memset(&str, 0, sizeof(str)); - Log->println("Official Nuki change recieved"); + Log->println("Official Nuki change received"); Log->print(F("Topic: ")); Log->println(topic); Log->print(F("Value: ")); diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index b1b3980..ef548cc 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -313,7 +313,8 @@ void NukiOpenerWrapper::update() } if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted) { - setupHASS(); + _network->setupHASS(2); + _hassSetupCompleted = true; } if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs)) { @@ -610,13 +611,13 @@ void NukiOpenerWrapper::updateConfig() } else { - Log->println(F("Invalid/Unexpected opener config recieved, ID does not matched saved ID")); + Log->println(F("Invalid/Unexpected opener config received, ID does not matched saved ID")); expectedConfig = false; } } else { - Log->println(F("Invalid/Unexpected opener config recieved, Config is not valid")); + Log->println(F("Invalid/Unexpected opener config received, Config is not valid")); expectedConfig = false; } @@ -633,7 +634,7 @@ void NukiOpenerWrapper::updateConfig() } else { - Log->println(F("Invalid/Unexpected opener advanced config recieved, Advanced config is not valid")); + Log->println(F("Invalid/Unexpected opener advanced config received, Advanced config is not valid")); expectedConfig = false; } } @@ -646,7 +647,7 @@ void NukiOpenerWrapper::updateConfig() else { ++_retryConfigCount; - Log->println(F("Invalid/Unexpected opener config and/or advanced config recieved, retrying in 10 seconds")); + Log->println(F("Invalid/Unexpected opener config and/or advanced config received, retrying in 10 seconds")); int64_t ts = espMillis(); _nextConfigUpdateTs = ts + 10000; } @@ -3957,43 +3958,6 @@ void NukiOpenerWrapper::readAdvancedConfig() postponeBleWatchdog(); } -void NukiOpenerWrapper::setupHASS() -{ - if(!_nukiConfigValid) - { - return; - } - if(_preferences->getUInt(preference_nuki_id_opener, 0) != _nukiConfig.nukiId) - { - return; - } - - String baseTopic = _preferences->getString(preference_mqtt_lock_path); - baseTopic.concat("/opener"); - char uidString[20]; - itoa(_nukiConfig.nukiId, uidString, 16); - - if(_preferences->getBool(preference_opener_continuous_mode, false)) - { - _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), _publishAuthData, _hasKeypad, (char*)"deactivateCM", (char*)"activateCM", (char*)"electricStrikeActuation"); - } - else - { - _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), _publishAuthData, _hasKeypad, (char*)"deactivateRTO", (char*)"activateRTO", (char*)"electricStrikeActuation"); - } - - _hassSetupCompleted = true; - - Log->println("HASS setup for opener completed."); -} - -void NukiOpenerWrapper::disableHASS() -{ - char uidString[20]; - itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16); - _network->removeHASSConfig(uidString); -} - void NukiOpenerWrapper::printCommandResult(Nuki::CmdResult result) { char resultStr[15]; diff --git a/src/NukiOpenerWrapper.h b/src/NukiOpenerWrapper.h index f0c892f..aea104d 100644 --- a/src/NukiOpenerWrapper.h +++ b/src/NukiOpenerWrapper.h @@ -31,9 +31,6 @@ public: uint16_t getPin(); void unpair(); - - void disableHASS(); - void disableWatchdog(); const NukiOpener::OpenerState& keyTurnerState(); @@ -77,8 +74,6 @@ private: void readConfig(); void readAdvancedConfig(); - void setupHASS(); - void printCommandResult(Nuki::CmdResult result); NukiOpener::LockAction lockActionToEnum(const char* str); // char array at least 14 characters diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index 49aba9a..36d03ce 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -405,7 +405,8 @@ void NukiWrapper::update() } if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted) { - setupHASS(); + _network->setupHASS(1); + _hassSetupCompleted = true; } if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs)) { @@ -693,13 +694,13 @@ void NukiWrapper::updateConfig() } else { - Log->println(F("Invalid/Unexpected lock config recieved, ID does not matched saved ID")); + Log->println(F("Invalid/Unexpected lock config received, ID does not matched saved ID")); expectedConfig = false; } } else { - Log->println(F("Invalid/Unexpected lock config recieved, Config is not valid")); + Log->println(F("Invalid/Unexpected lock config received, Config is not valid")); expectedConfig = false; } @@ -716,7 +717,7 @@ void NukiWrapper::updateConfig() } else { - Log->println(F("Invalid/Unexpected lock advanced config recieved, Advanced config is not valid")); + Log->println(F("Invalid/Unexpected lock advanced config received, Advanced config is not valid")); expectedConfig = false; } } @@ -729,7 +730,7 @@ void NukiWrapper::updateConfig() else { ++_retryConfigCount; - Log->println(F("Invalid/Unexpected lock config and/or advanced config recieved, retrying in 10 seconds")); + Log->println(F("Invalid/Unexpected lock config and/or advanced config received, retrying in 10 seconds")); int64_t ts = espMillis(); _nextConfigUpdateTs = ts + 10000; } @@ -4074,28 +4075,6 @@ void NukiWrapper::readAdvancedConfig() } } -void NukiWrapper::setupHASS() -{ - if(!_nukiConfigValid) - { - return; - } - if(_preferences->getUInt(preference_nuki_id_lock, 0) != _nukiConfig.nukiId) - { - return; - } - - String baseTopic = _preferences->getString(preference_mqtt_lock_path); - baseTopic.concat("/lock"); - char uidString[20]; - itoa(_nukiConfig.nukiId, uidString, 16); - - _network->publishHASSConfig((char*)"SmartLock", baseTopic.c_str(),(char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), hasDoorSensor(), _hasKeypad, _publishAuthData, (char*)"lock", (char*)"unlock", (char*)"unlatch"); - _hassSetupCompleted = true; - - Log->println("HASS setup for lock completed."); -} - bool NukiWrapper::hasDoorSensor() const { return _keyTurnerState.doorSensorState == Nuki::DoorSensorState::DoorClosed || @@ -4103,13 +4082,6 @@ bool NukiWrapper::hasDoorSensor() const _keyTurnerState.doorSensorState == Nuki::DoorSensorState::Calibrating; } -void NukiWrapper::disableHASS() -{ - char uidString[20]; - itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16); - _network->removeHASSConfig(uidString); -} - const BLEAddress NukiWrapper::getBleAddress() const { return _nukiLock.getBleAddress(); diff --git a/src/NukiWrapper.h b/src/NukiWrapper.h index 2358e02..2212058 100644 --- a/src/NukiWrapper.h +++ b/src/NukiWrapper.h @@ -33,8 +33,6 @@ public: uint16_t getPin(); void unpair(); - void disableHASS(); - void disableWatchdog(); const NukiLock::KeyTurnerState& keyTurnerState(); @@ -81,8 +79,6 @@ private: void readConfig(); void readAdvancedConfig(); - void setupHASS(); - void printCommandResult(Nuki::CmdResult result); NukiLock::LockAction lockActionToEnum(const char* str); // char array at least 14 characters diff --git a/src/PreferencesKeys.h b/src/PreferencesKeys.h index 8390e0f..bcf1598 100644 --- a/src/PreferencesKeys.h +++ b/src/PreferencesKeys.h @@ -48,6 +48,7 @@ #define preference_gpio_configuration (char*)"gpiocfg" #define preference_mqtt_hass_enabled (char*)"hassena" #define preference_mqtt_hass_discovery (char*)"hassdiscovery" +#define preference_hass_device_discovery (char*)"hassdevdisc" #define preference_webserver_enabled (char*)"websrvena" #define preference_update_from_mqtt (char*)"updMqtt" #define preference_disable_non_json (char*)"disnonjson" @@ -294,7 +295,7 @@ private: preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi, preference_network_custom_pwr, preference_network_custom_mdio, preference_ntw_reconfigure, preference_lock_max_auth_entry_count, preference_opener_max_auth_entry_count, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_wifi_ssid, preference_wifi_pass, - preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_mqtt_hass_enabled + preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_mqtt_hass_enabled, preference_hass_device_discovery }; std::vector _redact = { @@ -309,7 +310,7 @@ private: preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_register_opener_as_app, preference_ip_dhcp_enabled, preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_enabled, preference_mqtt_hass_enabled, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt, - preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled, + preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled, preference_hass_device_discovery, preference_ntw_reconfigure, preference_keypad_check_code_enabled, preference_disable_network_not_connected }; std::vector _bytePrefs = diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 61255a7..80302b4 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -1740,39 +1740,35 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) //configChanged = true; } } + else if(key == "HADEVDISC") + { + if(_preferences->getBool(preference_hass_device_discovery, false) != (value == "1")) + { + _network->disableHASS(); + _preferences->putBool(preference_hass_device_discovery, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } else if(key == "ENHADISC") { if(_preferences->getBool(preference_mqtt_hass_enabled, false) != (value == "1")) { - if(!_preferences->getBool(preference_mqtt_hass_enabled, false)) - { - if (_nuki != nullptr) - { - _nuki->disableHASS(); - } - if (_nukiOpener != nullptr) - { - _nukiOpener->disableHASS(); - } - } + _network->disableHASS(); _preferences->putBool(preference_mqtt_hass_enabled, (value == "1")); Log->print(F("Setting changed: ")); Log->println(key); configChanged = true; } } + + HADEVDISC else if(key == "HASSDISCOVERY") { if(_preferences->getString(preference_mqtt_hass_discovery, "") != value) { - if (_nuki != nullptr) - { - _nuki->disableHASS(); - } - if (_nukiOpener != nullptr) - { - _nukiOpener->disableHASS(); - } + _network->disableHASS(); _preferences->putString(preference_mqtt_hass_discovery, value); Log->print(F("Setting changed: ")); Log->println(key); @@ -3543,6 +3539,7 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request) printInputField(&response, "MQTTPASS", "MQTT Password", "*", 30, "", true, true); printInputField(&response, "MQTTPATH", "MQTT NukiHub Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, ""); printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), "chkHass"); + printCheckBox(&response, "HADEVDISC", "Use Home Assistant device based discovery", _preferences->getBool(preference_hass_device_discovery), ""); response.print("
    "); response.print("

    Advanced MQTT Configuration

    "); @@ -4209,7 +4206,7 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request) response.print(_preferences->getInt(preference_command_nr_of_retries, 3)); response.print("\nBluetooth command retry delay (ms): "); response.print(_preferences->getInt(preference_command_retry_delay, 100)); - response.print("\nSeconds until reboot when no BLE beacons recieved: "); + response.print("\nSeconds until reboot when no BLE beacons received: "); response.print(_preferences->getInt(preference_restart_ble_beacon_lost, 60)); response.print("\n\n------------ QUERY / PUBLISH SETTINGS ------------"); response.print("\nLock/Opener state query interval (s): "); @@ -4643,14 +4640,14 @@ esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, bool opener) if(!opener && _nuki != nullptr) { - _nuki->disableHASS(); _nuki->unpair(); } if(opener && _nukiOpener != nullptr) { - _nukiOpener->disableHASS(); _nukiOpener->unpair(); } + + _network->disableHASS(); waitAndProcess(false, 1000); restartEsp(RestartReason::DeviceUnpaired); return res; @@ -4769,15 +4766,14 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request) if(_nuki != nullptr) { - _nuki->disableHASS(); _nuki->unpair(); } if(_nukiOpener != nullptr) { - _nukiOpener->disableHASS(); _nukiOpener->unpair(); } + _network->disableHASS(); _preferences->clear(); #ifndef CONFIG_IDF_TARGET_ESP32H2 From 4f380b4bf8fbfe8e09f402662cca77e8c9cf64c2 Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 5 Nov 2024 22:21:54 +0100 Subject: [PATCH 11/17] Update README.md --- README.md | 92 +++++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 9110acf..d7a29ee 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ See the "[Connecting via Ethernet](#connecting-via-ethernet-optional)" section f ## Recommended ESP32 devices -- If WIFI6 is required: ESP32-C6 +- If WIFI6 is absolutely required: ESP32-C6 - If PoE is required: Any of the above mentioned devices with PoE or any other ESP device in combination with a SPI Ethernet module ([W5500](https://www.aliexpress.com/w/wholesale-w5500.html)) and [PoE to Ethernet and USB type B/C splitter](https://aliexpress.com/w/wholesale-poe-splitter-usb-c.html) - If you want maximum performance and intend to run any or multiple of the following: - a Nuki Lock and Nuki Opener and/or @@ -69,17 +69,17 @@ The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using P | Feature | Nuki Hub | Nuki Bridge | |---|---|---| | Bridge API | | x | -| Smart Lock remote control | (optional via smarthome solution) | x | +| Smart Lock remote control | x | x | | Smart Home integration via Matter | | x | | Apple HomeKit integration via Matter | | x | -| MQTT API | x | x (only for SL > 3, Pro models) | +| MQTT API | x | x (only for SL 3, 4 and 4 Pro models) | | Wired LAN support | x | | | Power over Ethernet (PoE) | x (if supported by LAN/ESP module) | | -| WLAN support | x | x (only for SL > 3, Pro Models) || Home Assistant integration | x (full integration of most Nuki features) | x | +| WLAN support | x | x (only for SL 3 and 4 Pro Models) || Home Assistant integration | x (full integration of most Nuki features) | x | | Home Automation platform integration | x | x | -| Cloud support | (optional via smarthome solution) | x | -| Cloud-less operation | x | x (since fw 3.8.2, to be tested) | -| Smarthome app integration | | x | +| Cloud support | x (optional via smarthome solution) | x | +| Cloud-less operation | x | x (since fw 3.8.2) | +| Official Nuki app integration | | x | | Nuki Smartlocks all models | x | x | | Nuki opener | x | x | | Nuki Keypad (1.0 and 2.0) | x | x | @@ -87,9 +87,7 @@ The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using P | Fine-grained access control of MQTT API | x | | | Export of lock actions via MQTT API | x | | | Control via GPIO | x | | -| Hybrid mode for Pro locks | x | | - - +| Hybrid mode for WiFI and Thread connected locks | x | | ## Support Nuki Hub development @@ -346,39 +344,39 @@ After importing the device will reboot. - opener/retry: Reports the current number of retries for the current command. 0 when command is successful, "failed" if the number of retries is greater than the maximum configured number of retries. ### Configuration -- [lock/opener]/configuration/buttonEnabled: 1 if the Nuki Lock/Opener button is enabled, otherwise 0. -- [lock/opener]/configuration/ledEnabled: 1 if the Nuki Lock/Opener LED is enabled, otherwise 0. -- [lock/opener]/configuration/ledBrightness: Set to the brightness of the LED on the Nuki Lock (0=min; 5=max) (Lock only). -- [lock/opener]/configuration/singleLock: 0 if the Nuki Lock is set to double-lock the door, otherwise 1 (= single-lock) (Lock only). -- [lock/opener]/configuration/autoLock: 1 if the Nuki Lock is set to Auto Lock, otherwise 0 (Lock only). -- [lock/opener]/configuration/autoUnlock: 1 if the Nuki Lock is set to Auto Unlock, otherwise 0 (Lock only). -- [lock/opener]/configuration/soundLevel: Set to the volume for sounds the Nuki Opener plays (0 = min; 255 = max) (Opener only). -- [lock/opener]/configuration/action: Allows changing configuration settings of the Nuki Lock/Opener using a JSON formatted value. After receiving the action, the value is set to "--". See the "[Changing Nuki Lock/Opener Configuration](#changing-nuki-lockopener-configuration)" section of this README for possible actions/values -- [lock/opener]/configuration/commandResult: Result of the last configuration change action as JSON data. See the "[Changing Nuki Lock/Opener Configuration](#changing-nuki-lockopener-configuration)" section of this README for possible values -- [lock/opener]/configuration/basicJson: The current basic configuration of the Nuki Lock/Opener as JSON data. See [Nuki Smart Lock API](https://developer.nuki.io/page/nuki-smart-lock-api-2/2/#heading--set-config) and [Nuki Opener API](https://developer.nuki.io/page/nuki-opener-api-1/7/#heading--set-config) for available settings. Please note: Longitude and Latitude of the Lock/Opener are not published to MQTT by design. These values can still be changed though. -- [lock/opener]/configuration/advancedJson: The current advanced configuration of the Nuki Lock/Opener as JSON data. See [Nuki Smart Lock API](https://developer.nuki.io/page/nuki-smart-lock-api-2/2/#heading--advanced-config) and [Nuki Opener API](https://developer.nuki.io/page/nuki-opener-api-1/7/#heading--advanced-config) for available settings. +- [lock/opener/]configuration/buttonEnabled: 1 if the Nuki Lock/Opener button is enabled, otherwise 0. +- [lock/opener/]configuration/ledEnabled: 1 if the Nuki Lock/Opener LED is enabled, otherwise 0. +- [lock/opener/]configuration/ledBrightness: Set to the brightness of the LED on the Nuki Lock (0=min; 5=max) (Lock only). +- [lock/opener/]configuration/singleLock: 0 if the Nuki Lock is set to double-lock the door, otherwise 1 (= single-lock) (Lock only). +- [lock/opener/]configuration/autoLock: 1 if the Nuki Lock is set to Auto Lock, otherwise 0 (Lock only). +- [lock/opener/]configuration/autoUnlock: 1 if the Nuki Lock is set to Auto Unlock, otherwise 0 (Lock only). +- [lock/opener/]configuration/soundLevel: Set to the volume for sounds the Nuki Opener plays (0 = min; 255 = max) (Opener only). +- [lock/opener/]configuration/action: Allows changing configuration settings of the Nuki Lock/Opener using a JSON formatted value. After receiving the action, the value is set to "--". See the "[Changing Nuki Lock/Opener Configuration](#changing-nuki-lockopener-configuration)" section of this README for possible actions/values +- [lock/opener/]configuration/commandResult: Result of the last configuration change action as JSON data. See the "[Changing Nuki Lock/Opener Configuration](#changing-nuki-lockopener-configuration)" section of this README for possible values +- [lock/opener/]configuration/basicJson: The current basic configuration of the Nuki Lock/Opener as JSON data. See [Nuki Smart Lock API](https://developer.nuki.io/page/nuki-smart-lock-api-2/2/#heading--set-config) and [Nuki Opener API](https://developer.nuki.io/page/nuki-opener-api-1/7/#heading--set-config) for available settings. Please note: Longitude and Latitude of the Lock/Opener are not published to MQTT by design. These values can still be changed though. +- [lock/opener/]configuration/advancedJson: The current advanced configuration of the Nuki Lock/Opener as JSON data. See [Nuki Smart Lock API](https://developer.nuki.io/page/nuki-smart-lock-api-2/2/#heading--advanced-config) and [Nuki Opener API](https://developer.nuki.io/page/nuki-opener-api-1/7/#heading--advanced-config) for available settings. ### Query -- [lock/opener]/query/lockstate: Set to 1 to trigger query lockstate. Auto-resets to 0. -- [lock/opener]/query/config: Set to 1 to trigger query config. Auto-resets to 0. -- [lock/opener]/query/keypad: Set to 1 to trigger query keypad. Auto-resets to 0. -- [lock/opener]/query/battery: Set to 1 to trigger query battery. Auto-resets to 0. -- [lock/opener]/query/lockstateCommandResult: Set to 1 to trigger query lockstate command result. Auto-resets to 0. +- [lock/opener/]query/lockstate: Set to 1 to trigger query lockstate. Auto-resets to 0. +- [lock/opener/]query/config: Set to 1 to trigger query config. Auto-resets to 0. +- [lock/opener/]query/keypad: Set to 1 to trigger query keypad. Auto-resets to 0. +- [lock/opener/]query/battery: Set to 1 to trigger query battery. Auto-resets to 0. +- [lock/opener/]query/lockstateCommandResult: Set to 1 to trigger query lockstate command result. Auto-resets to 0. ### Battery -- [lock/opener]/battery/level: Battery level in percent (Lock only). -- [lock/opener]/battery/critical: 1 if battery level is critical, otherwise 0. -- [lock/opener]/battery/charging: 1 if charging, otherwise 0 (Lock only). -- [lock/opener]/battery/voltage: Current Battery voltage (V). -- [lock/opener]/battery/drain: The drain of the last lock action in Milliwattseconds (mWs) (Lock only). -- [lock/opener]/battery/maxTurnCurrent: The highest current of the turn motor during the last lock action (A) (Lock only). -- [lock/opener]/battery/lockDistance: The total distance during the last lock action in centidegrees (Lock only). -- [lock/opener]/battery/keypadCritical: 1 if the battery level of a connected keypad is critical, otherwise 0. -- [lock/opener]/battery/doorSensorCritical (only available in hybdrid mode): 1 if the battery level of a connected doorsensor is critical, otherwise 0. -- [lock/opener]/battery/basicJson: The current battery state (critical, charging, level and keypad critical) of the Nuki Lock/Opener as JSON data. -- [lock/opener]/battery/advancedJson: : The current battery state (critical, batteryDrain, batteryVoltage, lockAction, startVoltage, lowestVoltage, lockDistance, startTemperature, maxTurnCurrent and batteryResistance) of the Nuki Lock/Opener as JSON data. +- [lock/opener/]battery/level: Battery level in percent (Lock only). +- [lock/opener/]battery/critical: 1 if battery level is critical, otherwise 0. +- [lock/opener/]battery/charging: 1 if charging, otherwise 0 (Lock only). +- [lock/opener/]battery/voltage: Current Battery voltage (V). +- [lock/opener/]battery/drain: The drain of the last lock action in Milliwattseconds (mWs) (Lock only). +- [lock/opener/]battery/maxTurnCurrent: The highest current of the turn motor during the last lock action (A) (Lock only). +- [lock/opener/]battery/lockDistance: The total distance during the last lock action in centidegrees (Lock only). +- [lock/opener/]battery/keypadCritical: 1 if the battery level of a connected keypad is critical, otherwise 0. +- [lock/opener/]battery/doorSensorCritical (only available in hybrid mode): 1 if the battery level of a connected doorsensor is critical, otherwise 0. +- [lock/opener/]battery/basicJson: The current battery state (critical, charging, level and keypad critical) of the Nuki Lock/Opener as JSON data. +- [lock/opener/]battery/advancedJson: : The current battery state (critical, batteryDrain, batteryVoltage, lockAction, startVoltage, lowestVoltage, lockDistance, startTemperature, maxTurnCurrent and batteryResistance) of the Nuki Lock/Opener as JSON data. ### Keypad @@ -391,8 +389,8 @@ After importing the device will reboot. ### Info - info/nukiHubVersion: Set to the current version number of the Nuki Hub firmware. -- info/firmwareVersion: Set to the current version number of the Nuki Lock/Opener firmware. -- info/hardwareVersion: Set to the hardware version number of the Nuki Lock/Opener. +- [lock/opener/]info/firmwareVersion: Set to the current version number of the Nuki Lock/Opener firmware. +- [lock/opener/]info/hardwareVersion: Set to the hardware version number of the Nuki Lock/Opener. - info/nukiHubIp: Set to the IP of the Nuki Hub. - info/nukiHubLatest: Set to the latest available Nuki Hub firmware version number (if update checking is enabled in the settings). @@ -520,10 +518,10 @@ If Home Assistant discovery is enabled (see the [Home Assistant Discovery](#hom After the initial installation of the Nuki Hub firmware via serial connection, further updates can be deployed via OTA update from a browser.
    In the configuration portal, select "Firmware update" from the main page.

    -The easiest way to upgrade Nuki Hub, if Nuki Hub is connected to the internet, is to select "Auto Update".
    +The easiest way to upgrade Nuki Hub, if Nuki Hub is connected to the internet, is to select "Update to latest version".
    This will download the latest Nuki Hub and Nuki Hub updater and automatically upgrade both applications.
    Nuki Hub will reboot 3 times during this process, which will take about 5 minutes.
    -If you have enabled "Allow updating using MQTT" you can also use the Home Assistant updater or write "1" to the `nukihub/maintanance/reset` topic to start the update process.
    +If you have enabled "Allow updating using MQTT" you can also use the Home Assistant updater or write "1" to the `nukihub/maintanance/update` topic to start the update process.

    Alternatively you can select a binary file from your file system to update Nuki Hub or the Nuki Hub updator manually
    You can only update Nuki Hub from the Nuki Hub updater and update the updater only from Nuki Hub
    @@ -567,7 +565,7 @@ openssl req -new -key server.key -out server.csr -subj "/C=US/ST=YourState/L=You ## Home Assistant Discovery (optional) This software supports [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/) for integrating Nuki Hub with Home Assistant.
    -To enable autodiscovery, supply the discovery topic that is configured in your Home Assistant instance (If you have not changed this setting in Home Assistant the default is "homeassistant") in the MQTT Configuration page.
    +To enable autodiscovery, enable the checkbox on the "MQTT Configuration" page.
    Once enabled, the Nuki Lock and/or Opener and related entities should automatically appear in your Home Assistant MQTT devices. The following mapping between Home Assistant services and Nuki commands is setup when enabling autodiscovery: @@ -735,7 +733,7 @@ To enable GPIO control, go the the "GPIO Configuration" page where each GPIO can If you prefer to connect to via Ethernet instead of Wi-Fi, you either use one of the supported ESP32 modules with built-in ethernet (see "[Supported devices](#supported-devices)" section) or wire a seperate SPI Ethernet module.
    Currently the Wiznet W5x00 Module (W5100, W5200, W5500), DN9051 and KSZ8851SNL chips are supported.
    -To use a supported module, flash the firmware, connect via Wi-Fi and select the correct network hardware in the "MQTT and Network Configuration" section. +To use a supported module, flash the firmware, connect via Wi-Fi and select the correct network hardware in the "Network Configuration" section. To wire an external W5x00 module to the ESP, use this wiring scheme: @@ -750,7 +748,7 @@ To wire an external W5x00 module to the ESP, use this wiring scheme: Now connect via Wi-Fi and change the network hardware to "Generic W5500".
    -If Ethernet hwardware isn't detected, Wi-Fi is used as a fallback, unless this is disabled in the settings.
    +If Ethernet hardware isn't detected or initialised properly after changing the network device, Wi-Fi will be used as a fallback.

    Note: LAN8720 modules are only supported on the ESP32 and ESP32-Solo1, not on the ESP32-S3, ESP32-C3 or ESP-C6
    @@ -759,7 +757,7 @@ Note: LAN8720 modules are only supported on the ESP32 and ESP32-Solo1, not on th ### Random Wi-Fi disconnects Unfortunately the ESP32 has problems with some access points and reconnecting fails.
    -As a workaround you can navigate to "MQTT and Network Configuration" and enable "Restart on disconnect".
    +As a workaround you can navigate to "Network Configuration" and enable "Restart on disconnect".
    This will reboot the ESP as soon as it gets disconnected from Wi-Fi.
    Also, this reduces the config portal timeout to three minutes to prevent the ESP being stuck in config mode in case an access point is offline temporarily.
    If this still doesn't fix the disconnects and the ESP becomes unreachable, the "Restart timer" option can be used as a last resort.
    @@ -782,7 +780,7 @@ A note about the [M5Stack PoESP32 Unit](https://docs.m5stack.com/en/unit/poesp32 Make sure you are using at least version 2023.8.0 of Home Assistant.
    The Home Assistant developers have made changes to MQTT auto discovery which break support for older version and Nuki Hub has adopted these changes.
    -This unfortunately means that older versions of Home Assistant are not supported by the Nuki Hub discovery implemenation anymore. +This unfortunately means that older versions of Home Assistant are not supported by the Nuki Hub discovery implementation anymore. ## FAQ @@ -816,7 +814,7 @@ This button is disabled by default, but can be enabled in the Home Assistant UI. ### When controlling two locks (or openers) connected to two ESPs, both devices react to the same command. When using Home Asistant, the same status is display for both locks. When using multiple Nuki devices, different paths for each device have to be configured.
    -Navigate to "Nuki Configuration" and change the "MQTT Nuki Smartlock Path" or "MQTT Nuki Opener Path" under "Basic Nuki Configuration" for at least one of the devices.
    +Navigate to "MQTT Configuration" and change the "MQTT NukiHub Path" under "Basic MQTT Configuration" for at least one of the devices.
    ### The Nuki battery is draining quickly. From 86dd78f66b4bb4eaceb6b459373a449820a2211d Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 5 Nov 2024 23:00:14 +0100 Subject: [PATCH 12/17] Update NukiNetwork.cpp --- src/NukiNetwork.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 2edf3d0..505e1fc 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -365,17 +365,6 @@ bool NukiNetwork::update() int64_t ts = espMillis(); _device->update(); - if(_networkTimeout > 0 && (ts - _lastConnectedTs > _networkTimeout * 1000) && ts > 60000) - { - if(!_webEnabled) - { - forceEnableWebServer = true; - } - Log->println("Network timeout has been reached, restarting ..."); - delay(200); - restartEsp(RestartReason::NetworkTimeoutWatchdog); - } - if(disableNetwork || !_mqttEnabled || _device->isApOpen()) { return false; From d120def1a237e573355c5d4f0cb413fa8095aea1 Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 5 Nov 2024 23:01:17 +0100 Subject: [PATCH 13/17] Update NukiNetwork.cpp --- src/NukiNetwork.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 100dac1..4b91d97 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -363,17 +363,6 @@ bool NukiNetwork::update() int64_t ts = espMillis(); _device->update(); - if(_networkTimeout > 0 && (ts - _lastConnectedTs > _networkTimeout * 1000) && ts > 60000) - { - if(!_webEnabled) - { - forceEnableWebServer = true; - } - Log->println("Network timeout has been reached, restarting ..."); - delay(200); - restartEsp(RestartReason::NetworkTimeoutWatchdog); - } - if(disableNetwork || !_mqttEnabled || _device->isApOpen()) { return false; From 5df1ad0854a4cee635b22253dc788a515274aa19 Mon Sep 17 00:00:00 2001 From: iranl Date: Wed, 6 Nov 2024 16:03:44 +0100 Subject: [PATCH 14/17] Move maintenance actions to NukiNetwork --- src/Config.h | 2 +- src/NukiNetwork.cpp | 193 ++++++++++++++++++++++++++++++++++++++++ src/NukiNetwork.h | 3 + src/NukiNetworkLock.cpp | 149 +------------------------------ 4 files changed, 198 insertions(+), 149 deletions(-) diff --git a/src/Config.h b/src/Config.h index 7ef96ef..fc4c53e 100644 --- a/src/Config.h +++ b/src/Config.h @@ -4,7 +4,7 @@ #define NUKI_HUB_VERSION "9.02" #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2024-11-04" +#define NUKI_HUB_DATE "2024-11-06" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 4b91d97..23cc9fa 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -237,6 +237,14 @@ void NukiNetwork::initialize() if(!disableNetwork) { + String mqttPath = _preferences->getString(preference_mqtt_lock_path, ""); + + size_t len = mqttPath.length(); + for(int i=0; i < len; i++) + { + _nukiHubPath[i] = mqttPath.charAt(i); + } + _hostname = _preferences->getString(preference_hostname, ""); if(_hostname == "") @@ -719,6 +727,32 @@ void NukiNetwork::initTopic(const char *prefix, const char *path, const char *va _initTopics[pathStr] = valueStr; } +void NukiNetwork::buildMqttPath(const char *path, char *outPath) +{ + int offset = 0; + char inPath[181] = {0}; + + memcpy(inPath, _nukiHubPath, sizeof(_nukiHubPath)); + + for(const char& c : inPath) + { + if(c == 0x00) + { + break; + } + outPath[offset] = c; + ++offset; + } + int i=0; + while(outPath[i] != 0x00) + { + outPath[offset] = path[i]; + ++i; + ++offset; + } + outPath[i+1] = 0x00; +} + void NukiNetwork::buildMqttPath(char* outPath, std::initializer_list paths) { int offset = 0; @@ -769,6 +803,8 @@ void NukiNetwork::onMqttDataReceived(const espMqttClientTypes::MessageProperties if(_mqttConnectedTs == -1 || (millis() - _mqttConnectedTs < 2000)) return; parseGpioTopics(properties, topic, payload, len, index, total); + + onMqttDataReceived(topic, (byte*)payload, index); for(auto receiver : _mqttReceivers) { @@ -776,6 +812,155 @@ void NukiNetwork::onMqttDataReceived(const espMqttClientTypes::MessageProperties } } +void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) +{ + char* data = (char*)payload; + + if(comparePrefixedPath(topic, mqtt_topic_reset) && strcmp(data, "1") == 0) + { + Log->println(F("Restart requested via MQTT.")); + clearWifiFallback(); + delay(200); + restartEsp(RestartReason::RequestedViaMqtt); + } + else if(comparePrefixedPath(topic, mqtt_topic_update) && strcmp(data, "1") == 0 && _preferences->getBool(preference_update_from_mqtt, false)) + { + Log->println(F("Update requested via MQTT.")); + + bool otaManifestSuccess = false; + JsonDocument doc; + + NetworkClientSecure *client = new NetworkClientSecure; + if (client) + { + client->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start); + { + HTTPClient https; + https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + https.useHTTP10(true); + + if (https.begin(*client, GITHUB_OTA_MANIFEST_URL)) + { + int httpResponseCode = https.GET(); + + if (httpResponseCode == HTTP_CODE_OK || httpResponseCode == HTTP_CODE_MOVED_PERMANENTLY) + { + DeserializationError jsonError = deserializeJson(doc, https.getStream()); + + if (!jsonError) + { + otaManifestSuccess = true; + } + } + } + https.end(); + } + delete client; + } + + if (otaManifestSuccess) + { + String currentVersion = NUKI_HUB_VERSION; + + if(atof(doc["release"]["version"]) >= atof(currentVersion.c_str())) + { + if(strcmp(NUKI_HUB_VERSION, doc["release"]["fullversion"].as()) == 0 && strcmp(NUKI_HUB_BUILD, doc["release"]["build"].as()) == 0 && strcmp(NUKI_HUB_DATE, doc["release"]["time"].as()) == 0) + { + Log->println(F("Nuki Hub is already on the latest release version, OTA update aborted.")); + } + else + { + _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); + _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL); + Log->println(F("Updating to latest release version.")); + delay(200); + restartEsp(RestartReason::OTAReboot); + } + } + else if(currentVersion.indexOf("beta") > 0) + { + if(strcmp(NUKI_HUB_VERSION, doc["beta"]["fullversion"].as()) == 0 && strcmp(NUKI_HUB_BUILD, doc["beta"]["build"].as()) == 0 && strcmp(NUKI_HUB_DATE, doc["beta"]["time"].as()) == 0) + { + Log->println(F("Nuki Hub is already on the latest beta version, OTA update aborted.")); + } + else + { + _preferences->putString(preference_ota_updater_url, GITHUB_BETA_RELEASE_BINARY_URL); + _preferences->putString(preference_ota_main_url, GITHUB_BETA_UPDATER_BINARY_URL); + Log->println(F("Updating to latest beta version.")); + delay(200); + restartEsp(RestartReason::OTAReboot); + } + } + else if(currentVersion.indexOf("master") > 0) + { + if(strcmp(NUKI_HUB_VERSION, doc["master"]["fullversion"].as()) == 0 && strcmp(NUKI_HUB_BUILD, doc["master"]["build"].as()) == 0 && strcmp(NUKI_HUB_DATE, doc["master"]["time"].as()) == 0) + { + Log->println(F("Nuki Hub is already on the latest development version, OTA update aborted.")); + } + else + { + _preferences->putString(preference_ota_updater_url, GITHUB_MASTER_RELEASE_BINARY_URL); + _preferences->putString(preference_ota_main_url, GITHUB_MASTER_UPDATER_BINARY_URL); + Log->println(F("Updating to latest developmemt version.")); + delay(200); + restartEsp(RestartReason::OTAReboot); + } + } + else + { + if(strcmp(NUKI_HUB_VERSION, doc["release"]["fullversion"].as()) == 0 && strcmp(NUKI_HUB_BUILD, doc["release"]["build"].as()) == 0 && strcmp(NUKI_HUB_DATE, doc["release"]["time"].as()) == 0) + { + Log->println(F("Nuki Hub is already on the latest release version, OTA update aborted.")); + } + else + { + _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); + _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL); + Log->println(F("Updating to latest release version.")); + delay(200); + restartEsp(RestartReason::OTAReboot); + } + } + } + else + { + Log->println(F("Failed to retrieve OTA manifest, OTA update aborted.")); + } + } + else if(comparePrefixedPath(topic, mqtt_topic_webserver_action)) + { + if(strcmp(data, "") == 0 || + strcmp(data, "--") == 0) + { + return; + } + + if(strcmp(data, "1") == 0) + { + if(_preferences->getBool(preference_webserver_enabled, true) || forceEnableWebServer) + { + return; + } + Log->println(F("Webserver enabled, restarting.")); + _preferences->putBool(preference_webserver_enabled, true); + + } + else if (strcmp(data, "0") == 0) + { + if(!_preferences->getBool(preference_webserver_enabled, true) && !forceEnableWebServer) + { + return; + } + Log->println(F("Webserver disabled, restarting.")); + _preferences->putBool(preference_webserver_enabled, false); + } + + clearWifiFallback(); + delay(200); + restartEsp(RestartReason::ReconfigureWebServer); + } +} void NukiNetwork::parseGpioTopics(const espMqttClientTypes::MessageProperties &properties, const char *topic, const uint8_t *payload, size_t& len, size_t& index, size_t& total) { @@ -4046,6 +4231,14 @@ uint16_t NukiNetwork::subscribe(const char *topic, uint8_t qos) return _device->mqttSubscribe(topic, qos); } +bool NukiNetwork::comparePrefixedPath(const char *fullPath, const char *subPath) +{ + char prefixedPath[500]; + buildMqttPath(subPath, prefixedPath); + + return strcmp(fullPath, prefixedPath) == 0; +} + void NukiNetwork::addReconnectedCallback(std::function reconnectedCallback) { _reconnectedCallbacks.push_back(reconnectedCallback); diff --git a/src/NukiNetwork.h b/src/NukiNetwork.h index a867311..0cf6265 100644 --- a/src/NukiNetwork.h +++ b/src/NukiNetwork.h @@ -119,10 +119,12 @@ private: #ifndef NUKI_HUB_UPDATER static void onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); void onMqttDataReceived(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total); + void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length); void onMqttConnect(const bool& sessionPresent); void onMqttDisconnect(const espMqttClientTypes::DisconnectReason& reason); void parseGpioTopics(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total); void gpioActionCallback(const GpioAction& action, const int& pin); + bool comparePrefixedPath(const char* fullPath, const char* subPath); String createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); JsonDocument createHassJson(const String& uidString, @@ -139,6 +141,7 @@ private: std::vector> additionalEntries = {} ); void buildMqttPath(char* outPath, std::initializer_list paths); + void buildMqttPath(const char *path, char *outPath); const char* _lastWillPayload = "offline"; char _mqttConnectionStateTopic[211] = {0}; diff --git a/src/NukiNetworkLock.cpp b/src/NukiNetworkLock.cpp index 9855787..1086f02 100644 --- a/src/NukiNetworkLock.cpp +++ b/src/NukiNetworkLock.cpp @@ -7,8 +7,6 @@ #include "RestartReason.h" #include #include -#include -#include extern bool forceEnableWebServer; extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start"); @@ -169,152 +167,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const return; } - if(comparePrefixedPath(topic, mqtt_topic_reset) && strcmp(data, "1") == 0) - { - Log->println(F("Restart requested via MQTT.")); - _network->clearWifiFallback(); - delay(200); - restartEsp(RestartReason::RequestedViaMqtt); - } - else if(comparePrefixedPath(topic, mqtt_topic_update) && strcmp(data, "1") == 0 && _preferences->getBool(preference_update_from_mqtt, false)) - { - Log->println(F("Update requested via MQTT.")); - - bool otaManifestSuccess = false; - JsonDocument doc; - - NetworkClientSecure *client = new NetworkClientSecure; - if (client) - { - client->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start); - { - HTTPClient https; - https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - https.useHTTP10(true); - - if (https.begin(*client, GITHUB_OTA_MANIFEST_URL)) - { - int httpResponseCode = https.GET(); - - if (httpResponseCode == HTTP_CODE_OK || httpResponseCode == HTTP_CODE_MOVED_PERMANENTLY) - { - DeserializationError jsonError = deserializeJson(doc, https.getStream()); - - if (!jsonError) - { - otaManifestSuccess = true; - } - } - } - https.end(); - } - delete client; - } - - if (otaManifestSuccess) - { - String currentVersion = NUKI_HUB_VERSION; - - if(atof(doc["release"]["version"]) >= atof(currentVersion.c_str())) - { - if(strcmp(NUKI_HUB_VERSION, doc["release"]["fullversion"].as()) == 0 && strcmp(NUKI_HUB_BUILD, doc["release"]["build"].as()) == 0 && strcmp(NUKI_HUB_DATE, doc["release"]["time"].as()) == 0) - { - Log->println(F("Nuki Hub is already on the latest release version, OTA update aborted.")); - } - else - { - _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); - _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL); - Log->println(F("Updating to latest release version.")); - delay(200); - restartEsp(RestartReason::OTAReboot); - } - } - else if(currentVersion.indexOf("beta") > 0) - { - if(strcmp(NUKI_HUB_VERSION, doc["beta"]["fullversion"].as()) == 0 && strcmp(NUKI_HUB_BUILD, doc["beta"]["build"].as()) == 0 && strcmp(NUKI_HUB_DATE, doc["beta"]["time"].as()) == 0) - { - Log->println(F("Nuki Hub is already on the latest beta version, OTA update aborted.")); - } - else - { - _preferences->putString(preference_ota_updater_url, GITHUB_BETA_RELEASE_BINARY_URL); - _preferences->putString(preference_ota_main_url, GITHUB_BETA_UPDATER_BINARY_URL); - Log->println(F("Updating to latest beta version.")); - delay(200); - restartEsp(RestartReason::OTAReboot); - } - } - else if(currentVersion.indexOf("master") > 0) - { - if(strcmp(NUKI_HUB_VERSION, doc["master"]["fullversion"].as()) == 0 && strcmp(NUKI_HUB_BUILD, doc["master"]["build"].as()) == 0 && strcmp(NUKI_HUB_DATE, doc["master"]["time"].as()) == 0) - { - Log->println(F("Nuki Hub is already on the latest development version, OTA update aborted.")); - } - else - { - _preferences->putString(preference_ota_updater_url, GITHUB_MASTER_RELEASE_BINARY_URL); - _preferences->putString(preference_ota_main_url, GITHUB_MASTER_UPDATER_BINARY_URL); - Log->println(F("Updating to latest developmemt version.")); - delay(200); - restartEsp(RestartReason::OTAReboot); - } - } - else - { - if(strcmp(NUKI_HUB_VERSION, doc["release"]["fullversion"].as()) == 0 && strcmp(NUKI_HUB_BUILD, doc["release"]["build"].as()) == 0 && strcmp(NUKI_HUB_DATE, doc["release"]["time"].as()) == 0) - { - Log->println(F("Nuki Hub is already on the latest release version, OTA update aborted.")); - } - else - { - _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); - _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL); - Log->println(F("Updating to latest release version.")); - delay(200); - restartEsp(RestartReason::OTAReboot); - } - } - } - else - { - Log->println(F("Failed to retrieve OTA manifest, OTA update aborted.")); - } - } - else if(comparePrefixedPath(topic, mqtt_topic_webserver_action)) - { - if(strcmp(data, "") == 0 || - strcmp(data, "--") == 0) - { - return; - } - - if(strcmp(data, "1") == 0) - { - if(_preferences->getBool(preference_webserver_enabled, true) || forceEnableWebServer) - { - return; - } - Log->println(F("Webserver enabled, restarting.")); - _preferences->putBool(preference_webserver_enabled, true); - - } - else if (strcmp(data, "0") == 0) - { - if(!_preferences->getBool(preference_webserver_enabled, true) && !forceEnableWebServer) - { - return; - } - Log->println(F("Webserver disabled, restarting.")); - _preferences->putBool(preference_webserver_enabled, false); - } - - publishString(mqtt_topic_webserver_action, "--", true); - _network->clearWifiFallback(); - delay(200); - restartEsp(RestartReason::ReconfigureWebServer); - } - else if(comparePrefixedPath(topic, mqtt_topic_lock_log_rolling_last)) + if(comparePrefixedPath(topic, mqtt_topic_lock_log_rolling_last)) { if(strcmp(data, "") == 0 || strcmp(data, "--") == 0) From 1d1c25043d0be6c4825526de2b03a75650d6ea00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Ole=20Sch=C3=BCmann?= Date: Fri, 8 Nov 2024 19:44:51 +0700 Subject: [PATCH 15/17] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0202a0..fc34d51 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using P ## Support Nuki Hub development If you haven't ordered your Nuki product yet, you can support me by using my referrer code when placing your order:
    -REF443RM5HR5X
    +REF2BJHFVHZKK
    This will also give you a 10% discount on 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:
    From c2d3a3e77d74831e93b64283e53cf3d39533e8c8 Mon Sep 17 00:00:00 2001 From: iranl Date: Thu, 7 Nov 2024 23:11:47 +0100 Subject: [PATCH 16/17] Refractor --- README.md | 1 - clion/CMakeLists.txt | 1 + src/Config.h | 9 +- src/HomeAssistantDiscovery.cpp | 966 ++++++++++++------------ src/HomeAssistantDiscovery.h | 83 +- src/MqttReceiver.h | 2 - src/MqttTopics.h | 2 +- src/NukiNetwork.cpp | 56 +- src/NukiNetwork.h | 43 +- src/NukiNetworkLock.cpp | 5 + src/NukiNetworkLock.h | 1 + src/NukiNetworkOpener.cpp | 5 + src/NukiNetworkOpener.h | 1 + src/NukiOfficial.cpp | 6 +- src/NukiOpenerWrapper.cpp | 4 +- src/NukiWrapper.cpp | 77 +- src/NukiWrapper.h | 2 +- src/PreferencesKeys.h | 77 +- src/WebCfgServer.cpp | 4 +- src/WebCfgServer.h | 2 + src/main.cpp | 16 +- src/networkDevices/EthernetDevice.cpp | 3 + src/networkDevices/EthernetDevice.h | 15 +- src/networkDevices/LAN8720Definitions.h | 22 +- src/networkDevices/NetworkDevice.cpp | 87 ++- src/networkDevices/NetworkDevice.h | 33 +- src/networkDevices/WifiDevice.cpp | 3 +- src/networkDevices/WifiDevice.h | 6 +- 28 files changed, 827 insertions(+), 705 deletions(-) diff --git a/README.md b/README.md index d7a29ee..020d0f3 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,6 @@ In a browser navigate to the IP address assigned to the ESP32. - MQTT Password : If using authentication on the MQTT broker set to the password belonging to a username with read/write rights on the MQTT broker, set to # to clear - MQTT NukiHub Path: Set to the preferred MQTT root topic for NukiHub, defaults to "nukihub". Make sure this topic is unique when using multiple ESP32 NukiHub devices - Enable Home Assistant auto discovery: Enable Home Assistant MQTT auto discovery. Will automatically create entities in Home Assistant for NukiHub and connected Nuki Lock and/or Opener when enabled. -- Use Home Assistant device based discovery: Use Home Assistant Device discovery instead of single component discovery. Recommended, but requires Home Assistant 2024.11 or newer. #### Advanced MQTT Configuration diff --git a/clion/CMakeLists.txt b/clion/CMakeLists.txt index 741aea4..0d93523 100644 --- a/clion/CMakeLists.txt +++ b/clion/CMakeLists.txt @@ -49,6 +49,7 @@ set(SRCFILES ../src/util/NetworkUtil.cpp ../src/enums/NetworkDeviceType.h ../src/util/NetworkDeviceInstantiator.cpp + ../src/HomeAssistantDiscovery.cpp ../src/NukiOfficial.cpp ../src/NukiPublisher.cpp ../src/EspMillis.h diff --git a/src/Config.h b/src/Config.h index fc4c53e..cf7ca8a 100644 --- a/src/Config.h +++ b/src/Config.h @@ -4,7 +4,7 @@ #define NUKI_HUB_VERSION "9.02" #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2024-11-06" +#define NUKI_HUB_DATE "2024-11-08" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" @@ -22,6 +22,7 @@ #define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32c3.bin" #define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32c3.bin" #define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32c3.bin" +#define NUKI_HUB_HW (char*)"ESP32-C3" #elif defined(CONFIG_IDF_TARGET_ESP32S3) #if defined(CONFIG_SPIRAM_MODE_OCT) #define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32s3oct.bin" @@ -36,6 +37,7 @@ #define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32s3oct.bin" #define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32s3oct.bin" #define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32s3oct.bin" +#define NUKI_HUB_HW (char*)"ESP32-S3 (Octal PSRAM)" #else #define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32s3.bin" #define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32s3.bin" @@ -49,6 +51,7 @@ #define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32s3.bin" #define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32s3.bin" #define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32s3.bin" +#define NUKI_HUB_HW (char*)"ESP32-S3" #endif #elif defined(CONFIG_IDF_TARGET_ESP32C6) #define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32c6.bin" @@ -63,6 +66,7 @@ #define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32c6.bin" #define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32c6.bin" #define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32c6.bin" +#define NUKI_HUB_HW (char*)"ESP32-C6" #elif defined(CONFIG_IDF_TARGET_ESP32H2) #define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32h2.bin" #define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32h2.bin" @@ -76,6 +80,7 @@ #define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32h2.bin" #define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32h2.bin" #define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32h2.bin" +#define NUKI_HUB_HW (char*)"ESP32-H2" #else #if defined(CONFIG_FREERTOS_UNICORE) #define GITHUB_LATEST_RELEASE_BINARY_URL "https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32-solo1.bin" @@ -90,6 +95,7 @@ #define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32-solo1.bin" #define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32-solo1.bin" #define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32-solo1.bin" +#define NUKI_HUB_HW (char*)"ESP32-SOLO1" #else #define GITHUB_LATEST_RELEASE_BINARY_URL "https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32.bin" #define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32.bin" @@ -103,6 +109,7 @@ #define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32.bin" #define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32.bin" #define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32.bin" +#define NUKI_HUB_HW (char*)"ESP32" #endif #endif diff --git a/src/HomeAssistantDiscovery.cpp b/src/HomeAssistantDiscovery.cpp index cd86bd3..ccfb3ac 100644 --- a/src/HomeAssistantDiscovery.cpp +++ b/src/HomeAssistantDiscovery.cpp @@ -1,139 +1,460 @@ -void NukiWrapper::setupHASS(int type) +#include "HomeAssistantDiscovery.h" +#include "Config.h" +#include "Logger.h" +#include "PreferencesKeys.h" +#include "MqttTopics.h" + +HomeAssistantDiscovery::HomeAssistantDiscovery(NetworkDevice* device, Preferences *preferences, char* buffer, size_t bufferSize) +: _device(device), + _preferences(preferences), + _buffer(buffer), + _bufferSize(bufferSize) { - if(_preferences->getUInt(preference_nuki_id_lock, 0) != _nukiConfig.nukiId) - { - return; - } - - String baseTopic = _preferences->getString(preference_mqtt_lock_path); - baseTopic.concat("/lock"); - char uidString[20]; - itoa(_nukiConfig.nukiId, uidString, 16); - - _network->publishHASSConfig((char*)"SmartLock", baseTopic.c_str(),(char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), hasDoorSensor(), _hasKeypad, _publishAuthData, (char*)"lock", (char*)"unlock", (char*)"unlatch"); - Log->println("HASS setup for lock completed."); + _discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery, ""); + _baseTopic = _preferences->getString(preference_mqtt_lock_path); + _offEnabled = _preferences->getBool(preference_official_hybrid_enabled, false); + _checkUpdates = _preferences->getBool(preference_check_updates, false); + _updateFromMQTT = _preferences->getBool(preference_update_from_mqtt, false); + _hostname = _preferences->getString(preference_hostname, ""); + sprintf(_nukiHubUidString, "%u", _preferences->getUInt(preference_device_id_lock, 0)); } -void NukiWrapper::disableHASS() +void HomeAssistantDiscovery::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad) { char uidString[20]; - itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16); - _network->removeHASSConfig(uidString); + itoa(nukiId, uidString, 16); + bool publishAuthData = _preferences->getBool(preference_publish_authdata, false); + + if(type == 0) + { + publishHASSNukiHubConfig(); + Log->println("HASS setup for NukiHub completed."); + } + else if(type == 1) + { + if(_preferences->getUInt(preference_nuki_id_lock, 0) != nukiId) + { + return; + } + String lockTopic = _baseTopic; + lockTopic.concat("/lock"); + publishHASSConfig((char*)"SmartLock", lockTopic.c_str(), nukiName, uidString, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, publishAuthData, (char*)"lock", (char*)"unlock", (char*)"unlatch"); + Log->println("HASS setup for lock completed."); + } + else if(type == 2) + { + if(_preferences->getUInt(preference_nuki_id_opener, 0) != nukiId) + { + return; + } + String openerTopic = _baseTopic; + openerTopic.concat("/opener"); + if(_preferences->getBool(preference_opener_continuous_mode, false)) + { + publishHASSConfig((char*)"Opener", openerTopic.c_str(), nukiName, uidString, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, publishAuthData, (char*)"deactivateCM", (char*)"activateCM", (char*)"electricStrikeActuation"); + } + else + { + publishHASSConfig((char*)"Opener", openerTopic.c_str(), nukiName, uidString, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, publishAuthData, (char*)"deactivateRTO", (char*)"activateRTO", (char*)"electricStrikeActuation"); + } + + Log->println("HASS setup for opener completed."); + } } -void NukiOpenerWrapper::setupHASS() +void HomeAssistantDiscovery::disableHASS() { - if(_preferences->getUInt(preference_nuki_id_opener, 0) != _nukiConfig.nukiId) - { - return; - } + removeHASSConfig(_nukiHubUidString); - String baseTopic = _preferences->getString(preference_mqtt_lock_path); - baseTopic.concat("/opener"); char uidString[20]; - itoa(_nukiConfig.nukiId, uidString, 16); - if(_preferences->getBool(preference_opener_continuous_mode, false)) + if(_preferences->getUInt(preference_nuki_id_lock, 0) != 0) { - _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), _publishAuthData, _hasKeypad, (char*)"deactivateCM", (char*)"activateCM", (char*)"electricStrikeActuation"); + itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16); + removeHASSConfig(uidString); + } + if(_preferences->getUInt(preference_nuki_id_opener, 0) != 0) + { + itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16); + removeHASSConfig(uidString); + } +} + +void HomeAssistantDiscovery::publishHASSNukiHubConfig() +{ + JsonDocument json; + json.clear(); + JsonObject dev = json["dev"].to(); + JsonArray ids = dev["ids"].to(); + ids.add(String("nuki_") + _nukiHubUidString); + json["dev"]["mf"] = "Technyon"; + json["dev"]["mdl"] = "NukiHub"; + json["dev"]["name"] = _hostname.c_str(); + json["dev"]["sw"] = NUKI_HUB_VERSION; + json["dev"]["hw"] = NUKI_HUB_HW; + + String cuUrl = _preferences->getString(preference_mqtt_hass_cu_url, ""); + + if (cuUrl != "") + { + json["dev"]["cu"] = cuUrl; } else { - _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), _publishAuthData, _hasKeypad, (char*)"deactivateRTO", (char*)"activateRTO", (char*)"electricStrikeActuation"); + json["dev"]["cu"] = "http://" + _device->localIP(); } - _hassSetupCompleted = true; + json["~"] = _baseTopic; + json["name"] = "Restart Nuki Hub"; + json["unique_id"] = String(_nukiHubUidString) + "_reset"; + json["avty"][0]["t"] = String("~") + mqtt_topic_mqtt_connection_state; + json["opt"] = "false"; + json["stat_t"] = String("~") + mqtt_topic_reset; + json["ent_cat"] = "diagnostic"; + json["cmd_t"] = String("~") + mqtt_topic_reset; + json["ic"] = "mdi:restart"; + json["pl_on"] = "1"; + json["pl_off"] = "0"; + json["stat_on"] = "1"; + json["stat_off"] = "0"; - Log->println("HASS setup for opener completed."); -} + serializeJson(json, _buffer, _bufferSize); -void NukiOpenerWrapper::disableHASS() -{ - char uidString[20]; - itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16); - _network->removeHASSConfig(uidString); -} + String path = _preferences->getString(preference_mqtt_hass_discovery, "homeassistant"); + path.concat("/switch/"); + path.concat(_nukiHubUidString); + path.concat("/reset/config"); -void NukiNetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction) -{ - String availabilityTopic = _preferences->getString(preference_mqtt_lock_path); - availabilityTopic.concat("/maintenance/mqttConnectionState"); - - _network->publishHASSConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, lockAction, unlockAction, openAction); - _network->publishHASSConfigAdditionalOpenerEntities(deviceType, baseTopic, name, uidString); - if(publishAuthData) - { - _network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString); - } - else - { - _network->removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); - _network->removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); - } - if(hasKeypad) - { - _network->publishHASSConfigKeypad(deviceType, baseTopic, name, uidString); - } - else - { - _network->removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString); - _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); - } -} - -void NukiNetworkOpener::removeHASSConfig(char* uidString) -{ - _network->removeHASSConfig(uidString); -} - -void NukiNetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction, - char *unlockAction, char *openAction) -{ - String availabilityTopic = _preferences->getString(preference_mqtt_lock_path); - availabilityTopic.concat("/maintenance/mqttConnectionState"); - _network->publishHASSConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, lockAction, unlockAction, openAction); - _network->publishHASSConfigAdditionalLockEntities(deviceType, baseTopic, name, uidString); - - if(hasDoorSensor) - { - _network->publishHASSConfigDoorSensor(deviceType, baseTopic, name, uidString); - } - else - { - _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"door_sensor", uidString); - } + _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); #ifndef CONFIG_IDF_TARGET_ESP32H2 - _network->publishHASSWifiRssiConfig(deviceType, baseTopic, name, uidString); + publishHassTopic("sensor", + "wifi_signal_strength", + _nukiHubUidString, + "_wifi_signal_strength", + "WIFI signal strength", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_wifi_rssi, + "NukiHub", + "signal_strength", + "measurement", + "diagnostic", + "", + { {(char*)"unit_of_meas", (char*)"dBm"} }); #endif + // MQTT Connected + publishHassTopic("binary_sensor", + "mqtt_connected", + _nukiHubUidString, + "_mqtt_connected", + "MQTT connected", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_mqtt_connection_state, + "NukiHub", + "", + "", + "diagnostic", + "", + { + {(char*)"pl_on", (char*)"online"}, + {(char*)"pl_off", (char*)"offline"}, + {(char*)"ic", (char*)"mdi:lan-connect"} + }); + + // Network device + publishHassTopic("sensor", + "network_device", + _nukiHubUidString, + "_network_device", + "Network device", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_network_device, + "NukiHub", + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" }}); + + // Nuki Hub Webserver enabled + publishHassTopic("switch", + "webserver", + _nukiHubUidString, + "_webserver", + "Nuki Hub webserver enabled", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_webserver_state, + "NukiHub", + "", + "", + "diagnostic", + String("~") + mqtt_topic_webserver_action, + { + { (char*)"pl_on", (char*)"1" }, + { (char*)"pl_off", (char*)"0" }, + { (char*)"stat_on", (char*)"1" }, + { (char*)"stat_off", (char*)"0" } + }); + + // Uptime + publishHassTopic("sensor", + "uptime", + _nukiHubUidString, + "_uptime", + "Uptime", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_uptime, + "NukiHub", + "duration", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + { (char*)"unit_of_meas", (char*)"min"} + }); + + if(_preferences->getBool(preference_mqtt_log_enabled, false)) + { + // MQTT Log + publishHassTopic("sensor", + "mqtt_log", + _nukiHubUidString, + "_mqtt_log", + "MQTT Log", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_log, + "NukiHub", + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" }}); + } + else + { + removeHassTopic((char*)"sensor", (char*)"mqtt_log", _nukiHubUidString); + } + + // Nuki Hub version + publishHassTopic("sensor", + "nuki_hub_version", + _nukiHubUidString, + "_nuki_hub_version", + "Nuki Hub version", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_info_nuki_hub_version, + "NukiHub", + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // Nuki Hub build + publishHassTopic("sensor", + "nuki_hub_build", + _nukiHubUidString, + "_nuki_hub_build", + "Nuki Hub build", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_info_nuki_hub_build, + "NukiHub", + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // Nuki Hub restart reason + publishHassTopic("sensor", + "nuki_hub_restart_reason", + _nukiHubUidString, + "_nuki_hub_restart_reason", + "Nuki Hub restart reason", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_restart_reason_fw, + "NukiHub", + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" }}); + + // Nuki Hub restart reason ESP + publishHassTopic("sensor", + "nuki_hub_restart_reason_esp", + _nukiHubUidString, + "_nuki_hub_restart_reason_esp", + "Nuki Hub restart reason ESP", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_restart_reason_esp, + "NukiHub", + "", + "", + "diagnostic", + "", + { { (char*)"en", (char*)"true" }}); + + if(_checkUpdates) + { + // NUKI Hub latest + publishHassTopic("sensor", + "nuki_hub_latest", + _nukiHubUidString, + "_nuki_hub_latest", + "NUKI Hub latest", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_info_nuki_hub_latest, + "NukiHub", + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // NUKI Hub update + char latest_version_topic[250]; + _baseTopic.toCharArray(latest_version_topic,_baseTopic.length() + 1); + strcat(latest_version_topic, mqtt_topic_info_nuki_hub_latest); + + if(!_updateFromMQTT) + { + publishHassTopic("update", + "nuki_hub_update", + _nukiHubUidString, + "_nuki_hub_update", + "NUKI Hub firmware update", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_info_nuki_hub_version, + "NukiHub", + "firmware", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + { (char*)"ent_pic", (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/master/icon/favicon-32x32.png" }, + { (char*)"rel_u", (char*)GITHUB_LATEST_RELEASE_URL }, + { (char*)"l_ver_t", (char*)latest_version_topic } + }); + } + else + { + publishHassTopic("update", + "nuki_hub_update", + _nukiHubUidString, + "_nuki_hub_update", + "NUKI Hub firmware update", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_info_nuki_hub_version, + "NukiHub", + "firmware", + "", + "diagnostic", + String("~") + mqtt_topic_update, + { + { (char*)"en", (char*)"true" }, + { (char*)"pl_inst", (char*)"1" }, + { (char*)"ent_pic", (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/master/icon/favicon-32x32.png" }, + { (char*)"rel_u", (char*)GITHUB_LATEST_RELEASE_URL }, + { (char*)"l_ver_t", (char*)latest_version_topic } + }); + } + } + else + { + removeHassTopic((char*)"sensor", (char*)"nuki_hub_latest", _nukiHubUidString); + removeHassTopic((char*)"update", (char*)"nuki_hub_update", _nukiHubUidString); + } + + // Nuki Hub IP Address + publishHassTopic("sensor", + "nuki_hub_ip", + _nukiHubUidString, + "_nuki_hub_ip", + "Nuki Hub IP", + _hostname.c_str(), + _baseTopic.c_str(), + String("~") + mqtt_topic_info_nuki_hub_ip, + "NukiHub", + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:ip"} + }); +} + +void HomeAssistantDiscovery::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction, char *unlockAction, char *openAction) +{ + String availabilityTopic = _baseTopic; + availabilityTopic.concat(mqtt_topic_mqtt_connection_state); + + publishHASSDeviceConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, lockAction, unlockAction, openAction); + + if(strcmp(deviceType, "SmartLock") == 0) + { + publishHASSConfigAdditionalLockEntities(deviceType, baseTopic, name, uidString); + } + else + { + publishHASSConfigAdditionalOpenerEntities(deviceType, baseTopic, name, uidString); + } + if(hasDoorSensor) + { + publishHASSConfigDoorSensor(deviceType, baseTopic, name, uidString); + } + else + { + removeHASSConfigTopic((char*)"binary_sensor", (char*)"door_sensor", uidString); + } if(publishAuthData) { - _network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString); + publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString); } else { - _network->removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); - _network->removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); + removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); + removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); } - if(hasKeypad) { - _network->publishHASSConfigKeypad(deviceType, baseTopic, name, uidString); + publishHASSConfigKeypad(deviceType, baseTopic, name, uidString); } else { - _network->removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString); - _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); + removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString); + removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString); } } -void NukiNetworkLock::removeHASSConfig(char *uidString) -{ - _network->removeHASSConfig(uidString); -} - -void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction) +void HomeAssistantDiscovery::publishHASSDeviceConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction) { JsonDocument json; json.clear(); @@ -145,6 +466,7 @@ void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, cha json["dev"]["name"] = name; json["dev"]["sw"] = softwareVersion; json["dev"]["hw"] = hardwareVersion; + json["dev"]["via_device"] = String("nuki_") + _nukiHubUidString; String cuUrl = _preferences->getString(preference_mqtt_hass_cu_url, ""); @@ -192,6 +514,45 @@ void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, cha _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); + + // Firmware version + publishHassTopic("sensor", + "firmware_version", + uidString, + "_firmware_version", + "Firmware version", + name, + baseTopic, + String("~") + mqtt_topic_info_firmware_version, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + + // Hardware version + publishHassTopic("sensor", + "hardware_version", + uidString, + "_hardware_version", + "Hardware version", + name, + baseTopic, + String("~") + mqtt_topic_info_hardware_version, + deviceType, + "", + "", + "diagnostic", + "", + { + { (char*)"en", (char*)"true" }, + {(char*)"ic", (char*)"mdi:counter"} + }); + // Battery critical publishHassTopic("binary_sensor", "battery_low", @@ -247,133 +608,12 @@ void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, cha "", { { (char*)"en", (char*)"true" } }); - // MQTT Connected - publishHassTopic("binary_sensor", - "mqtt_connected", - uidString, - "_mqtt_connected", - "MQTT connected", - name, - baseTopic, - _lockPath + mqtt_topic_mqtt_connection_state, - deviceType, - "", - "", - "diagnostic", - "", - { - {(char*)"pl_on", (char*)"online"}, - {(char*)"pl_off", (char*)"offline"}, - {(char*)"ic", (char*)"mdi:lan-connect"} - }); - - // Reset - publishHassTopic("switch", - "reset", - uidString, - "_reset", - "Restart Nuki Hub", - name, - baseTopic, - String("~") + mqtt_topic_reset, - deviceType, - "", - "", - "diagnostic", - String("~") + mqtt_topic_reset, - { - { (char*)"ic", (char*)"mdi:restart" }, - { (char*)"pl_on", (char*)"1" }, - { (char*)"pl_off", (char*)"0" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - - // Network device - publishHassTopic("sensor", - "network_device", - uidString, - "_network_device", - "Network device", - name, - baseTopic, - _lockPath + mqtt_topic_network_device, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" }}); - - // Nuki Hub Webserver enabled - publishHassTopic("switch", - "webserver", - uidString, - "_webserver", - "Nuki Hub webserver enabled", - name, - baseTopic, - _lockPath + mqtt_topic_webserver_state, - deviceType, - "", - "", - "diagnostic", - _lockPath + mqtt_topic_webserver_action, - { - { (char*)"pl_on", (char*)"1" }, - { (char*)"pl_off", (char*)"0" }, - { (char*)"stat_on", (char*)"1" }, - { (char*)"stat_off", (char*)"0" } - }); - - // Uptime - publishHassTopic("sensor", - "uptime", - uidString, - "_uptime", - "Uptime", - name, - baseTopic, - _lockPath + mqtt_topic_uptime, - deviceType, - "duration", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - { (char*)"unit_of_meas", (char*)"min"} - }); - - if(_preferences->getBool(preference_mqtt_log_enabled, false)) - { - // MQTT Log - publishHassTopic("sensor", - "mqtt_log", - uidString, - "_mqtt_log", - "MQTT Log", - name, - baseTopic, - _lockPath + mqtt_topic_log, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" }}); - } - else - { - removeHassTopic((char*)"sensor", (char*)"mqtt_log", uidString); - } - if(_offEnabled) { // Hybrid connected - String hybridPath = _lockPath; + String hybridPath = _baseTopic; hybridPath.concat("/lock"); - hybridPath.concat(mqtt_hybrid_state); + hybridPath.concat(mqtt_topic_hybrid_state); publishHassTopic("binary_sensor", "hybrid_connected", uidString, @@ -398,211 +638,6 @@ void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, cha removeHassTopic((char*)"binary_sensor", (char*)"hybrid_connected", uidString); } - // Firmware version - publishHassTopic("sensor", - "firmware_version", - uidString, - "_firmware_version", - "Firmware version", - name, - baseTopic, - String("~") + mqtt_topic_info_firmware_version, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // Hardware version - publishHassTopic("sensor", - "hardware_version", - uidString, - "_hardware_version", - "Hardware version", - name, - baseTopic, - String("~") + mqtt_topic_info_hardware_version, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // Nuki Hub version - publishHassTopic("sensor", - "nuki_hub_version", - uidString, - "_nuki_hub_version", - "Nuki Hub version", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_version, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // Nuki Hub build - publishHassTopic("sensor", - "nuki_hub_build", - uidString, - "_nuki_hub_build", - "Nuki Hub build", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_build, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // Nuki Hub restart reason - publishHassTopic("sensor", - "nuki_hub_restart_reason", - uidString, - "_nuki_hub_restart_reason", - "Nuki Hub restart reason", - name, - baseTopic, - _lockPath + mqtt_topic_restart_reason_fw, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" }}); - - // Nuki Hub restart reason ESP - publishHassTopic("sensor", - "nuki_hub_restart_reason_esp", - uidString, - "_nuki_hub_restart_reason_esp", - "Nuki Hub restart reason ESP", - name, - baseTopic, - _lockPath + mqtt_topic_restart_reason_esp, - deviceType, - "", - "", - "diagnostic", - "", - { { (char*)"en", (char*)"true" }}); - - if(_checkUpdates) - { - // NUKI Hub latest - publishHassTopic("sensor", - "nuki_hub_latest", - uidString, - "_nuki_hub_latest", - "NUKI Hub latest", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_latest, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:counter"} - }); - - // NUKI Hub update - char latest_version_topic[250]; - _lockPath.toCharArray(latest_version_topic,_lockPath.length() + 1); - strcat(latest_version_topic, mqtt_topic_info_nuki_hub_latest); - - if(!_updateFromMQTT) - { - publishHassTopic("update", - "nuki_hub_update", - uidString, - "_nuki_hub_update", - "NUKI Hub firmware update", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_version, - deviceType, - "firmware", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - { (char*)"ent_pic", (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/master/icon/favicon-32x32.png" }, - { (char*)"rel_u", (char*)GITHUB_LATEST_RELEASE_URL }, - { (char*)"l_ver_t", (char*)latest_version_topic } - }); - } - else - { - publishHassTopic("update", - "nuki_hub_update", - uidString, - "_nuki_hub_update", - "NUKI Hub firmware update", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_version, - deviceType, - "firmware", - "", - "diagnostic", - _lockPath + mqtt_topic_update, - { - { (char*)"en", (char*)"true" }, - { (char*)"pl_inst", (char*)"1" }, - { (char*)"ent_pic", (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/master/icon/favicon-32x32.png" }, - { (char*)"rel_u", (char*)GITHUB_LATEST_RELEASE_URL }, - { (char*)"l_ver_t", (char*)latest_version_topic } - }); - } - } - else - { - removeHassTopic((char*)"sensor", (char*)"nuki_hub_latest", uidString); - removeHassTopic((char*)"update", (char*)"nuki_hub_update", uidString); - } - - // Nuki Hub IP Address - publishHassTopic("sensor", - "nuki_hub_ip", - uidString, - "_nuki_hub_ip", - "Nuki Hub IP", - name, - baseTopic, - _lockPath + mqtt_topic_info_nuki_hub_ip, - deviceType, - "", - "", - "diagnostic", - "", - { - { (char*)"en", (char*)"true" }, - {(char*)"ic", (char*)"mdi:ip"} - }); - // Query Lock State publishHassTopic("button", "query_lockstate", @@ -676,7 +711,7 @@ void NukiNetwork::publishHASSConfig(char* deviceType, const char* baseTopic, cha { {(char*)"unit_of_meas", (char*)"dBm"} }); } -void NukiNetwork::publishHASSConfigAdditionalLockEntities(char *deviceType, const char *baseTopic, char *name, char *uidString) +void HomeAssistantDiscovery::publishHASSConfigAdditionalLockEntities(char *deviceType, const char *baseTopic, char *name, char *uidString) { uint32_t aclPrefs[17]; _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); @@ -1790,7 +1825,7 @@ void NukiNetwork::publishHASSConfigAdditionalLockEntities(char *deviceType, cons } } -void NukiNetwork::publishHASSConfigDoorSensor(char *deviceType, const char *baseTopic, char *name, char *uidString) +void HomeAssistantDiscovery::publishHASSConfigDoorSensor(char *deviceType, const char *baseTopic, char *name, char *uidString) { publishHassTopic("binary_sensor", "door_sensor", @@ -1812,7 +1847,7 @@ void NukiNetwork::publishHASSConfigDoorSensor(char *deviceType, const char *base }); } -void NukiNetwork::publishHASSConfigAdditionalOpenerEntities(char *deviceType, const char *baseTopic, char *name, char *uidString) +void HomeAssistantDiscovery::publishHASSConfigAdditionalOpenerEntities(char *deviceType, const char *baseTopic, char *name, char *uidString) { uint32_t aclPrefs[17]; _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); @@ -1942,9 +1977,9 @@ void NukiNetwork::publishHASSConfigAdditionalOpenerEntities(char *deviceType, co { { (char*)"en", (char*)"true" }, { (char*)"ic", (char*)"mdi:led-variant-on" }, - { (char*)"pl_on", (char*)"{ \"ledEnabled\": \"1\"}" }, - { (char*)"pl_off", (char*)"{ \"ledEnabled\": \"0\"}" }, - { (char*)"val_tpl", (char*)"{{value_json.ledEnabled}}" }, + { (char*)"pl_on", (char*)"{ \"ledFlashEnabled\": \"1\"}" }, + { (char*)"pl_off", (char*)"{ \"ledFlashEnabled\": \"0\"}" }, + { (char*)"val_tpl", (char*)"{{value_json.ledFlashEnabled}}" }, { (char*)"stat_on", (char*)"1" }, { (char*)"stat_off", (char*)"0" } }); @@ -2709,7 +2744,7 @@ void NukiNetwork::publishHASSConfigAdditionalOpenerEntities(char *deviceType, co } } -void NukiNetwork::publishHASSConfigAccessLog(char *deviceType, const char *baseTopic, char *name, char *uidString) +void HomeAssistantDiscovery::publishHASSConfigAccessLog(char *deviceType, const char *baseTopic, char *name, char *uidString) { publishHassTopic("sensor", "last_action_authorization", @@ -2731,7 +2766,6 @@ void NukiNetwork::publishHASSConfigAccessLog(char *deviceType, const char *baseT String rollingSate = "~"; rollingSate.concat(mqtt_topic_lock_log_rolling); - const char *rollingStateChr = rollingSate.c_str(); publishHassTopic("sensor", "rolling_log", @@ -2748,12 +2782,12 @@ void NukiNetwork::publishHASSConfigAccessLog(char *deviceType, const char *baseT "", { { (char*)"ic", (char*)"mdi:format-list-bulleted" }, - { (char*)"json_attr_t", (char*)rollingStateChr }, + { (char*)"json_attr_t", (char*)rollingSate.c_str() }, { (char*)"val_tpl", (char*)"{{value_json.index}}" } }); } -void NukiNetwork::publishHASSConfigKeypad(char *deviceType, const char *baseTopic, char *name, char *uidString) +void HomeAssistantDiscovery::publishHASSConfigKeypad(char *deviceType, const char *baseTopic, char *name, char *uidString) { // Keypad battery critical publishHassTopic("binary_sensor", @@ -2813,25 +2847,7 @@ void NukiNetwork::publishHASSConfigKeypad(char *deviceType, const char *baseTopi }); } -void NukiNetwork::publishHASSWifiRssiConfig(char *deviceType, const char *baseTopic, char *name, char *uidString) -{ - publishHassTopic("sensor", - "wifi_signal_strength", - uidString, - "_wifi_signal_strength", - "WIFI signal strength", - name, - baseTopic, - _lockPath + mqtt_topic_wifi_rssi, - deviceType, - "signal_strength", - "measurement", - "diagnostic", - "", - { {(char*)"unit_of_meas", (char*)"dBm"} }); -} - -void NukiNetwork::publishHassTopic(const String& mqttDeviceType, +void HomeAssistantDiscovery::publishHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString, const String& uidStringPostfix, @@ -2857,7 +2873,7 @@ void NukiNetwork::publishHassTopic(const String& mqttDeviceType, } } -String NukiNetwork::createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString) +String HomeAssistantDiscovery::createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString) { String path = _discoveryTopic; path.concat("/"); @@ -2871,7 +2887,7 @@ String NukiNetwork::createHassTopicPath(const String& mqttDeviceType, const Stri return path; } -void NukiNetwork::removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString) +void HomeAssistantDiscovery::removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString) { if (_discoveryTopic != "") { @@ -2880,7 +2896,7 @@ void NukiNetwork::removeHassTopic(const String& mqttDeviceType, const String& mq } } -void NukiNetwork::removeHASSConfig(char* uidString) +void HomeAssistantDiscovery::removeHASSConfig(char* uidString) { removeHassTopic((char*)"lock", (char*)"smartlock", uidString); removeHassTopic((char*)"binary_sensor", (char*)"battery_low", uidString); @@ -2976,33 +2992,31 @@ void NukiNetwork::removeHASSConfig(char* uidString) removeHassTopic((char*)"sensor", (char*)"nuki_hub_restart_reason_esp", uidString); } -void NukiNetwork::removeHASSConfigTopic(char *deviceType, char *name, char *uidString) +void HomeAssistantDiscovery::removeHASSConfigTopic(char *deviceType, char *name, char *uidString) { removeHassTopic(deviceType, name, uidString); } -JsonDocument NukiNetwork::createHassJson(const String& uidString, - const String& uidStringPostfix, - const String& displayName, - const String& name, - const String& baseTopic, - const String& stateTopic, - const String& deviceType, - const String& deviceClass, - const String& stateClass, - const String& entityCat, - const String& commandTopic, - std::vector> additionalEntries - ) +JsonDocument HomeAssistantDiscovery::createHassJson(const String& uidString, + const String& uidStringPostfix, + const String& displayName, + const String& name, + const String& baseTopic, + const String& stateTopic, + const String& deviceType, + const String& deviceClass, + const String& stateClass, + const String& entityCat, + const String& commandTopic, + std::vector> additionalEntries + ) { JsonDocument json; json.clear(); JsonObject dev = json["dev"].to(); JsonArray ids = dev["ids"].to(); ids.add(String("nuki_") + uidString); - json["dev"]["mf"] = "Nuki"; - json["dev"]["mdl"] = deviceType; - json["dev"]["name"] = name; + json["~"] = baseTopic; json["name"] = displayName; json["unique_id"] = String(uidString) + uidStringPostfix; @@ -3032,7 +3046,7 @@ JsonDocument NukiNetwork::createHassJson(const String& uidString, json["cmd_t"] = commandTopic; } - json["avty"]["t"] = _lockPath + mqtt_topic_mqtt_connection_state; + json["avty"]["t"] = _baseTopic + mqtt_topic_mqtt_connection_state; for(const auto& entry : additionalEntries) { diff --git a/src/HomeAssistantDiscovery.h b/src/HomeAssistantDiscovery.h index aeb72c9..967de52 100644 --- a/src/HomeAssistantDiscovery.h +++ b/src/HomeAssistantDiscovery.h @@ -1,16 +1,15 @@ - void setupHASS(int type=0); +#pragma once +#include +#include +#include "networkDevices/NetworkDevice.h" + +class HomeAssistantDiscovery +{ +public: + explicit HomeAssistantDiscovery(NetworkDevice* device, Preferences* preferences, char* buffer, size_t bufferSize); + void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad); void disableHASS(); - void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction); - void removeHASSConfig(char* uidString); - void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction); - void publishHASSConfigAdditionalLockEntities(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSConfigDoorSensor(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSConfigAdditionalOpenerEntities(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSConfigAccessLog(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSConfigKeypad(char* deviceType, const char* baseTopic, char* name, char* uidString); - void publishHASSWifiRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString); - void removeHASSConfig(char* uidString); - void removeHASSConfigTopic(char* deviceType, char* name, char* uidString); + void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); void publishHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString, @@ -25,19 +24,51 @@ const String& entityCat = "", const String& commandTopic = "", std::vector> additionalEntries = {} - ); - void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); + ); +private: + void publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction, char *unlockAction, char *openAction); + void publishHASSDeviceConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction); + void publishHASSNukiHubConfig(); + + void publishHASSConfigAdditionalLockEntities(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigDoorSensor(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigAdditionalOpenerEntities(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigAccessLog(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigKeypad(char* deviceType, const char* baseTopic, char* name, char* uidString); + void publishHASSConfigWifiRssi(char* deviceType, const char* baseTopic, char* name, char* uidString); + + + void removeHASSConfig(char* uidString); + void removeHASSConfigTopic(char* deviceType, char* name, char* uidString); + String createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); JsonDocument createHassJson(const String& uidString, - const String& uidStringPostfix, - const String& displayName, - const String& name, - const String& baseTopic, - const String& stateTopic, - const String& deviceType, - const String& deviceClass, - const String& stateClass = "", - const String& entityCat = "", - const String& commandTopic = "", - std::vector> additionalEntries = {} - ); \ No newline at end of file + const String& uidStringPostfix, + const String& displayName, + const String& name, + const String& baseTopic, + const String& stateTopic, + const String& deviceType, + const String& deviceClass, + const String& stateClass = "", + const String& entityCat = "", + const String& commandTopic = "", + std::vector> additionalEntries = {} + ); + + NetworkDevice* _device = nullptr; + Preferences* _preferences = nullptr; + + String _discoveryTopic; + String _baseTopic; + String _hostname; + + char _nukiHubUidString[20]; + + bool _offEnabled = false; + bool _checkUpdates = false; + bool _updateFromMQTT = false; + + char* _buffer; + const size_t _bufferSize; +}; \ No newline at end of file diff --git a/src/MqttReceiver.h b/src/MqttReceiver.h index 3db350d..e442919 100644 --- a/src/MqttReceiver.h +++ b/src/MqttReceiver.h @@ -1,7 +1,5 @@ #pragma once -#include - class MqttReceiver { public: diff --git a/src/MqttTopics.h b/src/MqttTopics.h index f842b13..1171ae0 100644 --- a/src/MqttTopics.h +++ b/src/MqttTopics.h @@ -110,7 +110,7 @@ #define mqtt_topic_restart_reason_esp "/maintenance/restartReasonNukiEsp" #define mqtt_topic_mqtt_connection_state "/maintenance/mqttConnectionState" #define mqtt_topic_network_device "/maintenance/networkDevice" -#define mqtt_hybrid_state "/hybridConnected" +#define mqtt_topic_hybrid_state "/hybridConnected" #define mqtt_topic_gpio_prefix "/gpio" #define mqtt_topic_gpio_pin "/pin_" diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 10999d9..730cf04 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -11,12 +11,7 @@ #endif #include "networkDevices/EthernetDevice.h" -#ifndef NUKI_HUB_UPDATER -#include -#endif - NukiNetwork* NukiNetwork::_inst = nullptr; -HomeAssistantDiscovery* HomeAssistantDiscovery::_hadiscovery = nullptr; extern bool wifiFallback; extern bool disableNetwork; @@ -42,9 +37,7 @@ NukiNetwork::NukiNetwork(Preferences *preferences) } _inst = this; - _hadiscovery = new HomeAssistantDiscovery(_inst, _preferences); _webEnabled = _preferences->getBool(preference_webserver_enabled, true); - _updateFromMQTT = _preferences->getBool(preference_update_from_mqtt, false); #ifndef NUKI_HUB_UPDATER memset(_maintenancePathPrefix, 0, sizeof(_maintenancePathPrefix)); @@ -148,7 +141,10 @@ void NukiNetwork::setupDevice() { onMqttDisconnect(reason); }); + + _hadiscovery = new HomeAssistantDiscovery(_device, _preferences, _buffer, _bufferSize); #endif + } void NukiNetwork::reconfigureDevice() @@ -246,7 +242,7 @@ void NukiNetwork::initialize() { _nukiHubPath[i] = mqttPath.charAt(i); } - + _hostname = _preferences->getString(preference_hostname, ""); if(_hostname == "") @@ -339,8 +335,6 @@ void NukiNetwork::initialize() } } - _discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery, ""); - _offEnabled = _preferences->getBool(preference_official_hybrid_enabled, false); readSettings(); } } @@ -640,7 +634,7 @@ bool NukiNetwork::reconnect() _device->mqttSetCredentials(_mqttUser, _mqttPass); } - _device->setWill(_mqttConnectionStateTopic, 1, true, _lastWillPayload); + _device->mqttSetWill(_mqttConnectionStateTopic, 1, true, _lastWillPayload); _device->mqttSetServer(_mqttBrokerAddr, _mqttPort); _device->mqttConnect(); @@ -683,7 +677,12 @@ bool NukiNetwork::reconnect() publishString(_maintenancePathPrefix, mqtt_topic_network_device, _device->deviceName().c_str(), true); for(const auto& it : _initTopics) { - _device->mqttPublish(it.first.c_str(), MQTT_QOS_LEVEL, true, it.second.c_str()); + publish(it.first.c_str(), it.second.c_str(), true); + } + + if(_preferences->getBool(preference_mqtt_hass_enabled, false)) + { + setupHASS(0, 0, {0}, {0}, {0}, false, false); } } @@ -804,7 +803,7 @@ void NukiNetwork::onMqttDataReceived(const espMqttClientTypes::MessageProperties if(_mqttConnectedTs == -1 || (millis() - _mqttConnectedTs < 2000)) return; parseGpioTopics(properties, topic, payload, len, index, total); - + onMqttDataReceived(topic, (byte*)payload, index); for(auto receiver : _mqttReceivers) @@ -816,7 +815,7 @@ void NukiNetwork::onMqttDataReceived(const espMqttClientTypes::MessageProperties void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) { char* data = (char*)payload; - + if(comparePrefixedPath(topic, mqtt_topic_reset) && strcmp(data, "1") == 0) { Log->println(F("Restart requested via MQTT.")); @@ -960,7 +959,7 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns clearWifiFallback(); delay(200); restartEsp(RestartReason::ReconfigureWebServer); - } + } } void NukiNetwork::parseGpioTopics(const espMqttClientTypes::MessageProperties &properties, const char *topic, const uint8_t *payload, size_t& len, size_t& index, size_t& total) @@ -1027,7 +1026,7 @@ void NukiNetwork::publishFloat(const char* prefix, const char* topic, const floa dtostrf(value, 0, precision, str); char path[200] = {0}; buildMqttPath(path, { prefix, topic }); - _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str); + publish(path, str, retain); } void NukiNetwork::publishInt(const char* prefix, const char *topic, const int value, bool retain) @@ -1036,7 +1035,7 @@ void NukiNetwork::publishInt(const char* prefix, const char *topic, const int va itoa(value, str, 10); char path[200] = {0}; buildMqttPath(path, { prefix, topic }); - _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str); + publish(path, str, retain); } void NukiNetwork::publishUInt(const char* prefix, const char *topic, const unsigned int value, bool retain) @@ -1045,7 +1044,7 @@ void NukiNetwork::publishUInt(const char* prefix, const char *topic, const unsig utoa(value, str, 10); char path[200] = {0}; buildMqttPath(path, { prefix, topic }); - _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str); + publish(path, str, retain); } void NukiNetwork::publishULong(const char* prefix, const char *topic, const unsigned long value, bool retain) @@ -1054,7 +1053,7 @@ void NukiNetwork::publishULong(const char* prefix, const char *topic, const unsi utoa(value, str, 10); char path[200] = {0}; buildMqttPath(path, { prefix, topic }); - _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str); + publish(path, str, retain); } void NukiNetwork::publishLongLong(const char* prefix, const char *topic, int64_t value, bool retain) @@ -1075,7 +1074,7 @@ void NukiNetwork::publishLongLong(const char* prefix, const char *topic, int64_t } char path[200] = {0}; buildMqttPath(path, { prefix, topic }); - _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, result); + publish(path, result, retain); } void NukiNetwork::publishBool(const char* prefix, const char *topic, const bool value, bool retain) @@ -1084,21 +1083,26 @@ void NukiNetwork::publishBool(const char* prefix, const char *topic, const bool str[0] = value ? '1' : '0'; char path[200] = {0}; buildMqttPath(path, { prefix, topic }); - _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str); + publish(path, str, retain); } void NukiNetwork::publishString(const char* prefix, const char *topic, const char *value, bool retain) { char path[200] = {0}; buildMqttPath(path, { prefix, topic }); - _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, value); + publish(path, value, retain); +} + +void NukiNetwork::publish(const char *topic, const char *value, bool retain) +{ + _device->mqttPublish(topic, MQTT_QOS_LEVEL, retain, value); } void NukiNetwork::removeTopic(const String& mqttPath, const String& mqttTopic) { String path = mqttPath; path.concat(mqttTopic); - _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, ""); + publish(path.c_str(), "", true); #ifdef DEBUG_NUKIHUB Log->print(F("Removing MQTT topic: ")); @@ -1106,9 +1110,9 @@ void NukiNetwork::removeTopic(const String& mqttPath, const String& mqttTopic) #endif } -void NukiNetwork::setupHASS(int type) +void NukiNetwork::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad) { - _hadiscovery->setupHASS(type); + _hadiscovery->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad); } void NukiNetwork::disableHASS() @@ -1354,7 +1358,7 @@ void NukiNetwork::addReconnectedCallback(std::function reconnectedCallba void NukiNetwork::disableMqtt() { - _device->disableMqtt(); + _device->mqttDisable(); _mqttEnabled = false; } diff --git a/src/NukiNetwork.h b/src/NukiNetwork.h index 52c4069..ee40c97 100644 --- a/src/NukiNetwork.h +++ b/src/NukiNetwork.h @@ -15,6 +15,7 @@ #include "Gpio.h" #include #include "NukiConstants.h" +#include "HomeAssistantDiscovery.h" #endif class NukiNetwork @@ -56,11 +57,31 @@ public: void publishLongLong(const char* prefix, const char* topic, int64_t value, bool retain); void publishBool(const char* prefix, const char* topic, const bool value, bool retain); void publishString(const char* prefix, const char* topic, const char* value, bool retain); + void publish(const char *topic, const char *value, bool retain); void removeTopic(const String& mqttPath, const String& mqttTopic); void batteryTypeToString(const Nuki::BatteryType battype, char* str); void advertisingModeToString(const Nuki::AdvertisingMode advmode, char* str); void timeZoneIdToString(const Nuki::TimeZoneId timeZoneId, char* str); + void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad); + void disableHASS(); + void publishHassTopic(const String& mqttDeviceType, + const String& mqttDeviceName, + const String& uidString, + const String& uidStringPostfix, + const String& displayName, + const String& name, + const String& baseTopic, + const String& stateTopic, + const String& deviceType, + const String& deviceClass, + const String& stateClass, + const String& entityCat, + const String& commandTopic, + std::vector> additionalEntries + ); + void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); + int mqttConnectionState(); // 0 = not connected; 1 = connected; 2 = connected and mqtt processed bool mqttRecentlyConnected(); bool pathEquals(const char* prefix, const char* path, const char* referencePath); @@ -87,8 +108,6 @@ private: NetworkDeviceType _networkDeviceType = (NetworkDeviceType)-1; bool _firstBootAfterDeviceChange = false; bool _webEnabled = true; - bool _updateFromMQTT = false; - bool _offEnabled = false; #ifndef NUKI_HUB_UPDATER static void onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); @@ -99,30 +118,16 @@ private: void parseGpioTopics(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total); void gpioActionCallback(const GpioAction& action, const int& pin); bool comparePrefixedPath(const char* fullPath, const char* subPath); - - String createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); - JsonDocument createHassJson(const String& uidString, - const String& uidStringPostfix, - const String& displayName, - const String& name, - const String& baseTopic, - const String& stateTopic, - const String& deviceType, - const String& deviceClass, - const String& stateClass = "", - const String& entityCat = "", - const String& commandTopic = "", - std::vector> additionalEntries = {} - ); - void buildMqttPath(char* outPath, std::initializer_list paths); void buildMqttPath(const char *path, char *outPath); + void buildMqttPath(char* outPath, std::initializer_list paths); const char* _lastWillPayload = "offline"; char _mqttConnectionStateTopic[211] = {0}; String _lockPath; - String _discoveryTopic; String _brokerAddr; + HomeAssistantDiscovery* _hadiscovery = nullptr; + Gpio* _gpio; int _mqttConnectionState = 0; diff --git a/src/NukiNetworkLock.cpp b/src/NukiNetworkLock.cpp index 6f50275..9169e3a 100644 --- a/src/NukiNetworkLock.cpp +++ b/src/NukiNetworkLock.cpp @@ -1598,6 +1598,11 @@ uint8_t NukiNetworkLock::queryCommands() return qc; } +void NukiNetworkLock::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad) +{ + _network->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad); +} + void NukiNetworkLock::buttonPressActionToString(const NukiLock::ButtonPressAction btnPressAction, char* str) { switch (btnPressAction) diff --git a/src/NukiNetworkLock.h b/src/NukiNetworkLock.h index d172784..231ce5f 100644 --- a/src/NukiNetworkLock.h +++ b/src/NukiNetworkLock.h @@ -56,6 +56,7 @@ public: void setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char* value)); void setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char* value)); void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override; + void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad); void publishFloat(const char* topic, const float value, bool retain, const uint8_t precision = 2); void publishInt(const char* topic, const int value, bool retain); diff --git a/src/NukiNetworkOpener.cpp b/src/NukiNetworkOpener.cpp index ca286dc..08f0e0a 100644 --- a/src/NukiNetworkOpener.cpp +++ b/src/NukiNetworkOpener.cpp @@ -1556,6 +1556,11 @@ uint8_t NukiNetworkOpener::queryCommands() return qc; } +void NukiNetworkOpener::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad) +{ + _network->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad); +} + void NukiNetworkOpener::buttonPressActionToString(const NukiOpener::ButtonPressAction btnPressAction, char* str) { switch (btnPressAction) diff --git a/src/NukiNetworkOpener.h b/src/NukiNetworkOpener.h index 6db0a38..9ccc180 100644 --- a/src/NukiNetworkOpener.h +++ b/src/NukiNetworkOpener.h @@ -47,6 +47,7 @@ public: void setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char* value)); void setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char* value)); void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override; + void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad); int mqttConnectionState(); bool reconnected(); //SETBACK diff --git a/src/NukiOfficial.cpp b/src/NukiOfficial.cpp index 64960b3..d1bd966 100644 --- a/src/NukiOfficial.cpp +++ b/src/NukiOfficial.cpp @@ -13,7 +13,6 @@ NukiOfficial::NukiOfficial(Preferences *preferences) _disableNonJSON = preferences->getBool(preference_disable_non_json, false); } - void NukiOfficial::setUid(const uint32_t& uid) { char uidString[20]; @@ -43,7 +42,6 @@ void NukiOfficial::setPublisher(NukiPublisher *publisher) _publisher = publisher; } - const char *NukiOfficial::getMqttPath() const { return mqttPath; @@ -99,14 +97,14 @@ void NukiOfficial::onOfficialUpdateReceived(const char *topic, const char *value Log->print(F("Connected: ")); Log->println((strcmp(value, "true") == 0 ? 1 : 0)); offConnected = (strcmp(value, "true") == 0 ? 1 : 0); - _publisher->publishBool(mqtt_hybrid_state, offConnected, true); + _publisher->publishBool(mqtt_topic_hybrid_state, offConnected, true); } else if(strcmp(topic, mqtt_topic_official_state) == 0) { offState = atoi(value); _statusUpdated = true; Log->println(F("Lock: Updating status on Hybrid state change")); - _publisher->publishBool(mqtt_hybrid_state, offConnected, true); + _publisher->publishBool(mqtt_topic_hybrid_state, offConnected, true); NukiLock::lockstateToString((NukiLock::LockState)offState, str); _publisher->publishString(mqtt_topic_lock_state, str, true); diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index ef548cc..2e70327 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -54,7 +54,7 @@ void NukiOpenerWrapper::initialize() _nukiOpener.setConnectTimeout(3); _nukiOpener.setDisconnectTimeout(5000); - _hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != ""; + _hassEnabled = _preferences->getBool(preference_mqtt_hass_enabled, false); readSettings(); } @@ -313,7 +313,7 @@ void NukiOpenerWrapper::update() } if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted) { - _network->setupHASS(2); + _network->setupHASS(2, _nukiConfig.nukiId, (char*)_nukiConfig.name, _firmwareVersion.c_str(), _hardwareVersion.c_str(), false, _hasKeypad); _hassSetupCompleted = true; } if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs)) diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index 36d03ce..c680edd 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -1,6 +1,3 @@ -#ifndef CONFIG_IDF_TARGET_ESP32H2 -#include "esp_wifi.h" -#endif #include "NukiWrapper.h" #include "PreferencesKeys.h" #include "MqttTopics.h" @@ -50,7 +47,7 @@ NukiWrapper::~NukiWrapper() } -void NukiWrapper::initialize(const bool& firstStart) +void NukiWrapper::initialize() { _nukiLock.initialize(); _nukiLock.registerBleScanner(_bleScanner); @@ -58,75 +55,7 @@ void NukiWrapper::initialize(const bool& firstStart) _nukiLock.setConnectTimeout(3); _nukiLock.setDisconnectTimeout(5000); - if(firstStart) - { - Log->println("First start, setting preference defaults"); - -#ifndef CONFIG_IDF_TARGET_ESP32H2 - wifi_config_t wifi_cfg; - if(esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) - { - Log->println("Failed to get Wi-Fi configuration in RAM"); - } - - if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) != ESP_OK) - { - Log->println("Failed to set storage Wi-Fi"); - } - - memset(wifi_cfg.sta.ssid, 0, sizeof(wifi_cfg.sta.ssid)); - memset(wifi_cfg.sta.password, 0, sizeof(wifi_cfg.sta.password)); - - if (esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) - { - Log->println("Failed to clear NVS Wi-Fi configuration"); - } -#endif - _preferences->putString(preference_mqtt_lock_path, "nukihub"); - - _preferences->putBool(preference_check_updates, true); - _preferences->putBool(preference_opener_continuous_mode, false); - _preferences->putBool(preference_official_hybrid_enabled, false); - _preferences->putBool(preference_official_hybrid_actions, false); - _preferences->putBool(preference_official_hybrid_retry, false); - _preferences->putBool(preference_disable_non_json, false); - _preferences->putBool(preference_update_from_mqtt, false); - _preferences->putBool(preference_ip_dhcp_enabled, true); - _preferences->putBool(preference_enable_bootloop_reset, false); - _preferences->putBool(preference_show_secrets, false); - - _preferences->putBool(preference_conf_info_enabled, true); - _preferences->putBool(preference_keypad_info_enabled, false); - _preferences->putBool(preference_keypad_topic_per_entry, false); - _preferences->putBool(preference_keypad_publish_code, false); - _preferences->putBool(preference_keypad_control_enabled, false); - _preferences->putBool(preference_timecontrol_info_enabled, false); - _preferences->putBool(preference_timecontrol_topic_per_entry, false); - _preferences->putBool(preference_timecontrol_control_enabled, false); - _preferences->putBool(preference_publish_authdata, false); - _preferences->putBool(preference_register_as_app, false); - _preferences->putBool(preference_register_opener_as_app, false); - - _preferences->putInt(preference_mqtt_broker_port, 1883); - _preferences->putInt(preference_buffer_size, CHAR_BUFFER_SIZE); - _preferences->putInt(preference_task_size_network, NETWORK_TASK_SIZE); - _preferences->putInt(preference_task_size_nuki, NUKI_TASK_SIZE); - _preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG); - _preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD); - _preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL); - _preferences->putInt(preference_query_interval_hybrid_lockstate, 600); - _preferences->putInt(preference_rssi_publish_interval, 60); - _preferences->putInt(preference_network_timeout, 60); - _preferences->putInt(preference_command_nr_of_retries, 3); - _preferences->putInt(preference_command_retry_delay, 100); - _preferences->putInt(preference_restart_ble_beacon_lost, 60); - _preferences->putInt(preference_query_interval_lockstate, 1800); - _preferences->putInt(preference_query_interval_configuration, 3600); - _preferences->putInt(preference_query_interval_battery, 1800); - _preferences->putInt(preference_query_interval_keypad, 1800); - } - - _hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != ""; + _hassEnabled = _preferences->getBool(preference_mqtt_hass_enabled, false); readSettings(); } @@ -405,7 +334,7 @@ void NukiWrapper::update() } if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted) { - _network->setupHASS(1); + _network->setupHASS(1, _nukiConfig.nukiId, (char*)_nukiConfig.name, _firmwareVersion.c_str(), _hardwareVersion.c_str(), hasDoorSensor(), _hasKeypad); _hassSetupCompleted = true; } if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs)) diff --git a/src/NukiWrapper.h b/src/NukiWrapper.h index 2212058..82484d5 100644 --- a/src/NukiWrapper.h +++ b/src/NukiWrapper.h @@ -17,7 +17,7 @@ public: NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences); virtual ~NukiWrapper(); - void initialize(const bool& firstStart); + void initialize(); void readSettings(); void update(); diff --git a/src/PreferencesKeys.h b/src/PreferencesKeys.h index bcf1598..7272158 100644 --- a/src/PreferencesKeys.h +++ b/src/PreferencesKeys.h @@ -2,6 +2,9 @@ #include #include "Config.h" +#ifndef CONFIG_IDF_TARGET_ESP32H2 +#include "esp_wifi.h" +#endif //CHANGE REQUIRES REBOOT TO TAKE EFFECT #define preference_ip_dhcp_enabled (char*)"dhcpena" @@ -129,17 +132,17 @@ #define preference_presence_detection_timeout (char*)"prdtimeout" #define preference_network_wifi_fallback_disabled (char*)"nwwififb" -inline bool initPreferences(Preferences* preferences) +inline void initPreferences(Preferences* preferences) { #ifdef NUKI_HUB_UPDATER - bool firstStart = false; - return firstStart; + return; #else bool firstStart = !preferences->getBool(preference_started_before); - #endif if(firstStart) { + Serial.println("First start, setting preference defaults"); + preferences->putBool(preference_started_before, true); preferences->putBool(preference_lock_enabled, true); uint32_t aclPrefs[17] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; @@ -152,6 +155,69 @@ inline bool initPreferences(Preferences* preferences) preferences->putBytes(preference_conf_lock_advanced_acl, (byte*)(&advancedLockConfigAclPrefs), sizeof(advancedLockConfigAclPrefs)); uint32_t advancedOpenerConfigAclPrefs[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; preferences->putBytes(preference_conf_opener_advanced_acl, (byte*)(&advancedOpenerConfigAclPrefs), sizeof(advancedOpenerConfigAclPrefs)); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + wifi_config_t wifi_cfg; + if(esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) + { + Serial.println("Failed to get Wi-Fi configuration in RAM"); + } + + if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) != ESP_OK) + { + Serial.println("Failed to set storage Wi-Fi"); + } + + memset(wifi_cfg.sta.ssid, 0, sizeof(wifi_cfg.sta.ssid)); + memset(wifi_cfg.sta.password, 0, sizeof(wifi_cfg.sta.password)); + + if (esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) + { + Serial.println("Failed to clear NVS Wi-Fi configuration"); + } +#endif + preferences->putString(preference_mqtt_lock_path, "nukihub"); + + preferences->putBool(preference_check_updates, true); + preferences->putBool(preference_opener_continuous_mode, false); + preferences->putBool(preference_official_hybrid_enabled, false); + preferences->putBool(preference_official_hybrid_actions, false); + preferences->putBool(preference_official_hybrid_retry, false); + preferences->putBool(preference_disable_non_json, false); + preferences->putBool(preference_update_from_mqtt, false); + preferences->putBool(preference_ip_dhcp_enabled, true); + preferences->putBool(preference_enable_bootloop_reset, false); + preferences->putBool(preference_show_secrets, false); + + preferences->putBool(preference_conf_info_enabled, true); + preferences->putBool(preference_keypad_info_enabled, false); + preferences->putBool(preference_keypad_topic_per_entry, false); + preferences->putBool(preference_keypad_publish_code, false); + preferences->putBool(preference_keypad_control_enabled, false); + preferences->putBool(preference_timecontrol_info_enabled, false); + preferences->putBool(preference_timecontrol_topic_per_entry, false); + preferences->putBool(preference_timecontrol_control_enabled, false); + preferences->putBool(preference_publish_authdata, false); + preferences->putBool(preference_register_as_app, false); + preferences->putBool(preference_register_opener_as_app, false); + + preferences->putInt(preference_mqtt_broker_port, 1883); + preferences->putInt(preference_buffer_size, CHAR_BUFFER_SIZE); + preferences->putInt(preference_task_size_network, NETWORK_TASK_SIZE); + preferences->putInt(preference_task_size_nuki, NUKI_TASK_SIZE); + preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG); + preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD); + preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL); + preferences->putInt(preference_query_interval_hybrid_lockstate, 600); + preferences->putInt(preference_rssi_publish_interval, 60); + preferences->putInt(preference_network_timeout, 60); + preferences->putInt(preference_command_nr_of_retries, 3); + preferences->putInt(preference_command_retry_delay, 100); + preferences->putInt(preference_restart_ble_beacon_lost, 60); + preferences->putInt(preference_query_interval_lockstate, 1800); + preferences->putInt(preference_query_interval_configuration, 3600); + preferences->putInt(preference_query_interval_battery, 1800); + preferences->putInt(preference_query_interval_keypad, 1800); } else { @@ -266,8 +332,7 @@ inline bool initPreferences(Preferences* preferences) preferences->putInt(preference_config_version, atof(NUKI_HUB_VERSION) * 100); } } - - return firstStart; + #endif } class DebugPreferences diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 80302b4..051a1cc 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -1762,8 +1762,6 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) configChanged = true; } } - - HADEVDISC else if(key == "HASSDISCOVERY") { if(_preferences->getString(preference_mqtt_hass_discovery, "") != value) @@ -3539,12 +3537,12 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request) printInputField(&response, "MQTTPASS", "MQTT Password", "*", 30, "", true, true); printInputField(&response, "MQTTPATH", "MQTT NukiHub Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, ""); printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), "chkHass"); - printCheckBox(&response, "HADEVDISC", "Use Home Assistant device based discovery", _preferences->getBool(preference_hass_device_discovery), ""); response.print("
    "); response.print("

    Advanced MQTT Configuration

    "); response.print(""); printInputField(&response, "HASSDISCOVERY", "Home Assistant discovery topic (usually \"homeassistant\")", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, "class=\"chkHass\""); + //printCheckBox(&response, "HADEVDISC", "Use Home Assistant device based discovery (2024.11+)", _preferences->getBool(preference_hass_device_discovery), ""); if(_preferences->getBool(preference_opener_enabled, false)) { printCheckBox(&response, "OPENERCONT", "Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode", _preferences->getBool(preference_opener_continuous_mode), ""); diff --git a/src/WebCfgServer.h b/src/WebCfgServer.h index 31e7ab7..db6dab0 100644 --- a/src/WebCfgServer.h +++ b/src/WebCfgServer.h @@ -2,7 +2,9 @@ #include #include +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE #include +#endif #include "esp_ota_ops.h" #include "Config.h" diff --git a/src/main.cpp b/src/main.cpp index 013d3a1..0a86511 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -446,7 +446,7 @@ void setup() preferences = new Preferences(); preferences->begin("nukihub", false); - bool firstStart = initPreferences(preferences); + initPreferences(preferences); bool doOta = false; uint8_t partitionType = checkPartition(); @@ -468,13 +468,6 @@ void setup() doOta = true; } -#ifndef NUKI_HUB_UPDATER - if(preferences->getBool(preference_enable_bootloop_reset, false)) - { - bootloopDetection(); - } -#endif - #ifdef NUKI_HUB_UPDATER Log->print(F("Nuki Hub OTA version ")); Log->println(NUKI_HUB_VERSION); @@ -511,6 +504,11 @@ void setup() }); } #else + if(preferences->getBool(preference_enable_bootloop_reset, false)) + { + bootloopDetection(); + } + Log->print(F("Nuki Hub version ")); Log->println(NUKI_HUB_VERSION); Log->print(F("Nuki Hub build ")); @@ -571,7 +569,7 @@ void setup() } nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences); - nuki->initialize(firstStart); + nuki->initialize(); } Log->println(openerEnabled ? F("Nuki Opener enabled") : F("Nuki Opener disabled")); diff --git a/src/networkDevices/EthernetDevice.cpp b/src/networkDevices/EthernetDevice.cpp index 4dec1a0..f4c5f93 100644 --- a/src/networkDevices/EthernetDevice.cpp +++ b/src/networkDevices/EthernetDevice.cpp @@ -2,6 +2,7 @@ #include "../PreferencesKeys.h" #include "../Logger.h" #include "../RestartReason.h" +#include "../EspMillis.h" extern bool ethCriticalFailure; extern bool wifiFallback; @@ -48,7 +49,9 @@ EthernetDevice::EthernetDevice(const String &hostname, _useSpi(true), _preferences(preferences) { +#ifndef NUKI_HUB_UPDATER NetworkDevice::init(); +#endif } const String EthernetDevice::deviceName() const diff --git a/src/networkDevices/EthernetDevice.h b/src/networkDevices/EthernetDevice.h index 6d5251d..fb0a737 100644 --- a/src/networkDevices/EthernetDevice.h +++ b/src/networkDevices/EthernetDevice.h @@ -11,9 +11,6 @@ #include #include #include "NetworkDevice.h" -#ifndef NUKI_HUB_UPDATER -#include "espMqttClient.h" -#endif class EthernetDevice : public NetworkDevice { @@ -65,16 +62,20 @@ private: void onNetworkEvent(arduino_event_id_t event, arduino_event_info_t info); bool _connected = false; - char* _path; bool _hardwareInitialized = false; + bool _useSpi = false; + + int64_t _checkIpTs = -1; const std::string _deviceName; uint8_t _phy_addr; + eth_phy_type_t _type; // LAN8720 int _power; int _mdc; int _mdio; + eth_clock_mode_t _clock_mode; // W55000 and DM9051 int _cs; @@ -83,10 +84,4 @@ private: int _spi_sck; int _spi_miso; int _spi_mosi; - - int64_t _checkIpTs = -1; - - eth_phy_type_t _type; - eth_clock_mode_t _clock_mode; - bool _useSpi = false; }; \ No newline at end of file diff --git a/src/networkDevices/LAN8720Definitions.h b/src/networkDevices/LAN8720Definitions.h index f45f5fc..6de25b1 100644 --- a/src/networkDevices/LAN8720Definitions.h +++ b/src/networkDevices/LAN8720Definitions.h @@ -2,19 +2,19 @@ #ifndef CONFIG_IDF_TARGET_ESP32 typedef enum { - ETH_CLOCK_GPIO0_IN = 0, - ETH_CLOCK_GPIO16_OUT = 2, - ETH_CLOCK_GPIO17_OUT = 3 - } eth_clock_mode_t; + ETH_CLOCK_GPIO0_IN = 0, + ETH_CLOCK_GPIO16_OUT = 2, + ETH_CLOCK_GPIO17_OUT = 3 +} eth_clock_mode_t; - #define ETH_PHY_TYPE_LAN8720 ETH_PHY_MAX +#define ETH_PHY_TYPE_LAN8720 ETH_PHY_MAX #else #define ETH_PHY_TYPE_LAN8720 ETH_PHY_LAN8720 #endif -#define ETH_CLK_MODE_LAN8720 ETH_CLOCK_GPIO0_IN -#define ETH_PHY_ADDR_LAN8720 0 -#define ETH_PHY_MDC_LAN8720 23 -#define ETH_PHY_MDIO_LAN8720 18 -#define ETH_PHY_POWER_LAN8720 -1 -#define ETH_RESET_PIN_LAN8720 1 \ No newline at end of file +#define ETH_CLK_MODE_LAN8720 ETH_CLOCK_GPIO0_IN +#define ETH_PHY_ADDR_LAN8720 0 +#define ETH_PHY_MDC_LAN8720 23 +#define ETH_PHY_MDIO_LAN8720 18 +#define ETH_PHY_POWER_LAN8720 -1 +#define ETH_RESET_PIN_LAN8720 1 \ No newline at end of file diff --git a/src/networkDevices/NetworkDevice.cpp b/src/networkDevices/NetworkDevice.cpp index 4ad1d84..091a12b 100644 --- a/src/networkDevices/NetworkDevice.cpp +++ b/src/networkDevices/NetworkDevice.cpp @@ -4,7 +4,7 @@ #ifndef NUKI_HUB_UPDATER #include "../MqttTopics.h" -#include "espMqttClient.h" +#include "PreferencesKeys.h" void NetworkDevice::init() { @@ -58,17 +58,38 @@ void NetworkDevice::update() void NetworkDevice::mqttSetClientId(const char *clientId) { - getMqttClient()->setClientId(clientId); + if (_useEncryption) + { + _mqttClientSecure->setClientId(clientId); + } + else + { + _mqttClient->setClientId(clientId); + } } void NetworkDevice::mqttSetCleanSession(bool cleanSession) { - getMqttClient()->setCleanSession(cleanSession); + if (_useEncryption) + { + _mqttClientSecure->setCleanSession(cleanSession); + } + else + { + _mqttClient->setCleanSession(cleanSession); + } } void NetworkDevice::mqttSetKeepAlive(uint16_t keepAlive) { - getMqttClient()->setKeepAlive(keepAlive); + if (_useEncryption) + { + _mqttClientSecure->setKeepAlive(keepAlive); + } + else + { + _mqttClient->setKeepAlive(keepAlive); + } } uint16_t NetworkDevice::mqttPublish(const char *topic, uint8_t qos, bool retain, const char *payload) @@ -88,7 +109,14 @@ bool NetworkDevice::mqttConnected() const void NetworkDevice::mqttSetServer(const char *host, uint16_t port) { - getMqttClient()->setServer(host, port); + if (_useEncryption) + { + _mqttClientSecure->setServer(host, port); + } + else + { + _mqttClient->setServer(host, port); + } } bool NetworkDevice::mqttConnect() @@ -101,29 +129,64 @@ bool NetworkDevice::mqttDisconnect(bool force) return getMqttClient()->disconnect(force); } -void NetworkDevice::setWill(const char *topic, uint8_t qos, bool retain, const char *payload) +void NetworkDevice::mqttSetWill(const char *topic, uint8_t qos, bool retain, const char *payload) { - getMqttClient()->setWill(topic, qos, retain, payload); + if (_useEncryption) + { + _mqttClientSecure->setWill(topic, qos, retain, payload); + } + else + { + _mqttClient->setWill(topic, qos, retain, payload); + } } void NetworkDevice::mqttSetCredentials(const char *username, const char *password) { - getMqttClient()->setCredentials(username, password); + if (_useEncryption) + { + _mqttClientSecure->setCredentials(username, password); + } + else + { + _mqttClient->setCredentials(username, password); + } } void NetworkDevice::mqttOnMessage(espMqttClientTypes::OnMessageCallback callback) { - getMqttClient()->onMessage(callback); + if (_useEncryption) + { + _mqttClientSecure->onMessage(callback); + } + else + { + _mqttClient->onMessage(callback); + } } void NetworkDevice::mqttOnConnect(espMqttClientTypes::OnConnectCallback callback) { - getMqttClient()->onConnect(callback); + if(_useEncryption) + { + _mqttClientSecure->onConnect(callback); + } + else + { + _mqttClient->onConnect(callback); + } } void NetworkDevice::mqttOnDisconnect(espMqttClientTypes::OnDisconnectCallback callback) { - getMqttClient()->onDisconnect(callback); + if (_useEncryption) + { + _mqttClientSecure->onDisconnect(callback); + } + else + { + _mqttClient->onDisconnect(callback); + } } uint16_t NetworkDevice::mqttSubscribe(const char *topic, uint8_t qos) @@ -131,7 +194,7 @@ uint16_t NetworkDevice::mqttSubscribe(const char *topic, uint8_t qos) return getMqttClient()->subscribe(topic, qos); } -void NetworkDevice::disableMqtt() +void NetworkDevice::mqttDisable() { getMqttClient()->disconnect(); _mqttEnabled = false; diff --git a/src/networkDevices/NetworkDevice.h b/src/networkDevices/NetworkDevice.h index 2705245..7e99eef 100644 --- a/src/networkDevices/NetworkDevice.h +++ b/src/networkDevices/NetworkDevice.h @@ -2,10 +2,8 @@ #ifndef NUKI_HUB_UPDATER #include "espMqttClient.h" -#include "MqttClientSetup.h" #endif #include "IPConfiguration.h" -#include "../EspMillis.h" class NetworkDevice { @@ -31,42 +29,45 @@ public: virtual String BSSIDstr() = 0; #ifndef NUKI_HUB_UPDATER + virtual bool mqttConnect(); + virtual bool mqttDisconnect(bool force); + virtual void mqttDisable(); + virtual bool mqttConnected() const; + + virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const char* payload); + virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length); + virtual uint16_t mqttSubscribe(const char* topic, uint8_t qos); + + virtual void mqttSetServer(const char* host, uint16_t port); virtual void mqttSetClientId(const char* clientId); virtual void mqttSetCleanSession(bool cleanSession); virtual void mqttSetKeepAlive(uint16_t keepAlive); - virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const char* payload); - virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length); - virtual bool mqttConnected() const; - virtual void mqttSetServer(const char* host, uint16_t port); - virtual bool mqttConnect(); - virtual bool mqttDisconnect(bool force); - virtual void setWill(const char* topic, uint8_t qos, bool retain, const char* payload); + virtual void mqttSetWill(const char* topic, uint8_t qos, bool retain, const char* payload); virtual void mqttSetCredentials(const char* username, const char* password); + virtual void mqttOnMessage(espMqttClientTypes::OnMessageCallback callback); virtual void mqttOnConnect(espMqttClientTypes::OnConnectCallback callback); virtual void mqttOnDisconnect(espMqttClientTypes::OnDisconnectCallback callback); - virtual void disableMqtt(); - - virtual uint16_t mqttSubscribe(const char* topic, uint8_t qos); #endif protected: + const IPConfiguration* _ipConfiguration = nullptr; + Preferences* _preferences = nullptr; #ifndef NUKI_HUB_UPDATER espMqttClient *_mqttClient = nullptr; espMqttClientSecure *_mqttClientSecure = nullptr; - bool _useEncryption = false; - bool _mqttEnabled = true; - void init(); MqttClient *getMqttClient() const; + bool _useEncryption = false; + bool _mqttEnabled = true; + char* _path; char _ca[TLS_CA_MAX_SIZE] = {0}; char _cert[TLS_CERT_MAX_SIZE] = {0}; char _key[TLS_KEY_MAX_SIZE] = {0}; #endif const String _hostname; - const IPConfiguration* _ipConfiguration = nullptr; }; \ No newline at end of file diff --git a/src/networkDevices/WifiDevice.cpp b/src/networkDevices/WifiDevice.cpp index 460e566..0109e7f 100644 --- a/src/networkDevices/WifiDevice.cpp +++ b/src/networkDevices/WifiDevice.cpp @@ -1,9 +1,10 @@ +#include "WifiDevice.h" #include "esp_wifi.h" #include -#include "WifiDevice.h" #include "../PreferencesKeys.h" #include "../Logger.h" #include "../RestartReason.h" +#include "../EspMillis.h" WifiDevice::WifiDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration) : NetworkDevice(hostname, preferences, ipConfiguration), diff --git a/src/networkDevices/WifiDevice.h b/src/networkDevices/WifiDevice.h index b08f6f6..ef1915b 100644 --- a/src/networkDevices/WifiDevice.h +++ b/src/networkDevices/WifiDevice.h @@ -1,7 +1,5 @@ #pragma once -#include -#include #include #include "NetworkDevice.h" #include "IPConfiguration.h" @@ -29,10 +27,10 @@ private: void onDisconnected(); void onConnected(); bool connect(); - char* _path; Preferences* _preferences = nullptr; + char* _path; int _foundNetworks = 0; int _disconnectCount = 0; bool _connectOnScanDone = false; @@ -45,4 +43,4 @@ private: uint8_t _connectedChannel = 0; uint8_t* _connectedBSSID; int64_t _disconnectTs = 0; -}; +}; \ No newline at end of file From b1380b5e0c447c10a791a152ffc1b894830c79c2 Mon Sep 17 00:00:00 2001 From: iranl Date: Sun, 10 Nov 2024 21:20:21 +0100 Subject: [PATCH 17/17] Fix #518 --- src/HomeAssistantDiscovery.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/HomeAssistantDiscovery.cpp b/src/HomeAssistantDiscovery.cpp index ccfb3ac..70fc1f5 100644 --- a/src/HomeAssistantDiscovery.cpp +++ b/src/HomeAssistantDiscovery.cpp @@ -1954,6 +1954,7 @@ void HomeAssistantDiscovery::publishHASSConfigAdditionalOpenerEntities(char *dev json = createHassJson(uidString, "_ring_event", "Ring", name, baseTopic, String("~") + mqtt_topic_lock_ring, deviceType, "doorbell", "", "", "", {{(char*)"val_tpl", (char*)"{ \"event_type\": \"{{ value }}\" }"}}); json["event_types"][0] = "ring"; json["event_types"][1] = "ringlocked"; + json["event_types"][2] = "standby"; serializeJson(json, _buffer, _bufferSize); String path = createHassTopicPath("event", "ring", uidString); _device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer); @@ -3065,4 +3066,4 @@ JsonDocument HomeAssistantDiscovery::createHassJson(const String& uidString, } return json; -} \ No newline at end of file +}