Add post about using NodeMCU MAC address as the MQTT unique ID
This commit is contained in:
parent
b1366ba1f3
commit
959c614a1d
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).
|
Loading…
Reference in New Issue
Block a user