From 3054bdf5a5d4d9fe79d36d0dd1b287f5a70109f3 Mon Sep 17 00:00:00 2001 From: Hristo Kapanakov Date: Mon, 2 Nov 2020 19:16:23 +0200 Subject: [PATCH] HTTPUpdateServer library (#4244) --- .../examples/WebUpdater/WebUpdater.ino | 51 ++++++ libraries/HTTPUpdateServer/keywords.txt | 20 +++ libraries/HTTPUpdateServer/library.properties | 9 + .../HTTPUpdateServer/src/HTTPUpdateServer.h | 166 ++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 libraries/HTTPUpdateServer/examples/WebUpdater/WebUpdater.ino create mode 100644 libraries/HTTPUpdateServer/keywords.txt create mode 100644 libraries/HTTPUpdateServer/library.properties create mode 100644 libraries/HTTPUpdateServer/src/HTTPUpdateServer.h diff --git a/libraries/HTTPUpdateServer/examples/WebUpdater/WebUpdater.ino b/libraries/HTTPUpdateServer/examples/WebUpdater/WebUpdater.ino new file mode 100644 index 00000000..f2503a7e --- /dev/null +++ b/libraries/HTTPUpdateServer/examples/WebUpdater/WebUpdater.ino @@ -0,0 +1,51 @@ +/* + To upload through terminal you can use: curl -F "image=@firmware.bin" esp32-webupdate.local/update +*/ + +#include +#include +#include +#include +#include + +#ifndef STASSID +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char* host = "esp32-webupdate"; +const char* ssid = STASSID; +const char* password = STAPSK; + +WebServer httpServer(80); +HTTPUpdateServer httpUpdater; + +void setup(void) { + + Serial.begin(115200); + Serial.println(); + Serial.println("Booting Sketch..."); + WiFi.mode(WIFI_AP_STA); + WiFi.begin(ssid, password); + + while (WiFi.waitForConnectResult() != WL_CONNECTED) { + WiFi.begin(ssid, password); + Serial.println("WiFi failed, retrying."); + } + + MDNS.begin(host); + if (MDNS.begin("esp32")) { + Serial.println("mDNS responder started"); + } + + + httpUpdater.setup(&httpServer); + httpServer.begin(); + + MDNS.addService("http", "tcp", 80); + Serial.printf("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", host); +} + +void loop(void) { + httpServer.handleClient(); +} \ No newline at end of file diff --git a/libraries/HTTPUpdateServer/keywords.txt b/libraries/HTTPUpdateServer/keywords.txt new file mode 100644 index 00000000..a6f8a0c1 --- /dev/null +++ b/libraries/HTTPUpdateServer/keywords.txt @@ -0,0 +1,20 @@ +####################################### +# Syntax Coloring Map For HTTPUpdateServer +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP32HTTPUpdateServer KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +setup KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### \ No newline at end of file diff --git a/libraries/HTTPUpdateServer/library.properties b/libraries/HTTPUpdateServer/library.properties new file mode 100644 index 00000000..0601fd57 --- /dev/null +++ b/libraries/HTTPUpdateServer/library.properties @@ -0,0 +1,9 @@ +name=HTTPUpdateServer +version=1.0 +author=Hristo Kapanakov +maintainer= +sentence=Simple HTTP Update server based on the WebServer +paragraph=The library accepts HTTP post requests to the /update url, and updates the ESP32 firmware. +category=Communication +url= +architectures=esp32 \ No newline at end of file diff --git a/libraries/HTTPUpdateServer/src/HTTPUpdateServer.h b/libraries/HTTPUpdateServer/src/HTTPUpdateServer.h new file mode 100644 index 00000000..aa53ce6b --- /dev/null +++ b/libraries/HTTPUpdateServer/src/HTTPUpdateServer.h @@ -0,0 +1,166 @@ +#ifndef __HTTP_UPDATE_SERVER_H +#define __HTTP_UPDATE_SERVER_H + +#include +#include +#include +#include + + +static const char serverIndex[] PROGMEM = +R"( + + + + + + +
+ Firmware:
+ + +
+
+ FileSystem:
+ + +
+ + )"; +static const char successResponse[] PROGMEM = +"Update Success! Rebooting..."; + +class HTTPUpdateServer +{ +public: + HTTPUpdateServer(bool serial_debug=false) { + _serial_output = serial_debug; + _server = NULL; + _username = emptyString; + _password = emptyString; + _authenticated = false; + } + + void setup(WebServer *server) + { + setup(server, emptyString, emptyString); + } + + void setup(WebServer *server, const String& path) + { + setup(server, path, emptyString, emptyString); + } + + void setup(WebServer *server, const String& username, const String& password) + { + setup(server, "/update", username, password); + } + + void setup(WebServer *server, const String& path, const String& username, const String& password) + { + + _server = server; + _username = username; + _password = password; + + // handler for the /update form page + _server->on(path.c_str(), HTTP_GET, [&]() { + if (_username != emptyString && _password != emptyString && !_server->authenticate(_username.c_str(), _password.c_str())) + return _server->requestAuthentication(); + _server->send_P(200, PSTR("text/html"), serverIndex); + }); + + // handler for the /update form POST (once file upload finishes) + _server->on(path.c_str(), HTTP_POST, [&]() { + if (!_authenticated) + return _server->requestAuthentication(); + if (Update.hasError()) { + _server->send(200, F("text/html"), String(F("Update error: ")) + _updaterError); + } + else { + _server->client().setNoDelay(true); + _server->send_P(200, PSTR("text/html"), successResponse); + delay(100); + _server->client().stop(); + ESP.restart(); + } + }, [&]() { + // handler for the file upload, get's the sketch bytes, and writes + // them through the Update object + HTTPUpload& upload = _server->upload(); + + if (upload.status == UPLOAD_FILE_START) { + _updaterError.clear(); + if (_serial_output) + Serial.setDebugOutput(true); + + _authenticated = (_username == emptyString || _password == emptyString || _server->authenticate(_username.c_str(), _password.c_str())); + if (!_authenticated) { + if (_serial_output) + Serial.printf("Unauthenticated Update\n"); + return; + } + + if (_serial_output) + Serial.printf("Update: %s\n", upload.filename.c_str()); + if (upload.name == "filesystem") { + if (!Update.begin(SPIFFS.totalBytes(), U_SPIFFS)) {//start with max available size + if (_serial_output) Update.printError(Serial); + } + } + else { + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if (!Update.begin(maxSketchSpace, U_FLASH)) {//start with max available size + _setUpdaterError(); + } + } + } + else if (_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()) { + if (_serial_output) Serial.printf("."); + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + _setUpdaterError(); + } + } + else if (_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()) { + if (Update.end(true)) { //true to set the size to the current progress + if (_serial_output) Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + } + else { + _setUpdaterError(); + } + if (_serial_output) Serial.setDebugOutput(false); + } + else if (_authenticated && upload.status == UPLOAD_FILE_ABORTED) { + Update.end(); + if (_serial_output) Serial.println("Update was aborted"); + } + delay(0); + }); + } + + void updateCredentials(const String& username, const String& password) + { + _username = username; + _password = password; + } + +protected: + void _setUpdaterError() + { + if (_serial_output) Update.printError(Serial); + StreamString str; + Update.printError(str); + _updaterError = str.c_str(); + } + +private: + bool _serial_output; + WebServer *_server; + String _username; + String _password; + bool _authenticated; + String _updaterError; +}; + + +#endif \ No newline at end of file