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