MH Electrical Engineer

Moving HomeAssistant to a single-board computer

Aug 30, 2026

My Home Assistant container has been running on my Intel-based server with the rest of my containers. But with power outages becoming more frequent, I thought I would move it to a low-power ARM computer and put it on my UPS with the router and modem.

Hardware

Software

DietPi on Le Potato

  1. Download Armbian and flash the SD card using your imaging tool.
  2. Attempt to follow the instructions for the DietPi conversion script.
  3. While this is going is a good time to assign a static lease in OpenWRT.
  4. When the script hangs on systemctl start serial-getty@ttyS0, reboot. Le Potato doesn't have a hardware serial on ttyS0 but instead has ttyAML0.
  5. Continue the installation. Don't forget to enable ethernet with dietpi-config or it will not connect to the internet at next boot
  6. Use dietpi-software to install docker, docker-compose, and mosquitto.
  7. Configure the mqtt password with mosquitto-passwd. It helps to remove /etc/mosquitto/passwd before creating a new one. Restart with systemctl restart mosquitto
  8. Use apt install to get small utilities like rsync, tmux, btop, and vim-tiny.

Home Assistant container

  1. Create a working directory with mkdir homeassistant && cd homeassistant
  2. Create docker compose file:

docker-compose.yml

services:
  homeassistant:
    image: "ghcr.io/home-assistant/home-assistant:stable"
    container_name: homeassistant
    restart: unless-stopped
    privileged: false
    volumes:
      - ./config:/config
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 8123:8123
11. Copy your homeassistant config with `rsync -a yourname@oldserver:homeassistant/config . --info=progress2` If the config directory was inside your docker image, you can get them out with `docker cp`. But you really should keep the config files outside the container. 12. Start the image with `docker compose up`. This will take a while, as the homeassistant image is large and takes a while to start up. The good thing is, copying over the config file should include all your integrations and HACS, if installed. 13. Now you need to change all your MQTT devices to point to the new IP address that you chose in openWRT. Also reconfigure the MQTT integration in HomeAssistant to point to this IP.

The UPS

Since we're connected to a UPS, why not try to monitor it too? Install the Network UPS Tools with apt install nut. Follow the setup instructions from the Debian wiki. Maybe read the Arch Linux wiki too. Here's what my config files ended up looking like:

/etc/nut/nut.conf

MODE=standalone

/etc/nut/ups.conf

[CyberPower]
    driver = usbhid-ups
    port = auto
    vendorid = 0764
    productid = 0501
    desc = "UPS shared with router & modem"

Then start the service with systemctl restart nut-server and check the result.

systemctl status nut-server

● nut-server.service - Network UPS Tools - power devices information server
     Loaded: loaded (/usr/lib/systemd/system/nut-server.service; enabled; preset: enabled)
     Active: active (running) since Sun 2026-08-30 18:14:54 PDT; 3s ago
 Invocation: 2e39f7e6253b40018b55fc2479f175ab
    Process: 52137 ExecStartPre=/usr/bin/systemd-tmpfiles --create /usr/lib/tmpfiles.d/nut-common-tmpfiles.conf (code=exited, status=0/SUCCESS)
   Main PID: 52139 (upsd)
      Tasks: 1 (limit: 2098)
     Memory: 712K (peak: 1.7M)
        CPU: 109ms
     CGroup: /system.slice/nut-server.service
             └─52139 /lib/nut/upsd -F

Aug 30 18:14:54 lepotato upsd[52139]: Connected to UPS [CyberPower]: usbhid-ups-CyberPower Aug 30 18:14:54 lepotato upsd[52139]: Found 1 UPS defined in ups.conf Aug 30 18:14:54 lepotato nut-server[52139]: Running as foreground process, not saving a PID file Aug 30 18:14:54 lepotato nut-server[52139]: upsnotify: notify about state 2 with libsystemd: was requested, but not running as a service unit now, will not spam more about it Aug 30 18:14:54 lepotato nut-server[52139]: upsnotify: failed to notify about state 2: no notification tech defined, will not spam more about it Aug 30 18:14:54 lepotato nut-server[52139]: upsnotify: logged the systemd watchdog situation once, will not spam more about it Aug 30 18:14:54 lepotato upsd[52139]: Running as foreground process, not saving a PID file Aug 30 18:14:54 lepotato upsd[52139]: upsnotify: notify about state 2 with libsystemd: was requested, but not running as a service unit now, will not spam more about it Aug 30 18:14:54 lepotato upsd[52139]: upsnotify: failed to notify about state 2: no notification tech defined, will not spam more about it Aug 30 18:14:54 lepotato upsd[52139]: upsnotify: logged the systemd watchdog situation once, will not spam more about it

Now let's see what data is availble?

upsc cyberpower

battery.charge: 100
battery.charge.low: 10
battery.charge.warning: 20
battery.mfr.date: CPS
battery.runtime: 40320
battery.runtime.low: 300
battery.type: PbAcid
battery.voltage: 24.0
battery.voltage.nominal: 24
device.mfr: CPS
device.model: LX1500GU
device.serial: QAKGU2002418
device.type: ups
driver.debug: 0
driver.flag.allow_killpower: 0
driver.name: usbhid-ups
driver.parameter.pollfreq: 30
driver.parameter.pollinterval: 2
driver.parameter.port: auto
driver.parameter.productid: 0501
driver.parameter.synchronous: auto
driver.parameter.vendorid: 0764
driver.state: quiet
driver.version: 2.8.1
driver.version.data: CyberPower HID 0.8
driver.version.internal: 0.52
driver.version.usb: libusb-1.0.28 (API: 0x100010a)
input.voltage: 120.0
input.voltage.nominal: 120
output.voltage: 120.0
ups.beeper.status: enabled
ups.delay.shutdown: 20
ups.delay.start: 30
ups.load: 0
ups.mfr: CPS
ups.model: LX1500GU
ups.productid: 0501
ups.realpower.nominal: 900
ups.serial: QAKGU2002418
ups.status: OL
ups.test.result: No test initiated
ups.timer.shutdown: -60
ups.timer.start: -60
ups.vendorid: 0764

Publishing data

We'll need some extra tools for this. Run apt install mosquitto-clients yq-go

Next we're going to make an MQTT service and publish this data to Home Assistant. Here's the data that seems useful to have in a power outage:

So we'll set up a discovery.json file to communicate these:

discovery.json

{
  "qos": 2,
  "dev": {
    "mdl": "LX1500GU",
    "sw": "CyberPower HID 0.8",
    "ids": "QAKGU2002418",
    "name": "UPS",
    "mf": "CyberPower"
  },
  "o": {
    "name": "Marshal's UPS Script",
    "sw": "0.0.1"
  },
  "cmps": {
    "QAKGU2002418_charge": {
      "unit_of_measurement": "%",
      "value_template": "{{value_json['battery.charge']}}",
      "name": "Battery charge",
      "expire_after": 600,
      "device_class": "battery",
      "p": "sensor",
      "unique_id": "QAKGU2002418_charge",
      "state_topic": "homeassistant/ups/status"
    },
    "QAKGU2002418_bvolts": {
      "unit_of_measurement": "V",
      "value_template": "{{value_json['battery.voltage']}}",
      "name": "Battery Volts",
      "expire_after": 600,
      "device_class": "voltage",
      "p": "sensor",
      "unique_id": "QAKGU2002418_bvolts",
      "state_topic": "homeassistant/ups/status"
    },
    "QAKGU2002418_ivolts": {
      "unit_of_measurement": "V",
      "value_template": "{{value_json['input.voltage']}}",
      "name": "Mains Voltage",
      "expire_after": 600,
      "device_class": "voltage",
      "p": "sensor",
      "unique_id": "QAKGU2002418_ivolts",
      "state_topic": "homeassistant/ups/status"
    },
    "QAKGU2002418_load": {
      "unit_of_measurement": "W",
      "value_template": "{{value_json['ups.load']}}",
      "name": "UPS Load",
      "expire_after": 600,
      "device_class": "power",
      "p": "sensor",
      "unique_id": "QAKGU2002418_load",
      "state_topic": "homeassistant/ups/status"
    },
    "QAKGU2002418_runtime": {
      "unit_of_measurement": "min",
      "value_template": "{{value_json['battery.runtime']}}",
      "name": "UPS runtime",
      "expire_after": 600,
      "device_class": "duration",
      "p": "sensor",
      "unique_id": "QAKGU2002418_runtime",
      "state_topic": "homeassistant/ups/status"
    }
  }
}

mqtt.env

MQTT_USER="your mqtt user"
MQTT_PASS = "your mqtt password"

Next we'll pipe the UPS status into the MQTT message. I want this to run periodically, so we'll put it in a file.

ups_status.sh

#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
export $(cat $(SCRIPT_DIR)/mqtt.env | xargs)
upsc cyberpower | \
grep -E 'charge|voltage|runtime|load' | \
yq -o=json | \
mosquitto_pub -h localhost -u "$MQTT_USER" -P "$MQTT_PASS" \
-t "homeassistant/ups/status" -s

Now I have a new MQTT topic with the message

homeassistant/ups/status

{
  "battery.charge": 100,
  "battery.charge.low": 10,
  "battery.charge.warning": 20,
  "battery.runtime": 41610,
  "battery.runtime.low": 300,
  "battery.voltage": 24,
  "battery.voltage.nominal": 24,
  "input.voltage": 121,
  "input.voltage.nominal": 120,
  "output.voltage": 121,
  "ups.load": 0
}

And now in Home Assistant, we have a pretty picture!

UPS status in Home Assistant

Making it automatic

Although we have crontab, I find it difficult to debug because it only emails the error rather than printing them in the syslog. Instead, we will be doing this with Systemd Timers.

  1. Create a service file

/etc/systemd/system/ups_status.service

[Unit]
Description=Publishes UPS status
After=network-online.target
Requires=network-online.target
Requires=nut-server.service

[Service] Type=oneshot ExecStart=/home/marshal/homeassistant/ups_status.sh

  1. Create a timer file

/etc/systemd/system/ups_status.timer

[Unit]
Description=Publish UPS status

[Timer] OnBootSec=5min OnUnitActiveSec=1min

[Install] WantedBy=timers.target

  1. Enable the timer with systemctl enable ups_status.timer