I've recently setup Home Assistant, and one of my goals is to try and integrate my Raspberry Pi controlled LED strip desk lights into Home Assistant.

I want to have the LED strip automatically turn on when the motion sensor in my office detects motion, and to automatically turn off the lights when there has been no motion for a period of time (say, 15 minutes).

Previously, I put together a python script to control the lights that works off of a Redis queue. Messages posted to the Redis queue can be used to set the colour of the lights or perform other actions such as turning on or off the lights.

Ideally, I need Home Assistant to publish to the Redis queue when an automation is triggered.

Fortunately, Home Assistant has a solution in the form of AppDaemon:

AppDaemon is a loosely coupled, multi-threaded, sandboxed python execution environment for writing automation apps for home automation projects

Ultimately AppDaemon is going to let you run any Python script/codebase in a sandboxes environment within the Home Assistant ecosystem – this will work great for my specific scenario, but I can also see AppDaemon being a great solution where anyone wants to do something custom with Home Assistant.

Installing AppDaemon

Navigate to: Supervisor > Add-on store

Search for AppDaemon 4 and install:

Once the Add-on is installed, at minimum enable Start on boot and Watchdog

How AppDaemon Works

By default the code for AppDaemon apps lives in ~/config/appdaemon/apps

Within this folder you will find apps.yaml:

---
hello_world:
  module: hello
  class: HelloWorld

And a corresponding hello.py

import appdaemon.plugins.hass.hassapi as hass

#
# Hellow World App
#
# Args:
#

class HelloWorld(hass.Hass):

  def initialize(self):
     self.log("Hello from AppDaemon")
     self.log("You are now ready to run Apps!")

The apps.yaml file is responsible for telling AppDaemon which apps exist and where to find them. hello.py is a very basic implementation of an app.

The example HelloWorld class extends hass.Hass which would allow any implementing class to easily access the Home Assistant API which is documented here: HASS API Reference.

How will our app work?

The goal of my AppDaemon app will be to act as a bridge between Home Assistant and the Raspberry PI that controls my LED desk lights, so that Home Assistant can turn on and off the desk lights.

Provided I can transmit an event using PyRSMQ from AppDaemon, my Raspberry PI should be able to pick this up. And this interaction with PyRSMQ will need to happen when one of my motion sensors detects motion, or no motion for a given period.

Fortunately, AppDaemon supports installing external packages, so I'm now confident I will be able to get AppDaemon to do what I need it to do. The next challenge is how do we make AppDaemon aware that motion has occurred or stopped.

With Home Assistant, one of the supported trigger types for an automation is to fire an event. So the goal will be to have a few Home Assistant automatons which will fire off events based on the state of the motion sensor. AppDaemon will then pick up these events and pass on the relevant message to the Raspberry Pi over PyRSMQ.

Building the app

Installing third-party dependencies

The first thing that I will need to do is install a third party library for communicating with the Redis queue that the Raspberry PI uses – PyRSMQ.

External packages can be specified by the Configuration tab of the AppDaemon add-on:

In this example I have added PyRSMQ to python_packages. Save the configuration change before jumping back to the Info tab and restarting AppDaemon so that our config change takes affect.

While AppDaemon is restarting you should be able to navigate to the Log tab and refresh the logs until you see AppDaemon installed PyRSMQ on boot:

Basic App - Subscribing to a standard event

We can now set about creating our app. Within ~/config/appdaemon/apps create a office_motion.py file.

Let's start basic and see if we can get AppDaemon to turn on the LED light strip when motion occurs:

import json
import time
import hassapi as hass
from rsmq import RedisSMQ


class OfficeMotion(hass.Hass):

  def initialize(self):
    self.listen_event(self.motion_sensor_state_on, "state_changed", entity_id="binary_sensor.office_motion_sensor")
    self.log("Listening for motion events")

  def motion_sensor_state_on(self, event, data, kwargs):
    new_state = data['new_state']['state']
    self.log("New state: %s", new_state)
    if new_state == 'on':
      queue = RedisSMQ(host="192.168.75.243", qname="home-office-lights")
      queue.sendMessage().message({"type": "on"}).execute()
      self.log("Message sent")

When our app is started AppDaemon will call the initialize method, which will then register an event listener for us (using listen_event provided by the hass.Hass API). This event listener is subscribing to the standard state_changed event that Home Assistant emits. We've specifically subscribed to the motion sensor in my office (binary_sensor.office_motion_sensor). And AppDaemon will invoke motion_sensor_state_on with the event data when a relevant event is received.

When an event is received by motion_sensor_state_on we're just checking that the new state of the sensor is on before trying to send our a message over PyRSMQ.

Installing the App

For AppDaemon to pickup our app we need to register it in ~/config/appdaemon/apps/apps.yml:

---
hello_world:
  module: hello
  class: HelloWorld
office_motion:
  module: office_motion
  class: OfficeMotion

I have registered my office_motion app alongside the hello_world example that is provided out of the box. This just proves that you can have multiple apps running at the same time from within AppDaemon.

Using the Home Assistant interface we will need to restart AppDaemon for our configuration changes to take affect. Again, using the Log tab as AppDaemon restarts we should be able to see our app running thanks to the log statement in the initialize method:

As the second from last log line shows office_motion is Listening for motion events.

Supporting a custom event

The app we have built so far will use the built in state_changed events that Home Assistant emits to turn on the desk lights.

The other goal of this project was to also turn off the lights after 15 minutes of inactivity. One option would be to try and do this entirely in code using the state_changed event. However, as Home Assistants automations support duration bounded triggers we might as well use an automation rather than reinventing the wheel.

Let's go ahead and create an automation! We will create a single trigger of stopped detecting motion for 15 minutes on the relevant motion sensor:

The action that we will perform will be to fire an event. The name of the event will be office_no_motion - this is a custom event name that I have just made up:

We can save our automation and go back to our app code.

All we need to do now is add in another event listener as part of the initialize method, and define another method for handling the event:

import json
import time
import hassapi as hass
from rsmq import RedisSMQ


class OfficeMotion(hass.Hass):

  def initialize(self):
    self.listen_event(self.motion_sensor_state_on, "state_changed", entity_id="binary_sensor.office_motion_sensor")
    self.listen_event(self.office_lights_turn_off, "office_no_motion")
    self.log("Listening for motion events")

  def motion_sensor_state_on(self, event, data, kwargs):
    new_state = data['new_state']['state']
    self.log("New state: %s", new_state)
    if new_state == 'on':
      queue = RedisSMQ(host="192.168.75.243", qname="home-office-lights")
      queue.sendMessage().message({"type": "on"}).execute()
      self.log("Message sent")

  def office_lights_turn_off(self, event, data, kwargs):
      queue = RedisSMQ(host="192.168.75.243", qname="home-office-lights")
      queue.sendMessage().message({"type": "off"}).execute()
      self.log("Lights off")

In this revised version of our app code I have added the following to the initialize method:

self.listen_event(self.office_lights_turn_off, "office_no_motion")

And I have also added a new office_lights_turn_off method:

  def office_lights_turn_off(self, event, data, kwargs):
      queue = RedisSMQ(host="192.168.75.243", qname="home-office-lights")
      queue.sendMessage().message({"type": "off"}).execute()
      self.log("Lights off")

Because our automation makes sure the sensor is in the right state before emitting the office_no_motion event the implementation of our office_lights_turn_off doesn't need to check the state of the sensor and only needs to emit the relevant message over PyRSMQ.

Conclusion

AppDaemon is a great little add-on that is going to make building out custom functionality in Home Assistant a breeze, especially given how it supports installing any third-party libraries.

Whilst I've started with a very basic app, I can see myself writing more integrations with AppDaemon as I refine my home automation setup over time.