12 KiB
Using the MAC address of an ESP8266’s WiFi adapter as the MQTT unique ID
- date
2021-01-31T05:35:00Z
- category
blog
- tags
homeassistant,arduino
- url
blog/using-esp8266-macaddress-as-mqtt-unique-id/
- save_as
blog/using-esp8266-macaddress-as-mqtt-unique-id/index.html
- status
published
- author
Gergely Polonkai
Note
This is a long story. If you are interested only in the solution, click here
The story
I recently dug my nose in the Arduino world, my goal being to build my own weather station. After experimenting with Arduinos and other boards of different sizes, i finally settled using ESP8266 based NodeMCU boards. They are cheap, have a well developed and maintained library for the Arduino IDE, and can be accessed via WiFi so no cabling is needed throughout my house.
The first version was based on Prometheus, running on a Raspberry Pi. Prometheus periodically queried the station for the weather data, put it on a graph, and all was set…
Except keeping a web server constantly up is really power consuming so it can’t be operated from a battery; i had to install the station near a mains socket, which is in a place in wind shade, providing less accurate temperature readings. It also takes away a precious wall socket, of which i have only two outside.
I got a little stuck in an X-Y problem, and started looking for another solution, based on a Prometheus Push Gateway. While browsing documentation and generally hanging around the Interwebz, i stumbled upon the Home Assistant project (which was on my ToDo list for a looong time anyway). Without hesitating, i quickly backed up the SD card of my Raspberry Pi and installed Hassio on it. I never looked back since.
With Home Assistant installed and configured, i installed and configured the Mosquitto add-on and started tinkering with my ESP boards.
Logging the “weather” of my office (and adding a doorbell to the mix)
Since i didn’t want to dismantle the Prometheus based station yet (even though there was no-one to query it except me calling http get http://192.168.0.12/metrics
manually), i decided to measure the temperature and humidity of my home office environment.
The circuit is really simple:
- Take an ESP8266 board and a DHT22 sensor (you can go with a DHT11, it doesn’t really matter)
- Connect the GND, VCC, and OUT pins of the DHT sensor to the GND, 3V3, and D5 (any other pin can work, though)
- Install the code below, and behold!
OK, i had a bit of precaution, and installed Mosquitto on my local machine, so i could test it before sending data directly to Hassio.
Meanwhile, i was expecting some package deliveries. I trusted the dogs that they will bark whenever someone stops at our gate, which i can clearly hear in my office. Well, it turned out they don’t do that if it’s raining, and since my smartphone rebooted for some unknown reason, the delivery guy could not reach me and left; he will try to deliver the package again on Monday. All this happened because i don’t have a doorbell.
So while at it, i quickly installed a push button on our gate, led the wire to my office (a good 5 meters or so), and did some more soldering:
- Connect the GND pin of the ESP board to D4 through a 10kΩ resistor
- Connect one pin of the push button to the 3V3 pin of the ESP board
- Connect the other pin of the push button to the D4 pin of the ESP board
This way D4 is pulled down (ie. remains low) all the time, and is pulled up (becomes high) when someone pushes the button.
All is good, now to the coding part!
The code
The code below is already tailored to be used with Home Assistant. While testing, BROKER_ADDR
pointed to my PC, and BROKER_USERNAME
and BROKER_PASSWORD
were not defined.
To compile it, you will need the Arduino IDE (or any toolchain that can compile Arduino code), the ESP8266 board files for Arduino IDE, the Adafruit DHT library, and the home-assistant-integration library.
#include <Adafruit_Sensor.h>
#include <ESP8266WiFi.h>
#include <ArduinoHA.h>
#include <DHT.h>
#define LED_PIN BUILTIN_LED
#define BROKER_ADDR IPAddress(192, 168, 0, 16)
#define BROKER_USERNAME "homeassistant"
#define BROKER_PASSWORD "verySecureHomeAssistantMQTTPassword"
#define BROKER_NAME "office-weather-station"
#define WIFI_SSID "MyHomeWiFi"
#define WIFI_PASSWORD "MyHomeWiFiPassword"
#define DHT_PIN D5
#define DHT_TYPE DHT22
#define DOORBELL_PIN D4
#define UPDATE_INTERVAL 5000
#define UNIQUE_ID "84cca8aa2673"
#define BUZZER_PIN D7
;
WiFiClient client(UNIQUE_ID);
HADevice device(client, device);
HAMqtt mqtt("led", false, mqtt);
HASwitch led("doorbell", false, mqtt);
HABinarySensor doorbell<double> temperature("temperature", 0, mqtt);
HASensor<double> humidity("humidity", 0, mqtt);
HASensorunsigned long last_update = 0;
(DHT_PIN, DHT_TYPE);
DHT dhtbool doorbell_pushed = false;
unsigned long doorbell_push_time = 0;
bool ringing = false;
void onSwitchStateChanged(bool state, HASwitch* s)
{
(LED_PIN, (state ? LOW : HIGH));
digitalWrite}
void setup() {
sensor_t sensor;
.begin(9600);
Serial.println("Starting...");
Serial
.begin();
dht
(LED_PIN, OUTPUT);
pinMode(LED_PIN, HIGH);
digitalWrite
(DOORBELL_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode
// connect to wifi
.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi
while (WiFi.status() != WL_CONNECTED) {
.print(".");
Serial(500); // waiting for the connection
delay}
.println();
Serial.print("Connected to the network, IP address: ");
Serial.println(WiFi.localIP());
Serial
// set device's details (optional)
.setName(BROKER_NAME);
device.setSoftwareVersion("0.3.0");
device
// handle switch state
.onStateChanged(onSwitchStateChanged);
led
.setUnitOfMeasurement("°C");
temperature.setUnitOfMeasurement("%");
humidity
.begin(BROKER_ADDR
mqtt#if defined(BROKER_USERNAME) && defined(BROKER_PASSWORD)
, BROKER_USERNAME, BROKER_PASSWORD
#endif
);
}
void loop() {
sensors_event_t event;
unsigned long now = millis();
float temp_value;
float humid_value;
int doorbell_state = digitalRead(DOORBELL_PIN);
.loop();
mqtt
if ((doorbell_state == HIGH) && !doorbell_pushed) {
= true;
doorbell_pushed = now;
doorbell_push_time } else if ((doorbell_state == LOW) && doorbell_pushed) {
= false;
doorbell_pushed .setState(false);
doorbell= false;
ringing .println("Doorbell released");
Serial(BUZZER_PIN, 0);
analogWrite(100);
delay}
if (doorbell_pushed && !ringing && (now - doorbell_push_time > 100)) {
= true;
ringing .println("Doorbell pushed");
Serial.setState(true);
doorbell(BUZZER_PIN, 255);
analogWrite}
if ((now - last_update) >= UPDATE_INTERVAL) {
= now;
last_update
= dht.readTemperature();
temp_value .setValue(temp_value);
temperature.print("Read temperature ");
Serial.println(temp_value);
Serial
= dht.readHumidity();
humid_value .setValue(humid_value);
humidity.print("Read humidity ");
Serial.println(humid_value);
Serial}
}
Nice, isn’t it? Well, can you see that UNIQUE_ID
thing? That’s the ugly part, and that’s what this article is about.
Into the depths of the home-assistant-integration
The NodeMCU example of the library goes like this (only the relevant parts are here):
[6];
byte mac;
WiFiClient client(mac, sizeof(mac));
HADevice device(client, device);
HAMqtt mqtt("led", false, mqtt); // you can use custom name in place of "led"
HASwitch led
void setup() {
.macAddress(mac);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi.begin(BROKER_ADDRESS);
mqtt}
Now guess what the unique ID of the device will be. I’ll wait…
Was your answer “the MAC address of the ESP board’s WiFi chip”? Yeah, mine too. Except it will be 000000000000
. If you want to install one station in your house, that’s not a big deal. But i want one outside, one in my office, in the kitchen, the bedroom, bathroom, and so one. Having the same unique ID makes it not-so-unique in this case. So I dug deeper in the code of HADevice
.
It has the following constructors:
::HADevice(const char *uniqueId) :
HADevice(uniqueId);
_uniqueId
HADEVICE_INIT{}
uint16_t HADevice::HADevice(const byte *uniqueId, const uint16_t &length) :
(HAUtils::byteArrayToStr(uniqueId, length)),
_uniqueId
HADEVICE_INIT{}
Meanwhile, the WiFi.macAddress(mac)
line calls a function that gets the MAC address of the WiFi chip, and stores the bytes in the mac
array.
So what happens? How does the unique ID become a string of zeroes? Well, the example code calls the second constructor, effectively converting the mac
array (full of zeroes) to a character string full of zeroes.
The solution
As you can’t get the MAC address of the WiFi card outside of a function like setup()
, we can’t rely on the second constructor form. However, the first form is more interesting if you look at the code behind it: if you provide a string as the unique ID, it will be used without any mangling. So let’s update our code a bit:
#include <ESP8266WiFi.h>
#include <ArduinoHA.h>
#define LED_PIN D0
#define BROKER_ADDR IPAddress(192, 168, 0, 17)
#define WIFI_SSID "MyNetwork"
#define WIFI_PASSWORD "MyPassword"
char macaddress[13];
;
WiFiClient client(macaddress);
HADevice device(client, device);
HAMqtt mqtt("led", false, mqtt); // you can use custom name in place of "led"
HASwitch led
void onSwitchStateChanged(bool state, HASwitch* s)
{
(LED_PIN, (state ? HIGH : LOW));
digitalWrite}
void setup() {
[6];
byte mac
.begin(9600);
Serial.println("Starting...");
Serial
.macAddress(mac);
WiFi(LED_PIN, OUTPUT);
pinMode(LED_PIN, LOW);
digitalWrite
// connect to wifi
.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi
while (WiFi.status() != WL_CONNECTED) {
.print(".");
Serial(500); // waiting for the connection
delay}
.println();
Serial.println("Connected to the network");
Serial
(macaddress, 13,
snprintf"%02x%02x%02x%02x%02x%02x",
[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
mac
// set device's details (optional)
.setName("NodeMCU");
device.setSoftwareVersion("1.0.0");
device
// handle switch state
.onStateChanged(onSwitchStateChanged);
led
.begin(BROKER_ADDR);
mqtt}
void loop() {
.loop();
mqtt}
And now you have a unique(ish) ID (well, unless you start tinkering with MAC addresses on you network, but then you are on your own). And if you want to update the unique ID while the software is still running, you can do that, too. But i won’t help you with such perversions.