WiFi portal

This commit is contained in:
iranl
2024-10-09 21:33:18 +02:00
parent 220ea7a96d
commit 06e96b5ea7
18 changed files with 999 additions and 497 deletions

View File

@@ -84,7 +84,8 @@ Unpack the zip archive and read the included how-to-flash.txt for installation i
## Initial setup (Network and MQTT)
Power up the ESP32 and a new Wi-Fi access point named "ESP32_(8 CHARACTER ALPHANUMERIC)" should appear.<br>
Power up the ESP32 and a new Wi-Fi access point named "NukiHub" should appear.<br>
The password of the access point is "NukiHubESP32".<br>
Connect a client device to this access point and in a browser navigate to "http://192.168.4.1".<br>
Use the web interface to connect the ESP to your preferred Wi-Fi network.<br>
<br>
@@ -138,7 +139,7 @@ PSRAM is usually 2, 4 or 8MB in size and thus greatly enlarges the 320kb of inte
It is basically impossible to run out of RAM when PSRAM is available.
You can check on the info page of the Web configurator if PSRAM is available.
Note that there are two build of Nuki Hub for the ESP32-S3 available.<br>
Note that there are two builds of Nuki Hub for the ESP32-S3 available.<br>
One for devices with no or Quad SPI PSRAM and one for devices with Octal SPI PSRAM.<br>
If your ESP32-S3 device has PSRAM but it is not detected please flash the other S3 binary.
@@ -165,7 +166,6 @@ In a browser navigate to the IP address assigned to the ESP32.
- 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.
- MQTT SSL Client Key: Optionally set to the Client SSL key of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional)" section of this README.
- Network hardware: "Wi-Fi only" by default, set to one of the specified ethernet modules if available, see the "Supported Ethernet devices" and "[Connecting via Ethernet](#connecting-via-ethernet-optional)" section of this README.
- Disable fallback to Wi-Fi / Wi-Fi config portal: By default the Nuki Hub will fallback to Wi-Fi and open the Wi-Fi configuration portal when the network connection fails. Enable this setting to disable this fallback.
- Connect to AP with the best signal in an environment with multiple APs with the same SSID: Enable to perform a scan for the Access Point with the best signal strenght for the specified SSID in a multi AP/Mesh environment.
- RSSI Publish interval: Set to a positive integer to set the amount of seconds between updates to the maintenance/wifiRssi MQTT topic with the current Wi-Fi RSSI, set to -1 to disable, default 60.
- MQTT Timeout until restart: Set to a positive integer to restart the Nuki Hub after the set amount of seconds has passed without an active connection to the MQTT broker, set to -1 to disable, default 60.

View File

@@ -27,6 +27,7 @@ board_build.partitions = partitions.csv
build_unflags =
-DCONFIG_BT_NIMBLE_LOG_LEVEL
-DCONFIG_BTDM_BLE_SCAN_DUPL
-DESP32
-Werror=all
-Wall
build_flags =

View File

@@ -4,7 +4,7 @@
#define NUKI_HUB_VERSION "9.01"
#define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2024-09-06"
#define NUKI_HUB_DATE "2024-10-14"
#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"

View File

@@ -69,7 +69,7 @@ void NukiNetwork::setupDevice()
{
_ipConfiguration = new IPConfiguration(_preferences);
int hardwareDetect = _preferences->getInt(preference_network_hardware, 0);
Log->print(F("Hardware detect : "));
Log->print(F("Hardware detect: "));
Log->println(hardwareDetect);
_firstBootAfterDeviceChange = _preferences->getBool(preference_ntw_reconfigure, false);
@@ -95,7 +95,7 @@ void NukiNetwork::setupDevice()
if(strcmp(WiFi_fallbackDetect, "wifi_fallback") == 0)
{
#ifndef CONFIG_IDF_TARGET_ESP32H2
if(_preferences->getBool(preference_network_wifi_fallback_disabled) && !_firstBootAfterDeviceChange)
if(!_firstBootAfterDeviceChange)
{
Log->println(F("Failed to connect to network. Wi-Fi fallback is disabled, rebooting."));
memset(WiFi_fallbackDetect, 0, sizeof(WiFi_fallbackDetect));
@@ -122,7 +122,7 @@ void NukiNetwork::setupDevice()
_device = NetworkDeviceInstantiator::Create(_networkDeviceType, _hostname, _preferences, _ipConfiguration);
Log->print(F("Network device: "));
Log->print(_device->deviceName());
Log->println(_device->deviceName());
}
void NukiNetwork::reconfigureDevice()
@@ -130,6 +130,16 @@ void NukiNetwork::reconfigureDevice()
_device->reconfigure();
}
void NukiNetwork::scan(bool passive, bool async)
{
_device->scan(passive, async);
}
bool NukiNetwork::isApOpen()
{
return _device->isApOpen();
}
const String NukiNetwork::networkDeviceName() const
{
return _device->deviceName();
@@ -317,7 +327,6 @@ void NukiNetwork::readSettings()
{
_restartOnDisconnect = _preferences->getBool(preference_restart_on_disconnect, false);
_checkUpdates = _preferences->getBool(preference_check_updates, false);
_reconnectNetworkOnMqttDisconnect = _preferences->getBool(preference_recon_netw_on_mqtt_discon, false);
_rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval, 0) * 1000;
if(_rssiPublishInterval == 0)
@@ -341,38 +350,17 @@ bool NukiNetwork::update()
int64_t ts = (esp_timer_get_time() / 1000);
_device->update();
if(!_mqttEnabled)
if(!_mqttEnabled || _device->isApOpen())
{
return true;
}
if(!_device->isConnected() || (_mqttConnectCounter > 15 && _reconnectNetworkOnMqttDisconnect && !_firstConnect))
if(!_device->isConnected() || (_mqttConnectCounter > 15 && !_firstConnect))
{
_mqttConnectCounter = 0;
if(!_webEnabled) forceEnableWebServer = true;
if(_restartOnDisconnect && (esp_timer_get_time() / 1000) > 60000) restartEsp(RestartReason::RestartOnDisconnectWatchdog);
Log->println(F("Network not connected. Trying reconnect."));
ReconnectStatus reconnectStatus = _device->reconnect(true);
switch(reconnectStatus)
{
case ReconnectStatus::CriticalFailure:
strcpy(WiFi_fallbackDetect, "wifi_fallback");
Log->println("Network device has a critical failure, enable fallback to Wi-Fi and reboot.");
delay(200);
restartEsp(RestartReason::NetworkDeviceCriticalFailure);
break;
case ReconnectStatus::Success:
memset(WiFi_fallbackDetect, 0, sizeof(WiFi_fallbackDetect));
Log->print(F("Reconnect successful: IP: "));
Log->println(_device->localIP());
break;
case ReconnectStatus::Failure:
Log->println(F("Reconnect failed"));
break;
}
}
if(_device->isConnected() && !_mqttClientInitiated && strcmp(_mqttBrokerAddr, "") != 0)

View File

@@ -24,6 +24,8 @@ public:
void readSettings();
bool update();
void reconfigureDevice();
void scan(bool passive = false, bool async = true);
bool isApOpen();
void clearWifiFallback();
const String networkDeviceName() const;
@@ -168,7 +170,6 @@ private:
std::vector<MqttReceiver*> _mqttReceivers;
bool _restartOnDisconnect = false;
bool _checkUpdates = false;
bool _reconnectNetworkOnMqttDisconnect = false;
bool _firstConnect = true;
bool _publishDebugInfo = false;
bool _logIp = true;

View File

@@ -1772,21 +1772,21 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
String allowedFromTime;
String allowedUntilTime;
if(json.containsKey("code")) code = json["code"].as<unsigned int>();
if(json["code"].is<unsigned int>()) code = json["code"].as<unsigned int>();
else code = 12;
if(json.containsKey("enabled")) enabled = json["enabled"].as<unsigned int>();
if(json["enabled"].is<unsigned int>()) enabled = json["enabled"].as<unsigned int>();
else enabled = 2;
if(json.containsKey("timeLimited")) timeLimited = json["timeLimited"].as<unsigned int>();
if(json["timeLimited"].is<unsigned int>()) timeLimited = json["timeLimited"].as<unsigned int>();
else timeLimited = 2;
if(json.containsKey("name")) name = json["name"].as<String>();
if(json.containsKey("allowedFrom")) allowedFrom = json["allowedFrom"].as<String>();
if(json.containsKey("allowedUntil")) allowedUntil = json["allowedUntil"].as<String>();
if(json.containsKey("allowedWeekdays")) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json.containsKey("allowedFromTime")) allowedFromTime = json["allowedFromTime"].as<String>();
if(json.containsKey("allowedUntilTime")) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(json["name"].is<String>()) name = json["name"].as<String>();
if(json["allowedFrom"].is<String>()) allowedFrom = json["allowedFrom"].as<String>();
if(json["allowedUntil"].is<String>()) allowedUntil = json["allowedUntil"].as<String>();
if(json["allowedWeekdays"].is<String>()) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json["allowedFromTime"].is<String>()) allowedFromTime = json["allowedFromTime"].as<String>();
if(json["allowedUntilTime"].is<String>()) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(action)
{
@@ -2209,12 +2209,12 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
String lockAction;
NukiOpener::LockAction timeControlLockAction;
if(json.containsKey("enabled")) enabled = json["enabled"].as<unsigned int>();
if(json["enabled"].is<unsigned int>()) enabled = json["enabled"].as<unsigned int>();
else enabled = 2;
if(json.containsKey("weekdays")) weekdays = json["weekdays"].as<String>();
if(json.containsKey("time")) time = json["time"].as<String>();
if(json.containsKey("lockAction")) lockAction = json["lockAction"].as<String>();
if(json["weekdays"].is<String>()) weekdays = json["weekdays"].as<String>();
if(json["time"].is<String>()) time = json["time"].as<String>();
if(json["lockAction"].is<String>()) lockAction = json["lockAction"].as<String>();
if(lockAction.length() > 0)
{
@@ -2443,22 +2443,22 @@ void NukiOpenerWrapper::onAuthCommandReceived(const char *value)
String allowedFromTime;
String allowedUntilTime;
if(json.containsKey("remoteAllowed")) remoteAllowed = json["remoteAllowed"].as<unsigned int>();
if(json["remoteAllowed"].is<unsigned int>()) remoteAllowed = json["remoteAllowed"].as<unsigned int>();
else remoteAllowed = 2;
if(json.containsKey("enabled")) enabled = json["enabled"].as<unsigned int>();
if(json["enabled"].is<unsigned int>()) enabled = json["enabled"].as<unsigned int>();
else enabled = 2;
if(json.containsKey("timeLimited")) timeLimited = json["timeLimited"].as<unsigned int>();
if(json["timeLimited"].is<unsigned int>()) timeLimited = json["timeLimited"].as<unsigned int>();
else timeLimited = 2;
if(json.containsKey("name")) name = json["name"].as<String>();
//if(json.containsKey("sharedKey")) sharedKey = json["sharedKey"].as<String>();
if(json.containsKey("allowedFrom")) allowedFrom = json["allowedFrom"].as<String>();
if(json.containsKey("allowedUntil")) allowedUntil = json["allowedUntil"].as<String>();
if(json.containsKey("allowedWeekdays")) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json.containsKey("allowedFromTime")) allowedFromTime = json["allowedFromTime"].as<String>();
if(json.containsKey("allowedUntilTime")) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(json["name"].is<String>()) name = json["name"].as<String>();
//if(json["sharedKey"].is<String>()) sharedKey = json["sharedKey"].as<String>();
if(json["allowedFrom"].is<String>()) allowedFrom = json["allowedFrom"].as<String>();
if(json["allowedUntil"].is<String>()) allowedUntil = json["allowedUntil"].as<String>();
if(json["allowedWeekdays"].is<String>()) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json["allowedFromTime"].is<String>()) allowedFromTime = json["allowedFromTime"].as<String>();
if(json["allowedUntilTime"].is<String>()) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(action)
{

View File

@@ -1,3 +1,6 @@
#ifndef CONFIG_IDF_TARGET_ESP32H2
#include "esp_wifi.h"
#endif
#include "NukiWrapper.h"
#include "PreferencesKeys.h"
#include "MqttTopics.h"
@@ -58,8 +61,24 @@ void NukiWrapper::initialize(const bool& firstStart)
if(firstStart)
{
Log->println("First start, setting preference defaults");
_preferences->putBool(preference_network_wifi_fallback_disabled, false);
_preferences->putBool(preference_find_best_rssi, false);
#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->putBool(preference_check_updates, true);
_preferences->putBool(preference_opener_continuous_mode, false);
_preferences->putBool(preference_official_hybrid_enabled, false);
@@ -1894,21 +1913,21 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
String allowedFromTime;
String allowedUntilTime;
if(json.containsKey("code")) code = json["code"].as<unsigned int>();
if(json["code"].is<unsigned int>()) code = json["code"].as<unsigned int>();
else code = 12;
if(json.containsKey("enabled")) enabled = json["enabled"].as<unsigned int>();
if(json["enabled"].is<unsigned int>()) enabled = json["enabled"].as<unsigned int>();
else enabled = 2;
if(json.containsKey("timeLimited")) timeLimited = json["timeLimited"].as<unsigned int>();
if(json["timeLimited"].is<unsigned int>()) timeLimited = json["timeLimited"].as<unsigned int>();
else timeLimited = 2;
if(json.containsKey("name")) name = json["name"].as<String>();
if(json.containsKey("allowedFrom")) allowedFrom = json["allowedFrom"].as<String>();
if(json.containsKey("allowedUntil")) allowedUntil = json["allowedUntil"].as<String>();
if(json.containsKey("allowedWeekdays")) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json.containsKey("allowedFromTime")) allowedFromTime = json["allowedFromTime"].as<String>();
if(json.containsKey("allowedUntilTime")) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(json["name"].is<String>()) name = json["name"].as<String>();
if(json["allowedFrom"].is<String>()) allowedFrom = json["allowedFrom"].as<String>();
if(json["allowedUntil"].is<String>()) allowedUntil = json["allowedUntil"].as<String>();
if(json["allowedWeekdays"].is<String>()) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json["allowedFromTime"].is<String>()) allowedFromTime = json["allowedFromTime"].as<String>();
if(json["allowedUntilTime"].is<String>()) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(action)
{
@@ -2331,12 +2350,12 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
String lockAction;
NukiLock::LockAction timeControlLockAction;
if(json.containsKey("enabled")) enabled = json["enabled"].as<unsigned int>();
if(json["enabled"].is<unsigned int>()) enabled = json["enabled"].as<unsigned int>();
else enabled = 2;
if(json.containsKey("weekdays")) weekdays = json["weekdays"].as<String>();
if(json.containsKey("time")) time = json["time"].as<String>();
if(json.containsKey("lockAction")) lockAction = json["lockAction"].as<String>();
if(json["weekdays"].is<String>()) weekdays = json["weekdays"].as<String>();
if(json["time"].is<String>()) time = json["time"].as<String>();
if(json["lockAction"].is<String>()) lockAction = json["lockAction"].as<String>();
if(lockAction.length() > 0)
{
@@ -2567,22 +2586,22 @@ void NukiWrapper::onAuthCommandReceived(const char *value)
String allowedFromTime;
String allowedUntilTime;
if(json.containsKey("remoteAllowed")) remoteAllowed = json["remoteAllowed"].as<unsigned int>();
if(json["remoteAllowed"].is<unsigned int>()) remoteAllowed = json["remoteAllowed"].as<unsigned int>();
else remoteAllowed = 2;
if(json.containsKey("enabled")) enabled = json["enabled"].as<unsigned int>();
if(json["enabled"].is<unsigned int>()) enabled = json["enabled"].as<unsigned int>();
else enabled = 2;
if(json.containsKey("timeLimited")) timeLimited = json["timeLimited"].as<unsigned int>();
if(json["timeLimited"].is<unsigned int>()) timeLimited = json["timeLimited"].as<unsigned int>();
else timeLimited = 2;
if(json.containsKey("name")) name = json["name"].as<String>();
//if(json.containsKey("sharedKey")) sharedKey = json["sharedKey"].as<String>();
if(json.containsKey("allowedFrom")) allowedFrom = json["allowedFrom"].as<String>();
if(json.containsKey("allowedUntil")) allowedUntil = json["allowedUntil"].as<String>();
if(json.containsKey("allowedWeekdays")) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json.containsKey("allowedFromTime")) allowedFromTime = json["allowedFromTime"].as<String>();
if(json.containsKey("allowedUntilTime")) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(json["name"].is<String>()) name = json["name"].as<String>();
//if(json["sharedKey"].is<String>()) sharedKey = json["sharedKey"].as<String>();
if(json["allowedFrom"].is<String>()) allowedFrom = json["allowedFrom"].as<String>();
if(json["allowedUntil"].is<String>()) allowedUntil = json["allowedUntil"].as<String>();
if(json["allowedWeekdays"].is<String>()) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json["allowedFromTime"].is<String>()) allowedFromTime = json["allowedFromTime"].as<String>();
if(json["allowedUntilTime"].is<String>()) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(action)
{

View File

@@ -52,9 +52,10 @@
#define preference_update_from_mqtt (char*)"updMqtt"
#define preference_disable_non_json (char*)"disnonjson"
#define preference_official_hybrid_enabled (char*)"offHybrid"
#define preference_wifi_ssid (char*)"wifiSSID"
#define preference_wifi_pass (char*)"wifiPass"
// CHANGE DOES NOT REQUIRE REBOOT TO TAKE EFFECT
#define preference_find_best_rssi (char*)"nwbestrssi"
#define preference_ntw_reconfigure (char*)"ntwRECONF"
#define preference_auth_max_entries (char*)"authmaxentry"
#define preference_auth_info_enabled (char*)"authInfoEna"
@@ -88,14 +89,12 @@
#define preference_command_retry_delay (char*)"rtryDelay"
#define preference_query_interval_hybrid_lockstate (char*)"hybridTimer"
#define preference_mqtt_hass_cu_url (char*)"hassConfigUrl"
#define preference_network_wifi_fallback_disabled (char*)"nwwififb"
#define preference_check_updates (char*)"checkupdates"
#define preference_opener_continuous_mode (char*)"openercont"
#define preference_rssi_publish_interval (char*)"rssipb"
#define preference_network_timeout (char*)"nettmout"
#define preference_restart_on_disconnect (char*)"restdisc"
#define preference_publish_debug_info (char*)"pubdbg"
#define preference_recon_netw_on_mqtt_discon (char*)"recNtwMqttDis"
#define preference_official_hybrid_actions (char*)"hybridAct"
#define preference_official_hybrid_retry (char*)"hybridRtry"
@@ -118,12 +117,14 @@
#define preference_lock_max_timecontrol_entry_count (char*)"maxtc"
#define preference_opener_max_timecontrol_entry_count (char*)"opmaxtc"
#define preference_latest_version (char*)"latest"
#define preference_wifi_converted (char*)"wifiConv"
//OBSOLETE
#define preference_access_level (char*)"accLvl"
#define preference_gpio_locking_enabled (char*)"gpiolck"
#define preference_network_hardware_gpio (char*)"nwhwdt"
#define preference_presence_detection_timeout (char*)"prdtimeout"
#define preference_network_wifi_fallback_disabled (char*)"nwwififb"
inline bool initPreferences(Preferences* preferences)
{
@@ -248,9 +249,15 @@ inline bool initPreferences(Preferences* preferences)
if (configVer < 901)
{
#if defined(CONFIG_IDF_TARGET_ESP32S3)
if (preferences->getInt(preference_network_hardware) == 3) preferences->putInt(preference_network_hardware, 10);
if (preferences->getInt(preference_network_hardware) == 3)
{
preferences->putInt(preference_network_hardware, 10);
}
#endif
if (preferences->getInt(preference_network_hardware) == 2) preferences->putInt(preference_network_hardware, 3);
if (preferences->getInt(preference_network_hardware) == 2)
{
preferences->putInt(preference_network_hardware, 3);
}
}
preferences->putInt(preference_config_version, atof(NUKI_HUB_VERSION) * 100);
@@ -271,8 +278,8 @@ private:
preference_opener_continuous_mode, preference_mqtt_opener_path, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count,
preference_lock_max_timecontrol_entry_count, preference_opener_max_timecontrol_entry_count, preference_enable_bootloop_reset, preference_mqtt_ca, preference_mqtt_crt,
preference_mqtt_key, preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url, preference_buffer_size, preference_ip_dhcp_enabled, preference_ip_address,
preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, preference_network_hardware, preference_network_wifi_fallback_disabled,
preference_rssi_publish_interval, preference_hostname, preference_find_best_rssi, preference_network_timeout, preference_restart_on_disconnect,
preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, preference_network_hardware,
preference_rssi_publish_interval, preference_hostname, preference_network_timeout, preference_restart_on_disconnect,
preference_restart_ble_beacon_lost, preference_query_interval_lockstate, preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry,
preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_keypad_control_enabled,
preference_keypad_info_enabled, preference_keypad_publish_code, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_conf_info_enabled,
@@ -280,26 +287,26 @@ private:
preference_cred_password, preference_disable_non_json, preference_publish_authdata, preference_publish_debug_info,
preference_official_hybrid_enabled, preference_query_interval_hybrid_lockstate, preference_official_hybrid_actions, preference_official_hybrid_retry,
preference_task_size_network, preference_task_size_nuki, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries,
preference_update_from_mqtt, preference_show_secrets, preference_ble_tx_power, preference_recon_netw_on_mqtt_discon, preference_webserial_enabled,
preference_update_from_mqtt, preference_show_secrets, preference_ble_tx_power, preference_webserial_enabled,
preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, preference_network_custom_irq,
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_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_wifi_ssid, preference_wifi_pass
};
std::vector<char*> _redact =
{
preference_mqtt_user, preference_mqtt_password, preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key, preference_cred_user, preference_cred_password,
preference_nuki_id_lock, preference_nuki_id_opener,
preference_nuki_id_lock, preference_nuki_id_opener, preference_wifi_pass
};
std::vector<char*> _boolPrefs =
{
preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_opener_enabled, preference_opener_continuous_mode,
preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_enable_bootloop_reset, preference_webserver_enabled, preference_find_best_rssi,
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_network_wifi_fallback_disabled, preference_official_hybrid_enabled,
preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_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_recon_netw_on_mqtt_discon, preference_webserial_enabled,
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled,
preference_ntw_reconfigure
};
std::vector<char*> _bytePrefs =

View File

@@ -33,12 +33,9 @@ enum class RestartReason
extern int restartReason;
extern uint64_t restartReasonValidDetect;
extern bool rebuildGpioRequested;
extern RestartReason currentRestartReason;
extern bool restartReason_isValid;
inline static void restartEsp(RestartReason reason)
{
if(reason == RestartReason::GpioConfigurationUpdated)

View File

@@ -9,6 +9,7 @@
#endif
#ifndef CONFIG_IDF_TARGET_ESP32H2
#include <esp_wifi.h>
#include <WiFi.h>
#endif
#include <Update.h>
@@ -77,12 +78,22 @@ 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.");
#ifndef NUKI_HUB_UPDATER
return buildHtml(request);
#else
return buildOtaHtml(request);
if(!_network->isApOpen())
{
#ifndef NUKI_HUB_UPDATER
return buildHtml(request);
#else
return buildOtaHtml(request);
#endif
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
else
{
return buildWifiConnectHtml(request);
}
#endif
});
_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.");
return sendCss(request);
@@ -91,128 +102,6 @@ void WebCfgServer::initialize()
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
return sendFavicon(request);
});
#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.");
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.");
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.");
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.");
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.");
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.");
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.");
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.");
return buildCredHtml(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.");
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.");
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.");
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.");
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(_allowRestartToPortal)
{
esp_err_t res = buildConfirmHtml(request, "Restarting. Connect to ESP access point to reconfigure Wi-Fi.", 0);
waitAndProcess(false, 1000);
_network->reconfigureDevice();
return res;
}
return(ESP_OK);
});
#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.");
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.");
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.");
return processFactoryReset(request);
});
_psychicServer->on("/infopg", 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.");
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.");
_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.");
_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.");
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.");
processGpioArgs(request);
esp_err_t res = buildConfirmHtml(request, "Saving GPIO configuration. Restarting.", 3, true);
Log->println(F("Restarting"));
waitAndProcess(true, 1000);
restartEsp(RestartReason::GpioConfigurationUpdated);
return res;
});
#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.");
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.");
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.");
esp_err_t res = buildConfirmHtml(request, "Rebooting to other partition", 2, true);
waitAndProcess(true, 1000);
esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL));
restartEsp(RestartReason::OTAReboot);
return res;
});
_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.");
esp_err_t res = buildConfirmHtml(request, "Rebooting", 2, true);
@@ -220,50 +109,371 @@ void WebCfgServer::initialize()
restartEsp(RestartReason::RequestedViaWebServer);
return res;
});
_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.");
#ifndef NUKI_HUB_UPDATER
return processUpdate(request);
#else
return request->redirect("/");
#endif
});
PsychicUploadHandler *updateHandler = new PsychicUploadHandler();
updateHandler->onUpload([&](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final)
{
if(_network->isApOpen())
{
#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.");
return handleOtaUpload(request, filename, index, data, len, final);
}
);
return buildSSIDListHtml(request);
});
_psychicServer->on("/savewifi", 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.");
String message = "";
bool connected = processWiFi(request, message);
esp_err_t res = buildConfirmHtml(request, message, 10, true);
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.");
String result;
if (!Update.hasError())
{
Log->print("Update code or data OK Update.errorString() ");
Log->println(Update.errorString());
result = "<b style='color:green'>Update OK.</b>";
esp_err_t res = request->reply(200,"text/html",result.c_str());
restartEsp(RestartReason::OTACompleted);
if(connected)
{
waitAndProcess(true, 3000);
restartEsp(RestartReason::ReconfigureWifi);
//abort();
}
return res;
}
else {
result = " Update.errorString() " + String(Update.errorString());
Log->print("ERROR : error ");
Log->println(result.c_str());
esp_err_t res = request->reply(500, "text/html", result.c_str());
restartEsp(RestartReason::OTAAborted);
});
#endif
}
else
{
#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.");
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.");
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.");
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.");
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.");
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.");
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.");
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.");
return buildCredHtml(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.");
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.");
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.");
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.");
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(_allowRestartToPortal)
{
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;
}
return(ESP_OK);
});
#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.");
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.");
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.");
return processFactoryReset(request);
});
_psychicServer->on("/infopg", 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.");
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.");
_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.");
_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.");
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.");
processGpioArgs(request);
esp_err_t res = buildConfirmHtml(request, "Saving GPIO configuration. Restarting.", 3, true);
Log->println(F("Restarting"));
waitAndProcess(true, 1000);
restartEsp(RestartReason::GpioConfigurationUpdated);
return res;
}
});
});
#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.");
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.");
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.");
esp_err_t res = buildConfirmHtml(request, "Rebooting to other partition", 2, true);
waitAndProcess(true, 1000);
esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL));
restartEsp(RestartReason::OTAReboot);
return res;
});
_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.");
#ifndef NUKI_HUB_UPDATER
return processUpdate(request);
#else
return request->redirect("/");
#endif
});
_psychicServer->on("/uploadota", HTTP_POST, updateHandler);
//Update.onProgress(printProgress);
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.");
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.");
String result;
if (!Update.hasError())
{
Log->print("Update code or data OK Update.errorString() ");
Log->println(Update.errorString());
result = "<b style='color:green'>Update OK.</b>";
esp_err_t res = request->reply(200,"text/html",result.c_str());
restartEsp(RestartReason::OTACompleted);
return res;
}
else {
result = " Update.errorString() " + String(Update.errorString());
Log->print("ERROR : error ");
Log->println(result.c_str());
esp_err_t res = request->reply(500, "text/html", result.c_str());
restartEsp(RestartReason::OTAAborted);
return res;
}
});
_psychicServer->on("/uploadota", HTTP_POST, updateHandler);
//Update.onProgress(printProgress);
}
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
esp_err_t WebCfgServer::buildSSIDListHtml(PsychicRequest *request)
{
_network->scan(true, false);
createSsidList();
PsychicStreamResponse response(request, "text/plain");
response.beginSend();
for (int i = 0; i < _ssidList.size(); i++)
{
response.print("<tr class=\"trssid\" onclick=\"document.getElementById('inputssid').value = '" + _ssidList[i] + "';\"><td colspan=\"2\">" + _ssidList[i] + String(F(" (")) + String(_rssiList[i]) + String(F(" %)")) + "</td></tr>");
}
return response.endSend();
}
void WebCfgServer::createSsidList()
{
int _foundNetworks = WiFi.scanComplete();
std::vector<String> _tmpSsidList;
std::vector<int> _tmpRssiList;
for (int i = 0; i < _foundNetworks; i++)
{
int rssi = constrain((100.0 + WiFi.RSSI(i)) * 2, 0, 100);
auto it1 = std::find(_ssidList.begin(), _ssidList.end(), WiFi.SSID(i));
auto it2 = std::find(_tmpSsidList.begin(), _tmpSsidList.end(), WiFi.SSID(i));
if(it1 == _ssidList.end())
{
_ssidList.push_back(WiFi.SSID(i));
_rssiList.push_back(rssi);
_tmpSsidList.push_back(WiFi.SSID(i));
_tmpRssiList.push_back(rssi);
}
else if (it2 == _tmpSsidList.end())
{
_tmpSsidList.push_back(WiFi.SSID(i));
_tmpRssiList.push_back(rssi);
int index = it1 - _ssidList.begin();
_rssiList[index] = rssi;
}
else
{
int index = it1 - _ssidList.begin();
int index2 = it2 - _tmpSsidList.begin();
if (_tmpRssiList[index2] < rssi)
{
_tmpRssiList[index2] = rssi;
_rssiList[index] = rssi;
}
}
}
}
esp_err_t WebCfgServer::buildWifiConnectHtml(PsychicRequest *request)
{
String header = "<style>.trssid:hover { cursor: pointer; color: blue; }</style><script>let intervalId; window.onload = function() { intervalId = setInterval(updateSSID, 3000); }; function updateSSID() { var request = new XMLHttpRequest(); request.open('GET', '/ssidlist', true); request.onload = () => { if (document.getElementById(\"aplist\") !== null) { document.getElementById(\"aplist\").innerHTML = request.responseText; } }; request.send(); }</script>";
PsychicStreamResponse response(request, "text/plain");
response.beginSend();
buildHtmlHeader(&response, header);
response.print("<h3>Available WiFi networks</h3>");
response.print("<table id=\"aplist\">");
createSsidList();
for (int i = 0; i < _ssidList.size(); i++)
{
response.print("<tr class=\"trssid\" onclick=\"document.getElementById('inputssid').value = '" + _ssidList[i] + "';\"><td colspan=\"2\">" + _ssidList[i] + String(F(" (")) + String(_rssiList[i]) + String(F(" %)")) + "</td></tr>");
}
response.print("</table>");
response.print("<form class=\"adapt\" method=\"post\" action=\"savewifi\">");
response.print("<h3>WiFi credentials</h3>");
response.print("<table>");
printInputField(&response, "WIFISSID", "SSID", "", 32, "id=\"inputssid\"", false, true);
printInputField(&response, "WIFIPASS", "Secret key", "", 63, "id=\"inputpass\"", false, true);
response.print("</table>");
response.print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.print("</form>");
response.print("<form action=\"/reboot\" method=\"get\"><br><input type=\"submit\" value=\"Reboot\" /></form>");
response.print("</body></html>");
return response.endSend();
}
bool WebCfgServer::processWiFi(PsychicRequest *request, String& message)
{
bool res = false;
int params = request->params();
String ssid;
String pass;
for(int index = 0; index < params; index++)
{
const PsychicWebParameter* p = request->getParam(index);
String key = p->name();
String value = p->value();
if(index < params -1)
{
const PsychicWebParameter* next = request->getParam(index+1);
if(key == next->name()) continue;
}
if(key == "WIFISSID")
{
ssid = value;
}
else if(key == "WIFIPASS")
{
pass = value;
}
}
ssid.trim();
pass.trim();
if (ssid.length() > 0 && pass.length() > 0)
{
WiFi.begin(ssid, pass);
int loop = 0;
while(WiFi.status() != WL_CONNECTED && loop < 150)
{
delay(100);
loop++;
}
if (WiFi.status() != WL_CONNECTED)
{
message = "Failed to connect to the given SSID with the given secret key, credentials not saved<br/>";
}
else
{
message = "Connection successful. Rebooting Nuki Hub.<br/>";
if(WiFi.isConnected())
{
esp_wifi_disconnect();
}
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");
return res;
}
if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) != ESP_OK) {
Log->println("Failed to set storage Wi-Fi");
return res;
}
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 set Wi-Fi configuration");
return res;
}
_preferences->putString(preference_wifi_ssid, ssid);
_preferences->putString(preference_wifi_pass, pass);
res = true;
}
}
else
{
message = "No SSID or secret key entered, credentials not saved<br/>";
}
return res;
}
#endif
esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, bool debug)
{
PsychicStreamResponse response(request, "text/plain");
@@ -648,6 +858,62 @@ String WebCfgServer::generateConfirmCode()
return String(code);
}
void WebCfgServer::printInputField(PsychicStreamResponse *response,
const char *token,
const char *description,
const char *value,
const size_t& maxLength,
const char *args,
const bool& isPassword,
const bool& showLengthRestriction)
{
char maxLengthStr[20];
itoa(maxLength, maxLengthStr, 10);
response->print("<tr><td>");
response->print(description);
if(showLengthRestriction)
{
response->print(" (Max. ");
response->print(maxLength);
response->print(" characters)");
}
response->print("</td><td>");
response->print("<input type=");
response->print(isPassword ? "\"password\"" : "\"text\"");
if(strcmp(args, "") != 0)
{
response->print(" ");
response->print(args);
}
if(strcmp(value, "") != 0)
{
response->print(" value=\"");
response->print(value);
}
response->print("\" name=\"");
response->print(token);
response->print("\" size=\"25\" maxlength=\"");
response->print(maxLengthStr);
response->print("\"/>");
response->print("</td></tr>");
}
void WebCfgServer::printInputField(PsychicStreamResponse *response,
const char *token,
const char *description,
const int value,
size_t maxLength,
const char *args)
{
char valueStr[20];
itoa(value, valueStr, 10);
printInputField(response, token, description, valueStr, maxLength, args);
}
#ifndef NUKI_HUB_UPDATER
esp_err_t WebCfgServer::sendSettings(PsychicRequest *request)
{
@@ -1162,16 +1428,6 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message)
//configChanged = true;
}
}
else if(key == "BESTRSSI")
{
if(_preferences->getBool(preference_find_best_rssi, false) != (value == "1"))
{
_preferences->putBool(preference_find_best_rssi, (value == "1"));
Log->print(F("Setting changed: "));
Log->println(key);
//configChanged = true;
}
}
else if(key == "HOSTNAME")
{
if(_preferences->getString(preference_hostname, "") != value)
@@ -1202,16 +1458,6 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message)
//configChanged = true;
}
}
else if(key == "RECNWTMQTTDIS")
{
if(_preferences->getBool(preference_recon_netw_on_mqtt_discon, false) != (value == "1"))
{
_preferences->putBool(preference_recon_netw_on_mqtt_discon, (value == "1"));
Log->print(F("Setting changed: "));
Log->println(key);
//configChanged = true;
}
}
else if(key == "MQTTLOG")
{
if(_preferences->getBool(preference_mqtt_log_enabled, false) != (value == "1"))
@@ -2794,13 +3040,10 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request)
printTextarea(&response, "MQTTKEY", "MQTT SSL Client Key (*, optional)", _preferences->getString(preference_mqtt_key).c_str(), TLS_KEY_MAX_SIZE, true, true);
printDropDown(&response, "NWHW", "Network hardware", String(_preferences->getInt(preference_network_hardware)), getNetworkDetectionOptions(), "");
#ifndef CONFIG_IDF_TARGET_ESP32H2
printCheckBox(&response, "NWHWWIFIFB", "Disable fallback to Wi-Fi / Wi-Fi config portal", _preferences->getBool(preference_network_wifi_fallback_disabled), "");
printCheckBox(&response, "BESTRSSI", "Connect to AP with the best signal in an environment with multiple APs with the same SSID", _preferences->getBool(preference_find_best_rssi), "");
printInputField(&response, "RSSI", "RSSI Publish interval (seconds; -1 to disable)", _preferences->getInt(preference_rssi_publish_interval), 6, "");
#endif
printInputField(&response, "NETTIMEOUT", "MQTT Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5, "");
printCheckBox(&response, "RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect), "");
printCheckBox(&response, "RECNWTMQTTDIS", "Reconnect network on MQTT connection failure", _preferences->getBool(preference_recon_netw_on_mqtt_discon), "");
printCheckBox(&response, "MQTTLOG", "Enable MQTT logging", _preferences->getBool(preference_mqtt_log_enabled), "");
printCheckBox(&response, "CHECKUPDATE", "Check for Firmware Updates every 24h", _preferences->getBool(preference_check_updates), "");
printCheckBox(&response, "UPDATEMQTT", "Allow updating using MQTT", _preferences->getBool(preference_update_from_mqtt), "");
@@ -2945,13 +3188,13 @@ String WebCfgServer::pinStateToString(uint8_t value) {
switch(value)
{
case 0:
return (String)"PIN not set";
return String("PIN not set");
case 1:
return (String)"PIN valid";
return String("PIN valid");
case 2:
return (String)"PIN set but invalid";;
return String("PIN set but invalid");
default:
return (String)"Unknown";
return String("Unknown");
}
}
@@ -3230,7 +3473,7 @@ esp_err_t WebCfgServer::buildConfigureWifiHtml(PsychicRequest *request)
response.beginSend();
buildHtmlHeader(&response);
response.print("<h3>Wi-Fi</h3>");
response.print("Click confirm to restart ESP into Wi-Fi configuration mode. After restart, connect to ESP access point to reconfigure Wi-Fi.<br><br>");
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.<br><br>");
buildNavigationButton(&response, "Confirm", "/wifimanager");
response.print("</body></html>");
return response.endSend();
@@ -3366,12 +3609,8 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request)
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
response.print("\nFallback to Wi-Fi / Wi-Fi config portal disabled: ");
response.print(_preferences->getBool(preference_network_wifi_fallback_disabled, false) ? "Yes" : "No");
if(_network->networkDeviceName() == "Built-in Wi-Fi")
{
response.print("\nConnect to AP with the best signal enabled: ");
response.print(_preferences->getBool(preference_find_best_rssi, false) ? "Yes" : "No");
response.print("\nRSSI Publish interval (s): ");
if(_preferences->getInt(preference_rssi_publish_interval, 60) < 0) response.print("Disabled");
@@ -3380,8 +3619,6 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request)
#endif
response.print("\nRestart ESP32 on network disconnect enabled: ");
response.print(_preferences->getBool(preference_restart_on_disconnect, false) ? "Yes" : "No");
response.print("\nReconnect network on MQTT connection failure enabled: ");
response.print(_preferences->getBool(preference_recon_netw_on_mqtt_discon, false) ? "Yes" : "No");
response.print("\nMQTT Timeout until restart (s): ");
if(_preferences->getInt(preference_network_timeout, 60) < 0) response.print("Disabled");
else response.print(_preferences->getInt(preference_network_timeout, 60));
@@ -3967,11 +4204,6 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request)
#ifndef CONFIG_IDF_TARGET_ESP32H2
if(resetWifi)
{
wifi_config_t current_conf;
esp_wifi_get_config((wifi_interface_t)ESP_IF_WIFI_STA, &current_conf);
memset(current_conf.sta.ssid, 0, sizeof(current_conf.sta.ssid));
memset(current_conf.sta.password, 0, sizeof(current_conf.sta.password));
esp_wifi_set_config((wifi_interface_t)ESP_IF_WIFI_STA, &current_conf);
_network->reconfigureDevice();
}
#endif
@@ -3981,62 +4213,6 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request)
return res;
}
void WebCfgServer::printInputField(PsychicStreamResponse *response,
const char *token,
const char *description,
const char *value,
const size_t& maxLength,
const char *args,
const bool& isPassword,
const bool& showLengthRestriction)
{
char maxLengthStr[20];
itoa(maxLength, maxLengthStr, 10);
response->print("<tr><td>");
response->print(description);
if(showLengthRestriction)
{
response->print(" (Max. ");
response->print(maxLength);
response->print(" characters)");
}
response->print("</td><td>");
response->print("<input type=");
response->print(isPassword ? "\"password\"" : "\"text\"");
if(strcmp(args, "") != 0)
{
response->print(" ");
response->print(args);
}
if(strcmp(value, "") != 0)
{
response->print(" value=\"");
response->print(value);
}
response->print("\" name=\"");
response->print(token);
response->print("\" size=\"25\" maxlength=\"");
response->print(maxLengthStr);
response->print("\"/>");
response->print("</td></tr>");
}
void WebCfgServer::printInputField(PsychicStreamResponse *response,
const char *token,
const char *description,
const int value,
size_t maxLength,
const char *args)
{
char valueStr[20];
itoa(value, valueStr, 10);
printInputField(response, token, description, valueStr, maxLength, args);
}
void WebCfgServer::printCheckBox(PsychicStreamResponse *response, const char *token, const char *description, const bool value, const char *htmlClass)
{
response->print("<tr><td>");
@@ -4183,7 +4359,7 @@ const std::vector<std::pair<String, String>> WebCfgServer::getNetworkDetectionOp
{
std::vector<std::pair<String, String>> options;
options.push_back(std::make_pair("1", "Wi-Fi only"));
options.push_back(std::make_pair("1", "Wi-Fi"));
options.push_back(std::make_pair("2", "Generic W5500"));
options.push_back(std::make_pair("3", "M5Stack Atom POE (W5500)"));
options.push_back(std::make_pair("10", "M5Stack Atom POE S3 (W5500)"));

View File

@@ -55,7 +55,7 @@ private:
esp_err_t buildCredHtml(PsychicRequest *request);
esp_err_t buildImportExportHtml(PsychicRequest *request);
esp_err_t buildMqttConfigHtml(PsychicRequest *request);
esp_err_t buildStatusHtml(PsychicRequest *request);
esp_err_t buildStatusHtml(PsychicRequest *request);
esp_err_t buildAdvancedConfigHtml(PsychicRequest *request);
esp_err_t buildNukiConfigHtml(PsychicRequest *request);
esp_err_t buildGpioConfigHtml(PsychicRequest *request);
@@ -67,8 +67,6 @@ private:
esp_err_t processUnpair(PsychicRequest *request, bool opener);
esp_err_t processUpdate(PsychicRequest *request);
esp_err_t processFactoryReset(PsychicRequest *request);
void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const char* value, const size_t& maxLength, const char* args, const bool& isPassword = false, const bool& showLengthRestriction = false);
void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const int value, size_t maxLength, const char* args);
void printCheckBox(PsychicStreamResponse *response, const char* token, const char* description, const bool value, const char* htmlClass);
void printTextarea(PsychicStreamResponse *response, const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false);
void printDropDown(PsychicStreamResponse *response, const char *token, const char *description, const String preselectedValue, std::vector<std::pair<String, String>> options, const String className);
@@ -94,19 +92,30 @@ private:
bool _brokerConfigured = false;
bool _rebootRequired = false;
#endif
std::vector<String> _ssidList;
std::vector<int> _rssiList;
String generateConfirmCode();
String _confirmCode = "----";
esp_err_t buildSSIDListHtml(PsychicRequest *request);
esp_err_t buildConfirmHtml(PsychicRequest *request, const String &message, uint32_t redirectDelay = 5, bool redirect = false);
esp_err_t buildOtaHtml(PsychicRequest *request, bool debug = false);
esp_err_t buildOtaCompletedHtml(PsychicRequest *request);
esp_err_t sendCss(PsychicRequest *request);
esp_err_t sendFavicon(PsychicRequest *request);
void createSsidList();
void buildHtmlHeader(PsychicStreamResponse *response, String additionalHeader = "");
void waitAndProcess(const bool blocking, const uint32_t duration);
esp_err_t handleOtaUpload(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final);
void printProgress(size_t prg, size_t sz);
#ifndef CONFIG_IDF_TARGET_ESP32H2
esp_err_t buildWifiConnectHtml(PsychicRequest *request);
bool processWiFi(PsychicRequest *request, String& message);
#endif
void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const char* value, const size_t& maxLength, const char* args, const bool& isPassword = false, const bool& showLengthRestriction = false);
void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const int value, size_t maxLength, const char* args);
PsychicHttpServer* _psychicServer = nullptr;
NukiNetwork* _network = nullptr;
Preferences* _preferences = nullptr;

View File

@@ -5,7 +5,7 @@
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include <esp_task_wdt.h>
#include "esp_task_wdt.h"
#include "Config.h"
#ifndef NUKI_HUB_UPDATER
@@ -78,7 +78,6 @@ TaskHandle_t networkTaskHandle = nullptr;
ssize_t write_fn(void* cookie, const char* buf, ssize_t size)
{
Log->write((uint8_t *)buf, (size_t)size);
return size;
}
@@ -104,7 +103,7 @@ int _log_vprintf(const char *fmt, va_list args) {
void setReroute(){
esp_log_set_vprintf(_log_vprintf);
if(preferences->getBool(preference_mqtt_log_enabled))
if(preferences->getBool(preference_mqtt_log_enabled))
{
esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("mqtt", ESP_LOG_NONE);
@@ -377,8 +376,11 @@ void setupTasks(bool ota)
xTaskCreatePinnedToCore(networkTask, "ntw", preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), NULL, 3, &networkTaskHandle, 1);
esp_task_wdt_add(networkTaskHandle);
#ifndef NUKI_HUB_UPDATER
xTaskCreatePinnedToCore(nukiTask, "nuki", preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), NULL, 2, &nukiTaskHandle, 0);
esp_task_wdt_add(nukiTaskHandle);
if(!network->isApOpen())
{
xTaskCreatePinnedToCore(nukiTask, "nuki", preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), NULL, 2, &nukiTaskHandle, 0);
esp_task_wdt_add(nukiTaskHandle);
}
#endif
}
}
@@ -434,7 +436,7 @@ void setup()
webCfgServer = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer);
webCfgServer->initialize();
psychicServer->listen(80);
psychicServer->onNotFound([](PsychicRequest* request) { return request->redirect("/"); });
psychicServer->onNotFound([](PsychicRequest* request) { return request->redirect("/"); });
}
#else
Log->print(F("Nuki Hub version "));
@@ -453,7 +455,6 @@ void setup()
}
char16_t buffer_size = preferences->getInt(preference_buffer_size, 4096);
CharBuffer::initialize(buffer_size);
gpio = new Gpio(preferences);
@@ -461,22 +462,30 @@ void setup()
gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration(), "\n\r");
Log->print(gpioDesc.c_str());
const String mqttLockPath = preferences->getString(preference_mqtt_lock_path);
network = new NukiNetwork(preferences, gpio, mqttLockPath, CharBuffer::get(), buffer_size);
network->initialize();
lockEnabled = preferences->getBool(preference_lock_enabled);
openerEnabled = preferences->getBool(preference_opener_enabled);
if(network->isApOpen())
{
forceEnableWebServer = true;
doOta = false;
lockEnabled = false;
openerEnabled = false;
}
bleScanner = new BleScanner::Scanner();
// Scan interval and window according to Nuki recommendations:
// https://developer.nuki.io/t/bluetooth-specification-questions/1109/27
bleScanner->initialize("NukiHub", true, 40, 40);
bleScanner->setScanDuration(0);
lockEnabled = preferences->getBool(preference_lock_enabled);
openerEnabled = preferences->getBool(preference_opener_enabled);
const String mqttLockPath = preferences->getString(preference_mqtt_lock_path);
nukiOfficial = new NukiOfficial(preferences);
network = new NukiNetwork(preferences, gpio, mqttLockPath, CharBuffer::get(), buffer_size);
network->initialize();
networkLock = new NukiNetworkLock(network, nukiOfficial, preferences, CharBuffer::get(), buffer_size);
networkLock->initialize();
@@ -533,7 +542,7 @@ void setup()
if(doOta) setupTasks(true);
else setupTasks(false);
#ifdef DEBUG_NUKIHUB
Log->print("Task Name\tStatus\tPrio\tHWM\tTask\tAffinity\n");
char stats_buffer[1024];

View File

@@ -3,6 +3,9 @@
#include "../Logger.h"
#include "../RestartReason.h"
RTC_NOINIT_ATTR bool criticalEthFailure;
extern char WiFi_fallbackDetect[14];
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),
_deviceName(deviceName),
@@ -54,20 +57,34 @@ const String EthernetDevice::deviceName() const
void EthernetDevice::initialize()
{
delay(250);
if(criticalEthFailure)
{
criticalEthFailure = false;
Log->println(F("Failed to initialize ethernet hardware"));
Log->println("Network device has a critical failure, enable fallback to Wi-Fi and reboot.");
strcpy(WiFi_fallbackDetect, "wifi_fallback");
delay(200);
restartEsp(RestartReason::NetworkDeviceCriticalFailure);
return;
}
Log->println(F("Init Ethernet"));
if(_useSpi)
{
Log->println(F("Use SPI"));
criticalEthFailure = true;
SPI.begin(_spi_sck, _spi_miso, _spi_mosi);
_hardwareInitialized = ETH.begin(_type, _phy_addr, _cs, _irq, _rst, SPI);
criticalEthFailure = false;
}
#ifdef CONFIG_IDF_TARGET_ESP32
else
{
Log->println(F("Use RMII"));
criticalEthFailure = true;
_hardwareInitialized = ETH.begin(_type, _phy_addr, _mdc, _mdio, _power, _clock_mode);
criticalEthFailure = false;
if(!_ipConfiguration->dhcpEnabled())
{
_checkIpTs = (esp_timer_get_time() / 1000) + 2000;
@@ -78,6 +95,7 @@ void EthernetDevice::initialize()
if(_hardwareInitialized)
{
Log->println(F("Ethernet hardware Initialized"));
memset(WiFi_fallbackDetect, 0, sizeof(WiFi_fallbackDetect));
if(_useSpi && !_ipConfiguration->dhcpEnabled())
{
@@ -92,6 +110,11 @@ void EthernetDevice::initialize()
else
{
Log->println(F("Failed to initialize ethernet hardware"));
Log->println("Network device has a critical failure, enable fallback to Wi-Fi and reboot.");
strcpy(WiFi_fallbackDetect, "wifi_fallback");
delay(200);
restartEsp(RestartReason::NetworkDeviceCriticalFailure);
return;
}
}
@@ -173,25 +196,23 @@ void EthernetDevice::reconfigure()
restartEsp(RestartReason::ReconfigureETH);
}
void EthernetDevice::scan(bool passive, bool async)
{
}
bool EthernetDevice::isConnected()
{
return _connected;
}
ReconnectStatus EthernetDevice::reconnect(bool force)
bool EthernetDevice::isApOpen()
{
if(!_hardwareInitialized)
{
return ReconnectStatus::CriticalFailure;
}
delay(200);
return isConnected() ? ReconnectStatus::Success : ReconnectStatus::Failure;
return false;
}
void EthernetDevice::onDisconnected()
{
if(_preferences->getBool(preference_restart_on_disconnect, false) && ((esp_timer_get_time() / 1000) > 60000)) restartEsp(RestartReason::RestartOnDisconnectWatchdog);
reconnect();
}
int8_t EthernetDevice::signalStrength()

View File

@@ -45,19 +45,19 @@ public:
virtual void initialize();
virtual void reconfigure();
virtual void update();
virtual ReconnectStatus reconnect(bool force = false);
virtual void scan(bool passive = false, bool async = true);
virtual bool isConnected();
virtual bool isApOpen();
int8_t signalStrength() override;
String localIP() override;
String BSSIDstr() override;
private:
Preferences* _preferences;
void onDisconnected();
void onNetworkEvent(arduino_event_id_t event, arduino_event_info_t info);

View File

@@ -1,13 +1,6 @@
#pragma once
#include "IPConfiguration.h"
enum class ReconnectStatus
{
Failure = 0,
Success = 1,
CriticalFailure = 2
};
class NetworkDevice
{
public:
@@ -19,11 +12,12 @@ public:
virtual const String deviceName() const = 0;
virtual void initialize() = 0;
virtual ReconnectStatus reconnect(bool force = false) = 0;
virtual void reconfigure() = 0;
virtual void update();
virtual void scan(bool passive = false, bool async = true) = 0;
virtual bool isConnected() = 0;
virtual bool isApOpen() = 0;
virtual int8_t signalStrength() = 0;
virtual String localIP() = 0;

View File

@@ -1,17 +1,14 @@
#include "esp_wifi.h"
#include <WiFi.h>
#include "WifiDevice.h"
#include "../PreferencesKeys.h"
#include "../Logger.h"
#include "../RestartReason.h"
RTC_NOINIT_ATTR char WiFiDevice_reconfdetect[17];
WifiDevice::WifiDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration)
: NetworkDevice(hostname, ipConfiguration),
_preferences(preferences),
_wm(preferences->getString(preference_cred_user, "").c_str(), preferences->getString(preference_cred_password, "").c_str())
_preferences(preferences)
{
_startAp = strcmp(WiFiDevice_reconfdetect, "reconfigure_wifi") == 0;
}
const String WifiDevice::deviceName() const
@@ -21,123 +18,399 @@ const String WifiDevice::deviceName() const
void WifiDevice::initialize()
{
std::vector<const char *> wm_menu;
wm_menu.push_back("wifi");
wm_menu.push_back("exit");
_wm.setEnableConfigPortal(_startAp || !_preferences->getBool(preference_network_wifi_fallback_disabled, false));
// reduced timeout if ESP is set to restart on disconnect
_wm.setFindBestRSSI(_preferences->getBool(preference_find_best_rssi));
_wm.setConnectTimeout(20);
_wm.setConfigPortalTimeout(_preferences->getBool(preference_restart_on_disconnect, false) ? 60 * 3 : 60 * 30);
_wm.setShowInfoUpdate(false);
_wm.setMenu(wm_menu);
_wm.setHostname(_hostname);
String ssid = _preferences->getString(preference_wifi_ssid, "");
String pass = _preferences->getString(preference_wifi_pass, "");
WiFi.setHostname(_hostname.c_str());
if(!_ipConfiguration->dhcpEnabled())
{
_wm.setSTAStaticIPConfig(_ipConfiguration->ipAddress(), _ipConfiguration->defaultGateway(), _ipConfiguration->subnet(), _ipConfiguration->dnsServer());
}
_wm.setAPCallback(clearRtcInitVar);
bool res = false;
bool connectedFromPortal = false;
if(_startAp)
{
Log->println(F("Opening Wi-Fi configuration portal."));
res = _wm.startConfigPortal();
connectedFromPortal = true;
}
else
{
res = _wm.autoConnect(); // password protected ap
}
if(!res)
{
esp_wifi_disconnect();
esp_wifi_stop();
esp_wifi_deinit();
Log->println(F("Failed to connect. Wait for ESP restart."));
delay(1000);
restartEsp(RestartReason::WifiInitFailed);
}
else {
Log->print(F("Wi-Fi connected: "));
Log->println(WiFi.localIP().toString());
if(connectedFromPortal)
{
Log->println(F("Connected using WifiManager portal. Wait for ESP restart."));
delay(1000);
restartEsp(RestartReason::ConfigurationUpdated);
}
WiFi.config(_ipConfiguration->ipAddress(), _ipConfiguration->dnsServer(), _ipConfiguration->defaultGateway(), _ipConfiguration->subnet());
}
WiFi.onEvent([&](WiFiEvent_t event, WiFiEventInfo_t info)
{
if(event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED)
{
onDisconnected();
if(!_openAP && !_connecting && _connected)
{
onDisconnected();
}
}
else if(event == ARDUINO_EVENT_WIFI_STA_GOT_IP)
else if(event == ARDUINO_EVENT_WIFI_STA_CONNECTED)
{
onConnected();
}
else if(event == ARDUINO_EVENT_WIFI_SCAN_DONE)
{
Log->println(F("Wi-Fi scan done"));
_foundNetworks = WiFi.scanComplete();
for (int i = 0; i < _foundNetworks; i++)
{
Log->println(String(F("SSID ")) + WiFi.SSID(i) + String(F(" found with RSSI: ")) +
String(WiFi.RSSI(i)) + String(F("(")) +
String(constrain((100.0 + WiFi.RSSI(i)) * 2, 0, 100)) +
String(F(" %) and BSSID: ")) + WiFi.BSSIDstr(i) +
String(F(" and channel: ")) + String(WiFi.channel(i)));
}
if (_connectOnScanDone && _foundNetworks > 0)
{
connect();
}
else if (_connectOnScanDone)
{
Log->println("No networks found, restarting scan");
scan(false, true);
}
else if (_openAP)
{
openAP();
}
else if(_convertOldWiFi)
{
_convertOldWiFi = false;
_preferences->putBool(preference_wifi_converted, true);
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");
}
String tempSSID = String(reinterpret_cast<const char*>(wifi_cfg.sta.ssid));
String tempPass = String(reinterpret_cast<const char*>(wifi_cfg.sta.password));
tempSSID.trim();
tempPass.trim();
bool found = false;
for (int i = 0; i < _foundNetworks; i++)
{
if(tempSSID.length() > 0 && tempSSID == WiFi.SSID(i) && tempPass.length() > 0)
{
ssid = tempSSID;
pass = tempPass;
_preferences->putString(preference_wifi_ssid, ssid);
_preferences->putString(preference_wifi_pass, pass);
found = true;
break;
}
}
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");
}
if(found)
{
Log->println(String("Attempting to connect to saved SSID ") + String(ssid));
_connectOnScanDone = true;
_openAP = false;
scan(false, true);
}
else
{
Log->println("No SSID or Wifi password saved, opening AP");
_connectOnScanDone = false;
_openAP = true;
scan(false, true);
}
}
}
});
ssid.trim();
pass.trim();
if(ssid.length() > 0 && ssid != "~" && pass.length() > 0)
{
Log->println(String("Attempting to connect to saved SSID ") + String(ssid));
_connectOnScanDone = true;
_openAP = false;
scan(false, true);
}
else
{
if(!_preferences->getBool(preference_wifi_converted, false))
{
_connectOnScanDone = false;
_openAP = false;
_convertOldWiFi = true;
scan(false, true);
}
ssid.trim();
pass.trim();
if(ssid.length() > 0 && ssid != "~" && pass.length() > 0)
{
Log->println(String("Attempting to connect to saved SSID ") + String(ssid));
_connectOnScanDone = true;
_openAP = false;
scan(false, true);
}
else
{
Log->println("No SSID or Wifi password saved, opening AP");
_connectOnScanDone = false;
_openAP = true;
scan(false, true);
}
}
}
void WifiDevice::scan(bool passive, bool async)
{
WiFi.scanDelete();
if(async)
{
Log->println(F("Wi-Fi async scan started"));
}
else
{
Log->println(F("Wi-Fi sync scan started"));
}
if(passive)
{
WiFi.scanNetworks(async,false,true,75U);
}
else
{
WiFi.scanNetworks(async);
}
}
void WifiDevice::openAP()
{
if(_startAP)
{
WiFi.persistent(false);
WiFi.mode(WIFI_AP_STA);
WiFi.persistent(false);
WiFi.softAPsetHostname(_hostname.c_str());
WiFi.softAP("NukiHub", "NukiHubESP32");
WiFi.persistent(false);
_startAP = false;
}
}
bool WifiDevice::connect()
{
bool ret = false;
String ssid = _preferences->getString(preference_wifi_ssid, "");
String pass = _preferences->getString(preference_wifi_pass, "");
WiFi.persistent(false);
WiFi.mode(WIFI_STA);
WiFi.setHostname(_hostname.c_str());
delay(500);
int bestConnection = -1;
for (int i = 0; i < _foundNetworks; i++)
{
if (ssid == WiFi.SSID(i))
{
Log->println(String(F("Saved SSID ")) + ssid + String(F(" found with RSSI: ")) +
String(WiFi.RSSI(i)) + String(F("(")) +
String(constrain((100.0 + WiFi.RSSI(i)) * 2, 0, 100)) +
String(F(" %) and BSSID: ")) + WiFi.BSSIDstr(i) +
String(F(" and channel: ")) + String(WiFi.channel(i)));
if (bestConnection == -1)
{
bestConnection = i;
}
else
{
if (WiFi.RSSI(i) > WiFi.RSSI(bestConnection))
{
bestConnection = i;
}
}
}
}
if (bestConnection == -1)
{
Log->print("No network found with SSID: ");
Log->println(ssid);
if(_preferences->getBool(preference_restart_on_disconnect, false) && ((esp_timer_get_time() / 1000) > 60000)) restartEsp(RestartReason::RestartOnDisconnectWatchdog);
_connectOnScanDone = true;
_openAP = false;
scan(false, true);
return false;
}
else
{
_connecting = true;
Log->println(String(F("Trying to connect to SSID ")) + ssid + String(F(" found with RSSI: ")) +
String(WiFi.RSSI(bestConnection)) + String(F("(")) +
String(constrain((100.0 + WiFi.RSSI(bestConnection)) * 2, 0, 100)) +
String(F(" %) and BSSID: ")) + WiFi.BSSIDstr(bestConnection) +
String(F(" and channel: ")) + String(WiFi.channel(bestConnection)));
ret = WiFi.begin(ssid.c_str(), pass.c_str(), WiFi.channel(bestConnection), WiFi.BSSID(bestConnection), true);
WiFi.persistent(false);
_connecting = false;
}
if(!ret)
{
int loop = 0;
while(!isConnected() && loop < 200)
{
loop++;
delay(100);
}
if(!isConnected())
{
esp_wifi_disconnect();
esp_wifi_stop();
esp_wifi_deinit();
Log->println(F("Failed to connect. Wait for ESP restart."));
delay(1000);
restartEsp(RestartReason::WifiInitFailed);
}
}
else
{
if(!_preferences->getBool(preference_wifi_converted, false))
{
_preferences->putBool(preference_wifi_converted, true);
}
int loop = 0;
while(!isConnected() && loop < 200)
{
loop++;
delay(100);
}
if(!isConnected())
{
if(_preferences->getBool(preference_restart_on_disconnect, false) && ((esp_timer_get_time() / 1000) > 60000))
{
restartEsp(RestartReason::RestartOnDisconnectWatchdog);
return false;
}
Log->print("Connection failed, retrying");
_connectOnScanDone = true;
_openAP = false;
scan(false, true);
return false;
}
}
return ret;
}
void WifiDevice::reconfigure()
{
strcpy(WiFiDevice_reconfdetect, "reconfigure_wifi");
bool changed = false;
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");
}
if(sizeof(wifi_cfg.sta.ssid) > 0)
{
memset(wifi_cfg.sta.ssid, 0, sizeof(wifi_cfg.sta.ssid));
changed = true;
}
if(sizeof(wifi_cfg.sta.password) > 0)
{
memset(wifi_cfg.sta.password, 0, sizeof(wifi_cfg.sta.password));
changed = true;
}
if(changed)
{
if (esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) {
Log->println("Failed to clear NVS Wi-Fi configuration");
}
}
_preferences->putString(preference_wifi_ssid, "");
_preferences->putString(preference_wifi_pass, "");
delay(200);
restartEsp(RestartReason::ReconfigureWifi);
}
bool WifiDevice::isConnected()
{
return WiFi.isConnected();
}
ReconnectStatus WifiDevice::reconnect(bool force)
{
_wm.setFindBestRSSI(_preferences->getBool(preference_find_best_rssi));
if((!isConnected() || force) && !_isReconnecting)
{
_isReconnecting = true;
WiFi.disconnect();
int loop = 0;
while(isConnected() && loop <20)
{
delay(100);
loop++;
}
_wm.resetScan();
_wm.autoConnect();
_isReconnecting = false;
}
if(!isConnected() && _disconnectTs > (esp_timer_get_time() / 1000) - 120000) _wm.setEnableConfigPortal(_startAp || !_preferences->getBool(preference_network_wifi_fallback_disabled, false));
return isConnected() ? ReconnectStatus::Success : ReconnectStatus::Failure;
return (WiFi.status() == WL_CONNECTED);
}
void WifiDevice::onConnected()
{
_isReconnecting = false;
_wm.setEnableConfigPortal(_startAp || !_preferences->getBool(preference_network_wifi_fallback_disabled, false));
Log->println(F("Wi-Fi connected"));
_connectedChannel = WiFi.channel();
_connectedBSSID = WiFi.BSSID();
_connected = true;
}
void WifiDevice::onDisconnected()
{
_disconnectTs = (esp_timer_get_time() / 1000);
if(_preferences->getBool(preference_restart_on_disconnect, false) && ((esp_timer_get_time() / 1000) > 60000)) restartEsp(RestartReason::RestartOnDisconnectWatchdog);
_wm.setEnableConfigPortal(false);
reconnect();
if(_connected)
{
_connected = false;
_disconnectTs = (esp_timer_get_time() / 1000);
Log->println(F("Wi-Fi disconnected"));
//QUICK RECONNECT
_connecting = true;
String ssid = _preferences->getString(preference_wifi_ssid, "");
String pass = _preferences->getString(preference_wifi_pass, "");
WiFi.begin(ssid.c_str(), pass.c_str(), _connectedChannel, _connectedBSSID, true);
WiFi.persistent(false);
int loop = 0;
while(!isConnected() && loop < 50)
{
loop++;
delay(100);
}
_connecting = false;
//END QUICK RECONECT
if(!isConnected())
{
if(_preferences->getBool(preference_restart_on_disconnect, false) && ((esp_timer_get_time() / 1000) > 60000)) restartEsp(RestartReason::RestartOnDisconnectWatchdog);
WiFi.persistent(false);
WiFi.disconnect(true);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(500);
wifi_mode_t wifiMode;
esp_wifi_get_mode(&wifiMode);
while (wifiMode != WIFI_MODE_STA || WiFi.status() == WL_CONNECTED)
{
delay(500);
Log->println(F("Waiting for WiFi mode change or disconnection."));
esp_wifi_get_mode(&wifiMode);
}
_connectOnScanDone = true;
_openAP = false;
scan(false, true);
}
}
}
int8_t WifiDevice::signalStrength()
@@ -155,7 +428,7 @@ String WifiDevice::BSSIDstr()
return WiFi.BSSIDstr();
}
void WifiDevice::clearRtcInitVar(WiFiManager *)
bool WifiDevice::isApOpen()
{
memset(WiFiDevice_reconfdetect, 0, sizeof WiFiDevice_reconfdetect);
return _openAP;
}

View File

@@ -4,7 +4,6 @@
#include <NetworkClientSecure.h>
#include <Preferences.h>
#include "NetworkDevice.h"
#include "WiFiManager.h"
#include "IPConfiguration.h"
class WifiDevice : public NetworkDevice
@@ -16,25 +15,32 @@ public:
virtual void initialize();
virtual void reconfigure();
virtual ReconnectStatus reconnect(bool force = false);
virtual void scan(bool passive = false, bool async = true);
virtual bool isConnected();
virtual bool isApOpen();
int8_t signalStrength() override;
String localIP() override;
String BSSIDstr() override;
private:
static void clearRtcInitVar(WiFiManager*);
void openAP();
void onDisconnected();
void onConnected();
bool connect();
WiFiManager _wm;
Preferences* _preferences = nullptr;
bool _startAp = false;
bool _isReconnecting = false;
int _foundNetworks = 0;
int _disconnectCount = 0;
bool _connectOnScanDone = false;
bool _connecting = false;
bool _openAP = false;
bool _startAP = true;
bool _convertOldWiFi = false;
bool _connected = false;
uint8_t _connectedChannel = 0;
uint8_t* _connectedBSSID;
int64_t _disconnectTs = 0;
};

View File

@@ -24,7 +24,8 @@ board_build.embed_txtfiles =
build_type = release
custom_build = release
board_build.partitions = partitions.csv
build_unflags =
build_unflags =
-DESP32
-Werror=all
-Wall
build_flags =
@@ -54,7 +55,7 @@ lib_ignore =
WiFiProv
lib_deps =
PsychicHttp=symlink://../lib/PsychicHttp
WiFiManager=symlink://../lib/WiFiManager
ArduinoJson=symlink://../lib/ArduinoJson
monitor_speed = 115200
monitor_filters =