1 changed files with 331 additions and 0 deletions
@ -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). |
Loading…
Reference in new issue