Skip to content

Commit aec1951

Browse files
authored
Merge pull request #23 from Thokoop/feature/ux-improvements
2 parents b050f2f + cde49a5 commit aec1951

File tree

10 files changed

+1079
-798
lines changed

10 files changed

+1079
-798
lines changed

src/SplitFlapDisplay.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,18 +160,7 @@ void SplitFlapDisplay::writeChar(char inputChar, float speed) {
160160
moveTo(targetPositions, speed);
161161
}
162162

163-
String sanitizeInput(const String &input) {
164-
String sanitized = input;
165-
166-
// Replace problematic characters
167-
sanitized.replace("'", "'\\'");
168-
sanitized.replace("%", "%%");
169-
170-
return sanitized;
171-
}
172-
173163
void SplitFlapDisplay::writeString(String inputString, float speed, bool centering) {
174-
inputString = sanitizeInput(inputString);
175164
String displayString = inputString.substring(0, numModules);
176165

177166
if (centering) {

src/SplitFlapDisplay.ino

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ JsonSettings settings = JsonSettings("config", {
1818
{"name", JsonSetting("My Display")},
1919
{"mdns", JsonSetting("splitflap")},
2020
{"otaPass", JsonSetting("")},
21-
{"timezone", JsonSetting("Etc/UTC")},
21+
{"timezone", JsonSetting("UTC0")},
22+
{"dateFormat", JsonSetting("{dd}-{mm}-{yy}")},
23+
{"timeFormat", JsonSetting("{HH}:{mm}")},
2224
// Wifi Settings
2325
{"ssid", JsonSetting("")},
2426
{"password", JsonSetting("")},
@@ -82,9 +84,8 @@ void setup() {
8284
splitflapMqtt.setup();
8385
splitflapMqtt.setDisplay(&display);
8486
display.setMqtt(&splitflapMqtt);
85-
display.homeToString("");
8687

87-
display.writeString("OK");
88+
display.homeToString("OK");
8889
delay(250);
8990
display.writeString("");
9091
}
@@ -138,48 +139,33 @@ void multiInputMode() {
138139
void dateMode() {
139140
if (millis() - webServer.getLastCheckDateTime() > webServer.getDateCheckInterval()) {
140141
webServer.setLastCheckDateTime(millis());
141-
String currentDay = webServer.getCurrentDay();
142-
String dayPrefix = webServer.getDayPrefix(3);
143-
144-
String outputString = " ";
145-
switch (display.getNumModules()) {
146-
case 2: outputString = currentDay; break;
147-
case 3: outputString = dayPrefix; break;
148-
case 4: outputString = " " + currentDay + " "; break;
149-
case 5: outputString = dayPrefix + currentDay; break;
150-
case 6: outputString = dayPrefix + " " + currentDay; break;
151-
case 7: outputString = dayPrefix + " " + currentDay; break;
152-
case 8: outputString = dayPrefix + currentDay + webServer.getMonthPrefix(3); break;
153-
default: break;
154-
}
155-
if (outputString != webServer.getWrittenString()) {
156-
display.writeString(outputString, MAX_RPM, webServer.getCentering());
157-
webServer.setWrittenString(outputString);
142+
143+
String format = settings.getString("dateFormat");
144+
String strftimeFormat = convertToStrftime(format);
145+
String result = renderDate(strftimeFormat);
146+
147+
if (result.length() <= display.getNumModules() && result != webServer.getWrittenString()) {
148+
display.writeString(result, MAX_RPM);
149+
webServer.setWrittenString(result);
158150
}
159151
}
160152
}
161153

162154
void timeMode() {
163155
if (millis() - webServer.getLastCheckDateTime() > webServer.getDateCheckInterval()) {
164156
webServer.setLastCheckDateTime(millis());
165-
String currentHour = webServer.getCurrentHour();
166-
String currentMinute = webServer.getCurrentMinute();
167-
String outputString = " ";
168-
169-
switch (display.getNumModules()) {
170-
case 2: outputString = currentMinute; break;
171-
case 3: outputString = " " + currentMinute; break;
172-
case 4: outputString = currentHour + "" + currentMinute; break;
173-
case 5: outputString = currentHour + " " + currentMinute; break;
174-
case 6: outputString = " " + currentHour + " " + currentMinute; break;
175-
case 7: outputString = " " + currentHour + " " + currentMinute + " "; break;
176-
case 8: outputString = " " + currentHour + currentMinute + " "; break;
177-
default: break;
178-
}
179157

180-
if (outputString != webServer.getWrittenString()) {
181-
display.writeString(outputString, MAX_RPM, webServer.getCentering());
182-
webServer.setWrittenString(outputString);
158+
// Get user-friendly format from settings (fallback to "HH:mm")
159+
String userFormat = settings.getString("timeFormat").length() > 0 ? settings.getString("timeFormat") : "HH:mm";
160+
161+
// Convert to strftime-compatible format
162+
String strftimeFormat = convertToStrftime(userFormat);
163+
String result = renderTime(strftimeFormat);
164+
165+
// Write to display if it changed
166+
if (result != webServer.getWrittenString()) {
167+
display.writeString(result, MAX_RPM);
168+
webServer.setWrittenString(result);
183169
}
184170
}
185171
}
@@ -240,3 +226,61 @@ String extractFromCSV(String str, int index) {
240226

241227
return str.substring(startIndex, endIndex);
242228
}
229+
230+
String renderDate(const String &format) {
231+
char buf[64];
232+
time_t now = time(nullptr);
233+
struct tm *timeinfo = localtime(&now);
234+
235+
strftime(buf, sizeof(buf), format.c_str(), timeinfo);
236+
237+
return trimToModuleCount(String(buf), display.getNumModules());
238+
}
239+
240+
String renderTime(const String &format) {
241+
char buf[64];
242+
time_t now = time(nullptr);
243+
struct tm *timeinfo = localtime(&now);
244+
245+
strftime(buf, sizeof(buf), format.c_str(), timeinfo);
246+
247+
return trimToModuleCount(String(buf), display.getNumModules());
248+
}
249+
250+
String trimToModuleCount(const String &str, int maxLen) {
251+
return str.length() > maxLen ? str.substring(0, maxLen) : str;
252+
}
253+
254+
String convertToStrftime(String userFormat) {
255+
struct FormatToken
256+
{
257+
const char *token;
258+
const char *strftime;
259+
};
260+
261+
FormatToken tokens[] = {
262+
// Date formats
263+
{"{yyyy}", "%Y"}, // 4-digit year (e.g. 2025)
264+
{"{dddd}", "%A"}, // Full weekday name (e.g. Monday)
265+
{"{mmmm}", "%B"}, // Full month name (e.g. January)
266+
{"{ddd}", "%a"}, // Abbreviated weekday name (e.g. Mon)
267+
{"{mmm}", "%b"}, // Abbreviated month name (e.g. Apr)
268+
{"{dd}", "%d"}, // 2-digit day of month, zero-padded (01–31)
269+
{"{mm}", "%m"}, // 2-digit month number, zero-padded (01–12)
270+
{"{yy}", "%y"}, // 2-digit year (e.g. 25)
271+
{"{ww}", "%V"}, // ISO 8601 week number (01–53)
272+
{"{D}", "%j"}, // Day of the year (001–366)
273+
274+
// Time formats
275+
{"{HH}", "%H"}, // Hours (24-hour clock, 00–23)
276+
{"{hh}", "%I"}, // Hours (12-hour clock, 01–12)
277+
{"{MM}", "%M"}, // Minutes (00–59)
278+
{"{AMPM}", "%p"}, // AM or PM
279+
};
280+
281+
for (auto &t : tokens) {
282+
userFormat.replace(t.token, t.strftime);
283+
}
284+
285+
return userFormat;
286+
}

src/SplitFlapMqtt.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,43 +37,42 @@ void SplitFlapMqtt::setup() {
3737
void SplitFlapMqtt::connectToMqtt() {
3838
if (! mqttClient.connected()) {
3939
Serial.println("[MQTT] Attempting to connect...");
40-
String clientId = "SplitFlap-" + settings.getString("mdns");
40+
String mdns = settings.getString("mdns");
41+
String name = settings.getString("name");
42+
4143
if (mqttUser.length() > 0) {
42-
mqttClient.connect(clientId.c_str(), mqttUser.c_str(), mqttPass.c_str());
44+
mqttClient.connect(mdns.c_str(), mqttUser.c_str(), mqttPass.c_str());
4345
} else {
44-
mqttClient.connect(clientId.c_str());
46+
mqttClient.connect(mdns.c_str());
4547
}
4648

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

50-
String mdns = settings.getString("mdns");
51-
5252
// clang-format off
5353
String payload_text = "{"
54-
"\"name\":\"" + settings.getString("name") + "\","
55-
"\"unique_id\":\"splitflap_text_" + mdns + "\","
54+
"\"name\":\"Display\","
55+
"\"unique_id\":\"text_" + mdns + "\","
5656
"\"command_topic\":\"" + topic_command + "\","
5757
"\"availability_topic\":\"" + topic_avail + "\","
5858
"\"device\":{"
5959
"\"identifiers\":[\"splitflap_" + mdns + "\"],"
60-
"\"name\":\"" + settings.getString("name") + "\","
60+
"\"name\":\"" + name + "\","
6161
"\"manufacturer\":\"SplitFlap\","
6262
"\"model\":\"SplitFlap Display\","
6363
"\"sw_version\":\"1.0.0\""
6464
"}"
6565
"}";
6666

6767
String payload_sensor = "{"
68-
"\"name\":\"" + settings.getString("name") + " (Sensor)\","
69-
"\"unique_id\":\"splitflap_sensor_" + mdns + "\","
68+
"\"name\":\"Currently Displayed\","
69+
"\"unique_id\":\"sensor_" + mdns + "\","
7070
"\"state_topic\":\"" + topic_state + "\","
7171
"\"availability_topic\":\"" + topic_avail + "\","
72-
"\"device_class\":\"none\","
7372
"\"entity_category\":\"diagnostic\","
7473
"\"device\":{"
7574
"\"identifiers\":[\"splitflap_" + mdns + "\"],"
76-
"\"name\":\"" + settings.getString("name") + "\","
75+
"\"name\":\"" + name + "\","
7776
"\"manufacturer\":\"SplitFlap\","
7877
"\"model\":\"SplitFlap Display\","
7978
"\"sw_version\":\"1.0.0\""
@@ -86,6 +85,7 @@ void SplitFlapMqtt::connectToMqtt() {
8685
mqttClient.publish(topic_state.c_str(), "", true);
8786

8887
mqttClient.publish(topic_config_text.c_str(), payload_text.c_str(), true);
88+
mqttClient.publish(topic_config_sensor.c_str(), payload_sensor.c_str(), true);
8989
} else {
9090
Serial.println("[MQTT] Failed to connect");
9191
}

src/SplitFlapWebServer.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,9 +521,5 @@ String SplitFlapWebServer::decodeURIComponent(String encodedString) {
521521
decodedString.replace("%7D", "}"); // right brace
522522
decodedString.replace("%7E", "~"); // tilde
523523

524-
// Handle percent-encoded values for characters beyond basic ASCII (e.g.,
525-
// extended Unicode)
526-
decodedString.replace("%", "");
527-
528524
return decodedString;
529525
}

0 commit comments

Comments
 (0)