ota: additional code for when the web server support is disabled

Using Core's ESP8266WebServer + some additional handlers.
Includes barebones page, without authorization.

Allows to explicitly set OTA_WEB_SUPPORT=1 with the ESPURNA_CORE and still get a web page.
Around the same size as ArduinoOTA + MDNS, so it might also be a good
suggestion to disable those when web support is enabled.
This commit is contained in:
Maxim Prokhorov
2021-09-03 14:34:07 +03:00
parent 5134cd17c0
commit 56f74cfee5
6 changed files with 224 additions and 28 deletions

View File

@@ -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"

View File

@@ -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 //

View File

@@ -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();

View File

@@ -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);

View File

@@ -121,7 +121,7 @@ void setup() {
} // namespace arduino
} // namespace ota
void arduinoOtaSetup() {
void otaArduinoSetup() {
ota::arduino::setup();
}

View File

@@ -0,0 +1,197 @@
/*
Part of the OTA MODULE
Copyright (C) 2019-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#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 <ESP8266WebServer.h>
namespace ota {
namespace basic_web {
namespace {
const char HomePage[] PROGMEM = R"(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<body>
<form method="POST" action="/upgrade" enctype="multipart/form-data">
<fieldset>
<legend>Firmware upgrade</legend>
<div>
<input type="file" name="filename">
</div>
<div>
<button type="submit">Upload</button>
</div>
</form>
</body>
</html>
)";
const char UpgradePageHead[] PROGMEM = R"(
<head>
<meta http-equiv="refresh" content="5; url=./">
</head>
)";
namespace handlers {
namespace internal {
struct Result {
template <typename T>
void set(int code, T&& value) {
_code = code;
_output = std::forward<T>(value);
}
void setFromUpdate() {
if (Update.hasError()) {
StreamString stream;
Update.printError(stream);
set(500, stream);
}
}
template <typename Server>
void send(Server& server) {
String output;
output.reserve(_output.length() + sizeof(UpgradePageHead) + 16);
output.concat(UpgradePageHead, sizeof(UpgradePageHead) - 1);
output += F("<code>");
output += _output;
output += F("</code>");
server.send(_code, PSTR("text/html"), output);
}
void reset() {
_code = 200;
_output = "";
}
private:
int _code { 200 };
String _output;
};
Result result;
} // namespace internal
template <typename Server>
void result(Server& server) {
internal::result.send(server);
}
template <typename Server>
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 <typename Server>
void home(Server& server) {
server.send_P(200, PSTR("text/html"), HomePage);
}
} // namespace handlers
template <typename Server>
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