Added Home Assistant brightness endpoint and support static display for short messages (≤8 chars).

This commit is contained in:
M-Factory
2025-11-21 12:04:22 +09:00
parent a306daeb2b
commit 3bee52f6d2
6 changed files with 374 additions and 340 deletions

View File

@@ -75,6 +75,7 @@ int messageDisplaySeconds;
int messageScrollTimes;
unsigned long messageStartTime = 0;
int currentScrollCount = 0;
int currentDisplayCycleCount = 0;
// Dimming
bool dimmingEnabled = false;
@@ -91,7 +92,7 @@ int sunriseMinute = 0;
int sunsetHour = 18;
int sunsetMinute = 0;
//Countdown Globals - NEW
//Countdown Globals
bool countdownEnabled = false;
time_t countdownTargetTimestamp = 0; // Unix timestamp
char countdownLabel[64] = ""; // Label for the countdown
@@ -520,40 +521,6 @@ void connectWiFi() {
}
void clearWiFiCredentialsInConfig() {
DynamicJsonDocument doc(2048);
// Open existing config, if present
File configFile = LittleFS.open("/config.json", "r");
if (configFile) {
DeserializationError err = deserializeJson(doc, configFile);
configFile.close();
if (err) {
Serial.print(F("[SECURITY] Error parsing config.json: "));
Serial.println(err.f_str());
return;
}
}
doc["ssid"] = "";
doc["password"] = "";
// Optionally backup previous config
if (LittleFS.exists("/config.json")) {
LittleFS.rename("/config.json", "/config.bak");
}
File f = LittleFS.open("/config.json", "w");
if (!f) {
Serial.println(F("[SECURITY] ERROR: Cannot write to /config.json to clear credentials!"));
return;
}
serializeJson(doc, f);
f.close();
Serial.println(F("[SECURITY] Cleared WiFi credentials in config.json."));
}
// -----------------------------------------------------------------------------
// Time / NTP Functions
// -----------------------------------------------------------------------------
@@ -985,24 +952,6 @@ void setupWebServer() {
}
});
server.on("/clear_wifi", HTTP_POST, [](AsyncWebServerRequest *request) {
Serial.println(F("[WEBSERVER] Request: /clear_wifi"));
clearWiFiCredentialsInConfig();
DynamicJsonDocument okDoc(128);
okDoc[F("message")] = "✅ WiFi credentials cleared! Rebooting...";
String response;
serializeJson(okDoc, response);
request->send(200, "application/json", response);
request->onDisconnect([]() {
Serial.println(F("[WEBSERVER] Rebooting after clearing WiFi..."));
saveUptime();
delay(100); // ensure file is written
ESP.restart();
});
});
server.on("/ap_status", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.print(F("[WEBSERVER] Request: /ap_status. isAPMode = "));
Serial.println(isAPMode);
@@ -1017,37 +966,49 @@ void setupWebServer() {
request->send(400, "application/json", "{\"error\":\"Missing value\"}");
return;
}
String sourceHeader = request->header("X-Source");
bool isFromUI = (sourceHeader == "UI");
bool isFromHA = !isFromUI;
int newBrightness = request->getParam("value", true)->value().toInt();
// Handle "off" request
// Handle OFF request
if (newBrightness == -1) {
P.displayShutdown(true); // Fully shut down display driver
P.displayShutdown(true);
P.displayClear();
displayOff = true;
Serial.println("[WEBSERVER] Display set to OFF (shutdown mode)");
Serial.printf("[BRIGHTNESS] Display OFF via %s\n",
isFromUI ? "UI" : "HA");
request->send(200, "application/json", "{\"ok\":true, \"display\":\"off\"}");
return;
}
// Clamp brightness to valid range
if (newBrightness < 0) newBrightness = 0;
if (newBrightness > 15) newBrightness = 15;
// Clamp brightness range (015)
newBrightness = constrain(newBrightness, 0, 15);
// Only run robust clear/reset when coming from "off"
if (displayOff) {
// Wake from OFF
P.setIntensity(newBrightness);
advanceDisplayModeSafe();
P.displayShutdown(false);
brightness = newBrightness;
displayOff = false;
Serial.println("[WEBSERVER] Display woke from OFF");
Serial.printf("[BRIGHTNESS] Display woke from OFF via %s → %d\n",
isFromUI ? "UI" : "HA",
newBrightness);
} else {
// Display already on, just set brightness
// Display already ON
brightness = newBrightness;
P.setIntensity(brightness);
Serial.printf("[WEBSERVER] Set brightness to %d\n", brightness);
}
Serial.printf("[BRIGHTNESS] Set to %d via %s\n",
brightness,
isFromUI ? "UI" : "HA");
}
request->send(200, "application/json", "{\"ok\":true}");
});
@@ -1917,7 +1878,7 @@ void fetchWeather() {
if (doc.containsKey(F("main")) && doc[F("main")].containsKey(F("temp"))) {
float temp = doc[F("main")][F("temp")];
currentTemp = String((int)round(temp)) + "º";
currentTemp = String((int)round(temp)) + "°";
Serial.printf("[WEATHER] Temp: %s\n", currentTemp.c_str());
weatherAvailable = true;
} else {
@@ -2814,7 +2775,7 @@ void loop() {
switch (ntpAnimFrame % 3) {
case 0: P.print(F("S Y N C ®")); break;
case 1: P.print(F("S Y N C ¯")); break;
case 2: P.print(F("S Y N C °")); break;
case 2: P.print(F("S Y N C º")); break;
}
ntpAnimFrame++;
}
@@ -3533,108 +3494,161 @@ void loop() {
}
// --- Custom Message Display Mode (displayMode == 6) ---
if (displayMode == 6) {
if (strlen(customMessage) == 0) {
advanceDisplayMode();
yield();
return;
}
// --- CHECK FOR TIMEOUT ---
bool timedOut = false;
// Check if a time limit (messageDisplaySeconds > 0) has been exceeded
if (messageDisplaySeconds > 0 && (millis() - messageStartTime) >= (messageDisplaySeconds * 1000UL)) {
Serial.printf("[MESSAGE] Custom message timed out after %d seconds.\n", messageDisplaySeconds);
timedOut = true;
}
// --- CHECK FOR SCROLL LIMIT BEFORE DISPLAYING ---
bool scrollsComplete = (messageScrollTimes > 0) && (currentScrollCount >= messageScrollTimes);
// --- ADVANCE MODE CHECK (Check if done based on time or scrolls) ---
if (timedOut || scrollsComplete) {
Serial.println(F("[MESSAGE] Custom message finished."));
// Reset common counters, regardless of what happens next
currentScrollCount = 0;
messageStartTime = 0;
// ----------------------------------------------------------------------
// CRITICAL LOGIC: RESTORE PERSISTENT MESSAGE
// ----------------------------------------------------------------------
if (strlen(lastPersistentMessage) > 0) {
// A persistent message exists, restore it to customMessage
strncpy(customMessage, lastPersistentMessage, sizeof(customMessage));
messageScrollSpeed = GENERAL_SCROLL_SPEED; // Persistent messages use global speed
// Clear HA timing/scroll variables (restored persistent message is infinite)
messageDisplaySeconds = 0;
messageScrollTimes = 0;
Serial.printf("[MESSAGE] Restored persistent message: '%s'. Staying in mode 6.\n", customMessage);
// DO NOT advanceDisplayMode() or clear customMessage[0]!
// The function returns, and the next loop cycle will immediately display the restored message.
} else {
// No persistent message to restore. Exit mode 6.
customMessage[0] = '\0'; // Clear the buffer to exit mode 6 in the next loop cycle
Serial.println(F("[MESSAGE] No persistent message to restore. Advancing display mode."));
advanceDisplayMode();
}
yield();
return;
}
String msg = String(customMessage);
// Replace standard digits 09 with your custom font character codes
for (int i = 0; i < msg.length(); i++) {
if (isDigit(msg[i])) {
int num = msg[i] - '0'; // 09
msg[i] = 145 + ((num + 9) % 10); // Maps 0→154, 1→145, ... 9→153
}
}
// --- Determine if we need left padding based on previous mode ---
bool addPadding = false;
bool humidityVisible = showHumidity && weatherAvailable && strlen(openWeatherApiKey) == 32 && strlen(openWeatherCity) > 0 && strlen(openWeatherCountry) > 0;
// If coming from CLOCK mode
if (prevDisplayMode == 0 && (showDayOfWeek || colonBlinkEnabled)) {
addPadding = true;
} else if (prevDisplayMode == 1 && humidityVisible) {
addPadding = true;
}
// Apply padding (4 spaces) if needed
if (addPadding) {
msg = " " + msg;
}
// --- Display scrolling message ---
P.setTextAlignment(PA_LEFT);
P.setCharSpacing(1);
textEffect_t actualScrollDirection = getEffectiveScrollDirection(PA_SCROLL_LEFT, flipDisplay);
extern int messageScrollSpeed;
// START SCROLL CYCLE
P.displayScroll(msg.c_str(), PA_LEFT, actualScrollDirection, messageScrollSpeed);
// BLOCKING WAIT: Completes 1 full scroll, matching your definition of "1 scroll"
while (!P.displayAnimate()) yield();
// SCROLL COUNT INCREMENT
if (messageScrollTimes > 0) {
currentScrollCount++;
Serial.printf("[MESSAGE] Scroll complete. Count: %d/%d\n", currentScrollCount, messageScrollTimes);
}
P.setTextAlignment(PA_CENTER);
// --- Custom Message Display Mode (displayMode == 6) ---
if (displayMode == 6) {
// 1. Initial Check: If message is empty, skip mode 6.
if (strlen(customMessage) == 0) {
advanceDisplayMode();
yield();
return;
}
// --- CHARACTER REPLACEMENT AND PADDING (Common to both short and long) ---
const size_t MAX_NON_SCROLLING_CHARS = 8;
String msg = String(customMessage);
// Replace standard digits 09 with your custom font character codes
for (int i = 0; i < msg.length(); i++) {
if (isDigit(msg[i])) {
int num = msg[i] - '0';
msg[i] = 145 + ((num + 9) % 10);
}
}
// --- CHECK FOR TIMEOUT (Applies to temporary short & long messages) ---
bool timedOut = false;
// Check if a time limit (messageDisplaySeconds > 0) has been exceeded
if (messageDisplaySeconds > 0 && (millis() - messageStartTime) >= (messageDisplaySeconds * 1000UL)) {
Serial.printf("[MESSAGE] HA message timed out after %d seconds.\n", messageDisplaySeconds);
timedOut = true;
}
// --- CHECK FOR SCROLL/CYCLE LIMIT BEFORE DISPLAYING ---
// Scrolls complete applies to long messages.
bool scrollsComplete = (messageScrollTimes > 0) && (currentScrollCount >= messageScrollTimes);
// Cycles complete applies to short messages.
extern int currentDisplayCycleCount; // Use the dedicated short message counter
bool cyclesComplete = (messageScrollTimes > 0) && (currentDisplayCycleCount >= messageScrollTimes);
// --- ADVANCE MODE CHECK (Check if HA parameters are complete) ---
// If either timer or cycle/scroll count is finished, we clean up the temporary message.
if (scrollsComplete || cyclesComplete) {
Serial.println(F("[MESSAGE] HA-controlled message finished."));
// Reset common counters
currentScrollCount = 0;
messageStartTime = 0;
currentDisplayCycleCount = 0; // Reset the cycle counter
// CRITICAL LOGIC: RESTORE PERSISTENT MESSAGE (Exit Mode 6 Logic)
if (strlen(lastPersistentMessage) > 0) {
// A persistent message exists, restore it
strncpy(customMessage, lastPersistentMessage, sizeof(customMessage));
messageScrollSpeed = GENERAL_SCROLL_SPEED;
messageDisplaySeconds = 0;
messageScrollTimes = 0;
Serial.printf("[MESSAGE] Restored persistent message: '%s'. Staying in mode 6.\n", customMessage);
} else {
// No persistent message to restore. Clear the temporary HA message and Exit mode 6.
customMessage[0] = '\0';
Serial.println(F("[MESSAGE] No persistent message to restore. Advancing display mode."));
advanceDisplayMode();
}
yield();
return;
}
// ----------------------------------------------------------------------
// BRANCH A: NON-SCROLLING (Short Message: strlen <= 8)
// ----------------------------------------------------------------------
if (msg.length() <= MAX_NON_SCROLLING_CHARS) {
// Determine the duration: use HA seconds if set, otherwise use weatherDuration.
unsigned long durationMs = (messageDisplaySeconds > 0)
? (messageDisplaySeconds * 1000UL)
: weatherDuration;
// If HA seconds is set, we use the timedOut check at the top.
// If only scrollTimes is set, we still display for weatherDuration before incrementing the cycle count.
Serial.printf("[MESSAGE] Displaying timed short message: '%s' for %lu ms. Advancing mode.\n", customMessage, durationMs);
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
P.print(msg.c_str());
// Block execution for the specified duration (non-HA uses weatherDuration)
unsigned long displayUntil = millis() + durationMs;
while (millis() < displayUntil) {
yield();
}
// --- CYCLE TRACKING FOR SCROLLTIMES ---
// Increment the counter if the HA message is configured to clear by scroll count.
if (messageScrollTimes > 0) {
currentDisplayCycleCount++;
Serial.printf("[MESSAGE] Short message cycle complete. Count: %d/%d\n", currentDisplayCycleCount, messageScrollTimes);
}
// After display, the message content must persist, but the display must cycle.
Serial.println(F("[MESSAGE] Short message duration complete. Advancing display mode."));
advanceDisplayMode();
yield();
return;
}
// ----------------------------------------------------------------------
// BRANCH B: SCROLLING (Long Message: strlen > 8) - (Existing Logic)
// ----------------------------------------------------------------------
// --- Determine if we need left padding based on previous mode ---
bool addPadding = false;
bool humidityVisible = showHumidity && weatherAvailable && strlen(openWeatherApiKey) == 32 && strlen(openWeatherCity) > 0 && strlen(openWeatherCountry) > 0;
// If coming from CLOCK mode
if (prevDisplayMode == 0 && (showDayOfWeek || colonBlinkEnabled)) {
addPadding = true;
} else if (prevDisplayMode == 1 && humidityVisible) {
addPadding = true;
}
// Apply padding (4 spaces) if needed
if (addPadding) {
msg = " " + msg;
}
// --- Display scrolling message ---
P.setTextAlignment(PA_LEFT);
P.setCharSpacing(1);
textEffect_t actualScrollDirection = getEffectiveScrollDirection(PA_SCROLL_LEFT, flipDisplay);
extern int messageScrollSpeed;
// START SCROLL CYCLE
P.displayScroll(msg.c_str(), PA_LEFT, actualScrollDirection, messageScrollSpeed);
// BLOCKING WAIT: Completes 1 full scroll
while (!P.displayAnimate()) yield();
// SCROLL COUNT INCREMENT
if (messageScrollTimes > 0) {
currentScrollCount++;
Serial.printf("[MESSAGE] Scroll complete. Count: %d/%d\n", currentScrollCount, messageScrollTimes);
}
// If no HA parameters are set, this is a persistent/infinite scroll, so advance mode after 1 scroll cycle.
// If HA parameters ARE set, the mode relies on the check at the top to break out.
if (messageDisplaySeconds == 0 && messageScrollTimes == 0) {
P.setTextAlignment(PA_CENTER);
advanceDisplayMode();
}
yield();
return;
}
unsigned long currentMillis = millis();
unsigned long runtimeSeconds = (currentMillis - bootMillis) / 1000;
unsigned long currentTotal = totalUptimeSeconds + runtimeSeconds;

File diff suppressed because one or more lines are too long

View File

@@ -179,7 +179,7 @@ MD_MAX72XX::fontType_t mFactory[] PROGMEM =
0, // 173 - '­'
5, 64, 0, 0, 0, 0, // 174 - '®'
5, 64, 0, 64, 0, 0, // 175 - '¯'
5, 64, 0, 64, 0, 64, // 176 - '°'
3, 4, 10, 4, // 176 - '°'
5, 254, 146, 146, 146, 254, // 177 - '±'
6, 128, 126, 42, 42, 170, 254, // 178 - '²'
7, 128, 152, 64, 62, 80, 136, 128, // 179 - '³'
@@ -189,7 +189,7 @@ MD_MAX72XX::fontType_t mFactory[] PROGMEM =
7, 128, 136, 136, 254, 136, 136, 128, // 183 - '·'
1, 128, // 184 - '¸'
0, // 185 - '¹'
3, 4, 10, 4, // 186 - 'º'
5, 64, 0, 64, 0, 64, // 186 - 'º'
0, // 187 - '»'
0, // 188 - '¼'
0, // 189 - '½'

View File

@@ -75,6 +75,7 @@ int messageDisplaySeconds;
int messageScrollTimes;
unsigned long messageStartTime = 0;
int currentScrollCount = 0;
int currentDisplayCycleCount = 0;
// Dimming
bool dimmingEnabled = false;
@@ -516,39 +517,6 @@ void connectWiFi() {
}
}
void clearWiFiCredentialsInConfig() {
DynamicJsonDocument doc(2048);
// Open existing config, if present
File configFile = LittleFS.open("/config.json", "r");
if (configFile) {
DeserializationError err = deserializeJson(doc, configFile);
configFile.close();
if (err) {
Serial.print(F("[SECURITY] Error parsing config.json: "));
Serial.println(err.f_str());
return;
}
}
doc["ssid"] = "";
doc["password"] = "";
// Optionally backup previous config
if (LittleFS.exists("/config.json")) {
LittleFS.rename("/config.json", "/config.bak");
}
File f = LittleFS.open("/config.json", "w");
if (!f) {
Serial.println(F("[SECURITY] ERROR: Cannot write to /config.json to clear credentials!"));
return;
}
serializeJson(doc, f);
f.close();
Serial.println(F("[SECURITY] Cleared WiFi credentials in config.json."));
}
// -----------------------------------------------------------------------------
// Time / NTP Functions
@@ -982,24 +950,6 @@ void setupWebServer() {
}
});
server.on("/clear_wifi", HTTP_POST, [](AsyncWebServerRequest *request) {
Serial.println(F("[WEBSERVER] Request: /clear_wifi"));
clearWiFiCredentialsInConfig();
DynamicJsonDocument okDoc(128);
okDoc[F("message")] = "✅ WiFi credentials cleared! Rebooting...";
String response;
serializeJson(okDoc, response);
request->send(200, "application/json", response);
request->onDisconnect([]() {
Serial.println(F("[WEBSERVER] Rebooting after clearing WiFi..."));
saveUptime();
delay(100); // ensure file is written
ESP.restart();
});
});
server.on("/ap_status", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.print(F("[WEBSERVER] Request: /ap_status. isAPMode = "));
Serial.println(isAPMode);
@@ -1014,37 +964,49 @@ void setupWebServer() {
request->send(400, "application/json", "{\"error\":\"Missing value\"}");
return;
}
String sourceHeader = request->header("X-Source");
bool isFromUI = (sourceHeader == "UI");
bool isFromHA = !isFromUI;
int newBrightness = request->getParam("value", true)->value().toInt();
// Handle "off" request
// Handle OFF request
if (newBrightness == -1) {
P.displayShutdown(true); // Fully shut down display driver
P.displayShutdown(true);
P.displayClear();
displayOff = true;
Serial.println("[WEBSERVER] Display set to OFF (shutdown mode)");
Serial.printf("[BRIGHTNESS] Display OFF via %s\n",
isFromUI ? "UI" : "HA");
request->send(200, "application/json", "{\"ok\":true, \"display\":\"off\"}");
return;
}
// Clamp brightness to valid range
if (newBrightness < 0) newBrightness = 0;
if (newBrightness > 15) newBrightness = 15;
// Clamp brightness range (015)
newBrightness = constrain(newBrightness, 0, 15);
// Only run robust clear/reset when coming from "off"
if (displayOff) {
// Wake from OFF
P.setIntensity(newBrightness);
advanceDisplayModeSafe();
P.displayShutdown(false);
brightness = newBrightness;
displayOff = false;
Serial.println("[WEBSERVER] Display woke from OFF");
Serial.printf("[BRIGHTNESS] Display woke from OFF via %s → %d\n",
isFromUI ? "UI" : "HA",
newBrightness);
} else {
// Display already on, just set brightness
// Display already ON
brightness = newBrightness;
P.setIntensity(brightness);
Serial.printf("[WEBSERVER] Set brightness to %d\n", brightness);
}
Serial.printf("[BRIGHTNESS] Set to %d via %s\n",
brightness,
isFromUI ? "UI" : "HA");
}
request->send(200, "application/json", "{\"ok\":true}");
});
@@ -1909,7 +1871,7 @@ void fetchWeather() {
if (doc.containsKey(F("main")) && doc[F("main")].containsKey(F("temp"))) {
float temp = doc[F("main")][F("temp")];
currentTemp = String((int)round(temp)) + "º";
currentTemp = String((int)round(temp)) + "°";
Serial.printf("[WEATHER] Temp: %s\n", currentTemp.c_str());
weatherAvailable = true;
} else {
@@ -2806,7 +2768,7 @@ void loop() {
switch (ntpAnimFrame % 3) {
case 0: P.print(F("S Y N C ®")); break;
case 1: P.print(F("S Y N C ¯")); break;
case 2: P.print(F("S Y N C °")); break;
case 2: P.print(F("S Y N C º")); break;
}
ntpAnimFrame++;
}
@@ -3524,108 +3486,160 @@ void loop() {
}
// --- Custom Message Display Mode (displayMode == 6) ---
if (displayMode == 6) {
if (strlen(customMessage) == 0) {
advanceDisplayMode();
yield();
return;
}
// --- CHECK FOR TIMEOUT ---
bool timedOut = false;
// Check if a time limit (messageDisplaySeconds > 0) has been exceeded
if (messageDisplaySeconds > 0 && (millis() - messageStartTime) >= (messageDisplaySeconds * 1000UL)) {
Serial.printf("[MESSAGE] Custom message timed out after %d seconds.\n", messageDisplaySeconds);
timedOut = true;
}
// --- CHECK FOR SCROLL LIMIT BEFORE DISPLAYING ---
bool scrollsComplete = (messageScrollTimes > 0) && (currentScrollCount >= messageScrollTimes);
// --- ADVANCE MODE CHECK (Check if done based on time or scrolls) ---
if (timedOut || scrollsComplete) {
Serial.println(F("[MESSAGE] Custom message finished."));
// Reset common counters, regardless of what happens next
currentScrollCount = 0;
messageStartTime = 0;
// ----------------------------------------------------------------------
// CRITICAL LOGIC: RESTORE PERSISTENT MESSAGE
// ----------------------------------------------------------------------
if (strlen(lastPersistentMessage) > 0) {
// A persistent message exists, restore it to customMessage
strncpy(customMessage, lastPersistentMessage, sizeof(customMessage));
messageScrollSpeed = GENERAL_SCROLL_SPEED; // Persistent messages use global speed
// Clear HA timing/scroll variables (restored persistent message is infinite)
messageDisplaySeconds = 0;
messageScrollTimes = 0;
Serial.printf("[MESSAGE] Restored persistent message: '%s'. Staying in mode 6.\n", customMessage);
// DO NOT advanceDisplayMode() or clear customMessage[0]!
// The function returns, and the next loop cycle will immediately display the restored message.
} else {
// No persistent message to restore. Exit mode 6.
customMessage[0] = '\0'; // Clear the buffer to exit mode 6 in the next loop cycle
Serial.println(F("[MESSAGE] No persistent message to restore. Advancing display mode."));
advanceDisplayMode();
}
yield();
return;
}
String msg = String(customMessage);
// Replace standard digits 09 with your custom font character codes
for (int i = 0; i < msg.length(); i++) {
if (isDigit(msg[i])) {
int num = msg[i] - '0'; // 09
msg[i] = 145 + ((num + 9) % 10); // Maps 0→154, 1→145, ... 9→153
}
}
// --- Determine if we need left padding based on previous mode ---
bool addPadding = false;
bool humidityVisible = showHumidity && weatherAvailable && strlen(openWeatherApiKey) == 32 && strlen(openWeatherCity) > 0 && strlen(openWeatherCountry) > 0;
// If coming from CLOCK mode
if (prevDisplayMode == 0 && (showDayOfWeek || colonBlinkEnabled)) {
addPadding = true;
} else if (prevDisplayMode == 1 && humidityVisible) {
addPadding = true;
}
// Apply padding (4 spaces) if needed
if (addPadding) {
msg = " " + msg;
}
// --- Display scrolling message ---
P.setTextAlignment(PA_LEFT);
P.setCharSpacing(1);
textEffect_t actualScrollDirection = getEffectiveScrollDirection(PA_SCROLL_LEFT, flipDisplay);
extern int messageScrollSpeed;
// START SCROLL CYCLE
P.displayScroll(msg.c_str(), PA_LEFT, actualScrollDirection, messageScrollSpeed);
// BLOCKING WAIT: Completes 1 full scroll, matching your definition of "1 scroll"
while (!P.displayAnimate()) yield();
// SCROLL COUNT INCREMENT
if (messageScrollTimes > 0) {
currentScrollCount++;
Serial.printf("[MESSAGE] Scroll complete. Count: %d/%d\n", currentScrollCount, messageScrollTimes);
}
P.setTextAlignment(PA_CENTER);
// --- Custom Message Display Mode (displayMode == 6) ---
if (displayMode == 6) {
// 1. Initial Check: If message is empty, skip mode 6.
if (strlen(customMessage) == 0) {
advanceDisplayMode();
yield();
return;
}
// --- CHARACTER REPLACEMENT AND PADDING (Common to both short and long) ---
const size_t MAX_NON_SCROLLING_CHARS = 8;
String msg = String(customMessage);
// Replace standard digits 09 with your custom font character codes
for (int i = 0; i < msg.length(); i++) {
if (isDigit(msg[i])) {
int num = msg[i] - '0';
msg[i] = 145 + ((num + 9) % 10);
}
}
// --- CHECK FOR TIMEOUT (Applies to temporary short & long messages) ---
bool timedOut = false;
// Check if a time limit (messageDisplaySeconds > 0) has been exceeded
if (messageDisplaySeconds > 0 && (millis() - messageStartTime) >= (messageDisplaySeconds * 1000UL)) {
Serial.printf("[MESSAGE] HA message timed out after %d seconds.\n", messageDisplaySeconds);
timedOut = true;
}
// --- CHECK FOR SCROLL/CYCLE LIMIT BEFORE DISPLAYING ---
// Scrolls complete applies to long messages.
bool scrollsComplete = (messageScrollTimes > 0) && (currentScrollCount >= messageScrollTimes);
// Cycles complete applies to short messages.
extern int currentDisplayCycleCount; // Use the dedicated short message counter
bool cyclesComplete = (messageScrollTimes > 0) && (currentDisplayCycleCount >= messageScrollTimes);
// --- ADVANCE MODE CHECK (Check if HA parameters are complete) ---
// If either timer or cycle/scroll count is finished, we clean up the temporary message.
if (scrollsComplete || cyclesComplete) {
Serial.println(F("[MESSAGE] HA-controlled message finished."));
// Reset common counters
currentScrollCount = 0;
messageStartTime = 0;
currentDisplayCycleCount = 0; // Reset the cycle counter
// CRITICAL LOGIC: RESTORE PERSISTENT MESSAGE (Exit Mode 6 Logic)
if (strlen(lastPersistentMessage) > 0) {
// A persistent message exists, restore it
strncpy(customMessage, lastPersistentMessage, sizeof(customMessage));
messageScrollSpeed = GENERAL_SCROLL_SPEED;
messageDisplaySeconds = 0;
messageScrollTimes = 0;
Serial.printf("[MESSAGE] Restored persistent message: '%s'. Staying in mode 6.\n", customMessage);
} else {
// No persistent message to restore. Clear the temporary HA message and Exit mode 6.
customMessage[0] = '\0';
Serial.println(F("[MESSAGE] No persistent message to restore. Advancing display mode."));
advanceDisplayMode();
}
yield();
return;
}
// ----------------------------------------------------------------------
// BRANCH A: NON-SCROLLING (Short Message: strlen <= 8)
// ----------------------------------------------------------------------
if (msg.length() <= MAX_NON_SCROLLING_CHARS) {
// Determine the duration: use HA seconds if set, otherwise use weatherDuration.
unsigned long durationMs = (messageDisplaySeconds > 0)
? (messageDisplaySeconds * 1000UL)
: weatherDuration;
// If HA seconds is set, we use the timedOut check at the top.
// If only scrollTimes is set, we still display for weatherDuration before incrementing the cycle count.
Serial.printf("[MESSAGE] Displaying timed short message: '%s' for %lu ms. Advancing mode.\n", customMessage, durationMs);
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
P.print(msg.c_str());
// Block execution for the specified duration (non-HA uses weatherDuration)
unsigned long displayUntil = millis() + durationMs;
while (millis() < displayUntil) {
yield();
}
// --- CYCLE TRACKING FOR SCROLLTIMES ---
// Increment the counter if the HA message is configured to clear by scroll count.
if (messageScrollTimes > 0) {
currentDisplayCycleCount++;
Serial.printf("[MESSAGE] Short message cycle complete. Count: %d/%d\n", currentDisplayCycleCount, messageScrollTimes);
}
// After display, the message content must persist, but the display must cycle.
Serial.println(F("[MESSAGE] Short message duration complete. Advancing display mode."));
advanceDisplayMode();
yield();
return;
}
// ----------------------------------------------------------------------
// BRANCH B: SCROLLING (Long Message: strlen > 8) - (Existing Logic)
// ----------------------------------------------------------------------
// --- Determine if we need left padding based on previous mode ---
bool addPadding = false;
bool humidityVisible = showHumidity && weatherAvailable && strlen(openWeatherApiKey) == 32 && strlen(openWeatherCity) > 0 && strlen(openWeatherCountry) > 0;
// If coming from CLOCK mode
if (prevDisplayMode == 0 && (showDayOfWeek || colonBlinkEnabled)) {
addPadding = true;
} else if (prevDisplayMode == 1 && humidityVisible) {
addPadding = true;
}
// Apply padding (4 spaces) if needed
if (addPadding) {
msg = " " + msg;
}
// --- Display scrolling message ---
P.setTextAlignment(PA_LEFT);
P.setCharSpacing(1);
textEffect_t actualScrollDirection = getEffectiveScrollDirection(PA_SCROLL_LEFT, flipDisplay);
extern int messageScrollSpeed;
// START SCROLL CYCLE
P.displayScroll(msg.c_str(), PA_LEFT, actualScrollDirection, messageScrollSpeed);
// BLOCKING WAIT: Completes 1 full scroll
while (!P.displayAnimate()) yield();
// SCROLL COUNT INCREMENT
if (messageScrollTimes > 0) {
currentScrollCount++;
Serial.printf("[MESSAGE] Scroll complete. Count: %d/%d\n", currentScrollCount, messageScrollTimes);
}
// If no HA parameters are set, this is a persistent/infinite scroll, so advance mode after 1 scroll cycle.
// If HA parameters ARE set, the mode relies on the check at the top to break out.
if (messageDisplaySeconds == 0 && messageScrollTimes == 0) {
P.setTextAlignment(PA_CENTER);
advanceDisplayMode();
}
yield();
return;
}
unsigned long currentMillis = millis();
unsigned long runtimeSeconds = (currentMillis - bootMillis) / 1000;

File diff suppressed because one or more lines are too long

View File

@@ -47,7 +47,7 @@ MD_MAX72XX::fontType_t mFactory[] PROGMEM =
17, 130, 186, 198, 254, 134, 234, 134, 254, 250, 130, 250, 254, 134, 234, 134, 254, 124, // 41 - ')'
20, 250, 130, 250, 254, 130, 170, 186, 254, 130, 250, 226, 250, 134, 254, 130, 234, 234, 246, 254, 124, // 42 - '*'
5, 8, 8, 62, 8, 8, // 43 - '+'
2, 128, 64, // 44 - ','
2, 128, 64, // 44 - ','
2, 8, 8, // 45 - '-'
1, 64, // 46 - '.'
3, 96, 24, 6, // 47 - '/'
@@ -179,7 +179,7 @@ MD_MAX72XX::fontType_t mFactory[] PROGMEM =
0, // 173 - '­'
5, 64, 0, 0, 0, 0, // 174 - '®'
5, 64, 0, 64, 0, 0, // 175 - '¯'
5, 64, 0, 64, 0, 64, // 176 - '°'
3, 4, 10, 4, // 176 - '°'
5, 254, 146, 146, 146, 254, // 177 - '±'
6, 128, 126, 42, 42, 170, 254, // 178 - '²'
7, 128, 152, 64, 62, 80, 136, 128, // 179 - '³'
@@ -189,7 +189,7 @@ MD_MAX72XX::fontType_t mFactory[] PROGMEM =
7, 128, 136, 136, 254, 136, 136, 128, // 183 - '·'
1, 128, // 184 - '¸'
0, // 185 - '¹'
3, 4, 10, 4, // 186 - 'º'
5, 64, 0, 64, 0, 64, // 186 - 'º'
0, // 187 - '»'
0, // 188 - '¼'
0, // 189 - '½'
@@ -258,5 +258,5 @@ MD_MAX72XX::fontType_t mFactory[] PROGMEM =
0, // 252 - 'ü'
0, // 253 - 'ý'
0, // 254 - 'þ'
1, 8, // 255 - 'ÿ'
1, 8, // 255 - 'ÿ'
};