333 lines
12 KiB
ReStructuredText
333 lines
12 KiB
ReStructuredText
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.
|