AppDaemon Q&A

Correct, I have not written any apps yet, but I figured I would see the hello app running like the documentation below:

2016-08-22 10:08:16,575 INFO Got initial state
2016-08-22 10:08:16,576 INFO Loading Module: /export/hass/appdaemon_test/conf/apps/hello.py
2016-08-22 10:08:16,578 INFO Loading Object hello_world using class HelloWorld from module hello
2016-08-22 10:08:16,580 INFO Hello from AppDaemon
2016-08-22 10:08:16,584 INFO You are now ready to run Apps!

I agree with beeaton, I’ve got the same output, with the expectation of it running the hello app.

OK, so you need to check a couple of things:

  1. You have the App directory correctly set in the config file
  2. The HelloWorld App is in the App directory

That was the problem. I had the wrong directory set. Thank you for your help.

1 Like

I will check that setting when I get home. Thanks for the help!!!

Just checked and everything looks correct as far as I can tell. I installed it in the root directory and didn’t change anything

Here’s my config file:

[AppDaemon]
ha_url = ********
ha_key = ********
logfile = STDOUT
errorfile = STDERR
app_dir = /conf/apps
threads = 10
latitude = 99.9999999
longitude = -99.999999
elevation = 17
time_zone = America/Chicago

Apps

[hello_world]
module = hello
class = HelloWorld

Here’s where the app is installed.

Ok, after play around for a bit, I figured it out.

I had to add an “/” at the end of the path in the config file to get it to work:

Before:

app_dir = conf/apps

After:

app_dir = conf/apps/

Ok, I’m at my wits end with *args and **kwargs

I have a lot of automations created, and honestly, I have had to do trial and error when it comes to *args and **kwargs on each and every automation.

Now I understand *args are individual argument values, without keywords, and I understand that **kwargs are key/value pairs, my problem is, when to use them, because there are apparently pre-determined and required variables flying all over the place.

There is entity, attribute, old, new and kwargs, and I can’t figure out when they are sent, where they are available, if they are global or local to each individual class or what???

So just about every one of my automations I end up with errors about a function requiring a set number of positional arguments but only receiving less than that, or more than that expected number.

The documentation says the layout for using run_in is:

self.handle = self.run_in(callback, delay, **kwargs)

and the layout for the callback is:

def callback(self, **kwargs):

But using those together, results in errors.

So I have tried all of the following, all with different errors:

self.lock_timer = self.run_in(self.send_notification, 10)

self.lock_timer = self.run_in(self.send_notification, 10, entity=entity)

self.lock_timer = self.run_in(self.send_notification, 10, kwargs)

self.lock_timer = self.run_in(self.send_notification, 10, **kwargs)

And I have used all of the following as a scheme for the callback function:

def send_notification(self, **kwargs):

def send_notification(self, *args, **kwargs):

def send_notification(self, entity):

def send_notification(self, entity, attribute, old, new, kwargs):

No matter what combination I use, about half the time I get either an error telling me I am not sending the right amount of arguments and the other half of the time I get this error:

KeyError: ‘attribute’

Most of the time I can muddle through, and often get it to pass without an error, but even during those times, I feel like it may be working, but it is not correct, or I’m passing variables that I don’t need to just to stop from getting errors.

Most of my automations start out with a listen_state in the initialize function, and they work fine as long as my callback function looks like this:

def callback(self, entity, attribute, old, new, kwargs):

But beyond calling from the initialize function to the first callback, everything goes off the rails. From within the callback that was called by initialize, I then have a run_in call to call another callback, but I never know what to put in that callback. I can’t list it like this:

def second_callback(self, entity, attribute, old, new, kwargs):

because I get errors that the run_in is not providing enough arguments, so its just completely confusing. Even if I follow the documentation on the correct layout of a callback, which is different form a listen callback and a schedule callback, but the layout given by the documentation always errors out.

Then you said in a pervious post not to use (*args, **kwargs) and to only use **kwargs, but I have several automations that will not run without errors unless I have both *args and **kwargs in the function. It just doesn’t seem consistent, and I can’t figure out how to get the entity, new and old states to pass from the initial function call to subsequent function calls.

Sorry, I’m just very frustrated right now :fearful:

i understand your frustration @jbardi.
i get that sometimes to.

for what i do myselve to avoid problems:

like you said the listen_state needs all (self, entity, old, new, kwargs).
time functions are like (self, kwargs)
and any function i call from out of the callback are for me like (self, any_var_i_like_to_give_to_the_function)

in the init you dont need to give the kwargs to the callback.
and args are there without giving anything.

i havent used run_in but i guess it would be the combination:

self.lock_timer = self.run_in(self.send_notification, 10)
def send_notification(self, kwargs):

i dont callback from a callback. i just use normal functions after the first callback.

hope this helps a bit.

There are a couple of different things going on here but it all makes sense I promise!

First, *args and **kwargs are standard python concepts. *args is a placeholder for zero or more optional positional arguments, **kwargs is a placeholder for zero or more keyword arguments. There are some good explanations out here, here is one, you need to understand this before any of the AppDaemon callback system will make sense,

Second, there is the way that AppDaemon uses this mechanism, which breaks down into 2 parts.

Part 1 - In some cases I use just optional positional arguments for the calls - for instance, in get_state():

get_state(entity = None, attribute = None)

In the example above, entity = None is a standard python way of saying, put a value here if you need to, otherwise it will default to the value None. So you don’t use a keyword here as the examples show, you use a single positional parameter for the entity id if you want to specify it or skip it otherwise.

get_state("light.living_room")

or

get_state("light.living_room", "brightness")

or even

get_state()
handle = listen_state(callback, entity = None, **kwargs)

For your Apps perspective, nothing difficult is needed here, just use the positional arguments and keywords if you need them, omit them otherwise. In the API doc, these are individually documented. Just to be clear here **kwargs is not an actual parameter, it is a Python notation meaning “zero or more keyword arguments”.

Some calls will use both positional and keyword arguments. This is common in Python if you have a large number of optional arguments. For example, in listen_state():

handle = listen_state(callback, entity = None, **kwargs)

Here we have one mandatory argument (callback), one optional argument(entity) and **kwargs which is a Python placeholder for zero or more keyword arguments. Valid keyword arguments, e.g. old and new are listed in the API doc and demonstrated in the examples.

For instance:

# Listen for a state change involving light.office1 changing from brightness 100 to 200 and return the state attribute
self.handle = self.listen_state(self.entity, "light.office_1", old = "100", new = "200")

Here we are using 2 out of the possible keyword arguments, old and new.

All of the above is a fairly standard Python way to put an API together, I am not doing anything strange.

Part 2 - the next part to this is that you need to understand a little more about how AppDaemon works to construct your callback arguments. In all of the cases above, as long as you stick to the arguments I have documented, you cannot influence what gets handed to the callbacks - they have a standard and fixed parameter format that has to match what AppDaemon is using internally - it is really an agreed upon part of the interface API. I have given you the exact signatures to use in each case, and yes they are different, because they are conveying different information and doing different jobs. If you use the exact signature that piece of the problem is taken out of the equation, it should never vary.

The only additional complication is that to increase flexibility, I also allow users to make use of the **kwargs mechanism to pass through additional user arguments. All this means is that for the calls that support it, I allow users to add arbitary keyword value pairs to the registration call, and make them available in the kwargs dictionary when the callback is called. This is entirely optional but can be very useful in some cases. Whether or not you choose to use this, you still need to specify the kwargs argument in the callback because that is part of the function signature.

One final part - you should not see *args anywhere in the docs - In fact I found one just now and removed it, sorry for the confusion. Before I released AppDaemon I allowed users to pass arbitary positional parameters as well as keyword parameters but I thought that was overkill and stuck with just the keyword parameters. A couple of the examples weren’t updated.

Hopefully the above helps.

2 Likes

so i should use

self.listen_state(self.switch, “input_boolean”, **kwargs)

and not

self.listen_state(self.switch, “input_boolean”)

i must say that it didnt hurt me that i didnt use **kwargs :wink:

You probably got away with it because you weren’t passing any additional parameter, but you are also relying on undocumented workings of AppDaemon which has changed in the past and may change in future :slight_smile:

1 Like

i Always use what i have at hand to get what i want :wink:
if i want to put in a nail and a stone is more near then a hammer then i use a stone :wink: (or any other heavy thing that is near my hand :wink: )

but i guess i nead to read up again. so i will try to understand the kwargs part of python :wink:

Sounds very practical to me :wink:

This may be obvious, or perhaps it can’t even be done, but how would you send additional variables to a notify service call?

For instance, in the normal service call for a notification you would put:

self.call_service("notify/myservice", message = "My Message")

which equates to the following JSON body:

{
  "message":"Testing"
}

But for the official iOS app they are working on, the notifications requires, at the very least, a notification category under the data and push subheadings, in which the JSON body looks like this:

{
  "message":"Testing",
  "data":
  {
    "push":
    {
      "category":"GENERIC"
    }
  }
}

So how would this be sent in a self.service_call?

I imagine the same problem would arise for a call to the Telegram service if you were attaching a picture or a location

i dont know if that would work but i would try:

self.call_service(‘notify/notify’, message = ‘Testing’, data =’{“push”:{“category”:“GENERIC”}}’)

or

self.call_service(“notify/notify”, message = “Testing”, data [“push”][“category”]=“GENERIC”)

Yep, that looks along the right lines Rene, the first example would probably work, but I believe the second would error out as you haven’t created the “push” dictionary before you reference it and add a sub-dictionary. You can also create the dictionary with a few lines of code prior to the call, maybe like this:

data = {}
data["message"] = "testing"
data["push"] = {}
data["push"]["category ] = "GENERIC"
self.call_service("notify/notify", data}

When I created the notify() call I figured I would have to expand on it eventually as I have a very simple case for notification compared to others, and now we have iOS (which I am looking at too) I will probably need to do some work to build this out.

1 Like

I feel really stupid asking this but after reading the documentation (https://github.com/acockburn/appdaemon/blob/master/README.md) I’m still unsure how to start. I guess most of you are active ‘builders’ of apps but for now, I’m just going to be a user which is super keen on getting some apps (and especially AIMC new occupancy app) running.

I’m asking this with the perspective of a total NOOB when it comes down to Linux. To get HASS up and running I used the AIO installer and got by with the instructions I found on the AIO installer page and Brusc videos.

So given the fact that I have an RPI3 with the AIO install I still have the following Q’s (I just want to be sure as I don;t want to mess my working system);

  • In the instructions (the link above). It mentions “For either method you will need to clone the AppDaemon repository to the current local directory on your machine.” : Is the current local directory the HASS directory (where my configuration.yaml is stored) or is this the directory I get when I login to the RPI3?
  • Then: $ git clone https://github.com/acockburn/appdaemon.git
  • Then: $ cd appdaemon
  • Then: $ sudo pip3 install
  • Then: Do the configuration part according to instuctions
  • Then: run it via: $ appdaemon -c conf/appdaemon.cfg (can I run this from any directory?)
  • Then: I want to add this to startup as I don’t want to start it manually. It mentions: “To run AppDaemon at reboot, I have provided a sample init script in the ./scripts directory. These have been tested on a Raspberry PI - your mileage may vary on other systems. There is also a sample Systemd script.”. My Q: I have never added anything to a startup of an RPI. Can somebody please explain in small steps what I should do here to have the AppDeamon start automatically).
  • I guess from there on I’m good to go and can use Apps from others…

Again, sorry for taking your time for this basic stuff but I guess your answers might be very usefull for other NOOBS wanting to use Apps.

Hi there - and welcome to AppDaemon :slight_smile:

It doesn’t matter - you just need to clone the repository to somewhere where you can run the install. Some people make a directory to keep this kind of thing in - you could make that a subdirectory of the hass user if you like.

Yes - by this time, AppDaemon has been installed and is in your system path.

Take a look at the init script - it has instructions on how to set it up at the top.

Yep - once it is set up you add apps just by copying them into the correct directory and configuring them in the configuration file.

No worries :slight_smile:

Now got stuck at running Appdeamon.

Modified the config:

[AppDaemon]
ha_url = http://192.168.1.196:8123
ha_key = [deleted]
logfile = STDOUT
errorfile = STDERR
app_dir = 
threads = 10
latitude = [xxxxxx]
longitude = [yyyyyy]
elevation = 9
time_zone = Europe/Amsterdam
# Apps
[hello_world]
module = hello
class = HelloWorld

When I then do

appdaemon -c conf/appdaemon.cfg

I get below (if I remove the ha_key)

And this with HA_key being my HASS API password (basically nothing happen after “Got initital state”, if I press CTRL-C it says it can’t connect to HASS):

for completness I have done a Sudo reboot and then executed “appdaemon -c conf/appdaemon.cfg” I got below

There is an appdeamon.cfg present in that directory (owner is root while owners of other files in that dir are PI)