From 36f47ade70351e8d5ddbc6ec608b3092427f54bb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 9 Jun 2019 17:03:51 +0200 Subject: [PATCH] Add Captive Portal (#624) * WIP: Captive Portal * Updates * Updates * Lint * Fixes --- esphome/components/captive_portal/__init__.py | 26 +++ .../captive_portal/captive_portal.cpp | 173 ++++++++++++++++++ .../captive_portal/captive_portal.h | 80 ++++++++ esphome/components/captive_portal/index.html | 55 ++++++ esphome/components/captive_portal/lock.svg | 1 + .../components/captive_portal/stylesheet.css | 58 ++++++ .../captive_portal/wifi-strength-1.svg | 1 + .../captive_portal/wifi-strength-2.svg | 1 + .../captive_portal/wifi-strength-3.svg | 1 + .../captive_portal/wifi-strength-4.svg | 1 + .../components/light/addressable_light.cpp | 1 - esphome/components/mqtt/mqtt_component.cpp | 5 - esphome/components/ota/__init__.py | 5 +- esphome/components/ota/ota_component.cpp | 21 +-- esphome/components/time/real_time_clock.cpp | 1 - esphome/components/web_server/__init__.py | 19 +- esphome/components/web_server/web_server.cpp | 100 +--------- esphome/components/web_server/web_server.h | 14 +- .../components/web_server_base/__init__.py | 24 +++ .../web_server_base/web_server_base.cpp | 96 ++++++++++ .../web_server_base/web_server_base.h | 76 ++++++++ esphome/components/wifi/__init__.py | 24 +-- esphome/components/wifi/wifi_component.cpp | 61 +++--- esphome/components/wifi/wifi_component.h | 14 +- .../components/wifi/wifi_component_esp32.cpp | 4 +- .../wifi/wifi_component_esp8266.cpp | 4 +- esphome/core/application.cpp | 14 +- esphome/core/component.cpp | 45 +++-- esphome/core/component.h | 18 +- esphome/core/defines.h | 1 + esphome/core/preferences.cpp | 114 ++++++++---- esphome/core/preferences.h | 33 +++- esphome/core/util.cpp | 44 ----- esphome/core/util.h | 2 - esphome/dashboard/dashboard.py | 8 +- esphome/dashboard/templates/index.html | 2 +- esphome/wizard.py | 16 +- script/ci-custom.py | 9 +- 38 files changed, 846 insertions(+), 326 deletions(-) create mode 100644 esphome/components/captive_portal/__init__.py create mode 100644 esphome/components/captive_portal/captive_portal.cpp create mode 100644 esphome/components/captive_portal/captive_portal.h create mode 100644 esphome/components/captive_portal/index.html create mode 100644 esphome/components/captive_portal/lock.svg create mode 100644 esphome/components/captive_portal/stylesheet.css create mode 100644 esphome/components/captive_portal/wifi-strength-1.svg create mode 100644 esphome/components/captive_portal/wifi-strength-2.svg create mode 100644 esphome/components/captive_portal/wifi-strength-3.svg create mode 100644 esphome/components/captive_portal/wifi-strength-4.svg create mode 100644 esphome/components/web_server_base/__init__.py create mode 100644 esphome/components/web_server_base/web_server_base.cpp create mode 100644 esphome/components/web_server_base/web_server_base.h diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py new file mode 100644 index 0000000000..52885ae449 --- /dev/null +++ b/esphome/components/captive_portal/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import web_server_base +from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID +from esphome.const import CONF_ID +from esphome.core import coroutine_with_priority + +AUTO_LOAD = ['web_server_base'] +DEPENDENCIES = ['wifi'] + +captive_portal_ns = cg.esphome_ns.namespace('captive_portal') +CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(CaptivePortal), + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase), +}).extend(cv.COMPONENT_SCHEMA) + + +@coroutine_with_priority(64.0) +def to_code(config): + paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) + + var = cg.new_Pvariable(config[CONF_ID], paren) + yield cg.register_component(var, config) + cg.add_define('USE_CAPTIVE_PORTAL') diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp new file mode 100644 index 0000000000..1b533b570e --- /dev/null +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -0,0 +1,173 @@ +#include "captive_portal.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/components/wifi/wifi_component.h" + +namespace esphome { +namespace captive_portal { + +static const char *TAG = "captive_portal"; + +void CaptivePortal::handle_index(AsyncWebServerRequest *request) { + AsyncResponseStream *stream = request->beginResponseStream("text/html"); + stream->print(F("")); + stream->print(App.get_name().c_str()); + stream->print(F("")); + stream->print(F("")); + stream->print(F("")); + stream->print(F("

WiFi Networks

")); + + if (request->hasArg("save")) { + stream->print(F("
The ESP will now try to connect to the network...
Please give it some " + "time to connect.
Note: Copy the changed network to your YAML file - the next OTA update will " + "overwrite these settings.
")); + } + + for (auto &scan : wifi::global_wifi_component->get_scan_result()) { + if (scan.get_is_hidden()) + continue; + + stream->print(F("")); + } + + stream->print(F("

WiFi Settings







")); + stream->print(F("

OTA Update

")); + stream->print(F("
")); + request->send(stream); +} +void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { + std::string ssid = request->arg("ssid").c_str(); + std::string psk = request->arg("psk").c_str(); + ESP_LOGI(TAG, "Captive Portal Requested WiFi Settings Change:"); + ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); + ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); + this->override_sta_(ssid, psk); + request->redirect("/?save=true"); +} +void CaptivePortal::override_sta_(const std::string &ssid, const std::string &password) { + CaptivePortalSettings save{}; + strcpy(save.ssid, ssid.c_str()); + strcpy(save.password, password.c_str()); + this->pref_.save(&save); + + wifi::WiFiAP sta{}; + sta.set_ssid(ssid); + sta.set_password(password); + wifi::global_wifi_component->set_sta(sta); +} + +void CaptivePortal::setup() { + // Hash with compilation time + // This ensures the AP override is not applied for OTA + uint32_t hash = fnv1_hash(App.get_compilation_time()); + this->pref_ = global_preferences.make_preference(hash, true); + + CaptivePortalSettings save{}; + if (this->pref_.load(&save)) { + this->override_sta_(save.ssid, save.password); + } +} +void CaptivePortal::start() { + this->base_->init(); + if (!this->initialized_) { + this->base_->add_handler(this); + this->base_->add_ota_handler(); + } + + this->dns_server_ = new DNSServer(); + this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); + IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); + this->dns_server_->start(53, "*", ip); + + this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { + bool not_found = false; + if (!this->active_) { + not_found = true; + } else if (req->host() == wifi::global_wifi_component->wifi_soft_ap_ip().toString()) { + not_found = true; + } + + if (not_found) { + req->send(404, "text/html", "File not found"); + return; + } + + auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().toString(); + req->redirect(url); + }); + + this->initialized_ = true; + this->active_ = true; +} + +const char STYLESHEET_CSS[] PROGMEM = + R"(*{box-sizing:inherit}div,input{padding:5px;font-size:1em}input{width:95%}body{text-align:center;font-family:sans-serif}button{border:0;border-radius:.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;padding:0}.main{text-align:left;display:inline-block;min-width:260px}.network{display:flex;justify-content:space-between;align-items:center}.network-left{display:flex;align-items:center}.network-ssid{margin-bottom:-7px;margin-left:10px}.info{border:1px solid;margin:10px 0;padding:15px 10px;color:#4f8a10;background-color:#dff2bf})"; +const char LOCK_SVG[] PROGMEM = + R"()"; + +void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { + if (req->url() == "/") { + this->handle_index(req); + return; + } else if (req->url() == "/wifisave") { + this->handle_wifisave(req); + return; + } else if (req->url() == "/stylesheet.css") { + req->send_P(200, "text/css", STYLESHEET_CSS); + return; + } else if (req->url() == "/lock.svg") { + req->send_P(200, "image/svg+xml", LOCK_SVG); + return; + } + + AsyncResponseStream *stream = req->beginResponseStream("image/svg+xml"); + stream->print(F("url() == "/wifi-strength-4.svg") { + stream->print(F("3z")); + } else { + if (req->url() == "/wifi-strength-1.svg") { + stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.4")); + } else if (req->url() == "/wifi-strength-2.svg") { + stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.4")); + } else if (req->url() == "/wifi-strength-3.svg") { + stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.")); + } + stream->print(F("4A16.94 16.94 0 0 1 12 5z")); + } + stream->print(F("\"/>")); + req->send(stream); +} +CaptivePortal::CaptivePortal(web_server_base::WebServerBase *base) : base_(base) { global_captive_portal = this; } +float CaptivePortal::get_setup_priority() const { + // Before WiFi + return setup_priority::WIFI + 1.0f; +} + +CaptivePortal *global_captive_portal = nullptr; + +} // namespace captive_portal +} // namespace esphome diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h new file mode 100644 index 0000000000..4b1717d157 --- /dev/null +++ b/esphome/components/captive_portal/captive_portal.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" +#include "esphome/components/web_server_base/web_server_base.h" + +namespace esphome { + +namespace captive_portal { + +struct CaptivePortalSettings { + char ssid[33]; + char password[65]; +} PACKED; // NOLINT + +class CaptivePortal : public AsyncWebHandler, public Component { + public: + CaptivePortal(web_server_base::WebServerBase *base); + void setup() override; + void loop() override { + if (this->dns_server_ != nullptr) + this->dns_server_->processNextRequest(); + } + float get_setup_priority() const override; + void start(); + bool is_active() const { return this->active_; } + void end() { + this->active_ = false; + this->base_->deinit(); + this->dns_server_->stop(); + delete this->dns_server_; + } + + bool canHandle(AsyncWebServerRequest *request) override { + if (!this->active_) + return false; + + if (request->method() == HTTP_GET) { + if (request->url() == "/") + return true; + if (request->url() == "/stylesheet.css") + return true; + if (request->url() == "/wifi-strength-1.svg") + return true; + if (request->url() == "/wifi-strength-2.svg") + return true; + if (request->url() == "/wifi-strength-3.svg") + return true; + if (request->url() == "/wifi-strength-4.svg") + return true; + if (request->url() == "/lock.svg") + return true; + if (request->url() == "/wifisave") + return true; + } + + return false; + } + + void handle_index(AsyncWebServerRequest *request); + + void handle_wifisave(AsyncWebServerRequest *request); + + void handleRequest(AsyncWebServerRequest *req) override; + + protected: + void override_sta_(const std::string &ssid, const std::string &password); + + web_server_base::WebServerBase *base_; + bool initialized_{false}; + bool active_{false}; + ESPPreferenceObject pref_; + DNSServer *dns_server_{nullptr}; +}; + +extern CaptivePortal *global_captive_portal; + +} // namespace captive_portal +} // namespace esphome diff --git a/esphome/components/captive_portal/index.html b/esphome/components/captive_portal/index.html new file mode 100644 index 0000000000..627bf81215 --- /dev/null +++ b/esphome/components/captive_portal/index.html @@ -0,0 +1,55 @@ + + + + + + + {{ App.get_name() }} + + + + +
+

WiFi Networks

+
+ The ESP will now try to connect to the network...
+ Please give it some time to connect.
+ Note: Copy the changed network to your YAML file - the next OTA update will overwrite these settings. +
+ + + +

WiFi Settings

+
+
+
+
+ +
+

+
+ +

OTA Update

+
+ + +
+
+ + diff --git a/esphome/components/captive_portal/lock.svg b/esphome/components/captive_portal/lock.svg new file mode 100644 index 0000000000..743a1cc55a --- /dev/null +++ b/esphome/components/captive_portal/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/captive_portal/stylesheet.css b/esphome/components/captive_portal/stylesheet.css new file mode 100644 index 0000000000..73f82f05f1 --- /dev/null +++ b/esphome/components/captive_portal/stylesheet.css @@ -0,0 +1,58 @@ +* { + box-sizing: inherit; +} + +div, input { + padding: 5px; + font-size: 1em; +} + +input { + width: 95%; +} + +body { + text-align: center; + font-family: sans-serif; +} + +button { + border: 0; + border-radius: 0.3rem; + background-color: #1fa3ec; + color: #fff; + line-height: 2.4rem; + font-size: 1.2rem; + width: 100%; + padding: 0; +} + +.main { + text-align: left; + display: inline-block; + min-width: 260px; +} + +.network { + display: flex; + justify-content: space-between; + align-items: center; +} + +.network-left { + display: flex; + align-items: center; +} + +.network-ssid { + margin-bottom: -7px; + margin-left: 10px; +} + +.info { + border: 1px solid; + margin: 10px 0px; + padding: 15px 10px; + color: #4f8a10; + background-color: #dff2bf; +} diff --git a/esphome/components/captive_portal/wifi-strength-1.svg b/esphome/components/captive_portal/wifi-strength-1.svg new file mode 100644 index 0000000000..189a38193c --- /dev/null +++ b/esphome/components/captive_portal/wifi-strength-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-2.svg b/esphome/components/captive_portal/wifi-strength-2.svg new file mode 100644 index 0000000000..9b4b2d2396 --- /dev/null +++ b/esphome/components/captive_portal/wifi-strength-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-3.svg b/esphome/components/captive_portal/wifi-strength-3.svg new file mode 100644 index 0000000000..44b7532bb7 --- /dev/null +++ b/esphome/components/captive_portal/wifi-strength-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-4.svg b/esphome/components/captive_portal/wifi-strength-4.svg new file mode 100644 index 0000000000..a22b0b8281 --- /dev/null +++ b/esphome/components/captive_portal/wifi-strength-4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index c93392418c..68a303f23d 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -162,7 +162,6 @@ int32_t HOT interpret_index(int32_t index, int32_t size) { } void AddressableLight::call_setup() { - this->setup_internal_(); this->setup(); #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 53c4e89e98..4201d41c44 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -149,9 +149,6 @@ void MQTTComponent::set_availability(std::string topic, std::string payload_avai } void MQTTComponent::disable_availability() { this->set_availability("", "", ""); } void MQTTComponent::call_setup() { - // Call component internal setup. - this->setup_internal_(); - if (this->is_internal()) return; @@ -173,8 +170,6 @@ void MQTTComponent::call_setup() { } void MQTTComponent::call_loop() { - this->loop_internal_(); - if (this->is_internal()) return; diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index e290e57baf..869de777d6 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -22,6 +22,8 @@ def to_code(config): cg.add(var.set_port(config[CONF_PORT])) cg.add(var.set_auth_password(config[CONF_PASSWORD])) + yield cg.register_component(var, config) + if config[CONF_SAFE_MODE]: cg.add(var.start_safe_mode()) @@ -29,6 +31,3 @@ def to_code(config): cg.add_library('Update', None) elif CORE.is_esp32: cg.add_library('Hash', None) - - # Register at end for safe mode - yield cg.register_component(var, config) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index d37a7a0c6a..7a00c5bb41 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -358,7 +358,7 @@ void OTAComponent::start_safe_mode(uint8_t num_attempts, uint32_t enable_time) { this->safe_mode_start_time_ = millis(); this->safe_mode_enable_time_ = enable_time; this->safe_mode_num_attempts_ = num_attempts; - this->rtc_ = global_preferences.make_preference(233825507UL); + this->rtc_ = global_preferences.make_preference(233825507UL, false); this->safe_mode_rtc_value_ = this->read_rtc_(); ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_); @@ -369,19 +369,18 @@ void OTAComponent::start_safe_mode(uint8_t num_attempts, uint32_t enable_time) { ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode."); this->status_set_error(); - network_setup(); - this->call_setup(); + this->set_timeout(enable_time, []() { + ESP_LOGE(TAG, "No OTA attempt made, restarting."); + App.reboot(); + }); + + App.setup(); ESP_LOGI(TAG, "Waiting for OTA attempt."); - uint32_t begin = millis(); - while ((millis() - begin) < enable_time) { - this->call_loop(); - network_tick(); - App.feed_wdt(); - yield(); + + while (true) { + App.loop(); } - ESP_LOGE(TAG, "No OTA attempt made, restarting."); - App.reboot(); } else { // increment counter this->write_rtc_(this->safe_mode_rtc_value_ + 1); diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 81524826be..96722229b1 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -12,7 +12,6 @@ static const char *TAG = "time"; RealTimeClock::RealTimeClock() = default; void RealTimeClock::call_setup() { - this->setup_internal_(); setenv("TZ", this->timezone_.c_str(), 1); tzset(); this->setup(); diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 206fc2c733..ea7b179d1e 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -1,10 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.components import web_server_base +from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import CONF_CSS_URL, CONF_ID, CONF_JS_URL, CONF_PORT -from esphome.core import CORE, coroutine_with_priority +from esphome.core import coroutine_with_priority -DEPENDENCIES = ['network'] -AUTO_LOAD = ['json'] +AUTO_LOAD = ['json', 'web_server_base'] web_server_ns = cg.esphome_ns.namespace('web_server') WebServer = web_server_ns.class_('WebServer', cg.Component, cg.Controller) @@ -14,18 +15,18 @@ CONFIG_SCHEMA = cv.Schema({ cv.Optional(CONF_PORT, default=80): cv.port, cv.Optional(CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"): cv.string, cv.Optional(CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"): cv.string, + + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase), }).extend(cv.COMPONENT_SCHEMA) @coroutine_with_priority(40.0) def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) + + var = cg.new_Pvariable(config[CONF_ID], paren) yield cg.register_component(var, config) - cg.add(var.set_port(config[CONF_PORT])) + cg.add(paren.set_port(config[CONF_PORT])) cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) - - if CORE.is_esp32: - cg.add_library('FS', None) - cg.add_library('ESP Async WebServer', '1.1.1') diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 882af4b995..fe36d6c2ce 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -6,13 +6,6 @@ #include "StreamString.h" -#ifdef ARDUINO_ARCH_ESP32 -#include -#endif -#ifdef ARDUINO_ARCH_ESP8266 -#include -#endif - #include #include @@ -66,7 +59,7 @@ void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; } void WebServer::setup() { ESP_LOGCONFIG(TAG, "Setting up web server..."); - this->server_ = new AsyncWebServer(this->port_); + this->base_->init(); this->events_.onConnect([this](AsyncEventSourceClient *client) { // Configure reconnect timeout @@ -114,91 +107,18 @@ void WebServer::setup() { logger::global_logger->add_on_log_callback( [this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); }); #endif - this->server_->addHandler(this); - this->server_->addHandler(&this->events_); - - this->server_->begin(); + this->base_->add_handler(&this->events_); + this->base_->add_handler(this); + this->base_->add_ota_handler(); this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); }); } void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); - ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); + ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->base_->get_port()); } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } -void WebServer::handle_update_request(AsyncWebServerRequest *request) { - AsyncWebServerResponse *response; - if (!Update.hasError()) { - response = request->beginResponse(200, "text/plain", "Update Successful!"); - } else { - StreamString ss; - ss.print("Update Failed: "); - Update.printError(ss); - response = request->beginResponse(200, "text/plain", ss); - } - response->addHeader("Connection", "close"); - request->send(response); -} - -void report_ota_error() { - StreamString ss; - Update.printError(ss); - ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str()); -} - -void WebServer::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, - size_t len, bool final) { - bool success; - if (index == 0) { - ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str()); - this->ota_read_length_ = 0; -#ifdef ARDUINO_ARCH_ESP8266 - Update.runAsync(true); - success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); -#endif -#ifdef ARDUINO_ARCH_ESP32 - if (Update.isRunning()) - Update.abort(); - success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH); -#endif - if (!success) { - report_ota_error(); - return; - } - } else if (Update.hasError()) { - // don't spam logs with errors if something failed at start - return; - } - - success = Update.write(data, len) == len; - if (!success) { - report_ota_error(); - return; - } - this->ota_read_length_ += len; - - const uint32_t now = millis(); - if (now - this->last_ota_progress_ > 1000) { - if (request->contentLength() != 0) { - float percentage = (this->ota_read_length_ * 100.0f) / request->contentLength(); - ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage); - } else { - ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_); - } - this->last_ota_progress_ = now; - } - - if (final) { - if (Update.end(true)) { - ESP_LOGI(TAG, "OTA update successful!"); - this->set_timeout(100, []() { App.safe_reboot(); }); - } else { - report_ota_error(); - } - } -} - void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/html"); std::string title = App.get_name() + " Web Server"; @@ -248,7 +168,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(F("

See ESPHome Web API for " "REST API documentation.

" - "

OTA Update

OTA Update
" "

Debug Log

"
                   "