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 <solution_>`__

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 <https://en.wikipedia.org/wiki/XY_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
<https://github.com/dawidchyrzynski/arduino-home-assistant>`_ library.

.. code-block:: c++

   #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;
   HADevice device(UNIQUE_ID);
   HAMqtt mqtt(client, device);
   HASwitch led("led", false, mqtt);
   HABinarySensor doorbell("doorbell", false, mqtt);
   HASensor<double> temperature("temperature", 0, mqtt);
   HASensor<double> 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 dug 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?  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
============

.. _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 <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;
   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).  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.
