Home Assistant automation for preserving last door opened state

Home Assistant Automation in Depth: Fusing Sensors Together for Stateful Automations

This post may contain affiliate links. Please see the disclaimer for more information.

This post is part of my “Home Assistant Automation in Depth” series. Here is the series index:

One of the most powerful things about Home Automation is being able to undertake many actions simultaneously, based upon a single input. For example, almost every automation system has the concept of scenes which allow you to set the state of multiple devices.

Just as powerful, if not more, is the ability to perform an action based on multiple inputs. We see this in the Home Assistant automation language which introduces the concepts of triggers and conditions. Triggers are the initial event that that the automation reacts to. Conditions are extra inputs which should be in the correct state to proceed.

With these simple concepts, we can easily create automation routines which operate only at certain times, are dependent on the state of the sun or even the moon (if you happen to be a werewolf). However, how do we track the state of something that happened, given that it’s current state may have changed?

State and Events

As a brief aside, we’ll discuss the difference between the state of something and events which occur. I’ll discuss this in general computing terms as well as within HASS.

State is data which describes the current situation. For example if the temperature is 24°C, then the state of the temperature entity should have a numeric value of 24 and a unit of Celsius. In Home Assistant states come in two varieties, the primary state and attributes. The primary state is generally just referred to as the state of the entity. Attributes are more like ancillary data, or metadata, but can be just as useful as the main state.

Events are things that happen. As such they are transient and only exist at the exact point in time at which they occur. Home Assistant automation events can be triggered when something changes state, when a particular time occurs, when a message is received and many others. Even an entity being in a particular state for a certain length of time can be considered an event. See the HASS Triggers documentation for more details. It should be noted that all the items on which an automation can trigger are events in the general computing sense not just those which trigger the ‘event’ trigger.

A system is ‘event driven’ if it primarily reacts to events occurring in the environment rather then polling the state of the environment. In this way, a Home Assistant automation can be seen as an event driven system, since they are primarily triggered by events.

Just get on with it!

Okay, okay.

Enough with the computer science lesson. How is this relevant to triggering an automation when multiple things happen? Well let me describe a situation:

You get up in the middle of the night, open the bedroom door and walk down the hall. You go into the bathroom, where the motion detector senses your presence and helpfully turns the light on. At 100% brightness. Temporarily blind you then proceeds to do whatever it was that got you out of bed.

The light controlled by Home Assistant automation
A very bad picture of the light in question.

Now I can guess what you are saying. Why not just adjust the brightness based on time of day. Well we could, but what happens when we have multiple people in the house, some of whom are awake and some asleep?

[For those that are wondering, the light pictured above is a Mi-Light RGB-CCT downlight. I think that link is to the correct one, but I can’t be sure as I bought mine from LimitlessLED before they closed down. Being an NZ company they were able to provide the documentation for my electrician to install these. I had several of these installed when the house was built, but I actually wish I’d had the whole house done with them.]

Sensor Fusion

Hopefully, we have more than just the time of day and the motion event to go on. With the help of another sensor (or set of sensors) we can integrate the data together and not blind anyone. It should be noted that this isn’t really sensor fusion in the strict mathematical sense! However, the definition fits quite well and I like the name!

In my case the second source of data are my zigbee door sensors. One of these is sensing the state of the bedroom door and the other is sensing the state of the kitchen door to the main living space. The logic is simple, if the bedroom door is opened the light comes on dim when motion is detected. If the kitchen door is opened, the light comes on bright when motion is detected.

Here’s where the difference between state and events becomes important. If I close the door behind me, the system cannot determine the correct door that should drive the lighting. This is because either neither is open, or the wrong door is open (if it was left open). It turns out the event of the door opening was the important part. Not the actual state at the time of the motion event.

We need to convert the door opening event into a state which we can store somewhere else. Luckily, we can do this easily in Home Assistant using an input_select entity.

Finally, some YAML

input_select:
  last_door:
    name: "Last Opened Door"
    options:
      - "Kitchen"
      - "Bedroom"

Here, we create an input_select entity which will store the last opened door. Note, you can extend this to store more states. For my purposes I only need to differentiate between bedrooms and kitchen.

An automation is used to catch the door open events and translate that to the correct state of the input_select:

automation:
  - alias: Set Last Opened Door
    trigger:
      - platform: state
        entity_id: binary_sensor.kitchen_hall_door_contact
        from: "off"
        to: "on"
      - platform: state
        entity_id: binary_sensor.bedroom_door_contact
        from: "off"
        to: "on"
        # further triggers for other bedroom doors can go here
    action:
      service: input_select.select_option
      entity_id: input_select.last_door
      data_template:
          option: "{% if trigger.entity_id == 'binary_sensor.kitchen_hall_door_contact' %}Kitchen{% else %}Bedroom{% endif %}"

I’ll walk through this automation step by step:

  • First we have our triggers, one for each door contact sensor we have. Obviously you can add as may of these as you want. For now I just have two.
  • There are no conditions, the automation just runs whenever triggered.
  • We call a single action which calls the select_option service of our input_select entity.
  • A simple template is used to compare the triggering entity ID (i.e. which contact sensor triggered the automation) with the entity ID of the kitchen door sensor. In that case we set the option to ‘Kitchen’ otherwise we set it to ‘Bedroom’. In this way, this automation will scale to multiple bedrooms, but can only have one kitchen. If you have multiple kitchens (!) or other relevant rooms, you’ll need a more complex template.

Stateful Home Assistant Automation

Next comes our automation to control the light. This automation is stateful, in that it executes differently depending on the state of the input_select we just set:

automation:
  - alias: Bathroom Motion Light
    trigger:
      platform: state
      entity_id: binary_sensor.bathroom_motion
      from: "off"
      to: "on"
    condition:
      condition: or
      conditions:
        - condition: sun
          after: sunset
          after_offset: "-00:20:00"
        - condition: sun
          before: sunrise
          before_offset: "00:20:00"
    action:
      - service: timer.start
        entity_id: timer.bathroom_light_timeout
      - condition: template
        value_template: "{{ states.light.bathroom.state == 'off' }}"
      - service: light.turn_on
        entity_id: light.bathroom
        data_template:
          brightness_pct: >
            {% if now().hour >= 20 or now().hour < 7 %}
            {% if states.input_select.last_door.state == 'Bedroom' %}
            1
            {% else %}
            100
            {% endif %}
            {% else %}
            100
            {% endif %}

Breaking it down

Again, I’ll go through the automation step by step:

  • First we have our trigger, in this case from our motion sensor.
  • Next we have a couple of conditions to only run the automation when it’s dark enough. In this case 20 minutes before sunset and 20 minutes after sunrise. We wrap these in an or block so that only one has to match.
  • Now we get to the actions, the first of these is to start a timer. This will be used later for turning off the light. For completeness the YAML for the timer looks like this:
timer:
  bathroom_light_timeout:
    duration: "00:02:00"
  • The next action is actually another condition, which basically matches only if the light is already off. If this is not matched the action block will terminate here. This condition ensures that the brightness of the light does not get changed if the door state changes. Placing this after the timer start also ensures that the timer is restarted by motion events in the bathroom.
  • The final action is obviously to turn the light on. The interesting part is the template logic. This contains a nested if block, the first part of which checks against some pre-defined times where we only want the light bright (before 8pm and after 7am). This is mostly useful in winter, since the sunset and sunrise rules will prevent the automation from running before/after these times in summer.
  • The inner if statement is where we check our door state. Here we check whether the last door was to a bedroom. In that case we set the brightness to 1% (plenty bright enough for night time use on these lights). Otherwise we go to full brightness.

Finishing It Off

The final piece of the puzzle is turning off the light when the timer expires:

automation:
  - alias: Bathroom Light Timeout
    trigger:
      platform: event
      event_type: timer.finished
      event_data:
        entity_id: timer.bathroom_light_timeout
    action:
      - service: light.turn_off
        entity_id: light.bathroom

This is reasonably self explanatory. The only odd looking bit is in the trigger where we must use the event platform to directly catch the timer.finished event, since the timer doesn’t have it’s own trigger type.

Conclusion

Phew! Hopefully you got this far and didn’t get lost in the weeds of the states vs events stuff!

The automations shown here have been running for several months pretty much flawlessly. I’ve given them some minor tweaks such as introducing the outer if statement to check the hours, when winter came around and it became apparent that it was needed.

The usual complaint is that motion sensing lights tend to turn off when people are still in the room but not moving. Since the motion sensor in this case is so sensitive we’ve not had much of an issue with this, it will trigger even on the slightest motion. Interestingly it also seems not to false trigger, so I think the balance is just right.

Thanks for following along! I don’t have any more plans for another Home Assistant Automation in Depth article, but I’m sure there will be another installment in the future. I just need to write some more interesting automations.

If you liked this post and want to see more, please consider subscribing to the mailing list (below) or the RSS feed. You can also follow me on Twitter. If you want to show your appreciation, feel free to buy me a coffee.

Zigbee sniffer dongle for zigbee2mqtt

zigbee2mqtt: Cheap Zigbee Without a Gateway

This post may contain affiliate links. Please see the disclaimer for more information.

I’m continuing to work through my backlog of completed but as yet undocumented Home Automation projects. As such you may be familiar with zigbee2mqtt already, since it’s been around for a while. However, I wanted to document my setup and show off a few automations for some specific use cases of mine.

Introduction

Two sensor projects have constantly eluded me in my ongoing efforts to transform my house into a DIY smarthome. The first of these are open/closed sensors for both internal and external doors and all the windows. The second are some input buttons allowing manual switching of devices in the cases where things aren’t automatically set correctly.

I’ve followed the various options over time and never quite managed to find anything I was happy with. I’ve even strongly considered building my own solution (as I’m doing with my room sensors). However, the trifecta of low power battery operation, nice compact physical design and being cheap enough to install everywhere stumped me. Then the Xiaomi Aqara/Mijia range of sensors started appearing. Those looked perfect, except for that horrible gateway. Enter zigbee2mqtt!

Zigbee2mqtt is a nodejs based software project that uses a cheap zigbee sniffer module with some custom firmware to replace the manufacturers gateways for a range of zigbee devices. As well as the Xiaomi devices it also supports Philips Hue and IKEA Tradfri devices among others. The software connects to the USB serial port provided by the dongle and sends messages via MQTT. It even comes with built in Home Assistant MQTT discovery support! (regular readers will know how much of a fan I am of that).

A Setup for Testing

To start playing around with these devices and not really knowing how well they were going to work in general and specifically with zigbee2mqtt, I decided to start with just a few devices. To this end, I ordered:

In addition, I needed the a CC-debugger programming tool and the associated cable in order to program the CC2531 dongle.

Installation and Setup

Once these arrived I was able to easily program the custom firmware to the CC2531 using the official instructions (I used the Linux version). After that I installed zigbee2mqtt again following the instructions. The only wrinkle in this process was getting the USB serial device to show up in the LXD container that runs my home automation components. This is reasonably specific to my environment of running in LXD, you wouldn’t encounter it on a bare Linux system.

Zigbee sniffer dongle for zigbee2mqtt
The zigbee sniffer dongle installed in the server. The green LED is extremely bright!

After that I set about pairing my devices, again following the excellent documentation. I found that I had to have my devices very close (within 30cm) to the dongle during the pairing process. I’m not familiar with the specifics of the Zigbee pairing process. However, I’m assuming that it uses a very low transmit power. This would act as a security measure to prevent pairing with unauthorised devices. Once the devices were paired I was immediately able to see the door sensors as binary sensors in HASS.

I was able to complete this whole process in about an hour. It was really almost too easy. Huzzah for more than adequate documentation and things going according to plan (that almost never happens).

Reading Button Presses

Integrating the buttons into Home Assistant is a little more difficult. This is because there is no built in abstraction for a button or control surface. Instead we fall back to catching the value published via MQTT when the button is pressed (actually, I note that there is a way to do this using an entity now, but that didn’t exist at the time I did it). Here is the set of automations for the button I put in my kitchen:

automation:
  - alias: Kitchen button downlights on
    trigger:
      platform: mqtt
      topic: 'zigbee2mqtt/kitchen_button'
    condition:
      condition: and
      conditions:
        - condition: template
          value_template: "{{ 'single' == trigger.payload_json.click }}"
        - condition: template
          value_template: "{{ states.light.kitchen_downlights.state == 'off' }}"
    action:
      service: script.turn_on
      data:
        entity_id: script.downlights_bright

  - alias: Kitchen button downlights off
    trigger:
      platform: mqtt
      topic: 'zigbee2mqtt/kitchen_button'
    condition:
      condition: and
      conditions:
        - condition: template
          value_template: "{{ 'single' == trigger.payload_json.click }}"
        - condition: template
          value_template: "{{ states.light.kitchen_downlights.state == 'on' }}"
    action:
      service: light.turn_off
      data:
        entity_id: light.kitchen_downlights

  - alias: Kitchen button dining room on
    trigger:
      platform: mqtt
      topic: 'zigbee2mqtt/kitchen_button'
    condition:
      condition: and
      conditions:
        - condition: template
          value_template: "{{ 'double' == trigger.payload_json.click }}"
        - condition: template
          value_template: "{{ states.light.dining_room_spots.state == 'off' }}"
    action:
      service: scene.turn_on
      entity_id: scene.dining_spots_bright

  - alias: Kitchen button dining room off
    trigger:
      platform: mqtt
      topic: 'zigbee2mqtt/kitchen_button'
    condition:
      condition: and
      conditions:
        - condition: template
          value_template: "{{ 'double' == trigger.payload_json.click }}"
        - condition: template
          value_template: "{{ states.light.dining_room_spots.state == 'on' }}"
    action:
      service: light.turn_off
      data:
        entity_id: light.dining_room_spots

  - alias: Kitchen button all lights on
    trigger:
      platform: mqtt
      topic: 'zigbee2mqtt/kitchen_button'
    condition:
      condition: and
      conditions:
        - condition: template
          value_template: "{{ 'long' == trigger.payload_json.click }}"
        - condition: or
          conditions:
            - condition: template
              value_template: "{{ states.light.kitchen_downlights.state == 'off' }}"
            - condition: template
              value_template: "{{ states.light.dining_room_spots.state == 'off' }}"
            - condition: template
              value_template: "{{ states.light.living_room_spots.state == 'off' }}"
    action:
      service: scene.turn_on
      entity_id: scene.main_lights

  - alias: Kitchen button all lights off
    trigger:
      platform: mqtt
      topic: 'zigbee2mqtt/kitchen_button'
    condition:
      condition: and
      conditions:
        - condition: template
          value_template: "{{ 'long' == trigger.payload_json.click }}"
        - condition: template
          value_template: "{{ states.light.kitchen_downlights.state == 'on' }}"
        - condition: template
          value_template: "{{ states.light.dining_room_spots.state == 'on' }}"
        - condition: template
          value_template: "{{ states.light.living_room_spots.state == 'on' }}"
    action:
      - service: light.turn_off
        entity_id: light.kitchen_downlights
      - service: light.turn_off
        entity_id: light.dining_room_spots
      - service: light.turn_off
        entity_id: light.living_room_spots

  - alias: Kitchen button candlelight dinner
    trigger:
      platform: mqtt
      topic: 'zigbee2mqtt/kitchen_button'
    condition:
      condition: and
      conditions:
        - condition: template
          value_template: "{{ 'triple' == trigger.payload_json.click }}"
    action:
      - service: light.turn_on
        data:
          entity_id: light.dining_room_spots
          brightness_pct: 1
      - service: light.turn_off
        entity_id: light.kitchen_downlights
      - service: light.turn_off
        entity_id: light.living_room_spots

Phew, that was ALOT of YAML! Each of these automations is basically the same, with a trigger on the relevant topic from MQTT. There then follows some conditions. The most important of these uses a template to unpack the click field of the JSON payload and check it against the type we are interested in. In this way we can do different things depending on the click type (e.g. single, double and triple clicks or long presses).

The other conditions in the automations perform some state checking to make sure that the action performed is correct depending on the current state of the house. For example there is no point turning the kitchen downlights on when they are already on. Instead the user clearly wants to turn them off. I spent quite some time tweaking these conditions over a few days of usage to get them just right. The final (and easiest) part of each automation is just performing the required action (or actions).

Button Functionality

With all this in place we end up with a single button which has the following functionality:

  • On a single click turn on or off the kitchen downlights, depending on their state at the time
  • For a double click turn on or off the dining room spotlights, again depending on their state
  • On a long click turn on or off all lights in our main living space, again depending on their state
  • A triple press turns off all the lights except the dining room which get dimmed (our romantic dinner scene)

That doesn’t even exhaust the capabilities of this switch. It can do quadruple (and maybe quintuple?) presses as well as variable length long presses. At some point the usability just starts to get silly though.

Xiaomi button driven by zigbee2mqtt
I installed one of the buttons in the kitchen, above the other light switches.

I have the other button set up to do something similar. However it’s on;y switching two things so it’s a little less interesting than the example above.

Monitoring Battery Status

Since these are battery powered devices, it’s useful to be able to monitor the battery level. In doing so we can avoid the otherwise inevitable situation of a significant other pressing the button and the lights not coming on. Luckily, zigbee2mqtt has us covered in this respect. For my buttons and sensors I added the following sensors for my battery levels:

sensor:
  - platform: mqtt
    name: "Kitchen/Hall Door Battery"
    state_topic: "zigbee2mqtt/kitchen_hall_door"
    unit_of_measurement: '%'
    value_template: "{{ value_json.battery }}"
  - platform: template
    sensors:
      kitchen_button_battery:
        friendly_name: "Kitchen Button Battery"
        unit_of_measurement: "%"
        value_template: "{{ states.sensor.kitchen_button.attributes.battery }}"

Here I’m just showing two sensors, one for the battery level of the door sensor and one for the battery level of the button. I obviously have similar configuration entries for the other devices.

For some reason the configuration required is a little inconsistent here. I found this was because the button has an entity created via MQTT discovery, which includes the battery level as an attribute, whilst the battery level is not included in the attributes for the door sensor entity.

This means that we create a raw MQTT sensor for the door sensor battery level and extract the reading out in the value_template. For the button battery sensor, we use a template sensor and just pull the required attribute out.

Battery Notifications

Of course, just having the battery levels as entities in Home Assistant is of limited use. What we really want it a notification when the battery gets low:

automation:
  - alias: Low Battery Notification
    trigger:
      - platform: numeric_state
        entity_id: sensor.kitchenhall_door_battery
        below: 20
      - platform: numeric_state
        entity_id: sensor.kitchen_button_battery
        below: 20
    action:
      service: notify.notify
      data_template:
        title: "Low Battery Alert"
        message: "Device '{{ trigger.to_state.name }}' has LOW battery: {{ trigger.to_state.state }}%."

Here we are triggering when any of the battery sensors we are interested in get below 20%. We then send the notification via the default notification service, using the trigger template variable to include the name of the sensor that triggered the alert and the state.

I actually have two such rules in my set up. This one at 20% and another critical warning at 5%. In this way I get an early heads up of a low battery in order to check I have replacement batteries ready. I then get another when the battery actually needs changing.

Quick Bonus Automation

As I haven’t said much about the actual door sensors in this post, here are the automations I use to turn on and off a dimmed downlight in the kitchen if the door opens at night:

automation:
  - alias: "Night mode kitchen light on"
    trigger:
      - platform: state
        entity_id: binary_sensor.kitchen_hall_door
        from: "off"
        to: "on"
    condition:
      - condition: state
        entity_id: input_select.home_mode
        state: "Night"
    action:
      - alias: Lights to Goodnight Scene
        service: scene.turn_on
        data:
          entity_id: scene.goodnight

  - alias: "Night mode kitchen light off"
    trigger:
      - platform: state
        entity_id: binary_sensor.kitchen_hall_door
        from: "on"
        to: "off"
        for:
          seconds: 30
    condition:
      - condition: state
        entity_id: input_select.home_mode
        state: "Night"
    action:
      - alias: Light Off
        service: light.turn_off
        entity_id: light.kitchen_downlight_1

These are pretty self explanatory so I won’t go into them further. The interesting thing here is that the combined reaction time of the sensors, HASS server and smart bulb is so quick that the light is always on by the time the door is open enough to see the bulb. More than once this has tricked me into thinking that the off automation is not actually working and that the light is staying on all night. I found myself waiting outside the door a few times for the light to go off, until I had built up confidence that it was working as expected.

Xiaomi door sensor driven by zigbee2mqtt
One of the door sensors installed on the door. Due to the design of our doors I opted to install the sensor on the inside of the door jamb with the magnet at 90 degrees, which works perfectly.

Impressions of the System

I’m really happy with both the software and hardware components of this project. Aside from the inevitable tweaking of the automation rules I had all four devices that I ordered working in an evening. This included flashing the dongle, installing zigbee2mqtt and all the HASS configuration and automations. Zigbee2mqtt seems to be a pretty well put together piece of software with excellent documentation. I’ve also had no stability issues from it in the several months it’s been running.

In terms of the sensors themselves, they are physically quite nice and well designed. Although they are made of plastic, the matte finish on the outside makes them feel more expensive than they are. They are also paintable, if you are so inclined. The adhesive tape used to stick the devices to the wall is also incredibly strong. You really have to make sure you get them in the right place before you push down!

Conclusion

Overall, I can recommend both zigbee2mqtt and these specific devices for anyone wanting to introduce zigbee devices to their home automation system. The barrier to entry both in terms of cost and set up time/complexity is low if you already have a bit of Linux and Home Assistant knowledge. For example, if you’ve been running a HASS server for a little while and have tacked a few other integration projects you should have no problem. At this stage I wouldn’t recommend it for absolute newcomers due to the firmware flashing part.

For my part, I will definitely be buying more of the window/door sensors with the aim of fitting out the whole house eventually. I’ll probably also get a couple more buttons too! In addition I’ll be investigating the ever growing list of zigbee2mqtt supported devices to see what else I can add to my system over time.

If you liked this post and want to see more, please consider subscribing to the mailing list (below) or the RSS feed. You can also follow me on Twitter. If you want to show your appreciation, feel free to buy me a coffee.