The project is a basic model for building an MQTT client. It should be considered as a reference for more complex projects based on the MQTT protocol on ESP32.
It uses the library AsyncMqttClient
(link: https://github.com/OttoWinter/async-mqtt-client)
(see also https://github.com/khoih-prog/AsyncMQTT_ESP32 with more complete documentation)
The sample accepts one subscribed topics:
ESP32_base/logoTopic
to turn on/off the logo led
and publish on the topics:
Rover/Battery
Rover/Motor1
Rover/Motor2
Rover/Motor3
Rover/Motor4
messages that represents the battery level (in Volt) and the current absorbed by each motor (in Ampere).
The \APPLICATION
folder is designed to better organize your project code.
Enter here the auxiliary functions specific to the application under development, for example the callbacks to turn on and off the LEDs depending on the content of the payload.
The ESP32 board is configured as a STATION on a WiFi network:
- edit the 'WIFI/credentials.h' file with your own 'SSID' and 'Password'
The WiFi object's events are used to check the connection status.
Connection loss situations are handled by automatic reconnection.
Pin '23' is used as a digital output to indicate connection to the WiFi access point. To configure another LED position, change the parameter 'pinWiFiConnected' in the file 'HWCONFIG\hwConfig.h'
A NTP server is used to manage the date and time:
- edit the file 'LOCALTIME/LOCALTime.cpp' with your own NTP parameters
For date and time printing use the 'printLocalTime()' function.
Three MQTT brokers are predefined via their configuration files:
MQTT/broker/mosquitto.h
MQTT/broker/raspi4.h
MQTT/broker/shiftr_io.h
and you can add others respecting the file format and the name of the variables:
#ifndef __BROKERNAME_H
#define __BROKERNAME_H
// Access parameters for the broker NOMEBROKER MQTT
const char *mqttServer = "MQTT_broker_IP_Address_or_url";
const int mqttPort = 1883;
const char *mqttUser = "username_to_access_the_broker";
const char *mqttPassword = "password_to_access_the_broker";
#endif
In the 'main.cpp' file, include only the chosen broker login file, for example:
// your MQTT Broker:
// uncomment one of following #include to set the MQTT broker.
#include <MQTT/broker/shiftr_io.h>
// #include <MQTT/broker/raspi4.h>
// #include <MQTT/broker/mosquitto.h>
IMPORTANT: Do NOT include more than one broker definition file
Depending on the application to be created, you must define the topics to which the client must subscribe to receive data or commands remotely.
The MQTT client must have a unique name on the broker. The unique name is declared in file 'MQTT\custom\mqtt_topics.h', which you can customize:
// MQTT client ID
#define thisClient "ESP32_base"
The topics on which the client publishes data to the broker must then be defined.
Two dictionaries are used for this purpose:
- for subscribed topics ("inputs" for the client):
subscribedTopics
- for publishing topics ("outputs" for the client):
publishedTopics
Both dictionaries have the structure: <key>, <value>
both entries are of type 'String'.
<key>
indicates a simple name to be assigned to the topic- The
<value>
contains the logical path of the topic.
Customization must be done in the file 'MQTT\custom\mqtt_topics.cpp'
- For incoming topics (Subscribed topics) change the function:
void compileSubTopics(Dictionary<String, String> &subTopics)
- For outgoing topics (Published topics) change the function:
void compilePubTopics(Dictionary<String, String> &pubTopics)
Proceed in order:
-
define what information the ESP32 board should receive from the broker and associate subscribed topics, defining a unique name (key) for each topic and its path on the broker.
For example: you want to remotely turn on/off a yellow LED connected to the ESP32;- define the subscribed topic as (key)
"yellowOnOffTopic"
- with logical path
"ESP32_base/yellowTopic"
- From this topic will come the string
"0"
to turn off the LED, or the string"1"
to turn it on.
- define the subscribed topic as (key)
-
similarly, define what information the ESP32 will publish to the broker and associate publisher topics, defining a unique name for each topic and its path on the broker.
For example: You want to remotely notify that a button has been pressed or released;- define the published topic as (key)
"outTopic"
- with logical path
"ESP32_base/output"
- on this topic the ESP32 will send a message about the status of the button
- define the published topic as (key)
-
For subscribed topics, in the
compileSubTopics()
function add the topic to the subscribed topics dictionary using the command:
// Yellow LED control subscribed topic
subTopics.set("yellowOnOffTopic", thisClient "/yellowTopic");
- Repeat step 3. for each subscribed topic required by the application
- for publishing topic, in the function
compilePubTopics()
add the topic to the dictionary of published topics using the command:
// Message Publishing Topic
pubTopics.set("outTopic", thisClient "/output");
- Repeat step 5. for each published topic required by the application.
When the MQTT client starts, subscribed topics will be automatically registered on the broker.
The MQTT client handles traffic with the broker in asynchronous mode, so it is not necessary for the application programmer to worry about the phases of receiving or transmitting data on the topics.
Instead, it is the programmer's responsibility to decide what to do when a message is received on a subscribed topic.
In the MQTT\custom\parseMessage.cpp
file
the function parseMessage()
must be customized.
Referring to the case of the yellow LED shown above, in the parseMessage()
function:
- you will check if it is the topic described by the key
"yellowOnOffTopic"
. If so, then you pass the content of the payload to the auxiliary function programmed by the developer that will manage the information:
// What to do when a message is received
// it is called by mqtt_onMqttMessage() function
void parseMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
// Payload cleanup
// extracts only the first `len` characters of the payload
char data[len + 1];
strncpy(data, payload, len);
// print some information about the received message
printRcvMsg(topic, payload, properties, len, index, total);
// to be customized
// Yellow LED control
// a message has arrived from yellowOnOffTopic
if (strcmp(topic, subscribedTopics.get("yellowOnOffTopic").c_str()) == 0)
{
// Controls on/off yellow LED from payload
driveOnOffYellow(data);
}
}
- in the auxiliary function, if the payload is the string
"0"
then the yellow led is driven off; otherwise, if the payload is the string"1"
then the yellow led is driven on.
The corresponding code structure is:
#include <APPLICATION/application.h>
#include <HWCONFIG/hwConfig.h>
// Controls on/off yellow LED from payload
void driveOnOffYellow(char *data)
{
if (strncmp(data, "0", 1) == 0)
{
digitalWrite(pinYellow, LOW);
Serial.println("Yellow LED turned off");
}
else if (strncmp(data, "1", 1) == 0)
{
digitalWrite(pinYellow, HIGH);
Serial.println("Yellow LED turned on");
}
}
NOTE: In payload string comparison operations (treated as char array) it is recommended to use the strncmp()
comparison function specifying exactly the number of characters to be compared.
Even the publication of a data from ESP32 to the broker is managed asynchronously by the layers of the MQTT library.
In the application, it is sufficient for the programmer to use the mqttClient.publish()
method whenever he wants to publish information about a particular topic.
The mqttClient.publish()
method requires the following parameters:
- the topic on which to publish, for example
"ESP32_base/output"
- the required QOS level (for example, 0)
- whether the message is a retain message or not
- the payload to be transmitted (for example, a char array containing text)
- the size in byte of the payload
and returns the packet ID (other than 0) of the message if it was able to put the payload in the queue of messages to be published to the broker, otherwise it returns error code 0.
For example, to publish the status of the button, the programmer can write:
if (button.fell())
{
const char msgButton[] = "Button pressed";
Serial.println(msgButton);
// publish on topic outTopic
if(mqttClient.connected()) {
uint16_t res = 0;
res = mqttClient.publish(publishedTopics.get("outTopic").c_str(),0,false, msgButton, strlen(msgButton));
}
}
Note that the payload (the information sent on the channel defined by the topic) will be treated by low-level layers as array of byte. Therefore, the payload can also be an int
, a float
or a user-defined data type, as long as its size in bytes can be determined and the data type is recognizable and manageable by those who will receive the information on the other side of the broker.
In the setup()
function it is important to respect the sequence of operations:
- configure all hardware devices
- assign default values to application variables/objects
- create any RTOS tasks
- configure the MQTT client with
configMqttClient(mqttServer, mqttPort, mqttUser, mqttPassword);
- start the WiFi subsystem with
initWiFi_STA();