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
11 changes: 0 additions & 11 deletions src/SplitFlapDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,7 @@ void SplitFlapDisplay::writeChar(char inputChar, float speed) {
moveTo(targetPositions, speed);
}

String sanitizeInput(const String &input) {
String sanitized = input;

// Replace problematic characters
sanitized.replace("'", "'\\'");
sanitized.replace("%", "%%");

return sanitized;
}

void SplitFlapDisplay::writeString(String inputString, float speed, bool centering) {
inputString = sanitizeInput(inputString);
String displayString = inputString.substring(0, numModules);

if (centering) {
Expand Down
118 changes: 81 additions & 37 deletions src/SplitFlapDisplay.ino
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ JsonSettings settings = JsonSettings("config", {
{"name", JsonSetting("My Display")},
{"mdns", JsonSetting("splitflap")},
{"otaPass", JsonSetting("")},
{"timezone", JsonSetting("Etc/UTC")},
{"timezone", JsonSetting("UTC0")},
{"dateFormat", JsonSetting("{dd}-{mm}-{yy}")},
{"timeFormat", JsonSetting("{HH}:{mm}")},
// Wifi Settings
{"ssid", JsonSetting("")},
{"password", JsonSetting("")},
Expand Down Expand Up @@ -82,9 +84,8 @@ void setup() {
splitflapMqtt.setup();
splitflapMqtt.setDisplay(&display);
display.setMqtt(&splitflapMqtt);
display.homeToString("");

display.writeString("OK");
display.homeToString("OK");
delay(250);
display.writeString("");
}
Expand Down Expand Up @@ -138,48 +139,33 @@ void multiInputMode() {
void dateMode() {
if (millis() - webServer.getLastCheckDateTime() > webServer.getDateCheckInterval()) {
webServer.setLastCheckDateTime(millis());
String currentDay = webServer.getCurrentDay();
String dayPrefix = webServer.getDayPrefix(3);

String outputString = " ";
switch (display.getNumModules()) {
case 2: outputString = currentDay; break;
case 3: outputString = dayPrefix; break;
case 4: outputString = " " + currentDay + " "; break;
case 5: outputString = dayPrefix + currentDay; break;
case 6: outputString = dayPrefix + " " + currentDay; break;
case 7: outputString = dayPrefix + " " + currentDay; break;
case 8: outputString = dayPrefix + currentDay + webServer.getMonthPrefix(3); break;
default: break;
}
if (outputString != webServer.getWrittenString()) {
display.writeString(outputString, MAX_RPM, webServer.getCentering());
webServer.setWrittenString(outputString);

String format = settings.getString("dateFormat");
String strftimeFormat = convertToStrftime(format);
String result = renderDate(strftimeFormat);

if (result.length() <= display.getNumModules() && result != webServer.getWrittenString()) {
display.writeString(result, MAX_RPM);
webServer.setWrittenString(result);
}
}
}

void timeMode() {
if (millis() - webServer.getLastCheckDateTime() > webServer.getDateCheckInterval()) {
webServer.setLastCheckDateTime(millis());
String currentHour = webServer.getCurrentHour();
String currentMinute = webServer.getCurrentMinute();
String outputString = " ";

switch (display.getNumModules()) {
case 2: outputString = currentMinute; break;
case 3: outputString = " " + currentMinute; break;
case 4: outputString = currentHour + "" + currentMinute; break;
case 5: outputString = currentHour + " " + currentMinute; break;
case 6: outputString = " " + currentHour + " " + currentMinute; break;
case 7: outputString = " " + currentHour + " " + currentMinute + " "; break;
case 8: outputString = " " + currentHour + currentMinute + " "; break;
default: break;
}

if (outputString != webServer.getWrittenString()) {
display.writeString(outputString, MAX_RPM, webServer.getCentering());
webServer.setWrittenString(outputString);
// Get user-friendly format from settings (fallback to "HH:mm")
String userFormat = settings.getString("timeFormat").length() > 0 ? settings.getString("timeFormat") : "HH:mm";

// Convert to strftime-compatible format
String strftimeFormat = convertToStrftime(userFormat);
String result = renderTime(strftimeFormat);

// Write to display if it changed
if (result != webServer.getWrittenString()) {
display.writeString(result, MAX_RPM);
webServer.setWrittenString(result);
}
}
}
Expand Down Expand Up @@ -240,3 +226,61 @@ String extractFromCSV(String str, int index) {

return str.substring(startIndex, endIndex);
}

String renderDate(const String &format) {
char buf[64];
time_t now = time(nullptr);
struct tm *timeinfo = localtime(&now);

strftime(buf, sizeof(buf), format.c_str(), timeinfo);

return trimToModuleCount(String(buf), display.getNumModules());
}

String renderTime(const String &format) {
char buf[64];
time_t now = time(nullptr);
struct tm *timeinfo = localtime(&now);

strftime(buf, sizeof(buf), format.c_str(), timeinfo);

return trimToModuleCount(String(buf), display.getNumModules());
}

String trimToModuleCount(const String &str, int maxLen) {
return str.length() > maxLen ? str.substring(0, maxLen) : str;
}

String convertToStrftime(String userFormat) {
struct FormatToken
{
const char *token;
const char *strftime;
};

FormatToken tokens[] = {
// Date formats
{"{yyyy}", "%Y"}, // 4-digit year (e.g. 2025)
{"{dddd}", "%A"}, // Full weekday name (e.g. Monday)
{"{mmmm}", "%B"}, // Full month name (e.g. January)
{"{ddd}", "%a"}, // Abbreviated weekday name (e.g. Mon)
{"{mmm}", "%b"}, // Abbreviated month name (e.g. Apr)
{"{dd}", "%d"}, // 2-digit day of month, zero-padded (01–31)
{"{mm}", "%m"}, // 2-digit month number, zero-padded (01–12)
{"{yy}", "%y"}, // 2-digit year (e.g. 25)
{"{ww}", "%V"}, // ISO 8601 week number (01–53)
{"{D}", "%j"}, // Day of the year (001–366)

// Time formats
{"{HH}", "%H"}, // Hours (24-hour clock, 00–23)
{"{hh}", "%I"}, // Hours (12-hour clock, 01–12)
{"{MM}", "%M"}, // Minutes (00–59)
{"{AMPM}", "%p"}, // AM or PM
};

for (auto &t : tokens) {
userFormat.replace(t.token, t.strftime);
}

return userFormat;
}
24 changes: 12 additions & 12 deletions src/SplitFlapMqtt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,43 +37,42 @@ void SplitFlapMqtt::setup() {
void SplitFlapMqtt::connectToMqtt() {
if (! mqttClient.connected()) {
Serial.println("[MQTT] Attempting to connect...");
String clientId = "SplitFlap-" + settings.getString("mdns");
String mdns = settings.getString("mdns");
String name = settings.getString("name");

if (mqttUser.length() > 0) {
mqttClient.connect(clientId.c_str(), mqttUser.c_str(), mqttPass.c_str());
mqttClient.connect(mdns.c_str(), mqttUser.c_str(), mqttPass.c_str());
} else {
mqttClient.connect(clientId.c_str());
mqttClient.connect(mdns.c_str());
}

if (mqttClient.connected()) {
Serial.println("[MQTT] Connected to broker");

String mdns = settings.getString("mdns");

// clang-format off
String payload_text = "{"
"\"name\":\"" + settings.getString("name") + "\","
"\"unique_id\":\"splitflap_text_" + mdns + "\","
"\"name\":\"Display\","
"\"unique_id\":\"text_" + mdns + "\","
"\"command_topic\":\"" + topic_command + "\","
"\"availability_topic\":\"" + topic_avail + "\","
"\"device\":{"
"\"identifiers\":[\"splitflap_" + mdns + "\"],"
"\"name\":\"" + settings.getString("name") + "\","
"\"name\":\"" + name + "\","
"\"manufacturer\":\"SplitFlap\","
"\"model\":\"SplitFlap Display\","
"\"sw_version\":\"1.0.0\""
"}"
"}";

String payload_sensor = "{"
"\"name\":\"" + settings.getString("name") + " (Sensor)\","
"\"unique_id\":\"splitflap_sensor_" + mdns + "\","
"\"name\":\"Currently Displayed\","
"\"unique_id\":\"sensor_" + mdns + "\","
"\"state_topic\":\"" + topic_state + "\","
"\"availability_topic\":\"" + topic_avail + "\","
"\"device_class\":\"none\","
"\"entity_category\":\"diagnostic\","
"\"device\":{"
"\"identifiers\":[\"splitflap_" + mdns + "\"],"
"\"name\":\"" + settings.getString("name") + "\","
"\"name\":\"" + name + "\","
"\"manufacturer\":\"SplitFlap\","
"\"model\":\"SplitFlap Display\","
"\"sw_version\":\"1.0.0\""
Expand All @@ -86,6 +85,7 @@ void SplitFlapMqtt::connectToMqtt() {
mqttClient.publish(topic_state.c_str(), "", true);

mqttClient.publish(topic_config_text.c_str(), payload_text.c_str(), true);
mqttClient.publish(topic_config_sensor.c_str(), payload_sensor.c_str(), true);
} else {
Serial.println("[MQTT] Failed to connect");
}
Expand Down
4 changes: 0 additions & 4 deletions src/SplitFlapWebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,9 +521,5 @@ String SplitFlapWebServer::decodeURIComponent(String encodedString) {
decodedString.replace("%7D", "}"); // right brace
decodedString.replace("%7E", "~"); // tilde

// Handle percent-encoded values for characters beyond basic ASCII (e.g.,
// extended Unicode)
decodedString.replace("%", "");

return decodedString;
}
Loading