From c67c107f97dbd5d2d9ed37973c3c7fcad2ab34cc Mon Sep 17 00:00:00 2001 From: Dan Suwanseree Date: Fri, 3 Apr 2020 09:51:45 +0700 Subject: [PATCH] Added OTA and MDNS Added OTA update functionality and MDNS transponder. --- Node main module/src/HTTPUpdateServer.cpp | 135 ++++++++++++++++++++++ Node main module/src/HTTPUpdateServer.h | 43 +++++++ Node main module/src/main.cpp | 40 +++++-- 3 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 Node main module/src/HTTPUpdateServer.cpp create mode 100644 Node main module/src/HTTPUpdateServer.h diff --git a/Node main module/src/HTTPUpdateServer.cpp b/Node main module/src/HTTPUpdateServer.cpp new file mode 100644 index 0000000..d5e6bb1 --- /dev/null +++ b/Node main module/src/HTTPUpdateServer.cpp @@ -0,0 +1,135 @@ +/** + * 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(); +} + +/** + * Shared empty String instance + */ +const String HTTPUpdateServer::_emptyString; + +#endif // !ARDUINO_ARCH_ESP32 diff --git a/Node main module/src/HTTPUpdateServer.h b/Node main module/src/HTTPUpdateServer.h new file mode 100644 index 0000000..1cc5d1e --- /dev/null +++ b/Node main module/src/HTTPUpdateServer.h @@ -0,0 +1,43 @@ +/** + * 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; + static const String _emptyString; +}; + +#endif // !ARDUINO_ARCH_ESP32 +#endif // !__HTTP_UPDATE_SERVER_H diff --git a/Node main module/src/main.cpp b/Node main module/src/main.cpp index c34d729..e2c0258 100644 --- a/Node main module/src/main.cpp +++ b/Node main module/src/main.cpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include "HTTPUpdateServer.h" #include #include @@ -16,16 +18,16 @@ // Time is in milliseconds #define LED_TICKER 33 +#define LED_BUILTIN 2 #define BUTTON_PIN 32 #define DEFAULT_UPDATE_INTERVAL 60000 #define LIVE_SENSOR_INTERVAL 1000 #define SETTINGS_FILE "/settings.txt" -#define DATA_TRANSMISSION_TIMEOUT 20 // arbitrary number +#define DATA_TRANSMISSION_TIMEOUT 32 // arbitrary number #define REBOOT_BUTTON_HOLD_DURATION 3000 #define FACTORY_RESET_BUTTON_HOLD_DURATION 10000 - byte modules[MAX_SENSORS]; // array with address listings of connected sensor modules AsyncDelay delay_sensor_update; // delay timer for asynchronous update interval AsyncDelay delay_sensor_view; // 1 second delay for real time sensor viewing @@ -44,6 +46,8 @@ String nodeLEDSetting = "On"; unsigned long currentUpdateRate = DEFAULT_UPDATE_INTERVAL; WebServer server; // HTTP server to serve web UI +HTTPUpdateServer updateServer(true); // OTA update handler, true param is for serial debug +AutoConnectAux update("/update", "Update"); AutoConnect Portal(server); // AutoConnect handler object AutoConnectConfig portalConfig("MainModuleAP", "12345678"); @@ -130,17 +134,17 @@ void asyncBlink(unsigned long ms = 0) // Button input checking function void checkButton(){ - static unsigned long pushedDownTime = NULL; - if (pushedDownTime == NULL && digitalRead(BUTTON_PIN) == LOW){ // Button being pressed + static unsigned long pushedDownTime = 0; + if (pushedDownTime == 0 && digitalRead(BUTTON_PIN) == LOW){ // Button being pressed pushedDownTime = millis(); - }else if (pushedDownTime != NULL && digitalRead(BUTTON_PIN) == LOW){ // Pressing the button, change the LED light according to the pressing time. + }else if (pushedDownTime != 0 && digitalRead(BUTTON_PIN) == LOW){ // Pressing the button, change the LED light according to the pressing time. unsigned int pressingDuration = millis() - pushedDownTime; if (pressingDuration > REBOOT_BUTTON_HOLD_DURATION && pressingDuration < FACTORY_RESET_BUTTON_HOLD_DURATION){ asyncBlink(FACTORY_RESET_BUTTON_HOLD_DURATION - pressingDuration); } } - else if (pushedDownTime != NULL && digitalRead(BUTTON_PIN) == HIGH ){ // Button released, check the pressed duration and peform action + else if (pushedDownTime != 0 && digitalRead(BUTTON_PIN) == HIGH ){ // Button released, check the pressed duration and peform action unsigned int pressingDuration = millis() - pushedDownTime; if (pressingDuration > FACTORY_RESET_BUTTON_HOLD_DURATION){ @@ -153,7 +157,7 @@ void checkButton(){ ESP.restart(); } - pushedDownTime = NULL; + pushedDownTime = 0; } } @@ -618,6 +622,10 @@ void setup() server.on("/save_settings", handle_SaveSettings); server.on("/getJSON", handle_getSensorJSON); + // setup update server + updateServer.setup(&server); + Serial.println("OTA update server started."); + // setup AutoConnect with a configuration portalConfig.title = "Main Module v1.0"; portalConfig.apid = "MainModule-" + String((uint32_t)(ESP.getEfuseMac() >> 32), HEX); @@ -628,6 +636,8 @@ void setup() portalConfig.tickerPort = LED_TICKER; portalConfig.tickerOn = HIGH; Portal.config(portalConfig); + // add update page aux + Portal.join({update}); // load custom page JSON and build pages into memory if(!Portal.load(customPageJSON)) @@ -653,10 +663,24 @@ void setup() Serial.print("IP: "); Serial.println(WiFi.localIP()); Serial.println(); + // initialize MDNS + String mdnshostname = nodeName; + mdnshostname.toLowerCase(); + if(MDNS.begin(mdnshostname.c_str())) + { + MDNS.addService("http","tcp",80); + Serial.println("MDNS transponder started."); + Serial.println("Access at http://" + mdnshostname + ".local"); + } + else + { + Serial.println("MDNS Initialization failed. Service will not be available."); + } + } else { - Serial.println("Connection failed, rebooting."); + Serial.println("Portal initialization failed, rebooting."); ESP.restart(); }