Home Assistant is one of the most popular open-source home automation solutions, and also my personal preference for few years now. It’s open-source which allows my to debug stuff more easily since I’m able to look onto the source code itself when I find something is not working. Further more most of its features are free: you just download the software, install it, and you’re good to go. There is also decent documentation, and furthermore because it’s not widely used the community is mostly willing to help you out if they can. And off course it also helps that it’s running on the insanely popular Raspberry Pi, I must admit.
Add-ons, but why?
Add-ons is one of those other feature which is really nice about Home Assistant. It allows you to build new stuf into Home Assistant without having to touch the core software. There is currently a broad set of official and community driver add-ons that can easily be deployed from the Home Assistant user interface all with the click of a button. All together Home Assistant will probable cover most of your use cases, but their may be some corner cases where it may not fit your exact needs. Unfortunately I found myself in one of those corner cases where I had started automating my house with relay modules that I bought from a previous employer before I on-boarded Home Assistant. In those earlier days I had made the complete home automation software stack myself: tuned Raspberry Pi operating system, backend software (REST API that wraps the .Net libraries needed to work with those relay modules), and mobile Android app. It was fun while it lasted, but I found out quick enough that if I wanted to expand the possibilities of that system that I needed a foundation to build upon instead of doing everything myself. And that’s how I came to try out some open-source automation suites. Home Assistant was particularly interesting back then because it had an easy way of deploying itself using docker images, I found it easy to use plus it could also easily be interfaced with through MQTT. All I had to do was writing that MQTT interface code so that aside of the HTTP REST API that I already had, the relays where also announced over MQTT and could be communicated with. Huray!
But I found this was not enough. As most of you may have encountered too the Raspberry Pi’s sd-card gave up after some time and it took me too much time to get everything up and running again so I wanted to streamline some of that stuff. I noticed by then that the HA guys had come up with a pretty decent embedded linux distro, so I decided to give this a chance too since it will remove those steps of setting up and tweaking the OS myself. HA’s OS literally allows you to download an image from their website, deploy it to an sd-card and boot right into the HA user interface. But as a drawback I had to pick up modifying my own software again so that it installs within Home Assistant… as an Add-On!
Where to start
The best place to start writing your own add-ons is by going to Home Assistant developer’s documentation that’s focusing on brewing your own add-ons. Important to understand is that Home Assistant Add-Ons basically are Docker containers with a few environment variables and arguments predefined, plus some pre-wired bits here and there. So the basic concepts of Docker containers and their images apply here as well. First you need to build an add-on image similar to what a Docker image is. Once you have that you can either run it locally, or you distribute it online and have someone else run a container instance of your add-on. Vice versa: someone else can also deploy their own add-on images so that you can run them yourselves on your own local setup, hence what the officially supported HA Add-Ons basically are doing.
As the docs explain to you there are 2 ways of deploying your add-ons to your own Home Assistant setup:
- locally: means build and install on the Home Assistant’s machine
- through publishing: build on a developer/build machine, host online and from then take your Home Assistant’s machine and install it
Option ‘locally’ is the easiest one to start with as it involves the least amount of infrastructure to setup. You can try build it on your PC first, and copy the entire sources that need to be build to the target Home Assistant machine and build it from there (again). My guidance here is that you should always first try to build it on your development PC as in nearly all cases it will build way faster than what the Home Assistant machine can do. The HA team has setup a Dockerized build environment so that you can easily pull in those build dependencies and start using them without contaminating your host OS. Look for the HA builder source repo if you want to find out more. But first we’re going to need to setup some meta-data files and a proper directory layout.
Start by creating a new empty folder. In my case I’ve also created the build subfolder. This is not required, but in my case it contains the binaries and config files that I need to run my actual application. Also create the run.sh script, since this is the one that’s going to be executed by the Add-on once it is being started:
#!/usr/bin/with-contenv bashio
echo "Listing serial ports"
ls /dev/tty*
echo "Running..."
cd /app
export MONO_THREADS_PER_CPU=100
mono ShutterService.exe
Create a build.json file that defines the base layer from which your Dockerfile is going to start:
{
"build_from": {
"aarch64": "homeassistant/aarch64-base-debian:buster",
"amd64": "homeassistant/amd64-base-debian:buster"
},
"squash": false,
"args": {
}
}
Also create a config.json file that describes your add-on:
{
"name": "ATDevices to MQTT",
"version": "1.0.0",
"slug": "atdevices_service",
"image": "afterhourscoding/ha-atdevices-addon",
"description": "Service that exposes Alphatronics gen1 and gen2 devices to Home Assistant",
"arch": ["aarch64", "amd64"],
"startup": "application",
"boot": "auto",
"full_access": true,
"init": false,
"options": {
},
"schema": {
}
}
Note that nowadays Home Assistant is mostly referring to yaml files for config, but the json files are still reported and it isn’t particularly hard to swap from one format to the other.
Then there is also the Dockerfile:
ARG BUILD_FROM
# hadolint ignore=DL3006
FROM ${BUILD_FROM}
# insta mono
ENV MONO_VERSION 5.20.1.34
RUN apt-get update \
&& apt-get install -y --no-install-recommends gnupg dirmngr \
&& rm -rf /var/lib/apt/lists/* \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --batch --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF \
&& gpg --batch --export --armor 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF > /etc/apt/trusted.gpg.d/mono.gpg.asc \
&& gpgconf --kill all \
&& rm -rf "$GNUPGHOME" \
&& apt-key list | grep Xamarin \
&& apt-get purge -y --auto-remove gnupg dirmngr
RUN echo "deb http://download.mono-project.com/repo/debian stable-stretch/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list \
&& apt-get update \
&& apt-get install -y mono-runtime \
&& rm -rf /var/lib/apt/lists/* /tmp/*
RUN apt-get update \
&& apt-get install -y binutils curl mono-devel ca-certificates-mono fsharp mono-vbnc nuget referenceassemblies-pcl \
&& rm -rf /var/lib/apt/lists/* /tmp/*
ADD ./build /app
# Copy data for add-on
COPY run.sh /
RUN chmod a+x /run.sh
CMD [ "/run.sh" ]
At last you can also dress up your add-on by providing a README.md, a logo.png and icon.png.
And here is a tree-view of my folder containing all sources:
$ tree
.
├── build
│ └── binaries that make the actual application ...
├── build.json
├── config.json
├── Dockerfile
├── icon.png
├── logo.png
├── run.sh
├── buildAddon.sh
├── README.md
└── testAddon.sh
Running the build as quite an extended command that I don’t prefer to manually enter each time, hence I’ve also setup a script to perform those PC builds of my add-on:
#!/bin/bash
BUILDCONTAINER_DATA_PATH="/data"
PATHTOBUILD="$BUILDCONTAINER_DATA_PATH"
#ARCH=all
ARCH=amd64
PROJECTDIR=$(pwd)
echo "project directory is $PROJECTDIR"
echo "build container data path is $BUILDCONTAINER_DATA_PATH"
echo "build container target build path is $PATHTOBUILD"
CMD="docker run --rm -ti --name hassio-builder --privileged -v $PROJECTDIR:$BUILDCONTAINER_DATA_PATH -v /var/run/docker.sock:/var/run/docker.sock:ro homeassistant/amd64-builder:2022.11.0 --target $PATHTOBUILD --$ARCH --test --docker-hub local"
echo "$CMD"
$CMD
Running the build script may take a while… Afterwards I’ve also tried running that container we’ve just build using the testAddon.sh script:
#!/bin/bash
docker run --rm -it local/my-first-addon
Let’s see that output:
$ ./testAddon.sh
s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
Listing serial ports
/dev/tty
Running...
###########################################
[21:49:32,457] [INFO ] [SHUTTERSERVICE] [1] [Domotica] [Main] ###########################################
[21:49:32,466] [INFO ] [SHUTTERSERVICE] [1] [Domotica] [Main] Version: 1.2.5.0
...
Bingo! Okay now copy those files to the Home Assistant machine’s /addon
folder. Next steps is to perform the build again, but since we’re now doing this on the HA machine the add-on will be picked up by the user interface and you’ll be able to install if from there on. But first repeat the steps in the same manner as given on the HA docs:
- Open the Home Assistant frontend
- Go to “Configuration”
- Click on “Add-ons, backups & Supervisor”
- Click “add-on store” in the bottom right corner.
- On the top right overflow menu, click the “Check for updates” button
- You should now see a new section at the top of the store called “Local add-ons” that lists your add-on!
- Click on your add-on to go to the add-on details page.
- Install your add-on
Be sure to start the add-on and inspect the logs for anomalies.
Improved way of working
Now that we have the basics working it’s time to improve upon that. Because what I dislike about the previous approach is that it takes a very long time for the build to complete on a Raspberry Pi. In case I ever have to rollback it may take most of my time switching from one build to the another and vice versa. So I decided to cross-build the Add-on image and host it online so that it can by pulled in by my HA machine without ever having to build something. Know that cross-building is not a big issue as the HA builder can do that out of the box. Before we can start hosting things there are some modifications needed to our add-ons source code which allows HA to pick it up. Because what is going to chance is that we no longer have any files manually copied to the HA machine. The /addon
folder no longer needs to contain a copy of our add-on sources since it’s no longer performing the build itself. This should therefore also free up some disc space! Go ahead and remove those files, and don’t forget to hit the update add-ons button using the UI so that any reference to our local build add-on is removed. However once we have our add-on hosted somewhere HA is going to need to know where to pull these pre-build container images from, and it is this magic sauce that we’ll be cooking next.
Let me first briefly explain what we want to achieve here. Home Assistant relies on the concept of add-on repositories. An add-on repository basically is a collection of add-ons from which people can choice which one they want to install. Much alike the software repositories found in your favorite linux distro. Anyone is free to create and host their own repositories, but it is mandatory of you want to tell HA what add-ons you have and where it can download those pre-build images from.
We with restructuring a bit: create a new directory in the top of your project, name it to your addon and move all files that we previously had into that folder. Also create repository.json in the top of your project map:
{
"name": "Home Assistant Geoffrey's Add-ons",
"url": "https://afterhourscoding.wordpress.com",
"maintainer": "Afterhourscoding <afterhourscoding@gmail.com>"
}
This file is just that tells other about what’s the repo named like and who the maintainer is. Next we’re also going to need to list what add-ons are to be found in our repository. Therefor create the .addons.yml file:
---
channel: stable
addons:
atdevices:
repository: afterhourscoding/ha-atdevices-addon:latest
target: atdevices
image: afterhourscoding/ha-atdevices-addon
The image name refers to the one it can find docker hub, as if you would docker pull afterhourscoding/ha-atdevices-addon
. Don’t worry if the image is not hosted at this stage, we will do that later on. Finally here is a tree-view of all these changes:
$ tree
.
├── .addons.yml
├── atdevices
│ ├── build
│ │ └── binaries that make the actual application ...
│ ├── build.json
│ ├── config.json
│ ├── Dockerfile
│ ├── icon.png
│ ├── logo.png
│ ├── README.md
│ └── run.sh
├── buildAddon.sh // this is the script I've shown you above
├── repository.json
└── testAddon.sh
Next we’re going to put our add-on repository in public space and set up HA so that it can parse the add-ons index. HA deals with repositories as if it were git repo’s. So enter git init
in your command line and basically do all the stuff that you’d do with your other git projects including uploading to github. Afterwards in HA’s UI go to the add-on store.
In the overflow menu, select “Repositories” and enter the HTTPS URL to your github repo. In my case I had to choose for hosting it my source code privately which makes things a bit more complicated. I rather not but hey sometimes we have to do deal with closed source binaries that you may not redistribute yourselves. For those protected repo’s to work you need to add a Personal Access Token to your project in github and give this token ‘repo’ acces. The token can than be put in the URL so that HA is able to fetch the repo through the token ownership. Keep in mind that this is stored non-secure on your HA setup! Use the following format for private hosted repo’s:
https://USERNAME:PERSONALACCESSTOKEN@github.com/USERNAME/REPONAME
This was just the first step. Next step is hosting your add-on container image on Dockerhub. Go ahead and create a Dockerhub account. One thing you could do now is adjust the buildAddon.sh script so that it is no longer running in test mode. I’ve went for another option, one where I’ve setup a Github Action on my git repo so that I get server builds which automatically push my add-on images to Docherhub. Here is my GH Action:
name: "Publish"
on:
release:
types: [published]
workflow_dispatch:
jobs:
publish:
name: Publish build
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Publish build - Home Assistant builder
uses: home-assistant/builder@2022.11.0
with:
args: |
--aarch64 \
--target /data/atdevices
Note that you also need to setup these 2 secrets that make your Dockerhub login because the build user will have to login on your behalf. This GH action can be triggered manually through the GH webpage:
Launch and wait, the build can easily take 10 minutes. Once it has completed go back to the Dockerhub website, you should now see your add-on image added:
One last final thing we need to do is enter your Dockerhub credentials in Home Assistant. This is only required for privately hosted images. Go back to your HA add-store, click the “Registries” menu option and add your registry:
Finally click “Check for updates”. It should now find your add-on again:
That brings us to the end of this small article. We’ve looked at ways how you can make your own Home Assistant Add-On and even keep it hosted privately. The workflow where you can have a build server automatically push the container image so that you only have to use Home Assistant user interface to update your add-on makes the process a little less handcrafted and a tad more professional looking. I hope as always that you find something useful in this. Credits go to whoever has been working on Home Assistant and those people responding in the community forums. I hope you find this encouraging enough to go that extra mile, who nows maybe one day you can make some money out of it. PS: did you now there are nowadays companies such as Homate selling products that use Home Assistant in their base, what’s next?!