Waiting for MQTT QOS ACK

Hi gang. I’m trying not to reinvent the wheel with a workaround, so I wanted to sanity check with the community first.

There are 4 pieces involved: OH, Telegram bot, MQTT, and RPi connected to a relay, listening on MQTT.

Here’s the current flow of operations:

  1. I text the message ‘OPEN’ to tg bot
  2. OH rule listens for state change on tg bot
  3. OH rule: If bot message is ‘OPEN’, publish ‘OPEN_REQ’ on MQTT topic, qos=1, reply to tg ‘Open Requested’
  4. RPi is checking topic every 1s. If message received on the topic is ‘OPEN_REQ’, trigger relay.

The issue is with step 3. The ‘Open requested’ message is always sent to TG bot regardless of anything else (ie: mqtt broker down, rpi not responding, etc). I would like the tg message to be sent only when the MQTT broker replies back with the publish ack (qos=1).

I’m uncertain how to wait for the ack in the OH rules. Additionally, I don’t see any way to script this since there is only 1 getActions-action which is publishMQTT which doesn’t appear to have a return value.

My workaround is to use several OH rules. The first rule does like above and publishes ‘OPEN_REQ’ to MQTT but doesn’t reply to tg bot. The Pi does its check, as above, and would then publish back ‘OPEN_ACK’ to same topic. Another OH rule would trigger on topic update and if equal to ‘OPEN_ACK’, then send TG bot message.

Is there some way to do this all with 1 rule/script? Multiple rules seems not ideal.

Thanks!

Why polling? That’s not how MQTT is designed to work.

There are two types of MQTT messages (for our purposes here), retained and not retained. A retained message lives forever on the broker until it’s replaced with a new retained message. Not retained messages are ephemeral. If the subscribers are not subscribed and receiving messages when the message is sent, it’s gone.

Why that matters here is that the only way your polling script can reliably receive the messages is if they are sent as retained because the likelihood that it will be connected when OH publishes the message is slim to none. But, this means that when ever your polling script connects to the broker, it will receive that retained message, even if it was sent days or weeks ago (the latest version of MQTT supports a timeout for retained messages but I don’t know if Mosquitto supports it and am pretty sure OH does not.

When working with MQTT, it’s far better to have your client up and always listening rather than polling. Then messages that represent commands like these can be sent without the retained flag and you won’t face weird things like lights turning on just because your RPi rebooted and the like.

The QOS messaging in MQTT are not available (nor should they be available) to you. In fact, the ack you are talking about is only between the client and the broker, not between the broker publisher and the subscriber. So even if you could get insight into that ack, all it means is the message made it as far as the broker which doesn’t help you. And if that connection fails, OH will mark the Broker Thing and all the Things under the Broker Thing as OFFLINE.

However, MQTT supports a LWT feature. When a client connects to the MQTT broker it registers a LWT topic and message. When the client disconnects from the broker or otherwise stops responding the broker will publish the LWT message to the LWT topic, informing the world that the client is offline. If you make the client on the RPi a service instead of polling, you can have it publish an ONLINE message to the LWT topic and register an LWT OFFLINE message and in OH you can configure the Thing to watch the LWT topic and set the Thing to OFFLINE when it sees the OFFLINE message. These messages should be retained.

With that set up, individual Things can be configured with that LWT information and they will be marked ONLINE/OFFLINE in OH based on the contents of the LWT topic for that device. Furthermore, you can create a Channel to subscribe to that LWT topic and link that to an Item which can be used in rules.

You don’t wait for the ack. Instead, configure your MQTT described as above and in your rule you can check the state of the MQTT Broker Thing and/or a Generic MQTT Thing configured with the LWT, or an Item linked to a Generic MQTT Thing’s Channel that subscribes to the LWT of the RPi.

If the broker is offline, the MQTT Broker Thing will be OFFLINE.
If the end device is offline, the Generic MQTT Thing representing that device will be OFFLINE and/or you’ll have a Switch Item that will be OFF when the device is OFFLINE. Both of these can be checked in the rule prior to calling publishMQTT and sending the Telegram message. You could add an additional Network Thing to the mix which will poll the RPi and set a switch to OFF if the whole machine falls off the network.

Sure, you could merge those two into one rule if you wanted to. Having multiple rules for this isn’t a problem and often having multiple smaller simple rules is better than one big complicated rule for long term understanding and maintenance.

But if you use the features built into MQTT the way they are intended, you don’t need any new rules at all. You can test to see if the publish even has a chance of succeeding even before the sending the message. And then instead of replying on telegram ‘Open Requested’ you can reply with ‘Relay is down’ or some other such informative message.

I do this for my garage door opener which is run off of an RPi. If a command to trigger the opener is received, I have a rule that checks if the RPi is online (using Network Binding) and whether the Generic MQTT Thing is ONLINE and send a broadcast notification back if it’s offline explaining that the RPi is down and the opener will not trigger.

If you need help with having a service instead of polling the MQTT topic, see GitHub - rkoshak/sensorReporter: A python based service that receives sensor inputs and publishes them in various ways. which is what I use to implement that garage door opener system. The relay is connected to GPIO.

The micropython (MP) MQTT library makes you do it this way. I’m using micropython on the RPi Pico W.

The example code they give (example_sub.py) is “check if there is a message waiting on your subscribed topic, if not, sleep a bit and check again. if there is message, read it and execute linked callback function.”

It is. The MP MQTT lib makes a connection to the broker and does all the standard subscribe, keepalive, etc.

Ahh. I didn’t realize that was how LWT worked. I think that might be very helpful with the logic. Thanks!

I think that’s different from what it appears you initially described (which is running a cron job every second to connect to and get any retained messages once a second). In this case, the RPi is maintaining it’s connect to the MQTT broker. It just isn’t processing the messages as they arrive.

That’s fine and it should support registering a LWT topic and message.