The world of Internet of Things (IoT) is characterized by interconnected devices communicating seamlessly, creating a network that powers smart homes, industrial automation, and various other applications. At the heart of this communication lies MQTT (Message Queuing Telemetry Transport), a lightweight and efficient protocol designed for scenarios with low bandwidth, high latency, or unreliable networks.
In the realm of IoT, one of the critical aspects is the ability to monitor the online or offline status of devices in real-time. Imagine a scenario where an ESP32-based sensor, a vital component of your IoT project, suddenly goes offline. The repercussions could range from data inconsistencies to operational disruptions. This is where the Last Will Message feature in MQTT comes into play.
In this post, I will show you how to use the MQTT protocol to detect an ESP32 board online or offline. Specifically, I will guide you on using the LWT Message to check the state of the ESP32’s connection. Before starting, we need some interesting things:
- We need an MQTT broker: A free broker is good for testing, and we can use broker.hivemq.com.
- We need a dashboard that can display the state of the ESP32. You can use my own dashboard at this address: “https://luuvachiase.net/dashboard”. I provide a guest account for testing with the username: guest and password: dashboard. To create an account and use the dashboard, the user manual is provided [here].
- ESP32: We can use a programmable virtual ESP32 on Wokwi.com. The completed code is shown in Part IV of this post.
Let’s get started.
I. What is MQTT and Last Will and Testament (LWT)?
1.1 MQTT
MQTT, with its publish-subscribe model, has become the de facto choice for IoT communication. It allows devices to exchange information in a reliable and efficient manner. MQTT’s lightweight nature makes it suitable for resource-constrained devices, a common characteristic of IoT devices.
1.2 Understanding Last Will and Testament (TWT)
At the core of our discussion is the LWT message, a feature within the MQTT protocol designed to specify a message that will be automatically send to a subscriber by the broker in the cases of unexpected disconnection detected. This feature acts as a digital heartbeat, signaling its status to the MQTT broker and other connected devices.
Situations in which the Will Message is published include, but are not limited to:
- An I/O error or network failure detected by the Server.
- The Client fails to communicate within the Keep Alive time.
- The Client closes the Network Connection without first sending a DISCONNECT Packet.
- The Server closes the Network Connection because of a protocol error.
Let’s analysis a CONNECT and PUBLISH packet to see how it can help us for detect the state of device connection.
CONNECT Packet
lastWillQos: It is the same QoS of a publish message, including different levels.
lastWillMessage: The Will Message defines the Application Message that is to be published to the Will Topic.
lastWillFlag: This bit is set to 1 this indicates that, if the Connect request is accepted, a Will Message MUST be stored on the Server and associated with the Network Connection. The Will Message MUST be published when the Network Connection is subsequently closed unless the Will Message has been deleted by the Server on receipt of a DISCONNECT Packet
If the Will Flag is set to 1:
- If Will Retain is set to 0, the Server MUST publish the Will Message as a non-retained message.
- If Will Retain is set to 1, the Server MUST publish the Will Message as a retained message.
lastWillRetain: This bit specifies if the Will Message is to be Retained when it is published.
PUBLISH packet
When a new subscription is established, the last retained message, if any, on each matching topic name MUST be sent to the subscriber.
If the Server receives a QoS 0 message with the RETAIN flag set to 1 it MUST discard any message previously retained for that topic. It SHOULD store the new QoS 0 message as the new retained message for that topic, but MAY choose to discard it at any time – if this happens there will be no retained message for that topic.
Retained messages are useful where publishers send state messages on an irregular basis. A new subscriber will receive the most recent state. (MQTT 3.1.1 document)
With these features, LWT message is clearly a promising technique for detect a device “online” of “offline”.
II. Detecting ESP32 online status
This part will show you how to use a dashboard to detect your device online or offline.
![](https://luuvachiase.net/wp-content/uploads/2024/01/ESP32onlineCheck.png)
III. Setting Up MQTT and Implementing LWT on ESP32
We deeply analysis two main functions that help us detect the state of a device, including connect() and publish() in PubSubClient.h
Connect function:
1 |
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession); |
The code above show the connect() function in PubSubClient.h, then an example is provided bellows:
1 2 3 4 5 6 7 8 9 10 11 |
StaticJsonDocument<50> lwtJson; lwtJson["stt"] = 0; // Customize the JSON data as needed // Convert the JSON object to a JSON string char lwtMessage[200]; serializeJson(lwtJson, lwtMessage); if (client.connect(clientId.c_str(),lwt_topic, 2, true, lwtMessage)) { Serial.println("connected"); } |
When connect() is called, we need to specify the willTopic, willRetain and willMessage.
willTopic: will be created based on mac address, for example “LWT_00:11:22:33:44:55”
willMessage: “stt:0”.
Publish function:
1 |
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained); |
1 2 3 4 5 6 7 8 9 10 |
RtJson["stt"] = 1; // Customize the JSON data as needed // Convert the JSON object to a JSON string String RtMessage; serializeJson(RtJson, RtMessage); // Once connected, publish an announcement... const char* pl_RtMessage = RtMessage.c_str(); client.publish(lwt_topic, pl_RtMessage, true); // ... and resubscribe client.subscribe(stopic); |
Similar to connect() function, this is a standard function in PubSubClient.h. In this case, we need to specify bit retained=true as shown in the example code.
IV. Completed code and video demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
#include <WiFi.h> #include <PubSubClient.h> #include <ArduinoJson.h> const char* ssid = "Wokwi-GUEST"; const char* password = ""; const char* mqtt_server = "broker.hivemq.com"; String macAddressString; // Set MQTT topics using the MAC address as a string char lwt_topic[30]; char macAddress[18]; uint8_t mac[6]; bool cn_check = false; WiFiClient espClient; PubSubClient client(espClient); unsigned long lastMsg = 0; void setup_wifi() { Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); // Obtain the MAC address as a string WiFi.macAddress(mac); sprintf(macAddress, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); // Convert macAddress to a string for (int i = 0; i < 6; i++) { char hexChar[8]; sprintf(hexChar, "%02X", mac[i]); macAddressString += hexChar; } Serial.print("MAC Address: "); Serial.println(macAddress); //Serial.println(lwt_topic); sprintf(lwt_topic, "lwt_topic_%s", macAddress); // Generate LWT topic based on Mac address Serial.println(lwt_topic); } void connectBroker() { // Loop until we're reconnected while (!client.connected()) { Serial.print("Attempting MQTT connection..."); while (WiFi.status() != WL_CONNECTED) { Serial.print("Wifi re-connect successfully"); } // Create a random client ID String clientId = "ESP32Client-"+macAddressString; Serial.println(clientId); StaticJsonDocument<50> lwtJson; lwtJson["stt"] = 0; // Customize the JSON data as needed // Convert the JSON object to a JSON string char lwtMessage[200]; serializeJson(lwtJson, lwtMessage); if (client.connect(clientId.c_str(),lwt_topic, 2, true, lwtMessage)) { Serial.println("Connected to Broker and setup state message"); cn_check =true; DynamicJsonDocument RtJson(50); RtJson["stt"] = 1; // Customize the JSON data as needed // Convert the JSON object to a JSON string String RtMessage; serializeJson(RtJson, RtMessage); // Once connected, publish an announcement... const char* pl_RtMessage = RtMessage.c_str(); client.publish(lwt_topic, pl_RtMessage, true); } else { cn_check =false; Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); // Wait 5 seconds before retrying delay(5000); } } } void setup() { // put your setup code here, to run once: Serial.begin(115200); setup_wifi(); client.setServer(mqtt_server, 1883); } void loop() { if (!client.connected()) { cn_check =false; connectBroker(); } client.loop(); delay(200); } |
Video demo:
REFERENCES
[1] https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html
[2] https://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament/