Triggering HomeAssistant Automations with Kapacitor
In an earlier post, I described how I've set up monitoring our home electricity usage using InfluxDB.
However, I thought it'd be good to be able to have this interact with our existing Home Automation stuff - we use HomeAssistant (previously Hass.io) for that.
In my earlier post, I described using Kapacitor to generate alert emails when the tumble dryer was finished, so in many ways it made sense to make this an extension of that. TICK scripts support calling arbitrary HTTP endpoints via the HTTPPost node, and HomeAssistant allows you to control sensors via HTTP API, so it's reasonably straightforward to implement.
Authorisation
HomeAssistant requires that API requests are accompanied by an API token, so the first thing to do is to generate one.
In HomeAssistant, you click your user icon and scroll down to Long-Lived Access tokens
before clicking Create Token
.

Make a note of the token, it's needed later
The aim
Part of the point in having Kapacitor call HomeAssistant is so that you can base automations on a combination of Kapacitor's decisions and information that HomeAssistant holds.
So, for the initial project, I decided I wanted to test the following
- Has the tumble dryer been turned on (Kapacitor knows/decides this)
- Is it Sunny outside (HomeAssistant knows this)
- If so, send a slightly grumbly notification
We need Kapacitor to push a boolean to HomeAssistant: is the tumble dryer on, or not?
HA Binary Sensor
Home Assistant's API allows us to control a Binary Sensor, which is perfect for this - we only care about whether it's on, or not.
To toggle the sensor, we just need to place a request to https://[home assistant]/api/states/binary_sensor.DEVICE_NAME
with a JSON payload like the following
{"state": "on", "attributes": {"friendly_name": "Radio"}}
This brings us to our first issue - Kapacitor's HTTPPost node submits JSON, but it's not structured the way we need.
Luckily, Kapacitor allows us to specify a template to use in alerts, so in /etc/kapacitor/kapacitor.conf
we add
[[httppost]]
endpoint = "home_assistant_binary_sensor"
url = "https://homeass.example.com/api/states/binary_sensor.{{.ID}}"
alert-template = "{\"state\": \"{{.Details}}\", \"attributes\": {\"friendly_name\": \"{{.Message}}\"}}"
headers = { Authorization = "Bearer REPLACE_THIS" }
Let's walk through this quickly:
-
endpoint
specifies a name for the config - it allows us to have multiplehttppost
configs in the same config file. We'll reference the endpoint name from our TICK script. -
url
specifies where the POST should be made to, I've included a template string ({{.ID}}
) so that we can control the device name from within the TICK script (that way we don't need multiple config sections if we want to add other sensors later) -
alert-template
is the magic we need, it provides a template for the request body - we're providing JSON structured the way HomeAssistant expects, and including template variables to be replaced by the TICK script. -
headers
adds our authorization header, you need to replace REPLACE_THIS with the long-lived access token generated earlier
We can then write a TICKscript to call this endpoint
var db = 'Systemstats'
var rp = 'autogen'
var measurement = 'power_watts'
var groupBy = []
var whereFilter = lambda: ("host" == 'tumble-dryer') AND isPresent("consumption")
var name = 'Tumble Dryer On'
var idVar = 'tumble_dryer_on'
var message = name
var idTag = 'alertID'
var levelTag = 'level'
var messageField = 'message'
var durationField = 'duration'
var outputDB = 'chronograf'
var outputRP = 'autogen'
var outputMeasurement = 'alerts'
var triggerType = 'threshold'
var details = '{{ if eq .Level "CRITICAL" }}on{{ else }}off{{ end }}'
var crit = 90
var data = stream
|from()
.database(db)
.retentionPolicy(rp)
.measurement(measurement)
.groupBy(groupBy)
.where(whereFilter)
|eval(lambda: "consumption")
.as('value')
var trigger = data
|alert()
.crit(lambda: "value" > crit)
.message(message)
.details(details)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
.post()
.endpoint('home_assistant_binary_sensor')
trigger
|eval(lambda: float("value"))
.as('value')
.keep()
|influxDBOut()
.create()
.database(outputDB)
.retentionPolicy(outputRP)
.measurement(outputMeasurement)
.tag('alertName', name)
.tag('triggerType', triggerType)
trigger
|httpOut('output')
In my previous post, I built a slightly more complex set of criteria for when the check was critical, but for brevity have left that out here.
The bits we're really interested in for the purposes of this post are
var name = 'Tumble Dryer On'
var idVar = 'tumble_dryer_on'
var message = name
...
var details = '{{ if eq .Level "CRITICAL" }}on{{ else }}off{{ end }}'
....
var trigger = data
|alert()
.crit(lambda: "value" > crit)
.message(message)
.details(details)
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
.post()
.endpoint('home_assistant_binary_sensor')
These set the variables we use, and call the HomeAssistant endpoint. If we assume the check's gone critical, we'd expect to see the following JSON payload being sent to https://homeass.example.com/api/states/binary_sensor.tumble_dryer_on
{"state": "on", "attributes": {"friendly_name": "Tumble Dryer On"}}
That POST creates a device within HomeAssistant, which we can then show the state of (screenshot is of the desk-plug I was initially testing with)

Further Automation
We're now ready to create a HomeAssistant automation to trigger whenever our new device changes state:
- id: '1629723901331' alias: Tumble dryer on description: '' trigger: - platform: state entity_id: binary_sensor.tumble_dryer_on to: 'on' condition: - condition: state entity_id: weather.home state: sunny action: - service: notify.mobile_app data: message: Really? It's sunny... mode: single
And voila! HomeAssistant will send a slightly passive-aggressive notification in the mobile app if the tumble dryer is turned on whilst it's Sunny outside. The wisdom of doing this (especially if you send the message to your spouse) is, of course, up for debate.
Value Sensor
Although it wasn't needed for this small project, having set up control of a binary sensor, I thought I'd quickly play around with how easy it would be to control a normal sensor (i.e. one that accepts values).
We have to use a different endpoint for this - https://[home assistant]/api/states/sensor.DEVICE_NAME
The JSON is more or less the same, though you can (optionally) include an attribute unit_of_measurement
. I decided not to, as you can add the measurement on in HomeAssistant's gauges anyway, so it seemed better to have the config section be portable between TICK scripts.
We add a new section to /etc/kapacitor/kapacitor.conf
[[httppost]]
endpoint = "home_assistant_sensor"
url = "https://homeass.example.com/api/states/sensor.{{.ID}}_value"
alert-template = "{\"state\": \"{{.Details}}\", \"attributes\": {\"friendly_name\": \"{{.Message}}\"}}"
headers = { Authorization = "Bearer REPLACE_THIS" }
This time, when we want to throw an alert, we do things a little differently
var trigger = data
|alert()
.crit(lambda: "value" > crit)
.message(message + '_value')
.details('{{ index .Fields "value" }}')
.id(idVar)
.idTag(idTag)
.levelTag(levelTag)
.messageField(messageField)
.durationField(durationField)
.post()
.endpoint('home_assistant_sensor')
The differences are
- We've passed a template string into
details()
so that the value is embedded - We've called endpoint
home_assitant_sensor
instead - In both the Kapacitor config and the TICKscript, I've appended
_value
. This isn't required by HomeAssistant, it just means that if you're calling both endpoints in the same TICK script, the sensors are already differentiated so you can reuse variables
It's worth noting, too, that this quickly thrown together example will only trigger when the level crosses into, or leaves critical - you won't get realtime updates without adjusting the TICKscript.
With calls going out, you can then have HomeAssistant draw a gauge

Of course, you could already generate that gauge in Chronograf, but you can also now trigger HomeAssistant automations based on the level.
Conclusion
With a little bit of fiddling around, I've managed to get my TICK stack to call HomeAssistant when aspects of our electricity usage cross a threshold, enabling further automation against the devices that HomeAssistant has control of.
Aside from sending jokey notifications, I don't actually know what my use-case for this is quite yet. One obvious possibility is "skinflint mode" where if the day's cost crosses a threshold, Kapacitor tells HomeAssistant, which then turns all the lights off. I strongly suspect such an automation wouldn't be well received by others in the house though.
Slightly more seriously, though, I can see us reaching a point where I've the ability to monitor usage on certain "trouble" sockets - where I know things sometimes get forgotten/left on - and then have HomeAssistant turn them off if other criteria (time, proximity etc) are met.
Whilst it might seem, ostensibly, that HomeAssistant could handle that on it's own (if it can control the sockets, it can meter them), it's not always that straightforward. As noted in my previous post, our Tumble dryer takes rest-breaks, so additional averaging over time is required. It's all but certain that other use-cases will yield other examples of situations where the additional flexibility of being able to perform arbitrary processing is helpful.
Until then, though, I'll just enjoy the passive aggressive notification and await the day where HomeAssistant says it's sunny, but it's actually raining outside.