Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<br>Waveshare ESP32-C3-Zero |
| `esp32_s3` | ESP32-S3FH4R2 | Waveshare ESP32-S3-Zero<sup>\*</sup> |
| Environment | Processor | Tested Boards |
| -------------------- | ------------- | ------------------------------------------------------------------------ |
| `esp32_c3` (default) | ESP32-C3FN4 | Teyleten Robot ESP32-C3-SuperMini<br>Waveshare ESP32-C3-Zero |
| `esp32_s3` | ESP32-S3FH4R2 | Waveshare ESP32-S3-Zero<sup>\*</sup><br>ESP32-S3 Super Mini<sup>\*</sup> |

<sub>\* 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.</sub>

Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
7 changes: 7 additions & 0 deletions src/SplitFlapDisplay.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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("")},
Expand Down Expand Up @@ -59,6 +60,7 @@ void setup() {

if (! webServer.connectToWifi()) {
webServer.startAccessPoint();
webServer.enableOta();
webServer.startMDNS();
webServer.startWebServer();

Expand All @@ -71,6 +73,7 @@ void setup() {
display.writeChar('X');
}
} else {
webServer.enableOta();
webServer.startMDNS();
webServer.startWebServer();

Expand Down Expand Up @@ -100,10 +103,12 @@ void loop() {
default: break;
}

webServer.handleOta();
checkConnection();

reconnectIfNeeded();

webServer.checkRebootRequired();
yield();
}

Expand Down Expand Up @@ -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");
Expand Down
69 changes: 67 additions & 2 deletions src/SplitFlapWebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -296,6 +354,7 @@ void SplitFlapWebServer::startWebServer() {
Serial.println("Received settings update request");
Serial.println(json.as<String>());

bool rebootRequired = false;
bool reconnect = false;
JsonDocument response;
response["message"] = "Settings saved successfully!";
Expand All @@ -308,6 +367,11 @@ void SplitFlapWebServer::startWebServer() {
json["ssid"].as<String>() + " network";
}

if (json["otaPass"].is<String>() && json["otaPass"].as<String>() != 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<String>() && json["mdns"].as<String>() != settings.getString("mdns")) {
reconnect = true;
response["message"] =
Expand Down Expand Up @@ -338,6 +402,7 @@ void SplitFlapWebServer::startWebServer() {

request->send(200, "application/json", response.as<String>());

this->rebootRequired = rebootRequired;
this->attemptReconnect = reconnect;
}
));
Expand Down
5 changes: 5 additions & 0 deletions src/SplitFlapWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <Arduino.h>
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
#include <ESPAsyncWebServer.h>
#include <ESPmDNS.h>
#include <LittleFS.h>
Expand All @@ -15,6 +16,7 @@ class SplitFlapWebServer {
SplitFlapWebServer(JsonSettings &settings);
void init();
void setTimezone();
void checkRebootRequired();

// Wifi Connectivity
bool loadWiFiCredentials();
Expand All @@ -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; }
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 16 additions & 0 deletions src/web/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ <h1 class="text-2xl font-bold mx-auto" x-text="header"></h1>
x-text="errors.message"
></div>

<label for="otaPass" class="block text-left text-lg mt-4"
>OTA Password (will restart)</label
>
<input
class="w-full p-3 mt-2 text-lg border border-gray-600 rounded-md text-center bg-gray-700 text-gray-100"
type="text"
x-model="settings.otaPass"
placeholder="Enter OTA Password"
/>
<div
class="w-full p-3 mt-2 text-sm text-white bg-red-500 rounded-md"
x-cloak
x-show="errors.key === 'otaPass'"
x-text="errors.message"
></div>

<label for="timezone" class="block text-left text-lg mt-4"
>Timezone</label
>
Expand Down