micropython / micropython-lib

Core Python libraries ported to MicroPython
Other
2.37k stars 994 forks source link

umqtt last will is not sent #304

Closed RichardMore closed 1 week ago

RichardMore commented 6 years ago

Last will message is not sent when Nodemcu loses power.

Code:

client = MQTTClient(client_id=MQTT_CLIENT, server=MQTT_SERVER, port=MQTT_PORT, user=MQTT_USER, password=MQTT_PASS)
client.set_last_will('status', 'disconnected', retain=True)
client.set_callback(sub_cb)
client.connect()
client.subscribe(MQTT_ROOT + '#')
mqtt_publish('status', 'connected', retain=True)

The "connected" message is delivered after connection and topic subscribe, but no "disconnected" message ever.

SpotlightKid commented 6 years ago

I can't reproduce this, i.e. works perfectly here.

Which MQTT broker are you using and which MQTT client do you have subscribed to the statustopic to receive the last will message? Have you checked the broker server logs?

It is the responsibility of the server to publish the last will message and of course only clients subscribed (in your example) to the status topic will receive it. Depending on what value MQTT_ROOT has, your client code above does not subscribe to this topic, so it will not receive the disconnected messages on re-connect, even if retain=True is set for the last will.

For reference, here's the code, with which I tested this. If MQTT_ROOT is changed to '' (empty string), this client will receive messages to the status topic and so, on re-connect, will receive its own last will message from the previous disconnect.

from umqtt.simple import MQTTClient

MQTT_CLIENT = 'upy'
MQTT_SERVER = '127.0.0.1'
MQTT_ROOT = '/'

def sub_cb(topic, msg):
    print((topic, msg))

client = MQTTClient(client_id=MQTT_CLIENT, server=MQTT_SERVER)
client.set_last_will('status', 'disconnected', retain=True)
client.set_callback(sub_cb)
client.connect()
client.subscribe(MQTT_ROOT + '#')
client.publish('status', 'connected', retain=True)

try:
    while True:
        client.wait_msg()
except KeyboardInterrupt:
    pass
RichardMore commented 6 years ago

I am using the public Cloudmqtt and I am checking the messages through the websocket UI which shows every topic. Just as a side note, a Ubutntu based server that has a mosquitto service attached to it has a las will as well and I receive those, so probably the broker side is good from Cloudmqtt. Here is my full code I am testing with:

from umqtt.simple import MQTTClient
import wifi

# init program
wifi.do_connect()

MQTT_SERVER = 'm21.cloudmqtt.com'
MQTT_PORT = <PORT>
MQTT_USER = <USER>
MQTT_PASS = <PASSWORD>
MQTT_TIMEOUT = 30
MQTT_CLIENT = <ID>
MQTT_ROOT = <ROOT>

client = None
topic_l1 = 'sensors/'

# helper methods
def mqtt_publish(topic_l2, payload, retain=False, qos=0):
    client.publish(MQTT_ROOT + topic_l1 + topic_l2, payload, retain, 0)
    print('mqtt published msg: %s; to topic: %s' % (payload, MQTT_ROOT + topic_l1 + topic_l2))

def sub_cb(topic, msg):
    print('got msg')

client = MQTTClient(client_id=MQTT_CLIENT, server=MQTT_SERVER, port=MQTT_PORT, user=MQTT_USER, password=MQTT_PASS)
client.set_last_will('status', 'disconnected', retain=True)
client.set_callback(sub_cb)
client.connect()
client.subscribe(MQTT_ROOT + '#')
print('mqtt connected to %s, subscribed to %s topic' % (MQTT_SERVER, MQTT_ROOT))
mqtt_publish('status', 'connected', retain=True)

try:
    while True:
        client.wait_msg()
except KeyboardInterrupt:
    pass

Also I am running the code on a NodeMCU ESP8266 board.

Thank you.

SpotlightKid commented 6 years ago

I am using the public Cloudmqtt and I am checking the messages through the websocket UI which shows every topic. Just as a side note, a Ubutntu based server that has a mosquitto service attached to it has a las will as well and I receive those, so probably the broker side is good from Cloudmqtt.

I don't follow. If it works with the mosquito server (with which I tested it successfully as well), but not with Cloumqtt, my guess would be that Cloudmqtt is to blame.

Also I am running the code on a NodeMCU ESP8266 board.

I've only tested it with the micropython unix port so far, but knowing the umqtt.simple code very well, I don't see were this would make any difference. If the client sends the last will topic and message successfully when connecting to the broker, everything else is up to the broker.

SpotlightKid commented 5 years ago

There was a discussion on this topic at the forum here:

https://forum.micropython.org/viewtopic.php?f=2&t=5242

The gist of it is: if no keepalive timout is set when connecting and the connection to the client is lost, e.g in the case of power loss, and the client doesn't / can't close the socket properly, mosquitto does not realize the connection is gone, until either it tries to publish a message to the client or the client reconnects. Then it will publish the last will.

The lesson from this is: if you want the last will to be sent reliably as soon as possible, set a keepalive timeout and use client.ping() regularly within the timeout period.

jonnor commented 2 weeks ago

Setting keepalive seems to avoid this problem. Proposing to close this issue.

projectgus commented 1 week ago

Setting keepalive seems to avoid this problem. Proposing to close this issue.

Note also this refers to a keepalive timeout on the server. Reliable last will delivery depends on the server noticing the client has gone away.