AppDaemon
for beginner
Introduction
Any
automation you can think off in HomeAssistant can be done with AppDaemon.
The
first question that could probably be asked is: "Why use AppDaemon when it
is all possible in HomeAssistant itself?"
For
me the answer is: "I have some experience in python (not all that much,
but some) and very little experience with yaml, jinja, json and all other
things that are used to configure Home Assistant.
And
although things like yaml, jinja, etc. are not very hard to get to know, I
think they are not very easy to use to create slightly more complicated
automations and scripts.
So
if you know your way around in yaml, jinja, etc. there is no actual need to
start using AppDaemon unless you run into some boundaries.
Now
you know why I use AppDaemon, so let’s start with some basic understanding.
Our first App
Let
us first start with thinking about what an app actually is. In the "old
days" we had only programs, and then came apps. Apps are nothing more and
nothing less than a program.
You
can have a program or app to write a document, to make a photo, to edit a
picture, and so on. Our apps in AppDaemon are nothing else. It is just a few
lines of code which lead to a chosen action.
When
comparing apps to a part from HA, apps are the automations. But it is a bit
more than that. Maybe you would like to record every temperature change from 1
single sensor and log that into a file, or maybe you would like to make
calculations with all kinds of sensors and create another sensor with that
calculation.
With
apps the sky is the limit. Do you like to have your sensor values in a
spreadsheet? It’s possible. Do you like to know the highest temperature
yesterday outside your door or in your living room? It’s possible.
Do
you like to record how much time your heating is on, so you can calculate how
much energy you used? It’s possible also. Like I said anything you can think of
can be achieved.
So
let’s start now.
I
guess you use Home Assistant or else there would be no need to read this. ;)
And you are probably making automations in Home assistant too (if not you
should start ;) )
In
HA (Home Assistant) your automation would look something like this:
automation:
- alias: " Turn on light
when input boolean is switched on and someone is home"
trigger:
platform: state
entity_id:
input_boolean.some_choice
state: 'on'
condition:
condition: state
entity_id: device_tracker.some_mobile
state: 'home'
action:
service: light.turn_on
entity_id: light.some_light
This
is an easy automation which makes the device labeled "some_light"
turn on when you switch on the input boolean named "some_choice", but
only if the device "some_mobile" is home at the time.
I
think you can’t get any more basic then that, do you?
So
let’s try to translate that to AppDaemon.
import appdaemon.appapi as appapi
class your_class_name(appapi.AppDaemon):
def initialize(self):
self.listen_state(self.what_we_want_to_do,"input_boolean.some_choice",
new="on")
def what_we_want_to_do (self,
entity, attribute, old, new, kwargs):
a_variabele_with_a_usable_name =
self.get_state("device_tracker.some_mobile")
if a_variabele_with_a_usable_name ==
"home":
self.turn_on("light.some_light")
That
is the whole app. Don’t get frightened by all the things you don’t know and
understand in this part. I will take all the time you need to explain every
line separately if you read on.
And
I know you think that programming is difficult, but it actually isn’t. It is
like learning to speak in another language. If you go to another country and
they don’t speak your language you still want to order that beer, so you learn
how to do that.
The
next parts are nothing more than learning to order a beer and maybe something
to eat also. When we can feed ourselves maybe we can also learn how to ask
direction, and before you know it you will be able to give someone directions
in that other language.
And
before you think: "learning languages is nothing for me, I am terrible at
that", let me tell you that languages were always my weakest part in high
school. Now I live in a different country and speak 3 languages. Not by
learning from a book, but by using it.
And
you are probably going to make a whole lot of mistakes. Don’t think that’s not
normal. Everyone makes them all the time. The clue is to learn to recognize
your mistakes and to learn how to avoid them.
But
enough now with the psychology, back to our app.
General part:
First
we start with some parts that are in all apps:
import appdaemon.appapi as appapi
class your_class_name(appapi.AppDaemon):
def initialize(self):
At
the start of any python script you start with importing some libraries.
That
is like when you want to start writing a document about a certain topic and you
start by collecting some books which might help you write about your topic. You
might want to use some parts from those books later on.
Of
course you only collect those books which are helpful with your topic. If you
collect some other books later on, while writing, you can put those by the
others.
In
python it is the same. We won’t use any other libraries in this app, but maybe
later we use another one like datetime
and then we would set "import datetime" beneath the other import.
In
the second line we start a class.
And in this case I have given the class the name “your_class_name”. You can use
any name there that you like. The most important thing about naming things is
that it makes sense to you (and maybe later also for others).
After
that part, we find between the brackets, appapi.AppDaemon. In every class we
use in AppDaemon we use that. It would be too much to explain classes in
python, but you can search for "python classes" on Google if you
would like to learn more about it.
In
the third line we create a function.
I hear you think: "what is a function?"
In
short a function is a tiny action from your app. let’s say you want to add 2
figures over and over. Then you could write a function called addup which
returns that value. You call it by writing "addup(5,7)".
In
programming we use functions every time we want something to be used over and
over. Sometimes our functions may be called from different places. All the
libraries you import contain functions that you can use.
You
might want to talk about how functions are also used to help organize your code
and how apps may require functions with certain names so other programs know
how to interface with them. But keep it
simple like you have been, it’s great so far.
It
is like when you are writing that document and you put a link to a part from another
book and that part gets displayed to the user, without you writing that part
over and over. The well known functions
would be like a table of contents or an index.
They are parts of a book we expect to find. Chapters are like functions that organize our
book even though they are referenced multiple times.
In
this app alone we are going to make 2 functions (and use a lot more). The First
to start our app and the second which contains our action.
All
functions start with a line like this: def any_name(some, variables, you, want, to, pass, to, the, function):
This
function (initialize) is a special one that we use in all apps.
Anything
you create in this function will be done at the moment you start your app.
So
that part is easy. You can copy/paste those three lines and put them in the
beginning of all your apps and all you have to do is change the class name.
The Trigger:
Now
we come to the part where things start to get interesting.
self.listen_state(self.what_we_want_to_do,"input_boolean.some_choice",
new="on")
Wait
a moment before you start copying this! Before you do that I have to tell you
that like in yaml it is also important to check out indentions. But it is not
as difficult as in yaml. If something belongs to something else ( a
function(def) belongs to a class, this statement belongs to the initialize
function) then you have to indent. Everything else with the same indention is
seen as grouped together. You can use any amount of white space that you like,
but for every next statement the same amount)
So
this comes beneath the initialize line with an indention.
Oh,
you also want to know what it means? O well, because it’s you.
We
start with the self
part. Well that's a little hard to explain but lets say, you need the self part
if you want something created in AppDaemon. The listen_state is a call to another function(def)
which is somewhere in AppDaemon. And because you have connected this class to
AppDaemon you can use "self" to call all functions created in
AppDaemon.
Yeah
you heard it right. Listen_state is a function. Like our initialize and many
more we are going to use. So between the brackets we find some variables.
The
first variable that listen_state likes to have is the name from the function we
like to get executed when something happens.
In
the API from AppDaemon this is called a callback.
Besides
setting what we want to be done, we need to set when we want it to be done.
You
could listen to any kind of change in HA. A switch that is turned, a sensor
which changes value, a device that comes home.
In
our example we wanted to listen to input_boolean.some_choice.
You
can listen to any change from the entity if you don’t put anything behind it.
In that case the function "self.what_we_want_to_do" would be
triggered if you set the input Boolean to on, as well as when you set it to
off.
In
this case we chose to give the setting that the function only gets called if
the "new" state is on.
This
was the whole part from the trigger in yaml. You can set lots off triggers in
the same app pointing to the same function, or you can choose to make more apps
or use more functions.
I
like to divide my triggers as much as possible in more apps.
The Action:
In
the trigger we already did choose a name for our action. Remember that we used
self.what_we_want_to_do?
So
now we make a function with that name.
def what_we_want_to_do (self, entity, attribute, old, new, kwargs):
We
already talked about def and the name after that. So what comes behind?
First
there is that self again. It is there in all function (defs) we make, let’s say
that is to let python know that we want to have something in AppDaemon. And
your app is now part of AppDaemon.
Then
we see entity, attribute, old and new. This are all parts
from the "thing" that we are listening to. In our case entity is the Boolean
we are listening to. Attribute is the attributes from the input Boolean (friendly name for
instance). Old is the previous state from our Boolean and new is the new state.
Off
course in our case we only pulled the trigger if new = "on" so we can
predict all the values from this variables ourselves.
The
part between the brackets is just copy paste for all listen_state we use. In one
off the next parts I will show that there are one or two other ways according
to different situations.
By
the way don’t forget the “:” !! It states the beginning from a block off
commands that will be indented the same way. So after the “:” everything that
has more indention than this line will be considered part of the function.
Slowly
we are getting there. We named our action, now let’s make some.
self.turn_on("light.some_light")
That’s
all folks. The action is taken. So that means? Again self! Then a function. In
this case we will use the turn_on
function. And all we need to do is tell that function what we want to turn on.
Our
great friend Andrew has made a lot off those easy functions like turn_off, toggle and many more.
I will use a few off them later on, but you can find them all in the API from
AppDaemon.
I
guess we are ready to run our app.
The condition.
O
boy, I forgot I had that part in my yaml to. Ok, you don’t need conditions, but
I guess we want to use them anyway.
Let’s
go back to our yaml and find out what condition we had.
We
wanted our light only to go on if some_mobile was home. For that we need two
things.
1)
We need to know in what state our mobile is and
2)
We need to check if that state is the state we like.
To
know what state any entity is, is easy. Andrew has made another function for
that: get_state.
If
you have paid attention you probably can write the next line yourself or at
least a part from it.
a_variabele_with_a_usable_name =
self.get_state("device_tracker.some_mobile")
Do
I really need to tell you about the part behind the = ???
I
guess not. It is just another function and we give it the variable which it
needs.
What
else do we have? Just the name of a variable we want to store the returned from
the function, in. You can use any name there. Like in the function naming, make
it useful. If you start reading about programming you will probably find a lot
of examples from people and they use names like fo, foo, bar, etc. don’t use
things like that. Later on if you read back your code you want to know what
your variable is used for.
So
that was part 1. Now part 2.
We
are going to use a very common statement in any kind of programming. It can
differ how it’s written, but it does the same: "if ... then ..."
In
python it goes like this:
if
a_variabele_with_a_usable_name == "home":
That’s
all. We don’t need "then" at all. The “:” takes care of that. We put
the state from our device in the variable and now we compare that to what we
want it to be. If the statement is true the lines indented below it get done.
So
let’s put it all together again:
import appdaemon.appapi as appapi
class your_class_name(appapi.AppDaemon):
def initialize(self):
self.listen_state(self.what_we_want_to_do,"input_boolean.some_choice",
new="on")
def what_we_want_to_do (self,
entity, attribute, old, new, kwargs):
a_variabele_with_a_usable_name = self.get_state("device_tracker.some_mobile")
if a_variabele_with_a_usable_name ==
"home":
self.turn_on("light.some_light")
Our
app is ready to go. there is only 1 thing we need to do.
We
need to tell AppDaemon to use the app. For that we copy our app in the
appdirectory we have set in the cfg file and we give it a name, lets say
some_app.py
Then
we make a part in the cfg file to tell AppDaemon to use the app like this:
[our_automation_name] any name you like.
module = some_app that was the name we
used for our app
class = your_class_name and this was the name
from our class
That's
it. We made an app and we let AppDaemon use it. So go on! Make an input boolean
in HA and let it put 1 of your lights on, but only if your mobile is home.
In
the next part we will make things a little more complicated.
You
should also probably go through how to setup a debug environment and how to
debug your code. It wasn’t really
obvious, especially to people who may be coming from an IDE of some type.