Appdaemon/Python newbie - is this possible?

Hello @ReneTode,

I have a question. I have seen examples of when one wants to re-use an app for different entities, one tends to write the same app multiple times in the apps.yaml file. But what I tend to do, is to declare the app once, then have all my entities as those that use it. For example instead of having it the way you did, I might write instead

dash_button:
  module: dash
  class: simple_dash_class           
  dash_buttons:
     - dash_button1: Gate
     - dash_button2: Heating

Then in code, I look up what was pressed by reading the MQTT payload, and check what it is assigned to. Like if one is assigned to the “Gate” or “Heating” as he said.

My question is, is there a downside to how I am doing it? Will it make my code run slower or something? I am just trying to learn more efficient programming without making my apps.yaml file long.

Hi @ReneTode - my point was that to make it work I had to define the function with one argument (I used self) and call it with zero. I am sure this is just my Python ignorance causing me confusion…

If I might, I’d like to be cheeky and ask an off-topic unrelated question. I am trying to get notification to my iphone working in Appdaemon, and have succeeded. However, I would like to add a sound option. In HA I can do this by eg the following service call to notify.ios_myiphone

{"title":"title goes here", 
"message":"message goes here",
"data":{
   "push":{
      "sound": "US-EN-Morgan-Freeman-Welcome-Home.wav"}}}

and in Appdaemon with no sound I use
self.call_service('notify/ios_myiphone', title = "title goes here", message = "message goes here")
but I am struggling to find the right syntax to pass the sound file down - any hints? :slight_smile:

it simply depends on what you want to do.
the code isnt going to be slower (or not noticable)

if I want to do the same thing for a lot of entities i would also go for an entitylist.

in most times the decision comes down to: do i make my code more complicated or do i make my yaml more complicated. and which complicates less.

Hello @Odianosen25

If you’d like to help out a newbie, I’d be very interested to see your python code for this.

a function inside a class always expects self in the def, but not in the call (allthough it wont hurt to put it there too.)

i think it will be like this:

self.call_service('notify/ios_myiphone', title = "title goes here", message = "message ,data = {"push":{"sound": "US-EN-Morgan-Freeman-Welcome-Home.wav"}})
1 Like

@ReneTode,

Thanks for clarifying that.

@PianSom,

Well I am kind of a newbie myself, but like making things look a bit simpler (when I can). How I would do it is thus. When I have my apps.yaml file as I have it, I look up the self.args['dash_buttons'] in my self.initialize. Now it will report a list of dictionaries like the example above will give [{'dash_button1':'Gate'}, {'dash_button2':'Heating'}].

I convert that from a list to dictionary using the following code. Now there might be an easier way to do this, but this is what I know for now:

self.dash_buttons = dict()
for button in self.args['dash_buttons']:
    dash_buttons[list(button.keys())[0]] = list(button.values())[0]

This will now give me self.dash_buttons as {'dash_button1':'Gate','dash_button2':'Heating'}. In HA, since you have the dash buttons sending over mqtt (well I haven’t used one before), you can subscribe to the topic in HA, which creates an event, and sends it to Appdaemon. Look up example here.

Now depending on what your payload is, if its on or off, you can carry out the function based on what the button is allocated to in the dictionary.

This also makes it easier to lets say, use HA input select, to allocate different things to different functions. As all you have to do, is update the dictionary.

In all these, I am assuming that the names of the buttons, are part of the mqtt payload

thats really not needed.

self.args[“dash_buttons”] in itselve is already a list with dicts as you declared it.
so this:

self.dash_buttons = self.args['dash_buttons']

would be enough but also not needed.

you can call

self.args["dash_buttons"]["dash_button1"]

anywhere in your code and it will give “Gate” as a result.

1 Like

Very good point, for some reasons never figured out that :thinking:

Though the reason why sometimes I put them in their own dictionary are thus:

I have a habit of ensuring that the button or what ever it is (users in my code), are allowed in it. So I do something like

if button in self.dash_buttons:
    <do stuff>
else:
   <do another stuff and log>

it makes it easier for me to do that.

Also I could dynamically update the dictionary within the code, Though it will revert to the original if the module is reloaded.

Just to complete the example you linked to about sending MQTT messages as events, this is my code for receiving the events

    def initialize(self):
        self.listen_event(self.remote2_buttonC,
                "Four33Button", payload="13071009")

    def remote2_buttonC(self, event_name, data, kwargs):
        self.log("remote2_buttonC", level="DEBUG")
        self.turn_on("switch.computer_peripherals")

There are of course, different callback for the four buttons on each of 3 remotes.

If I want to change the operation of remote2, button c, I just change the code and it is loaded immediately.

Thanks @gpbenton,

But in this situation, it won’t be needed to define the payload, so the same function can be used to process different buttons. So instead it might be something like this

 def initialize(self):
        self.listen_event(self.dash_buttons_function, "DASH_BUTTONS")

 def dash_buttons_function(self, event_name, data, kwargs):
     self.log("__function__", level="DEBUG") #I like those lines
     if data.get("payload") in self.dash_buttons:
         if self.dash_buttons[data.get("payload")] == 'Gate':
             <gate function>
         elif self.dash_buttons[data.get("payload")] == 'Heating':
             <heating function>

if you want to change the dict then you need to get it into a var else it isnt needed.

this would work too:

if data.get("payload") in self.args["dash_buttons"]:
         if self.args["dash_buttons"][data.get("payload")] == 'Gate':
             <gate function>

for button in self.args["dash_buttons"]:
    <do stuff>

1 Like

Wow, that escalated quickly …

OK, so I have tried to follow the discussion above and put a test together.

I have a yaml

dash_app:
  module: dash
  class: simple_dash
  dash_buttons: 
    - switch.dash_brabantia: study_light_on
    - switch.dash_gillette: study_light_off

And so I need code which
1- initialzes by running a self_listen_state on every item in the list for an on action
2 - calls via getattr the second item on a line when one is found

Here is my attempt based on the discussion above. (I am struggling at the limits of my Python knowledge at this point, so please be gentle!)

import appdaemon.plugins.hass.hassapi as hass

class simple_dash(hass.Hass):

  def initialize(self):
    self.log("Hello from simple_dash")
    for control in self.args['dash_buttons']:
      self.listen_state(self.run_action(control), control[0], new="on")

  def run_action(self, entity, attribute, old, new, kwargs):
    self.log("Got an ON from {}, About to run {}".format(self.args["control"][0], self.args["control"][1] ))
    function = getattr(self, self.args["control"][1])
    function()


  def study_light_on(self):
    self.log("Running study_light_on")
    self.turn_on("light.study__ian")

  def study_light_off(self):
    self.log("Running study_light_off")
    self.turn_off("light.study__ian")

I am getting

  File "/home/pi/appdaemon/apps/dash/dash.py", line 7, in initialize
    for control in self.args['dash_buttons']:
KeyError: 'dash_buttons'

EDIT - now I look at this again I can see that the run_action function is redundant - it would be better to reference the actual function to be run directly in self.listen_state by reference to the control[1] eg study_light_on. The second argument of self.listen_state should be the first item of the list element control[0] eg switch.dash_brabantia

So the line in initialize should be something like

self.listen_state(getattr(self, control[1])(), control[0], new="on")

but correctly syntax’ed!! :slight_smile:

I think you are following a poor example to start. This code will have the same effect

class simple_dash(hass.Hass):

  def initialize(self):
    self.log("Hello from simple_dash")
    self.listen_state(self.brabantia, "switch.dash_brabantia", new="on")
    self.listen_state(self.gillette, "switch.dash_gillette", new="on")

  def brabantia(self, entity, attribute, old, new, kwargs):
    self.turn_on("light.study_ian")

  def gillette(self, entity, attribute, old, new, kwargs):
    self.turn_off("light.study_ian")

Thanks @gpbenton

Yes, I know I can do it that way, but for the reasons set out above I am trying to give myself flexibility by allowing modification of the behaviour through changing the .yaml rather than the .py

I don’t see the advantage - it isn’t any more ‘flexible’. Remember that when it breaks in six months time you are going to have to fix this, and remembering the data structures involved is complex enough when you know python.

But its your app, so I’ll shut up now.

@PianSom,

Well based on all said and done, now this is how I would have done it. Though its a stubborn way to do it against what @gpbenton said about data structure, and the pros may frown at it. But I believe it should work and achieve what you want. I will borrow some clues from @ReneTode above (believe me I didn’t know there was a function called eval() until I saw this tread. Told you I am also a newbie kind of, so take my advise on your own peril). But I am just trying to help a fellow new starter.

To start with, I am assuming your apps.yaml looks thus

dash_app:
  module: dash
  class: simple_dash
  dash_buttons: 
    - brabantia: 'Study light on'
    - gillette: 'Study light off'

and al you buttons are switches and start with the strings “dash_”. Then you can run the following codes

def initialize(self):
    self.dash_buttons = dict()
    for button in self.args['dash_buttons']:
        dash_buttons["switch.dash_{}".format(list(button.keys())[0])] = list(button.values())[0].lower() #.lower() just in case capital lets are used in the yaml file 
        self.listen_state(self.run_action, "switch.dash_{}".format(list(button.keys())[0]), new="on")
    self.log("__function__, buttons = {}".format(self.dash_buttons), level = "INFO")#print out buttons


def run_action(self, entity, attribute, old, new, kwargs):
    function = "self.{}()".format(self.dash_buttons[entity].replace(" ", "_")) #replace spaces with under score
    self.log("Got an ON from {}, About to run {}".format(entity, function ))
    eval(function)
    return


def study_light_on(self):
   self.log("Running study_light_on")
   self.turn_on("light.study__ian")
   return

def study_light_off(self):
    self.log("Running study_light_off")
    self.turn_off("light.study__ian")
    return

With the above, I believe once you declare the buttons in the apps.yaml file and it reloads, it should just work. As long as you use the same string in the .yaml file as your function names.

One could still make the .yaml file simpler in the sense of of writing single words, but it will entail more variables which obviously its not recommenced.

Hope it helps.

Thanks @Odianosen25 ! I will try your code later.

As I hinted in my EDIT at the end of my last post, do you think it is possible to get rid of run_action altogether by using

self.listen_state( getattr( list(button.keys())[1] ) ),   YOUR CODE HERE

(with the necessary changes to the .yaml to make it as I originally suggested, with the actual function name e.g. study_light_on)?

It would be more elegant (:slight_smile: ) not to have an unnecessary function.

@PianSom,

Well it might be possible, and honestly I don’t know much about what getattr will return. (one of those new things in this tread I learnt :smirk:).

You have a good point though as you could potentially get rid of it, and ensure all the functions as you put it “correctly syntax’ed!! :slight_smile:” . So the functions might look something like this, if that works:

def study_light_on(self, entity, attribute, old, new, kwargs):
   self.log("Running study_light_on")
   self.turn_on("light.study__ian")
   return

def study_light_off(self, entity, attribute, old, new, kwargs):
    self.log("Running study_light_off")
    self.turn_off("light.study__ian")
    return

But honestly I have no clue on that. Besides if adding an extra function makes it easier to read, see no reason to remove it really. And you might want to potentially use that function somewhere else. And if you use a call back for a switch, it will make it more difficult to re-use it again (i think)