Can I use a blocking call in an AppDaemon app?

Hello everyone

I am trying to move my NodeRed scenarios to HA using AppDaemon. I have MQTT messages I would like to listen to but AFAICT there is nothing built-in for that.

I decided to use MQTT Paho in my AppDaemon code but I am not sure whether I can use a blocking call (namely loop_forever()):

import appdaemon.plugins.hass.hassapi as hass
import paho.mqtt.client as mqtt

class HelloWorld(hass.Hass):

    def initialize(self):
        self.log("Hello from AppDaemon")
        client = mqtt.Client()
        client.on_message = self.alarm
        client.connect("mqtt.example.com")
        res = client.subscribe("maison/hello")
        client.loop_forever()

    def alarm(self, *kwargs):
        self.log("alarm raised")
        self.log(kwargs)

This code works (alarm is fired when I publish something on maison/hello) but:

  • I did not try to start another app yet
  • I do not know if using a blocking call is not interfering with HA, overall

No, you can’t do that. If you do, you will lock up appdaemon’s utility loop which is a very bad thing.

If you want to do this as things stand, you would need to create your own thread to do the run forever piece, initialize() can’t be held up, nor can any of the worker threads for callbacks.

The proper way to support this kind of activity is to write a new plugin which will allow a much deeper integration, and is intended to manage asynchronous events and blocking calls like that.

Luckily, there is already a pull request in place for a basic MQTT listener contributed by github user tschmidty69. I haven;t got around to looking at it yet but I will do soon as I think this is an excellent use for the new plugin functionality in AppDaemon 3.0.

1 Like

Thank you - so in the meantime I can use a normal threading.Thread to run my listener loop?

Yes, I’ve done that before in my apps - only trick is to make sure you terminate it cleanly when the app is restarted using the terminate() function or you may get strange things happening.

For simple mqtt messages, it is easy to forward them to AD from HA as events.

automation:
  - alias: Four33 Button
    trigger:
      platform: mqtt
      topic: home/433toMQTT
    action:
      event: Four33Button
      event_data_template:
        payload: '{{ trigger.payload }}'

1 Like

Thanks - that will be my solution of choice until the PR mentioned by @aimc is there.

As a side note to my initial question, starting the loop as

threading.Thread(self.client.loop_forever())

works fine as well.

What are you using to destroy the thread when done?

There is no way to kill a thread in a portable way. Fortunately, loop_forever is shut down upon a client disconnect() .

I just upgraded client to a property (self.client) and issue a self.client.disconnect() in terminate(). This said, when shutting down the app daemon via Ctrl-C I did not see it being used. This is only a dev environment, though, so in prod (on HA) it will be more useful if I ever need to stop the app (which is not my use case)

Correct - ctrl-c ends everything as quickly as possible and since it assumes its being run from the command line it doesn’t attempt any cleanup of the apps, the same is true when stopped form sysctl I assume - I’m not sure what mechanism is used to stop the process under those conditions, but the assumption is that everything will be cleaned up when the process terminates.

I know you’re solving this in a different way, but it’s probably worth knowing that paho-mqtt already has a threaded interface. You can use loop_start() and loop_stop() and the library will manage running the loop in a background thread for you.

1 Like

In systemd one can define how a service is stopped, via the ExecStop entry. This can be a kill which will send a signal which can then be trapped in the application.
So if appdaemon reacts to some signal and shuts down cleanly (including terminate()) this can be configured.

It wouldn’t be to hard to add app termination code to the shutdown logic I already have - then I could easily define multiple signals to force it to run.

I’ll look at doing this soon.

Very soon in fact - this turned out to be a lot easier than I thought. Now, ctrl-c will also force apps to terminate, and sending a SIGTERM to the process will make it shutdown cleanly, runnin terminates() etc.

In both cases, the apps will be terminated in revers order of any dependencies that they have.

This will be in 3.0.1

1 Like

Thanks a lot for your fantastic product, by the way :slight_smile:

1 Like