Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -8,6 +8,7 @@ Firmware for the modular Split Flap Display created by [Morgan Manly](https://gi
## Features
- Fully 3D Printed Modular Split Flap Display with 37 Characters Per Module
- Small Size, 8 Modules are 320mm, 3 Modules are 130mm Wide. 80mm Tall
- OTA updates
- Fully configurable and controllable via Web Interface
- Switch Between Operation Modes, modes include custom input, date mode, and time mode
- Configure WiFi, Timezone, and hardware settings
Expand All @@ -23,10 +24,10 @@ Firmware for the modular Split Flap Display created by [Morgan Manly](https://gi
- Option to have messages complete at the same time ( delayed start )

## 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 @@ -49,6 +50,9 @@ Firmware for the modular Split Flap Display created by [Morgan Manly](https://gi
* Compiles and uploads the littlefs filesystem ( `npm run pio:filesystem` or `pio run -t uploadfs -e <environment>` )
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"build": "npm run format && npm run assets && npm run pio",
"format": "npm run format:cpp && npm run format:web",
"format:web": "prettier --write 'src/**/*.{js,json,css,html}'",
"format:cpp": "command -v dlang-format >/dev/null 2>&1 && find src -iname '*.h' -o -iname '*.cpp' | xargs clang-format -i || echo 'clang-format not found, skipping format:cpp'",
"format:cpp": "command -v clang-format >/dev/null 2>&1 && find src -iname '*.h' -o -iname '*.cpp' | xargs clang-format -i || echo 'clang-format not found, skipping format:cpp'",
"assets": "vite build --emptyOutDir",
"pio": "npm run pio:firmware && sleep 2 && npm run pio:filesystem",
"pio:firmware": "pio run -t upload",
Expand Down
18 changes: 14 additions & 4 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,31 @@ 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
monitor_speed = 460800
build_flags =
${env.build_flags}
'-D SERIAL_SPEED=460800'
'-D ARDUINO_USB_MODE=1'
'-D ARDUINO_USB_CDC_ON_BOOT=1'
'-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
monitor_speed = 115200
build_flags =
${env.build_flags}
'-D SERIAL_SPEED=115200'
'-D WIFI_TX_POWER=28'
'-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 @@ -12,6 +12,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 @@ -46,6 +47,7 @@ void setup() {

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

Expand All @@ -60,6 +62,7 @@ void setup() {
}
}
else{
webServer.enableOta();
webServer.startMDNS();
webServer.startWebServer();

Expand Down Expand Up @@ -104,10 +107,12 @@ void loop() {
break;
}

webServer.handleOta();
checkConnection();

reconnectIfNeeded();

webServer.checkRebootRequired();
yield();
}

Expand Down Expand Up @@ -245,10 +250,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
73 changes: 70 additions & 3 deletions src/SplitFlapWebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
// Constructor
SplitFlapWebServer::SplitFlapWebServer(JsonSettings &settings)
: settings(settings), server(80), multiWordDelay(1000),
attemptReconnect(false), multiWordCurrentIndex(0), numMultiWords(0),
wifiCheckInterval(1000), connectionMode(0), checkDateInterval(250),
centering(1) {
rebootRequired(false), attemptReconnect(false), multiWordCurrentIndex(0),
numMultiWords(0), wifiCheckInterval(1000), connectionMode(0),
checkDateInterval(250), centering(1) {
lastSwitchMultiTime = millis();
}

Expand Down Expand Up @@ -170,6 +170,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()) {

Expand Down Expand Up @@ -291,6 +348,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 @@ -306,6 +364,14 @@ 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;
Expand All @@ -330,6 +396,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 @@ -7,6 +7,7 @@
#include "LittleFS.h" // use `pio run -t uploadfs` to upload file system
#include "time.h" //for network time protocol
#include <ArduinoJson.h> //for settings json
#include <ArduinoOTA.h>
#include <ESPAsyncWebServer.h> //by ESP32Async, Requires AsyncTCP by ESP32Async
#include <ESPmDNS.h>
#include <WiFi.h>
Expand All @@ -16,6 +17,7 @@ class SplitFlapWebServer {
SplitFlapWebServer(JsonSettings &settings);
void init();
void setTimezone();
void checkRebootRequired();

// Wifi Connectivity
bool loadWiFiCredentials();
Expand All @@ -25,6 +27,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 @@ -89,6 +93,7 @@ class SplitFlapWebServer {
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