AppDaemon
Taking Home Assistant to the next level with Python — part 3
AppDaemon is a great way to take your smart home automations to the next level. In this series I will guide you through AppDaemon and how to use it to take your home automation to the next level.
- Part 1: setting up AppDaemon and creating a simple light sequence
- Part 2: turning off power to a sit-stand desk if it’s no longer in use after a certain time, and how to test your app
- Part 3: controlling mechanical ventilation using HTTP requests and reporting its status back to Home Assistant (this article)
In this part I’ll show you an app that communicates with an API on a ESP8266 which controls a mechanical ventilation unit using RF.
Background
The mechanical ventilation unit is made by Zehnder and I have an ESP8266 with a nRF905 transceiver which is able to control it while also providing a nice API to interact with the module. The nRF905 API is made by eelcohn and can be found here. If you don’t have this Zehnder ventilation unit, you can still read along to learn about setting up slightly more complex AppDaemon apps, calling HTTP API’s and providing event data to the AppDaemon app.
For this project I had these requirements:
- Should be possible to set the ventilation to a certain speed via Home Assistant
- Should update Home Assistant every x seconds with the fan status
The app
I understand that for most people reading this the part that calls the nRF905 API isn’t that important so I will keep that for the end.
Inside the /config/appdaemon/apps
directory is a ventilation.py
file which contains the Ventilation
class.
The initialize()
method starts with getting the arguments provided by the apps.yaml
configuration, which looks like this:
ventilation:
module: ventilation
class: Ventilation
username: admin
password: !secret ventilation_password
device_id: 97
ip: !secret ventilation_host_ip
The AppDaemon configuration plays nicely with your secrets.yaml
file, so you don’t have to hardcode your passwords in your apps.
After getting the arguments, the method takes care of the following:
- Create a
VentilationClient
instance and assigns it to theself.client
field. This client handles the HTTP requests to the nRF905 API - Subscribes to the
SET_VENTILATION_EVENT
withself.set_fan_speed
as callback - Checks if
self.report_status_handler
isNone
, and when that’s the case it assign a handle to it. When it is notNone
, it cancels the timer of the handle before assigning a new one. This prevents thread starvation.
The Ventilation
class has three other methods:
set_fan_speed()
, which calls theVentilationClient
to set a fan speed for a certain amount of time. If the call to the client was successful,set_fan_speed()
will callget_ventilation_status()
to update the entities with the new fan state. By getting the status directly after a successful call, the frontend is updated without having to wait for the interval to pass.get_ventilation_status()
, which retrieves the fan’s status and calls_update_entities()
_update_entities()
which will update or create Home Assistant entities. Note: If the entity doesn’t exist, it will be created without a unique id. This means you cannot edit the entity through Home Assistant frontend.
If you take a look at line 36 and 37, you can see I retrieve the speed
and minutes
data from the data
parameter. This data is provided later on when triggering the event from Home Assistant. This eliminates the need for hardcoding each combination in the AppDaemon app.
The API client
The client itself has two main method: set_fan_speed
and get_status
. These methods are basically a wrapper around requests.get
, and by using the speed
and minute
parameters to create a URL to send to the nRF905 API it can set the fan to any speed for any amount of time.
The response is in JSON, which Python can handle just fine and the response.json()
method does a great job parsing the data and guessing the encoding. One thing I don’t like about just using JSON is the lack of type hints when developing the app. Which is why I’ve created the two dataclasses which represent a FanSpeedResponse
and a StatusResponse
. By using a dataclass
I don’t need to declare a __init__()
method and assign each parameter to an attribute (e.g. self.foo = foo
). It also makes it possible to type response.voltage
instead of response['voltage']
.
Triggering from Home Assistant
Script
In part 1 I already mentioned how scripts.yaml
can be used to trigger AppDaemon apps. For the ventilation app I use the same approach, but I’ve created multiple entries which all trigger SET_VENTILATION_EVENT
.
ventilation_max_10_min:
alias: Ventilatie max voor 10 minuten
sequence:
- event: SET_VENTILATION_EVENT
event_data:
speed: max
minutes: 10ventilation_max_30_min:
alias: Ventilatie max voor 30 minuten
sequence:
- event: SET_VENTILATION_EVENT
event_data:
speed: max
minutes: 30ventilation_max_60_min:
alias: Ventilatie max voor 60 minuten
sequence:
- event: SET_VENTILATION_EVENT
event_data:
speed: max
minutes: 60
As you can see, all three entries call the SET_VENTILATION_EVENT
, but with different event_data
. This makes it easy to change or add configurations without having to change your AppDaemon app.
Button
Within Home Assistant, I have a horizontal stack card with three button entities. Each button entity calls a service when clicked.
- type: button
name: 10 mins
tap_action:
action: call-service
service: script.turn_on
service_data: {}
target:
entity_id: script.ventilation_max_10_min
icon: mdi:clock-time-two-outline
icon_height: 30px
- type: button
name: 30 mins
tap_action:
action: call-service
service: script.turn_on
service_data: {}
target:
entity_id: script.ventilation_max_30_min
icon: mdi:clock-time-six-outline
icon_height: 30px
- type: button
name: 60 mins
tap_action:
action: call-service
service: script.turn_on
service_data: {}
target:
entity_id: script.ventilation_max_60_min
icon: mdi:clock-time-twelve-outline
icon_height: 30px
Other events
Manually triggering the ventilation isn’t the only way to use these scripts. You could also combine Home Assistant automations or sensor states to trigger these scripts. For example:
- Set the ventilation to max for 60 minutes when your cooking stove is turned on
- Set the ventilation to max when your humidity sensor detects someone showering
- Lower the ventilation when the temperature gets too low
- If your mechanical ventilation doesn’t have a CO2 sensor, you can link an external one via Home Assistant
Final thoughts
The possibilities are (nearly) endless when it comes to these custom AppDaemon apps. If it has an API, you can control it. I hope this series was a nice introduction to AppDaemon for creating automations for Home Assistant. Be sure to check out the AppDaemon documentation and play around with AppDaemon on your local Home Assistant instance, maybe even migrate some of your traditional automations just to get the hang of it :)