mirror of
https://github.com/home-assistant/home-assistant.io.git
synced 2025-07-23 09:17:06 +00:00
Cleanup AppDaemon documentation from Home Assistant documentation (#13341)
This commit is contained in:
parent
d4cc3d8077
commit
1223f85740
@ -1,103 +0,0 @@
|
||||
---
|
||||
title: "AppDaemon"
|
||||
description: "AppDaemon is a loosely coupled, multithreaded, sandboxed Python execution environment for writing automation apps for Home Assistant"
|
||||
redirect_from: /ecosystem/appdaemon/
|
||||
---
|
||||
|
||||
AppDaemon is a loosely coupled, multithreaded, sandboxed Python execution environment for writing automation apps for Home Assistant.
|
||||
|
||||
# Another Take on Automation
|
||||
|
||||
AppDaemon is not meant to replace Home Assistant Automations and Scripts, rather complement them. For a lot of things, automations work well and can be very succinct. However, there is a class of more complex automations for which they become harder to use, and AppDaemon then comes into its own. It brings quite a few things to the table:
|
||||
|
||||
- New paradigm - Some problems require a procedural and/or iterative approach, and `AppDaemon` Apps are a much more natural fit for this. Recent enhancements to Home Assistant scripts and templates have made huge strides, but for the most complex scenarios, Apps can do things that automations can't.
|
||||
- Ease of use - AppDaemon's API is full of helper functions that make programming as easy and natural as possible. The functions and their operation are as "Pythonic" as possible; experienced Python programmers should feel right at home.
|
||||
- Reuse - write a piece of code once and instantiate it as an App as many times as you need with different parameters; e.g., a motion light program that you can use in five different places around your home. The code stays the same, you just dynamically add new instances of it in the configuration file.
|
||||
- Dynamic - AppDaemon has been designed from the start to enable the user to make changes without requiring a restart of Home Assistant, thanks to its loose coupling. However, it is better than that - the user can make changes to code and AppDaemon will automatically reload the code, figure out which Apps were using it, and restart them to use the new code without the need to restart `AppDaemon` itself. It is also possible to change parameters for an individual or multiple Apps and have them picked up dynamically. For a final trick, removing or adding Apps is also picked up dynamically. Testing cycles become a lot more efficient as a result.
|
||||
- Complex logic - Python's If/Else constructs are clearer and easier to code for arbitrarily complex nested logic.
|
||||
- Durable variables and state - Variables can be kept between events to keep track of things like the number of times a motion sensor has been activated, or how long it has been since a door opened.
|
||||
- All the power of Python - use any of Python's libraries, create your own modules, share variables, refactor and re-use code, create a single App to do everything, or multiple Apps for individual tasks - nothing is off limits!
|
||||
|
||||
It is in fact a testament to Home Assistant's open nature that a component like `AppDaemon` can be integrated so neatly and closely that it acts in all ways like an extension of the system, not a second class citizen. Part of the strength of Home Assistant's underlying design is that it makes no assumptions whatsoever about what it is controlling, reacting to, or reporting state on. This is made achievable in part by the great flexibility of Python as a programming environment for Home Assistant, and carrying that forward has enabled me to use the same philosophy for `AppDaemon` - it took surprisingly little code to be able to respond to basic events and call services in a completely open ended manner. The bulk of the work after that was adding additional functions to make things that were already possible easier.
|
||||
|
||||
# How it Works
|
||||
|
||||
The best way to show what AppDaemon does is through a few simple examples.
|
||||
|
||||
## Sunrise/Sunset Lighting
|
||||
|
||||
Let's start with a simple App to turn a light on every night at sunset and off every morning at sunrise. Every App when first started will have its `initialize()` function called, which gives it a chance to register a callback for AppDaemons's scheduler for a specific time. In this case, we are using `run_at_sunrise()` and `run_at_sunset()` to register two separate callbacks. The argument `0` is the number of seconds offset from sunrise or sunset and can be negative or positive. For complex intervals, it can be convenient to use Python's `datetime.timedelta` class for calculations. When sunrise or sunset occurs, the appropriate callback function, `sunrise_cb()` or `sunset_cb()`, is called, which then makes a call to Home Assistant to turn the porch light on or off by activating a scene. The variables `args["on_scene"]` and `args["off_scene"]` are passed through from the configuration of this particular App, and the same code could be reused to activate completely different scenes in a different version of the App.
|
||||
|
||||
```python
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
|
||||
class OutsideLights(hass.Hass):
|
||||
def initialize(self):
|
||||
self.run_at_sunrise(self.sunrise_cb)
|
||||
self.run_at_sunset(self.sunset_cb)
|
||||
|
||||
def sunrise_cb(self, kwargs):
|
||||
self.turn_on(self.args["off_scene"])
|
||||
|
||||
def sunset_cb(self, kwargs):
|
||||
self.turn_on(self.args["on_scene"])
|
||||
```
|
||||
|
||||
This is also fairly easy to achieve with Home Assistant automations, but we are just getting started.
|
||||
|
||||
## Motion Light
|
||||
|
||||
Our next example is to turn on a light when motion is detected and it is dark, and turn it off after a period of time. This time, the `initialize()` function registers a callback on a state change (of the motion sensor) rather than a specific time. We tell AppDaemon that we are only interested in state changes where the motion detector comes on by adding an additional parameter to the callback registration - `new = "on"`. When the motion is detected, the callback function `motion()` is called, and we check whether or not the sun has set using a built-in convenience function: `sun_down()`. Next, we turn the light on with `turn_on()`, then set a timer using `run_in()` to turn the light off after 60 seconds, which is another call to the scheduler to execute in a set time from now, which results in `AppDaemon` calling `light_off()` 60 seconds later using the `turn_off()` call to actually turn the light off. This is still pretty simple in code terms:
|
||||
|
||||
```python
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
|
||||
class FlashyMotionLights(hass.Hass):
|
||||
def initialize(self):
|
||||
self.listen_state(self.motion, "binary_sensor.drive", new="on")
|
||||
|
||||
def motion(self, entity, attribute, old, new, kwargs):
|
||||
if self.sun_down():
|
||||
self.turn_on("light.drive")
|
||||
self.run_in(self.light_off, 60)
|
||||
|
||||
def light_off(self, kwargs):
|
||||
self.turn_off("light.drive")
|
||||
```
|
||||
|
||||
This is starting to get a little more complex in Home Assistant automations, requiring an automation rule and two separate scripts.
|
||||
|
||||
Now let's extend this with a somewhat artificial example to show something that is simple in AppDaemon but very difficult if not impossible using automations. Let's warn someone inside the house that there has been motion outside by flashing a lamp on and off ten times. We are reacting to the motion as before by turning on the light and setting a timer to turn it off again, but in addition, we set a 1-second timer to run `flash_warning()`, which, when called, toggles the inside light and sets another timer to call itself a second later. To avoid re-triggering forever, it keeps a count of how many times it has been activated and bails out after ten iterations.
|
||||
|
||||
```python
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
|
||||
class MotionLights(hass.Hass):
|
||||
def initialize(self):
|
||||
self.listen_state(self.motion, "binary_sensor.drive", new="on")
|
||||
|
||||
def motion(self, entity, attribute, old, new, kwargs):
|
||||
if self.self.sun_down():
|
||||
self.turn_on("light.drive")
|
||||
self.run_in(self.light_off, 60)
|
||||
self.flashcount = 0
|
||||
self.run_in(self.flash_warning, 1)
|
||||
|
||||
def light_off(self, kwargs):
|
||||
self.turn_off("light.drive")
|
||||
|
||||
def flash_warning(self, kwargs):
|
||||
self.toggle("light.living_room")
|
||||
self.flashcount += 1
|
||||
if self.flashcount < 10:
|
||||
self.run_in(self.flash_warning, 1)
|
||||
```
|
||||
|
||||
Of course, if I wanted to make this App or its predecessor reusable, I would have provide parameters for the sensor, the light to activate on motion, the warning light, and even the number of flashes and delay between flashes.
|
||||
|
||||
In addition, Apps can write to `AppDaemon`'s log files, and there is a system of constraints that allows you to control when and under what circumstances Apps and callbacks are active to keep the logic clean and simple.
|
||||
|
||||
For full installation instructions, see the [AppDaemon Project Documentation pages](http://appdaemon.readthedocs.io/en/stable/).
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
---
|
||||
title: "Configuration"
|
||||
description: "AppDaemon Configuration"
|
||||
redirect_from: /ecosystem/appdaemon/configuration/
|
||||
---
|
||||
|
||||
the documentation for configuring AppDaemon can be found in its own documentation.
|
||||
|
||||
https://appdaemon.readthedocs.io/en/latest/CONFIGURE.html
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: "Example Apps"
|
||||
description: "AppDaemon Example Apps"
|
||||
redirect_from: /ecosystem/appdaemon/example_apps/
|
||||
---
|
||||
|
||||
There are a number of example apps under conf/examples, and the `conf/examples.cfg` file gives sample parameters for them.
|
@ -1,9 +0,0 @@
|
||||
---
|
||||
title: "Installation"
|
||||
description: "AppDaemon Installation"
|
||||
redirect_from: /ecosystem/appdaemon/installation/
|
||||
---
|
||||
|
||||
Installation is either by `pip3` or Docker.
|
||||
|
||||
Follow [these instructions](https://github.com/home-assistant/appdaemon/blob/dev/README.rst) for full details.
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: "Operation"
|
||||
description: "Operation"
|
||||
redirect_from: /ecosystem/appdaemon/tutorial/
|
||||
---
|
||||
|
||||
Since `AppDaemon` under the covers uses the exact same APIs as the frontend UI, you typically see it react at about the same time to a given event. Calling back to Home Assistant is also pretty fast especially if they are running on the same machine. In action, observed latency above the built in automation integration is usually sub-second.
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: "Starting at Reboot"
|
||||
description: "Starting at Reboot"
|
||||
redirect_from: /ecosystem/appdaemon/reboot/
|
||||
---
|
||||
|
||||
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.
|
@ -1,89 +0,0 @@
|
||||
---
|
||||
title: "Running AppDaemon"
|
||||
description: "Running AppDaemon"
|
||||
redirect_from: /ecosystem/appdaemon/running/
|
||||
---
|
||||
|
||||
As configured, `AppDaemon` comes with a single HelloWorld App that will send a greeting to the logfile to show that everything is working correctly.
|
||||
|
||||
## Docker
|
||||
|
||||
Assuming you have set the configuration up as described above for Docker, you can run it with the command:
|
||||
|
||||
```bash
|
||||
$ docker run -d -v <Path to Config>/conf:/conf --name appdaemon acockburn/appdaemon:latest
|
||||
```
|
||||
|
||||
In the example above you would use:
|
||||
|
||||
```bash
|
||||
$ docker run -d -v /Users/foo/ha-config:/conf --name appdaemon acockburn/appdaemon:latest
|
||||
```
|
||||
|
||||
Where you place the `conf` and `conf/apps` directory is up to you - it can be in downloaded repository, or anywhere else on the host, as long as you use the correct mapping in the `docker run` command.
|
||||
|
||||
You can inspect the logs as follows:
|
||||
|
||||
```bash
|
||||
$ docker logs appdaemon
|
||||
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!
|
||||
```
|
||||
|
||||
Note that for Docker, the error and regular logs are combined.
|
||||
|
||||
## `pip3`
|
||||
|
||||
You can then run AppDaemon from the command line as follows:
|
||||
|
||||
```bash
|
||||
$ appdaemon -c conf/appdaemon.cfg
|
||||
```
|
||||
|
||||
If all is well, you should see something like the following:
|
||||
|
||||
```bash
|
||||
$ appdaemon -c conf/appdaemon.cfg
|
||||
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!
|
||||
```
|
||||
|
||||
## AppDaemon arguments
|
||||
|
||||
```txt
|
||||
usage: appdaemon [-h] [-c CONFIG] [-p PIDFILE] [-t TICK] [-s STARTTIME]
|
||||
[-e ENDTIME] [-i INTERVAL]
|
||||
[-D {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-v] [-d]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c CONFIG, --config CONFIG
|
||||
full path to config file
|
||||
-p PIDFILE, --pidfile PIDFILE
|
||||
full path to PID File
|
||||
-t TICK, --tick TICK time in seconds that a tick in the schedular lasts
|
||||
-s STARTTIME, --starttime STARTTIME
|
||||
start time for scheduler <YYYY-MM-DD HH:MM:SS>
|
||||
-e ENDTIME, --endtime ENDTIME
|
||||
end time for scheduler <YYYY-MM-DD HH:MM:SS>
|
||||
-i INTERVAL, --interval INTERVAL
|
||||
multiplier for scheduler tick
|
||||
-D {DEBUG,INFO,WARNING,ERROR,CRITICAL}, --debug {DEBUG,INFO,WARNING,ERROR,CRITICAL}
|
||||
debug level
|
||||
-v, --version show program's version number and exit
|
||||
-d, --daemon run as a background process
|
||||
```
|
||||
|
||||
-c is the path to the configuration file. If not specified, AppDaemon will look for a file named `appdaemon.cfg` first in `~/.homeassistant` then in `/etc/appdaemon`. If the file is not specified and it is not found in either location, AppDaemon will raise an exception.
|
||||
|
||||
-d and -p are used by the init file to start the process as a daemon and are not required if running from the command line.
|
||||
|
||||
-D can be used to increase the debug level for internal AppDaemon operations as well as apps using the logging function.
|
||||
|
||||
The -s, -i, -t and -s options are for the Time Travel feature and should only be used for testing. They are described in more detail in the API documentation.
|
@ -1,137 +0,0 @@
|
||||
---
|
||||
title: "AppDaemon Tutorial"
|
||||
description: "AppDaemon Tutorial"
|
||||
redirect_from: /ecosystem/appdaemon/tutorial/
|
||||
---
|
||||
|
||||
## Another Take on Automation
|
||||
|
||||
If you haven't yet read Paulus' excellent Blog entry on [Perfect Home Automation](/blog/2016/01/19/perfect-home-automation/) I would encourage you to take a look. As a veteran of several Home Automation systems with varying degrees success, it was this article more than anything else that convinced me that Home Assistant had the right philosophy behind it and was on the right track. One of the most important points made is that being able to control your lights from your phone, 9 times out of 10 is harder than using a light switch - where Home Automation really comes into its own is when you start removing the need to use a phone or the switch - the "Automation" in Home Automation. A surprisingly large number of systems out there miss this essential point and have limited abilities to automate anything which is why a robust and open system such as Home Assistant is such an important part of the equation in bring this all together in the vast and chaotic ecosystem that is the "Internet of Things".
|
||||
|
||||
So given the importance of Automation, what should Automation allow us to do? I am a pragmatist at heart so I judge individual systems by the ease of accomplishing a few basic but representative tasks:
|
||||
|
||||
- Can the system respond to presence or absence of people?
|
||||
- Can I turn a light on at Sunset +/- a certain amount of time?
|
||||
- Can I arrive home in light or dark and have the lights figure out if they should be on or off?
|
||||
- As I build my system out, can I get the individual pieces to co-operate and use and re-use (potentially complex) logic to make sure everything works smoothly?
|
||||
- Is it open and expandable?
|
||||
- Does it run locally without any reliance on the cloud?
|
||||
|
||||
In my opinion, Home Assistant accomplishes the majority of these very well with a combination of Automations, Scripts and Templates, and its Restful API.
|
||||
|
||||
So why `AppDaemon`? AppDaemon is not meant to replace Home Assistant Automations and Scripts, rather complement them. For a lot of things, automations work well and can be very succinct. However, there is a class of more complex automations for which they become harder to use, and AppDaemon then comes into its own. It brings quite a few things to the table:
|
||||
|
||||
- New paradigm - some problems require a procedural and/or iterative approach, and `AppDaemon` Apps are a much more natural fit for this. Recent enhancements to Home Assistant scripts and templates have made huge strides, but for the most complex scenarios, Apps can do things that Automations can't
|
||||
- Ease of use - AppDaemon's API is full of helper functions that make programming as easy and natural as possible. The functions and their operation are as "Pythonic" as possible, experienced Python programmers should feel right at home.
|
||||
- Reuse - write a piece of code once and instantiate it as an app as many times as you need with different parameters e.g., a motion light program that you can use in 5 different places around your home. The code stays the same, you just dynamically add new instances of it in the configuration file
|
||||
- Dynamic - AppDaemon has been designed from the start to enable the user to make changes without requiring a restart of Home Assistant, thanks to its loose coupling. However, it is better than that - the user can make changes to code and AppDaemon will automatically reload the code, figure out which Apps were using it and restart them to use the new code with out the need to restart `AppDaemon` itself. It is also possible to change parameters for an individual or multiple apps and have them picked up dynamically, and for a final trick, removing or adding apps is also picked up dynamically. Testing cycles become a lot more efficient as a result.
|
||||
- Complex logic - Python's If/Else constructs are clearer and easier to code for arbitrarily complex nested logic
|
||||
- Durable variables and state - variables can be kept between events to keep track of things like the number of times a motion sensor has been activated, or how long it has been since a door opened
|
||||
- All the power of Python - use any of Python's libraries, create your own modules, share variables, refactor and re-use code, create a single app to do everything, or multiple apps for individual tasks - nothing is off limits!
|
||||
|
||||
It is in fact a testament to Home Assistant's open nature that a component like `AppDaemon` can be integrated so neatly and closely that it acts in all ways like an extension of the system, not a second class citizen. Part of the strength of Home Assistant's underlying design is that it makes no assumptions whatever about what it is controlling or reacting to, or reporting state on. This is made achievable in part by the great flexibility of Python as a programming environment for Home Assistant, and carrying that forward has enabled me to use the same philosophy for `AppDaemon` - it took surprisingly little code to be able to respond to basic events and call services in a completely open ended manner - the bulk of the work after that was adding additional functions to make things that were already possible easier.
|
||||
|
||||
## How it Works
|
||||
|
||||
The best way to show what AppDaemon does is through a few simple examples.
|
||||
|
||||
### Sunrise/Sunset Lighting
|
||||
|
||||
Lets start with a simple App to turn a light on every night fifteen
|
||||
minutes (900 seconds) before sunset and off every morning at sunrise.
|
||||
Every App when first started will have its ``initialize()`` function
|
||||
called which gives it a chance to register a callback for AppDaemons's
|
||||
scheduler for a specific time. In this case we are using
|
||||
`run_at_sunrise()` and `run_at_sunset()` to register 2 separate
|
||||
callbacks. The named argument `offset` is the number of seconds offset
|
||||
from sunrise or sunset and can be negative or positive (it defaults to
|
||||
zero). For complex intervals it can be convenient to use Python's
|
||||
`datetime.timedelta` class for calculations. In the example below,
|
||||
when sunrise or just before sunset occurs, the appropriate callback
|
||||
function, `sunrise_cb()` or `before_sunset_cb()` is called which
|
||||
then makes a call to Home Assistant to turn the porch light on or off by
|
||||
activating a scene. The variables `args["on_scene"]` and
|
||||
`args["off_scene"]` are passed through from the configuration of this
|
||||
particular App, and the same code could be reused to activate completely
|
||||
different scenes in a different version of the App.
|
||||
|
||||
```python
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
|
||||
class OutsideLights(hass.Hass):
|
||||
def initialize(self):
|
||||
self.run_at_sunrise(self.sunrise_cb)
|
||||
self.run_at_sunset(self.before_sunset_cb, offset=-900)
|
||||
|
||||
def sunrise_cb(self, kwargs):
|
||||
self.turn_on(self.args["off_scene"])
|
||||
|
||||
def before_sunset_cb(self, kwargs):
|
||||
self.turn_on(self.args["on_scene"])
|
||||
```
|
||||
|
||||
This is also fairly easy to achieve with Home Assistant automations, but we are just getting started.
|
||||
|
||||
### Motion Light
|
||||
|
||||
Our next example is to turn on a light when motion is detected and it is dark, and turn it off after a period of time. This time, the `initialize()` function registers a callback on a state change (of the motion sensor) rather than a specific time. We tell AppDaemon that we are only interested in state changes where the motion detector comes on by adding an additional parameter to the callback registration - `new = "on"`. When the motion is detected, the callback function `motion()` is called, and we check whether or not the sun has set using a built-in convenience function: `sun_down()`. Next, we turn the light on with `turn_on()`, then set a timer using `run_in()` to turn the light off after 60 seconds, which is another call to the scheduler to execute in a set time from now, which results in `AppDaemon` calling `light_off()` 60 seconds later using the `turn_off()` call to actually turn the light off. This is still pretty simple in code terms:
|
||||
|
||||
```python
|
||||
import appdaemon.appapi as appapi
|
||||
|
||||
|
||||
class FlashyMotionLights(appapi.AppDaemon):
|
||||
def initialize(self):
|
||||
self.listen_state(self.motion, "binary_sensor.drive", new="on")
|
||||
|
||||
def motion(self, entity, attribute, old, new, kwargs):
|
||||
if self.sun_down():
|
||||
self.turn_on("light.drive")
|
||||
self.run_in(self.light_off, 60)
|
||||
|
||||
def light_off(self, kwargs):
|
||||
self.turn_off("light.drive")
|
||||
```
|
||||
|
||||
This is starting to get a little more complex in Home Assistant automations requiring an Automation rule and two separate scripts.
|
||||
|
||||
Now lets extend this with a somewhat artificial example to show something that is simple in AppDaemon but very difficult if not impossible using automations. Lets warn someone inside the house that there has been motion outside by flashing a lamp on and off 10 times. We are reacting to the motion as before by turning on the light and setting a timer to turn it off again, but in addition, we set a 1 second timer to run `flash_warning()` which when called, toggles the inside light and sets another timer to call itself a second later. To avoid re-triggering forever, it keeps a count of how many times it has been activated and bales out after 10 iterations.
|
||||
|
||||
```python
|
||||
import homeassistant.appapi as appapi
|
||||
|
||||
|
||||
class MotionLights(appapi.AppDaemon):
|
||||
def initialize(self):
|
||||
self.listen_state(self.motion, "binary_sensor.drive", new="on")
|
||||
|
||||
def motion(self, entity, attribute, old, new, kwargs):
|
||||
if self.self.sun_down():
|
||||
self.turn_on("light.drive")
|
||||
self.run_in(self.light_off, 60)
|
||||
self.flashcount = 0
|
||||
self.run_in(self.flash_warning, 1)
|
||||
|
||||
def light_off(self, kwargs):
|
||||
self.turn_off("light.drive")
|
||||
|
||||
def flash_warning(self, kwargs):
|
||||
self.toggle("light.living_room")
|
||||
self.flashcount += 1
|
||||
if self.flashcount < 10:
|
||||
self.run_in(self.flash_warning, 1)
|
||||
```
|
||||
|
||||
Of course if I wanted to make this App or its predecessor reusable I would have provide parameters for the sensor, the light to activate on motion, the warning light and even the number of flashes and delay between flashes.
|
||||
|
||||
In addition, Apps can write to `AppDaemon`'s logfiles, and there is a system of constraints that allows you to control when and under what circumstances Apps and callbacks are active to keep the logic clean and simple.
|
||||
|
||||
I have spent the last few weeks moving all of my (fairly complex) automations over to `AppDaemon` and so far it is working very reliably.
|
||||
|
||||
Some people will maybe look at all of this and say "what use is this, I can already do all of this", and that is fine, as I said this is an alternative not a replacement, but I am hopeful that for some users this will seem a more natural, powerful and nimble way of building potentially very complex automations.
|
||||
|
||||
If this has whet your appetite, feel free to give it a try.
|
||||
|
||||
Happy Automating!
|
||||
|
@ -1,13 +0,0 @@
|
||||
---
|
||||
title: "Updating AppDaemon"
|
||||
description: "Updating AppDaemon"
|
||||
redirect_from: /ecosystem/appdaemon/updating/
|
||||
---
|
||||
|
||||
To update AppDaemon after I have released new code, just run the following command to update your copy:
|
||||
|
||||
```bash
|
||||
sudo pip3 install --upgrade appdaemon
|
||||
```
|
||||
|
||||
If you are using Docker, rerun the steps to grab the latest Docker image.
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "Windows Support"
|
||||
description: "Windows Support"
|
||||
redirect_from: /ecosystem/appdaemon/windows/
|
||||
---
|
||||
|
||||
AppDaemon runs under windows and has been tested with the official 3.5.2 release of Python. There are a couple of caveats however:
|
||||
|
||||
- The `-d` or `--daemonize` option is not supported owing to limitations in the Windows implementation of Python.
|
||||
- Some internal diagnostics are disabled. This is not user visible but may hamper troubleshooting of internal issues if any crop up
|
||||
|
||||
AppDaemon can be installed exactly as per the instructions for every other version using pip3.
|
||||
|
||||
## Windows Under the Linux Subsystem
|
||||
|
||||
Windows 10 now supports a full Linux bash environment that is capable of running Python. This is essentially an Ubuntu distribution and works extremely well. It is possible to run AppDaemon in exactly the same way as for Linux distributions, and none of the above Windows Caveats apply to this version. This is the recommended way to run AppDaemon in a Windows 10 and later environment.
|
@ -1,34 +0,0 @@
|
||||
---
|
||||
title: "HADashboard"
|
||||
description: "HADashboard is a dashboard for Home Assistant that is intended to be wall mounted, and is optimized for distance viewing."
|
||||
redirect_from: /ecosystem/hadashboard/
|
||||
---
|
||||
|
||||
HADashboard is a modular, skinnable dashboard for [Home Assistant](/) that is intended to be wall mounted, and is optimized for distance viewing.
|
||||
|
||||
<p class='img'>
|
||||
<img src='/images/hadashboard/dash1.png' />
|
||||
Default Theme
|
||||
</p>
|
||||
|
||||
<p class='img'>
|
||||
<img src='/images/hadashboard/dash2.png' />
|
||||
Obsidian Theme
|
||||
</p>
|
||||
|
||||
<p class='img'>
|
||||
<img src='/images/hadashboard/dash3.png' />
|
||||
Zen Theme
|
||||
</p>
|
||||
|
||||
<p class='img'>
|
||||
<img src='/images/hadashboard/dash4.png' />
|
||||
Simply Red Theme
|
||||
</p>
|
||||
|
||||
<p class='img'>
|
||||
<img src='/images/hadashboard/dash5.png' />
|
||||
Glassic Theme
|
||||
</p>
|
||||
|
||||
For full installation instructions see the HADashboard section in the [AppDaemon Project Documentation](http://appdaemon.readthedocs.io/en/stable/DASHBOARD_INSTALL.html).
|
@ -261,12 +261,6 @@
|
||||
<li>{% active_link /docs/autostart/synology/ Synology NAS %}</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
{% active_link /docs/ecosystem/appdaemon/ AppDaemon %}
|
||||
</li>
|
||||
<li>
|
||||
{% active_link /docs/ecosystem/hadashboard/ HADashboard %}
|
||||
</li>
|
||||
<li>
|
||||
Remote access
|
||||
<ul>
|
||||
|
@ -230,145 +230,6 @@ automation:
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### AppDaemon
|
||||
|
||||
#### AppDaemon event helper
|
||||
|
||||
Helper app that creates a sensor `sensor.deconz_event` with a state that represents the id from the last event and an attribute to show the event data.
|
||||
|
||||
Put this in `apps.yaml`:
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
deconz_helper:
|
||||
module: deconz_helper
|
||||
class: DeconzHelper
|
||||
```
|
||||
|
||||
Put this in `deconz_helper.py`:
|
||||
|
||||
```python
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DeconzHelper(hass.Hass):
|
||||
def initialize(self) -> None:
|
||||
self.listen_event(self.event_received, "deconz_event")
|
||||
|
||||
def event_received(self, event_name, data, kwargs):
|
||||
event_data = data["event"]
|
||||
event_id = data["id"]
|
||||
event_received = datetime.now()
|
||||
|
||||
self.log(f"Deconz event received from {event_id}. Event was: {event_data}")
|
||||
self.set_state(
|
||||
"sensor.deconz_event",
|
||||
state=event_id,
|
||||
attributes={
|
||||
"event_data": event_data,
|
||||
"event_received": str(event_received),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
Note: the event will not be visible before one event gets sent.
|
||||
|
||||
#### AppDaemon remote template
|
||||
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
remote_control:
|
||||
module: remote_control
|
||||
class: RemoteControl
|
||||
event: deconz_event
|
||||
id: dimmer_switch_1
|
||||
```
|
||||
|
||||
```python
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
|
||||
class RemoteControl(hass.Hass):
|
||||
def initialize(self):
|
||||
if "event" in self.args:
|
||||
self.listen_event(self.handle_event, self.args["event"])
|
||||
|
||||
def handle_event(self, event_name, data, kwargs):
|
||||
if data["id"] == self.args["id"]:
|
||||
self.log(data["event"])
|
||||
if data["event"] == 1002:
|
||||
self.log("Button on")
|
||||
elif data["event"] == 2002:
|
||||
self.log("Button dim up")
|
||||
elif data["event"] == 3002:
|
||||
self.log("Button dim down")
|
||||
elif data["event"] == 4002:
|
||||
self.log("Button off")
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
#### AppDaemon IKEA Tradfri remote template
|
||||
|
||||
Community app from [Teachingbirds](https://community.home-assistant.io/u/teachingbirds/summary). This app uses an IKEA Tradfri remote to control Sonos speakers with play/pause, volume up and down, next and previous track.
|
||||
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
sonos_remote_control:
|
||||
module: sonos_remote
|
||||
class: SonosRemote
|
||||
event: deconz_event
|
||||
id: sonos_remote
|
||||
sonos: media_player.sonos
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
{% raw %}
|
||||
|
||||
```python
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
|
||||
class SonosRemote(hass.Hass):
|
||||
def initialize(self):
|
||||
self.sonos = self.args["sonos"]
|
||||
if "event" in self.args:
|
||||
self.listen_event(self.handle_event, self.args["event"])
|
||||
|
||||
def handle_event(self, event_name, data, kwargs):
|
||||
if data["id"] == self.args["id"]:
|
||||
if data["event"] == 1002:
|
||||
self.log("Button toggle")
|
||||
self.call_service("media_player/media_play_pause", entity_id=self.sonos)
|
||||
|
||||
elif data["event"] == 2002:
|
||||
self.log("Button volume up")
|
||||
self.call_service("media_player/volume_up", entity_id=self.sonos)
|
||||
|
||||
elif data["event"] == 3002:
|
||||
self.log("Button volume down")
|
||||
self.call_service("media_player/volume_down", entity_id=self.sonos)
|
||||
|
||||
elif data["event"] == 4002:
|
||||
self.log("Button previous")
|
||||
self.call_service(
|
||||
"media_player/media_previous_track", entity_id=self.sonos
|
||||
)
|
||||
|
||||
elif data["event"] == 5002:
|
||||
self.log("Button next")
|
||||
self.call_service("media_player/media_next_track", entity_id=self.sonos)
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
## Binary Sensor
|
||||
|
||||
The following sensor types are supported:
|
||||
|
@ -21,15 +21,15 @@ This integration allows you to write Python scripts that are exposed as services
|
||||
|
||||
<div class='note'>
|
||||
|
||||
It is not possible to use Python imports with this integration. If you want to do more advanced scripts, you can take a look at [AppDaemon](/docs/ecosystem/appdaemon/)
|
||||
It is not possible to use Python imports with this integration. If you want to do more advanced scripts, you can take a look at [AppDaemon](https://appdaemon.readthedocs.io/en/latest/)
|
||||
|
||||
</div>
|
||||
|
||||
## Writing your first script
|
||||
|
||||
- Add to `configuration.yaml`: `python_script:`
|
||||
- Create folder `<config>/python_scripts`
|
||||
- Create a file `hello_world.py` in the folder and give it this content:
|
||||
- Add to `configuration.yaml`: `python_script:`
|
||||
- Create folder `<config>/python_scripts`
|
||||
- Create a file `hello_world.py` in the folder and give it this content:
|
||||
|
||||
```python
|
||||
name = data.get("name", "world")
|
||||
@ -37,8 +37,8 @@ logger.info("Hello %s", name)
|
||||
hass.bus.fire(name, {"wow": "from a Python script!"})
|
||||
```
|
||||
|
||||
- Start Home Assistant
|
||||
- Call service `python_script.hello_world` with parameters
|
||||
- Start Home Assistant
|
||||
- Call service `python_script.hello_world` with parameters
|
||||
|
||||
```yaml
|
||||
name: you
|
||||
@ -62,6 +62,7 @@ if entity_id is not None:
|
||||
service_data = {"entity_id": entity_id, "rgb_color": rgb_color, "brightness": 255}
|
||||
hass.services.call("light", "turn_on", service_data, False)
|
||||
```
|
||||
|
||||
The above `python_script` can be called using the following YAML as an input.
|
||||
|
||||
```yaml
|
||||
@ -78,7 +79,7 @@ You can add descriptions for your Python scripts that will be shown in the Call
|
||||
```yaml
|
||||
# services.yaml
|
||||
turn_on_light:
|
||||
description: Turn on a light and set its color.
|
||||
description: Turn on a light and set its color.
|
||||
fields:
|
||||
entity_id:
|
||||
description: The light that will be turned on.
|
||||
|
@ -181,9 +181,7 @@ The optional `html` field makes a custom text/HTML multi-part message, allowing
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/js/bootstrap.min.js"></script>
|
||||
</html>
|
||||
|
||||
```
|
||||
Obviously, this kind of complex html email reporting is done much more conveniently using Jinja2 templating from an [AppDaemon app](/docs/ecosystem/appdaemon/tutorial/), for example.
|
||||
|
||||
This platform is fragile and not able to catch all exceptions in a smart way because of the large number of possible configuration combinations.
|
||||
|
||||
|
@ -275,6 +275,7 @@ action:
|
||||
An example to show the use of event_data in action:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
- alias: 'Kitchen Telegram Speak'
|
||||
trigger:
|
||||
@ -288,6 +289,7 @@ An example to show the use of event_data in action:
|
||||
message: >
|
||||
Message from {{ trigger.event.data["from_first"] }}. {% for state in trigger.event.data["args"] %} {{ state }} {% endfor %}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
### Sample automations with callback queries and inline keyboards
|
||||
@ -301,6 +303,7 @@ A quick example to show some of the callback capabilities of inline keyboards wi
|
||||
Text repeater:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
- alias: 'Telegram bot that repeats text'
|
||||
trigger:
|
||||
@ -317,11 +320,13 @@ Text repeater:
|
||||
- "Edit message:/edit_msg, Don't:/do_nothing"
|
||||
- "Remove this button:/remove button"
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
Message editor:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
- alias: 'Telegram bot that edits the last sent message'
|
||||
trigger:
|
||||
@ -348,11 +353,13 @@ Message editor:
|
||||
Message id: {{ trigger.event.data.message.message_id }}.
|
||||
Data: {{ trigger.event.data.data }}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
Keyboard editor:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
- alias: 'Telegram bot that edits the keyboard'
|
||||
trigger:
|
||||
@ -372,11 +379,13 @@ Keyboard editor:
|
||||
inline_keyboard:
|
||||
- "Edit message:/edit_msg, Don't:/do_nothing"
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
Only acknowledges the 'NO' answer:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
- alias: 'Telegram bot that simply acknowledges'
|
||||
trigger:
|
||||
@ -390,11 +399,13 @@ Only acknowledges the 'NO' answer:
|
||||
callback_query_id: '{{ trigger.event.data.id }}'
|
||||
message: 'OK, you said no!'
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
Telegram callbacks also support arguments and commands the same way as normal messages.
|
||||
|
||||
{% raw %}
|
||||
|
||||
```yaml
|
||||
- alias: 'Telegram bot repeats arguments on callback query'
|
||||
trigger:
|
||||
@ -409,102 +420,7 @@ Telegram callbacks also support arguments and commands the same way as normal me
|
||||
callback_query_id: '{{ trigger.event.data.id }}'
|
||||
message: 'I repeat: {{trigger.event.data["args"]}}'
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
In this case, having a callback with `/repeat 1 2 3` with pop a notification saying `I repeat: [1, 2, 3]`
|
||||
|
||||
|
||||
For a more complex usage of the `telegram_bot` capabilities, using [AppDaemon](/docs/ecosystem/appdaemon/tutorial/) is advised.
|
||||
|
||||
This is how the previous 4 automations would be through a simple AppDaemon app:
|
||||
|
||||
```python
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
|
||||
class TelegramBotEventListener(hass.Hass):
|
||||
"""Event listener for Telegram bot events."""
|
||||
|
||||
def initialize(self):
|
||||
"""Listen to Telegram Bot events of interest."""
|
||||
self.listen_event(self.receive_telegram_text, "telegram_text")
|
||||
self.listen_event(self.receive_telegram_callback, "telegram_callback")
|
||||
|
||||
def receive_telegram_text(self, event_id, payload_event, *args):
|
||||
"""Text repeater."""
|
||||
assert event_id == "telegram_text"
|
||||
user_id = payload_event["user_id"]
|
||||
msg = "You said: ``` %s ```" % payload_event["text"]
|
||||
keyboard = [
|
||||
[("Edit message", "/edit_msg"), ("Don't", "/do_nothing")],
|
||||
[("Remove this button", "/remove button")],
|
||||
]
|
||||
self.call_service(
|
||||
"telegram_bot/send_message",
|
||||
title="*Dumb automation*",
|
||||
target=user_id,
|
||||
message=msg,
|
||||
disable_notification=True,
|
||||
inline_keyboard=keyboard,
|
||||
)
|
||||
|
||||
def receive_telegram_callback(self, event_id, payload_event, *args):
|
||||
"""Event listener for Telegram callback queries."""
|
||||
assert event_id == "telegram_callback"
|
||||
data_callback = payload_event["data"]
|
||||
callback_id = payload_event["id"]
|
||||
chat_id = payload_event["chat_id"]
|
||||
# keyboard = ["Edit message:/edit_msg, Don't:/do_nothing",
|
||||
# "Remove this button:/remove button"]
|
||||
keyboard = [
|
||||
[("Edit message", "/edit_msg"), ("Don't", "/do_nothing")],
|
||||
[("Remove this button", "/remove button")],
|
||||
]
|
||||
|
||||
if data_callback == "/edit_msg": # Message editor:
|
||||
# Answer callback query
|
||||
self.call_service(
|
||||
"telegram_bot/answer_callback_query",
|
||||
message="Editing the message!",
|
||||
callback_query_id=callback_id,
|
||||
show_alert=True,
|
||||
)
|
||||
|
||||
# Edit the message origin of the callback query
|
||||
msg_id = payload_event["message"]["message_id"]
|
||||
user = payload_event["from_first"]
|
||||
title = "*Message edit*"
|
||||
msg = "Callback received from %s. Message id: %s. Data: ``` %s ```"
|
||||
self.call_service(
|
||||
"telegram_bot/edit_message",
|
||||
chat_id=chat_id,
|
||||
message_id=msg_id,
|
||||
title=title,
|
||||
message=msg % (user, msg_id, data_callback),
|
||||
inline_keyboard=keyboard,
|
||||
)
|
||||
|
||||
elif data_callback == "/remove button": # Keyboard editor:
|
||||
# Answer callback query
|
||||
self.call_service(
|
||||
"telegram_bot/answer_callback_query",
|
||||
message="Callback received for editing the " "inline keyboard!",
|
||||
callback_query_id=callback_id,
|
||||
)
|
||||
|
||||
# Edit the keyboard
|
||||
new_keyboard = keyboard[:1]
|
||||
self.call_service(
|
||||
"telegram_bot/edit_replymarkup",
|
||||
chat_id=chat_id,
|
||||
message_id="last",
|
||||
inline_keyboard=new_keyboard,
|
||||
)
|
||||
|
||||
elif data_callback == "/do_nothing": # Only Answer to callback query
|
||||
self.call_service(
|
||||
"telegram_bot/answer_callback_query",
|
||||
message="OK, you said no!",
|
||||
callback_query_id=callback_id,
|
||||
)
|
||||
```
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 152 KiB |
Binary file not shown.
Before Width: | Height: | Size: 188 KiB |
Binary file not shown.
Before Width: | Height: | Size: 50 KiB |
Binary file not shown.
Before Width: | Height: | Size: 87 KiB |
Loading…
x
Reference in New Issue
Block a user