MQTT’s Last Will and Testament (LWT) allows a broker to notify subscribers if a client disconnects unexpectedly.
Let’s see it in action. Imagine a simple temperature sensor publishing its readings. We want to know immediately if this sensor goes offline.
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected successfully with result code " + str(rc))
# Subscribe to the will message topic
client.subscribe("device/status/will")
else:
print("Connection failed with result code " + str(rc))
def on_message(client, userdata, msg):
print(f"Received message on topic {msg.topic}: {msg.payload.decode()}")
# Create an MQTT client instance
client = mqtt.Client("publisher_client_id")
# Set the Last Will and Testament
# This message will be published by the broker if the client disconnects uncleanly.
client.will_set("device/status/will", payload="Device offline", qos=1, retain=True)
# Assign callback functions
client.on_connect = on_connect
client.on_message = on_message
# Connect to the MQTT broker
client.connect("localhost", 1883, 60)
# Start the network loop
client.loop_start()
# Publish a message to indicate the device is online
client.publish("device/status/online", payload="Device online", qos=1, retain=True)
print("Published device online status.")
# Simulate the device running and publishing data
import time
for i in range(5):
temperature = 20 + i
client.publish("device/temperature", payload=str(temperature), qos=1)
print(f"Published temperature: {temperature}")
time.sleep(5)
# Simulate an unexpected disconnection
print("Simulating disconnection...")
client.loop_stop()
client.disconnect()
Now, let’s set up a subscriber to receive the will message.
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Subscriber connected successfully with result code " + str(rc))
# Subscribe to the topic where the will message is published
client.subscribe("device/status/will")
# Also subscribe to the online status to see when it comes back
client.subscribe("device/status/online")
else:
print("Subscriber connection failed with result code " + str(rc))
def on_message(client, userdata, msg):
print(f"Received message on topic {msg.topic}: {msg.payload.decode()}")
# Create an MQTT client instance for the subscriber
subscriber_client = mqtt.Client("subscriber_client_id")
# Assign callback functions
subscriber_client.on_connect = on_connect
subscriber_client.on_message = on_message
# Connect to the MQTT broker
subscriber_client.connect("localhost", 1883, 60)
# Start the network loop
subscriber_client.loop_forever()
When you run the publisher and then interrupt it (e.g., Ctrl+C), the broker, upon detecting the unclean disconnect, will publish the LWT message ("Device offline") to the "device/status/will" topic. The subscriber will then receive this message. If the publisher reconnects cleanly, it will publish its "Device online" status, which the subscriber will also see.
The core problem LWT solves is the "unknown state" of a client. Without it, a subscriber might continue acting as if a device is online and operational, processing stale data or attempting to send commands that will never be received. LWT provides an immediate, reliable signal that the connection has been lost. The broker acts as the trusted intermediary, holding onto the will message until the client establishes a connection. When that connection is broken without a clean DISCONNECT packet, the broker fulfills its promise by sending the pre-registered will message to any subscribers of the will topic.
The will_set function takes the topic, payload, Quality of Service (QoS), and a retain flag. qos=1 ensures the message is delivered at least once. retain=True means the broker will store this last will message and deliver it to any new subscribers to the will topic immediately upon subscription, even if the client that set the will is no longer connected. This is crucial for clients that might subscribe after a device has already gone offline.
The client.loop_start() and client.loop_stop() methods manage the network traffic in a separate thread, allowing your main program to continue executing. client.loop_forever() in the subscriber is a blocking call that keeps the client connected and processing messages indefinitely.
When setting the LWT, the client.will_set() call must happen before client.connect(). This ensures the broker knows about the will message as soon as the client establishes its connection. The broker will only publish the will message if the client disconnects uncleanly. A clean disconnect, where the client sends a DISCONNECT packet, will not trigger the LWT.
The payload of the LWT message is what gets sent. It’s common practice to send a status update like "offline", "disconnected", or a specific error code. The retain flag on the will message is particularly powerful. If set to True, the broker stores the last will message published on that topic. Any client that subscribes to this topic after the LWT has been published will immediately receive it. This is invaluable for getting a snapshot of the system state when you first join a network.
The distinction between a clean and unclean disconnect is fundamental to LWT. A clean disconnect implies the client intentionally closed the connection, so there’s no need to trigger the will message. An unclean disconnect, such as a power failure, network interruption, or application crash, is what the LWT is designed to catch. The broker infers an unclean disconnect if the client’s keep-alive timer expires without any communication from the client.
The next logical step after receiving a device offline notification is often to try and re-establish connectivity or to alert an operator.