Add post about using NodeMCU MAC address as the MQTT unique ID
This commit is contained in:
		
							
								
								
									
										331
									
								
								content/blog/using-esp8266-macaddress-as-mqtt-unique-id.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								content/blog/using-esp8266-macaddress-as-mqtt-unique-id.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,331 @@ | ||||
| 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, 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 | ||||
| <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 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 <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). | ||||
		Reference in New Issue
	
	Block a user