Sending home assistant events on system startup and shutdown

Date:
2023-07-21

Tags:

home-assistant
nix
linux
systemd
sops-nix

It is quite useful to hook automations to the start or shutdown of certain computers. For instance, you can set to turn on or off certain lights or use the start up of a server as a condition to run other automations that depend on it. Here I will explain how I use nix systemd services together with sops-nix to send events from my computers managed through nix to my home-assistant server.

Home-assistant API

First, we need to check the home assistant API specification. To send events, we need to send a POST to the endpoint: /api/events/<event_name>.

To do so, we need to activate the api integration by adding api: into our configuration.yaml. And then, we can obtain the API token in http://$HASS_ADDR:8123/profile (where $HASS_ADDR is the address of your home-assistant instance). From this point on, $HASS_TOKEN refers to this home-assistant API token.

A useful tool to check events is http://$HASS_ADDR:8123/developer-tools/event, which allows us to subscribe to any event and watch the data that we receive. We can use this to try subscribing to a testing event and sending the following curl request to check that everything is working:

curl -X POST \
    -H "Authorization: Bearer $HASS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"hello" : "world"}' \
    $HASS_SERVER/api/events/testing

We can also use hass-cli which abstracts the API calls through a handy cli:

hass-cli event fire testing --json '{"hello" : "world"}'

If everything went well, curl should return {"message":"Event testing fired."} and in home-assistant the following entry will be present:

event_type: testing
data:
  hello: world
origin: REMOTE
time_fired: "2023-07-22T12:13:49.674053+00:00"
context:
  id: **************************
  parent_id: null
  user_id: *******************************

Systemd services

For my use case I wanted to send 4 different events:

  • Screen locked / unlock
  • System startup / shutdown

Screen lock/unlock

Since I use i3lock for my lock screen, I wrapped the call around event fires as follows:

#!/usr/bin/env bash

JSON_DATA="{\"hostname\":\"$(hostname)\"}"

curl -X POST \
    -H "Authorization: Bearer $HASS_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$JSON_DATA" \
    $HASS_SERVER/api/events/nixos.lock

i3lock "$@" --nofork

curl -X POST \
    -H "Authorization: Bearer $HASS_TOKEN" \
    -H "Content-Type: application/json" \
    -d "$JSON_DATA" \
    $HASS_SERVER/api/events/nixos.unlock

Notice the --nofork flag, without it, the lock will be non-blocking and the script will continue executing without waiting for the screen to unlock.

With nix and home-manager, you can wrap i3lock like this:

{config, pkgs, ...}:

{
  home.packages = [
    (pkgs.writeShellScriptBin "i3lock" ''
      ${pkgs.curl}/bin/curl -X POST \
          -H "Authorization: Bearer $HASS_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{"hostname" : "${config.networking.hostName}"}' \
          $HASS_SERVER/api/events/nixos.lock &

      ${pkgs.i3lock-color}/bin/i3lock-color --nofork "$@"

      ${pkgs.curl}/bin/curl -X POST \
          -H "Authorization: Bearer $HASS_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{"hostname" : "${config.networking.hostName}"}' \
          $HASS_SERVER/api/events/nixos.unlock
    '')
  ];
}

System startup / shutdown

Now, we need to specify the systemd service that sends home-assistant the startup and shutdown events. This will be a Type=oneshot since it will only run the script once, and we want RemainAfterExit=true so that ExecStop runs on system shutdown. Since for the request to work we need network connection, we have to set network-online.target as both wants and after:

[Unit]
Description=Notify Home Assistant of startup/shutdown
Wants=network-online.target
After=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/hass-startup
ExecStop=/usr/local/bin/hass-shutdown
RemainAfterExit=true

The scripts in /usr/local/bin/hass-{startup,shutdown} contain the curl calls for startup and shutdown messages. Note that you have either hardcode the HASS_TOKEN in the scripts or on the service Environment parameters.

Another option is to use EnvironmentFile pointing to a file in a secure location with the key.

In NixOS

In NixOS, we can define the systemd service as shown:

{config, pkgs, ...}:

{
  systemd.services = {
    hass-online = {
      enable = true;
      unitConfig.Description = "Notify Home Assistant of boot completion";
      wantedBy = [ "multi-user.target" ];
      wants = [ "network-online.target" ];
      after = [ "network-online.target" ];

      script = ''
        ${pkgs.curl}/bin/curl -X POST \
            -H "Authorization: Bearer $HASS_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{"hostname" : "${config.networking.hostName}"}' \
            $HASS_SERVER/api/events/nixos.online
      '';

      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;

        # sops-nix secrets:
        EnvironmentFile = config.sops.secrets.hass_env.path;
        SupplementaryGroups = [ config.users.groups.keys.name ];

        ExecStop = pkgs.writeShellScript "hass-offline" ''
          UPTIME="$(cut -d' ' -f1 /proc/uptime)"
          IDLETIME="$(cut -d' ' -f2 /proc/uptime)"
          NPROC="$(nproc --all)"
          ${pkgs.curl}/bin/curl -X POST \
              -H "Authorization: Bearer $HASS_TOKEN" \
              -H "Content-Type: application/json" \
              -d "{ \"hostname\": \"${config.networking.hostName}\", \"cores\": $NPROC, \"uptime\": $UPTIME, \"idletime\": $IDLETIME }" \
              $HASS_SERVER/api/events/nixos.shutdown
        '';
      };
    };
  };
}

The script can be modified to send other information and even take parameters from the NixOS config module (such as config.networking.hostname for the hostname). Here I also send the uptime and idle time of the computer when it shuts down, so I can track my computer usage trends with home-assistant.

Sops-nix

In the above snippet, I have included EnvironmentFile = config.sops.secrets.hass_env.path; This is the path of a file managed by sops-nix which contains the $HASS_TOKEN and $HASS_SERVER variables.

The hass.yaml file has this format when unencrypted:

hass_env: |
    HASS_TOKEN=<************************************>
    HASS_SERVER=http://10.0.0.123:8123

Which means that it can be used as EnvironmentFile in systemd services or in bash by sourcing it.

Sample home-assistant automation

A simple example of the use of the lock screen events is to turn on or of the lamp on my desk when I leave the desk (lock the computer):

alias: Screenlock controls desk_lamp
description: "Turn off desk lamp on lock and back on when I log back"
trigger:
  - platform: event
    event_type: nixos.lock
    event_data:
      hostname: kuro
condition:
  - condition: state
    entity_id: light.desk_lamp
    state: "on"
action:
  - service: light.turn_off
    data: {}
    target:
      entity_id: light.desk_lamp
  - wait_for_trigger:
      - platform: event
        event_type: nixos.unlock
        event_data:
          hostname: kuro
    continue_on_timeout: false
  - service: light.turn_on
    data: {}
    target:
      entity_id: light.desk_lamp
mode: single