Skip to content

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Please download all these libraries as ZIP from GitHub, and extract them in the
- https://github.com/adafruit/Adafruit_NeoPixel
- https://github.com/tzapu/WiFiManager
- https://github.com/adafruit/Adafruit_BusIO
- https://github.com/knolleary/pubsubclient

You can als install these libraries via the library manager in the Arduino IDE.

Expand All @@ -91,6 +92,7 @@ MySketchbookLocation
│ └───Adafruit_NeoPixel
│ └───WiFiManager
│ └───Adafruit_BusIO
│ └───PubSubClient
└───wordclock_esp8266
│ wordclock_esp8266.ino
Expand All @@ -103,6 +105,94 @@ MySketchbookLocation
└───icons
```

## Integrate with Home Assistant

### STEP1: Setup in Home Assistant
Within Home Assistant, install the MQTT integration including the Mosquitto broker following [these steps](https://www.home-assistant.io/integrations/mqtt/).

Within the [Mosquitto broker addon](https://github.com/home-assistant/addons/blob/master/mosquitto/DOCS.md), navigate to 'Configuration' and add a new user.

```yaml
logins:
- username: user
password: passwd
```

Then head to your `configuration.yaml` and add the following code:

```yaml
mqtt:
switch:
- name: "Word Clock LED"
state_topic: "wordclock/led"
command_topic: "wordclock/led"
payload_on: "on"
payload_off: "off"
- name: "Word Clock Night Mode"
state_topic: "wordclock/nightmode/activated"
command_topic: "wordclock/nightmode/activated"
payload_on: "1"
payload_off: "0"
- name: "Word Clock Color Shift Active"
state_topic: "wordclock/colorshift/active"
command_topic: "wordclock/colorshift/active"
payload_on: "1"
payload_off: "0"
- name: "Word Clock Auto State Change"
state_topic: "wordclock/state/autochange"
command_topic: "wordclock/state/autochange"
payload_on: "1"
payload_off: "0"
select:
- name: "Word Clock Mode"
state_topic: "wordclock/mode/state"
command_topic: "wordclock/mode"
options:
- "clock"
- "diclock"
- "spiral"
- "tetris"
- "snake"
- "pingpong"
number:
- name: "Word Clock Brightness"
state_topic: "wordclock/brightness"
command_topic: "wordclock/brightness"
min: 10
max: 255
step: 1
- name: "Word Clock Color Shift Speed"
state_topic: "wordclock/colorshift/speed"
command_topic: "wordclock/colorshift/speed"
min: 1
max: 10
step: 1
sensor:
- name: "Word Clock Night Mode Start"
state_topic: "wordclock/nightmode/start"
- name: "Word Clock Night Mode End"
state_topic: "wordclock/nightmode/end"
light:
- name: "Word Clock Color"
command_topic: "wordclock/led/color"
rgb_command_template: "{{ red }},{{ green }},{{ blue }}"
brightness_command_topic: "wordclock/brightness"
brightness_scale: 255
```

### STEP2: Setup in Arduino IDE
Within `wordclock_esp8266.ino` head to the section `// MQTT Configuration` and make the required changes.

```C++
// MQTT Configuration
const char* mqtt_server = "192.168.1.170"; // Replace with your Home Assistant MQTT broker IP
const char* mqtt_user = "user"; // Replace with your MQTT username
const char* mqtt_password = "passwd"; // Replace with your MQTT password
const char* mqtt_client_id = "wordclock"; // Unique client ID for the Word Clock

```

`mqtt_user` & `mqtt_password` will be the new user you added previously within Home Assistant. `mqtt_server` will be your Home Assistant server IP address.

## Upload program to ESP8266 with Arduino IDE

Expand Down
176 changes: 170 additions & 6 deletions wordclock_esp8266.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -114,6 +115,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 = "mqqt_password"; // Replace with your MQTT password
const char* mqtt_client_id = "wordclock"; // Unique client ID for the Word Clock

WiFiClient espClient;
PubSubClient mqttClient(espClient);

// ip addresses for Access Point
IPAddress IPAdress_AccessPoint(192,168,10,2);
IPAddress Gateway_AccessPoint(192,168,10,0);
Expand Down Expand Up @@ -241,7 +251,7 @@ void setup() {


// Uncomment and run it once, if you want to erase all the stored information
//wifiManager.resetSettings();
// wifiManager.resetSettings();

// set custom ip for portal
//wifiManager.setAPStaticIPConfig(IPAdress_AccessPoint, Gateway_AccessPoint, Subnetmask_AccessPoint);
Expand Down Expand Up @@ -356,6 +366,13 @@ void setup() {
loadNightmodeSettingsFromEEPROM();
loadBrightnessSettingsFromEEPROM();
loadColorShiftStateFromEEPROM();

// Initialize MQTT
mqttClient.setServer(mqtt_server, 1883);
mqttClient.setCallback(mqttCallback);

// Connect to MQTT
connectToMQTT();

if(ESP.getResetReason().equals("Power On") || ESP.getResetReason().equals("External System")){
// test quickly each LED
Expand Down Expand Up @@ -407,6 +424,12 @@ void loop() {
// handle Webserver
server.handleClient();

// Handle MQTT connection
if (!mqttClient.connected()) {
connectToMQTT();
}
mqttClient.loop();

// send regularly heartbeat messages via UDP multicast
if(millis() - lastheartbeat > PERIOD_HEARTBEAT){
logger.logString("Heartbeat, state: " + stateNames[currentState] + ", FreeHeap: " + ESP.getFreeHeap() + ", HeapFrag: " + ESP.getHeapFragmentation() + ", MaxFreeBlock: " + ESP.getMaxFreeBlockSize() + "\n");
Expand All @@ -421,6 +444,9 @@ void loop() {
}
}

// Publish a heartbeat message
mqttClient.publish("wordclock/status", "Word Clock is running");

// handle state behaviours (trigger loopCycles of different states depending on current state)
if(!nightMode && !ledOff && (millis() - lastStep > behaviorUpdatePeriod) && (millis() - lastLEDdirect > TIMEOUT_LEDDIRECT)){
updateStateBehavior(currentState);
Expand Down Expand Up @@ -513,6 +539,113 @@ void loop() {
// OTHER FUNCTIONS
// ----------------------------------------------------------------------------------

/**
* @brief Handle incoming MQTT messages
*
* @param topic The topic of the message
* @param payload The payload of the message
* @param length The length of the payload
*/
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.printf("Message received on topic %s: %s\n", topic, message.c_str());

// Handle LED on/off commands
if (String(topic) == "wordclock/led") {
ledOff = (message == "off");
}

// Handle mode change
else if (String(topic) == "wordclock/mode") {
if (message == "clock") stateChange(st_clock, true);
else if (message == "diclock") stateChange(st_diclock, true);
else if (message == "spiral") stateChange(st_spiral, true);
else if (message == "tetris") stateChange(st_tetris, true);
else if (message == "snake") stateChange(st_snake, true);
else if (message == "pingpong") stateChange(st_pingpong, true);
}

// Handle brightness change
else if (String(topic) == "wordclock/brightness") {
brightness = message.toInt();
if (brightness < 10) brightness = 10; // Ensure minimum brightness
ledmatrix.setBrightness(brightness); // Apply brightness to the LED matrix
EEPROM.write(ADR_BRIGHTNESS, brightness); // Save to EEPROM
EEPROM.commit();
logger.logString("Brightness set to: " + String(brightness));
}

// Handle night mode activation
else if (String(topic) == "wordclock/nightmode/activated") {
nightModeActivated = (message == "1");
EEPROM.write(ADR_NM_ACTIVATED, nightModeActivated);
EEPROM.commit();
checkNightmode();
}

// Handle night mode start time
else if (String(topic) == "wordclock/nightmode/start") {
int separator = message.indexOf(':');
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();
}

// Handle dynamic color shift activation
else if (String(topic) == "wordclock/colorshift/active") {
dynColorShiftActive = (message == "1");
EEPROM.write(ADR_COLSHIFTACTIVE, dynColorShiftActive);
EEPROM.commit();
}

// Handle dynamic color shift speed
else if (String(topic) == "wordclock/colorshift/speed") {
dynColorShiftSpeed = message.toInt();
if (dynColorShiftSpeed == 0) dynColorShiftSpeed = 1; // Ensure minimum speed
EEPROM.write(ADR_COLSHIFTSPEED, dynColorShiftSpeed); // Save to EEPROM
EEPROM.commit();
logger.logString("Color shift speed set to: " + String(dynColorShiftSpeed));
}

// Handle state auto-change
else if (String(topic) == "wordclock/state/autochange") {
stateAutoChange = (message == "1");
}

// change the LED color
else if (String(topic) == "wordclock/led/color") {
// Expecting payload in the format "R,G,B" (e.g., "255,100,50")
int firstComma = message.indexOf(',');
int secondComma = message.indexOf(',', firstComma + 1);

if (firstComma != -1 && secondComma != -1) {
uint8_t red = message.substring(0, firstComma).toInt();
uint8_t green = message.substring(firstComma + 1, secondComma).toInt();
uint8_t blue = message.substring(secondComma + 1).toInt();

setMainColor(red, green, blue); // Set the main color
logger.logString("LED color set via MQTT to R:" + String(red) + " G:" + String(green) + " B:" + String(blue));
} else {
logger.logString("Invalid LED color format received via MQTT: " + message);
}
}
}

/**
* @brief Update mode behaviour depending on current state
*/
Expand Down Expand Up @@ -634,6 +767,36 @@ void checkNightmode(){
}
}

/**
* @brief Connect to MQTT
*
*/
void connectToMQTT() {
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);
}
}
}

/**
* @brief call entry action of given state
*
Expand Down Expand Up @@ -812,12 +975,13 @@ void handleButton(){
* @brief Set main color
*
*/
void setMainColor(uint8_t red, uint8_t green, uint8_t blue){
maincolor_clock = LEDMatrix::Color24bit(red, green, blue);
EEPROM.put(ADR_MC_RED, red);
EEPROM.put(ADR_MC_GREEN, green);
EEPROM.put(ADR_MC_BLUE, blue);
void setMainColor(uint8_t red, uint8_t green, uint8_t blue) {
maincolor_clock = LEDMatrix::Color24bit(red, green, blue); // Update the main color
EEPROM.put(ADR_MC_RED, red); // Save red to EEPROM
EEPROM.put(ADR_MC_GREEN, green); // Save green to EEPROM
EEPROM.put(ADR_MC_BLUE, blue); // Save blue to EEPROM
EEPROM.commit();
logger.logString("Main color updated to R:" + String(red) + " G:" + String(green) + " B:" + String(blue));
}

/**
Expand Down