diff --git a/README.md b/README.md index 9d8da160..66798f54 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,7 @@ In a browser navigate to the IP address assigned to the ESP32. - Lock: Nuki Bridge is running alongside Nuki Hub: Enable to allow Nuki Hub to co-exist with a Nuki Bridge by registering Nuki Hub as an (smartphone) app instead of a bridge. Changing this setting will require re-pairing. Enabling this setting is strongly discouraged as described in the "[Pairing with a Nuki Lock or Opener](#pairing-with-a-nuki-lock-or-opener)" section of this README, ***unless when used in [Hybrid mode](/HYBRID.md) (Official MQTT / Nuki Hub co-existance)*** - Opener: Nuki Bridge is running alongside Nuki Hub: Enable to allow Nuki Hub to co-exist with a Nuki Bridge by registering Nuki Hub as an (smartphone) app instead of a bridge. Changing this setting will require re-pairing. Enabling this setting is strongly discouraged as described in the "[Pairing with a Nuki Lock or Opener](#pairing-with-a-nuki-lock-or-opener)" section of this README - Restart if bluetooth beacons not received: Set to a positive integer to restart the Nuki Hub after the set amount of seconds has passed without receiving a bluetooth beacon from the Nuki device, set to -1 to disable, default 60. Because the bluetooth stack of the ESP32 can silently fail it is not recommended to disable this setting. -- BLE transmit power in dB: Set to a integer between -12 and 9 to set the Bluetooth transmit power, default 9. +- BLE transmit power in dB: Set to a integer between -12 and 9 (ESP32) or -12 and 20 (All newer ESP32 variants) to set the Bluetooth transmit power, default 9. - Update Nuki Hub and Lock/Opener time using NTP: Enable to update the ESP32 time and Nuki Lock and/or Nuki Opener time every 12 hours using a NTP time server. Updating the Nuki device time requires the Nuki security code / PIN to be set, see "[Nuki Lock PIN / Nuki Opener PIN](#nuki-lock-pin--nuki-opener-pin)" below. - NTP server: Set to the NTP server you want to use, defaults to "pool.ntp.org". If DHCP is used and NTP servers are provided using DHCP these will take precedence over the specified NTP server. diff --git a/src/Config.h b/src/Config.h index e5eebdfd..f8688282 100644 --- a/src/Config.h +++ b/src/Config.h @@ -5,7 +5,7 @@ #define NUKI_HUB_VERSION "9.09" #define NUKI_HUB_VERSION_INT (uint32_t)909 #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2025-01-23" +#define NUKI_HUB_DATE "2025-01-28" #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/HomeAssistantDiscovery.cpp b/src/HomeAssistantDiscovery.cpp index fd2f536a..2c704e8e 100644 --- a/src/HomeAssistantDiscovery.cpp +++ b/src/HomeAssistantDiscovery.cpp @@ -127,6 +127,7 @@ void HomeAssistantDiscovery::publishHASSNukiHubConfig() json["dev"]["name"] = _hostname.c_str(); json["dev"]["sw"] = NUKI_HUB_VERSION; json["dev"]["hw"] = NUKI_HUB_HW; + _uidToName[_nukiHubUidString] = _hostname.c_str(); String cuUrl = _preferences->getString(preference_mqtt_hass_cu_url, ""); @@ -502,6 +503,7 @@ void HomeAssistantDiscovery::publishHASSDeviceConfig(char* deviceType, const cha json["dev"]["sw"] = softwareVersion; json["dev"]["hw"] = hardwareVersion; json["dev"]["via_device"] = String("nuki_") + _nukiHubUidString; + _uidToName[uidString] = name; String cuUrl = _preferences->getString(preference_mqtt_hass_cu_url, ""); @@ -3175,6 +3177,7 @@ JsonDocument HomeAssistantDiscovery::createHassJson(const String& uidString, } json["avty"][0]["t"] = _baseTopic + mqtt_topic_mqtt_connection_state; + json["dev"]["name"] = _uidToName[uidString]; if (displayName != "Query lock state" && uidString.length() < 10) { diff --git a/src/HomeAssistantDiscovery.h b/src/HomeAssistantDiscovery.h index 967de522..176990ba 100644 --- a/src/HomeAssistantDiscovery.h +++ b/src/HomeAssistantDiscovery.h @@ -63,6 +63,7 @@ class HomeAssistantDiscovery String _baseTopic; String _hostname; + JsonDocument _uidToName; char _nukiHubUidString[20]; bool _offEnabled = false; diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index 28552f6b..0538b5bd 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -76,7 +76,30 @@ void NukiOpenerWrapper::readSettings() if(pwrLvl >= 9) { + #if defined(CONFIG_IDF_TARGET_ESP32) powerLevel = ESP_PWR_LVL_P9; + #else + if(pwrLvl >= 20) + { + powerLevel = ESP_PWR_LVL_P20; + } + else if(pwrLvl >= 18) + { + powerLevel = ESP_PWR_LVL_P18; + } + else if(pwrLvl >= 15) + { + powerLevel = ESP_PWR_LVL_P15; + } + else if(pwrLvl >= 12) + { + powerLevel = ESP_PWR_LVL_P12; + } + else + { + powerLevel = ESP_PWR_LVL_P9; + } + #endif } else if(pwrLvl >= 6) { diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index e6b8a6a5..b056e86f 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -82,7 +82,30 @@ void NukiWrapper::readSettings() if(pwrLvl >= 9) { + #if defined(CONFIG_IDF_TARGET_ESP32) powerLevel = ESP_PWR_LVL_P9; + #else + if(pwrLvl >= 20) + { + powerLevel = ESP_PWR_LVL_P20; + } + else if(pwrLvl >= 18) + { + powerLevel = ESP_PWR_LVL_P18; + } + else if(pwrLvl >= 15) + { + powerLevel = ESP_PWR_LVL_P15; + } + else if(pwrLvl >= 12) + { + powerLevel = ESP_PWR_LVL_P12; + } + else + { + powerLevel = ESP_PWR_LVL_P9; + } + #endif } else if(pwrLvl >= 6) { diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index 96a6375a..d5a6e6f4 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -3363,7 +3363,11 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S } else if(key == "TXPWR") { + #if defined(CONFIG_IDF_TARGET_ESP32) if(value.toInt() >= -12 && value.toInt() <= 9) + #else + if(value.toInt() >= -12 && value.toInt() <= 20) + #endif { if(_preferences->getInt(preference_ble_tx_power, 9) != value.toInt()) { @@ -5030,6 +5034,7 @@ esp_err_t WebCfgServer::buildHtml(PsychicRequest *request, PsychicResponse* resp buildNavigationMenuEntry(&response, "Configure Wi-Fi", "/get?page=wifi"); } #endif + buildNavigationMenuEntry(&response, "Info page", "/get?page=info"); String rebooturl = "/get?page=reboot&CONFIRMTOKEN=" + _confirmCode; buildNavigationMenuEntry(&response, "Reboot Nuki Hub", rebooturl.c_str()); if (_preferences->getInt(preference_http_auth_type, 0) == 2) @@ -5847,7 +5852,11 @@ esp_err_t WebCfgServer::buildNukiConfigHtml(PsychicRequest *request, PsychicResp printCheckBox(&response, "REGAPPOPN", "Opener: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_opener_as_app), ""); } printInputField(&response, "RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10, ""); + #if defined(CONFIG_IDF_TARGET_ESP32) printInputField(&response, "TXPWR", "BLE transmit power in dB (minimum -12, maximum 9)", _preferences->getInt(preference_ble_tx_power, 9), 10, ""); + #else + printInputField(&response, "TXPWR", "BLE transmit power in dB (minimum -12, maximum 20)", _preferences->getInt(preference_ble_tx_power, 9), 10, ""); + #endif printCheckBox(&response, "UPTIME", "Update Nuki Hub and Lock/Opener time using NTP", _preferences->getBool(preference_update_time, false), ""); printInputField(&response, "TIMESRV", "NTP server", _preferences->getString(preference_time_server, "pool.ntp.org").c_str(), 255, ""); diff --git a/src/WebCfgServerConstants.h b/src/WebCfgServerConstants.h index 043500a1..1bb2264c 100644 --- a/src/WebCfgServerConstants.h +++ b/src/WebCfgServerConstants.h @@ -3,7 +3,7 @@ // escaped by https://www.cescaper.com/ // source: https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css -const char stylecss[] = ":root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000;--nc-tx-2:#1a1a1a;--nc-bg-1:#fff;--nc-bg-2:#f6f8fa;--nc-bg-3:#e5e7eb;--nc-lk-1:#0070f3;--nc-lk-2:#0366d6;--nc-lk-tx:#fff;--nc-ac-1:#79ffe1;--nc-ac-tx:#0c4047}@media(prefers-color-scheme:dark){:root{--nc-tx-1:#fff;--nc-tx-2:#eee;--nc-bg-1:#000;--nc-bg-2:#111;--nc-bg-3:#222;--nc-lk-1:#3291ff;--nc-lk-2:#0070f3;--nc-lk-tx:#fff;--nc-ac-1:#7928ca;--nc-ac-tx:#fff}}*{margin:0;padding:0}img,input,option,p,table,textarea,ul{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:normal;overflow-wrap:anywhere;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2) !important;}abbr{cursor:help}abbr:hover{cursor:help}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background:var(--nc-lk-2)}table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}th{background:var(--nc-bg-2)}tr:nth-child(even){background:var(--nc-bg-2)}textarea{max-width:100%}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;background:var(--nc-bg-2);color:var(--nc-tx-2);border:1px solid var(--nc-bg-3);border-radius:4px;box-shadow:none;box-sizing:border-box}img{max-width:100%}td>input{margin-top:0;margin-bottom:0}td>textarea{margin-top:0;margin-bottom:0}td>select{margin-top:0;margin-bottom:0}.warning{color:red}@media only screen and (max-width:600px){.adapt td{display:block}.adapt input[type=text],.adapt input[type=password],.adapt input[type=submit],.adapt textarea,.adapt select{width:100%}.adapt td:has(input[type=checkbox]){text-align:center}.adapt input[type=checkbox]{width:1.5em;height:1.5em}.adapt table td:first-child{border-bottom:0}.adapt table td:last-child{border-top:0}#tblnav a li>span{max-width:140px}}#tblnav a{border:0;border-bottom:1px solid;display:block;font-size:1rem;font-weight:bold;padding:.6rem 0;line-height:1;color:var(--nc-tx-1);text-decoration:none;background:linear-gradient(to left,transparent 50%,rgba(255,255,255,0.4) 50%) right;background-size:200% 100%;transition:all .2s ease}#tblnav a:nth-child(even){background:linear-gradient(to left,var(--nc-bg-2) 50%,rgba(255,255,255,0.4) 50%) right;background-size:200% 100%}#tblnav a:hover{background-position:left;transition:all .45s ease}#tblnav a:active{background:var(--nc-lk-1);transition:all .15s ease}#tblnav a li{list-style:none;padding:.5rem;display:inline-block;width:100%}#tblnav a li>span{float:right;text-align:right;margin-right:10px;color:#f70;font-weight:100;font-style:italic;display:block}.tdbtn{text-align:center;vertical-align:middle}.naventry{float:left;max-width:375px;width:100%}"; +const char stylecss[] = ":root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000;--nc-tx-2:#1a1a1a;--nc-bg-1:#fff;--nc-bg-2:#f6f8fa;--nc-bg-3:#e5e7eb;--nc-lk-1:#0070f3;--nc-lk-2:#0366d6;--nc-lk-tx:#fff;--nc-ac-1:#79ffe1;--nc-ac-tx:#0c4047}@media(prefers-color-scheme:dark){:root{--nc-tx-1:#fff;--nc-tx-2:#eee;--nc-bg-1:#000;--nc-bg-2:#111;--nc-bg-3:#222;--nc-lk-1:#3291ff;--nc-lk-2:#0070f3;--nc-lk-tx:#fff;--nc-ac-1:#7928ca;--nc-ac-tx:#fff}}*{margin:0;padding:0}img,input,option,p,table,textarea,ul{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:normal;overflow-wrap:anywhere;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2) !important;}abbr{cursor:help}abbr:hover{cursor:help}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background:var(--nc-lk-2)}table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}th{background:var(--nc-bg-2)}tr{background:var(--nc-bg-2)}textarea{max-width:100%}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;background:var(--nc-bg-2);color:var(--nc-tx-2);border:1px solid var(--nc-bg-3);border-radius:4px;box-shadow:none;box-sizing:border-box}img{max-width:100%}td>input{margin-top:0;margin-bottom:0}td>textarea{margin-top:0;margin-bottom:0}td>select{margin-top:0;margin-bottom:0}.warning{color:red}@media only screen and (max-width:600px){.adapt td{display:block}.adapt input[type=text],.adapt input[type=password],.adapt input[type=submit],.adapt textarea,.adapt select{width:100%}.adapt td:has(input[type=checkbox]){text-align:center}.adapt input[type=checkbox]{width:1.5em;height:1.5em}.adapt table td:first-child{border-bottom:0}.adapt table td:last-child{border-top:0}#tblnav a li>span{max-width:140px}}#tblnav a{border:0;border-bottom:1px solid;display:block;font-size:1rem;font-weight:bold;padding:.6rem 0;line-height:1;color:var(--nc-tx-1);text-decoration:none;background:linear-gradient(to left,transparent 50%,rgba(255,255,255,0.4) 50%) right;background-size:200% 100%;transition:all .2s ease}#tblnav a{background:linear-gradient(to left,var(--nc-bg-2) 50%,rgba(255,255,255,0.4) 50%) right;background-size:200% 100%}#tblnav a:hover{background-position:left;transition:all .45s ease}#tblnav a:active{background:var(--nc-lk-1);transition:all .15s ease}#tblnav a li{list-style:none;padding:.5rem;display:inline-block;width:100%}#tblnav a li>span{float:right;text-align:right;margin-right:10px;color:#f70;font-weight:100;font-style:italic;display:block}.tdbtn{text-align:center;vertical-align:middle}.naventry{float:left;max-width:375px;width:100%}"; // converted to char array by https://notisrac.github.io/FileToCArray/ const uint8_t favicon_32x32[] = {