<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Mqtt on mhemeryck</title><link>https://mhemeryck.xyz/tags/mqtt/</link><description>Recent content in Mqtt on mhemeryck</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 09 Aug 2021 00:00:00 +0000</lastBuildDate><atom:link href="https://mhemeryck.xyz/tags/mqtt/index.xml" rel="self" type="application/rss+xml"/><item><title>evok2mqtt ansible</title><link>https://mhemeryck.xyz/posts/2021-08-09-evok2mqtt_ansible/</link><pubDate>Mon, 09 Aug 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-08-09-evok2mqtt_ansible/</guid><description>&lt;p&gt;As part of my &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation series&lt;/a&gt;, I did write about a small interfacing library that I did write, called &lt;a href="https://github.com/mhemeryck/evok2mqtt"&gt;evok2mqtt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The purpose of this service is to translate between two different protocols: websockets, as exposed by &lt;a href="https://github.com/UniPiTechnology/evok"&gt;evok&lt;/a&gt; and &lt;a href="https://mqtt.org/"&gt;mqtt&lt;/a&gt;, which is the primary protocol I did use in communicating with &lt;a href="https://www.home-assistant.io/integrations/mqtt/"&gt;home assistant&lt;/a&gt;.
If you&amp;rsquo;d like to get more details about the setup itself, check the blog post series first.&lt;/p&gt;
&lt;p&gt;The focus of this post is the &lt;strong&gt;automated provisioning of a unipi unit using &lt;a href="https://www.ansible.com/"&gt;ansible&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;</description><content:encoded>&lt;p&gt;As part of my &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation series&lt;/a&gt;, I did write about a small interfacing library that I did write, called &lt;a href="https://github.com/mhemeryck/evok2mqtt"&gt;evok2mqtt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The purpose of this service is to translate between two different protocols: websockets, as exposed by &lt;a href="https://github.com/UniPiTechnology/evok"&gt;evok&lt;/a&gt; and &lt;a href="https://mqtt.org/"&gt;mqtt&lt;/a&gt;, which is the primary protocol I did use in communicating with &lt;a href="https://www.home-assistant.io/integrations/mqtt/"&gt;home assistant&lt;/a&gt;.
If you&amp;rsquo;d like to get more details about the setup itself, check the blog post series first.&lt;/p&gt;
&lt;p&gt;The focus of this post is the &lt;strong&gt;automated provisioning of a unipi unit using &lt;a href="https://www.ansible.com/"&gt;ansible&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="rationale"&gt;Rationale&lt;/h2&gt;
&lt;p&gt;Earlier, I did discuss all of the software running on each of my &lt;a href="https://www.unipi.technology/"&gt;unipi&lt;/a&gt; units, where each part is responsible for a layer in the overall process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the linux-based OS: for capturing and sending I/O events&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evok&lt;/code&gt;: for exposing this I/O information in various formats and protocols (of which I did use websockets)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evok2mqtt&lt;/code&gt;: for translating between websockets and MQTT&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Arguably, it would be better to have fewer of these layers, but the current situation is one of &lt;strong&gt;many different moving parts&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Next to that, I do have &lt;strong&gt;multiple of these unipi nodes&lt;/strong&gt;, each interfacing different types of I/O, yet fundamentally they share a common setup.&lt;/p&gt;
&lt;p&gt;Occasionally, there is a need to &lt;strong&gt;update or reprovision&lt;/strong&gt; the unipi nodes, e.g. in the case of regular software or in the unlucky event that and SD-card crashed.&lt;/p&gt;
&lt;p&gt;Considering all of these factors, having a way to automatically provision the unipi units in a predefined state is very helpful to have.&lt;/p&gt;
&lt;h2 id="automated-provisioning"&gt;Automated provisioning&lt;/h2&gt;
&lt;p&gt;Automated provisioning refers to tooling that can handle the tedious tasks related to setting up a client or server automatically.
Many different kinds of tooling can be used, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scripting like &lt;a href="https://www.gnu.org/software/bash/"&gt;bash&lt;/a&gt;:&lt;/li&gt;
&lt;li&gt;declarative-style recipes like &lt;a href="https://www.ansible.com/"&gt;ansible&lt;/a&gt; and &lt;a href="https://puppet.com/"&gt;puppet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;immutable builds like &lt;a href="https://www.packer.io/"&gt;packer&lt;/a&gt; or &lt;a href="https://www.docker.com/"&gt;docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;infrastructure-as-code like &lt;a href="https://www.terraform.io/"&gt;terraform&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Scripting&lt;/strong&gt; is one of the oldest and most widespread approaches to automation.
In scripting, you define a list of commands that each run in sequence after each other, generally from the top of the script to the bottom.
Scripting is &lt;em&gt;procedural&lt;/em&gt;, meaning you define &lt;em&gt;how&lt;/em&gt; a specific action is to be carried out, like a recipe.
The advantage of scripting is that it is simple and ubiquitous.
On the other side of the equation are all issues related to state manipulation.
As a script runs through its sequence of steps, it changes the state to end up in a desired target state.
In theory, you can write your scripts in such a way that they can adapt for any kind of initial or in-between state.
Also, you could attempt to write some parts of your scripts in an &lt;em&gt;idempotent&lt;/em&gt; way, meaning that they perform the same action regardless of their input.
At larger scale however, scripting quickly becomes unwieldy of this state manipulation it needs to take into account and idempotency is simply not always possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Declarative&lt;/strong&gt; tools take a different approach, not by defining &lt;em&gt;how&lt;/em&gt; the provisioning should take place, but rather &lt;em&gt;what&lt;/em&gt; the end-result needs to be.
Particularly in the case of provisioning, this makes for an interesting programming paradigm since ideally after running the provisioning tool, it will always result in the same end state, regardless of any initial state the node was in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Immutable build&lt;/strong&gt; tools also operate in this fashion, the difference being that the provisioning process itself consists of picking a pre-built image (virtual machine image or container image) and deploying that entirely.
Doing updates in this fashion just means killing the entire image and replacing it with another one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Infrastructure as code&lt;/strong&gt; augments this approach by extending it to any kind of resource.
Cloud providers make all of their virtualised infrastructure components available via APIs.
Infrastructure as code provides a common code-based framework to declaratively define your desired cloud architecture.&lt;/p&gt;
&lt;p&gt;All of these have their place, but for now, I did focus on using &lt;a href="https://www.ansible.com/"&gt;ansible&lt;/a&gt; as a declarative provisioning tool:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scripting is too much of a pain, considering the state manipulation issues mentioned before&lt;/li&gt;
&lt;li&gt;immutable build tools seem really interesting, but might not fit my use case where I would manually need to swap out built images&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;infrastructure as code: this does not apply here, since I want to run the tooling on the unipi unit itself&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;I recently did learn about ansible and also a good use case to apply it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some basic &lt;em&gt;ansible-lingo&lt;/em&gt; before we dive into the details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ansible&lt;/code&gt; is the tool you run on your local machine to perform actions on a remote system&lt;/li&gt;
&lt;li&gt;an action on a remote machine is a &lt;em&gt;task&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;a set of tasks can be combined into a &lt;em&gt;role&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;a set of roles can be further combined into a &lt;em&gt;playbook&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&amp;hellip; further higher levels of grouping also still exist (collections?), but I didn&amp;rsquo;t give that much extra thought&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ansible-playbook&lt;/code&gt; is the tool to orchestrate the execution of a playbook&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For my purposes, I did write a &lt;em&gt;playbook&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="initial-setup"&gt;Initial setup&lt;/h2&gt;
&lt;p&gt;Ansible has some basic initial conditions for it to be run on the target host:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the target host needs to be reachable via SSH (ideally key-based)&lt;/li&gt;
&lt;li&gt;the target host should run some version of python (as ansible itself is based on python)&lt;/li&gt;
&lt;li&gt;depending on the permissions you need to execute, the connecting user needs to have root access&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fortunately, unipi does provides such an image that already satisfies these constraints (most modern linux-based OS images would do, actually): the &lt;a href="https://kb.unipi.technology/en:files:software:os-images:00-start#neuron_opensource_os"&gt;Neuron OpenSource OS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Practical next steps include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;flash the SD card for the unipi with this image&lt;/li&gt;
&lt;li&gt;ensure an internet connection, preferably wired, although a &lt;a href="https://www.raspberrypi.com/documentation/computers/configuration.html#connect-to-a-wireless-network"&gt;headless raspberry pi wifi install&lt;/a&gt; would also work&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From here on, you can start running the playbook!&lt;/p&gt;
&lt;h2 id="running-evok2mqtt-ansible"&gt;Running &lt;code&gt;evok2mqtt-ansible&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The playbook can be found on github &lt;a href="https://github.com/mhemeryck/evok2mqtt-ansible"&gt;evok2mqtt-ansible&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From the README file:&lt;/p&gt;
&lt;p&gt;The first step is to clone the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/mhemeryck/evok2mqtt-ansible.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we need some extra requirements which can be installed using &lt;code&gt;ansible-galaxy&lt;/code&gt; (a tool to pull in other people&amp;rsquo;s playbooks).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible-galaxy install -r requirements.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adapt the &lt;code&gt;hosts.yaml&lt;/code&gt; file for your own purposes.&lt;/p&gt;
&lt;p&gt;Run the playbook with&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ansible-playbook site.yaml -i hosts.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="a-closer-look"&gt;A closer look&lt;/h2&gt;
&lt;p&gt;Taking a step back; what does this playbook actually do?
Let&amp;rsquo;s check the folder structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
├── hosts.yaml
├── LICENSE.txt
├── README.md
├── requirements.yaml
├── roles
│   ├── account
│   │   └── tasks
│   │   └── main.yaml
│   ├── evok2mqtt
│   │   ├── handlers
│   │   │   └── main.yaml
│   │   ├── tasks
│   │   │   └── main.yaml
│   │   └── templates
│   │   └── evok2mqtt.service.j2
│   ├── hostname
│   │   ├── tasks
│   │   │   └── main.yaml
│   │   └── vars
│   │   └── hostnames.yaml
│   ├── security
│   │   ├── handlers
│   │   │   └── main.yaml
│   │   └── tasks
│   │   └── main.yaml
│   └── wifi
│   ├── handlers
│   │   └── main.yaml
│   ├── tasks
│   │   └── main.yaml
│   └── templates
│   └── wpa_supplicant.conf.j2
└── site.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The playbook consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;site.yaml&lt;/code&gt; file which is the main entry point which invokes the different roles&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;hosts.yaml&lt;/code&gt; file which lists the groups of target hosts to run the commands against (you would definitely need to adapt this to your needs)&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;requirements.yaml&lt;/code&gt; file which defines external roles and plugins to be used&lt;/li&gt;
&lt;li&gt;5 different folders, each related to a specific role; a set of tasks that are available for execution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Discussing each of the roles themselves might be a bit too much (you can just was well read the source for that), but I want to discuss the main &lt;code&gt;site.yaml&lt;/code&gt; file which brings all of the roles together:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;hosts&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;testpi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;vars&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible_python_interpreter&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/usr/bin/python3&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Force py3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible_ssh_password&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unipi.technology&lt;/span&gt; &lt;span style="color:#75715e"&gt;# not really a secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible_ssh_user&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unipi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;mqtt_host&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;shuttle.lan&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;username&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mhemeryck&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;wifi_ssd&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;luctor_IoT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;wifi_passphrase&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;{{ lookup(&amp;#39;community.general.passwordstore&amp;#39;, &amp;#39;home/luctor&amp;#39;) }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;roles&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;account&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;hostname&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;wifi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;evok2mqtt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;hosts&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;testpi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;vars&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ansible_python_interpreter&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/usr/bin/python3&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Force py3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;username&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mhemeryck&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;roles&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;security&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The main file defines &lt;strong&gt;two sets of roles&lt;/strong&gt;, referring to the different roles that are made available.
The first set is the biggest one, the second one can only run after the first has completed.
The reason for this split is that when the initial image is made available, it does include a default &lt;code&gt;unipi&lt;/code&gt; user with a fixed password.
The first set of roles will run as this user (as this is the only user available at that time).
Afterwards, we can run a second security-related role as a newly available hardened user and then remove that &lt;code&gt;unipi&lt;/code&gt; user &amp;ndash; you obviously can&amp;rsquo;t remove the &lt;code&gt;unipi&lt;/code&gt; user while you are executing tasks with it.
You can see that the first set of roles does include the &lt;code&gt;ansible_ssh_user&lt;/code&gt; and &lt;code&gt;ansible_ssh_password&lt;/code&gt;: these refer to those standard credentials.&lt;/p&gt;
&lt;p&gt;In terms of &lt;strong&gt;credentials&lt;/strong&gt;, I did include those ssh user / password as variables in plain text, as this publicly available knowledge.
For the &lt;code&gt;wifi_passphrase&lt;/code&gt; however, I did use my password manager &lt;a href="https://www.passwordstore.org/"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; and the related ansible &lt;a href="https://docs.ansible.com/ansible/latest/collections/community/general/passwordstore_lookup.html"&gt;&lt;code&gt;pass&lt;/code&gt; plugin&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;A quick look at the &lt;strong&gt;different roles&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;account&lt;/code&gt;: this provisions my own user with proper root access and copies in my public SSH keys from github&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hostname&lt;/code&gt;: this role changes the host name to a sensible one. The mapping for the host name is determined from a variables file&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wifi&lt;/code&gt;: this sets up &lt;code&gt;wpa_supplicant&lt;/code&gt; with proper WIFI credentials for my home network.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evok2mqtt&lt;/code&gt;: the main job; it will do a full system upgrade, install &lt;code&gt;evok&lt;/code&gt;, install &lt;code&gt;evok2mqt&lt;/code&gt;, create a system unit file and make sure it runs as a daemon&lt;/li&gt;
&lt;li&gt;&lt;code&gt;security&lt;/code&gt;: some cleanup tasks, like disabling password-based SSH logins (key-based only) and removing the default &lt;code&gt;unipi&lt;/code&gt; user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After running these tasks, the unipi unit should in theory be in my ideal end state!&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Given the central place my unipi units play in my home automations setup, &lt;strong&gt;having any failures on them is, however rare, not fun&lt;/strong&gt;.
With this in mind and also a general interest in learning infrastructure automation, I did set out to automate those tasks with ansible.&lt;/p&gt;
&lt;p&gt;The current &lt;strong&gt;downside&lt;/strong&gt; of this approach is that the full system upgrade will mean that the unipi is occupied for a large portion of time.
Consequently, I haven&amp;rsquo;t yet set out to use it on my live environment.
However, any breaking failures on my unipi units could now be quickly mitigated by flashing an SD-card and running this playbook against, which is a huge improvement over the previous situation.&lt;/p&gt;
&lt;p&gt;Alternative &lt;strong&gt;future solutions&lt;/strong&gt; could consist of changing the software required to run on the unipi units (would simplify the install process) and / or fully immutable (packer) builds, which completely prepare the image upfront.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;in the cloud server world, this is of course an entirely different situation.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;a platform that could mimic infrastructure-as-code for local development would be really cool, though!&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;I did not include my own file, since this could contain sensitive information &amp;hellip;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>Service</title><link>https://mhemeryck.xyz/posts/2021-08-03-home_automation_service/</link><pubDate>Tue, 03 Aug 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-08-03-home_automation_service/</guid><description>&lt;p&gt;Up to this point, I did already lay out all of the tidbits concerning the electricity, the I/O hardware units, even some custom software to provide an event-based API to address all of the various components in the overall system.
The final fundamental piece to bring everything together is what I call the &lt;em&gt;service&lt;/em&gt; layer, and specifically &lt;a href="https://www.home-assistant.io/"&gt;home assistant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post is a part of a larger series of posts on my home automation setup.
See the &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation overview post&lt;/a&gt;, to learn about the rationale and a description of the other posts!&lt;/p&gt;</description><content:encoded>&lt;p&gt;Up to this point, I did already lay out all of the tidbits concerning the electricity, the I/O hardware units, even some custom software to provide an event-based API to address all of the various components in the overall system.
The final fundamental piece to bring everything together is what I call the &lt;em&gt;service&lt;/em&gt; layer, and specifically &lt;a href="https://www.home-assistant.io/"&gt;home assistant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post is a part of a larger series of posts on my home automation setup.
See the &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation overview post&lt;/a&gt;, to learn about the rationale and a description of the other posts!&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;p&gt;To recap, have a look at this drawing from the earlier &lt;a href="https://mhemeryck.xyz/posts/2021-06-22-home_automation_architecture/"&gt;home automation architecture post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-22/architecture.png" alt="architecture"&gt;&lt;/p&gt;
&lt;p&gt;In summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a central MQTT broker: this component takes in &lt;em&gt;events&lt;/em&gt; on a &lt;em&gt;topic&lt;/em&gt; from the various &lt;em&gt;publishers&lt;/em&gt; and forwards them to any of the &lt;em&gt;subscribers&lt;/em&gt; for that topic&lt;/li&gt;
&lt;li&gt;all the unipi units: can take in any MQTT event to handle as &lt;em&gt;commands&lt;/em&gt; and pushes out events as &lt;em&gt;state&lt;/em&gt; updates.&lt;/li&gt;
&lt;li&gt;home assistant: has logic to trigger specific events on other events incoming&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that for this publish / subscribe system, all of the clients can (and will) function as both publisher and subscriber.&lt;/p&gt;
&lt;h2 id="home-assistant"&gt;home assistant&lt;/h2&gt;
&lt;p&gt;I already did mention home assistant, but what is it really?&lt;/p&gt;
&lt;p&gt;Home assistant is an open source software platform that was created with the vision of being able to integrate all sorts of IoT solutions.&lt;/p&gt;
&lt;p&gt;It is bundled with a huge amount of &lt;em&gt;integrations&lt;/em&gt; for various different vendors; check out the &lt;a href="https://www.home-assistant.io/integrations/"&gt;home assistant integrations&lt;/a&gt; page for an overview.
The way the platform is structured is via a number of &lt;a href="https://developers.home-assistant.io/docs/core/entity/"&gt;entities&lt;/a&gt;, e.g. a generic switch, light, etc &amp;hellip;
Each integration can then implement such entities.
The home assistant community would typically encourage new contributors to put the interfacing logic (e.g. an API client) in its own, open source library, to be called from the custom integration.
The added value of this approach is twofold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the community rapidly gets more of these open source libraries&lt;/li&gt;
&lt;li&gt;the home assistant installation only needs to pull in the source libraries it needs, instead of having of a big blob of code.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The advantage of running your own platform is that you are more in control of your own hardware, less reliant on cloud solutions.&lt;/p&gt;
&lt;p&gt;Though not required, I did find it interesting to learn about more about the way home assistant itself is structured, check the &lt;a href="https://developers.home-assistant.io/docs/architecture_index"&gt;home assistant architecture dev docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The integration I have been mostly using is &lt;a href="https://www.home-assistant.io/integrations/mqtt/"&gt;home assistant MQTT integration&lt;/a&gt;.
At this point, this means that all of the I/O that is provided by the unipi units via an MQTT interface can now be readily represented as entities directly in home assistant!&lt;/p&gt;
&lt;h2 id="scenario"&gt;Scenario&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s make it more tangible by presenting the scenario from the &lt;a href="https://mhemeryck.xyz/posts/2021-06-22-home_automation_architecture/"&gt;home automation architecture post&lt;/a&gt; again, where a push button triggers a light.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-22/flow.png" alt="flow"&gt;&lt;/p&gt;
&lt;p&gt;In terms of the home assistant MQTT integration, the notion of &lt;em&gt;command&lt;/em&gt; and &lt;em&gt;state&lt;/em&gt; topics are quite relevant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;command topic: this is a specific MQTT topic, e.g. &lt;code&gt;/{device}/{io}/set&lt;/code&gt; which accepts and incoming command to update the I/O. This scenario is typically coming from home assistant to the IO module.&lt;/li&gt;
&lt;li&gt;state topic: this topic is specific for reporting state updates back to any listeners, e.g. &lt;code&gt;/{device}/{io}/state&lt;/code&gt;. This is for the opposite scenario where the IO module itself updates its state back again to home assistant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this pair of topics for each entity, a typical flow combining command and state topics means:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;home assistant requesting a state update on the command topic of the IO module&lt;/li&gt;
&lt;li&gt;the IO module updates its value (e.g. toggles a relay for a light)&lt;/li&gt;
&lt;li&gt;the IO module sends out an update on the corresponding state topic&lt;/li&gt;
&lt;li&gt;home assistant then updates its matching entity according to the state topic&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In relation to the flow diagram above:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;on the push of the button, an MQTT event is pushed out to the &lt;em&gt;state&lt;/em&gt; topic&lt;/li&gt;
&lt;li&gt;the MQTT broker pushes the state update to home assistant&lt;/li&gt;
&lt;li&gt;home assistant updates the matching &lt;a href="https://www.home-assistant.io/integrations/switch.mqtt/"&gt;home assistant MQTT switch&lt;/a&gt; entity&lt;/li&gt;
&lt;li&gt;home assistant triggers an &lt;em&gt;automation&lt;/em&gt; which connects a state update for the push button entity to a matching light entity&lt;/li&gt;
&lt;li&gt;to update the light entity, an event is published on that &lt;a href="https://www.home-assistant.io/integrations/light.mqtt/"&gt;home assistant MQTT light&lt;/a&gt; entity&lt;/li&gt;
&lt;li&gt;the MQTT broker pushes the command to the subscribed I/O module&lt;/li&gt;
&lt;li&gt;the I/O module takes in the command to update its state and toggles the light accordingly&lt;/li&gt;
&lt;li&gt;after that, the I/O module triggers the relay, it pushes back an update on its state topic&lt;/li&gt;
&lt;li&gt;the MQTT broker forwards the state update again to home assistant&lt;/li&gt;
&lt;li&gt;home assistant sees the state update for that light entity and updates its internal state&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="containers-and-services"&gt;Containers and services&lt;/h2&gt;
&lt;p&gt;Home assistant has multiple ways of installing and running the platform, see the &lt;a href="https://www.home-assistant.io/installation/"&gt;home assistant installation&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;First of all, you should be aware that the overall platform consists of multiple layers and the meaning of each of those has changed over time.
I would refer to the following diagram from the &lt;a href="https://developers.home-assistant.io/docs/architecture_index"&gt;home assistant architecture dev docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://developers.home-assistant.io/img/en/architecture/full.svg" alt="home assistant layers img"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;core: at the center, there&amp;rsquo;s the main home assistant software.&lt;/li&gt;
&lt;li&gt;supervisor: this component manages the core software, but it also has the capability to run other services besides home assistant, yet integrating them in a single UI&lt;/li&gt;
&lt;li&gt;operating system: the way I understand this, this is a minimal linux-based OS just to be able to run the supervisor for you. The idea is that at this level, you can just download a disk image, flash an SD-card and fire it up in a raspberry pi.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The core itself is actually a &lt;em&gt;containerized&lt;/em&gt; version of the main home assistant software, which in turn is a python application.
Check out the &lt;a href="https://hub.docker.com/r/homeassistant/home-assistant"&gt;home assistant core docker container&lt;/a&gt; on docker hub.
The rationale behind containerization is that you specify an image that contains an isolated environment for your application to run in.
Containers are different from &lt;em&gt;virtual machines&lt;/em&gt; in that multiple containers would share the same underlying OS kernel, which is better in terms of performance.&lt;/p&gt;
&lt;p&gt;Containerization has become dominant in current-day software development and infrastructure management; I use it on a daily basis.
A lot can be said on the topic of containerization and IT infrastructure, but I would like to keep this out of the discussion here.
If you would be considering gaining more knowledge on this, before anything, understand that containers are there to support a particular model of software development, i.e. &lt;em&gt;service-based&lt;/em&gt; development.
This typically means &lt;em&gt;stateless&lt;/em&gt;, &lt;em&gt;web-based&lt;/em&gt;, &lt;em&gt;services&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Apart from the container itself, you typically need something like a &lt;em&gt;supervisor&lt;/em&gt; or &lt;em&gt;container orchestrator&lt;/em&gt; to run the containers.
The orchestrator is responsible for e.g. starting the containers, making sure the correct amount of containers are running at the same time (redundancy, scaling), moving containers between hosts (in a multi-node setup), resource allocation, external networking, &amp;hellip;&lt;/p&gt;
&lt;p&gt;As mentioned, home assistant has its own orchestrator; the supervisor.
An orchestrator I often use for quick-and-dirty local development is &lt;a href="https://docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt;.
The current de-facto industry standard is the google-backed &lt;a href="https://kubernetes.io/"&gt;kubernetes&lt;/a&gt;, abbreviated to k8s.
The lightweight version of this is &lt;a href="https://k3s.io/"&gt;k3s&lt;/a&gt;.
This probably not the end of the list, others like &lt;a href="https://www.nomadproject.io/"&gt;hashicorp nomad&lt;/a&gt; also exist.&lt;/p&gt;
&lt;p&gt;All of these have their own merits and disadvantages:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;orchestrator&lt;/th&gt;
&lt;th&gt;pros&lt;/th&gt;
&lt;th&gt;cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;home assistant supervisor&lt;/td&gt;
&lt;td&gt;built into the eco-system of home assistant&lt;/td&gt;
&lt;td&gt;container orchestration is &lt;em&gt;hard&lt;/em&gt; to do right, why roll your own?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;allows extra functionality in the same UI&lt;/td&gt;
&lt;td&gt;to me, this indicates a bad design, where a supervisor is responsible for more than what it&amp;rsquo;s intended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;supposedly more suited for embedded platforms (raspi 3)&lt;/td&gt;
&lt;td&gt;low-memory footprint k8s alternatives exist, like the k3s mentioned before.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;docker-compose&lt;/td&gt;
&lt;td&gt;simple syntax&lt;/td&gt;
&lt;td&gt;only supports a subset of features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;easy to get up an running&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k8s&lt;/td&gt;
&lt;td&gt;the de-facto standard&lt;/td&gt;
&lt;td&gt;standard for web-based services and IT infrastructure management, might be overkill for IoT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;a lot of features, e.g. zero-downtime deployments!&lt;/td&gt;
&lt;td&gt;lots of concepts to grasp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Over the course of time, I think I have actually ran home assistant in all of these configurations (even including directly from source).
Like everything in IT, there is no real &lt;em&gt;right&lt;/em&gt; or &lt;em&gt;wrong&lt;/em&gt; solution, it depends on your specific situation.
My current approach is based on k3s &amp;ndash; but this is better left as the topic of a future post.&lt;/p&gt;
&lt;h2 id="sample-setup"&gt;Sample setup&lt;/h2&gt;
&lt;p&gt;All of the theoretical babble about containers and orchestration is just to come to this point where I can actually show something to run.
For this sample setup, I will build part of the service layer using only &lt;code&gt;docker-compose&lt;/code&gt; and a set of related home assistant configuration files.&lt;/p&gt;
&lt;p&gt;The final folder structure will look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;├── automations.yaml
├── blueprints
│   ├── automation
│   │   └── homeassistant
│   │   ├── motion_light.yaml
│   │   └── notify_leaving_zone.yaml
│   └── script
│   └── homeassistant
│   └── confirmable_notification.yaml
├── configuration.yaml
├── deps
├── docker-compose.yaml
├── groups.yaml
├── home-assistant.log
├── scenes.yaml
├── scripts.yaml
├── secrets.yaml
├── tts
└── ui-lovelace.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For those interested, have a look at &lt;a href="https://mhemeryck.xyz/2021-08-03/hass.zip"&gt;&lt;code&gt;hass.zip&lt;/code&gt;&lt;/a&gt; for all of the source files.&lt;/p&gt;
&lt;p&gt;Most of these files were actually generated automatically when home assistant started the first time.
The format for all of the most relevant files is YAML, which stands for &amp;ldquo;Yet Another Markup Language&amp;rdquo;.
YAML is fairly widespread nowadays as a format for configuration files.
To me, one of the most obvious advantages of the format is its conciseness and readability.
Indentation matters though, so be sure you use a proper (plain text) editor when editing in this format.&lt;/p&gt;
&lt;p&gt;I will go over the most important files in here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker-compose.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;configuration.yaml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ui-lovelace.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker-composeyaml"&gt;&lt;code&gt;docker-compose.yaml&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This the main file that will start all of the required containers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3.8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;homeassistant&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;homeassistant/home-assistant:2021.6.3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;8123:8123&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;.:/config&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;links&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;mosquitto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;db&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;mosquitto&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;eclipse-mosquitto:1.6.14&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;1883:1883&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;db&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres:13.3-alpine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;POSTGRES_DB&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;POSTGRES_USER&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;POSTGRES_PASSWORD&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;hass-db:/var/lib/postgresql/data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hass-db&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This file defines 3 &lt;em&gt;services&lt;/em&gt;, &lt;code&gt;homeassistant&lt;/code&gt; (the main home assistant service), &lt;code&gt;mosquitto&lt;/code&gt; (an MQTT broker) and &lt;code&gt;db&lt;/code&gt; (a database &amp;ndash; although this isn&amp;rsquo;t strictly required).
Some notes on the keys used in the configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ports&lt;/strong&gt;: this is a mapping of the ports within the container to the outside world, e.g. &lt;code&gt;&amp;quot;8123:8123&amp;quot;&lt;/code&gt; means &amp;ldquo;maps the port 8123 inside the container to the host on port 812 &amp;ldquo;. Port 8123 is the port home assistant runs on by default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;volumes&lt;/strong&gt;: this is mapping of the folder structure inside the container to the outside host. For the &lt;code&gt;homeassistant&lt;/code&gt; service, this means to map the configuration folder to the location where &lt;code&gt;docker-compose&lt;/code&gt; is executed. This also means it&amp;rsquo;s easy to put your own configuration inside the container this way. Volumes needn&amp;rsquo;t be mapped to an explicit host folder though, see the example for the &lt;code&gt;db&lt;/code&gt; service where there&amp;rsquo;s just a named volume called &lt;code&gt;hass-db&lt;/code&gt;, managed by docker.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;links&lt;/strong&gt;: links take care of the networking between containers. A link from &lt;code&gt;homeassistant&lt;/code&gt; to &lt;code&gt;mosquitto&lt;/code&gt; means that the home assistant instance will be able to reach that other service with the name &lt;code&gt;mosquitto&lt;/code&gt;. &lt;code&gt;docker-compose&lt;/code&gt; takes care of creating the proper docker network for that. It also means that if the &lt;code&gt;homeassistant&lt;/code&gt; service is started, the other 2 services are also automatically started.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, you can actually already just fire up home assistant using &lt;code&gt;docker-compose up&lt;/code&gt;!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker-compose up
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Starting hass_mosquitto_1 ... &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Starting hass_db_1 ... &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Starting hass_homeassistant_1 ... &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Attaching to hass_mosquitto_1, hass_db_1, hass_homeassistant_1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | PostgreSQL Database directory appears to contain a database; Skipping initialization
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 |
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:56.893 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: starting PostgreSQL 13.3 on x86_64-pc-linux-musl, compiled by gcc &lt;span style="color:#f92672"&gt;(&lt;/span&gt;Alpine 10.2.1_pre1&lt;span style="color:#f92672"&gt;)&lt;/span&gt; 10.2.1 20201203, 64-bit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:56.893 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: listening on IPv4 address &lt;span style="color:#e6db74"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;, port &lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:56.893 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: listening on IPv6 address &lt;span style="color:#e6db74"&gt;&amp;#34;::&amp;#34;&lt;/span&gt;, port &lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:56.902 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: listening on Unix socket &lt;span style="color:#e6db74"&gt;&amp;#34;/var/run/postgresql/.s.PGSQL.5432&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:56.912 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;21&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: database system was interrupted; last known up at 2021-06-08 20:37:13 UTC
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mosquitto_1 | 1623352796: mosquitto version 1.6.14 starting
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mosquitto_1 | 1623352796: Config loaded from /mosquitto/config/mosquitto.conf.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mosquitto_1 | 1623352796: Opening ipv4 listen socket on port 1883.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mosquitto_1 | 1623352796: Opening ipv6 listen socket on port 1883.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mosquitto_1 | 1623352796: mosquitto version 1.6.14 running
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:57.219 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;21&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: database system was not properly shut down; automatic recovery in progress
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:57.227 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;21&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: redo starts at 0/1920BF0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:57.231 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;21&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: invalid record length at 0/1931478: wanted 24, got &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:57.231 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;21&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: redo &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt; at 0/1931450
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;s6-init&lt;span style="color:#f92672"&gt;]&lt;/span&gt; making user provided files available at /var/run/s6/etc...exited 0.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;db_1 | 2021-06-10 19:19:57.298 UTC &lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: database system is ready to accept connections
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;s6-init&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ensuring user provided files have correct perms...exited 0.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;fix-attrs.d&lt;span style="color:#f92672"&gt;]&lt;/span&gt; applying ownership &amp;amp; permissions fixes...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;fix-attrs.d&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;cont-init.d&lt;span style="color:#f92672"&gt;]&lt;/span&gt; executing container initialization scripts...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;cont-init.d&lt;span style="color:#f92672"&gt;]&lt;/span&gt; udev.sh: executing...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;cont-init.d&lt;span style="color:#f92672"&gt;]&lt;/span&gt; udev.sh: exited 0.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;cont-init.d&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;services.d&lt;span style="color:#f92672"&gt;]&lt;/span&gt; starting services
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;homeassistant_1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;services.d&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This should actually have created the other configuration files for you.&lt;/p&gt;
&lt;h3 id="configurationyaml"&gt;&lt;code&gt;configuration.yaml&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The next file to discuss is the main home assistant configuration file.&lt;/p&gt;
&lt;p&gt;Here is my final adapted version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Configure a default setup of Home Assistant (frontend, api, etc)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;default_config&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Text to speech&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;tts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;platform&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;google_translate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;group&lt;/span&gt;: !&lt;span style="color:#ae81ff"&gt;include groups.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;script&lt;/span&gt;: !&lt;span style="color:#ae81ff"&gt;include scripts.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;scene&lt;/span&gt;: !&lt;span style="color:#ae81ff"&gt;include scenes.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;lovelace&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;mode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;recorder&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;db_url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgresql://hass:hass@db/hass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;mqtt&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;broker&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mosquitto&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Entities representing push buttons&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;switch&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;platform&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mqtt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;study room&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command_topic&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tesla/input/2_01/set&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state_topic&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tesla/input/2_01/state&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Entities representing lights&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;light&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;platform&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mqtt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;study room&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command_topic&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;edison/relay/2_05/set&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;state_topic&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;edison/relay/2_05/state&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;optimistic&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt; &lt;span style="color:#75715e"&gt;# only needed because of demo now&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Automation to link the two together&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;automation&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;alias&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Toggle study room&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;trigger&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;platform&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;state&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;entity_id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;switch.study_room&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;from&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;off&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;to&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;action&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;service&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;light.toggle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;entity_id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;light.study_room&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The top part of the file contains mostly &lt;strong&gt;boilerplate code&lt;/strong&gt; that was added by homeassistant itself, e.g. the &lt;code&gt;default_config&lt;/code&gt; and &lt;code&gt;tts&lt;/code&gt;.
The &lt;code&gt;group&lt;/code&gt;, &lt;code&gt;script&lt;/code&gt; and &lt;code&gt;scene&lt;/code&gt; blocks were also added automatically and use the &lt;code&gt;!include&lt;/code&gt; keyword, which home assistant would interpret as including those other yaml files within this main configuration file.
When you have a large number of entities to add, this would be the typical way to structure them.
For the discussion here, I did not do that though.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;code&gt;recorder&lt;/code&gt;&lt;/strong&gt; part is about the &lt;a href="https://www.home-assistant.io/integrations/recorder/#custom-database-engines"&gt;home assistant database integration&lt;/a&gt;.
This contains the connection URL (including the credentials).
If you compare this again with the &lt;code&gt;docker-compose.yaml&lt;/code&gt; file, you will see those credentials which were set from the &lt;code&gt;environment&lt;/code&gt; key for the &lt;code&gt;db&lt;/code&gt; postgres service.
In a proper setup, you&amp;rsquo;d use the &lt;a href="https://www.home-assistant.io/docs/configuration/secrets/"&gt;secrets system&lt;/a&gt;, but as the database integration isn&amp;rsquo;t that important here, I did not do that.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;code&gt;mqtt&lt;/code&gt;&lt;/strong&gt; integration is quite relevant here.
This should point to your MQTT broker.
Checking &lt;code&gt;docker-compose.yaml&lt;/code&gt;, this service was called &lt;code&gt;mosquitto&lt;/code&gt; and should be reachable with just thus name from inside the docker container network&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The following entry is a configuration for a &lt;strong&gt;push button&lt;/strong&gt; using the &lt;a href="https://www.home-assistant.io/integrations/switch.mqtt/"&gt;home assistant MQTT switch&lt;/a&gt; integration.
It minimally needs the integration platform, the name (which is relevant for later referring to the entity) and a command and state topic, the meaning of which was described earlier.&lt;/p&gt;
&lt;p&gt;Next would be the matching &lt;strong&gt;light&lt;/strong&gt; which we would like to have triggered whenever the push button is toggled, based on the &lt;a href="https://www.home-assistant.io/integrations/light.mqtt/"&gt;home assistant MQTT light&lt;/a&gt; integration.
It also has the MQTT platform, name (which matches now, but needn&amp;rsquo;t really), command and state topic.
I also configured the light to be in &lt;code&gt;optimistic&lt;/code&gt; mode.
This means the that the state of the light internally in home assistant is updated regardless of changes on the state topic.
I did this because this is a dummy setup and this light is not linked to an actual device that will acknowledge commands with a matching update on the state topic.
In my real setup, this is obviously omitted.&lt;/p&gt;
&lt;p&gt;At this point, both push button and light are known to home assistant, but they are not linked.
The &lt;strong&gt;automation&lt;/strong&gt; takes care of that.
The general outline of an &lt;a href="https://www.home-assistant.io/docs/automation/basics/"&gt;home assistant automation&lt;/a&gt; is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;| trigger | when the switch state changes |
| condition | and it&amp;#39;s nighttime |
| action | then toggle the light |
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;condition&lt;/code&gt; part is optional for the automation, and for the example given here quite nonsensical indeed.
Important here is that we only allow toggles when the push buttons gets toggled from off to on, not the other way around &amp;ndash; otherwise if would follow the push buttons state, which means the light would only be on for the duration the button is pressed.
The &lt;code&gt;light.toggle&lt;/code&gt; is an action that is built-in to home assistant and does just take: if the light was off, it will turn it on, and vice versa.&lt;/p&gt;
&lt;p&gt;Check the &lt;a href="https://www.home-assistant.io/docs/configuration/"&gt;home assistant configuration&lt;/a&gt; for more extensive documentation and further links.&lt;/p&gt;
&lt;h3 id="ui-lovelaceyaml"&gt;&lt;code&gt;ui-lovelace.yaml&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This file contains the visual outline for home assistant.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;views&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Home&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;icon&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mdi:home&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cards&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;entities&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Entities&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;entities&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;switch.study_room&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;light.study_room&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;automation.toggle_study_room&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This just makes the demo a bit easier to look at.&lt;/p&gt;
&lt;h2 id="demo"&gt;Demo&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s see it in action!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-08-03/demo.gif" alt="demo"&gt;&lt;/p&gt;
&lt;p&gt;The top left terminal just shows the &lt;code&gt;docker-compose&lt;/code&gt; output, containing the aggregated log output of the different services.
The right hand part shows the simplified home assistant UI, containing (top to bottom) the switch state, the light state and the automation.
The middle left terminal uses the &lt;a href="https://mosquitto.org/man/mosquitto_sub-1.html"&gt;&lt;code&gt;mosquitto_sub&lt;/code&gt;&lt;/a&gt; tool to read all events from the broker; the left one subscribes to &lt;code&gt;tesla/#&lt;/code&gt; meaning any topic below &lt;code&gt;tesla/&lt;/code&gt; (for the push button) and the right for &lt;code&gt;edison/&lt;/code&gt; (the light).&lt;/p&gt;
&lt;p&gt;The bottom left terminal is used to simulate the push of a button by sending out events directly to the push button&amp;rsquo;s state topic, using another tool called &lt;a href="https://mosquitto.org/man/mosquitto_pub-1.html"&gt;&lt;code&gt;mosquitto_pub&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The commands themselves are:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mosquitto_pub -h localhost -t tesla/input/2_01/state -m ON
mosquitto_pub -h localhost -t tesla/input/2_01/state -m OFF
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each of the lines represent a push to &lt;em&gt;host&lt;/em&gt; &amp;ldquo;&lt;code&gt;h&lt;/code&gt;&amp;rdquo; &lt;code&gt;localhost&lt;/code&gt;, on &lt;em&gt;topic&lt;/em&gt; &amp;ldquo;&lt;code&gt;t&lt;/code&gt;&amp;rdquo; &lt;code&gt;tesla/input/2_01/state&lt;/code&gt; with &lt;em&gt;message&lt;/em&gt; &amp;ldquo;&lt;code&gt;m&lt;/code&gt;&amp;rdquo; &lt;code&gt;ON&lt;/code&gt; or &lt;code&gt;OFF&lt;/code&gt;.
By rapidly firing them off shortly after each other, this simulates the push of a button (first on, then off again).&lt;/p&gt;
&lt;p&gt;When the push button state updates are pushed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;home assistant updates the internal state of the push button entity&lt;/li&gt;
&lt;li&gt;the automation kicks in, and updates the matching light entity is toggled&lt;/li&gt;
&lt;li&gt;the light entity sends out the update on the command topic&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Apart from controlling the light with the push button, it can also be controlled directly from home assistant; when toggling the light entity directly in home assistant, it just sends out the command on the MQTT topic directly.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;This concludes my approach to using (abusing?) home assistant and the MQTT integration as the central part of my home automation setup.&lt;/p&gt;
&lt;p&gt;I did use MQTT as the standard to centralize all my components on, have a look at the other posts presented in the &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation overview post&lt;/a&gt; of how to get there.
Examples of other components that use MQTT in the same fashion are window contacts, PIR, smoke and water detectors, motorized blinds, etc.&lt;/p&gt;
&lt;p&gt;The advantage of home assistant though is that it does not need to all be the same protocol, so it is quite easy to add other integrations in the mix as well.
A notable example of this is an automation where I link one of these push buttons to a &lt;a href="https://www.home-assistant.io/integrations/hue/"&gt;Philips Hue light&lt;/a&gt;; as far as home assistant is concerned, both of these are light entities.&lt;/p&gt;
&lt;p&gt;A disadvantage of using home assistant this way and by extent the overall approach is the &lt;em&gt;centralized&lt;/em&gt; nature of it.
Home assistant needs to be online all the time for everything to work.
Initially, I did run home assistant using &lt;code&gt;docker-compose&lt;/code&gt;, which was quite stable.
However, doing updates was never fun, since it meant that one container needs to go down before the next comes up, leaving in a short (but stressful) period of downtime.
Using k3s as the supervisor certainly fixed that.
Given that the majority of my interfacing hardware is actually all on MQTT, I might at some point add another service in the mix that mimics the automation engine as a backup.&lt;/p&gt;
&lt;p&gt;A similar argument can be made about the &lt;code&gt;mosquitto&lt;/code&gt; broker.
Currently, I actually run that as a dedicated service on the host itself.
Alternative solutions such as &lt;a href="https://www.emqx.com/en/blog/emqx-mqtt-broker-k8s-cluster"&gt;emqx&lt;/a&gt; exist though, which would run MQTT in a high-availability mode (multiple, load balanced instances).&lt;/p&gt;
&lt;p&gt;Currently though, the system works quite fine and everyone at home has gotten so accustomed to it, they only really notice when not being at home.
The backlog of improvements and extra bells and whistles is still quite long, so I&amp;rsquo;ll hope to write more on this later!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;the &lt;code&gt;mosquitto&lt;/code&gt; version used in the &lt;code&gt;docker-compose.yaml&lt;/code&gt; version was explicitly pinned to a version &amp;lt; 2.x, which did not enforce authentication, hence why the broker configuration just needs a host name to connect to.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>Software</title><link>https://mhemeryck.xyz/posts/2021-07-27-home_automation_software/</link><pubDate>Tue, 27 Jul 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-07-27-home_automation_software/</guid><description>&lt;p&gt;After connecting everything on the different &amp;ldquo;physical&amp;rdquo; layers, it is time to have a look at how to connect all on the &lt;strong&gt;software&lt;/strong&gt; side of things.
This post shall describe my current setup, specifically the different software components and the protocols they rely on for the unipi hardware.&lt;/p&gt;
&lt;p&gt;This post is a part of a larger series of posts on my home automation setup.
See the &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation overview post&lt;/a&gt;, to learn about the rationale and a description of the other posts!&lt;/p&gt;</description><content:encoded>&lt;p&gt;After connecting everything on the different &amp;ldquo;physical&amp;rdquo; layers, it is time to have a look at how to connect all on the &lt;strong&gt;software&lt;/strong&gt; side of things.
This post shall describe my current setup, specifically the different software components and the protocols they rely on for the unipi hardware.&lt;/p&gt;
&lt;p&gt;This post is a part of a larger series of posts on my home automation setup.
See the &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation overview post&lt;/a&gt;, to learn about the rationale and a description of the other posts!&lt;/p&gt;
&lt;p&gt;Particularly relevant is the &lt;a href="https://mhemeryck.xyz/posts/2021-07-20-home_automation_hardware/"&gt;hardware&lt;/a&gt; post, since I my software builds on the unipi neuron platform.&lt;/p&gt;
&lt;h2 id="software"&gt;Software&lt;/h2&gt;
&lt;p&gt;The following diagram shows the different software &lt;em&gt;layers&lt;/em&gt;, going from the kernel which polls the IO-boards to eventually pushing out / pulling in MQTT events.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-27/software.png" alt="software layers"&gt;&lt;/p&gt;
&lt;h3 id="kernel-polling-spi"&gt;Kernel: polling SPI&lt;/h3&gt;
&lt;p&gt;Unipi provides their own &lt;strong&gt;&lt;a href="https://kb.unipi.technology/en:files:software:os-images:00-start#neuron_opensource_os"&gt;neuron open source OS&lt;/a&gt;&lt;/strong&gt;, which is a modified version of &lt;a href="https://www.raspberrypi.com/software/operating-systems/"&gt;raspbian&lt;/a&gt; including custom kernel drivers to poll the I/O boards over &lt;a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface"&gt;SPI&lt;/a&gt;.
The kernel driver makes the data available via the &lt;a href="https://simplymodbus.ca/"&gt;modbus&lt;/a&gt; protocol.
The modbus protocol is a widely accepted, open industry standard for interfacing with PLCs.
I believe you would typically use it in a setup where you have one main controller and a series of follower controllers - which is pretty much the situation you would have using SPI.
Note that the unipi units also have an &lt;a href="https://en.wikipedia.org/wiki/RS-485"&gt;RS-485&lt;/a&gt; connection, commonly used for daisy chaining multiple controllers.&lt;/p&gt;
&lt;p&gt;Apart from the modbus interface, unipi also provides a &lt;strong&gt;&lt;a href="https://kb.unipi.technology/en:sw:02-apis:04-sysfs"&gt;sysfs interface&lt;/a&gt;&lt;/strong&gt;, which essentially maps the I/O states and controls to a number of files in a fixed file system structure.
I have used this sysfs interface in another project, &lt;a href="https://github.com/mhemeryck/unipitt"&gt;unipitt&lt;/a&gt;, but since I am currently not using this one, I will not discuss this further.&lt;/p&gt;
&lt;h3 id="evok-modbus-to-web-apis"&gt;Evok: modbus to web APIs&lt;/h3&gt;
&lt;p&gt;Unipi also provides an open source library called &lt;strong&gt;&lt;a href="https://github.com/UniPiTechnology/evok"&gt;evok&lt;/a&gt;&lt;/strong&gt; that will periodically poll the underlying modbus interface and make it available via all sorts of common web API formats and protocols: JSON / REST, JSONRPC, SOAP, &amp;hellip;
See the &lt;a href="https://evok.api-docs.io/1.0/jkctke5arbcnjt8az"&gt;evok API docs&lt;/a&gt; for an extensive overview of all possible interfaces.&lt;/p&gt;
&lt;p&gt;The most interesting interface provided by evok is the &lt;a href="https://evok.api-docs.io/1.0/mpqzDwPwirsoq7i5A/websocket"&gt;evok websockets interface&lt;/a&gt;, since this is the only interface that can also trigger actions for particular events.
This is a definite requirement here, since you&amp;rsquo;d need to be able toggle the relays fast in case of an incoming request.&lt;/p&gt;
&lt;p&gt;For the &lt;em&gt;receiving&lt;/em&gt; of messages (I/O board → outside) you would need to register a number of callback functions to the websocket.
The arguments to the callback function then hold information about the specific I/O action that triggered them.
For the &lt;em&gt;sending&lt;/em&gt; of messages (outside → I/O board), you can just send a structured message directly.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a python snippet directly taken from the &lt;a href="https://evok.api-docs.io/1.0/mpqzDwPwirsoq7i5A/websocket"&gt;evok websockets interface&lt;/a&gt; docs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; websocket
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ws://your.unipi.ip.address/ws&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_message&lt;/span&gt;(ws, message):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; obj &lt;span style="color:#f92672"&gt;=&lt;/span&gt; json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loads(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dev &lt;span style="color:#f92672"&gt;=&lt;/span&gt; obj[&lt;span style="color:#e6db74"&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; circuit &lt;span style="color:#f92672"&gt;=&lt;/span&gt; obj[&lt;span style="color:#e6db74"&gt;&amp;#34;circuit&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; value &lt;span style="color:#f92672"&gt;=&lt;/span&gt; obj[&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_error&lt;/span&gt;(ws, error):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(error)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_close&lt;/span&gt;(ws):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;Connection closed&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# receiving messages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ws &lt;span style="color:#f92672"&gt;=&lt;/span&gt; websocket&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WebSocketApp(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; on_message&lt;span style="color:#f92672"&gt;=&lt;/span&gt;on_message,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; on_error&lt;span style="color:#f92672"&gt;=&lt;/span&gt;on_error,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; on_close&lt;span style="color:#f92672"&gt;=&lt;/span&gt;on_close,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ws&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run_forever()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# sending messages&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ws &lt;span style="color:#f92672"&gt;=&lt;/span&gt; websocket&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WebSocket()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ws&lt;span style="color:#f92672"&gt;.&lt;/span&gt;connect(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ws&lt;span style="color:#f92672"&gt;.&lt;/span&gt;send(&lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;cmd&amp;#34;:&amp;#34;set&amp;#34;,&amp;#34;dev&amp;#34;:&amp;#34;relay&amp;#34;,&amp;#34;circuit&amp;#34;:&amp;#34;3&amp;#34;,&amp;#34;value&amp;#34;:&amp;#34;1&amp;#34;}&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ws&lt;span style="color:#f92672"&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="evok2mqtt-websocket-to-mqtt"&gt;&lt;code&gt;evok2mqtt&lt;/code&gt;: websocket to MQTT&lt;/h3&gt;
&lt;p&gt;The final layer is to &lt;strong&gt;translate the websocket &amp;ldquo;events&amp;rdquo; from and to MQTT events&lt;/strong&gt;.
This translation is required since this is the standard message-based system interface that was chosen to interface with &lt;a href="https://www.home-assistant.io/"&gt;home assistant&lt;/a&gt;.
In order to implement this, I did create a small application called &lt;a href="https://github.com/mhemeryck/evok2mqtt"&gt;evok2mqtt&lt;/a&gt;.
The application is written in python3 and uses the &lt;code&gt;websockets&lt;/code&gt; library for interfacing with the evok websocket and &lt;a href="https://pypi.org/project/paho-mqtt/"&gt;&lt;code&gt;paho-mqtt&lt;/code&gt;&lt;/a&gt; to do the same thing for MQTT.
This application runs alongside evok on the unipi neuron itself.
Note that the application could in theory also run elsewhere on the network as the websocket and the MQTT broker can just be addressed over the network.
Given that the program itself was actually quite simple and fun to write, I will outline how it works with some example code snippets.&lt;/p&gt;
&lt;p&gt;To express actions that need to occur on the receiving of messages, the MQTT library works pretty similar to the websockets library using callback functions, see e.g. &lt;a href="https://github.com/mhemeryck/evok2mqtt/blob/96bf7e19063b15e96522fac038fab89959f16475/evok2mqtt/__init__.py#L158-L195"&gt;this evok2mqtt &lt;code&gt;on_message&lt;/code&gt; snippet&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_message&lt;/span&gt;(client, userdata, message):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Callback for MQTT events&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;info(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Incoming MQTT message for topic &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;message&lt;span style="color:#f92672"&gt;.&lt;/span&gt;topic&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; with payload &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;message&lt;span style="color:#f92672"&gt;.&lt;/span&gt;payload&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extra&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: LOG_KIND&lt;span style="color:#f92672"&gt;.&lt;/span&gt;MQTT},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; MQTT_COMMAND_TOPIC_REGEX&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;(message&lt;span style="color:#f92672"&gt;.&lt;/span&gt;topic)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; device_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;group(&lt;span style="color:#e6db74"&gt;&amp;#34;device_name&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dev &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;group(&lt;span style="color:#e6db74"&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; circuit &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;group(&lt;span style="color:#e6db74"&gt;&amp;#34;circuit&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; device_name &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; _settings()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DEVICE_NAME:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;warning(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Handling incoming message for device &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, expected &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; device_name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _settings()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DEVICE_NAME,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extra&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: LOG_KIND&lt;span style="color:#f92672"&gt;.&lt;/span&gt;MQTT},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Update state topic&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;publish(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MQTT_TOPIC_FORMAT&lt;span style="color:#f92672"&gt;.&lt;/span&gt;format(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; device_name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;device_name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dev&lt;span style="color:#f92672"&gt;=&lt;/span&gt;dev,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; circuit&lt;span style="color:#f92672"&gt;=&lt;/span&gt;circuit,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hass_action&lt;span style="color:#f92672"&gt;=&lt;/span&gt;HASS_ACTION&lt;span style="color:#f92672"&gt;.&lt;/span&gt;STATE,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message&lt;span style="color:#f92672"&gt;.&lt;/span&gt;payload,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Send to websocket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; value &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; message&lt;span style="color:#f92672"&gt;.&lt;/span&gt;payload &lt;span style="color:#f92672"&gt;==&lt;/span&gt; _settings()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;MQTT_PAYLOAD_ON &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;info(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Push to output &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;dev&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;circuit&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;value&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, extra&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: LOG_KIND&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WEBSOCKET}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(_ws_trigger(dev, circuit, value))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This snippet essentially details all actions that occur for incoming events from the MQTT broker (originating from home assistant):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the message is logged&lt;/li&gt;
&lt;li&gt;the message topic is checked to ensure whether we should process this message further&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;the state of the I/O is updated to acknowledge to home assistant that the update went OK&lt;/li&gt;
&lt;li&gt;a call is issued to the websocket to do the update on the I/O&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the opposite action, a websocket event that needs to be translated to MQTT, &lt;a href="https://github.com/mhemeryck/evok2mqtt/blob/96bf7e19063b15e96522fac038fab89959f16475/evok2mqtt/__init__.py#L127-L137"&gt;a similar callback function&lt;/a&gt; is implemented.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_ws_loop&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Main loop polling incoming events from websockets&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;info(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Connecting to &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _settings()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WEBSOCKET_URI,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extra&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: LOG_KIND&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WEBSOCKET},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; websockets&lt;span style="color:#f92672"&gt;.&lt;/span&gt;connect(_settings()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WEBSOCKET_URI) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; websocket:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; websocket&lt;span style="color:#f92672"&gt;.&lt;/span&gt;recv()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _ws_process(payload)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This snippet sets up the websocket loop that will continually check the websocket for newly incoming messages.
Note that this uses and async-await on the websocket, a feature provided in python&amp;gt;3.5&amp;rsquo;s &lt;a href="https://docs.python.org/3/library/asyncio.html"&gt;asyncio&lt;/a&gt; library.
The key thing to understand about asyncio here is that even though this is an infinite loop, during the waiting times for new data to come in from the websocket (or more generally, during the waiting time for &lt;em&gt;any&lt;/em&gt; I/O), the thread scheduler can yield control to another thread to execute concurrently.&lt;/p&gt;
&lt;p&gt;The function &lt;a href="https://github.com/mhemeryck/evok2mqtt/blob/96bf7e19063b15e96522fac038fab89959f16475/evok2mqtt/__init__.py#L100-L124"&gt;&lt;code&gt;_ws_process&lt;/code&gt;&lt;/a&gt; that processes the incoming payload is next:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_ws_process&lt;/span&gt;(payload):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Process incoming websocket payload, push to MQTT&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; obj &lt;span style="color:#f92672"&gt;=&lt;/span&gt; json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loads(payload)[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;debug(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Incoming message for websocket &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, obj, extra&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: LOG_KIND&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WEBSOCKET}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; topic &lt;span style="color:#f92672"&gt;=&lt;/span&gt; MQTT_TOPIC_FORMAT&lt;span style="color:#f92672"&gt;.&lt;/span&gt;format(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; device_name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;_settings()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DEVICE_NAME,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dev&lt;span style="color:#f92672"&gt;=&lt;/span&gt;obj[&lt;span style="color:#e6db74"&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; circuit&lt;span style="color:#f92672"&gt;=&lt;/span&gt;obj[&lt;span style="color:#e6db74"&gt;&amp;#34;circuit&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hass_action&lt;span style="color:#f92672"&gt;=&lt;/span&gt;HASS_ACTION&lt;span style="color:#f92672"&gt;.&lt;/span&gt;STATE,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _settings()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;MQTT_PAYLOAD_ON
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; obj[&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;] &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; _settings()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;MQTT_PAYLOAD_OFF
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _mqtt_client()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;publish(topic, payload&lt;span style="color:#f92672"&gt;=&lt;/span&gt;payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;info(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;MQTT publish &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt; to topic &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; topic,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extra&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;: LOG_KIND&lt;span style="color:#f92672"&gt;.&lt;/span&gt;WEBSOCKET},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The operations it does, are quite straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;load the object representation from the incoming payload (as JSON).&lt;/li&gt;
&lt;li&gt;construct the MQTT topic based on the object name and circuit.&lt;/li&gt;
&lt;li&gt;construct the MQTT payload from the object payload&lt;/li&gt;
&lt;li&gt;publish the MQTT payload to the topic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There you have it: translation from-and-to websockets / MQTT.
Note that this translation covers both inputs (push buttons) as well as outputs (relays).&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;At this point, with all of these layers of software in between, the unipi neuron unit provides a standard MQTT-based interface to home assistant.&lt;/p&gt;
&lt;p&gt;The main downside I&amp;rsquo;ve felt at this point is that the reaction speed from the &lt;code&gt;evok&lt;/code&gt; interface is sometimes a bit too slow to react quickly enough to push button toggles.
On rare occasions, this can result in push buttons events being missed &amp;ndash; which most of the times can be easily fixed by pushing the buttons a bit longer.&lt;/p&gt;
&lt;p&gt;Also, having multiple of these translation layers makes the overall solution a bit more complex and will surely also add to the latency.&lt;/p&gt;
&lt;p&gt;I do intend at some point to have another look at this setup.
One option is to look at the (open source) implementation of evok and replace the async parts there with the more modern asyncio library.
However, if performance is key, I will more likely have a look again at the sysfs interface and my &lt;a href="https://github.com/mhemeryck/unipitt"&gt;unipitt&lt;/a&gt; project, which uses &lt;a href="https://golang.org/"&gt;golang&lt;/a&gt; under the hood.&lt;/p&gt;
&lt;p&gt;Nonetheless, this current setup has been working quite OK for me thus far.&lt;/p&gt;
&lt;h2 id="update-from-unipi"&gt;Update from unipi&lt;/h2&gt;
&lt;p&gt;A while after this post went live, I did get some &lt;a href="https://forum.unipi.technology/post/5807"&gt;friendly&lt;/a&gt; &lt;a href="https://forum.unipi.technology/post/5824"&gt;feedback&lt;/a&gt; on &lt;a href="https://forum.unipi.technology/topic/1410/home-assistant-diy-setup-blog"&gt;my unipi forum post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some selected parts of the notes I think I should share&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;evok&lt;/code&gt; might get an update, &lt;em&gt;including MQTT support&lt;/em&gt;!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Evok is planned (well, maybe &amp;ldquo;wished&amp;rdquo; is a better word:)) to be rewritten to Python 3 and to a multi-threaded application.
Along with that, major changes to the API will happen.
We will abandon some of the less used APIs (RPC and SOAP probably) and definitely add native MQTT support since it is a driving force in the industry.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Missed state changes should be possible to detect with the digital input counter:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The missed state changes of the DI can be detected by checking the state of the DI counter.
The counter is implemented in the HW and can count as fast as 10kHz signals.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The low-level communication I did outline using SPI and modbus is slightly different from what I did present above:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And a bit of explanation of the low level communication:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The HW boards are connected over SPI&lt;/li&gt;
&lt;li&gt;The protocol on the SPI level is modified ModbusRTU&lt;/li&gt;
&lt;li&gt;The Unipi kernel module (part of the &lt;code&gt;unipi-kernel-modules&lt;/code&gt; package) exposes this communication channel as &lt;code&gt;/dev/unipispi device&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The Unipi ModbusTCP server (part of the &lt;code&gt;unipi-modbus-tools&lt;/code&gt;) exposes this as a ModbusTCP server running on TCP/502&lt;/li&gt;
&lt;li&gt;The rest is correct. Evok polls the ModbusTCP server as fast as it can. And creates a &amp;ldquo;system image&amp;rdquo; of the HW state.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The IO boards might also get a firmware-level upgrade!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And in the firmware of the IOs we plan to introduce a &amp;ldquo;latching&amp;rdquo; state of the DIs.
Those will be registers and coils which will hold the leading edge of the DIs until they are read from the software. That will prevent the losing of the edge altogether.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I really appreciate this kind of feedback from the unipi team.
It is nice to see how they are continuously improving their product (e.g. the idea of including native MQTT support).
More importantly, I think this company really understands well the value of their product as an open platform and is actively working on community-building.
Like I did mention in some of my earlier posts in this series, this openness is one of the reasons I am really glad about their products!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;this could actually also be solved using configuration on the MQTT client setup, by only registering callbacks to certain topics.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;mostly literal, some formatting changes mine.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>Architecture</title><link>https://mhemeryck.xyz/posts/2021-06-22-home_automation_architecture/</link><pubDate>Tue, 22 Jun 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-06-22-home_automation_architecture/</guid><description>&lt;p&gt;Prior to actually building anything, it is important to think on a higher level what the various components are and how they will interact with each other, i.e. the &lt;em&gt;architecture&lt;/em&gt;.
Over the process of doing my own setup, I came across a large number of different possible configurations, each with their own benefits and downsides.
For the first part of this post, I will focus on the final layout.&lt;/p&gt;
&lt;p&gt;While discussing the architecture, it is important to realize there are a number of different &lt;em&gt;layers of abstraction&lt;/em&gt; to reason about these.
This will be the topic of the second part of this post.&lt;/p&gt;</description><content:encoded>&lt;p&gt;Prior to actually building anything, it is important to think on a higher level what the various components are and how they will interact with each other, i.e. the &lt;em&gt;architecture&lt;/em&gt;.
Over the process of doing my own setup, I came across a large number of different possible configurations, each with their own benefits and downsides.
For the first part of this post, I will focus on the final layout.&lt;/p&gt;
&lt;p&gt;While discussing the architecture, it is important to realize there are a number of different &lt;em&gt;layers of abstraction&lt;/em&gt; to reason about these.
This will be the topic of the second part of this post.&lt;/p&gt;
&lt;p&gt;To make everything more tangible, I did add a third section &lt;em&gt;detailing toggling a light with push button&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This post is a part of a larger series of posts on my home automation setup.
See the &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation overview post&lt;/a&gt;, to learn about the rationale and a description of the other posts.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;p&gt;The following diagram gives an high-level overview of the overall architecture.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-22/architecture.png" alt="architecture"&gt;&lt;/p&gt;
&lt;p&gt;Its main characteristics are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;distributed communication:
&lt;ul&gt;
&lt;li&gt;communication is (mostly) done via an event-based system, MQTT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;centralized wiring:
&lt;ul&gt;
&lt;li&gt;all I/O (push buttons, relays for lights, &amp;hellip;) is connected to modules which fit in a centralized electric cabinet&lt;/li&gt;
&lt;li&gt;there is a central service with automations connecting the different modules (home assistant)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s elaborate a bit more on the constituent components.&lt;/p&gt;
&lt;h3 id="mqtt-broker"&gt;MQTT broker&lt;/h3&gt;
&lt;p&gt;In the center of the drawing is a &lt;a href="https://hub.docker.com/_/eclipse-mosquitto"&gt;mosquitto MQTT broker&lt;/a&gt;.
MQTT is a &lt;em&gt;publish-subscribe&lt;/em&gt; protocol:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;there is one central instance, called the &lt;em&gt;broker&lt;/em&gt;, which manages different &lt;em&gt;topics&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;different clients can &lt;em&gt;publish&lt;/em&gt; updates, called &lt;em&gt;events&lt;/em&gt;, to the topics&lt;/li&gt;
&lt;li&gt;in addition, all clients can also &lt;em&gt;subscribe&lt;/em&gt; to topics to receive the events&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MQTT is a popular choice for IoT since its event-based model fits well for a lot of problems (e.g. a push button toggle).
Another advantage is that the each of the different clients only needs to maintain a connection to the broker and the topics they want to be subscribed to.
This makes for a very &lt;em&gt;loosely coupled&lt;/em&gt; and &lt;em&gt;scalable&lt;/em&gt; architecture: it is quite easy to add new components.
A disadvantage however is that all connections rely on the availability of the central broker.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id="mqtt-clients"&gt;MQTT clients&lt;/h3&gt;
&lt;p&gt;The principal other components of the setup are all MQTT clients which both subscribe and publish to specified topics, specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;subscribe: they listen to topics which instruct them to do some behavior (&amp;ldquo;command topics&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;publish: they publish events to topics that indicates their state (&amp;ldquo;state topics&amp;rdquo;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The clients in this setup can provide (a combination of):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;sensors&lt;/em&gt;: push buttons, motion sensors, smoke detectors, &amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;actors&lt;/em&gt;: lights, covers, sirens, &amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;logic&lt;/em&gt;: send out events on a certain condition, e.g. on another event (an automation)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="unipi"&gt;unipi&lt;/h4&gt;
&lt;p&gt;For the &lt;strong&gt;sensors&lt;/strong&gt; and &lt;strong&gt;actors&lt;/strong&gt; I did use hardware from &lt;a href="https://www.unipi.technology/"&gt;unipi&lt;/a&gt;.
In essence the unipi neuron units are DIN-rail mountable units housing a raspberry pi and a couple of I/O boards depending on the model you have.
The role of the unipi units is thus to provide an MQTT event-based interface for all home automation I/O: reading push buttons, toggling lights, controlling shades, reading smoke and movement detectors, etc.&lt;/p&gt;
&lt;h4 id="home-assistant"&gt;home assistant&lt;/h4&gt;
&lt;p&gt;Whereas the I/O modules themselves are distributed both physically and logically, I did want to &lt;strong&gt;centralize the controlling logic&lt;/strong&gt; in a dedicated service.
Cue in home assistant.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.home-assistant.io/"&gt;home assistant&lt;/a&gt; is an open source software project which aims to integrate all sorts of smart home devices.
Given my choice of MQTT as an interfacing protocol throughout my setup, I heavily rely on the &lt;a href="https://www.home-assistant.io/integrations/mqtt/"&gt;home assistant MQTT integration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From a high level, all sensors and actors which are available as MQTT topics can be represented as &lt;em&gt;entities&lt;/em&gt; within the home assistant configuration.
The home assistant instance itself connects to the central MQTT broker and listens to those MQTT topics for which it has entities.
Automations, which are also part of the configuration, can then link actions from one entity to another.
An obvious example is the push of a button that switches a light on.
The rationale behind using automations to link inputs with outputs, is that nothing is really hard-wired in this setup!&lt;/p&gt;
&lt;p&gt;Note that home assistant supports a wide range of integrations beyond MQTT.
The thermostat in the schematic for instance has an integration based on REST / HTTP.&lt;/p&gt;
&lt;h2 id="layers-of-abstraction"&gt;Layers of abstraction&lt;/h2&gt;
&lt;p&gt;Planning for a DIY wired open home automation setup requires some other specific thinking in terms of the different &lt;strong&gt;layers of abstraction&lt;/strong&gt;.
The following diagram focuses on these different layers.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-22/layers.png" alt="layers"&gt;&lt;/p&gt;
&lt;p&gt;From low to high level:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;wiring&lt;/strong&gt;: the physical wiring which connects to electrical components such as push buttons and light fixtures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hardware&lt;/strong&gt;: the physical hardware component that will interface with the electrical components (I/O).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;software&lt;/strong&gt;: a client library interfacing with the hardware and which can translate low-level hardware events to protocol events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;network&lt;/strong&gt;: layer which connects the different components together using the protocols&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;service&lt;/strong&gt;: the services are those parts which actually talk to each other over the network&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice that not all components need to have all of the layers.
Services like home assistant and the mosquitto broker act completely on the network itself.&lt;/p&gt;
&lt;p&gt;Why does this matter?
For the majority of commercially available IoT devices, you would get something that takes care of most of the lower levels.
Wiring mostly isn&amp;rsquo;t an issue (it is typically wireless) and the hardware and software are part of the package that you buy.
In this wired layout with custom components however, all of these layers need to be addressed.&lt;/p&gt;
&lt;p&gt;As an example, have a look at the wiring I did for my cover control:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-22/wiring.jpg" alt="wiring"&gt;&lt;/p&gt;
&lt;p&gt;Most of my wiring was done in a &lt;a href="https://en.wikipedia.org/wiki/Network_topology#Star"&gt;star topology&lt;/a&gt;.
This means for example that you do need a single cable run for &lt;em&gt;each&lt;/em&gt; of the individual (group of) lights you want to control.
The upside is that this is very flexible towards the future, since physical rewiring is still possible from the electrical cabinet.
The (visible) downside is that the amount of wires piles up rapidly and you will need to think of some kind of system to keep a sane overview of this.&lt;/p&gt;
&lt;h3 id="bus-based-systems"&gt;Bus-based systems&lt;/h3&gt;
&lt;p&gt;Alternatively, the wiring could have been done with a &lt;strong&gt;bus-based system&lt;/strong&gt; like &lt;a href="https://knx.org"&gt;KNX&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Digital_Addressable_Lighting_Interface"&gt;DALI&lt;/a&gt;.
The advantage of systems like these is that the wiring would have been a lot easier and they can also provide some of the functionalities on other parts of the stack.
For the case of KNX for instance, each of the components (push buttons, light control, &amp;hellip;) are provisioned individually, resulting in a fully distributed, more fault-tolerant system.
Note that there is a &lt;a href="https://www.home-assistant.io/integrations/knx/"&gt;home assistant KNX integration&lt;/a&gt;, so even in this case, it could fit the proposed architecture (but obviously less reliant on MQTT).&lt;/p&gt;
&lt;p&gt;At this point, I did not choose for a bus-based system though.
The first reason was one of cost, notably in the case of KNX.
Also, I did want the possibility to easily (re)program my setup using general purpose programming languages, whereas KNX relies on (costly, vendor-restricted) visual programming tools.
All of my light sockets are wired with 5-wire cable, so at some point I might have another look at using DALI for controlling my lights.&lt;/p&gt;
&lt;h2 id="toggling-a-light-switch-"&gt;Toggling a light switch &amp;hellip;&lt;/h2&gt;
&lt;p&gt;To illustrate the theory, let&amp;rsquo;s just walk through all the steps of toggling a push button:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-22/flow.png" alt="flow"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a push button is pushed down, making contact (off-on) and then released again (on-off)&lt;/li&gt;
&lt;li&gt;the unipi digital input unit sees this change and translates this to an MQTT state change event which is pushed to the central mosquitto broker&lt;/li&gt;
&lt;li&gt;the home assistant instance has a subscription on the broker for the input button state change and updates its internal entity state&lt;/li&gt;
&lt;li&gt;the home assistant instance has an automation configured for the push button entity and triggers the matching light entity to an action&lt;/li&gt;
&lt;li&gt;the light entity update pushes out another event to the light action topic to turn on&lt;/li&gt;
&lt;li&gt;another unipi unit which is connected to the physical light and has a subscription to the light action topic pulls in the update&lt;/li&gt;
&lt;li&gt;the unipi unit toggles a relay, switching on the light&lt;/li&gt;
&lt;li&gt;finally, after the light has turned on, a state update is also pushed out from the unipi unit, which updates the internal state on home assistant&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;The focus of this post was to outline the main components and links in my home automation setup, principally based on MQTT and home assistant as the brains behind it all.&lt;/p&gt;
&lt;p&gt;The main advantage of this setup is the flexibility if offers since everything is configurable from within home assistant.
On the other side of the equation, once home assistant breaks down, the entire system can break down, which poses high reliability constraints on such central components.&lt;/p&gt;
&lt;p&gt;I did not yet dive down into the details of the different components i.e. the sensors and actuators, most notably the unipi platform.
Also, the different applications themselves, such as push buttons, lighting fixtures, DALI, shades, an alarm system, &amp;hellip; all merit their own discussion.
Finally, I did also point the different layers of abstraction to consider when laying out a wired home automation system.&lt;/p&gt;
&lt;p&gt;This should conclude the &amp;ldquo;theoretical&amp;rdquo; part of the home automation series.
Check the &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation overview post&lt;/a&gt; for the more &amp;ldquo;practical&amp;rdquo; upcoming posts.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;although even this could be mitigated using a highly available MQTT broker. &lt;a href="https://www.emqx.com/en/blog/emqx-mqtt-broker-k8s-cluster"&gt;EMQX&lt;/a&gt; seems to be an example of this, but I haven&amp;rsquo;t tried it myself yet.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>MQTT over TLS</title><link>https://mhemeryck.xyz/posts/2018-11-12-mqtt_over_tls/</link><pubDate>Mon, 12 Nov 2018 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2018-11-12-mqtt_over_tls/</guid><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/MQTT"&gt;MQTT&lt;/a&gt; is a popular lightweight protocol for use in home automation.
If features a central broker where each of the clients can communicate with in a pub / sub fashion.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;ve been toying around with some home automation setups, I was curious how to secure it with TLS.&lt;/p&gt;
&lt;h2 id="mosquitto"&gt;Mosquitto&lt;/h2&gt;
&lt;p&gt;The most commonly used broker implementation I have come across is &lt;a href="https://mosquitto.org/"&gt;mosquitto&lt;/a&gt; and is quite straightforward to setup on most *nix flavors.
In my setup, I wanted to run it on Raspberry Pi, which I could leave running at my local home network.
Since it runs raspbian, installing it was as simple as:&lt;/p&gt;</description><content:encoded>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/MQTT"&gt;MQTT&lt;/a&gt; is a popular lightweight protocol for use in home automation.
If features a central broker where each of the clients can communicate with in a pub / sub fashion.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;ve been toying around with some home automation setups, I was curious how to secure it with TLS.&lt;/p&gt;
&lt;h2 id="mosquitto"&gt;Mosquitto&lt;/h2&gt;
&lt;p&gt;The most commonly used broker implementation I have come across is &lt;a href="https://mosquitto.org/"&gt;mosquitto&lt;/a&gt; and is quite straightforward to setup on most *nix flavors.
In my setup, I wanted to run it on Raspberry Pi, which I could leave running at my local home network.
Since it runs raspbian, installing it was as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ sudo apt-get install mosquitto
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="tls-setup-keys-and-certificates"&gt;TLS setup: keys and certificates&lt;/h2&gt;
&lt;p&gt;Doing the TLS setup requires a number of key and certificate files.
The official &lt;a href="https://mosquitto.org/man/mosquitto-tls-7.html"&gt;mosquitto-tls man page&lt;/a&gt; fortunately lists all of them and how to generate them.&lt;/p&gt;
&lt;p&gt;Note that for the final setup, not all of these files are required; these are the files we &lt;em&gt;do&lt;/em&gt; need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ca.crt&lt;/code&gt;: Certificate Authority certificate: the &amp;ldquo;central&amp;rdquo; authority certifying ownership of the public key&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.key&lt;/code&gt;: server private key, linked to the certificate.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.crt&lt;/code&gt;: certificate from the server (MQTT broker), signing the server key pair, by the CA.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="file-layout-on-the-broker"&gt;File layout on the broker&lt;/h2&gt;
&lt;p&gt;On my setup, most of these folders were already available after install, so I figured to actually use them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ tree /etc/mosquitto
/etc/mosquitto
├── ca_certificates
│   ├── ca.crt
│   └── README
├── certs
│   ├── README
│   ├── server.crt
│   └── server.key
├── conf.d
│   ├── README
│   └── tls.conf
└── mosquitto.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="mosquittoconf"&gt;&lt;code&gt;mosquitto.conf&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;This is pretty the same config that was also provided by the built-in; just to show:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ cat /etc/mosquitto/mosquitto.conf
# Place your local configuration in /etc/mosquitto/conf.d/
#
# A full description of the configuration file is at
# /usr/share/doc/mosquitto/examples/mosquitto.conf.example
pid_file /var/run/mosquitto.pid
persistence true
persistence_location /var/lib/mosquitto/
log_dest file /var/log/mosquitto/mosquitto.log
# This include assures that our own custom config gets loaded
include_dir /etc/mosquitto/conf.d
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="tlsconf"&gt;&lt;code&gt;tls.conf&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Actual TLS specific config, pointing to all the files that were added.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ cat /etc/mosquitto/conf.d/tls.conf
port 8883
cafile /etc/mosquitto/ca_certificates/ca.crt
keyfile /etc/mosquitto/certs/server.key
certfile /etc/mosquitto/certs/server.crt
tls_version tlsv1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="reload-the-setup"&gt;Reload the setup&lt;/h2&gt;
&lt;p&gt;The package installer also conveniently creates the required (systemd) service for us.
Simply restart the service now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart mosquitto.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="client-subscribe"&gt;Client subscribe&lt;/h2&gt;
&lt;p&gt;With the broker running the new config, testing can easily be done with the mosquitto built-in &lt;code&gt;mosquitto_pub&lt;/code&gt; / &lt;code&gt;mosquitto_sub&lt;/code&gt; commands.
Obviously, all clients that want to use this config now need the CA file.&lt;/p&gt;
&lt;p&gt;On a client: subscribe:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ mosquitto_sub --cafile ca.crt -h raspberrypi.lan -t test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the host name should correspond to the host name in the certificate.&lt;/p&gt;
&lt;h2 id="client-publish"&gt;Client publish&lt;/h2&gt;
&lt;p&gt;On a client: publish&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ mosquitto_pub --cafile ca.crt -h raspberrypi.lan -t test -m &amp;quot;hi there&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the window running the subscribe, the message should now appear.&lt;/p&gt;
&lt;p&gt;Here, the clients do &lt;em&gt;not&lt;/em&gt; verify themselves though, they just use the certificate to encrypt traffic over TLS.&lt;/p&gt;</content:encoded></item></channel></rss>