AppDaemon
Installation
Don't make this harder than it needs to be, and just use Docker.
This service definition can also be added to other compose files
services:
appdaemon:
container_name: appdaemon
image: acockburn/appdaemon:dev # (1)!
restart: unless-stopped # (2)!
tty: true # (3)!
volumes:
- /etc/localtime:/etc/localtime:ro # (4)!
- /etc/timezone:/etc/timezone:ro # (5)!
- ./:/conf # (6)!
ports:
- 5050:5050
- Use
acockburn/appdaemon:latestinstead if anything gets weird with the dev version. - A restart behavior is necessary for the container to get started when the host boots
- This is necessary to enable color output in the docker logs
- Passes the time of the host into the container for accurate logs
- Passes the timezone of the hose into the container for accurate logs
- Uses the directory of the
docker-compose.ymlfile as the/conffolder.
It's much faster to install pre-built packages from the package manager rather than having them built during a pip installation
Find package names in the Alpine Package Lists. They're named a little differently than the ones used for apt/apt-get.
appdaemon:
import_method: expert # (1)!
latitude: 0
longitude: 0
elevation: 0
time_zone: America/Chicago
plugins: # (2)!
HASS:
type: hass
ha_url: http://192.168.1.82:8123 # (3)!
token: !secret long_lived_token # (4)!
MQTT:
type: mqtt
namespace: default
client_host: 192.168.1.221
client_user: homeassistant
client_password: !secret mqtt_password # (5)!
client_topics:
- zigbee2mqtt/#
http:
url: http://0.0.0.0:5050 # (6)!
- This is necessary to be able to organize your code into Python packages
- There are basically 2 plugins -
HASSandMQTT - Need to be able to access Home Assistant at this address and port
- Secrets are loaded from
secrets.yamland accessed using!secret. The filename is plural, but the keyword is singular. - Secrets are loaded from
secrets.yamland accessed using!secret. The filename is plural, but the keyword is singular. - This has to match the right side of the port mapping in the
docker-compose.ymlfile.
Docker Launch Mechanics
Uses a dockerStart.sh script as the ENTRYPOINT12, so this all gets run every time the container (re)starts.
- Hard-codes the configuration directory as
/conf - Copies over some sample folders and content if they don't exist
- Modifies
/conf/appdaemon.yamlwith values from environment variables - Installs from
system_packages.txtusing anapk addcommand -
Installs pip packages
-
Final command is
Docker Commands
All these commands have to be run from the same folder as the docker-compose.yml file.
The up command will download the images if necessary, create new containers, and start them.
The -d flag detaches the shell from the running container.
These run commands start and run a one-off container with all the options defined in the compose file. The --rm flag makes the container remove itself after it stops.
This command is useful if you need to open a shell in the container. Overriding the entrypoint like that prevents the dockerStart.sh script from running too.
App Structure
Callbacks
run_in(self, callback, delay, **kwargs)run_once(self, callback, start, **kwargs)run_at(self, callback, start, **kwargs)run_daily(self, callback, start, **kwargs)run_hourly(self, callback, start, **kwargs)run_minutely(self, callback, start, **kwargs)run_every(self, callback, start, interval, **kwargs)
Example
from appdaemon.plugins.hass.hassapi import Hass
class MyClass(Hass):
def initialize(self):
self.run_in(
self.my_callback,
delay=1.5,
title='Test title',
message='Everything after delay is a custom keyword argument',
)
self.log('Running after a delay...')
def my_callback(self, cb_args: dict) -> None:
self.log(f'{cb_args["title"]}: {cb_args["message"]}')
from appdaemon.plugins.hass.hassapi import Hass
class MyClass(Hass):
def initialize(self):
self.run_in(
self.my_callback,
delay=1.5,
title='Test title',
message='Everything after delay is a custom keyword argument',
)
self.log('Running after a delay...')
def my_callback(self, title: str, message: str, **kwargs) -> None:
self.log(f'{title}: {message}')
self.log(kwargs) # (1)!
- There's always a
__thread_idkey in the kwargs dict.
Entity Class
Accessed with self.get_entity(entity_id)
listen_state(self, callback: Callable, **kwargs) → strattribute (str, optional), defaults tostatenew (optional)old (optional)duration (int, optional), units of seconds