diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index c790a823..ee7198f5 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -842,7 +842,7 @@ #endif #ifndef OTA_WEB_SUPPORT -#define OTA_WEB_SUPPORT 1 // Support `/upgrade` endpoint and WebUI OTA handler +#define OTA_WEB_SUPPORT WEB_SUPPORT // Support `/upgrade` endpoint and WebUI OTA handler #endif #define OTA_GITHUB_FP "CA:06:F5:6B:25:8B:7A:0D:4F:2B:05:47:09:39:47:86:51:15:19:84" diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 90f1929a..04078080 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -81,7 +81,8 @@ //#define BUTTON_SUPPORT 0 // don't need / have buttons //#define LED_SUPPORT 0 // don't need wifi indicator //#define RELAY_SUPPORT 0 // don't need to preserve pin state between resets - //#define OTA_ARDUINOOTA_SUPPORT 0 // when only using `ota` command + //#define OTA_ARDUINOOTA_SUPPORT 0 // when only using the `ota` command + //#define OTA_WEB_SUPPORT 0 // //#define MDNS_SERVER_SUPPORT 0 // //#define TELNET_SUPPORT 0 // when only using espota.py //#define TERMINAL_SUPPORT 0 // diff --git a/code/espurna/ota.cpp b/code/espurna/ota.cpp index c230e2bd..26aae63f 100644 --- a/code/espurna/ota.cpp +++ b/code/espurna/ota.cpp @@ -36,6 +36,10 @@ bool otaFinalize(size_t size, CustomResetReason reason, bool evenIfRemaining) { return false; } +bool otaFinalize(size_t size, CustomResetReason reason) { + return otaFinalize(size, reason, false); +} + // Helper methods from UpdaterClass that need to be called manually for async mode, // because we are not using Stream interface to feed it data. bool otaVerifyHeader(uint8_t* data, size_t len) { @@ -82,6 +86,11 @@ void otaProgress(size_t bytes, size_t each) { } } +void otaProgress(size_t bytes) { + constexpr size_t Each { 8192 }; + otaProgress(bytes, Each); +} + void otaSetup() { // Some magic to allow seamless Tasmota OTA upgrades // - inject dummy data sequence that is expected to hold current version info @@ -117,7 +126,10 @@ void otaSetup() { } #if OTA_ARDUINOOTA_SUPPORT - arduinoOtaSetup(); + otaArduinoSetup(); +#endif +#if !WEB_SUPPORT && OTA_WEB_SUPPORT + otaWebSetup(); #endif #if OTA_CLIENT != OTA_CLIENT_NONE otaClientSetup(); diff --git a/code/espurna/ota.h b/code/espurna/ota.h index a5ccfc89..936e6eac 100644 --- a/code/espurna/ota.h +++ b/code/espurna/ota.h @@ -8,36 +8,22 @@ OTA MODULE #include "espurna.h" -#if OTA_WEB_SUPPORT +// Main entrypoint for basic OTA methods +// (like clients, arduinoota and basic web) +void otaSetup(); void otaWebSetup(); - -#endif // OTA_WEB_SUPPORT == 1 - -#if OTA_ARDUINOOTA_SUPPORT - -void arduinoOtaSetup(); - -#endif // OTA_ARDUINOOTA_SUPPORT == 1 - -#if OTA_CLIENT == OTA_CLIENT_ASYNCTCP - +void otaArduinoSetup(); void otaClientSetup(); - -#endif // OTA_CLIENT == OTA_CLIENT_ASYNCTCP - -#if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE - void otaClientSetup(); -#endif // OTA_CLIENT == OTA_CLIENT_HTTPUPDATE - -void otaSetup(); -void otaPrintError(); -bool otaFinalize(size_t size, CustomResetReason reason, bool evenIfRemaining = false); - // Helper methods from UpdaterClass that need to be called manually for async mode, // because we are not using Stream interface to feed it data. bool otaVerifyHeader(uint8_t* data, size_t len); -void otaProgress(size_t bytes, size_t each = 8192u); +void otaProgress(size_t bytes, size_t each); +void otaProgress(size_t bytes); + +void otaPrintError(); +bool otaFinalize(size_t size, CustomResetReason reason, bool evenIfRemaining); +bool otaFinalize(size_t size, CustomResetReason reason); diff --git a/code/espurna/ota_arduinoota.cpp b/code/espurna/ota_arduinoota.cpp index d2fca39f..c38a028c 100644 --- a/code/espurna/ota_arduinoota.cpp +++ b/code/espurna/ota_arduinoota.cpp @@ -121,7 +121,7 @@ void setup() { } // namespace arduino } // namespace ota -void arduinoOtaSetup() { +void otaArduinoSetup() { ota::arduino::setup(); } diff --git a/code/espurna/ota_basicweb.cpp b/code/espurna/ota_basicweb.cpp new file mode 100644 index 00000000..19536f48 --- /dev/null +++ b/code/espurna/ota_basicweb.cpp @@ -0,0 +1,197 @@ +/* + +Part of the OTA MODULE + +Copyright (C) 2019-2021 by Maxim Prokhorov + +*/ + +#include "espurna.h" + +#if !WEB_SUPPORT && OTA_WEB_SUPPORT + +// When there's no WEB_SUPPORT, enable a basic server with a form upload and /upgrade endpoint. +// Based on the async-web-server code and the ESP8266HTTPUpdateServer.h bundled with the Core +// effectively repeats what is done in the async variant, but does not involve async-web-server +// (...but, still suffers from a similar std::function API, forcing to self-reference the server object and manage various global state & result objects) +// + +#include "ota.h" + +#include + +namespace ota { +namespace basic_web { +namespace { + +const char HomePage[] PROGMEM = R"( + + + + + + + +
+
+Firmware upgrade +
+ +
+
+ +
+ + + +)"; + +const char UpgradePageHead[] PROGMEM = R"( + + + +)"; + +namespace handlers { +namespace internal { + +struct Result { + template + void set(int code, T&& value) { + _code = code; + _output = std::forward(value); + } + + void setFromUpdate() { + if (Update.hasError()) { + StreamString stream; + Update.printError(stream); + set(500, stream); + } + } + + template + void send(Server& server) { + String output; + output.reserve(_output.length() + sizeof(UpgradePageHead) + 16); + + output.concat(UpgradePageHead, sizeof(UpgradePageHead) - 1); + output += F(""); + output += _output; + output += F(""); + + server.send(_code, PSTR("text/html"), output); + } + + void reset() { + _code = 200; + _output = ""; + } + +private: + int _code { 200 }; + String _output; +}; + +Result result; + +} // namespace internal + +template +void result(Server& server) { + internal::result.send(server); +} + +template +void upload(Server& server) { + auto& upload = server.upload(); + + switch (upload.status) { + + case UPLOAD_FILE_START: { + if (Update.isRunning()) { + server.client().stop(); + return; + } + + internal::result.reset(); + const size_t Available { (ESP.getFreeSketchSpace() - 0x1000ul) & 0xfffff000ul }; + if (!Update.begin(Available, U_FLASH)) { + server.client().stop(); + internal::result.set(500, F("Not enough available space")); + eepromRotate(true); + } + break; + } + + case UPLOAD_FILE_END: + if (otaFinalize(upload.totalSize, CustomResetReason::Ota, true)) { + internal::result.set(200, F("Rebooting...")); + } else { + internal::result.setFromUpdate(); + } + break; + + case UPLOAD_FILE_WRITE: + if (!Update.isRunning()) { + return; + } + + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + internal::result.set(500, F("Error during write()")); + server.client().stop(); + Update.end(); + eepromRotate(true); + return; + } + + break; + + case UPLOAD_FILE_ABORTED: + internal::result.set(500, F("Upload aborted")); + Update.end(); + eepromRotate(true); + break; + + } +} + +template +void home(Server& server) { + server.send_P(200, PSTR("text/html"), HomePage); +} + +} // namespace handlers + +template +void setup(Server& server) { + server.on("/", HTTP_GET, [&]() { + handlers::home(server); + }); + + server.on("/upgrade", HTTP_POST, + [&]() { + handlers::result(server); + }, + [&]() { + handlers::upload(server); + } + ); + + server.begin(); +} + +} // namespace +} // namespace basic_web +} // namespace ota + +void otaWebSetup() { + static ESP8266WebServer server(WEB_PORT); + ota::basic_web::setup(server); + + ::espurnaRegisterLoop([]() { + server.handleClient(); + }); +} + +#endif