In the second post in this series, I will setup two ESP8266 microcontrollers with MQTT publishing through a Raspberry Pi-hosted Mosquitto broker. The idea is that the microcontrollers will send sensor data (like temperatures or other events) that one or several MQTT subscribers can act on.
For an overview of MQTT, Mosquitto and paho-mqtt, see my previous post:
MQTT client libraries for Arduino
I have three different WiFi-based microcontrollers at my disposal that I can use for IP-based communication of sensor data.
They are all based on the ESP8266-chip that has a TCP/IP WiFi-stack, and each development board provides a USB-connection for programming and debugging and GPIO pins for connecting sensors and other electronics.
I will use the Arduino IDE for programming the microcontrollers. Though the brands of the boards are different, the “General ESP8266 board”-settings in the Arduino IDE works well for all of them in my examples.
There are several libraries available for the Arduino IDE that let’s you program an MQTT client. I will use the PubSub client library in this example.
The PubSub client library
To get started we need a WiFiClient object (from the ESP8266WiFi library) that sets up the WiFi communication. Then I create a PubSubClient and pass in the required parameters (the host name/ip-address of the broker + port and a reference to the WiFiClient). Calling connect() with an arbitrary client name on the MQTTClient object connects to the broker, and then we can add subscriptions to any number of topics with multiple subscribe() calls.
In the loop method, a publish() is made from the PubSubClient to the broker. mqttClient.loop() will check for new incoming messages and trigger the calllback if there are any messages to process. This method also maintains the connection to the broker so it should be called regularly. The sketch looks like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// This is a minimal example on MQTT publish and subscribe from an ESP8266 board | |
// to an MQTT broker (I have used a local Mosquitto running on a Raspberry Pi) | |
// This example uses the PubSub client library (https://github.com/knolleary/pubsubclient) | |
// Install it in the Arduino IDE before compiling the sketch | |
#include <ESP8266WiFi.h> | |
#include <PubSubClient.h> | |
#include "WIFI_and_broker_parameters.h" | |
#define CLIENT_NAME "ESP8266_1" // just a name to identify this client | |
WiFiClient wifiClient; | |
PubSubClient mqttClient(BROKER_IP,BROKER_PORT,wifiClient); | |
void setup() | |
{ | |
Serial.begin(9600); | |
mqttClient.setCallback(callback); | |
WiFi.begin(WLAN_SSID, WLAN_PASS); | |
connectToWiFiAndBroker(); | |
} | |
void connectToWiFiAndBroker() | |
{ | |
Serial.print("Connecting to WIFI"); | |
while (WiFi.status() != WL_CONNECTED) | |
{ | |
Serial.print("."); | |
delay(1000); | |
} | |
Serial.println("Connected to WIFI!"); | |
Serial.println("Connecting to broker"); | |
while (!mqttClient.connect(CLIENT_NAME)) | |
{ | |
Serial.print("."); | |
delay(1000); | |
} | |
Serial.println("Connected to broker!"); | |
mqttClient.subscribe("InterestingTopics/#"); | |
} | |
#define MILLISEC_DELAY_BETWEEN_PUBLISH 10000 | |
unsigned long lastTime = 0; | |
unsigned long currentValue = 1; | |
char msg[50]; | |
void loop() | |
{ | |
if (!mqttClient.connected()) | |
{ | |
connectToWiFiAndBroker(); | |
} | |
mqttClient.loop(); | |
if(millis() – lastTime > MILLISEC_DELAY_BETWEEN_PUBLISH) | |
{ | |
lastTime = millis(); | |
Serial.println("Publishing a new value"); | |
snprintf (msg, 75, "%ld", currentValue); | |
Serial.println(currentValue); | |
mqttClient.publish("ESP8266/CurrentValue", msg); | |
currentValue++; | |
} | |
} | |
void callback(char* topic, byte* payload, unsigned int length) | |
{ | |
Serial.println("Message received: "); | |
Serial.println(topic); | |
for (int i = 0; i < length; i++) | |
{ | |
Serial.print((char)payload[i]); | |
} | |
Serial.println(""); | |
} |
After this sketch is uploaded to an ESP8266-board, we can run a subscriber on the Raspberry Pi (or any other machine on the local LAN):
mosquitto_sub -h 192.168.1.16 -t 'ESP8266/#'
Every tenth second, there should be a new value coming to the subscriber and it should be echoed in the terminal.
If we make a publish call from the RPi with a topic that the ESP8266 listens to:
mosquitto_pub -h 192.168.1.16 -t 'InterestingTopics/Hellos' -m 'Hello from RPi'
there will be a serial output in the Arduino IDE from the ESP8266 saying “Hello from RPi”.
Setting up a real environment with sensors
In my IoT-environment I have the ESP8266 boards placed in two different places where they measure different things:
- Board 1: Placed in the garage. Measures indoor- and outdoor temperatures and humidity levels.
- Board 2: Placed on the ground floor. Measures indoor temperature and humidity and also the soil moisture level for one houseplant.
These 7 measurements can be structured in a topic hierarchy:
Home/Outdoor/Temperature
Home/Outdoor/Humidity
Home/Garage/Temperature
Home/Garage/Humidity
Home/GroundFloor/Temperature
Home/GroundFloor/Humidity
Home/GroundFloor/PlantStatus
Setting up the garage ESP8266 board
The first board is located in our garage. It has two DHT22 temperature/humidity sensors attached. One DHT22 is placed on the outside wall and its wires goes through a ventilation shaft to the ESP8266 board that is located on the inside.
The sketch for this board looks like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// This is an example on MQTT publish from an ESP8266 board | |
// to an MQTT broker (I have used a local Mosquitto running on a Raspberry Pi) | |
// This example uses the PubSub client library (https://github.com/knolleary/pubsubclient) | |
// Install it in the Arduino IDE before compiling the sketch | |
// Sensor values are fetched from an indoor DHT22 sensor and an outdoor DHT22 sensor | |
#include <ESP8266WiFi.h> | |
#include <PubSubClient.h> | |
#include "WIFI_and_broker_parameters.h" | |
// | |
// WIFI and MQTT setup | |
// | |
#define CLIENT_NAME "GarageClient" | |
WiFiClient wifiClient; | |
PubSubClient mqttClient(BROKER_IP,BROKER_PORT,wifiClient); | |
// | |
// Sensor setup | |
// | |
#include <DHT.h> | |
#define DHTPIN_OUTDOOR 4 | |
#define DHTTYPE_OUTDOOR DHT22 | |
DHT dht_outdoor(DHTPIN_OUTDOOR, DHTTYPE_OUTDOOR); | |
#define DHTPIN_INDOOR 5 | |
#define DHTTYPE_INDOOR DHT22 | |
DHT dht_indoor(DHTPIN_INDOOR, DHTTYPE_INDOOR); | |
void setup() | |
{ | |
Serial.begin(9600); | |
WiFi.begin(WLAN_SSID, WLAN_PASS); | |
dht_outdoor.begin(); | |
dht_indoor.begin(); | |
} | |
#define SECONDS_BETWEEN_MEASUREMENTS 600 | |
unsigned long lastTime = 0; | |
bool firstTime = true; | |
void loop() | |
{ | |
if ( firstTime || (millis() – lastTime > SECONDS_BETWEEN_MEASUREMENTS*1000) ) | |
{ | |
firstTime = false; | |
lastTime = millis(); | |
if (!mqttClient.connected()) | |
{ | |
connectToWiFiAndBroker(); | |
} | |
mqttClient.loop(); | |
float h_outdoor = dht_outdoor.readHumidity(); | |
float t_outdoor = dht_outdoor.readTemperature(); | |
float h_indoor = dht_indoor.readHumidity(); | |
float t_indoor = dht_indoor.readTemperature(); | |
publishFloatValue(h_outdoor,"Home/Outdoor/Humidity"); | |
publishFloatValue(t_outdoor,"Home/Outdoor/Temperature"); | |
publishFloatValue(h_indoor,"Home/Garage/Humidity"); | |
publishFloatValue(t_indoor,"Home/Garage/Temperature"); | |
} | |
} | |
void connectToWiFiAndBroker() | |
{ | |
Serial.print("Connecting to WIFI"); | |
while (WiFi.status() != WL_CONNECTED) | |
{ | |
Serial.print("."); | |
delay(1000); | |
} | |
Serial.println("Connected to WIFI!"); | |
Serial.println("Connecting to broker"); | |
while (!mqttClient.connect(CLIENT_NAME)) | |
{ | |
Serial.print("."); | |
delay(1000); | |
} | |
Serial.println("Connected to broker!"); | |
} | |
char msg[50]; | |
void publishFloatValue(float value, char* topic) | |
{ | |
if (isnan(value)) | |
{ | |
Serial.println("Invalid value!"); | |
return; | |
} | |
Serial.println("Publishing a new value"); | |
ftoa(msg,value); | |
Serial.println(msg); | |
mqttClient.publish(topic, msg); | |
} | |
char *ftoa(char *buffer, float f) | |
{ | |
char *returnString = buffer; | |
long integerPart = (long)f; | |
itoa(integerPart, buffer, 10); | |
while (*buffer != '\0') buffer++; | |
*buffer++ = '.'; | |
long decimalPart = abs((long)((f – integerPart) * 100)); | |
itoa(decimalPart, buffer, 10); | |
return returnString; | |
} |
Setting up the groundfloor ESP8266 board
The ground floor ESP8266 board has one DHT22 sensor for temperature and humidity and also a soil moisture sensor. The moisture sensor gives an analogue 0-3.3V signal on moisture level but also a digital 0v/3.3V when the moisture level reaches a certain value (that can be adjusted by a rotary resistor). As the ESP8266 boards only can handle max 1V as analogue signal level but max 3.3V as digital input, I have used the digital signal.
The sketch is very similar to the GarageClient. What’s different is the sensor values that are fetched in the Arduino loop:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void loop() | |
{ | |
if ( firstTime || (millis() – lastTime > SECONDS_BETWEEN_MEASUREMENTS*1000) ) | |
{ | |
firstTime = false; | |
lastTime = millis(); | |
if (!mqttClient.connected()) | |
{ | |
connectToWiFiAndBroker(); | |
} | |
mqttClient.loop(); | |
float h_indoor = dht_indoor.readHumidity(); | |
float t_indoor = dht_indoor.readTemperature(); | |
publishFloatValue(h_indoor,"Home/GroundFloor/Humidity"); | |
publishFloatValue(t_indoor,"Home/GroundFloor/Temperature"); | |
int moistureWarning = digitalRead(14); | |
if (moistureWarning == 1) | |
{ | |
mqttClient.publish("Home/GroundFloor/PlantStatus", "Please water the plant!"); | |
} | |
else | |
{ | |
mqttClient.publish("Home/GroundFloor/PlantStatus", "Plant is ok!"); | |
} | |
} | |
} |
Setting up the subscriber
For the Python script created in Part 1 to handle these new data, I only need to change what topics to subscribe to. As I want to listen to all “Home” messages, I simply subscribe to “Home/#”:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import paho.mqtt.client as mqtt | |
import datetime | |
def on_connect(client, userdata, flags, rc): | |
print("Connected with result code "+str(rc)) | |
client.subscribe("Home/#") | |
def on_message(client, userdata, msg): | |
print(str(datetime.datetime.now()) + ": " + msg.topic + " " + str(msg.payload)) | |
client = mqtt.Client() | |
client.on_connect = on_connect | |
client.on_message = on_message | |
client.connect("192.168.1.16", 1883, 60) | |
client.loop_forever() |
With the publishers set up to send data every 10 minutes, the output from the subscriber looks like this:
2016-06-24 15:04:15.363821: Home/GroundFloor/Humidity b'50.90' 2016-06-24 15:04:15.407021: Home/GroundFloor/Temperature b'25.10' 2016-06-24 15:04:15.410185: Home/GroundFloor/PlantStatus b'Plant is ok!' 2016-06-24 15:05:45.564683: Home/Outdoor/Humidity b'82.0' 2016-06-24 15:05:45.612739: Home/Outdoor/Temperature b'19.79' 2016-06-24 15:05:45.624698: Home/Garage/Humidity b'61.40' 2016-06-24 15:05:45.631266: Home/Garage/Temperature b'23.39' 2016-06-24 15:14:10.216790: Home/GroundFloor/Humidity b'50.90' 2016-06-24 15:14:10.257263: Home/GroundFloor/Temperature b'25.10' 2016-06-24 15:14:10.261389: Home/GroundFloor/PlantStatus b'Plant is ok!'
Next step
The next step will be to extend the Python subscriber so that it persists all incoming data in a database. This will be the topic for the next post in this series:
All code for these examples are available from GitHub:
Dear Sir, May I ask a question about the file to be included i.e. “WIFI_and_brocker_parameters.h” for what is this file for and where to get it? Appreciate your explanation.
LikeLike
At the bottom of the blog post, there is link to GitHub where the related code can be fetched. Specifically, the header-file exists here: https://github.com/LarsBergqvist/MQTT_IoT/tree/master/ESP8266_PubSub
The file contains constants that are specific for a local environment, WIFI-name, password etc, and you should change these values so that they match your own environment.
LikeLike