-
Notifications
You must be signed in to change notification settings - Fork 31
Integrate with Home Assistant #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bb25d48
8c372d3
28c2d9d
fd64d4a
0913d88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -34,6 +34,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <DNSServer.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <WiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <EEPROM.h> //from ESP8266 Arduino Core (automatically installed when ESP8266 was installed via Boardmanager) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <PubSubClient.h> // MQTT library | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // own libraries | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "udplogger.h" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -132,6 +133,15 @@ const unsigned int DNSPort = 53; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ip addresses for multicast logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IPAddress logMulticastIP = IPAddress(230, 120, 10, 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // MQTT Configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const char* mqtt_server = "mqtt_server"; // Replace with your Home Assistant MQTT broker IP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const char* mqtt_user = "mqtt_user"; // Replace with your MQTT username | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const char* mqtt_password = "mqtt_password"; // Replace with your MQTT password | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const char* mqtt_client_id = "wordclock"; // Unique client ID for the Word Clock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+137
to
+140
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const char* mqtt_server = "mqtt_server"; // Replace with your Home Assistant MQTT broker IP | |
| const char* mqtt_user = "mqtt_user"; // Replace with your MQTT username | |
| const char* mqtt_password = "mqqt_password"; // Replace with your MQTT password | |
| const char* mqtt_client_id = "wordclock"; // Unique client ID for the Word Clock | |
| // Provide these values in secrets.h, for example: | |
| // #define MQTT_SERVER "broker.example.local" | |
| // #define MQTT_USER "mqtt_user" | |
| // #define MQTT_PASSWORD "mqtt_password" | |
| // #define MQTT_CLIENT_ID "wordclock" | |
| #ifndef MQTT_SERVER | |
| #error "MQTT_SERVER must be defined in secrets.h" | |
| #endif | |
| #ifndef MQTT_USER | |
| #error "MQTT_USER must be defined in secrets.h" | |
| #endif | |
| #ifndef MQTT_PASSWORD | |
| #error "MQTT_PASSWORD must be defined in secrets.h" | |
| #endif | |
| #ifndef MQTT_CLIENT_ID | |
| #error "MQTT_CLIENT_ID must be defined in secrets.h" | |
| #endif | |
| const char* mqtt_server = MQTT_SERVER; | |
| const char* mqtt_user = MQTT_USER; | |
| const char* mqtt_password = MQTT_PASSWORD; | |
| const char* mqtt_client_id = MQTT_CLIENT_ID; |
Copilot
AI
Apr 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This publishes wordclock/status every loop iteration, which can generate a very high message rate and saturate the broker/WiFi. Consider publishing only on an interval (e.g., reuse PERIOD_HEARTBEAT) and/or using a retained LWT/availability topic instead of continuous spam.
Copilot
AI
Apr 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Night mode start parsing assumes the payload contains HH:MM. If : is missing, separator will be -1 and the substring/toInt logic can yield incorrect values. Add validation for the separator and range-check hours/minutes before writing to EEPROM.
| nightModeStartHour = message.substring(0, separator).toInt(); | |
| nightModeStartMin = message.substring(separator + 1).toInt(); | |
| EEPROM.write(ADR_NM_START_H, nightModeStartHour); | |
| EEPROM.write(ADR_NM_START_M, nightModeStartMin); | |
| EEPROM.commit(); | |
| } | |
| // Handle night mode end time | |
| else if (String(topic) == "wordclock/nightmode/end") { | |
| int separator = message.indexOf(':'); | |
| nightModeEndHour = message.substring(0, separator).toInt(); | |
| nightModeEndMin = message.substring(separator + 1).toInt(); | |
| EEPROM.write(ADR_NM_END_H, nightModeEndHour); | |
| EEPROM.write(ADR_NM_END_M, nightModeEndMin); | |
| EEPROM.commit(); | |
| if (separator > 0 && separator < message.length() - 1) { | |
| int parsedHour = message.substring(0, separator).toInt(); | |
| int parsedMin = message.substring(separator + 1).toInt(); | |
| if (parsedHour >= 0 && parsedHour <= 23 && parsedMin >= 0 && parsedMin <= 59) { | |
| nightModeStartHour = parsedHour; | |
| nightModeStartMin = parsedMin; | |
| EEPROM.write(ADR_NM_START_H, nightModeStartHour); | |
| EEPROM.write(ADR_NM_START_M, nightModeStartMin); | |
| EEPROM.commit(); | |
| } else { | |
| logger.logString("Invalid night mode start time range: " + message); | |
| } | |
| } else { | |
| logger.logString("Invalid night mode start time format: " + message); | |
| } | |
| } | |
| // Handle night mode end time | |
| else if (String(topic) == "wordclock/nightmode/end") { | |
| int separator = message.indexOf(':'); | |
| if (separator > 0 && separator < message.length() - 1) { | |
| int parsedHour = message.substring(0, separator).toInt(); | |
| int parsedMin = message.substring(separator + 1).toInt(); | |
| if (parsedHour >= 0 && parsedHour <= 23 && parsedMin >= 0 && parsedMin <= 59) { | |
| nightModeEndHour = parsedHour; | |
| nightModeEndMin = parsedMin; | |
| EEPROM.write(ADR_NM_END_H, nightModeEndHour); | |
| EEPROM.write(ADR_NM_END_M, nightModeEndMin); | |
| EEPROM.commit(); | |
| } else { | |
| logger.logString("Invalid night mode end time range: " + message); | |
| } | |
| } else { | |
| logger.logString("Invalid night mode end time format: " + message); | |
| } |
Copilot
AI
Apr 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Night mode end parsing has the same issue as start: missing/invalid HH:MM format can lead to incorrect EEPROM values. Validate the separator and range-check hours/minutes before persisting.
| nightModeStartHour = message.substring(0, separator).toInt(); | |
| nightModeStartMin = message.substring(separator + 1).toInt(); | |
| EEPROM.write(ADR_NM_START_H, nightModeStartHour); | |
| EEPROM.write(ADR_NM_START_M, nightModeStartMin); | |
| EEPROM.commit(); | |
| } | |
| // Handle night mode end time | |
| else if (String(topic) == "wordclock/nightmode/end") { | |
| int separator = message.indexOf(':'); | |
| nightModeEndHour = message.substring(0, separator).toInt(); | |
| nightModeEndMin = message.substring(separator + 1).toInt(); | |
| EEPROM.write(ADR_NM_END_H, nightModeEndHour); | |
| EEPROM.write(ADR_NM_END_M, nightModeEndMin); | |
| EEPROM.commit(); | |
| if (separator > 0 && separator < (message.length() - 1)) { | |
| int parsedHour = message.substring(0, separator).toInt(); | |
| int parsedMin = message.substring(separator + 1).toInt(); | |
| if (parsedHour >= 0 && parsedHour <= 23 && parsedMin >= 0 && parsedMin <= 59) { | |
| nightModeStartHour = parsedHour; | |
| nightModeStartMin = parsedMin; | |
| EEPROM.write(ADR_NM_START_H, nightModeStartHour); | |
| EEPROM.write(ADR_NM_START_M, nightModeStartMin); | |
| EEPROM.commit(); | |
| } else { | |
| logger.logString("Invalid night mode start time range: " + message); | |
| } | |
| } else { | |
| logger.logString("Invalid night mode start time format: " + message); | |
| } | |
| } | |
| // Handle night mode end time | |
| else if (String(topic) == "wordclock/nightmode/end") { | |
| int separator = message.indexOf(':'); | |
| if (separator > 0 && separator < (message.length() - 1)) { | |
| int parsedHour = message.substring(0, separator).toInt(); | |
| int parsedMin = message.substring(separator + 1).toInt(); | |
| if (parsedHour >= 0 && parsedHour <= 23 && parsedMin >= 0 && parsedMin <= 59) { | |
| nightModeEndHour = parsedHour; | |
| nightModeEndMin = parsedMin; | |
| EEPROM.write(ADR_NM_END_H, nightModeEndHour); | |
| EEPROM.write(ADR_NM_END_M, nightModeEndMin); | |
| EEPROM.commit(); | |
| } else { | |
| logger.logString("Invalid night mode end time range: " + message); | |
| } | |
| } else { | |
| logger.logString("Invalid night mode end time format: " + message); | |
| } |
Copilot
AI
Apr 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RGB parsing casts toInt() results directly to uint8_t, so values outside 0-255 (or negative) will wrap rather than clamp. Consider clamping each channel to [0,255] and rejecting invalid inputs before calling setMainColor.
Copilot
AI
Apr 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mqtt_client_id is constant ("wordclock") but the comment says it must be unique. If multiple clocks are deployed, they will kick each other off the broker. Consider deriving the client ID from the chip ID (e.g., append ESP.getChipId()), and/or allowing it to be configured.
Copilot
AI
Apr 4, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
connectToMQTT() blocks in a while (!mqttClient.connected()) loop and is called from both setup() and loop(). If the broker is down/unreachable, the device will stop servicing OTA/webserver/timekeeping and may watchdog reset. Consider making reconnection non-blocking (single attempt per call with backoff based on millis()) and allowing the clock to run without MQTT.
| while (!mqttClient.connected()) { | |
| Serial.print("Connecting to MQTT..."); | |
| if (mqttClient.connect(mqtt_client_id, mqtt_user, mqtt_password)) { | |
| Serial.println("connected"); | |
| // Subscribe to topics | |
| mqttClient.subscribe("wordclock/led"); | |
| mqttClient.subscribe("wordclock/mode"); | |
| mqttClient.subscribe("wordclock/brightness"); | |
| mqttClient.subscribe("wordclock/nightmode/activated"); | |
| mqttClient.subscribe("wordclock/nightmode/start"); | |
| mqttClient.subscribe("wordclock/nightmode/end"); | |
| mqttClient.subscribe("wordclock/colorshift/active"); | |
| mqttClient.subscribe("wordclock/colorshift/speed"); | |
| mqttClient.subscribe("wordclock/state/autochange"); | |
| mqttClient.subscribe("wordclock/led/color"); | |
| } else { | |
| Serial.print("failed, rc="); | |
| Serial.print(mqttClient.state()); | |
| Serial.println(" retrying in 5 seconds"); | |
| delay(5000); | |
| } | |
| } | |
| static unsigned long lastReconnectAttempt = 0; | |
| const unsigned long reconnectIntervalMs = 5000; | |
| if (mqttClient.connected()) { | |
| return; | |
| } | |
| unsigned long now = millis(); | |
| if (now - lastReconnectAttempt < reconnectIntervalMs) { | |
| return; | |
| } | |
| lastReconnectAttempt = now; | |
| Serial.print("Connecting to MQTT..."); | |
| if (mqttClient.connect(mqtt_client_id, mqtt_user, mqtt_password)) { | |
| Serial.println("connected"); | |
| // Subscribe to topics | |
| mqttClient.subscribe("wordclock/led"); | |
| mqttClient.subscribe("wordclock/mode"); | |
| mqttClient.subscribe("wordclock/brightness"); | |
| mqttClient.subscribe("wordclock/nightmode/activated"); | |
| mqttClient.subscribe("wordclock/nightmode/start"); | |
| mqttClient.subscribe("wordclock/nightmode/end"); | |
| mqttClient.subscribe("wordclock/colorshift/active"); | |
| mqttClient.subscribe("wordclock/colorshift/speed"); | |
| mqttClient.subscribe("wordclock/state/autochange"); | |
| mqttClient.subscribe("wordclock/led/color"); | |
| } else { | |
| Serial.print("failed, rc="); | |
| Serial.print(mqttClient.state()); | |
| Serial.println(" will retry later"); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Home Assistant YAML uses
state_topicfor many entities (andwordclock/mode/statespecifically), but the firmware never publishes those state topics (it only publisheswordclock/status). Either update the firmware to publish state updates (ideally retained) for LED/mode/brightness/nightmode/etc., or adjust the HA config to use optimistic mode / removestate_topicso the UI matches actual device state.