diff --git a/examples/WebUpdate/HTTPUpdateServer.cpp b/examples/WebUpdate/HTTPUpdateServer.cpp new file mode 100644 index 0000000..26d725f --- /dev/null +++ b/examples/WebUpdate/HTTPUpdateServer.cpp @@ -0,0 +1,130 @@ +/** + * Ported the ESP8266HTTPUpdateServer published by Arduino-core to + * provide the web browser based OTA update on the ESP32 platform. + * @file HTTPUpdateServer.cpp + * @author hieromon@gmail.com + * @version 0.9.10 + * @date 2019-06-06 + * @copyright MIT license. + */ + +#ifdef ARDUINO_ARCH_ESP32 +// This class will available only EPS32 actually. + +#include +#include +#include +#include +#include +#include +#include "StreamString.h" +#include "HTTPUpdateServer.h" + +static const char serverIndex[] PROGMEM = R"( + +
+ + +
+ +)"; + +static const char successResponse[] PROGMEM = +"Update Success! Rebooting...\n"; + +/** + * Setup for the web update. Register the authentication procedure and + * binary file upload handler required to update the actual sketch binary by OTA. + * @param server A pointer to the WebServer instance + * @param path URI of the update handler + * @param username Username for authentication + * @arama password Password for authentication + */ +void HTTPUpdateServer::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 = String(); + 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.println("Unauthenticated Update"); + return; + } + + if (_serial_output) + Serial.printf("Update: %s\n", upload.filename.c_str()); + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if (!Update.begin(maxSketchSpace)) { //start with max available size + _setUpdaterError(); + } + } + else if (_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()) { + if (_serial_output) + Serial.print('.'); + 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); + }); +} + +/** + * Convert the update error code to string for notation. + */ +void HTTPUpdateServer::_setUpdaterError() { + if (_serial_output) + Update.printError(Serial); + StreamString str; + Update.printError(str); + _updaterError = str.c_str(); +} + +#endif // !ARDUINO_ARCH_ESP32 diff --git a/examples/WebUpdate/HTTPUpdateServer.h b/examples/WebUpdate/HTTPUpdateServer.h new file mode 100644 index 0000000..df3d672 --- /dev/null +++ b/examples/WebUpdate/HTTPUpdateServer.h @@ -0,0 +1,42 @@ +/** + * Ported the ESP8266HTTPUpdateServer published by Arduino-core to + * provide the web browser based OTA update on the ESP32 platform. + * @file HTTPUpdateServer.h + * @author hieromon@gmail.com + * @version 0.9.10 + * @date 2019-06-06 + * @copyright MIT license. + */ + +#ifndef __HTTP_UPDATE_SERVER_H +#define __HTTP_UPDATE_SERVER_H + +#ifdef ARDUINO_ARCH_ESP32 +// This class will available only EPS32 actually. + +class WebServer; + +class HTTPUpdateServer { + public: + explicit HTTPUpdateServer(bool serial_debug = false) : _serial_output(serial_debug), _server(nullptr), _username(emptyString), _password(emptyString), _authenticated(false) {} + ~HTTPUpdateServer() {} + 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); + void updateCredentials(const String& username, const String& password) { _username = username; _password = password; } + + protected: + void _setUpdaterError(); + + private: + bool _serial_output; + WebServer* _server; + String _username; + String _password; + bool _authenticated; + String _updaterError; +}; + +#endif // !ARDUINO_ARCH_ESP32 +#endif // !__HTTP_UPDATE_SERVER_H diff --git a/examples/WebUpdate/WebUpdate.ino b/examples/WebUpdate/WebUpdate.ino index 034ff17..34e22a4 100644 --- a/examples/WebUpdate/WebUpdate.ino +++ b/examples/WebUpdate/WebUpdate.ino @@ -12,19 +12,13 @@ file on the update UI page to update the firmware. Notes: - 1. This example is valid only for ESP8266. In order to apply this - example to ESP32, it is necessary to quote WebUpdate.ino included - in the ESP32 arduino core distribution and implement a class - equivalent to ESP8266HTTPUpdateServer. But it is not included in this - example. - - 2. To experience this example, your client OS needs to be running a + 1. To experience this example, your client OS needs to be running a service that can respond to multicast DNS. For Mac OSX support is built in through Bonjour already. For Linux, install Avahi. For Windows10, available since Windows10 1803(April 2018 Update/RS4). - 3. If you receive an error as follows: + 2. If you receive an error as follows: Update error: ERROR[11]: Invalid bootstrapping state, reset ESP8266 before updating. You need reset the module before sketch running. Refer to https://hieromon.github.io/AutoConnect/faq.html#hang-up-after-reset for details. @@ -32,13 +26,27 @@ This software is released under the MIT License. https://opensource.org/licenses/MIT */ -#ifdef ARDUINO_ARCH_ESP8266 +#if defined(ARDUINO_ARCH_ESP8266) #include -#include #include #include #include +#define HOSTIDENTIFY "esp8266" +#define mDNSUpdate(c) do { c.update(); } while(0) +using WebServerClass = ESP8266WebServer; +using HTTPUpdateServerClass = ESP8266HTTPUpdateServer; +#elif defined(ARDUINO_ARCH_ESP32) +#include +#include +#include +#include "HTTPUpdateServer.h" +#define HOSTIDENTIFY "esp32" +#define mDNSUpdate(c) do {} while(0) +using WebServerClass = WebServer; +using HTTPUpdateServerClass = HTTPUpdateServer; +#endif +#include #include // This page for an example only, you can prepare the other for your application. @@ -64,17 +72,19 @@ static const char AUX_AppPage[] PROGMEM = R"( )"; // Fix hostname for mDNS. It is a requirement for the lightweight update feature. -static const char* host = "esp8266-webupdate"; +static const char* host = HOSTIDENTIFY "-webupdate"; #define HTTP_PORT 80 // ESP8266WebServer instance will be shared both AutoConnect and UpdateServer. -ESP8266WebServer httpServer(HTTP_PORT); +WebServerClass httpServer(HTTP_PORT); +#define USERNAME "user" //*< Replace the actual username you want */ +#define PASSWORD "pass" //*< Replace the actual password you want */ // Declare AutoConnectAux to bind the HTTPWebUpdateServer via /update url // and call it from the menu. // The custom web page is an empty page that does not contain AutoConnectElements. // Its content will be emitted by ESP8266HTTPUpdateServer. -ESP8266HTTPUpdateServer httpUpdater; +HTTPUpdateServerClass httpUpdater; AutoConnectAux update("/update", "Update"); // Declare AutoConnect and the custom web pages for an application sketch. @@ -88,7 +98,7 @@ void setup() { // Prepare the ESP8266HTTPUpdateServer // The /update handler will be registered during this function. - httpUpdater.setup(&httpServer); + httpUpdater.setup(&httpServer, USERNAME, PASSWORD); // Load a custom web page for a sketch and a dummy page for the updater. hello.load(AUX_AppPage); @@ -105,14 +115,10 @@ void setup() { } void loop() { - // Sketch the application here. + // Sketches the application here. // Invokes mDNS::update and AutoConnect::handleClient() for the menu processing. - MDNS.update(); + mDNSUpdate(MDNS); portal.handleClient(); + delay(1); } - -#else -void setup() {} -void loop() {} -#endif