Dec 12, 2021
Back in the fall, I started playing with home automation. I had researched some of these things in past years, but concluded that they seemed too much of a hassle then. My opinion this time around has changed, but it's still not for everyone.
A few things have progressed that I think really made this interesting for me:
This area still seems immature. Lots of devices, competing protocols, and different controllers. A lot of people's experience will be 3-4 different apps each controlling 1-2 devices over a proprietary app interace. If all you want to do is set a daily schedule for a light or voice enable something, this works rather well.
I never really felt that voice commands for turning on lights were an improvement over a wall switch. More of a gimmick. Here's a better example of what I had in mind:
I have a dumb dehumidifier on a smart power outlet. I want the dehumidifier to:
The dehumidifier can achieve #1 and #2 on it's own, though it is rudimentary. The smart plug can achieve #3 via the proprietary app, though I had to reset the schedule twice a year between summer / winter power schedule periods. The closest I could do to #4 was manually yelling at some smart assistant. The engineer in me was unsatisfied.
There are lots of options available. I wanted the following:
After extensive research, I settled on Home Assistant. If you prefer to write code than use GUIs, I don't think there is any other choice.
To clarify naming, Home Assistant (HA) is a few things. HA Core is a python program that manages the event loop. HA includes HA Core, a web UI, and runs add-ons. You can run Home Assistant as an OS, an application, or in a docker container. I'm going the OS route at the moment, which leads to ...
I chose to run Home Assistant on a Raspberry Pi 4. It's usually pretty idle, pulling around 2-3 W but even loaded is only about 6W. I am unconcerned about leaving this running all of the time. Per year, around 25 kWh or <$5 in power costs.
I initially made the mistake of just using some SD card I had laying around, but you do need one with decent I/O performance. I later picked up the one that Tom's Hardware recommended.
I am concerned about the longevity of any SD card, but I haven't tackled that concern yet. If it had been available at the time, I probably would have purchased the Home Assistant Blue. I spent ~$100 on the Raspberry Pi 4 as a kit with case and power supply, but the HA Blue seems like improvement to the SD card situation.
(optional) As I went along, I also eventually picked up a USB Hub for Z-Wave / Zigbee communication. This just plugs straight into the Raspberry Pi and Home Assistant understands it.
Home Assistant tracks the state over time of various values, representing real world or synthetic measurements. The state of these values can then be inputs into code. The name of one of these value is called an 'entity' and the current value is the 'state'. Each Entity / State is also associated with one Dictionary of 'attributes' which can be anything and is also mutable.
Here's an example entity, seen by clicking on "Developer Tools" in the HA web interface. This entity is one that I created as an output of some code which tells me when to open the windows. The entity is named "app.window-recommender" (left column). The current state is "closed" (middle column). The inputs to this decision process are the attributes (right column). The outdoor temperature is too cold to open the windows, but the air quality and wind are fine.
The state value is tracked historically over time for a configurable window. If you click the little circled 'i' icon, you can view the history. Here's the history for the chosen animation routine of my Christmas LED lights, which changes to a new random value every 2 minutes between Sunset and 11pm:
In both of these cases, the state is string valued, but many are numeric. For example, here is the chart of the last week of wattage detected at the inverter on 1 of my 18 solar panels:
I don't think the attributes are stored historically, but I could be wrong. The attributes can be arbitrary, like the 'temp_ok: false' above. There are also a number of specific attributes that Home Assistant understands and can use to automatically create UI elements such as buttons, charts, etc. Here are a few:
These special attributes are used as automatic hints by HA's graphical interfaces, such as in this widget which makes use out of all 3 of the above by only telling HA which 3 entities we want:
Just to give you some taste for the kinds of things that might be measured, here are a few that my HA can track:
The other thing that HA exposes are services (callbacks effectively). They trigger some change, usually in a device like turning on a light switch or smart plug. They can also be software only services or somewhere in between, such as popping up a notification on a cell phone:
Results in:
Home Assistant is great at automatically detecting available devices on the network and prompting you to add them to tracked state. When I first turned it on, it prompted me to set up solar panels, printer, chromecast, internet weather, and some Kasa smart switches. Each of these is called an "integration" and can be installed into HA. An integration is basically a driver library for some device that can expose one or more entities and services.
There are a large selection of integrations. For example, I added AirVisual for tracking outdoor air quality, Nest for thermostats and doorbell, extensions for my solar panels, Chromecast, Phone, my LED lights, Kasa smart switches, and the Z-Wave USB stick. Most things you can get onto the network, you can probably integrate, some easier than others.
Nest was particularly cumbersome to set up. The instructions are great, but to authenticate to Nest, you must expose HA on a domain name serving on https / tls using the default port. You don't need to do so permanently but to set it up. You also have to pay Google some money one time. The process is essentially registering as a developer and telling Google Nest that you are creating a Cloud application, though you will be the only one using it. I got this working, but it was particularly difficult.
As part of the Nest process, I used these directions for making home assistant available on the internet via Cloudflare. You'll need to do this if you want access / data from a cell phone from outside of your home network. This is an excellent time to set up 2-factor auth for Home Assistant.
If "integrations" are drivers, "add-ons" are apps. These appear to be running processes that you can trivially install which sometimes have their own UI or similar. I have a few that I'd recommend:
User-defined scripts that wire some combination of entity states to service calls are core to HA. There are a variety of ways to do it.
HA itself has a YAML config language built in and some simple GUI Wizards built on top. I think this is fine for basic things like: "if this switch is toggled, toggle thse 3 lights", but I found them awkward.
Lots of folks in the community swear by an add-on called Node-RED. This is a slick graphical wire chart editor that lets you draw lines between nodes and then configure them to do things. Create an input node from a sunset event and drag a line to a porch light service switch to turn on the porch at sunset. That kind of thing. This is super popular. I spent some time with it. I ended up just creating a bunch of input nodes that were event triggers, hooked up to one javascript code node which output what I want to some service nodes. This was an awkward way to use the tool. I'd recommend this for non-programmers, but I didn't care for it.
Finally there is AppDaemaon. This is a process that runs alongside HA and lets you write callback-style code in python to do whatever you want. Here's an simple example script to control my dehumidifier plugged into a smart switch:
import appdaemon.plugins.hass.hassapi as hass class Humidifier(hass.Hass): def initialize(self): self.listen_state(self.EventCallback, "sensor.tv_state") self.listen_state(self.EventCallback, "sensor.pge_rate") self.listen_state(self.EventCallback, "sensor.downstairs_thermostat_humidity") self.UpdateState() def EventCallback(self, entity, attribute, old, new, kwargs): self.UpdateState() def UpdateState(self): is_tv_on = (self.get_state("sensor.tv_state") == "on") is_dehumidifier_on = (self.get_state("switch.dehumidifier") == "on") pge_rate = self.get_state("sensor.pge_rate") humidity = float(self.get_state( "sensor.downstairs_thermostat_humidity")) # To avoid thrashing back and forth if the humidity is right on the # line, we set the goalState to be unmodified from the current # state inside a buffer region. goal_state = is_dehumidifier_on if humidity <= 55: goal_state=False if humidity >= 60: goal_state = True # We also want to disable the humidifier any time the TV is # running or the pgeRate is part / peak, re-enabling when # that is no longer the case. if is_tv_on: goal_state = False if pge_rate == "peak" or pge_rate == "partialpeak": goal_state = False if goal_state == False: self.turn_off("switch.dehumidifier") if goal_state == True: self.turn_on("switch.dehumidifier")
The structure I use is probably not optimal. I have one function (UpdateState) that grabs the relevant states, computes stuff, and then calls services. I call this function on startup and I also configure callbacks for all of the sensors that call this function on any change. I could do more with the callback event logic, but this works fine.
Many of the inputs are actually outputs from other AppDaemaon scripts. For example, "sensor.pge_rate" is computed in another AppDaemaon script from the current time and is reused in several other scripts.
AppDaemaon supports logging statements, automatic reloads whenever the file changes, and even has a test framework, which is sorely missing from other options. It's well documented.
Each python class or "App" runs in it's own pinned thread unless otherwise requested. It can use persistent storage and communicate with other apps. Each thread is isolated, so a crash just restarts it and doesn't affect the other apps or AppDaemaon. I haven't yet coded an infinite loop, so I don't know what happens if a thread blocks, but I'd be surprised if that doesn't degrade gracefully.
AppDaemaon also runs an admin web server that shows you all sorts of state, such as all of the registered callbacks, running threads, etc. It's super barebones, but quite functional. It's very ugly, which gave me the initial impression that the add-on is perhaps not very mature, but that's definitely not the case.
I only have two complaints: logs are immediately discarded if you don't have the logging window open in the browser. The web server runs on it's own port, doesn't integrate into HA's auth, and so is inaccessible from the internet because the web server is completely unauthenticated.
When just getting started, I found this short article on AppDaemaon to be a great hello world. That said, I didn't initially understand all of the entity / state / attribute / service concepts above, and I'd strongly recommend understanding those before getting too far.