Skip to content

Instantly share code, notes, and snippets.

@Resinchem
Last active August 11, 2024 07:48
Show Gist options
  • Save Resinchem/ecd86dfb52bd699c79acfa80cd348d7b to your computer and use it in GitHub Desktop.
Save Resinchem/ecd86dfb52bd699c79acfa80cd348d7b to your computer and use it in GitHub Desktop.
Sample Arduino code for publishing devices using Home Assistant MQTT Discovery. See the following for use case: https://youtu.be/VHiCtZqllU8 or https://resinchemtech.blogspot.com/2023/12/mqtt-auto-discovery.html
/* ===================================================================
SAMPLE CODE Segments for Home Assistant MQTT Discovery
This is NOT complete code but shows an example of generating a unique ID
for topic/entity publishing based on the device MAC address.
It also includes sample code for creating a Home Assistant device with four
entities via MQTT Discovery, as shown here:
Video: https://youtu.be/VHiCtZqllU8
Blog: https://resinchemtech.blogspot.com/2023/12/mqtt-auto-discovery.html
It is highly recommended that you include some sort of end-user accessible setting to
enable/disable Home Assistant discovery as some users may not want your device auto-discovered
and Home Assistant currently doesn't provide any prompting... devices/entities are immediately
added as soon as a valid topic/payload are received.
THIS IS AN INCOMPLETE EXAMPLE ONLY!!!!
===================================================================== */
//Partial list of libaries - also need Wifi, ESP8266/ESP32 libraries, etc.
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266WebServer.h> //Used for HTTP callback to enable/disable discovery
//Auto-discover enable/disable option
bool auto_discovery = false; //default to false and provide end-user interface to allow toggling
//Variables for creating unique entity IDs and topics
byte macAddr[6]; //Device MAC address
char uidPrefix[] = "rctdev"; //Prefix for unique ID generation (limit to 20 chars)
char devUniqueID[30]; //Generated Unique ID for this device (uidPrefix + last 6 MAC characters)
// =====================================
// Create Unique ID for topics/entities
// =====================================
void createDiscoveryUniqueID() {
//Generate UniqueID from uidPrefix + last 6 chars of device MAC address
//This should insure that even multiple devices installed in same HA instance are unique
strcpy(devUniqueID, uidPrefix);
int preSizeBytes = sizeof(uidPrefix);
int preSizeElements = (sizeof(uidPrefix) / sizeof(uidPrefix[0]));
//Now add last 6 chars from MAC address (these are 'reversed' in the array)
int j = 0;
for (int i = 2; i >= 0; i--) {
sprintf(&devUniqueID[(preSizeBytes - 1) + (j)], "%02X", macAddr[i]); //preSizeBytes indicates num of bytes in prefix - null terminator, plus 2 (j) bytes for each hex segment of MAC
j = j + 2;
}
// End result is a unique ID for this device (e.g. rctdevE350CA)
Serial.print("Unique ID: ");
Serial.println(devUniqueID);
}
// ===============================
// Main HA MQTT Discover Function
// This creates a single fictional device with four entities:
// - A dimmer switch with light level, temperature and IP address
// ===============================
void haDiscovery() {
char topic[128];
if (auto_discovery) {
char buffer1[512];
char buffer2[512];
char buffer3[512];
char buffer4[512];
char uid[128];
DynamicJsonDocument doc(512);
doc.clear();
Serial.println("Discovering new devices...");
Serial.println("Adding light switch...");
//Create unique topic based on devUniqueID
strcpy(topic, "homeassistant/light/");
strcat(topic, devUniqueID);
strcat(topic, "S/config");
//Create unique_id based on devUniqueID
strcpy(uid, devUniqueID);
strcat(uid, "S");
//Create JSON payload per HA documentation
doc["name"] = "My MQTT Light";
doc["obj_id"] = "mqtt_light";
doc["uniq_id"] = uid;
doc["stat_t"] = "stat/mydevice/switch";
doc["cmd_t"] = "cmnd/mydevice/switch";
doc["brightness"] = "true";
doc["bri_scl"] = "100";
doc["bri_stat_t"] = "stat/mydevice/brightness";
doc["bri_cmd_t"] = "cmnd/mydevice/brightness";
JsonObject device = doc.createNestedObject("device");
device["ids"] = "mymqttdevice01";
device["name"] = "My MQTT Device";
device["mf"] = "Resinchem Tech";
device["mdl"] = "ESP8266";
device["sw"] = "1.24";
device["hw"] = "0.45";
device["cu"] = "http://192.168.1.226/config"; //web interface for device, with discovery toggle
serializeJson(doc, buffer1);
//Publish discovery topic and payload (with retained flag)
client.publish(topic, buffer1, true);
//Lux Sensor
Serial.println("Adding light sensor...");
//Create unique Topic based on devUniqueID
strcpy(topic, "homeassistant/sensor/");
strcat(topic, devUniqueID);
strcat(topic, "L/config");
//Create unique_id based on decUniqueID
strcpy(uid, devUniqueID);
strcat(uid, "L");
//Create JSON payload per HA documentation
doc.clear();
doc["name"] = "Light Level";
doc["obj_id"] = "mqtt_light_level";
doc["dev_cla"] = "illuminance";
doc["uniq_id"] = uid;
doc["stat_t"] = "stat/mydevice/lightlevel";
doc["unit_of_meas"] = "lx";
JsonObject deviceS = doc.createNestedObject("device");
deviceS["ids"] = "mymqttdevice01";
deviceS["name"] = "My MQTT Device";
serializeJson(doc, buffer2);
//Publish discovery topic and payload (with retained flag)
client.publish(topic, buffer2, true);
//Temperature Sensor
Serial.println("Adding Temp Sensor...");
//Create unique Topic based on devUniqueID
strcpy(topic, "homeassistant/sensor/");
strcat(topic, devUniqueID);
strcat(topic, "T/config");
//Create unique_id based on decUniqueID
strcpy(uid, devUniqueID);
strcat(uid, "T");
//Create JSON payload per HA documentation
doc.clear();
doc["name"] = "Temperature";
doc["obj_id"] = "mqtt_temperature";
doc["deve_cla"] = "temperature";
doc["uniq_id"] = uid;
doc["stat_t"] = "stat/mydevice/temperature";
doc["unit_of_meas"] = "°F";
JsonObject deviceT = doc.createNestedObject("device");
deviceT["ids"] = "mymqttdevice01";
deviceT["name"] = "My MQTT Device";
serializeJson(doc, buffer3);
//Publish discovery topic and payload (with retained flag)
client.publish(topic, buffer3, true);
//IP Address Diagnostic
Serial.println("Adding IP Diagnostic Sensor...");
//Create unique Topic based on devUniqueID
strcpy(topic, "homeassistant/sensor/");
strcat(topic, devUniqueID);
strcat(topic, "I/config");
//Create unique_id based on decUniqueID
strcpy(uid, devUniqueID);
strcat(uid, "I");
//Create JSON payload per HA documentation
doc.clear();
doc["name"] = "IP Address";
doc["uniq_id"] = uid;
doc["ent_cat"] = "diagnostic";
doc["stat_t"] = "stat/mydevice/ipaddress";
JsonObject deviceI = doc.createNestedObject("device");
deviceI = doc.createNestedObject("device");
deviceI["ids"] = "mymqttdevice01";
deviceI["name"] = "My MQTT Device";
serializeJson(doc, buffer4);
//Publish discovery topic and payload (with retained flag)
client.publish(topic, buffer4, true);
Serial.println("All devices added!");
} else {
//Remove all entities by publishing empty payloads
//Must use original topic, so recreate from original Unique ID
//This will immediately remove/delete the device/entities from HA
Serial.println("Removing discovered devices...");
//Lux Sensor
strcpy(topic, "homeassistant/sensor/");
strcat(topic, devUniqueID);
strcat(topic, "L/config");
client.publish(topic, "");
//Temperature Sensor
strcpy(topic, "homeassistant/sensor/");
strcat(topic, devUniqueID);
strcat(topic, "T/config");
client.publish(topic, "");
//IP Address Sensor
strcpy(topic, "homeassistant/sensor/");
strcat(topic, devUniqueID);
strcat(topic, "I/config");
client.publish(topic, "");
//Light (switch)
strcpy(topic, "homeassistant/light/");
strcat(topic, devUniqueID);
strcat(topic, "S/config");
client.publish(topic, "");
Serial.println("Devices Removed...");
}
}
// =====================
// MAIN SETUP
// =====================
void Setup() {
// The following should be included in (or called from) the main setup routine
//Get MAC address when joining wifi and place into char array
WiFi.macAddress(macAddr);
//Call routing (or embed here) to create initial Unique ID
createDiscoveryUniqueID();
//Handle web callbacks for enabling or disabling discovery (using this method is just one of many ways to do this)
server.on("/discovery_on",[]() {
server.send(200, "text/html", "<h1>Discovery ON...<h1><h3>Home Assistant MQTT Discovery enabled</h3>");
delay(200);
auto_discovery = true;
haDiscovery();
});
server.on("/discovery_off",[]() {
server.send(200, "text/html", "<h1>Discovery OFF...<h1><h3>Home Assistant MQTT Discovery disabled. Previous entities removed.</h3>");
delay(200);
auto_discovery = false;
haDiscovery();
});
server.begin();
}
@DozoG
Copy link

DozoG commented Aug 10, 2024

LoL.. Yeah, I got that. The MQTT-integration is frequently part of the breaking changes. It's frustrating.
I read the Home assistant documentation for mqtt discovery. I tried using the full name instead of the abbreviations.. etc etc.

I read some topic named "Do not allow mqtt lights to set brightness to zero #91296"
But thats on the core... thats above my paygrade. (if it is relevant)

The example for an mqtt device with light on the HA website, is 6 years old. Its almost criminal to have it on the website. :-)

There is a library: https://github.com/dawidchyrzynski/arduino-home-assistant/tree/main
I may try that out (I am scanning it to see if it does things significantly different from the simple code I have so far.)
But it adds an extra level of abstraction. Once the HA implementation changes, I would depend on that library to do its maintenance.
It's a little similar to the reason I hessitate using ESP-Home. Very frequent updates and it's basically an abstraction layer on top of arduino code.
I love how mqtt-discovery creates device on HA, but if it breaks every two months..

So far I create quite some HomeAssistant entities in node-red. And then sending regular text commands to TCP or UDP node in node-red.
It works, but it is a task to manage them.
Anyway, we got options.

Hey, thank you man for at least introducing me to mqtt discovery and getting me this far.

@DozoG
Copy link

DozoG commented Aug 11, 2024

Dear Resinchem,

I thought it fair (and possibly helpfull to others that read your blog or watch your video) to report back that I figured it out.
And its simple but deceptive trick in the MQTT-Callback function:

I previously responded to receiving a switch command, by publishing the new switch_state.
And responded to receiving a brightness command, by publishing the new brightness_state.
(I had tried to simply respond by publishing all states upon receiving any command, but that did not work)

I now have included code that when receiving a brightness command: when it is >0 , ALSO publish switch_state = on
And when the brightness command has a value == 0, then ALSO publish the switch_state = off

And it magically works.
When checking the brightness value in the Developer_tools, it also updates.
When the switch is off, the brightness value is null. when I toggle the switch, it gets the (original) value through mqtt.
When I drag the slider, the light goes brighter/less bright, and goes off when slider set to lowest position.

Thanks again.

Edit.... OR..... it got solved in the core_2024.8.1 , because it now behaves quite a bit diffferent
Anyway.. will figure it out.

P.S. It works better if you set the [bri_scl] = "255" .. because then the attribite "color_mode" will switch between "brightnes" (switch is on) and "null" (switch is off)
And the attribute "brightness" will get a value depending on the mqtt state value .. or null when the switch is off.
OK, I will stop bothering you with updates.
Thanks for the last time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment