diff --git a/README.md b/README.md index 1f9c0cb..d7d3abb 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ Firmware for the modular Split Flap Display created by [Morgan Manly](https://gi ## Supported boards -| Environment | Processor | Tested Boards | -| -------------------- | ------------- | ------------------------------------------------------------ | -| `esp32_c3` (default) | ESP32-C3FN4 | Teyleten Robot ESP32-C3-SuperMini
Waveshare ESP32-C3-Zero | -| `esp32_s3` | ESP32-S3FH4R2 | Waveshare ESP32-S3-Zero\* | +| Environment | Processor | Tested Boards | +| -------------------- | ------------- | ------------------------------------------------------------------------ | +| `esp32_c3` (default) | ESP32-C3FN4 | Teyleten Robot ESP32-C3-SuperMini
Waveshare ESP32-C3-Zero | +| `esp32_s3` | ESP32-S3FH4R2 | Waveshare ESP32-S3-Zero\*
ESP32-S3 Super Mini\* | \* Requires manually resetting the board into firmware upload mode by holding BOOT, pressing & releasing RESET, then releasing BOOT prior to upload. After uploading is successful, either press & release RESET or power cycle the board to put it in normal operation mode. @@ -46,6 +46,10 @@ Firmware for the modular Split Flap Display created by [Morgan Manly](https://gi 1. Enjoy! +### Using OTA to update the firmware + +On the settings page set an OTA password to enable OTA updatable firmware. Use this same password for your `auth` flag in `platformio.ini`, and then use a device environment with `*_ota` appended (ie `esp32_s3_ota`) to upload a new firmware and/or filesystem + ## Contributing ### Setup diff --git a/platformio.ini b/platformio.ini index 727041f..3248da8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,6 +31,12 @@ build_flags= ; '-D WIFI_SSID="MySsid"' ; hardcoded wifi credentials ; '-D WIFI_PASS="123456"' ; ( for settings development ) +; OTA-specific settings (can be combined with any env) +[env:ota] +upload_protocol=espota +upload_port=splitflap.local +; upload_flags = --auth=yourpassword + [env:esp32_c3] extends=env board=esp32-c3-devkitm-1 @@ -42,6 +48,9 @@ build_flags= '-D ARDUINO_USB_MODE=1' '-D ARDUINO_USB_CDC_ON_BOOT=1' +[env:esp32_c3_ota] +extends=env:esp32_c3, env:ota + [env:esp32_s3] extends=env board=esp32-s3-fh4r2 @@ -51,3 +60,6 @@ build_flags= ${env.build_flags} '-D SERIAL_SPEED=115200' '-D WIFI_TX_POWER=28' + +[env:esp32_s3_ota] +extends=env:esp32_s3, env:ota diff --git a/src/SplitFlapDisplay.ino b/src/SplitFlapDisplay.ino index f8c18f4..bf2eec5 100644 --- a/src/SplitFlapDisplay.ino +++ b/src/SplitFlapDisplay.ino @@ -17,6 +17,7 @@ JsonSettings settings = JsonSettings("config", { // General Settings {"name", JsonSetting("My Display")}, {"mdns", JsonSetting("splitflap")}, + {"otaPass", JsonSetting("")}, {"timezone", JsonSetting("Etc/UTC")}, // Wifi Settings {"ssid", JsonSetting("")}, @@ -59,6 +60,7 @@ void setup() { if (! webServer.connectToWifi()) { webServer.startAccessPoint(); + webServer.enableOta(); webServer.startMDNS(); webServer.startWebServer(); @@ -71,6 +73,7 @@ void setup() { display.writeChar('X'); } } else { + webServer.enableOta(); webServer.startMDNS(); webServer.startWebServer(); @@ -100,10 +103,12 @@ void loop() { default: break; } + webServer.handleOta(); checkConnection(); reconnectIfNeeded(); + webServer.checkRebootRequired(); yield(); } @@ -197,10 +202,12 @@ void reconnectIfNeeded() { display.writeString(""); if (! webServer.connectToWifi()) { webServer.startAccessPoint(); + webServer.enableOta(); webServer.endMDNS(); webServer.startMDNS(); display.writeChar('X'); } else { + webServer.enableOta(); webServer.endMDNS(); webServer.startMDNS(); display.writeString("OK"); diff --git a/src/SplitFlapWebServer.cpp b/src/SplitFlapWebServer.cpp index 160267e..f041e56 100644 --- a/src/SplitFlapWebServer.cpp +++ b/src/SplitFlapWebServer.cpp @@ -14,8 +14,9 @@ #endif SplitFlapWebServer::SplitFlapWebServer(JsonSettings &settings) - : settings(settings), server(80), multiWordDelay(1000), attemptReconnect(false), multiWordCurrentIndex(0), - numMultiWords(0), wifiCheckInterval(1000), connectionMode(0), checkDateInterval(250), centering(1) { + : settings(settings), server(80), multiWordDelay(1000), rebootRequired(false), attemptReconnect(false), + multiWordCurrentIndex(0), numMultiWords(0), wifiCheckInterval(1000), connectionMode(0), checkDateInterval(250), + centering(1) { lastSwitchMultiTime = millis(); } @@ -180,6 +181,63 @@ bool SplitFlapWebServer::loadWiFiCredentials() { return false; // Return false if no credentials were found } +void SplitFlapWebServer::checkRebootRequired() { + if (rebootRequired) { + Serial.println("Reboot required. Restarting..."); + delay(1000); + ESP.restart(); + } +} + +void SplitFlapWebServer::handleOta() { + ArduinoOTA.handle(); +} +void SplitFlapWebServer::enableOta() { + // Skip OTA initialisation if no password is set + if (settings.getString("otaPass") == "") { + return; + } + + ArduinoOTA.setHostname(settings.getString("mdns").c_str()); // otherwise mdns name gets overwritten with default + ArduinoOTA.setPassword(settings.getString("otaPass").c_str()); + + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) { + type = "sketch"; + } else { // U_LITTLEFS + type = "filesystem"; + LittleFS.end(); // Unmount the filesystem before update + } + Serial.println("Start updating " + type); + }) + .onEnd([]() { + Serial.println("\nEnd"); + LittleFS.begin(); // Remount filesystem + }) + .onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }).onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + LittleFS.begin(); // Remount filesystem + if (error == OTA_AUTH_ERROR) { + Serial.println("Auth Failed"); + } else if (error == OTA_BEGIN_ERROR) { + Serial.println("Begin Failed"); + } else if (error == OTA_CONNECT_ERROR) { + Serial.println("Connect Failed"); + } else if (error == OTA_RECEIVE_ERROR) { + Serial.println("Receive Failed"); + } else if (error == OTA_END_ERROR) { + Serial.println("End Failed"); + } + }); + + ArduinoOTA.begin(); + Serial.println("OTA Initialized"); +} + bool SplitFlapWebServer::connectToWifi() { if (loadWiFiCredentials()) { unsigned long startAttemptTime = millis(); @@ -296,6 +354,7 @@ void SplitFlapWebServer::startWebServer() { Serial.println("Received settings update request"); Serial.println(json.as()); + bool rebootRequired = false; bool reconnect = false; JsonDocument response; response["message"] = "Settings saved successfully!"; @@ -308,6 +367,11 @@ void SplitFlapWebServer::startWebServer() { json["ssid"].as() + " network"; } + if (json["otaPass"].is() && json["otaPass"].as() != settings.getString("otaPass")) { + rebootRequired = true; // OTA password change can only be applied by rebooting + response["message"] = "Settings updated successfully, OTA Password has changed. Rebooting..."; + } + if (json["mdns"].is() && json["mdns"].as() != settings.getString("mdns")) { reconnect = true; response["message"] = @@ -338,6 +402,7 @@ void SplitFlapWebServer::startWebServer() { request->send(200, "application/json", response.as()); + this->rebootRequired = rebootRequired; this->attemptReconnect = reconnect; } )); diff --git a/src/SplitFlapWebServer.h b/src/SplitFlapWebServer.h index babfbac..a725527 100644 --- a/src/SplitFlapWebServer.h +++ b/src/SplitFlapWebServer.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,7 @@ class SplitFlapWebServer { SplitFlapWebServer(JsonSettings &settings); void init(); void setTimezone(); + void checkRebootRequired(); // Wifi Connectivity bool loadWiFiCredentials(); @@ -24,6 +26,8 @@ class SplitFlapWebServer { void startWebServer(); void endMDNS(); void startMDNS(); + void enableOta(); + void handleOta(); void startAccessPoint(); void checkWiFi(); unsigned long getLastCheckWifiTime() { return lastCheckWifiTime; } @@ -85,6 +89,7 @@ class SplitFlapWebServer { String inputString; // latest single input from user String writtenString; // string for whatever is currently written to the display + bool rebootRequired; bool attemptReconnect; unsigned long lastCheckWifiTime; int wifiCheckInterval; diff --git a/src/web/settings.html b/src/web/settings.html index ead2ca5..ee0a28f 100644 --- a/src/web/settings.html +++ b/src/web/settings.html @@ -68,6 +68,22 @@

x-text="errors.message" > + + +
+