Appdaemon dual plugin problem - Hass and MQTT

I would appreciate some help in configuring an Appdaemon app which will use both the Hass and the MQTT plugins. Hass plugin on its own worked well and MQTT plugin on its own also worked but I can’t get the configurations working when both plugins are required. I am using HA 0.91.1, AD 3.0.1 in Hassio and MQTT broker in Hassio and have read the AD docs. As the extracts below show the self.set_state method throws the TypeError. Even a reference to a practical example of such a configuration could be helpful.
Thanks.

appdaemon.yaml

appdaemon:
  ...
  plugins:
    HASS:
      type: hass
      ha_url: http://hassio/homeassistant
      namespace: hass
      token: ! secret appdaemon_token
      app_init_delay: 4
    MQTT:
      type: mqtt
      client_host: 192.168.0.22
      namespace: mqtt
  ...

object.yaml

office_test_light:
  module: gbrix_mqtt_down_light_01
  class: MotionLightsMQTT
  # plugin: hass ??
  # hass_namespace: hass  ??  
  # plugin: mqtt ??
  # mqtt_namespace: mqtt ??
  room: "office"
  no_log: False
  hold_dur: .5  #minutes to hold brightness factor at 1
...

Py class

import datetime
import json
import appdaemon.plugins.mqtt.mqttapi as mqtt
class MotionLightsMQTT(mqtt.Mqtt, hass.Hass):
  def initialize(self):
    #gather instance parameters
    self.room = self.args["room"]
    self.log("***** initialise {} sensor cb*****".format(self.room))
    self.hold_dur = self.args["hold_dur"]  #minutes to hold brightness factor at 1
    self.off_ramp_dur = self.args["off_ramp_dur"]  #minutes to ramp down the brightness factor
    self.time_delta = self.args["time_delta"]  #step duration during envelope generation  
    self.mq_light = self.args["mq_light"]
    self.mq_light_color = self.mq_light + "/cmnd/color"
    self.mq_light_fade = self.mq_light + "/cmnd/fade"
    self.mq_light_speed = self.mq_light + "/cmnd/speed"
    self.motion_sensor_1 = self.args["motion_sensor_1"]
    self.motion_sensor_2 = self.args["motion_sensor_2"]
    self.auto_switch = self.args["auto_switch"]  # input boolean
    self.ntl_on_time = self.args["ntl_on_time"]
    self.day_colour_temp = self.args["day_colour_temp"]
    self.day_brightness = self.args["day_brightness"]
    self.evening_colour_temp = self.args["evening_colour_temp"]
    self.evening_brightness = self.args["evening_brightness"]
    self.no_log = self.args["no_log"]
    
    #Setup motion, switch and daily callback functions
    self.set_namespace('hass')
    self.set_state(self.auto_switch, state = "on"

Appdaemon error log

2019-04-21 18:35:38.963735 WARNING AppDaemon: ------------------------------------------------------------
2019-04-21 18:35:38.963853 WARNING AppDaemon: Unexpected error running initialize() for office_test_light
2019-04-21 18:35:38.963957 WARNING AppDaemon: ------------------------------------------------------------
2019-04-21 18:35:38.964234 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/appdaemon/appdaemon.py", line 1581, in init_object
    init()
  File "/config/appdaemon/apps/gbrix_mqtt_down_light_01.py", line 61, in initialize
    self.set_state(self.auto_switch, state = "on")
  File "/usr/local/lib/python3.7/site-packages/appdaemon/plugins/hass/hassapi.py", line 109, in set_state
    if entity_id in self.get_state(namespace = namespace):
  File "/usr/local/lib/python3.7/site-packages/appdaemon/plugins/mqtt/mqttapi.py", line 82, in get_state
    return super(Mqtt, self).get_state(namespace, entity, **kwargs)
TypeError: get_state() takes from 1 to 2 positional arguments but 3 were given

I noticed this problem when I was doing some testing. I haven’t had a need for this in an app yet so I didn’t pursue it very far.

I noticed when when I tried to use multiple inheritance like you did that the Hass object threw errors when the app was created. It wasn’t being supplied the correct parameters when it was being created.

I’m surprised you don’t get an error with this the following code since you don’t have " import appdaemon.plugins.hass.hassapi as hass" in your code (unless it didn’t get copied over when you were posting):

hass.Hass doesn’t appear to be defined.

What I did find that worked was to write two apps:

  • one as a hass.Hass class
  • the other was a mqtt.Mqtt class

Then in the Hass class I used self.get_app(“Mqtt-App”) to get access to the Mqtt app. I passed “self” to a method in the Mqtt app and that gave the Mqtt app access to the Hass object.

Something like:

class MotionLightsMQTT(mqtt.Mqtt, hass.Hass):
 def initialize(self):
     self.hass = None
 def save_hass(self, Hass)
     self.hass = Hass
 ...
class HassObject(hass.Hass):
 def initialize(self):
      self.mqtt = self.get_app("MotionLightsMQTT")
      self.mqtt.save_hass(self)  # Pass this object to the Mqtt app
 ...

MotionLightsMQTT now has access to both plug-ins. Instead of using self.set_state() you would need to use self.hass.set_state().

I assume you could do it the other way around too. Pass the Mqtt object to a Hass app. Or pass each app the object of the other.

Here is a simple app I wrote some time ago. You have to take it on faith that per_app_logger is derived from hass.

I seem to remember the tricky bit was remembering the namespace parameter for mqtt calls, but I never tried a set_state, so I’m not sure about that.

from appdaemon.plugins.mqtt.mqttapi import Mqtt
from per_app_logger import PerAppLogger
import datetime


class MqttLights(PerAppLogger, Mqtt):

    def initialize(self):
        self.log("initalize", level="DEBUG")
        self.motion = False
        try:
            # required args
            self.args["motion_topics"]
            self.args["light_topics"]
            if self.entity_exists(self.args["dark_sensor"]):
                self.dark_sensor = self.args["dark_sensor"]
            else:
                self.log("{} does not exist".format(self.dark_sensor),
                        level="ERROR")
                return
        except KeyError as e:
            self.log("Argument not found : {}".format(e), level="ERROR")
            return

        for atopic in self.args["motion_topics"]:
            self.listen_event(self.motion_detected, "MQTT_MESSAGE",
                    namespace = "mqtt", topic = atopic)

    def motion_detected(self, event_name, data, kwargs):
        self.log("motion_detected - {} - {}".format(event_name, data),
                level="DEBUG")
        if (data["payload"] in ["1", "ON"]) and \
                self.get_state(self.dark_sensor) == "on":
            self.turn_on_lights()

    def turn_on_lights(self):
        self.log("turn_on_lights", level="DEBUG")
        for light_topic in self.args["light_topics"]:
            self.mqtt_publish(light_topic, "ON", retain = True,
                    namespace = "mqtt")

@MikeA, thanks for your detailed analysis of my problem and your way to solve it. I am inexperienced in using Python and struggling to get to grips with inheritance. Nevertheless, you gave such a detailed outline of your solution that I have coded it up for testing. I am getting some AttributeErrors which I need to sort out.
@gpbenton, thanks for your assistance. I gather from your remarks that hass is imported through PerAppLogger. Where can I find out about per_app_logger. (There is a lot for me to learn from your compact code so thanks for that too.)

More generally, I thought I was asking a simple question about using an mqtt component in a hass/appdaemon environment. Maybe there is a simpler way to get to my objective.
I have had an Appdaemon app monitoring zwave sensors and controlling milight ceiling lights for some time but due to some limitations of milights I am trying to replace the milight controls with MQTT - Tasmota controllers. The lights are rgbwwcw and I am trying to implement transitions. Since there seem to be some challenges to getting dual appdaemon plugins working together should I really be doing the MQTT handling in hass?

Hello @Waparo,

To use both Plugins in a single app in AD 3.0.3 below, the best and easiest approach will be to use the example as supplied by @MikeA, whereby you do the following

  • Have the app e.g hass_app developed using the HASS instance class MotionLightsMQTT(hass.Hass):, this will allow you to set state. Ensure the namespace is set to hass
  • Have another app using the MQTT instances and get its app from within this HASS instance using something like self.mqtt = self.get_app('hass_app')
  • With the MQTT instance using self.mqtt, within the HASS app you can listen, subscribe and publish MQTT events as thus
    • self.mqtt.listen_event()
    • self.mqtt.mqtt_publish/self.mqtt.mqtt_subscribe

I understand its a bit of a little work to use both, but this has been greatly improved in the new version that will be released soon.

Kind regards

One of the advantages that Appdaemon has is that you don’t have to restart Home Assistant every time you make a change to see if your change works. And because we can use Python code in Appdaemon it is easier to do more complicated things than it is using just Automations.

When I was testing my code I had to add the namespace=“mqtt” or namespace=“hass” parameters to function calls so that Appdaemon knew which plugin to use. I don’t remember what errors pop-up if you don’t.

@Odianosen25, thanks for your endorsement of @MikeA’s structure and for the extra bits of detail and emphasis which you have provided. I have previously been studying your presence sensor example looking for clues but had been unable to successfully apply it to my application. I did code up my apps to the structure @MikeA suggested but have yet to find where I have gone astray. Your framework will give me a way to check my code.
@MikeA, thanks for your additional comments. AD (python) was the reason I came to HA and I have not used Automations. But the learning curve is very steep for me particularly with object orientation.
I am now much more confident that I will be able to get things working in the near term.

Something just occurred to me.

From your initial post, you using AD 3.0.1. MQTT was introduced in AD 3.0.2. So first I will advise you upgrade to the latest which is AD 3.0.4, then work from there.

Regards

1 Like

I (incorrectly) reported AD 3.0.1 in the post because that is what appears in the top right corner of the hassio AD plugin.
However, I have noted that AD log reports that it is version 3.0.4. So all should be well on that front.
Thanks

@MikeA, @Odianosen25, I have tried to restructure in accordance with your guidelines and to keep it simple I have set up two apps with minimal code. The classes are TestHass and TestMqtt. The corresponding modules are test_hass and test_mqtt. I am trying to make the hass app the main focus with mqtt the supporting app. There is still an “AttributeError: ‘NoneType’ object has no attribute” error. I am out of my python depth and really need to put more effort into understanding objects and inheritance. So while I value your help I could also wait for the new release of AD (in Hassio) and possibly bypass the tricky object passing.
Here is where I am at.

Objects YAML

  module: test_mqtt
  class: TestMqtt
  plugin: mqtt
  mqtt_namespace: "mqtt"
  mq_light: "office/rgb2/light"

test_hass:
  module: test_hass
  class: TestHass
  plugin: hass
  hass_namespace: "hass"
  mq_light: "office/rgb2/light"
  motion_sensor_1: "binary_sensor.office_motion"
  motion_sensor_2: None
  auto_switch: "input_boolean.office_auto"
test_hass.py

test_hass.py

import appdaemon.plugins.hass.hassapi as hass
import datetime
import appdaemon.plugins.mqtt.mqttapi as mqtt

class TestHass(mqtt.Mqtt, hass.Hass):
  def initialize(self):
    #gather instance parameters
    self.set_namespace("hass")
    self.mqtt = None
    self.mq_light = self.args["mq_light"]
    self.mq_light_color = self.mq_light + "/cmnd/color"
    self.mq_light_fade = self.mq_light + "/cmnd/fade"
    self.mq_light_speed = self.mq_light + "/cmnd/speed"
    self.motion_sensor_1 = self.args["motion_sensor_1"]
    self.auto_switch = self.args["auto_switch"]  # input boolean

    #Setup motion, switch and daily callback functions
    self.set_state(self.auto_switch, state = "on")
#    self.listen_state(self.switch, self.auto_switch)
    # self.listen_state(self.motion, self.motion_sensor_1, new = "on")
    
    #Initialise variables
    # self.turn_off(self.mq_light) 
    self.mqtt.mqtt_publish(self.mq_light_fade, payload = "On", qos = 0, retain = True, namespace = "mqtt")
    self.mqtt.mqtt_publish(self.mq_light_speed, payload = "5", qos = 0, retain = True, namespace = "mqtt")
    self.mqtt.mqtt_publish(self.mq_light_color, payload = self.color_str(260,10), qos = 0, retain = True, namespace = "mqtt")
    # # self.mqtt.mqtt_publish(self.mq_light_color, payload = self.color_str(500,10), qos = 0, retain = True, namespace = "mqtt")    

    self.log("***** initialise {} sensor cb - complete*****".format(self.room))
    # self.logit("**********mqtt 1***********")

  def save_mqtt(self, Mqtt):
    self.mqtt = Mqtt 

test.mqtt.py

import appdaemon.plugins.hass.hassapi as hass
import datetime
import appdaemon.plugins.mqtt.mqttapi as mqtt

class TestMqtt(mqtt.Mqtt, hass.Hass):
  def initialize(self):
    self.set_namespace('mqtt')
    self.hass = self.get_app("TestHass")
    self.hass.save_mqtt(self)  # Pass this object to the Hass app
    self.mqtt_subscribe("office/rgb2/light")

AD Errors

2019-04-26 12:23:25.901841 WARNING AppDaemon: ------------------------------------------------------------
2019-04-26 12:23:25.901978 WARNING AppDaemon: Unexpected error running initialize() for test_mqtt
2019-04-26 12:23:25.902085 WARNING AppDaemon: ------------------------------------------------------------
2019-04-26 12:23:25.903216 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/appdaemon/appdaemon.py", line 1581, in init_object
    init()
  File "/config/appdaemon/apps/test_mqtt.py", line 9, in initialize
    self.hass.save_mqtt(self)  # Pass this object to the Hass app
AttributeError: 'NoneType' object has no attribute 'save_mqtt'

2019-04-26 12:23:25.903331 WARNING AppDaemon: ------------------------------------------------------------
2019-04-26 12:23:25.903660 WARNING AppDaemon: ------------------------------------------------------------
2019-04-26 12:23:25.903764 WARNING AppDaemon: Unexpected error running initialize() for test_hass
2019-04-26 12:23:25.903866 WARNING AppDaemon: ------------------------------------------------------------
2019-04-26 12:23:25.904530 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/appdaemon/appdaemon.py", line 1581, in init_object
    init()
  File "/config/appdaemon/apps/test_hass.py", line 36, in initialize
    self.set_state(self.auto_switch, state = "on")
  File "/usr/local/lib/python3.7/site-packages/appdaemon/plugins/hass/hassapi.py", line 109, in set_state
    if entity_id in self.get_state(namespace = namespace):
  File "/usr/local/lib/python3.7/site-packages/appdaemon/plugins/mqtt/mqttapi.py", line 82, in get_state
    return super(Mqtt, self).get_state(namespace, entity, **kwargs)
TypeError: get_state() takes from 1 to 2 positional arguments but 3 were given

2019-04-26 12:23:25.904647 WARNING AppDaemon: -----------------------------------------------------------

AD Log

2019-04-26 12:23:19.623961 INFO AppDaemon Version 3.0.4 starting
2019-04-26 12:23:19.624257 INFO Configuration read from: /config/appdaemon/appdaemon.yaml
2019-04-26 12:23:19.625435 INFO AppDaemon: Starting Apps
2019-04-26 12:23:19.630576 INFO AppDaemon: Loading Plugin HASS using class HassPlugin from module hassplugin
2019-04-26 12:23:19.796342 INFO AppDaemon: HASS: HASS Plugin Initializing
2019-04-26 12:23:19.796795 INFO AppDaemon: HASS: HASS Plugin initialization complete
2019-04-26 12:23:19.796976 INFO AppDaemon: Loading Plugin MQTT using class MqttPlugin from module mqttplugin
2019-04-26 12:23:19.830285 INFO AppDaemon: MQTT: MQTT Plugin Initializing
2019-04-26 12:23:19.830533 INFO AppDaemon: MQTT: Using 'mqtt client status' as Will Topic
2019-04-26 12:23:19.830645 INFO AppDaemon: MQTT: Using 'mqtt client status' as Birth Topic
2019-04-26 12:23:19.830815 INFO AppDaemon: MQTT: Using 'appdaemon_mqtt_client' as Client ID
2019-04-26 12:23:19.831411 INFO Dashboards are disabled
2019-04-26 12:23:19.831517 INFO API is disabled
2019-04-26 12:23:19.838055 INFO AppDaemon: HASS: Connected to Home Assistant 0.90.2
2019-04-26 12:23:19.964545 INFO AppDaemon: HASS: Delaying app initialization for 4 seconds
2019-04-26 12:23:20.082229 INFO AppDaemon: MQTT: Connected to Broker at URL 192.168.0.22:1883
2019-04-26 12:23:20.840151 INFO AppDaemon: Got initial state from namespace mqtt
2019-04-26 12:23:20.840648 INFO AppDaemon: MQTT: MQTT Plugin initialization complete
2019-04-26 12:23:23.993087 INFO AppDaemon: Got initial state from namespace hass
2019-04-26 12:23:25.879212 INFO AppDaemon: Reading config
2019-04-26 12:23:25.883634 INFO AppDaemon: /config/appdaemon/apps/Test Hass MQTT Objects.yaml added or modified
2019-04-26 12:23:25.883757 INFO AppDaemon: /config/appdaemon/apps/Test Hass MQTT Objects.yaml added or modified
2019-04-26 12:23:25.883869 INFO AppDaemon: App 'test_mqtt' added
2019-04-26 12:23:25.883975 INFO AppDaemon: App 'test_hass' added
2019-04-26 12:23:25.884139 INFO AppDaemon: Adding /config/appdaemon/apps to module import path
2019-04-26 12:23:25.884489 INFO AppDaemon: Loading App Module: /config/appdaemon/apps/test_hass.py
2019-04-26 12:23:25.901092 INFO AppDaemon: Loading App Module: /config/appdaemon/apps/test_mqtt.py
2019-04-26 12:23:25.901635 INFO AppDaemon: Initializing app test_mqtt using class TestMqtt from module test_mqtt
2019-04-26 12:23:25.903442 INFO AppDaemon: Initializing app test_hass using class TestHass from module test_hass
2019-04-26 12:23:25.905249 INFO AppDaemon: App initialization complete
1 Like

Sorry, I’m away at a family reunion so I won’t be near a computer until after May 1st.

@Waparo,

I am sorry just responding, for some reasons I missed your message.

First for the test_hass, this

class TestHass(mqtt.Mqtt, hass.Hass):

Should be

class TestHass(hass.Hass):

AD apps only inherits one class at a time.
Also add to your initialize() instead of self.mqtt = None

self.mqtt = self.get_app(''test_mqtt)

If the test_mqtt app is doing nothing, as you said the main one is that of HASS, then simply have the following in the test_mqtt

class TestMqtt(mqtt.Mqtt):
    def initialize(self):
        self.set_namespace('mqtt')

In the app configuration in the apps.yaml file, add a config in the test_mqtt app config as

priority: 30

This will ensure the test_mqtt app is already running, before the test_hass app asks for its app. If not you might get into a race condition which might mean ur code breaks sometimes.

Hope this helps, and regards

3 Likes

@Odianosen25, Your corrections worked and I was thrilled. I now have AD talking to the MQTT lights.
Your explanations of the reasons behind your defined changes were very helpful to me in furthering my understanding of AD and Python inheritance.
Thanks very much.

1 Like

glad it could help, and happy coding :slight_smile:

Just continue on this thread if you have further issues.

Regards

I used this thread because it was the first hit in search, but I couldn’t make it work.
As of this writing, two years later, multiple inheritance is possible now with appdaemon 4, here
is what worked for me:

appdaemon.yaml:

[...]
plugins:
    HASS:
      type: hass
      app_init_delay: 20

    MQTT:
      type: mqtt
      namespace: mqtt
      verbose: True
      client_host: ha.dns.name
      client_user: mqttuser
      client_password: mqttpass
flash.py

import appdaemon.plugins.hass.hassapi as hass
import datetime
import appdaemon.plugins.mqtt.mqttapi as mqtt
import time


class FlashLight(hass.Hass,mqtt.Mqtt):

    def initialize(self):
        #self.listen_state(self.scene_add,"input_boolean.scene_add", new = "on")
        #self.listen_state(self.scene_remove,"input_boolean.scene_remove", new = "on")

Hi mcmatti,
Thanks for your info. I did manage to get that solution working and then later upgraded to AD 4 successfully but with much difficulty on the way. My module looks a bit different but it does work.

import adbase as ad
import datetime
import json
import copy
class DownLightESP06(ad.ADBase):
  def initialize(self): 
    self.hass = self.get_plugin_api("HASS") # namespace name, see appdaemon.yaml
    self.ad = self.get_ad_api()
    self.mqtt = self.get_plugin_api("MQTT")

I did not go back to the double plugin class statement but not for any reason other than that all this area of python is a struggle for me and I am happy to just get it working. The AD documentation is very good for a competent programmer in modern computer languages and event driven systems but I hace not found any suitable training material. Odeanosen, Rene and others have been very helpful.

1 Like