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 is on my ToDo list for a looong time). 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 quicly 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. .. code-block:: c++ #include #include #include #include #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; HADevice device(UNIQUE_ID); HAMqtt mqtt(client, device); HASwitch led("led", false, mqtt); HABinarySensor doorbell("doorbell", false, mqtt); HASensor temperature("temperature", 0, mqtt); HASensor humidity("humidity", 0, mqtt); unsigned long last_update = 0; DHT dht(DHT_PIN, DHT_TYPE); bool doorbell_pushed = false; unsigned long doorbell_push_time = 0; bool ringing = false; void onSwitchStateChanged(bool state, HASwitch* s) { digitalWrite(LED_PIN, (state ? LOW : HIGH)); } void setup() { sensor_t sensor; Serial.begin(9600); Serial.println("Starting..."); dht.begin(); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH); pinMode(DOORBELL_PIN, INPUT); pinMode(BUZZER_PIN, OUTPUT); // connect to wifi WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); // waiting for the connection } Serial.println(); Serial.print("Connected to the network, IP address: "); Serial.println(WiFi.localIP()); // set device's details (optional) device.setName(BROKER_NAME); device.setSoftwareVersion("0.3.0"); // handle switch state led.onStateChanged(onSwitchStateChanged); temperature.setUnitOfMeasurement("°C"); humidity.setUnitOfMeasurement("%"); mqtt.begin(BROKER_ADDR #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); mqtt.loop(); if ((doorbell_state == HIGH) && !doorbell_pushed) { doorbell_pushed = true; doorbell_push_time = now; } else if ((doorbell_state == LOW) && doorbell_pushed) { doorbell_pushed = false; doorbell.setState(false); ringing = false; Serial.println("Doorbell released"); analogWrite(BUZZER_PIN, 0); delay(100); } if (doorbell_pushed && !ringing && (now - doorbell_push_time > 100)) { ringing = true; Serial.println("Doorbell pushed"); doorbell.setState(true); analogWrite(BUZZER_PIN, 255); } if ((now - last_update) >= UPDATE_INTERVAL) { last_update = now; temp_value = dht.readTemperature(); temperature.setValue(temp_value); Serial.print("Read temperature "); Serial.println(temp_value); humid_value = dht.readHumidity(); humidity.setValue(humid_value); Serial.print("Read humidity "); Serial.println(humid_value); } } 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): .. code-block:: c++ byte mac[6]; WiFiClient client; HADevice device(mac, sizeof(mac)); HAMqtt mqtt(client, device); HASwitch led("led", false, mqtt); // you can use custom name in place of "led" void setup() { WiFi.macAddress(mac); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); mqtt.begin(BROKER_ADDRESS); } 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 dag deeper in the code of ``HADevice``. It has the following constructors: .. code-block:: c++ HADevice::HADevice(const char *uniqueId) : _uniqueId(uniqueId); HADEVICE_INIT {} uint16_t HADevice::HADevice(const byte *uniqueId, const uint16_t &length) : _uniqueId(HAUtils::byteArrayToStr(uniqueId, length)), 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? The example code calls the second constructor, effectively converting the ``mac`` array (full of zeroes) to a character string full of zeroes. The solution ============ .. _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: .. code-block:: c++ #include #include #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; HADevice device(macaddress); HAMqtt mqtt(client, device); HASwitch led("led", false, mqtt); // you can use custom name in place of "led" void onSwitchStateChanged(bool state, HASwitch* s) { digitalWrite(LED_PIN, (state ? HIGH : LOW)); } void setup() { byte mac[6]; Serial.begin(9600); Serial.println("Starting..."); WiFi.macAddress(mac); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); // connect to wifi WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); // waiting for the connection } Serial.println(); Serial.println("Connected to the network"); snprintf(macaddress, 13, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); // set device's details (optional) device.setName("NodeMCU"); device.setSoftwareVersion("1.0.0"); // handle switch state led.onStateChanged(onSwitchStateChanged); mqtt.begin(BROKER_ADDR); } void loop() { mqtt.loop(); } 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).