Fun with AppDaemon

I’m making increasing use of AppDaemon with  Home Assistant, as I like the flexibility of using Python to structure my automations. I discussed it a couple of weeks ago and this time I’m going to work through another example.

Just for fun, I decided I wanted a lamp to flash in my lounge when I get a notification on my phone. The lamp has a colour bulb, so I wanted to choose the colour based on the notifying app – green for Whatsapp, blue for SMS and so on.

The Android app can present a notification sensor. The resulting sensor in Home Assistant will have a state and set of attributes. The state will be the text of the message and the attributes will include the title and the name of the package sending the notification. When a new notification arrives, the state changes and you can base an automation on that state change.

You can control which apps send notifications through the allow list in the app configuration, and I recommend doing that to filter the notifications to just those that you want to process in Home Assistant.

I could listen for that state change directly in AppDaemon, but, following my own advice from my last post, I’m doing that as a normal Home Assistant automation. That makes it easier to enable and disable the automation through the front end:

The automation is triggered by the state change:

I’ve also put a number of conditions into the automation:

  • That I’m home.
  • That one of the lounge lights is on, indicating that it’s evening and I’m probably in the room.
  • The lamp itself is off  – I don’t want to mess with the light of the lamp if I’ve switched it on for some other reason.

These were set up in the UI, but for brevity the YAML looks like this:

  condition:
  - condition: or
    conditions:
    - condition: state
      entity_id: light.lounge
      state: 'on'
    - condition: state
      entity_id: light.lounge_lamp
      state: 'on'
  - condition: state
    entity_id: light.lamp
    state: 'off'
  - condition: state
    entity_id: input_boolean.home
    state: 'on'

I could put these conditions into my AppDaemon code, but I like the flexibility of being able to tweak them through the front end. I think it shows the value of the hybrid approach of doing the easy bits in Home Assistant and letting AppDaemon do the heavy lifting.

The action is to fire the event that I’ll listen for in my code:

I’m providing the sensor’s entity ID and the light that I want to flash here so I don’t have to store them as hardcoded parameters

Given that I’ve got this far with native automations, why am I using AppDaemon for the action? Complex if/else conditions are difficult to do in YAML. Having a different colour for each notification type cries out for a dictionary of key values, so that’s what I’m doing.

The first thing I needed was the package names of each notifying app, and I got those by examining the attributes of the sensor when I received notifications. I then created a config file to store the colour mappings, a transition value to use for the light flashing, and the number of times I wanted the light to flash:

lightnotify:
  module: lightnotify
  class: LightNotify
  transition: 2
  repeat: 2
  light: light.lamp
  apps:
    com.todoist: "red"
    com.whatsapp: "green"
    com.samsung.android.messaging: "blue"

I’ve got a Samsung phone and I’m using the default SMS app – the package name will be different if you’re using another app. The app list acts as a second level filter on top of the filter in the app itself.  I’m only flashing the light for a subset of the the notifications that I’m sending from the app as some of them are being sent to use with other automations.

The basic AppDaemon code listens for the state change, identifies the colour and, if one is found, triggers the flash:

import appdaemon.plugins.hass.hassapi as hass

class LightNotify(hass.Hass):
    def initialize(self):
        self.listen_event(self.light_notify, "LIGHT_NOTIFY")
        self.listen_event(self.test_sequence, "LIGHT_NOTIFY_TEST")

    def light_notify(self, event_name, data, kwargs):

        sensor = data.get("sensor")
        light = data.get("light")

        if self.get_state(sensor) == "Checking for new messages":
            sensor = None

        if sensor and light:
            package = self.get_state(sensor, "package")
            colour = self.args["apps"].get(package)

            if colour:
                self.sequence(light, colour)

    def sequence(self, light, colour):

        transition = self.args.get("transition", 1)
        repeat = self.args.get("repeat", 2)

        light_sequence = []

        for _ in range(repeat):
            light_sequence.append({ "light/turn_on":  { "entity_id": light, "color_name": colour, "brightness_pct": 80, "transition": transition }})
            light_sequence.append({ "sleep": transition + 1})
            light_sequence.append({ "light/turn_off": { "entity_id": light,  "transition": transition}})
            light_sequence.append({ "sleep": transition + 1})
        light_sequence.append({ "light/turn_off": { "entity_id": light, "transition": transition}})

        self.run_sequence(light_sequence)


    def test_sequence(self, event_name, data, kwargs):
        colour = data.get("colour", "green")
        light = data.get("light", None)
        if light:
            self.sequence(light, colour)

As you can see, doing the mapping of colours to package in Python is trivial and much easier than trying to do it in YAML.

I’m also checking the specific message text “Checking for new messages” and not running the automation when it occurs. I noticed that sometimes the state changes to this message after a while. It appears to be a transient notification from WhatsApp, so trapping it just prevents unnecessary light flashing.

I split out the actual flashing into a separate function so that I could call it separately for testing. Using the developer tools in the UI, I can fire an event with a named colour and light to check that my sequence is working as expected.

You can make a light flash directly in Home Assistant using the “flash” attribute in the call to turn a light on, but it has some limitations.  In YAML this would be:

action:
  service: light.turn_on
  data:
    entity_id: light.lamp
    flash: "short"

There are two problems with it. Firstly, the flash can either be short – a single flash – or long. I wanted the light to flash twice, with a nice transition so that it appears to pulse on and off rather than just flash.

The second problem is that you can’t control the colour of the flash. It just flashes at the last colour the light was set to. Adding a colour attribute to the service call doesn’t work. This is not a limitation of Home Assistant – it’s a limitation of Hue itself. The flash attribute is translated into Hue’s “alert” command, and that doesn’t seem to use colour. The relevant Home Assistant code is:

if flash == FLASH_LONG:
    command["alert"] = "lselect"
    del command["on"]
elif flash == FLASH_SHORT:
    command["alert"] = "select"
    del command["on"]
elif not self.is_innr:
    command["alert"] = "none"

Luckily it’s easy to set up a sequence to do what I wanted:

    def sequence(self, light, colour): 
        transition = self.args.get("transition", 1) 
        repeat = self.args.get("repeat", 2) 
        light_sequence = [] 

        for _ in range(repeat): 
            light_sequence.append({ "light/turn_on":  { "entity_id": light, "color_name": colour, "brightness_pct": 80, "transition": transition }}) 
            light_sequence.append({ "sleep": transition + 1}) 
            light_sequence.append({ "light/turn_off": { "entity_id": light,  "transition": transition}}) 
            light_sequence.append({ "sleep": transition + 1}) 
        light_sequence.append({ "light/turn_off": { "entity_id": light, "transition": transition}}) 
        
        self.run_sequence(light_sequence)

There are a couple of things to note here.

Firstly, I added the sleep steps between stages because the transitions didn’t seem to complete before the next step in the sequence. Adding the sleep gave time for the transition to finish and hold the light for a second before the next step.

Secondly, I added the second call to switch off the light when I found that it wasn’t always switching off as expected.

It’s not the most important, or even useful, automation that I’ve built, but it’s a bit of fun and every automation is an opportunity to learn more about the endless possibilities of Home Assistant.

in Home Automation

Add a Comment

Your email address will not be published. All comments will be reviewed.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Posts