<?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>mhemeryck</title><link>https://mhemeryck.xyz/</link><description>Recent content on mhemeryck</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 11 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://mhemeryck.xyz/index.xml" rel="self" type="application/rss+xml"/><item><title>AWS SAA-C03</title><link>https://mhemeryck.xyz/posts/2026-06-11-aws_saa_c03/</link><pubDate>Thu, 11 Jun 2026 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2026-06-11-aws_saa_c03/</guid><description>&lt;h2 id="aws-certified-solutions-architect---associate-certification"&gt;AWS Certified Solutions Architect - Associate Certification&lt;/h2&gt;
&lt;p&gt;At the beginning of 2026, I worked towards my &lt;strong&gt;AWS&lt;/strong&gt; certification.
Earlier, I did my GCP certification, as you can read in my &lt;a href="https://mhemeryck.xyz/posts/2024-03-01-gcp_ace/"&gt;GCP ACE blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The reason to consider the AWS certification as well started from a change at work: the decision had been taken somewhere in 2025 to &lt;strong&gt;shift the company from GCP to AWS&lt;/strong&gt; as the main cloud platform.
As part of that, the idea was that all devs would join in a weekly routine where we would try to follow an online learning path by Pluralsight&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;.
Ideally, some of us would then also take the AWS certification exam, since it would be a tangible goal to work towards.
Naturally, I followed along in all those sessions.&lt;/p&gt;</description><content:encoded>&lt;h2 id="aws-certified-solutions-architect---associate-certification"&gt;AWS Certified Solutions Architect - Associate Certification&lt;/h2&gt;
&lt;p&gt;At the beginning of 2026, I worked towards my &lt;strong&gt;AWS&lt;/strong&gt; certification.
Earlier, I did my GCP certification, as you can read in my &lt;a href="https://mhemeryck.xyz/posts/2024-03-01-gcp_ace/"&gt;GCP ACE blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The reason to consider the AWS certification as well started from a change at work: the decision had been taken somewhere in 2025 to &lt;strong&gt;shift the company from GCP to AWS&lt;/strong&gt; as the main cloud platform.
As part of that, the idea was that all devs would join in a weekly routine where we would try to follow an online learning path by Pluralsight&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;.
Ideally, some of us would then also take the AWS certification exam, since it would be a tangible goal to work towards.
Naturally, I followed along in all those sessions.&lt;/p&gt;
&lt;p&gt;At the end of those sessions, I didn&amp;rsquo;t immediately take the exam, since I didn&amp;rsquo;t feel well-prepared (I was basically too involved with another side project).
For the &lt;strong&gt;beginning of 2026&lt;/strong&gt;, I figured the time was right to pick up the pace again and focus solely on the exam.&lt;/p&gt;
&lt;p&gt;The AWS &lt;strong&gt;certification levels&lt;/strong&gt; follow a similar reasoning as those for GCP.
There are basically again 3 + 1 levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;foundational: entrance levels, also for non-technical levels&lt;/li&gt;
&lt;li&gt;associate: technical level, all-round fundamental skills&lt;/li&gt;
&lt;li&gt;professional: advanced role-based certifications&lt;/li&gt;
&lt;li&gt;specialty: specific technical domains&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Compared to GCP, there is also an associate developer certification, but quite early on, we decided to go in the direction of the &lt;strong&gt;Solutions Architect&lt;/strong&gt; certification.
This certification focuses more on the holistic picture, getting to know the different services, whereas the developer is really more tailored towards work as an application developer.&lt;/p&gt;
&lt;p&gt;Having done the GCP certification before and just having practical day-to-day hands-on experience with GCP meant that a lot of the concepts were already quite familiar to me.
For a great deal of services that GCP provides, &lt;strong&gt;AWS almost always has an equivalent&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;My general feeling though is that AWS for some things has more managed services and that they also integrate better than what I was used to in GCP.
I particularly find the &lt;strong&gt;serverless offering&lt;/strong&gt; in AWS a lot better, certainly when thinking e.g. about AWS lambda.&lt;/p&gt;
&lt;h2 id="how-did-i-prepare"&gt;How did I prepare?&lt;/h2&gt;
&lt;p&gt;As mentioned in the intro, my main first focus point in 2025 was the &lt;strong&gt;&lt;a href="https://www.pluralsight.com/paths/aws-certified-solutions-architect-associate-saa-c03"&gt;Pluralsight AWS learning paths&lt;/a&gt;&lt;/strong&gt;.
It was a sequence of 5 learning paths, i.e. sequence of courses all centered around a specific portion of AWS (e.g compute, storage, scaling, &amp;hellip;)
Each of those also contained what were called &lt;em&gt;Labs&lt;/em&gt;, which are essentially guided exercises to carry out in a managed AWS on-the-fly sandbox accounts.
For each of those courses, I had also taken the time to summarize basically each of the course wrap-sessions.
New now was that I could use LLMs to assist me in building this set of course notes.&lt;/p&gt;
&lt;p&gt;By the time I started my &lt;strong&gt;reboot of 2026&lt;/strong&gt;, the 2025 course had been revamped by Pluralsight.
Even though perhaps not completely necessary, I also took the full 2026 version of the updated course in the end.&lt;/p&gt;
&lt;p&gt;Apart from the Pluralsight material, I also looked into the &lt;a href="https://docs.aws.amazon.com/aws-certification/latest/solutions-architect-associate-03/solutions-architect-associate-03.html"&gt;AWS official exam guide&lt;/a&gt;.
It clearly lists the 4 main sections and focus points of the exam.
Another source that AWS provides is &lt;a href="https://skillbuilder.aws/"&gt;AWS Skill Builder&lt;/a&gt;.
Skill Builder is a very broad platform put forward by AWS itself and also has courses, practice exams and overviews, both paid and free.
I found it useful in the sense that it contained a video overview of the official exam guide.
Additionally, it also featured some practice exams.
Two whitepapers the exam guide also recommends you to look into are the &lt;a href="https://docs.aws.amazon.com/pdfs/wellarchitected/latest/framework/wellarchitected-framework.pdf"&gt;AWS well-architected framework&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/pdfs/whitepapers/latest/introduction-aws-security/introduction-aws-security.pdf"&gt;AWS security&lt;/a&gt;.
I&amp;rsquo;ve found the well-architected framework follows the exam structure (or the other way around) quite well.
It&amp;rsquo;s also generally an interesting document since it communicates the spirit of working with AWS, beyond any certification.&lt;/p&gt;
&lt;p&gt;The key way to prepare however are &lt;strong&gt;practice exams&lt;/strong&gt;.
They give you a good insight on how the exam is structured and doing a lot of those typically reveals recurring themes and things to look out for.
The Pluralsight platform provided a large selection of practice questions to go through.
Next to those, there are also other practice resources out there, though I would be careful to stick to legitimate material.
In this instance as well, I really leveraged LLMs to help me solve questions and get more in-depth details in case of follow-up questions.&lt;/p&gt;
&lt;p&gt;I also looked into finding a proper &lt;strong&gt;book&lt;/strong&gt; as a study guide, e.g. &lt;a href="https://www.amazon.com/Certified-Solutions-Architect-Study-Guide/dp/1119982626"&gt;AWS Certified Solutions Architect Study Guide: Associate SAA-C03 Exam&lt;/a&gt; by Ben Piper.
The reviews for those weren&amp;rsquo;t too good though, so I didn&amp;rsquo;t further look into this one.&lt;/p&gt;
&lt;p&gt;One advantage of leaving a year between my first run through the Pluralsight courses and the second one was that I also had the time to gain more &lt;strong&gt;hands-on experience&lt;/strong&gt; at work on AWS.
Additionally, I also already had some experience with GCP, which I feel was quite transferable to some extent.&lt;/p&gt;
&lt;h2 id="whats-the-exam-like"&gt;What&amp;rsquo;s the exam like?&lt;/h2&gt;
&lt;p&gt;The exam consists of &lt;strong&gt;65 questions&lt;/strong&gt;, of which &lt;strong&gt;50 are scored&lt;/strong&gt; and &lt;strong&gt;15 are unscored&lt;/strong&gt;.
The unscored questions are new test questions which are added.
Obviously, you are not told which question is scored or unscored &amp;hellip;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a possibility to do the exam online or on-site in an exam center.
I chose to do the exam on-site, since that means I didn&amp;rsquo;t need to bother myself to comply with all exam regulations (it&amp;rsquo;s basically handled by the exam center).&lt;/p&gt;
&lt;p&gt;Question types include &lt;strong&gt;multiple choice&lt;/strong&gt; and &lt;strong&gt;multiple response&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The exam covers 4 domains:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;secure architectures&lt;/li&gt;
&lt;li&gt;resilient architectures&lt;/li&gt;
&lt;li&gt;high-performing architectures&lt;/li&gt;
&lt;li&gt;cost-optimized architectures&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Important to know is that each question is linked to one domain and the scores are weighed across the domains.&lt;/p&gt;
&lt;p&gt;The passing score is &lt;strong&gt;720 / 1000&lt;/strong&gt; of the weighted score.&lt;/p&gt;
&lt;h2 id="how-did-i-do-on-the-exam"&gt;How did I do on the exam?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I passed!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Unlike the GCP exam, I now also got a score, being 856 / 1000.&lt;/p&gt;
&lt;p&gt;I think I was overall well prepared, but the main struggle I still found at times that this format of exam is at some point really about guessing the &lt;em&gt;intent&lt;/em&gt; of it.
Depending on said intent, different answers can be valid at the same time.&lt;/p&gt;
&lt;h2 id="practical-advice-for-preparing"&gt;Practical advice for preparing&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Practical experience&lt;/strong&gt; with cloud-based environments helps a great deal.
The fact I already had hands-on experience through my job with GCP and AWS means I already knew most of the technologies.
From my point of view though, I don&amp;rsquo;t really use all of those services.
Additionally, if I do, it&amp;rsquo;s also through some infrastructure-as-code (e.g. Terraform), which is a different premise than what the exam presupposes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding what&amp;rsquo;s on the exam&lt;/strong&gt; also helps, so be sure to e.g. look up the &lt;a href="https://docs.aws.amazon.com/aws-certification/latest/solutions-architect-associate-03/solutions-architect-associate-03.html"&gt;AWS official exam guide&lt;/a&gt;.
The whitepapers, even though interesting, I don&amp;rsquo;t think were really that relevant.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Pluralsight learning paths&lt;/strong&gt; were also interesting to get a good overview on the theoretical part.
Regarding theory, I would say that it&amp;rsquo;s quite important to know some numbers / orders of magnitude.
Good examples of these are the different kinds of block storage, object storage, compute, &amp;hellip; and their related pricing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compared to the GCP exam&lt;/strong&gt;, I did not prepare with a book this time.
I would say that the Pluralsight courses basically took that function this time.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The key ingredient&lt;/em&gt; I would still say though is to do a &lt;strong&gt;lot of practice questions&lt;/strong&gt;.
Generally, learning by heart is discouraged, but my personal feeling is that doing the questions just prepares you well for the format of the exam.
Additionally, after doing a lot of those, you would typically see a lot of recurring themes, which then makes it easier to spot it on the exam.&lt;/p&gt;
&lt;h2 id="conclusions"&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;How do I think about having achieved this certification as well?
Well, overall, I have to admit I feel a bit mixed about it in hindsight.&lt;/p&gt;
&lt;p&gt;There are the clear advantages in that it aids me in my day-to-day job, potentially my longer-term career.
Another reason I started again on this was to prove to myself that I could commit to this and reach a tangible goal.&lt;/p&gt;
&lt;p&gt;After having achieved it though, I wonder if there&amp;rsquo;s that much added value to it.
My experience with GCP is to a large extent transferable to AWS as well, so doing another certification was likely less necessary.
Additionally, I don&amp;rsquo;t really feel it&amp;rsquo;s the main direction I want to evolve into anyway for my career, since I&amp;rsquo;m more interested in application development.&lt;/p&gt;
&lt;p&gt;I do believe in this notion of DevOps &amp;ndash; the original one, where application developers can handle their own infra &amp;ndash; not the one with Ops people reinventing themselves with a new term.
But that topic is perhaps something for a different post &amp;hellip;&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;formerly known as ACloudGuru&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>GCP ACE</title><link>https://mhemeryck.xyz/posts/2024-03-01-gcp_ace/</link><pubDate>Fri, 01 Mar 2024 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2024-03-01-gcp_ace/</guid><description>&lt;h2 id="google-cloud-platform-associate-cloud-engineer-certification"&gt;Google Cloud Platform Associate Cloud Engineer Certification&lt;/h2&gt;
&lt;p&gt;The past few months, I did spend quite some of my leisure time getting my &lt;strong&gt;Google Cloud Platform&lt;/strong&gt; (GCP) certification.&lt;/p&gt;
&lt;p&gt;I have always been interested in understanding infrastructure and doing operational work.
In my current job, we do use GCP quite extensively for our infrastructure needs and consequently I was given the opportunity there to train on GCP.&lt;/p&gt;
&lt;p&gt;One of the approaches of learning for a cloud platform turned out to be registering for &lt;a href="https://cloud.google.com/learn/certification?hl=en#why-get-google-cloud-certified"&gt;GCP certification&lt;/a&gt;.
Certification entails that you take an exam to show the knowledge attained.&lt;/p&gt;</description><content:encoded>&lt;h2 id="google-cloud-platform-associate-cloud-engineer-certification"&gt;Google Cloud Platform Associate Cloud Engineer Certification&lt;/h2&gt;
&lt;p&gt;The past few months, I did spend quite some of my leisure time getting my &lt;strong&gt;Google Cloud Platform&lt;/strong&gt; (GCP) certification.&lt;/p&gt;
&lt;p&gt;I have always been interested in understanding infrastructure and doing operational work.
In my current job, we do use GCP quite extensively for our infrastructure needs and consequently I was given the opportunity there to train on GCP.&lt;/p&gt;
&lt;p&gt;One of the approaches of learning for a cloud platform turned out to be registering for &lt;a href="https://cloud.google.com/learn/certification?hl=en#why-get-google-cloud-certified"&gt;GCP certification&lt;/a&gt;.
Certification entails that you take an exam to show the knowledge attained.&lt;/p&gt;
&lt;p&gt;At first, I was a bit hesitant for the &lt;strong&gt;need of certification&lt;/strong&gt; since I had no intention of really needing to prove my knowledge.
In hindsight, I would say that the certification was a good way to steer my learning of GCP and afterwards as a proof mainly to myself of my level of knowledge.&lt;/p&gt;
&lt;p&gt;GCP provides 3 tiers of certification:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;foundational: the bare minimum, mainly targeted at non-technical levels&lt;/li&gt;
&lt;li&gt;associate: technical level, all-round fundamental skills to work with GCP&lt;/li&gt;
&lt;li&gt;professional: targeted technical functions, such as architects, data analysts etc.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I did skip the foundational level since I felt it is mainly for non-technical roles and went straight for the associate level; &lt;strong&gt;Associate Cloud Engineer&lt;/strong&gt; or ACE.&lt;/p&gt;
&lt;h2 id="how-did-i-prepare"&gt;How did I prepare?&lt;/h2&gt;
&lt;p&gt;The first place to start from really is the official &lt;strong&gt;&lt;a href="https://cloud.google.com/learn/certification/cloud-engineer"&gt;GCP ACE&lt;/a&gt;&lt;/strong&gt; start page.
It clearly lists all the different steps, including what is expected on the exam, where to look for training, links to sample questions up to actually where to practically register and take the exam.&lt;/p&gt;
&lt;p&gt;One of the training options shown on that page is the &lt;a href="https://www.cloudskillsboost.google/"&gt;Cloud Skills Boost&lt;/a&gt; platform.
&lt;strong&gt;Cloud Skills Boost&lt;/strong&gt; is a platform affiliated with Google and provides multiple ways of learning about GCP.
There are theoretical lessons in video format, explaining in practical terms all the different components GCP has to offer, why they&amp;rsquo;re there and how you are supposed to use them.
An important part of the training is not just cramming all there is to know about GCP, but getting a feeling of best practices.
Apart from that, there are the lab sessions, which do give the ability to do actual work in a sandbox GCP environment, purposely set up around a given topic.
Personally, I found this the biggest advantage of the Cloud Skills Boost platform, since it really gives you the possibility to interact with GCP in a meaningful manner.
The platform has multiple of these &lt;em&gt;training sessions&lt;/em&gt; consisting of a combination of lessons and labs.
Training sessions can subsequently be part of one or multiple &lt;em&gt;learning paths&lt;/em&gt;.
I specifically took all training sessions of the GCP ACE learning path as part of my path to certification.&lt;/p&gt;
&lt;p&gt;For me personally, I felt the Cloud Skills Boost platform was excellent for getting a lot of the basics right and getting proper hands-on experience in a multitude of environment through the labs.
However, I did not really feel I did have a proper exhaustive view of what GCP all entails.
To close that gap, I also did read the &lt;strong&gt;&lt;a href="https://www.oreilly.com/library/view/official-google-cloud/9781119564416/"&gt;GCP official study guide&lt;/a&gt; by Dan Sullivan&lt;/strong&gt;.
The book closely follows the outline of the required knowledge for the certification.
It details all of the products and features of GCP to a reasonable level of detail.
Interesting for the exam is that each chapter concludes with a set of example questions (and answers), not necessarily tailored towards the exam, but it does give an interesting way to test your knowledge.
For the sake of the exam, I read through the entire book and did all the practice questions.&lt;/p&gt;
&lt;p&gt;Finally, I also read through a number of blog posts such as this &lt;a href="https://gcloud.devoteam.com/blog/from-zero-to-associate-cloud-engineer-a-complete-guide/"&gt;blog post from devoteam&lt;/a&gt; or this &lt;a href="https://medium.com/gowthamlabs/google-cloud-associate-engineer-notes-gcp-ace-8d2260cb3882"&gt;blog post containing exam notes&lt;/a&gt; essentially summarizing a bit of the same information I have here myself.
Additionally, I also did take the &lt;strong&gt;example exam online&lt;/strong&gt;.
The intent of the actual exam is not to learn each individual service or feature of GCP by heart.
However, in some cases, certain practice exam questions really pointed out some specific kinds of questions which seemed to come back a lot of the time, which prompted me to review certain aspects.&lt;/p&gt;
&lt;h2 id="whats-the-exam-like"&gt;What&amp;rsquo;s the exam like?&lt;/h2&gt;
&lt;p&gt;The exam I took consisted of &lt;strong&gt;50 questions&lt;/strong&gt;.
Each question is a &lt;strong&gt;multiple choice&lt;/strong&gt; question, with each &lt;strong&gt;4 possible answers&lt;/strong&gt;.
Only &lt;strong&gt;1 answer&lt;/strong&gt; is deemed the correct answer.
During the taking of the test, it is possible to mark a question to review later on.&lt;/p&gt;
&lt;p&gt;A time limit of &lt;strong&gt;2 hours&lt;/strong&gt; is given; I think I did a first pass through all questions in about 1 hour, which gave me enough time to review all my questions a couple of times.&lt;/p&gt;
&lt;p&gt;The exam can be taken both online or in an exam center.
I did opt for an exam in a center, just to avoid any potential technical issues from my (linux-based) computer.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;passing score is 70%&lt;/strong&gt; of all questions, i.e. 35 correct answers.&lt;/p&gt;
&lt;h2 id="how-did-i-do-on-the-exam"&gt;How did I do on the exam?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I passed!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I did feel the exam was hard enough though.
In some cases, despite all preparation I still felt I was questioned about certain GCP features I had not learned about before.&lt;/p&gt;
&lt;p&gt;I cannot tell how well I really did, since the exam results are only presented in a single pass-or-fail score.&lt;/p&gt;
&lt;h2 id="some-generic-tips"&gt;Some generic tips&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Learning-by-doing really helps&lt;/strong&gt; and is also considered essential by Google themselves.
Apart from my day-to-day experience in my current job, the Cloud Skills Boost labs were a really great way of attaining this.&lt;/p&gt;
&lt;p&gt;Even though &amp;ldquo;cramming&amp;rdquo; for the exam is discouraged, I think it is possible to some extent to focus on that as well.
The &lt;strong&gt;official study guide&lt;/strong&gt; gives in that sense a much more exhaustive overview &amp;ndash; be sure though to use an up-to-date version.
The &lt;strong&gt;practice exam&lt;/strong&gt; also pointed to some knowledge gaps in my case.&lt;/p&gt;
&lt;p&gt;Be aware that you &lt;strong&gt;cannot&lt;/strong&gt; and also &lt;strong&gt;should not know everything&lt;/strong&gt;.
The point is more to understand and pick up on the logical structure built into GCP allowing you to reason about specific issues.
Example: you cannot know every possible &lt;code&gt;gcloud&lt;/code&gt; command, but there is usually some logical structure to it.&lt;/p&gt;
&lt;p&gt;Understand the &lt;strong&gt;intent&lt;/strong&gt; and &lt;strong&gt;use cases&lt;/strong&gt; of each GCP service and focus on &lt;strong&gt;best practices&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="conclusions"&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Starting my training somewhere beginning of December 2023, I did take my exam on February 22 2024.
In less than a week, I did get my &lt;a href="https://google.accredible.com/87b138dc-8f0a-4d50-8d7a-583c933148f2"&gt;online certificate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I would not say that I have become a full expert on GCP, but I also don&amp;rsquo;t believe that is really the intent.
I certainly would say I feel more confident in using GCP and devops works in general since it helped me to get a more exhaustive overview.&lt;/p&gt;</content:encoded></item><item><title>Revamping the blog</title><link>https://mhemeryck.xyz/posts/2024-02-09-migrating_to_hugo/</link><pubDate>Thu, 29 Feb 2024 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2024-02-09-migrating_to_hugo/</guid><description>&lt;h2 id="why"&gt;Why&lt;/h2&gt;
&lt;p&gt;It has been a long time since I have dedicated some time to posting something on this blog.
Over the past years, I think I have spent quite some of my spare time examining things, coding, etc..
The main reason I would want to write again is that I believe it should help me scope things a bit more.&lt;/p&gt;
&lt;p&gt;I have had this idea already a couple of times before, but the whole blogging setup had become a bit of a nuisance to me.&lt;/p&gt;</description><content:encoded>&lt;h2 id="why"&gt;Why&lt;/h2&gt;
&lt;p&gt;It has been a long time since I have dedicated some time to posting something on this blog.
Over the past years, I think I have spent quite some of my spare time examining things, coding, etc..
The main reason I would want to write again is that I believe it should help me scope things a bit more.&lt;/p&gt;
&lt;p&gt;I have had this idea already a couple of times before, but the whole blogging setup had become a bit of a nuisance to me.&lt;/p&gt;
&lt;p&gt;Up to this post, I had been using &lt;a href="https://jekyllrb.com/"&gt;jekyll&lt;/a&gt;, a ruby-based blogging framework, which has been the standard framework used by &lt;a href="https://pages.github.com/"&gt;github pages&lt;/a&gt;.
Since github pages directly deploys the generated content after a merge to the main branch, I figured a way to preview the rendered result is quite important to me.
This was perfectly possible before, using a dockerized / docker compose based setup, see e.g. this link with the old &lt;a href="https://github.com/mhemeryck/mhemeryck.github.io/blob/e0977dac2425b40b42ba23d799e28f0f41469a9d/docker-compose.yml"&gt;docker compose setup&lt;/a&gt;.
However, for some reason &amp;ndash; probably related to a newer version of jekyll (v4), I could no longer reliably preview the generated site content.&lt;/p&gt;
&lt;p&gt;I did attempt a couple of ways to get a proper jekyll setup again:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;updating the compose setup&lt;/li&gt;
&lt;li&gt;installing ruby through &lt;a href="https://rvm.io/"&gt;&lt;code&gt;rvm&lt;/code&gt;&lt;/a&gt; &amp;ndash; the ruby version manager and installing jekyll and related dependencies&lt;/li&gt;
&lt;li&gt;using a &lt;a href="https://nixos.org/"&gt;nix&lt;/a&gt; based approach, through the a specific nix &amp;ldquo;flake&amp;rdquo; &amp;ndash; &lt;code&gt;rubyPackages.github-pages&lt;/code&gt; looked promising&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these approaches really worked out for me.
I suspect it might have been related to the theme I was using, the &lt;a href="https://beautifuljekyll.com/"&gt;beautiful jekyll theme&lt;/a&gt; in combination by trying to use a &amp;ldquo;remote theme&amp;rdquo; &amp;hellip;
Since I am not really an expert in ruby and since overall the intent is to write content and not focus on the blogging engine, I figured to look for something simpler.&lt;/p&gt;
&lt;p&gt;Enter &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="hugo"&gt;Hugo&lt;/h2&gt;
&lt;p&gt;Hugo is a similar blogging engine to jekyll in that it takes markdown content and using a theme, it generates static websites.
Since it is also based on markdown content, migrating my existing blog content would also be fairly to do.
The blogging engine itself is written in go, which seemed already simpler for me to install and maintain.
The actual process of building a website generally is also faster, which makes for faster feedback loops.&lt;/p&gt;
&lt;p&gt;Overall, the following where some things I did need to go through while migrating everything.&lt;/p&gt;
&lt;h3 id="theme"&gt;Theme&lt;/h3&gt;
&lt;p&gt;Hugo doesn&amp;rsquo;t seem to come with a true built-in theme.
There are quite a lot of websites which do list some free themes, such as &lt;a href="https://themes.gohugo.io/"&gt;hugo themes&lt;/a&gt; and it is even possible to write your own.
I did not want to bother that much with the theme itself, so I just went for &lt;a href="https://github.com/halogenica/beautifulhugo"&gt;beautiful hugo&lt;/a&gt; &amp;ndash; which is in the end just a port of &lt;a href="https://beautifuljekyll.com/"&gt;beautiful jekyll theme&lt;/a&gt;.
The pretty header images I had collected for each of my posts were not supported by this theme though, so these sadly had to go.&lt;/p&gt;
&lt;h3 id="preamble-format"&gt;Preamble format&lt;/h3&gt;
&lt;p&gt;Each jekyll post had a YAML-like preamble containing some metadata.
Since the default in hugo seems to be to use a TOML-based preamble and since not all keys were still supported (e.g. the header image), I did reformat all of those.
In hindsight, I did find out YAML might actually also supported, but TOML seemed to be the default way of doing things anyway.&lt;/p&gt;
&lt;h3 id="syntax"&gt;Syntax&lt;/h3&gt;
&lt;p&gt;Even though the main format of the posts is written in markdown, some &amp;ldquo;meta&amp;rdquo; tags, like linking to other posts is obviously different between the two frameworks.
That also thus needed to be updated.&lt;/p&gt;
&lt;h3 id="dead-links"&gt;Dead links&lt;/h3&gt;
&lt;p&gt;Considering my &lt;a href="https://mhemeryck.xyz/posts/2021-12-16-unifi_terraform/"&gt;last post&lt;/a&gt; had already been from 2021, there were bound to be some dead links in the posts.
Based on some CI checks (more on that later), I cleaned those where possible.&lt;/p&gt;
&lt;h3 id="social-links"&gt;Social links&lt;/h3&gt;
&lt;p&gt;The new theme had some support for more social links, so I did update those as well, linking e.g. to my mastodon page and strava.&lt;/p&gt;
&lt;h3 id="deployment-itself"&gt;Deployment itself&lt;/h3&gt;
&lt;p&gt;Since github pages by default still uses jekyll, I did need to change the deployment setup.
Fortunately, the &amp;ldquo;new way&amp;rdquo; of doing things for github pages nowadays seems to integrate quite easily with &lt;a href="https://github.com/features/actions"&gt;github actions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Speaking of github actions, I also did take some time to update those&lt;/p&gt;
&lt;h4 id="runners"&gt;runners&lt;/h4&gt;
&lt;p&gt;All runners, which were still using some older ubuntu version just got updated.&lt;/p&gt;
&lt;h4 id="linter-from-prettier-to-dprint"&gt;linter: from prettier to dprint&lt;/h4&gt;
&lt;p&gt;Before, I was using &lt;a href="https://prettier.io/"&gt;&lt;code&gt;prettier&lt;/code&gt;&lt;/a&gt; to format my markdown files locally and them check in the CI if they were formatted correctly.
With the proliferation of a lot of rust-based CLI tools, I also switched that one over to &lt;a href="https://dprint.dev/"&gt;&lt;code&gt;dprint&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="pyspelling"&gt;pyspelling&lt;/h4&gt;
&lt;p&gt;For &lt;a href="https://facelessuser.github.io/pyspelling/"&gt;pyspelling&lt;/a&gt;, a CLI spellchecker, I just did update the setup.&lt;/p&gt;
&lt;h4 id="dead-link-check"&gt;Dead link check&lt;/h4&gt;
&lt;p&gt;As mentioned before, I did check the dead links through the &lt;a href="https://github.com/tcort/markdown-link-check"&gt;markdown-link-check&lt;/a&gt; and overall, they still seemed quite up to date.
I did update some of those where possible.
I no longer made it a mandatory part of the CI though since it seemed quite brittle.
Instead, it should now run on a schedule.&lt;/p&gt;
&lt;h4 id="hugo-deploy-itself"&gt;Hugo deploy itself&lt;/h4&gt;
&lt;p&gt;This was the easiest part actually: the github actions marketplace just suggested the workflow for me and it ran without issues on the first merge to the main branch!&lt;/p&gt;
&lt;p&gt;For more details, check the &lt;a href="https://github.com/mhemeryck/mhemeryck.github.io/tree/33b938480e69477431faff0fb5a292b797aae5ac/.github/workflows"&gt;github repo actions folder&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="conclusions"&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Hugo is overall a lot simpler to me than my earlier setup with jekyll and it&amp;rsquo;s blazing fast.
I might even consider moving more things here, like my &lt;a href="https://cv.mhemeryck.com/"&gt;CV&lt;/a&gt;, since GitHub then publishes it for me.&lt;/p&gt;
&lt;p&gt;With this out of the way, let&amp;rsquo;s do some actual blogging!&lt;/p&gt;</content:encoded></item><item><title>unifi terraform</title><link>https://mhemeryck.xyz/posts/2021-12-16-unifi_terraform/</link><pubDate>Thu, 16 Dec 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-12-16-unifi_terraform/</guid><description>&lt;p&gt;Last week, I did write about &lt;a href="https://mhemeryck.xyz/posts/2021-12-08-cloudkey_reset/"&gt;resetting my unifi cloud key&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main reason I did gain a renewed interest in the cloud key was because I wanted to add some more fixed IPs to my home network.
One of the nice things about the unifi controller software is that it provides a single dashboard interface to manage everything network-related.&lt;/p&gt;
&lt;p&gt;On the downside, the interface is overall quite limited.
While it does offer DHCP (the fixed IPs) in this fashion, it does not offer built-in DNS (e.g. for naming dedicated hosts on the home network).&lt;/p&gt;</description><content:encoded>&lt;p&gt;Last week, I did write about &lt;a href="https://mhemeryck.xyz/posts/2021-12-08-cloudkey_reset/"&gt;resetting my unifi cloud key&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main reason I did gain a renewed interest in the cloud key was because I wanted to add some more fixed IPs to my home network.
One of the nice things about the unifi controller software is that it provides a single dashboard interface to manage everything network-related.&lt;/p&gt;
&lt;p&gt;On the downside, the interface is overall quite limited.
While it does offer DHCP (the fixed IPs) in this fashion, it does not offer built-in DNS (e.g. for naming dedicated hosts on the home network).&lt;/p&gt;
&lt;p&gt;Additionally, I also don&amp;rsquo;t really like these point-and-click interfaces which are hard to put under version control except for a full snapshot.
Coming from a software development background and with the current infrastructure-as-code movement, I wanted to look into an alternative.&lt;/p&gt;
&lt;h2 id="infrastructure-as-code-and-hashicorp-terraform"&gt;infrastructure-as-code and HashiCorp terraform&lt;/h2&gt;
&lt;p&gt;Nowadays, if you want to run some kind of software service, all the major cloud providers do provide web APIs to manage the (virtual) infrastructure for you.
Managing infrastructure at this point becomes another &lt;strong&gt;software problem&lt;/strong&gt; for interacting with these APIs, thus the term infrastructure-as-code.&lt;/p&gt;
&lt;p&gt;One platform that has become increasingly popular in that regard has been HashiCorp&amp;rsquo;s &lt;strong&gt;&lt;a href="https://www.terraform.io/"&gt;terraform&lt;/a&gt;&lt;/strong&gt;.
It provides an abstraction layer for all of these different cloud provider and offers an ecosystem for easily adding more modules.&lt;/p&gt;
&lt;p&gt;Additionally, the format is &lt;strong&gt;declarative&lt;/strong&gt;, meaning that the tool will manage the life cycle of the components you describe.
As an example, adding a resource means just declaring it in the configuration file and running it through the &lt;code&gt;terraform&lt;/code&gt; CLI tool.
At that point, the resource becomes managed by terraform.
Destroying the resource is done by removing the entry from the configuration file and once again applying.
Essentially, the configuration file will always reflect the state of the resources being managed and vice versa.&lt;/p&gt;
&lt;h2 id="unifi-terraform"&gt;unifi-terraform&lt;/h2&gt;
&lt;p&gt;What does any of that have to do with my tiny homelab setup?
Enter &lt;strong&gt;&lt;a href="https://registry.terraform.io/providers/paultyng/unifi/latest/docs"&gt;unifi-terraform&lt;/a&gt;&lt;/strong&gt;, which is a community provided terraform module that interacts with the API of the unifi cloud key.
This way, I could manage all the required fixed IPs.&lt;/p&gt;
&lt;p&gt;As I mentioned in my previous post, I did have some trouble getting started though, so let&amp;rsquo;s walk a bit through the steps I had taken.&lt;/p&gt;
&lt;h3 id="terraform-configuration-file"&gt;terraform configuration file&lt;/h3&gt;
&lt;p&gt;The first step is creating a &lt;code&gt;main.tf&lt;/code&gt; file containing the actual configuration.
I&amp;rsquo;m not going to elaborate too much on this here, see the &lt;a href="https://www.terraform.io/docs"&gt;terraform docs&lt;/a&gt; for that.&lt;/p&gt;
&lt;p&gt;Generally, my configuration file looked like this (redacted).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;terraform {
required_providers {
unifi = {
source = &amp;#34;paultyng/unifi&amp;#34;
version = &amp;#34;0.34.0&amp;#34;
}
}
}
provider &amp;#34;unifi&amp;#34; {
username = var.username # These are in a variables file
password = var.password
api_url = var.api_url
}
resource &amp;#34;unifi_site&amp;#34; &amp;#34;sitename&amp;#34; {
description = &amp;#34;Default&amp;#34;
}
resource &amp;#34;unifi_user_group&amp;#34; &amp;#34;default&amp;#34; {
name = &amp;#34;Default&amp;#34;
}
# the network / DHCP config
resource &amp;#34;unifi_network&amp;#34; &amp;#34;lan&amp;#34; {
name = &amp;#34;LAN&amp;#34;
purpose = &amp;#34;corporate&amp;#34;
dhcp_dns = [
&amp;#34;192.168.1.21&amp;#34;,
&amp;#34;1.1.1.1&amp;#34;,
&amp;#34;8.8.8.8&amp;#34;,
&amp;#34;9.9.9.9&amp;#34;,
]
dhcp_enabled = true
dhcp_start = &amp;#34;192.168.1.20&amp;#34;
dhcp_stop = &amp;#34;192.168.1.254&amp;#34;
domain_name = &amp;#34;lan&amp;#34;
igmp_snooping = true
subnet = &amp;#34;192.168.1.0/24&amp;#34;
}
# WLAN
resource &amp;#34;unifi_wlan&amp;#34; &amp;#34;wlan&amp;#34; {
name = &amp;#34;wlan&amp;#34;
security = &amp;#34;wpapsk&amp;#34;
passphrase = var.passphrase_wifi_wlan
network_id = unifi_network.lan.id
user_group_id = unifi_user_group.default.id
wlan_band = &amp;#34;5g&amp;#34;
multicast_enhance = true # chromecast
# default group ID
ap_group_ids = [
var.ap_group_id
]
# enable WPA2/WPA3 support
wpa3_support = true
wpa3_transition = true
pmf_mode = &amp;#34;optional&amp;#34;
}
...
# Devices
resource &amp;#34;unifi_device&amp;#34; &amp;#34;my-router&amp;#34; {
name = &amp;#34;my-router&amp;#34;
}
...
# Users: network clients
resource &amp;#34;unifi_user&amp;#34; &amp;#34;my_client&amp;#34; {
name = &amp;#34;my-client&amp;#34;
mac = &amp;#34;d9:6e:3d:12:8f:53&amp;#34;
fixed_ip = &amp;#34;192.168.1.10&amp;#34;
network_id = unifi_network.lan.id
}
...
resource &amp;#34;unifi_user&amp;#34; &amp;#34;floating_client&amp;#34; {
name = &amp;#34;floating&amp;#34;
mac = &amp;#34;bc:e4:26:db:df:e4&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At the top of this file the &lt;code&gt;required_provider&lt;/code&gt; is mentioned, i.e. the module that contains the client code to talk to the unifi cloud key API.
The &lt;code&gt;provider&lt;/code&gt; block contains the data used for the provider to do the actual connection (essentially username / password and a URL to connect to).&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a couple of interesting resources in there:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unifi_site&lt;/code&gt;: just the physical location &amp;ndash; I guess this makes more sense if you have multiple locations to manage&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_user_group&lt;/code&gt;: this is relevant if you want to separate out specific access for e.g. WiFi clients.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_network&lt;/code&gt;: main (wired) network parameters, including e.g. the DHCP settings&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_wlan&lt;/code&gt;: wireless network configuration. Helpful if you want push in passwords&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_device&lt;/code&gt;: the unifi hardware such as switches, routers and wireless access points. I actually don&amp;rsquo;t have anything in particular to manage here, but I guess if you want to have specific port profiles for a switch, this would be the way to link to it&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_user&lt;/code&gt;: the clients on the network. It&amp;rsquo;s at this point that I can actually fix the IPs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have a look at &lt;a href="https://registry.terraform.io/providers/paultyng/unifi/latest/docs"&gt;unifi-terraform&lt;/a&gt; for more resources that can be managed.&lt;/p&gt;
&lt;h3 id="terraform-import"&gt;terraform import&lt;/h3&gt;
&lt;p&gt;At this point, you could in theory start creating all of the resources by applying it (with &lt;code&gt;terraform apply&lt;/code&gt;).
In reality, these resources are likely already recognized and managed by the cloud key itself.
The way around that is by &lt;strong&gt;importing&lt;/strong&gt; the resources via &lt;code&gt;terraform import {resource_name} {resource_id}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;{resource_name}&lt;/code&gt; is just the name as you did provide it in the main configuration file, e.g. &lt;code&gt;unifi_user.my_client&lt;/code&gt;
The &lt;code&gt;{resource_id}&lt;/code&gt; is an ID that&amp;rsquo;s used in the API of the cloud key.
This was for me the point for which it all broke down since I couldn&amp;rsquo;t find any good documentation of this API.&lt;/p&gt;
&lt;h3 id="cloud-key-api"&gt;cloud key API&lt;/h3&gt;
&lt;p&gt;Fortunately, I did find some pointer into the cloud key API in a &lt;a href="https://thenewstack.io/how-to-manage-a-home-network-with-infrastructure-as-code/"&gt;blog post from the original module author&lt;/a&gt;.
Another real help to me has been this &lt;a href="https://github.com/Art-of-WiFi/UniFi-API-client/blob/fbfd6a824628d2d45f7b5dadb211cb1191335156/src/Client.php"&gt;unifi PHP client code&lt;/a&gt; which also lists most of the endpoints.&lt;/p&gt;
&lt;p&gt;I did distill all my findings into this &lt;a href="https://mhemeryck.xyz/2021-12-16/cloudkey.postman_collection.json"&gt;cloud key postman collection&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s walk through some of the API calls, built using &lt;a href="https://httpie.io/"&gt;&lt;code&gt;httpie&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="login-post-hostapilogin"&gt;login: &lt;code&gt;POST {host}/api/login&lt;/code&gt;&lt;/h4&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;printf &lt;span style="color:#e6db74"&gt;&amp;#39;{
&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;username&amp;#34;: &amp;#34;foo&amp;#34;,
&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;password&amp;#34;: &amp;#34;bar&amp;#34;
&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;#39;&lt;/span&gt;| http --follow --timeout &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt; POST &lt;span style="color:#e6db74"&gt;&amp;#39;https://cloudkey.lan:8443/api/login&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Content-Type:&lt;span style="color:#e6db74"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cookie:&lt;span style="color:#e6db74"&gt;&amp;#39;csrf_token=abc; unifises=dfcwZvHcpBwc5LTlOzgxYSlsutpC6F2b&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTTP/1.1 &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access-Control-Allow-Credentials: true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection: keep-alive
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Content-Length: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Content-Type: application/json;charset&lt;span style="color:#f92672"&gt;=&lt;/span&gt;UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Date: Thu, &lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; Dec &lt;span style="color:#ae81ff"&gt;2021&lt;/span&gt; 20:55:24 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive: timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Set-Cookie: unifises&lt;span style="color:#f92672"&gt;=&lt;/span&gt;19uFvBLta4optBuzwV4QyVip5TbdR0t6; Path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/; Secure; HttpOnly
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Set-Cookie: csrf_token&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Z1dK47sp8NsjfpdbgHNyVNgYewNYIodd; Path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/; Secure
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;X-Frame-Options: DENY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vary: Origin
&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;{&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;data&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;[]&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;meta&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&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;rc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ok&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This call will perform the login on the login endpoint and set a cookie for subsequent calls.
Note that all other calls will need this cookie and that it will expire.&lt;/p&gt;
&lt;h4 id="self-get-hostapissiteself"&gt;self: &lt;code&gt;GET {host}/api/s/{site}/self&lt;/code&gt;&lt;/h4&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;http --follow --timeout &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt; GET &lt;span style="color:#e6db74"&gt;&amp;#39;https://cloudkey.lan:8443/api/s/default/self&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cookie:&lt;span style="color:#e6db74"&gt;&amp;#39;csrf_token=cNHfNcA1zQoMW9wzFvCmGwEEo22irJ6D; unifises=dfcwZvHcpBwc5LTlOzgxYSlsutpC6F2b&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTTP/1.1 &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access-Control-Allow-Credentials: true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection: keep-alive
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Content-Encoding: gzip
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Content-Type: application/json;charset&lt;span style="color:#f92672"&gt;=&lt;/span&gt;UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Date: Thu, &lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; Dec &lt;span style="color:#ae81ff"&gt;2021&lt;/span&gt; 20:58:28 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive: timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer-Encoding: chunked
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;X-Frame-Options: DENY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vary: accept-encoding,origin,accept-encoding
&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;{&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;data&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&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;{&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:#e6db74"&gt;&amp;#34;site_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;{site_id}&amp;#34;&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;site_name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;default&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 style="color:#f92672"&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;]&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;meta&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&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;rc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ok&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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This just lists some generic information about the site (location), e.g. the &lt;code&gt;site_id&lt;/code&gt; to be imported.&lt;/p&gt;
&lt;h4 id="devices-get-hostapissitestatdevice"&gt;devices: &lt;code&gt;GET {host}/api/s/{site}/stat/device&lt;/code&gt;&lt;/h4&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;http --follow --timeout &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt; GET &lt;span style="color:#e6db74"&gt;&amp;#39;https://cloudkey.lan:8443/api/s/default/stat/device&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cookie:&lt;span style="color:#e6db74"&gt;&amp;#39;csrf_token=cNHfNcA1zQoMW9wzFvCmGwEEo22irJ6D; unifises=dfcwZvHcpBwc5LTlOzgxYSlsutpC6F2b&amp;#39;&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;{&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;meta&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&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;rc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ok&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;}&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;data&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&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;{&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;_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;5ea4395234309a00041ffa6e&amp;#34;&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;adopted&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;anon_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;d5416fde-c2ad-4558-9aa0-4b4024c441de&amp;#34;&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;antenna_table&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&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;{&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;default&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: 4,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Combined&amp;#34;&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;wifi0_gain&amp;#34;&lt;/span&gt;: 3,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;wifi1_gain&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;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;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&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:#f92672"&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;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This endpoint is interesting to get the IDs of the devices to import.&lt;/p&gt;
&lt;h4 id="users-get-hostapissiterestuser"&gt;users: &lt;code&gt;GET {host}/api/s/{site}/rest/user&lt;/code&gt;&lt;/h4&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;http --follow --timeout &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt; GET &lt;span style="color:#e6db74"&gt;&amp;#39;https://cloudkey.lan:8443/api/s/default/rest/user&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cookie:&lt;span style="color:#e6db74"&gt;&amp;#39;csrf_token=cNHfNcA1zQoMW9wzFvCmGwEEo22irJ6D; unifises=dfcwZvHcpBwc5LTlOzgxYSlsutpC6F2b&amp;#39;&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:#f92672"&gt;{&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;meta&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&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;rc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ok&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;}&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;data&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&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;{&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;_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;9dfeee213c227000f47aade2&amp;#34;&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;mac&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;48:98:e3:f6:21:a5&amp;#34;&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;site_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;5dffdda64c2370010eb91fa0&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 style="color:#f92672"&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;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This was the most important endpoint for my purposes: it lists the resource ID under &lt;code&gt;_id&lt;/code&gt; and makes it possible to import it.&lt;/p&gt;
&lt;p&gt;Practically, this means&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;terraform import unifi_user.my_client 9dfeee213c227000f47aade2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, the client should be managed by terraform.&lt;/p&gt;
&lt;p&gt;Refer to the &lt;a href="https://mhemeryck.xyz/2021-12-16/cloudkey.postman_collection.json"&gt;cloud key postman collection&lt;/a&gt; for more resources.
Note that I only really looked into the read-only endpoints (the &lt;code&gt;GET&lt;/code&gt; operations) since I would have terraform do the actual operations (&lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;I was really pleased to see that after some crawling of the cloud key API I was able to get the unifi resources managed with terraform.
At this point, I can &lt;strong&gt;manage my unifi configuration under source control&lt;/strong&gt;, most notably the DHCP fixed IPs and some firewall rules.
This only scratches the surface of what is now possible at this point though.&lt;/p&gt;
&lt;p&gt;Since terraform also has modules for various &lt;strong&gt;DNS providers&lt;/strong&gt; such as &lt;a href="https://registry.terraform.io/providers/pan-net/powerdns/latest/docs"&gt;powerdns&lt;/a&gt;, &lt;a href="https://github.com/shelmangroup/terraform-provider-coredns"&gt;coredns&lt;/a&gt; and even &lt;a href="https://registry.terraform.io/providers/ryanwholey/pihole/latest/docs"&gt;pihole&lt;/a&gt; I could start thinking of managing the fixed IPs and the host names from a single spot.
The same local network DNS could then be used by my local kubernetes to register service names into.&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s supposedly infrastructure-as-code, I could also start thinking of integrating everything in &lt;strong&gt;CI/CD&lt;/strong&gt; flow, where the terraform code is first validated and then automatically applied.
This in turn would mean I&amp;rsquo;d need to find a way to either self-host a source control / CI/CD platform (thinking of &lt;a href="https://about.gitlab.com/"&gt;gitlab&lt;/a&gt;) or find some way to safely tunnel out to a cloud-based platform with (thinking github actions + &lt;a href="https://www.wireguard.com/"&gt;wireguard&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Another change I&amp;rsquo;d like to make, is to use a &lt;strong&gt;&lt;a href="https://www.terraform.io/language/state/remote-state-data"&gt;remote state back-end&lt;/a&gt;&lt;/strong&gt; instead of the local file storage.&lt;/p&gt;
&lt;p&gt;&amp;hellip; but that&amp;rsquo;s for another time!&lt;/p&gt;</content:encoded></item><item><title>cloud key reset</title><link>https://mhemeryck.xyz/posts/2021-12-08-cloudkey_reset/</link><pubDate>Wed, 08 Dec 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-12-08-cloudkey_reset/</guid><description>&lt;p&gt;After my &lt;a href="https://mhemeryck.xyz/posts/2021-12-01-password_fsckup/"&gt;last password issues post&lt;/a&gt;, I still had some places for which I didn&amp;rsquo;t have an easy approach to reset the login.
For my local network, I still use a unifi cloud key gen 1, to keep an overview of the network.&lt;/p&gt;
&lt;h2 id="cloud-key"&gt;Cloud key&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://unifi-protect.ui.com/cloud-key-gen2"&gt;ubiquiti unifi cloud key&lt;/a&gt;&lt;/strong&gt; is a small client device on the local network, specifically running the ubiquiti unifi controller software.
The controller software is there to e.g. provision new (ubiquiti) hardware, to configure the network (switch ports, VLANs, firewalls, &amp;hellip;) but also to allow remote management from a web interface or phone app.&lt;/p&gt;</description><content:encoded>&lt;p&gt;After my &lt;a href="https://mhemeryck.xyz/posts/2021-12-01-password_fsckup/"&gt;last password issues post&lt;/a&gt;, I still had some places for which I didn&amp;rsquo;t have an easy approach to reset the login.
For my local network, I still use a unifi cloud key gen 1, to keep an overview of the network.&lt;/p&gt;
&lt;h2 id="cloud-key"&gt;Cloud key&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://unifi-protect.ui.com/cloud-key-gen2"&gt;ubiquiti unifi cloud key&lt;/a&gt;&lt;/strong&gt; is a small client device on the local network, specifically running the ubiquiti unifi controller software.
The controller software is there to e.g. provision new (ubiquiti) hardware, to configure the network (switch ports, VLANs, firewalls, &amp;hellip;) but also to allow remote management from a web interface or phone app.&lt;/p&gt;
&lt;p&gt;Note that you actually &lt;strong&gt;don&amp;rsquo;t need dedicated hardware&lt;/strong&gt; to run the controller software.
You can just as well run the software from your own local computer or instead use the &lt;a href="https://hub.docker.com/r/linuxserver/unifi-controller"&gt;dockerized controller software&lt;/a&gt;.
Having it on a dedicated device though means that it is always available, doing logging and monitoring in the background and providing an entry point for remote access.
Additionally, the cloud key does indeed mainly run the controller software, but it also has onboard firmware to set manage the controller software for you.&lt;/p&gt;
&lt;p&gt;On the upside, I think the &lt;em&gt;unifi experience&lt;/em&gt; is overall quite nice and graphically slick.
Despite being considered a &lt;em&gt;prosumer&lt;/em&gt; kind of brand, some features I would consider as basic are missing though (built-in DHCP / DNS, VPN termination, &amp;hellip;)&lt;/p&gt;
&lt;p&gt;In the end, I still like the unifi products since they do have somewhat more enhanced features yet mostly &lt;strong&gt;&amp;ldquo;just work&amp;rdquo;&lt;/strong&gt;.
There was a time when I was running my own &lt;a href="https://openwrt.org/"&gt;openWrt&lt;/a&gt;-enabled router, but the reality just is that after the initial setup, this is just something I barely feel the need to touch (and maintain).&lt;/p&gt;
&lt;h2 id="factory-reset"&gt;Factory reset&lt;/h2&gt;
&lt;p&gt;As mentioned above, the cloud key hardware runs both the main unifi controller software as well as its own firmware to manage said software.
The login for the controller software is done via my &lt;a href="https://ui.com/"&gt;ui.com&lt;/a&gt; account.
Retrieving that one was as simple as resetting this account.&lt;/p&gt;
&lt;p&gt;The controller &lt;strong&gt;firmware management&lt;/strong&gt; however is also password-protected and being a good citizen, I obviously changed it from its default of &lt;code&gt;ubnt&lt;/code&gt; / &lt;code&gt;ubnt&lt;/code&gt;.
Even though I still had access to the controller software, I figured it&amp;rsquo;d be better to have full access to the firmware management as well.
The rationale is that the times when you often need this is in case of some breaking issue &amp;ndash; in which event you want to have immediate access.&lt;/p&gt;
&lt;p&gt;The cloud key has an &lt;strong&gt;SD-card&lt;/strong&gt;, so I figured that it would run from there (pretty much how a raspberry pi does).
After checking the SD card contents though, they only seemed to contain the regularly scheduled backups.&lt;/p&gt;
&lt;p&gt;Checking the &lt;a href="https://dl.ubnt.com/guides/UniFi/UniFi_Cloud_Key_UC-CK_QSG.pdf"&gt;cloud key gen1 pdf docs&lt;/a&gt; showed me that there is simple &lt;strong&gt;factory reset button&lt;/strong&gt;.
After grabbing a backup snapshot of the controller software, I did decide on just triggering the factory reset.&lt;/p&gt;
&lt;p&gt;Sure enough, the firmware management did now work with the default credentials (prompting me to change it straight after that).&lt;/p&gt;
&lt;h2 id="lets-encrypt-certificates-setup"&gt;Let&amp;rsquo;s Encrypt certificates setup&lt;/h2&gt;
&lt;p&gt;I want to be able to also access the cloud key on a regular URL even if it&amp;rsquo;s only from my local network.
Also, by default the cloud key controller software will use an HTTPS version (which is good), but it obviously has no built-in way to provide a valid certificate (not so good).&lt;/p&gt;
&lt;p&gt;A side-effect of this factory reset was that I had now lost this setup in where the cloud key itself regularly pulls in an updates Let&amp;rsquo;s Encrypt SSL certificate.
In the past, I had followed this nicely detailed blog post on &lt;a href="https://www.jamesridgway.co.uk/auto-renewing-ssl-certificate-unifi-cloud-key-lets-encrypt-cloudflare-dns-validation/"&gt;cloud key SSL certificates with Let&amp;rsquo;s Encrypt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To summarize the steps for myself for future reference:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fixed IP for the cloud key: this actually easy to do from the unifi controller software (running on the cloud key)&lt;/li&gt;
&lt;li&gt;DNS entry for this fixed IP: I use &lt;a href="https://pi-hole.net/"&gt;pihole&lt;/a&gt;&amp;rsquo;s built-in &lt;a href="https://thekelleys.org.uk/dnsmasq/doc.html"&gt;dnsmasq&lt;/a&gt; for this. This means that all devices on my local network will be able to resolve the host for the cloud key&lt;/li&gt;
&lt;li&gt;Set up a job on the cloud key to regularly pull in updated SSL certificates&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="acmesh-lets-encrypt-cloud-key-bot-setup"&gt;&lt;code&gt;acme.sh&lt;/code&gt; Let&amp;rsquo;s Encrypt cloud key bot setup&lt;/h3&gt;
&lt;p&gt;The way &lt;strong&gt;Let&amp;rsquo;s Encrypt works&lt;/strong&gt; is that you can request a certificate for your own domain name.
Afterwards, the Let&amp;rsquo;s Encrypt server will verify with you whether or not you actually own the domain for which you did request the SSL certificate.
This can be done in a multitude of ways, but the most common ones are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTTP-based: on the service you expose, you host some kind of secret that was given to you before by Let&amp;rsquo;s Encrypt&lt;/li&gt;
&lt;li&gt;DNS-based: on the DNS provider on which you host the domain, you provide an &lt;code&gt;txt&lt;/code&gt; kind of record which Let&amp;rsquo;s Encrypt, again containing the secret Let&amp;rsquo;s Encrypt had provided before&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For my home network case, I am obliged to use the DNS-based approach, since my own service isn&amp;rsquo;t exposed on the wide internet for Let&amp;rsquo;s Encrypt to call back to.
It&amp;rsquo;s a nice approach to be able to get valid SSL certificates for a machine on a local network.
Additionally, a lot of tooling is readily available to automate all details for you, such as &lt;a href="https://certbot.eff.org/"&gt;certbot&lt;/a&gt; or &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Essentially, these steps are completely taken from the aforementioned post.
Since I wouldn&amp;rsquo;t like to lose these and since I have some minor differences due to using digital ocean as DNS provider, I detail them here again for myself.&lt;/p&gt;
&lt;p&gt;Installing the bot:&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;curl https://get.acme.sh | sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Installing a hook script to run on certificate renewal in &lt;code&gt;/root/.acme.sh/cloudkey-renew-hook.sh&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;&lt;span style="color:#75715e"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Renew-hook for ACME / Let&amp;#39;s encrypt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;** Configuring new Let&amp;#39;s Encrypt certs&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /etc/ssl/private
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rm -f /etc/ssl/private/cert.tar /etc/ssl/private/unifi.keystore.jks /etc/ssl/private/ssl-cert-snakeoil.key /etc/ssl/private/fullchain.pem
&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;openssl pkcs12 -export -in /etc/ssl/private/cloudkey.crt -inkey /etc/ssl/private/cloudkey.key -out /etc/ssl/private/cloudkey.p12 -name unifi -password pass:aircontrolenterprise
&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;keytool -importkeystore -deststorepass aircontrolenterprise -destkeypass aircontrolenterprise -destkeystore /usr/lib/unifi/data/keystore -srckeystore /etc/ssl/private/cloudkey.p12 -srcstoretype PKCS12 -srcstorepass aircontrolenterprise -alias unifi
&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;rm -f /etc/ssl/private/cloudkey.p12
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tar -cvf cert.tar *
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chown root:ssl-cert /etc/ssl/private/*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod &lt;span style="color:#ae81ff"&gt;640&lt;/span&gt; /etc/ssl/private/*
&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;echo &lt;span style="color:#e6db74"&gt;&amp;#34;** Testing Nginx and restarting&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/sbin/nginx -t
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/etc/init.d/nginx restart ; /etc/init.d/unifi restart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will modify the retrieved certificates in such a way that they can be used by the unifi controller software.&lt;/p&gt;
&lt;p&gt;Export the proper API keys to the environment.
In my case, I am using the &lt;a href="https://github.com/acmesh-official/acme.sh/wiki/dnsapi#20-use-digitalocean-api-native"&gt;digitalocean DNS provider&lt;/a&gt;.
This changes the line in the &lt;code&gt;.bashrc&lt;/code&gt; file to adapt to something like&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;export DO_API_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;key&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first certificate retrieval line changes to&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;acme.sh --force --issue --dns dns_dgon -d &amp;lt;YOUR_DOMAIN&amp;gt; --pre-hook &lt;span style="color:#e6db74"&gt;&amp;#34;touch /etc/ssl/private/cert.tar; tar -zcvf /root/.acme.sh/CloudKeySSL_`date +%Y-%m-%d_%H.%M.%S`.tgz /etc/ssl/private/*&amp;#34;&lt;/span&gt; --fullchainpath /etc/ssl/private/cloudkey.crt --keypath /etc/ssl/private/cloudkey.key --reloadcmd &lt;span style="color:#e6db74"&gt;&amp;#34;sh /root/.acme.sh/cloudkey-renew-hook.sh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The crontab entry becomes:&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;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; * * * /root/.acme.sh/acme.sh --renew --apache --renew-hook /root/.acme.sh/cloudkey-renew-hook.sh -d &amp;lt;YOUR_DOMAIN&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, everything should be back up and running and the cloud key should be accessible from the custom domain &lt;em&gt;with&lt;/em&gt; a valid certificate!&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;This post serves mainly as a &lt;strong&gt;note-to-self&lt;/strong&gt; after needing to go through some steps to manually recover my cloud key setup.&lt;/p&gt;
&lt;p&gt;At this point, the cloud key is just back running as before.
Going through this process again made me realize some points of improvement.&lt;/p&gt;
&lt;p&gt;Firstly, in the past I did &lt;strong&gt;run the controller software as part of my home &lt;a href="https://k3s.io/"&gt;k3s&lt;/a&gt; cluster&lt;/strong&gt;, but I ended up not doing that since it cost me a lot of resources and I basically did not want to manage that.
However, I still have some plans to upgrade the cluster setup itself at which point it might come interesting again to run it there.
The added value would be that I could manage the certificate renewal using &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt; instead of via a cron job.
The downside of the cron job on the cloud key is that it gets wiped for firmware upgrades.&lt;/p&gt;
&lt;p&gt;Secondly, I am also not completely happy with the &lt;strong&gt;manual host name&lt;/strong&gt; management.
I first need to configure a static IP manually in the controller software and then also manually configure pihole / dnsmasq.
It seems there is a &lt;a href="https://registry.terraform.io/providers/paultyng/unifi/latest/docs"&gt;unifi terraform&lt;/a&gt; provider that would be able to handle the IP management &amp;ndash; but I haven&amp;rsquo;t been able yet to get that working with the cloud key (perhaps it does not work for the gen 1?).
DNS configuration can be done via API using e.g. &lt;a href="https://www.powerdns.com/"&gt;powerdns&lt;/a&gt; or &lt;a href="https://coredns.io/"&gt;coredns&lt;/a&gt;, which is another route to explore.
Services running on the kubernetes cluster could then also register their host names using &lt;a href="https://github.com/kubernetes-sigs/external-dns"&gt;external-dns&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thirdly, it would also be nice that any of the host names I set for my local network are actually accessible in a similar manner over a &lt;strong&gt;&lt;a href="https://www.wireguard.com/"&gt;wireguard&lt;/a&gt; VPN&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A final small change would be for me to &lt;strong&gt;change DNS provider&lt;/strong&gt; from digital ocean to &lt;a href="https://www.hetzner.com/cloud"&gt;hetzner cloud&lt;/a&gt; since I have everything else there.&lt;/p&gt;
&lt;p&gt;Still a lot of home lab ideas, so little time, let&amp;rsquo;s see what&amp;rsquo;s next!&lt;/p&gt;</content:encoded></item><item><title>Password troubles</title><link>https://mhemeryck.xyz/posts/2021-12-01-password_fsckup/</link><pubDate>Wed, 01 Dec 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-12-01-password_fsckup/</guid><description>&lt;p&gt;Last Saturday, my new phone arrived in the mail.
As a techie, I was naturally delighted to get my new treat up and running.
Both my old and my new phone are Android phones and I was surprised to see how (shockingly) easy it was to migrate all my stuff.
Not all settings were migrated directly though; mostly passwords would need to be set up again.&lt;/p&gt;
&lt;p&gt;Since I did use gnu pass + GPG for my password management as described in my &lt;a href="https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/"&gt;previous password management post&lt;/a&gt; earlier this year, I figured this would be quite easy to do!&lt;/p&gt;</description><content:encoded>&lt;p&gt;Last Saturday, my new phone arrived in the mail.
As a techie, I was naturally delighted to get my new treat up and running.
Both my old and my new phone are Android phones and I was surprised to see how (shockingly) easy it was to migrate all my stuff.
Not all settings were migrated directly though; mostly passwords would need to be set up again.&lt;/p&gt;
&lt;p&gt;Since I did use gnu pass + GPG for my password management as described in my &lt;a href="https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/"&gt;previous password management post&lt;/a&gt; earlier this year, I figured this would be quite easy to do!&lt;/p&gt;
&lt;p&gt;Unfortunately, the whole endeavor ended with me losing my current GPG sub keys and (again) &lt;em&gt;all&lt;/em&gt; my passwords.&lt;/p&gt;
&lt;h2 id="recap"&gt;Recap&lt;/h2&gt;
&lt;p&gt;But first, let&amp;rsquo;s explain the setup again.&lt;/p&gt;
&lt;h3 id="gpg"&gt;GPG&lt;/h3&gt;
&lt;p&gt;I have a main GPG public / private key pair, valid indefinitely, stored offline.
From these, I generate 3 sub key pairs valid for the coming year:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;encryption key: encrypt / decrypt stuff, notably passwords&lt;/li&gt;
&lt;li&gt;signing key&lt;/li&gt;
&lt;li&gt;authentication key: to be used for SSH.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="pass"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.passwordstore.org/"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; is essentially a wrapper around GPG.
It manages a folder in your home folder containing an encrypted file using a previously defined GPG encryption key.
Typically, that&amp;rsquo;s the GPG encryption sub key mentioned earlier.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pass&lt;/code&gt; also wraps around &lt;code&gt;git&lt;/code&gt;, meaning you can have remote (encrypted) versioned backup of your password storage using &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="chrome-integration-browserpass"&gt;Chrome integration: browserpass&lt;/h3&gt;
&lt;p&gt;Chrome has an extension called &lt;a href="https://github.com/browserpass/browserpass-extension"&gt;browserpass&lt;/a&gt; which essentially allows you to fill in passwords by calling &lt;code&gt;pass&lt;/code&gt;.
Apart from the passwords, it&amp;rsquo;s also possible to store the usernames (or any extra data, really) as part of the secret in &lt;code&gt;pass&lt;/code&gt;, which browserpass will fill in for you.
I&amp;rsquo;m using the extension for chrome, but I suppose there are similar integrations for other browsers as well.&lt;/p&gt;
&lt;h3 id="android-integration-openkeychain--password-store"&gt;Android integration: openkeychain + password store&lt;/h3&gt;
&lt;p&gt;My phone setup is quite similar:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.openkeychain.org/"&gt;openkeychain&lt;/a&gt; for the keys (&lt;code&gt;gpg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=dev.msfjarvis.aps"&gt;password store&lt;/a&gt; for the passwords (&lt;code&gt;pass&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since &lt;code&gt;pass&lt;/code&gt; has the possibility to sync the full password store based on &lt;code&gt;git&lt;/code&gt;, it&amp;rsquo;s actually quite easy to keep my laptop and phone in sync.&lt;/p&gt;
&lt;h3 id="yubikey"&gt;yubikey&lt;/h3&gt;
&lt;p&gt;The yubikey provides a suite of different cryptographic operations (e.g. &lt;a href="https://www.yubico.com/authentication-standards/fido-u2f/"&gt;U2F&lt;/a&gt;), but in this case, I use to also &lt;em&gt;physically&lt;/em&gt; contain aforementioned GPG private sub keys.&lt;/p&gt;
&lt;p&gt;For example, in the case of password management:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;encrypt a new password: insert a new password with pass &lt;code&gt;pass insert &amp;lt;passname&amp;gt;&lt;/code&gt;. &lt;code&gt;pass&lt;/code&gt; will use the public key to encrypt the password&lt;/li&gt;
&lt;li&gt;decrypt the password: you&amp;rsquo;d need the private key to decrypt. Since the private key is never on my laptop, I need to insert my yubikey to decrypt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For SSH authentication, I use something similar where the GPG agent provides the SSH process an SSH private key, based on the GPG authentication sub key, which lives on the yubikey.&lt;/p&gt;
&lt;p&gt;Having the GPG sub keys not physically on the laptop is safer but means you can&amp;rsquo;t afford losing the yubikey.
To mitigate this risk of losing the yubikey (and the GPG sub keys on them), I would save an offline backup of the sub keys prior to copying them to the yubikey.&lt;/p&gt;
&lt;p&gt;So, at this point, when there&amp;rsquo;s a backup, nothing can go wrong anymore, right?&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;Well, no.&lt;/p&gt;
&lt;p&gt;In my &lt;a href="https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/"&gt;previous password management post&lt;/a&gt;, the issue was that I did drop my old GPG decryption key before decrypting the passwords.&lt;/p&gt;
&lt;p&gt;In this case, multiple things went wrong.&lt;/p&gt;
&lt;p&gt;First of all the &lt;a href="https://www.openkeychain.org/"&gt;openkeychain&lt;/a&gt; app on my &lt;strong&gt;new phone&lt;/strong&gt; did not directly recognize my yubikey.
After trying a second time, it indicated &lt;em&gt;there were no GPG keys on the yubikey&lt;/em&gt;.
Checking on my laptop indeed revealed the yubikey had no more GPG keys.
Even more, at this point, it looked like the yubikey had been pretty much wiped to factory state.&lt;/p&gt;
&lt;p&gt;Next step is checking out the offline backup.
I did find the sub keys again that I had saved for this very purpose.
Re-importing them did however not result in being able to decrypt again.&lt;/p&gt;
&lt;p&gt;To verify what had happened, I did start from a live OS boot (i.e. &lt;a href="https://tails.boum.org/index.en.html"&gt;tails&lt;/a&gt;) and tried a clean setup with all my private keys.&lt;/p&gt;
&lt;p&gt;What it showed me was something like this (redacted):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;~ gpg -K
/$HOME/.gnupg/pubring.kbx
----------------------------------
sec rsa4096 2016-06-24 [SC]
1B477ADA04B47A5CE61AEDE01AA36833BC86F0F1
uid [ultimate] Martijn ... &amp;lt;user@mail.com&amp;gt;
ssb rsa4096 2016-06-24 [E]
ssb&amp;gt; rsa4096 2021-03-09 [E] [expires: ...]
ssb&amp;gt; rsa4096 2021-03-09 [S] [expires: ...]
ssb&amp;gt; rsa4096 2021-03-09 [A] [expires: ...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The bracket &lt;code&gt;&amp;gt;&lt;/code&gt; in front of &lt;code&gt;ssb&lt;/code&gt; means that it&amp;rsquo;s a &lt;em&gt;stub&lt;/em&gt; of a sub key.
A stub is what is left over on the key ring after the private key has been moved to the key card (the yubikey).
Apparently, instead of making a backup of the key, I had made a back-up of the stub &amp;hellip;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;key issue is that the &lt;code&gt;gpg&lt;/code&gt; &lt;code&gt;keytocard&lt;/code&gt; operation is a destructive one&lt;/strong&gt;.
If you move the private key to the card, only the stub is left, never to be retrieved again.
To make a proper back-up of the private key (sub key or full key), do it before you move it over to the key card!&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Since this was the second time in a relatively short time I had principally lost all my logins, I really felt like giving up altogether on this solution for password management.&lt;/p&gt;
&lt;p&gt;However, the whole &lt;code&gt;gpg&lt;/code&gt; + &lt;code&gt;pass&lt;/code&gt; + &lt;code&gt;git&lt;/code&gt; + yubikey combo still is quite appealing to me.
It fits in the true &lt;a href="https://en.wikipedia.org/wiki/Unix_philosophy"&gt;Unix philosophy&lt;/a&gt; of &amp;ldquo;having each program do one thing well&amp;rdquo;.
I also like the fact I&amp;rsquo;m myself in complete control of the data instead of relying on a remote cloud provider, specifically if it is about sensitive data.&lt;/p&gt;
&lt;p&gt;Ultimately, after a couple of days of re-requesting logins, I&amp;rsquo;m back to before, this time with a proper backup (fingers crossed!).&lt;/p&gt;</content:encoded></item><item><title>modbusbackup</title><link>https://mhemeryck.xyz/posts/2021-11-16-modbusbackup/</link><pubDate>Tue, 16 Nov 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-11-16-modbusbackup/</guid><description>&lt;p&gt;Another home automation post!
Please check out my earlier &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation series&lt;/a&gt; first to get an idea what I am talking about.&lt;/p&gt;
&lt;p&gt;Recently, I figured to rework some part of it to be able to deal better with certain kind of failures, while at the same time still being able to offer all the automation abilities as before.&lt;/p&gt;
&lt;h2 id="recap"&gt;Recap&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s &lt;strong&gt;recap the architecture&lt;/strong&gt; that was already in place:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-22/architecture.png" alt="current architecture"&gt;&lt;/p&gt;</description><content:encoded>&lt;p&gt;Another home automation post!
Please check out my earlier &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation series&lt;/a&gt; first to get an idea what I am talking about.&lt;/p&gt;
&lt;p&gt;Recently, I figured to rework some part of it to be able to deal better with certain kind of failures, while at the same time still being able to offer all the automation abilities as before.&lt;/p&gt;
&lt;h2 id="recap"&gt;Recap&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s &lt;strong&gt;recap the architecture&lt;/strong&gt; that was already in place:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-22/architecture.png" alt="current architecture"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;inputs: a unipi unit is used to read the digital inputs (e.g. push buttons); updates are pushed as MQTT events&lt;/li&gt;
&lt;li&gt;MQTT broker: a centralized broker takes in the updates and pushes them to all subscribers&lt;/li&gt;
&lt;li&gt;home assistant: sees updates on the MQTT inputs and performs specific actions as automations on outputs push out another MQTT events&lt;/li&gt;
&lt;li&gt;outputs: a unipi unit takes in MQTT updates and triggers a corresponding relay change (e.g. a light).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more details, refer to the &lt;a href="https://mhemeryck.xyz/posts/2021-06-22-home_automation_architecture/"&gt;architecture post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Although this approach has worked fairly well for me, it inherently holds some potential issues.&lt;/p&gt;
&lt;h2 id="problem-centralized-points-of-failure"&gt;Problem: centralized points of failure&lt;/h2&gt;
&lt;p&gt;Firstly, on the &lt;strong&gt;software level&lt;/strong&gt;, all connections need to run through home assistant and the MQTT broker.
An advantage of this is that everything is configurable from a centralized place, in software.
However, this also requires a higher level of reliability on those software instances.
As an example, initially, doing any updates to this software component was a bit more involved since any downtime here, effectively translated into non-functioning light push buttons.
Downtime is fortunately a lot less common since I did start using &lt;a href="https://k3s.io/"&gt;k3s&lt;/a&gt; for orchestrating the deployment.
Nevertheless, it is (currently) not possible to run home assistant in an &amp;ldquo;high availability&amp;rdquo; (HA) mode.
HA mode means that multiple home assistant instances can run at the same time, where one instance sits idle as a fail-over for the main running instance.&lt;/p&gt;
&lt;p&gt;In addition, on the &lt;strong&gt;network level&lt;/strong&gt;, the links between the components all depend on the local network.
Any downtime at this level of the stack again translates into the non-availability of my light push buttons.&lt;/p&gt;
&lt;p&gt;This solution would need to be &lt;strong&gt;more robust&lt;/strong&gt; against issues on both &lt;strong&gt;software&lt;/strong&gt; and &lt;strong&gt;network&lt;/strong&gt; layers.
At the same time, I would still like to keep &lt;strong&gt;centralized control from home assistant&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="solution-direct-link"&gt;Solution: direct link&lt;/h2&gt;
&lt;p&gt;I did start digging around and got some helpful feedback on the unipi forum from this &lt;a href="https://forum.unipi.technology/topic/1434/connecting-2-unipi-controllers-over-rs485/7"&gt;post on connecting 2 unipi units together&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first issue of the &lt;strong&gt;dependency on the home assistant instance&lt;/strong&gt; could be mitigated by moving the automation logic out of home assistant.
This would mean that some other component could listen to the input events and do the required actions on the outputs.
The unipi units could easily run such extra software themselves.&lt;/p&gt;
&lt;p&gt;The second issue related to the &lt;strong&gt;network layer&lt;/strong&gt; could be solved by finding a way to connect to the involved unipi units directly together.
This post on &lt;a href="https://www.unipi.technology/news/the-four-ways-to-set-up-your-automation-project-257"&gt;unipi connections&lt;/a&gt; provided some inspiration in that respect: the third option details using the RS-485 bus which is available on most unipi units to connect devices together.&lt;/p&gt;
&lt;p&gt;Control from home assistant will still be possible: either the update should come from the toggle of an input button (via the direct connection) or from an MQTT event.
There should be no issue in having both options at the same time.&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 new architecture now changes into the following:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-11-16/arch.png" alt="proposal architecture"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;inputs / outputs: the edges of the diagram just again show the inputs and output&lt;/li&gt;
&lt;li&gt;unipi units: 1 unipi unit is shown for inputs, 1 for outputs&lt;/li&gt;
&lt;li&gt;home assistant: state updates on inputs and outputs still come in over MQTT. State changes on the outputs can still be pushed using MQTT.&lt;/li&gt;
&lt;li&gt;MQTT broker: sits between the unipi units and home assistant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;key difference&lt;/strong&gt; over the previous architecture is that the link between the input push buttons and the output relays is no longer established using an automation in home assistant.
Instead, there is a direct link over RS-485 using the &lt;a href="https://www.simplymodbus.ca/FAQ.htm"&gt;Modbus&lt;/a&gt; protocol.&lt;/p&gt;
&lt;h2 id="hardware"&gt;Hardware&lt;/h2&gt;
&lt;p&gt;The following image (taken from the post on &lt;a href="https://www.unipi.technology/news/the-four-ways-to-set-up-your-automation-project-257"&gt;unipi connections&lt;/a&gt;) shows a conceptual wiring diagram to connect one or more unipi units together.
Essentially, it is a 2-wire connection, &lt;a href="https://en.wikipedia.org/wiki/Daisy_chain_%28electrical_engineering%29"&gt;daisy-chaining&lt;/a&gt; all units.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-11-16/wiring.png" alt="wiring"&gt;&lt;/p&gt;
&lt;p&gt;As the photo shows, the wiring I did use was a simple piece of leftover &lt;strong&gt;CAT6 UTP&lt;/strong&gt; (the grey piece of cable behind the black ethernet connector in the front).
In fact, I only did use 2 of the 8 wires.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-11-16/hardware.jpg" alt="hardware"&gt;&lt;/p&gt;
&lt;p&gt;For my purposes I only did connect 2 units together, but you could in principle extend this over to multiple clients on the same bus.
This would practically mean extending the 2-wire connection from the last connection on.&lt;/p&gt;
&lt;p&gt;I did not need to introduce any additional resistors nor change any of the DIP switches, this all worked fine out of the box.&lt;/p&gt;
&lt;p&gt;As the unipi units readily expose the RS-485 interface, this step turned out to be really simple.&lt;/p&gt;
&lt;h2 id="software"&gt;Software&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;protocol&lt;/strong&gt; you would typically run on this wiring setup is &lt;strong&gt;&lt;a href="https://www.simplymodbus.ca/FAQ.htm"&gt;Modbus&lt;/a&gt;&lt;/strong&gt;.
Within Modbus, there is one server and all the other clients are daisy-chained on the same bus.
All clients provide their own set of inputs and outputs (&amp;ldquo;coils&amp;rdquo;, &amp;ldquo;discrete inputs&amp;rdquo;, &amp;ldquo;input registers&amp;rdquo; and &amp;ldquo;holding registers&amp;rdquo;) in a predefined range.
Read and write control is fully managed by the server.
To read specific values, the server will just poll those values from the client.
To write updates, the server can similarly just put the values where they need to be on the clients.
The speed at which updates from a client are propagated thus also depend on the polling speed the server uses.
In case any communications needs to occur between clients, this is only possible from the server.
The server would then first read those values from one client (by polling) and later on write them to the other client.&lt;/p&gt;
&lt;p&gt;The unipi OS exposes the RS-485 connection via a specific linux device; see the &lt;a href="https://kb.unipi.technology/en:hw:02-neuron:portmap"&gt;unipi serial port map&lt;/a&gt;.
For my neuron L403 and L303 I would need to use the &lt;code&gt;/dev/ttyNS0&lt;/code&gt; devices.&lt;/p&gt;
&lt;p&gt;Running a Modbus protocol stack on both the client and server devices was however &lt;em&gt;not&lt;/em&gt; provided by default by unipi.
Here I did write my own small script &lt;a href="https://github.com/mhemeryck/modbusbackup"&gt;&lt;code&gt;modbusbackup.py&lt;/code&gt;&lt;/a&gt;, which can run both in client and server mode.&lt;/p&gt;
&lt;p&gt;The idea is to run a Modbus client (server mode) on the device that will be expecting incoming Modbus requests.
A Modbus server is ran (client mode) on the device that sends out Modbus messages.&lt;/p&gt;
&lt;p&gt;The implementation itself is based on &lt;a href="https://pymodbus.readthedocs.io/en/latest/index.html"&gt;PyModbus&lt;/a&gt;, which makes it possible to easily write both client and server code.&lt;/p&gt;
&lt;p&gt;For now, I only implemented this connection between my unipi unit that reads my push buttons and the unipi unit that controls relays connected to lights.
To illustrate, let&amp;rsquo;s again go through toggling a light:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;push button is pushed&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/UniPiTechnology/evok"&gt;evok&lt;/a&gt; detects the push button change and exposes the event through a websocket event (among others).&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;modbusbackup.py&lt;/code&gt; client (on the inputs unit) sees the websocket event, maps it our own configured Modbus address and sends it out as a Modbus message&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;modbusbackup.py&lt;/code&gt; server (on the relays unit) sees the incoming Modbus message, and translates it to the correct relay update; the current relay state is first retrieved (evok REST API) and then toggled.&lt;/li&gt;
&lt;li&gt;evok handles the relay update&lt;/li&gt;
&lt;li&gt;the relay is toggled; the light is turned on&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="client-code"&gt;Client code&lt;/h3&gt;
&lt;p&gt;The key function is &lt;code&gt;ws_process&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-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 style="color:#f92672"&gt;-&amp;gt;&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:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Process incoming websocket payload, push to modbus RTU&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; &lt;span style="color:#75715e"&gt;# Ignore analog I?O&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; obj[&lt;span style="color:#e6db74"&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;] &lt;span style="color:#f92672"&gt;in&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#34;ai&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;ao&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;return&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 style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Incoming message for websocket &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;obj&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; &lt;span style="color:#75715e"&gt;# Don&amp;#39;t send any events related to trailing edges&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; 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;0&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; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; address &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _circuit_map()[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; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;KeyError&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 style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Could not find mapping address for &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;obj[&lt;span style="color:#e6db74"&gt;&amp;#39;circuit&amp;#39;&lt;/span&gt;]&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; &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; &lt;span style="color:#75715e"&gt;# Blocking sync call&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;Writing &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;obj[&lt;span style="color:#e6db74"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; to address &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;address&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; for circuit &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;obj[&lt;span style="color:#e6db74"&gt;&amp;#39;circuit&amp;#39;&lt;/span&gt;]&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; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _modbus_client()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write_coil(address, obj[&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;], unit&lt;span style="color:#f92672"&gt;=&lt;/span&gt;_UNIT)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The function takes a payload from a websocket event, made available through evok.
The reason we use a websocket event here is that it is easier to check any updates from the inputs as opposed to polling ourselves.
Additionally, evok already exposes the events using websockets.
This is part is quite close to the earlier solution presented in the &lt;a href="https://mhemeryck.xyz/posts/2021-07-27-home_automation_software/"&gt;software post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;circuit_map&lt;/code&gt; function is singleton wrapper around a dictionary mapping the unipi digital inputs to Modbus addresses.
Similarly, the &lt;code&gt;_modbus_client&lt;/code&gt; function is also a singleton wrapper to avoid having to constantly recreate the Modbus connection to the other unit.&lt;/p&gt;
&lt;p&gt;See this link for the full &lt;a href="https://github.com/mhemeryck/modbusbackup/blob/90a74da444bf514f59d68bc32d61c83ac9667e87/modbusbackup.py#L31-L112"&gt;client code&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="server-code"&gt;Server code&lt;/h3&gt;
&lt;p&gt;The key to get this to work for me was having a Modbus instance being able to accept incoming messages and handling those directly, i.e. in an event-based way.
After digging through some PyModbus examples, I did find this &lt;a href="https://github.com/pymodbus-dev/pymodbus/blob/7c2aa3953951fe2caf10500210dbf424c70a67cd/examples/server_callback.py"&gt;callback server example&lt;/a&gt;.
I did end up with the following set of functions, definitions:&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;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CallbackDataBlock&lt;/span&gt;(pymodbus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datastore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ModbusSparseDataBlock):
&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;callbacks on operation&amp;#34;&amp;#34;&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 style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(self) &lt;span style="color:#f92672"&gt;-&amp;gt;&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; super()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;({index: &lt;span style="color:#ae81ff"&gt;0x0&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; index &lt;span style="color:#f92672"&gt;in&lt;/span&gt; _relay_map()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;keys()})
&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;setValues&lt;/span&gt;(self, address: int, values: typing&lt;span style="color:#f92672"&gt;.&lt;/span&gt;List) &lt;span style="color:#f92672"&gt;-&amp;gt;&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; logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;debug(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Got &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;values&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; for &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;address&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; _trigger(address, values[&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; super()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;setValues(address, values)
&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;_run_server&lt;/span&gt;(port: str, timeout: float, baudrate: int) &lt;span style="color:#f92672"&gt;-&amp;gt;&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; block &lt;span style="color:#f92672"&gt;=&lt;/span&gt; CallbackDataBlock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; store &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pymodbus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datastore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ModbusSlaveContext(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; di&lt;span style="color:#f92672"&gt;=&lt;/span&gt;block,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; co&lt;span style="color:#f92672"&gt;=&lt;/span&gt;block,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hr&lt;span style="color:#f92672"&gt;=&lt;/span&gt;block,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ir&lt;span style="color:#f92672"&gt;=&lt;/span&gt;block,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; zero_mode&lt;span style="color:#f92672"&gt;=&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; )
&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; context &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pymodbus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datastore&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ModbusServerContext(slaves&lt;span style="color:#f92672"&gt;=&lt;/span&gt;store, single&lt;span style="color:#f92672"&gt;=&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; pymodbus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;server&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sync&lt;span style="color:#f92672"&gt;.&lt;/span&gt;StartSerialServer(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; framer&lt;span style="color:#f92672"&gt;=&lt;/span&gt;pymodbus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;transaction&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ModbusRtuFramer,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;port,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;timeout,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; baudrate&lt;span style="color:#f92672"&gt;=&lt;/span&gt;baudrate,
&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 &lt;code&gt;_run_server&lt;/code&gt; code wraps starting up a serial, RTU, Modbus server.
The port to be provided to the server is simple the Linux device mentioned above, &lt;code&gt;/dev/ttyNS0&lt;/code&gt;.
As part of a Modbus server configuration, you need to provide the outline of the various Modbus addresses it exposes for the main Modbus instance to poll.
In this, case I did pass in a custom block, the &lt;code&gt;CallbackDataBlock&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;CallbackDataBlock&lt;/code&gt; constructor sets up all required addresses based on the configuration (through the &lt;code&gt;_relay_map&lt;/code&gt; function).
The &lt;code&gt;_relay_map&lt;/code&gt; function keeps a (singleton) mapping of the relays for the given Modbus address.
In order to have callback-like handlers, you can override the &lt;code&gt;setValues&lt;/code&gt; method.
This method is called for any Modbus message for the address that is part of the function arguments.
At this point, I do have the message handled by another &lt;code&gt;_trigger&lt;/code&gt; function.&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;_trigger&lt;/span&gt;(address: int, value: bool, host&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&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:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Process incoming event&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Only check for rising edges since we&amp;#39;re dealing with lights&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; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; value:
&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; &lt;span style="color:#75715e"&gt;# Convert address to zero-based address&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; relay &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _relay_map()[address]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;KeyError&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 style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Could not find relay for address &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;address&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; &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; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _session()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;host&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/json/relay/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;relay&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; response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;raise_for_status()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;exceptions&lt;span style="color:#f92672"&gt;.&lt;/span&gt;HTTPError &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; error:
&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 style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Issue with API call: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;error&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; &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 style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; current &lt;span style="color:#f92672"&gt;=&lt;/span&gt; response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()[&lt;span style="color:#e6db74"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;][&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; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;KeyError&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ValueError&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 style="color:#e6db74"&gt;&amp;#34;Error reading current state&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;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; &lt;span style="color:#75715e"&gt;# Flip the bit from current by XOR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; toggled &lt;span style="color:#f92672"&gt;=&lt;/span&gt; current &lt;span style="color:#f92672"&gt;^&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _session()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;post(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;host&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/json/relay/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;relay&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, json&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;: str(toggled)})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;raise_for_status()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;exceptions&lt;span style="color:#f92672"&gt;.&lt;/span&gt;HTTPError &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; error:
&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 style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Issue with API call: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;error&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; &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 style="color:#66d9ef"&gt;else&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 style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Toggled relay &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;relay&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; to &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;toggled&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;_trigger&lt;/code&gt; function simply takes in the Modbus message and maps it again to a relay on the unipi output device.
For the purposes of light control, a simple toggle is enough, so the value is just used to see if the incoming event is actually for a rising edge, i.e. a &lt;code&gt;True&lt;/code&gt; value.
The current value is read first using evok; the new value is the toggled value (by XOR).&lt;/p&gt;
&lt;p&gt;Refer to this section for the full &lt;a href="https://github.com/mhemeryck/modbusbackup/blob/90a74da444bf514f59d68bc32d61c83ac9667e87/modbusbackup.py#L113-L216"&gt;server code&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="configuration-format"&gt;Configuration format&lt;/h3&gt;
&lt;p&gt;As configuration format, I did use YAML, similar to what I had before on home assistant.
The source code repository also holds a &lt;a href="https://github.com/mhemeryck/modbusbackup/blob/master/config.yaml"&gt;sample configuration file&lt;/a&gt; &amp;ndash; actually, my configuration file.
The format looks like this:&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;index&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; &lt;span style="color:#f92672"&gt;input&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2_03&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kelder inkom inbouw&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;output&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2_01&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;index&lt;/code&gt; can be an arbitrary number &amp;ndash; yet it needs to be unique.
The &lt;code&gt;input&lt;/code&gt; relates to the digital input on the unipi unit connected to the inputs.
Similarly, the &lt;code&gt;output&lt;/code&gt; relates to the relay output on the other unipi unit.
The &lt;code&gt;name&lt;/code&gt; field is just there for documentation purposes.&lt;/p&gt;
&lt;p&gt;The automation part previously handled by home assistant now effectively becomes part of this configuration file.
The link between input and output is done by the unique &lt;code&gt;index&lt;/code&gt; field.
The input-side will map its digital input change to the index; this index is used as Modbus address.
On the other end, the output-side will map the incoming Modbus address to the its relay it needs to change.&lt;/p&gt;
&lt;p&gt;This also means that this configuration file needs to be shared between both the input and output side.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;While my home automation setup described in the &lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;home automation series&lt;/a&gt; has been running OK for me for quite some time, I did want to be less dependent on the correct functioning of the home assistant instance and the network.&lt;/p&gt;
&lt;p&gt;To get there, I made changes on 2 levels.
On the &lt;strong&gt;hardware-side&lt;/strong&gt;, I did make use of the already existing RS-485 connection, removing any network-related issues.
On the &lt;strong&gt;software-side&lt;/strong&gt;, I did add a custom script to run on both the input and output side that essentially notifies the output-side of any input-related events.
The actual link between the input and output is through a simple configuration file, shared by both sides.&lt;/p&gt;
&lt;p&gt;I only did implement this connection for my &lt;strong&gt;lighting setup&lt;/strong&gt; since I did feel this is the most critical part for me to continue to keep on functioning.
For my alarm setup and my shades, all input and outputs are actually each on their own, same unipi unit.
This means that in order to decouple those from home assistant I could also write some custom code, but they would not need the whole RS-485 connection, nor any Modbus setup.&lt;/p&gt;
&lt;p&gt;Note that some push button-to-light connections still go through home assistant: I have e.g. some Philips Hue lights that get triggered via a push button change.
For this case, I think it still makes sense to keep this connection in home assistant.
However, once I would start to expand my DALI-bus based setup, I&amp;rsquo;m not yet sure how to tackle this.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;transition&lt;/strong&gt; between the fully home assistant-based outline and my current &amp;ldquo;modbusbackup&amp;rdquo; solution has gone pretty well though, to that extent that it&amp;rsquo;s not really clear to others in my home what I have actually been working on.
The main advantage I now see is being able to more easily make any changes on the home assistant / network side.&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 automation that was already in place in home assistant to link input to output would need to be removed though.&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>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>Hardware</title><link>https://mhemeryck.xyz/posts/2021-07-20-home_automation_hardware/</link><pubDate>Tue, 20 Jul 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-07-20-home_automation_hardware/</guid><description>&lt;p&gt;In this post, I will give some more details about the rationale for the &lt;strong&gt;hardware&lt;/strong&gt; that I had chosen for interpreting the input signals and controlling the outputs, specifically the &lt;a href="https://www.unipi.technology/products/unipi-neuron-3?categoryId=2&amp;amp;categorySlug=unipi-neuron"&gt;unipi neuron series&lt;/a&gt;.
Note that I am in no way affiliated with unipi, nor do I intend to do an in-depth description of their hardware.
For more details from unipi themselves, have a look at the &lt;a href="https://www.unipi.technology/"&gt;unipi&lt;/a&gt; website.&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;In this post, I will give some more details about the rationale for the &lt;strong&gt;hardware&lt;/strong&gt; that I had chosen for interpreting the input signals and controlling the outputs, specifically the &lt;a href="https://www.unipi.technology/products/unipi-neuron-3?categoryId=2&amp;amp;categorySlug=unipi-neuron"&gt;unipi neuron series&lt;/a&gt;.
Note that I am in no way affiliated with unipi, nor do I intend to do an in-depth description of their hardware.
For more details from unipi themselves, have a look at the &lt;a href="https://www.unipi.technology/"&gt;unipi&lt;/a&gt; website.&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="inside"&gt;Inside&lt;/h2&gt;
&lt;p&gt;The following picture shows what is actually &lt;strong&gt;inside such a unit&lt;/strong&gt;: a &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-3-model-b-plus/"&gt;raspberry pi 3B+&lt;/a&gt; main controller board and then (depending on the neuron series you have), 1 to 3 I/O boards, connected via &lt;a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface"&gt;SPI&lt;/a&gt; &amp;ndash; Serial Peripheral Interface.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;SPI interface&lt;/strong&gt; works by connecting a number of nodes in a series, &lt;em&gt;daisy-chained&lt;/em&gt;, on the same SPI-line(s).
The raspberry pi functions as the main &lt;em&gt;server&lt;/em&gt; node, the I/O boards are the &lt;em&gt;clients&lt;/em&gt;.
The server nodes periodically &lt;em&gt;selects&lt;/em&gt; one of the clients to become active.
Consequently, the selected client becomes active and puts its data on the SPI-lines that were made available.
Other clients remain inactive for that duration of time they were not selected.
Afterwards, the server moves on to the next client.
This process continues until all clients have been consulted, at which point the cycle concludes and a new one starts.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-20/unipi-hardware.png" alt="unipi inside"&gt;&lt;/p&gt;
&lt;p&gt;The main controller board has all the other &lt;strong&gt;functionalities&lt;/strong&gt; the raspberry pi has, including bluetooth, wifi, wired ethernet, USB ports, and even a HDMI interface&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;.
The I/O boards either feature digital inputs, relay outputs, analog inputs, analog outputs or a combination of these.
Additionally, there are connections like a RS-485 serial lines (e.g. for interfacing with modbus) as well as a connector for a 1-wire bus (e.g. for temperature sensors).&lt;/p&gt;
&lt;h2 id="rationale"&gt;Rationale&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://kb.unipi.technology/_media/en:hw:neuron_m103-top.jpg" alt="unipi module"&gt;&lt;/p&gt;
&lt;p&gt;Considering the hardware platform, I think the unipi platform provides a number of &lt;strong&gt;advantages&lt;/strong&gt; I couldn&amp;rsquo;t find with other vendors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;open platform&lt;/strong&gt;: the main controller board is a raspberry pi 3B+, meaning you can run any software on it you would want.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;software support&lt;/strong&gt;: next to the open platform, they also provide open source OS images as well as software libraries to extend yourself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;local&lt;/strong&gt;: obviously, it runs from my local network inside of my home.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wired&lt;/strong&gt;: the I/O interfaces use standard voltages, for instance the power supply to the unit uses 24V, the digital inputs use 24V, the relay outputs can switch 240V&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;, &amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;form factor&lt;/strong&gt;: the modules come in form factors of 4, 8 and 12 DIN rail modules and fit nicely on a household DIN rail in the electric cabinet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wiring connectors&lt;/strong&gt;: related to the form factor, it is quite easy to connect a large amount of I/O in a relatively small space.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;low cost&lt;/strong&gt;: the units themselves do not come that cheap, but calculated as a &lt;em&gt;cost per I/O&lt;/em&gt;, they are quite OK compared to other solutions like industrial PLCs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;community&lt;/strong&gt;: while working on the units and my own custom software, I would often reach out to the &lt;a href="https://forum.unipi.technology/"&gt;unipi community forum&lt;/a&gt;. They often reply quite quickly and have been a great help!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;connectivity&lt;/strong&gt;: apart from the I/O, the unipi units come with a lot of extra connectivity options such as wifi, wired ethernet, bluetooth, RS-485, one-wire, &amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="specific-units"&gt;Specific units&lt;/h2&gt;
&lt;p&gt;I have number of different unipi units in my setup, since I have a large amount of functionalities to support, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;push button read-out&lt;/li&gt;
&lt;li&gt;light control (relays)&lt;/li&gt;
&lt;li&gt;alarm system (window / door contacts, PIR detector, &amp;hellip; but also on the output side an indoor-and outdoor siren).&lt;/li&gt;
&lt;li&gt;shades (relays)&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While you can have both inputs and outputs on the same unipi unit, I did decide to dedicate a bigger unipi unit for the push button control and another one for the lights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lights, relay control: &lt;a href="https://www.unipi.technology/unipi-neuron-l403-p102"&gt;unipi neuron l403&lt;/a&gt;, which has up to 56 relay outputs&lt;/li&gt;
&lt;li&gt;push button read-out: the &lt;a href="https://www.unipi.technology/unipi-neuron-l303-p101"&gt;unipi neuron l303&lt;/a&gt;. This was an upgraded version of the &lt;a href="https://www.unipi.technology/unipi-neuron-m303-p98"&gt;unipi neuron m303&lt;/a&gt; with up to 64 digital inputs, but at the time of writing, it seems this model is no longer supported.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="unipi-axon"&gt;Unipi axon&lt;/h2&gt;
&lt;p&gt;As a side note, I also have a &lt;strong&gt;unipi axon S605&lt;/strong&gt; for &lt;em&gt;DALI light control&lt;/em&gt;.
The &lt;a href="https://www.unipi.technology/products/unipi-axon-135?categoryId=13&amp;amp;categorySlug=unipi-axon"&gt;unipi axon series&lt;/a&gt; is another series of PLCs from the same brand.
It differs mainly in the controller board it features: instead of being based on raspberry pi, it is based on an Allwinner H5 ARM processor.
Also, instead of having the main hard drive on a removable flash memory, it has internal eMMC memory.&lt;/p&gt;
&lt;p&gt;I did not look into the rest of the axon series as it wasn&amp;rsquo;t available at the time when I acquired them.
Additionally, I also favored the neuron series because I have more familiarity with the raspberry platform in general.&lt;/p&gt;
&lt;p&gt;DALI light control is something I haven&amp;rsquo;t yet fully deployed, but the advantage is that you can have more all-digital control of lights &amp;ndash; wherever the light fixtures support it.
The main feature I do like about DALI is the ability to dim lights directly from the LED driver that already needs to perform the AC / DC conversion, making it more effective.&lt;/p&gt;
&lt;p&gt;Note however that this specific unit also isn&amp;rsquo;t able anymore, I suspect due to low demand.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;This was just a short description mainly about &lt;em&gt;why&lt;/em&gt; I did choose unipi neuron as the main hardware platform for my home automation.&lt;/p&gt;
&lt;p&gt;Note that this sort of hardware is particularly suited for a wired, &lt;a href="https://en.wikipedia.org/wiki/Network_topology#Star"&gt;star topology&lt;/a&gt; setup.
If I were to name a &lt;strong&gt;disadvantage&lt;/strong&gt;, it would be that working with this kind of hardware requires a lot of &lt;em&gt;tedious manual wiring&lt;/em&gt; which also takes up a lot of &lt;em&gt;cabinet space&lt;/em&gt;.
This is however more of an issue of the overall setup, not the hardware itself.
The alternative is a bus-system, where all of the signaling is done on a shared bus.
The disadvantage here is either all of the hardware on the bus is more complex and costly (e.g. KNX, DALI) and / or that you are tied to a single hardware vendor.
Nonetheless, DALI (and maybe even KNX) is something I would still like to examine a bit more in detail at some point in the future.&lt;/p&gt;
&lt;p&gt;Another issue I have faced is with the &lt;strong&gt;removable flash drives&lt;/strong&gt; as these tend to go &lt;em&gt;corrupt after many read / write cycles&lt;/em&gt;.
Again, this is more of an issue of the raspberry pi and flash drives in general, but it could still pose an issue for the reliability of the overall system.
After changing the flash drives to a more durable pSLC SD card, I can confirm that I haven&amp;rsquo;t faced any issues with corrupt flash drives lately (fingers crossed!)
Check the &lt;a href="https://kb.unipi.technology/en:hw:02-neuron:suitable-sd-card"&gt;unipi SD card reference&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;Overall, I have been very happy with the unipi hardware platform though!&lt;/p&gt;
&lt;p&gt;For more commercial details check the &lt;a href="https://www.unipi.technology/"&gt;unipi&lt;/a&gt; main website.
On the technical side, check out the &lt;a href="https://kb.unipi.technology/en:hw:02-neuron"&gt;unipi kb&lt;/a&gt;.&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 HDMI interface isn&amp;rsquo;t readily exposed&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;for highly inductive loads, like motors, it is recommended to switch the loads not directly, but instead via an intermediary set of relays&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>Electrical cabinet</title><link>https://mhemeryck.xyz/posts/2021-07-13-home_automation_cabinet/</link><pubDate>Tue, 13 Jul 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-07-13-home_automation_cabinet/</guid><description>&lt;p&gt;In earlier posts, I did provide an overview of the architecture of my custom-made home automation system.
I also did describe some specificities related to a &lt;em&gt;wired&lt;/em&gt; home automation setup.
For my system, I decide on wiring everything in a &lt;em&gt;star-configuration&lt;/em&gt;, meaning all wiring around the house for lights, push buttons, &amp;hellip; all converges in a &lt;strong&gt;centralized electrical cabinet&lt;/strong&gt;.
The focus of this post is to show you some details of what this cabinet looks like and some pointers for practical issues I had run into.&lt;/p&gt;</description><content:encoded>&lt;p&gt;In earlier posts, I did provide an overview of the architecture of my custom-made home automation system.
I also did describe some specificities related to a &lt;em&gt;wired&lt;/em&gt; home automation setup.
For my system, I decide on wiring everything in a &lt;em&gt;star-configuration&lt;/em&gt;, meaning all wiring around the house for lights, push buttons, &amp;hellip; all converges in a &lt;strong&gt;centralized electrical cabinet&lt;/strong&gt;.
The focus of this post is to show you some details of what this cabinet looks like and some pointers for practical issues I had run into.&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="inside-the-cabinet"&gt;Inside the cabinet&lt;/h2&gt;
&lt;p&gt;Given the star-configuration for all of the lights, the amount of wiring in the central electricity cabinet quickly adds up.
The following picture shows a work-in-progress view while laying out the cables:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-13/cabinet-wip.jpg" alt="cabinet-wip"&gt;&lt;/p&gt;
&lt;p&gt;Be sure you have a consistent &lt;em&gt;labelling scheme&lt;/em&gt; while pulling all of the cable.
Looking back at these pictures, I think it is a bit of miracle I did not miss anything &amp;hellip;&lt;/p&gt;
&lt;h3 id="terminal-clamps"&gt;Terminal clamps&lt;/h3&gt;
&lt;p&gt;Besides from labelling the cables during the pulling of the cables, I also did use &lt;strong&gt;terminal clamps&lt;/strong&gt;, like the &lt;a href="https://www.wago.com/global/electrical-interconnections/discover-rail-mount-terminal-blocks"&gt;WAGO rail-mount terminal blocks&lt;/a&gt;.
On one end of such a block, you have one or more terminals where you can connect your wires.
The other end on this block can then later be used to connect whatever you need.&lt;/p&gt;
&lt;p&gt;The next figure details the terminal clamps I did use, the &lt;a href="https://www.wago.com/global/rail-mount-terminal-blocks/multilevel-installation-terminal-block/p/2003-7642"&gt;WAGO TOPJOB S 2003-7642&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-13/wago.png" alt="wago"&gt;&lt;/p&gt;
&lt;p&gt;The details include the nominal voltage and current ratings, wire gauges, the physical width of the clamp itself, etc.
The lines next to the clamp indicate the &lt;strong&gt;different clamp variants&lt;/strong&gt; available.
The first 2 variants are 2 &amp;ldquo;stories&amp;rdquo; high (2 electrical connections on a single clamp) whereas the bottom 2 feature 3 &amp;ldquo;stories&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Each &lt;strong&gt;story&lt;/strong&gt; represents a single electrical connection, isolated from the other stories.
The number of stories all have to do with saving space inside the cabinet: 2 stories really mean you need only half the amount of clamps.
Additionally, since most mains voltage wiring is for single phase electrical connection, you can group the phase and neutral wire in one such clamp.&lt;/p&gt;
&lt;p&gt;Aside from the first row indicating the details about the terminal clamp, the figure also details many different variants of &lt;strong&gt;jumpers&lt;/strong&gt; available.
These jumpers can be placed in the middle ends of the clamps and provide a connection between the different clamps, which facilitates making electrical parallel connections.&lt;/p&gt;
&lt;p&gt;Functionally, these terminal clamps does not do all that much.
In terms of costs, a single block is not that expensive but since the amount of blocks you need quickly adds up, so do the costs.
Nonetheless, &lt;strong&gt;I would still highly recommend it&lt;/strong&gt; to keep a clear overview while inside of your cabinet.&lt;/p&gt;
&lt;h3 id="relays"&gt;Relays&lt;/h3&gt;
&lt;p&gt;The next picture shows 2 DIN-rails with all of the terminal blocks for my lights.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-13/cabinet-clamps.jpg" alt="cabinet-clamps"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;top row: termination of phase (brown) and neutral (blue) wires.&lt;/li&gt;
&lt;li&gt;bottom row: termination of 2 more wires which I intend to use as a [DALI] bus&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;; DALI+ (grey) and DALI- (black).&lt;/li&gt;
&lt;li&gt;jumpers: middle of terminal blocks&lt;/li&gt;
&lt;li&gt;orange spacers: each set of blocks between these spacers represent a single electric circuit. This makes it easier to map the one-wire diagram to the actual wiring inside the cabinet.&lt;/li&gt;
&lt;li&gt;green / yellow wires: these are terminated separately on the copper bar (visible between the two DIN rails) and directly connected to the main earth wire.&lt;/li&gt;
&lt;li&gt;two-level terminal blocks: the terminal blocks used here have 2 levels, i.e. the outer 2 terminals are connected as well as the inner 2. A multitude of such terminal blocks exists, ranging for single to multiple levels as well as different functionalities (built-in diodes, resistors, &amp;hellip;). Here, two-level terminal blocks were used mostly because of space constraints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that at this point that the wires from the light fixtures in the house were just connected to these terminal blocks: having these blocks there means you can later still have all &lt;strong&gt;flexibility&lt;/strong&gt; for connecting them elsewhere.&lt;/p&gt;
&lt;p&gt;The next part is connecting them to the unit that contains the relays themselves.
The &lt;a href="https://kb.unipi.technology/en:hw:02-neuron:description-of-io:03-description-of-ro"&gt;unipi relay outputs KB&lt;/a&gt; lists all of the details for connecting a single relay:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://kb.unipi.technology/_media/en:hw:010_connection_of_io.png" alt="unipi relay outputs img"&gt;&lt;/p&gt;
&lt;p&gt;This connection in itself is not all that complex for a &lt;em&gt;single&lt;/em&gt; relay.
Since multiple lights on the same circuit are connected to the same &lt;strong&gt;circuit breaker&lt;/strong&gt;, there is the practical concern of how to ensure these are all properly connected in parallel while still maintaining again a good overview.
The following picture shows another set of terminal blocks that I did use for this (orange blocks on the left of the unipi unit):&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-13/cabinet-unipi.jpg" alt="cabinet-unipi"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;phase wire (brown): the terminal block holders (orange, left from the unipi module) hold a set of terminal blocks which have one end connected to the circuit breaker, the other ends serve to &lt;em&gt;distribute&lt;/em&gt; the voltage to multiple relays.&lt;/li&gt;
&lt;li&gt;neutral wire (blue): since all neutral wires of a single circuit are placed next to each other, these can just be connected in parallel using a jumper.&lt;/li&gt;
&lt;li&gt;relay: each of the lights are connected to &amp;ldquo;RO&amp;rdquo; connections coming from the unipi module.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="push-buttons"&gt;Push buttons&lt;/h3&gt;
&lt;p&gt;Next to the series of clamps for terminating the cables for the lights, I also did use the same type of clamps for &lt;strong&gt;terminating the SVV analog signaling cable&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-13/cabinet-clamps-svv.jpg" alt="cabinet-clamps-svv"&gt;&lt;/p&gt;
&lt;p&gt;At the top, the &lt;strong&gt;incoming signaling cable&lt;/strong&gt; is terminated.
Spacers are placed series of clamps which belong to the same signaling cable run.
The bottom part then connects each of the signaling wires to a digital input somewhere lower in the cabinet.
In the middle of the clamps, you can see a number of overlapping jumpers.
These jumpers have been set up such that they only make an electrical contact in a fixed number of slots, in this case for connecting one of the wires for each cable run to 24V.&lt;/p&gt;
&lt;p&gt;As before, the following image shows the electrical connection from the signaling cable to the unipi digital input unit.
The &lt;code&gt;DIGND&lt;/code&gt; corresponds to the ground level of your 24VDC power source.
The &lt;code&gt;DI&lt;/code&gt; level is the signaling cable to one of the push buttons.
Pushing a button means the 24V level gets connected to the signal cable and thus giving a &lt;code&gt;DI&lt;/code&gt; level of 24V.
For more technical details, consult the &lt;a href="https://kb.unipi.technology/en:hw:02-neuron:description-of-io:01-description-of-di"&gt;unipi digital input KB&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://kb.unipi.technology/_media/en:hw:001_connection_of_io.png" alt="unipi digital inputs img"&gt;&lt;/p&gt;
&lt;p&gt;As can be seen from the following picture, making the physical connection between the incoming signal wires and the unipi units is easier than for the relay unit, shown earlier.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-13/cabinet-unipi-di.jpg" alt="cabinet-unipi-di"&gt;&lt;/p&gt;
&lt;p&gt;There is no need to have dedicated circuit breakers for the different signal wire cable runs; all of them can run on the same 24VDC power source.
Also, since the wire diameter is much lower, the screw connectors can just be used directly to terminate multiple wires and effectively provide electrical parallel connections.
Since each of the green screw connectors could have up to 4 signal wire connections and 1 ground level, I did use 4-wire SVV cable to connect them to terminal clamps higher up in the cabinet.
Note that I also did use my color coding again here (red / yellow / blue / white).&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;I started this post with some scary pictures of electrical wiring spaghetti.&lt;/p&gt;
&lt;p&gt;However, it turns out that with proper &lt;strong&gt;planning&lt;/strong&gt;, &lt;strong&gt;schematics&lt;/strong&gt; and some &lt;strong&gt;determination&lt;/strong&gt;, it was actually quite doable to wire the full cabinet together.
&lt;strong&gt;Terminal blocks&lt;/strong&gt; also help a lot in this process: it makes it easier to visually separate everything, removes the need to do all of the wiring in the beginning and enables rewiring at some later point in time.&lt;/p&gt;
&lt;p&gt;As before in the series, I did try to explain how to make the connection for &lt;strong&gt;push buttons&lt;/strong&gt; (inputs) and &lt;strong&gt;relays&lt;/strong&gt; (outputs).
The connections for the inputs were a bit simpler in the sense that the wiring runs on a safer lower voltage of 24VDC.
For the relays, as it runs on mains voltage, there are the additional constraints of the need to add in a circuit breaker for each electrical circuit as well as a higher wire gauge.
Consequently, I did some extra clamps to ensure the proper connections.&lt;/p&gt;
&lt;p&gt;I did opt for this approach at the time, since it seemed the most simple to me (comes closest to a &amp;ldquo;classical&amp;rdquo; setup), the most flexible in terms of rewiring (all wires are within reach) and does not depend on some proprietary, more expensive closed-off bus system.
In hindsight, the main &lt;strong&gt;disadvantage&lt;/strong&gt; of such a central star-configuration is the excessive amount of wiring you need to deal with.
For costs, you have the additional costs of the wiring material itself.
In terms of work, I did do all of the connections myself, so even though I did not lose any money on that, it surely was a very long, laborious process I might rather avoid in the future.
With that in mind, at some point in the future I will surely have a closer look at bus-based systems.&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;DALI is a bus system for digital light control. In theory, you could do full light control with DALI only (removing the need for relay-based control), but this means all of your light fixtures need to be DALI-aware.&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>Schematics</title><link>https://mhemeryck.xyz/posts/2021-07-06-home_automation_schematics/</link><pubDate>Tue, 06 Jul 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-07-06-home_automation_schematics/</guid><description>&lt;p&gt;In previous posts, I did discuss the importance of &lt;a href="https://mhemeryck.xyz/posts/2021-06-22-home_automation_architecture/"&gt;planning and design&lt;/a&gt; as well as details on the &lt;a href="https://mhemeryck.xyz/posts/2021-06-29-home_automation_wiring/"&gt;physical wires&lt;/a&gt; to be used.&lt;/p&gt;
&lt;p&gt;Here, I&amp;rsquo;d like to focus on two types of schematics which are not only very useful, but also mandatory for doing the electrical wiring for a Belgian house: the &lt;strong&gt;one-wire diagram&lt;/strong&gt; and the &lt;strong&gt;floor plan&lt;/strong&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;In previous posts, I did discuss the importance of &lt;a href="https://mhemeryck.xyz/posts/2021-06-22-home_automation_architecture/"&gt;planning and design&lt;/a&gt; as well as details on the &lt;a href="https://mhemeryck.xyz/posts/2021-06-29-home_automation_wiring/"&gt;physical wires&lt;/a&gt; to be used.&lt;/p&gt;
&lt;p&gt;Here, I&amp;rsquo;d like to focus on two types of schematics which are not only very useful, but also mandatory for doing the electrical wiring for a Belgian house: the &lt;strong&gt;one-wire diagram&lt;/strong&gt; and the &lt;strong&gt;floor plan&lt;/strong&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="one-wire-diagram"&gt;One-wire diagram&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;one-wire diagram&lt;/strong&gt; is an easy representation of all of the circuits and its related consumers and actors in an electrical installation.
As its name implies, it does not show each of the individual wires of the installation but rather how the cables are laid out logically.
Initially, this diagram seemed a bit strange to me in the sense that it seems overly simple and does not take the different conductors into account, i.e. the neutral, phases, earth wire &amp;hellip;
From the point of view of an installer, this diagram actually does make a lot of sense and includes all the details you need.&lt;/p&gt;
&lt;p&gt;Note that I no longer make the distinction between push buttons and relays, as was the case in the previous posts.&lt;/p&gt;
&lt;p&gt;According to my local regulations, it is not required and even discouraged to include low voltage wiring on the one-wire diagram since it adds to the overall complexity of the diagram.
This does have some implications on the representation of light-related circuits though.&lt;/p&gt;
&lt;p&gt;In a &amp;ldquo;traditional&amp;rdquo; light circuit, a light toggle switch forms a direct mechanical connection to the mains voltage, either through terminating one (one-pole connection) or two (two-pole connection) of the conductors in the wire.
Two-pole connections are considered more safe and are even mandatory for humid environments.
In terms of the one-wire diagram, the diagram would reflect which toggle switch would connect to which light.&lt;/p&gt;
&lt;p&gt;For my setup however, none of the lights are directly connected to the push buttons as these run on their own circuit of 24VDC, which is not on the diagram.
Given the push button assignment is controlled by software, it might not even make sense to include them in this diagram.
As for safety, since the push button is nowhere near the physical mains voltage cabling, this obviously much safer.&lt;/p&gt;
&lt;p&gt;To illustrate, here&amp;rsquo;s a small annotated (red) part of my installation&amp;rsquo;s one-wire diagram:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-06/onewire.png" alt="onewire"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The bottom left corner holds the entry-point from the street level where the main cable enters the house. In this case, it is a 400V &lt;a href="https://en.wikipedia.org/wiki/Three-phase_electric_power"&gt;three-phase connection&lt;/a&gt;: 3 phase wires plus 1 neutral&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;Straight after entering the house, the main cable passes through the utility company&amp;rsquo;s power meter.&lt;/li&gt;
&lt;li&gt;Main residual current device, ΔI=300 mA. This means that if the circuit breaker detects a leakage current ≥ 300 mA, it will automatically shut down the entire installation. Note that for Belgium, the 300 mA is mandatory for the overall installation, for circuits that pass through more humid environments (e.g. bathrooms, toilets), an additional 30 mA residual-current device is required.&lt;/li&gt;
&lt;li&gt;At this level, all of the various circuits are placed in parallel, each with their own circuit breaker.&lt;/li&gt;
&lt;li&gt;Wire specs: note that the circuit breakers need to match the requirements of the wires and the related consumers, as indicated in the &lt;a href="https://mhemeryck.xyz/posts/2021-06-29-home_automation_wiring/"&gt;physical wires&lt;/a&gt; post.&lt;/li&gt;
&lt;li&gt;This level just illustrates two kinds of consumers; first a power socket, then a generic consumer.&lt;/li&gt;
&lt;li&gt;Another example with emergency lighting.&lt;/li&gt;
&lt;li&gt;An example circuit with a 240VAC / 24VDC transformer.&lt;/li&gt;
&lt;li&gt;An example circuit for my solar panel installation.&lt;/li&gt;
&lt;li&gt;This is a circuit exclusively containing lights: each of the branches indicates which lights are wired against the same control point. This means that each of the branches are switched using the same switch, or in this case relay. Note that this does not relate to how they are wired electrically: typically everything is wired in parallel&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;This is another example of a circuit with lights.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="floor-plan"&gt;Floor plan&lt;/h2&gt;
&lt;p&gt;Next to the one-wire diagram, there&amp;rsquo;s an additional diagram that lays out the circuits and their consumers on a floor plan.
Low voltage wiring is also not required for the official floor plan, but it does make sense to have it mapped out somewhere, even if that would be on a separate diagram.
The push buttons are obviously also on this plan.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-07-06/layout.png" alt="layout"&gt;&lt;/p&gt;
&lt;p&gt;Essentially, it just re-uses the numbering from the one-wire diagram presented earlier, so I won&amp;rsquo;t discuss these further.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Even though these diagrams only represent part of the information, they proved to be really valuable to me along the way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;planning: since there&amp;rsquo;s a finite number of consumers you can add to a circuit, it forces you to think ahead of the number of consumers on a single wire run.&lt;/li&gt;
&lt;li&gt;installation: on the construction site, you really don&amp;rsquo;t want to spend too much time (re)thinking your setup.&lt;/li&gt;
&lt;li&gt;posterity: in case anything breaks down (e.g. a circuit breaker switching off), you just want an easy to digest overview of the whole thing.&lt;/li&gt;
&lt;/ul&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 voltage between each phase wire and the neutral wire is 240V&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;although it &lt;em&gt;could&lt;/em&gt; be wired in series, e.g. for a series of current-controlled LED fixtures&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>Wiring</title><link>https://mhemeryck.xyz/posts/2021-06-29-home_automation_wiring/</link><pubDate>Tue, 29 Jun 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-06-29-home_automation_wiring/</guid><description>&lt;p&gt;In this post, I will take a deeper dive into the bottom layer, the &lt;strong&gt;wiring&lt;/strong&gt;.
Different kinds of inputs and outputs can be discussed, but for the sake of simplicity, I will focus on a push button operating a lamp.
These components will also come back later on in the series.&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;In this post, I will take a deeper dive into the bottom layer, the &lt;strong&gt;wiring&lt;/strong&gt;.
Different kinds of inputs and outputs can be discussed, but for the sake of simplicity, I will focus on a push button operating a lamp.
These components will also come back later on in the series.&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;I will cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;caution: safety first&lt;/li&gt;
&lt;li&gt;principle: operating principle behind a light push button and relay&lt;/li&gt;
&lt;li&gt;conductors: the physical wires and cables themselves&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-mandatory-word-of-caution-"&gt;A mandatory word of caution (!)&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-29/danger.jpg" alt="danger"&gt;&lt;/p&gt;
&lt;p&gt;This post will touch a number of points where working with mains voltage is required.
If you do not have any background in working with electricity, please be sure you get help from a &lt;em&gt;qualified electrician&lt;/em&gt;.
Also, some of the details really only apply to my local regulations (Belgium), so make sure you are well aware of &lt;em&gt;your&lt;/em&gt; local regulations.
Related to this, some of the terminology used might be a poor translation of some term which would have been straightforward in my native language (Dutch).
Even though I had a background as an electrical engineer, I still got a local professional electrician involved to ensure the overall safety &amp;hellip;&lt;/p&gt;
&lt;h2 id="principle"&gt;Principle&lt;/h2&gt;
&lt;h3 id="relays"&gt;Relays&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-29/principle.png" alt="principle relay"&gt;&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Relay"&gt;relay&lt;/a&gt;&lt;/strong&gt; is an electrically operated switch.
The voltage over two of its terminals can then control (switch) the voltage over the two other terminals.
The control voltage will typically be on a safe, low voltage, whereas the other terminals can carry higher (mains) voltage.
You can use it to control any other electrically switched device, e.g. lights or motors.&lt;/p&gt;
&lt;h3 id="push-buttons"&gt;Push buttons&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-29/principle_push_button.png" alt="principle push button"&gt;&lt;/p&gt;
&lt;p&gt;Where the relay serves as the output, the &lt;a href="https://en.wikipedia.org/wiki/Push-button"&gt;push button&lt;/a&gt;, together with a digital input sensor, serves as &lt;strong&gt;the input&lt;/strong&gt;.
The push button itself is actually a simple mechanical contact.
Pushing the button results in the internal mechanical contact making an electrical contact.&lt;/p&gt;
&lt;p&gt;There are two &lt;strong&gt;types of contacts&lt;/strong&gt;: a &lt;em&gt;normally open&lt;/em&gt; (NO) contact or a &lt;em&gt;normally closed&lt;/em&gt; (NC) contact.
Normally open means that the mechanical contact maintains an open electrical contact for the majority of the time, unless the button is pressed.
Normally closed is then the opposite situation, meaning a closed electrical contact whenever the button is &lt;em&gt;not&lt;/em&gt; pressed.
For push buttons, such as those to operate lamps, an NO contact would typically be used.
NC contacts make sense for situations where you want to be sure that the electrical contact is always guaranteed, e.g. a sensor for an alarm system.
Loss of signal for an NC contact might indicate the sensor has been tampered with.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;voltage&lt;/strong&gt; used for a push button is typically not mains voltage (240VAC), but rather a lower, safer voltage like 24VDC.
Typical digital input sensors such as those found on DIY devices like the &lt;a href="https://www.arduino.cc/en/Tutorial/Foundations/DigitalPins"&gt;arduino&lt;/a&gt; or &lt;a href="https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#gpio-and-the-40-pin-header"&gt;raspberry pi&lt;/a&gt; would operate on 5V or 3.3V, respectively.
For wiring a house, this is not an option because of the longer line runs and the resulting voltage drop related to the resistance of the wiring itself.&lt;/p&gt;
&lt;p&gt;An important thing to consider in the push button - lamp constellation is that the way the push button is used is &lt;strong&gt;stateless&lt;/strong&gt;.
Considering the lamp is off, one push would switch the light on.
Another push would switch it back off again.
Based on the position of the push button itself, you would not be able to tell the state of the light.
It differs from a rocker switch in that it only maintains the contact for the duration of the push of the button.
While these observations might be rather trivial, for the purposes of modeling them higher up on the stack, they are relevant.&lt;/p&gt;
&lt;h2 id="conductors-belgium-only"&gt;Conductors (Belgium only)&lt;/h2&gt;
&lt;h3 id="relays-1"&gt;Relays&lt;/h3&gt;
&lt;p&gt;The following image shows a typical tube containing 3 wires with diameters of 1.5mm², fit for lights.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-29/cable-3g15.jpg" alt="cable 3G 1.5mm2"&gt;&lt;/p&gt;
&lt;p&gt;For the cables carrying mains voltage, the following &lt;strong&gt;rules&lt;/strong&gt; are relevant (non-exhaustive see e.g. &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;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;a href="https://www.plantyn.com/webshop/product/project-huisinstallatie-naslagwerk-9789030142942"&gt;project huisinstallatie naslagwerk&lt;/a&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for circuits up to 16A (typically lights), a min 1.5 mm² cross-section wire is to be used&lt;/li&gt;
&lt;li&gt;for circuits up to 20A (lights and / or others like power sockets), a min 2.5 mm² cross-section wire is to be used&lt;/li&gt;
&lt;li&gt;each of the circuits need to have proper matching circuit breakers&lt;/li&gt;
&lt;li&gt;all cables consist of neutral wire, live wire and an earth wire&lt;/li&gt;
&lt;li&gt;color coding:
&lt;ul&gt;
&lt;li&gt;neutral: blue&lt;/li&gt;
&lt;li&gt;live: brown&lt;/li&gt;
&lt;li&gt;earth wire: green / yellow&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;max 8 consumers (lights, power sockets) per circuit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;circuit breakers&lt;/em&gt; protect &lt;em&gt;your wires&lt;/em&gt; from burning up&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Residual-current_device"&gt;residual-current devices&lt;/a&gt;&lt;/em&gt; protect &lt;em&gt;living things&lt;/em&gt;!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that this is only a subset of the rules and pointers to keep into account while doing the physical wiring.
Other things to consider are the type of cable (in a plastic tube or as a cable), solid vs stranded wire, stiff vs flexible wire, etc. &amp;hellip;&lt;/p&gt;
&lt;h3 id="push-buttons-1"&gt;Push buttons&lt;/h3&gt;
&lt;p&gt;Contrary to the cabling for the lights, the push buttons run on the lower, safer voltage of 24VDC.
Consequently, the wire diameter can be a lot smaller and there is no strict requirement on the use of circuit breakers.
An additional advantage in terms of safety compared to a more &amp;ldquo;traditional&amp;rdquo; installation is that the push button itself is never connected to the consumer on the higher mains voltage.&lt;/p&gt;
&lt;p&gt;The following image shows an &lt;em&gt;SVV&lt;/em&gt; &lt;strong&gt;signal cable&lt;/strong&gt;, which is typically used in Belgium for analog signaling applications.
I did some online research into the international naming for this type of cable, but there does not seem to be any.
For the sake of the discussion here however, the characteristics are actually more important:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;small diameters, on the order of 0.8mm²&lt;/li&gt;
&lt;li&gt;solid core, as this makes connecting to the push buttons easier as they mostly have screw connectors&lt;/li&gt;
&lt;li&gt;multiple wires in 1 cable (as shown in the picture). These cables mostly come with either 4, 8, 12 or 16 wires.&lt;/li&gt;
&lt;li&gt;visually clearly differing wires, i.e. highly contrasting colors for the wires&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-29/cable-svv.jpg" alt="cable SVV"&gt;&lt;/p&gt;
&lt;p&gt;The idea of having this multitude of wires in one signal cable is that you would ideally need &lt;strong&gt;one cable run&lt;/strong&gt; for e.g. the push buttons for one floor.
Each push button contact then uses one of the wires of the signal cable.
Apart from the push button contacts, you also need to reserve one of the wires for the positive level.
Essentially, the push buttons thus internally connect 1 signal wire (on ground level) to the common positive level &amp;ndash; which can be detected by your digital input readout.&lt;/p&gt;
&lt;p&gt;As a side note, some manufacturers also have push buttons with a built-in &lt;strong&gt;LED light&lt;/strong&gt;.
If you want to provide power to that LED, you would also need to reserve an extra wire for that feature.&lt;/p&gt;
&lt;p&gt;Concerning the &lt;strong&gt;various colors&lt;/strong&gt; available in the signal cable, I did find it useful to have a fixed order in which to assign them to the different inputs.
I would always use the red wire for the positive (24V) level.
The remainder of the wires would then be assigned according to the &lt;a href="https://en.wikipedia.org/wiki/Electromagnetic_spectrum"&gt;colors on the visible electromagnetic spectrum&lt;/a&gt;.
For example, you could go from black, grey, brown, orange, green, blue, &amp;hellip; all the way to white.&lt;/p&gt;
&lt;p&gt;The following image shows the &lt;strong&gt;wiring diagram&lt;/strong&gt; for a simple NO &lt;a href="https://www.niko.eu/en/products/switching-material-and-socket-outlets/switches-and-sockets/push-button-with-3-connection-terminals-n-o-productmodel-niko-025f93c2-c921-59b0-b204-af07ed3ee2a8"&gt;push button from Niko&lt;/a&gt;, a Belgian manufacturer of electrical switches and sockets.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/2021-06-29/push-button-wiring.png" alt="push-button-wiring"&gt;&lt;/p&gt;
&lt;p&gt;The diagram indicates the push button can be used to switch voltages up the 230VAC, but since we&amp;rsquo;re only interested in for the signaling application, it might just as well be a lower voltage.
Also visible is the optional LED light.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;The focus of this post was first to describe the &lt;strong&gt;basic principle of some key components&lt;/strong&gt;, the relay and the push button.
I did want to highlight what exactly they are and also how the operating principle relates to the modelling higher up in the stack, e.g. the fact that I mostly consider the push button to be stateless.&lt;/p&gt;
&lt;p&gt;The second part of the post dealt with the details of the &lt;strong&gt;physical electrical conductors&lt;/strong&gt; connecting to and from these components.
Relays, for lights, use 3 x 1.5mm², whereas higher loads require a higher wire gauge of 3 x 2.5mm².
The push buttons can use a lower wire gauge of e.g. 0.8mm² since you would run these on a safer, lower voltage of 24VDC.
While the regulations pertaining to the wiring are specific to my country&amp;rsquo;s regulations, I still hope someone else might learn from it!&lt;/p&gt;
&lt;p&gt;Finally, always keep safety into account!
Even you feel you are really knowledgeable, I still think it was an added value for me to involve a local professional electrician to get proper hands-on advice.&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;ISBN/EAN 9789030142942&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;the official Belgian regulation is the &lt;a href="https://economie.fgov.be/nl/publicaties/algemeen-reglement-op-de"&gt;AREI&lt;/a&gt;&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>Why</title><link>https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/</link><pubDate>Tue, 15 Jun 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/</guid><description>&lt;p&gt;This post shall be the first in a series of blog posts where I intend to outline my home automation setup.&lt;/p&gt;
&lt;p&gt;For this first post, I want to start by first explaining &lt;em&gt;why&lt;/em&gt; I worked on this.
Secondly, I want to give some more attention to the &lt;em&gt;vision&lt;/em&gt; and guiding principles I will use further on.
At the end, there is an overview of all the future posts that I have currently planned for this series.&lt;/p&gt;</description><content:encoded>&lt;p&gt;This post shall be the first in a series of blog posts where I intend to outline my home automation setup.&lt;/p&gt;
&lt;p&gt;For this first post, I want to start by first explaining &lt;em&gt;why&lt;/em&gt; I worked on this.
Secondly, I want to give some more attention to the &lt;em&gt;vision&lt;/em&gt; and guiding principles I will use further on.
At the end, there is an overview of all the future posts that I have currently planned for this series.&lt;/p&gt;
&lt;h2 id="why"&gt;Why&lt;/h2&gt;
&lt;p&gt;I am an electrical engineer by &lt;strong&gt;background&lt;/strong&gt;, specialized in signal processing and programming for embedded devices (high complexity on small systems).
Within my career, I did make the switch to software engineering, doing the opposite (low complexity on large systems).&lt;/p&gt;
&lt;p&gt;One of the things I really did enjoy while building this system (and by extension: my house) was being able to &lt;strong&gt;build something from the ground up&lt;/strong&gt;.
Along the way, I was able to learn about a myriad of things: electricity and lighting, IoT hardware, various concepts, paradigms, languages &amp;hellip; related to software development, networking and network-related infrastructure &amp;hellip;
Non-technical aspects, such as project management, were also quite important.&lt;/p&gt;
&lt;p&gt;Even though I feel there&amp;rsquo;s still a lot of room left for improvement, I did consider that &lt;strong&gt;now was a good time do a write-up&lt;/strong&gt; of everything accomplished thus far.
Firstly, I believe that writing everything down would help me in documenting everything.
Additionally, this provides the opportunity to reflect back on those things that went well as well as other parts that might still benefit from some improvements.
The process of writing itself is also a good exercise to improve on my technical writing (in a non-native language).
Possibly these posts might help or even inspire my readers to get into this topic.
Lastly, I do want to reach out to a larger community and get feedback.&lt;/p&gt;
&lt;h2 id="vision"&gt;Vision&lt;/h2&gt;
&lt;p&gt;There are many different approaches to build a home automation system.
Since the open source &lt;a href="https://www.home-assistant.io/"&gt;home assistant&lt;/a&gt; platform plays a central role in my system, I did borrow some inspiration from &lt;a href="https://www.home-assistant.io/blog/2016/01/19/perfect-home-automation/"&gt;home assistant&amp;rsquo;s vision&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Considering my own ideal system, the following are the key ideas.&lt;/p&gt;
&lt;h3 id="wired"&gt;Wired&lt;/h3&gt;
&lt;p&gt;A wired solution has the advantage of not being affected by unreliable wireless links.
Also, there&amp;rsquo;s generally no issue of ensuring a power supply, so there is no need to keep an eye on batteries.
The disadvantage is however that the overall setup is less flexible and needs to be thought out really well in advance.
The process of wiring itself can also be quite time consuming, costly or simply impossible, depending on your housing situation.&lt;/p&gt;
&lt;h3 id="open"&gt;Open&lt;/h3&gt;
&lt;p&gt;All hardware and software should be &amp;ldquo;open&amp;rdquo; in the sense that it can readily understood and changes can be made to it.
It should be possible to take a given component and swap it out for another one, unrelated to the original vendor.
On the software-side, it should be possible to program against it with standard protocols using general-purpose programming languages.
Combining different sorts of hardware and software should be straightforward.&lt;/p&gt;
&lt;h3 id="local"&gt;Local&lt;/h3&gt;
&lt;p&gt;The home automation should always work, even in the absence of an Internet connection.
The data from the home automation system itself should be owned by yourself.
This does not mean that you cannot use common network protocols, you can still have everything connected via a local network (LAN).
Nor does this mean that you cannot have non-critical integrations which are web-based, e.g. an integration that pulls in a weather forecast.
Ideally though, the majority of communications the home automations needs to do to function properly should be in the home itself.&lt;/p&gt;
&lt;h3 id="low-cost"&gt;Low cost&lt;/h3&gt;
&lt;p&gt;Home automation systems have been around for quite some time, but what has changed the latest years is the availability of both hardware and software that allows to mix and match.
Also open hardware platforms like the raspberry pi have made it much easier to develop own platforms.
The disadvantage of such DIY platforms is the lower intrinsic robustness, compared to an off-the-shelf commercial solution.&lt;/p&gt;
&lt;h3 id="reproducible"&gt;Reproducible&lt;/h3&gt;
&lt;p&gt;This is a key idea coming from my background as a software developer.
Essentially, this means that the provisioning and deployment of the overall setup should be automated to the extent that in case of any failure it can readily be reconstructed.&lt;/p&gt;
&lt;h2 id="posts"&gt;Posts&lt;/h2&gt;
&lt;p&gt;In the following series of blog posts, I will outline more of the details of the actual setup that I did design and build.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://mhemeryck.xyz/posts/2021-06-15-home_automation_why/"&gt;why&lt;/a&gt; introduction to the series, this post&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mhemeryck.xyz/posts/2021-06-22-home_automation_architecture/"&gt;architecture&lt;/a&gt; topology of the home automation components and different levels of abstraction&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mhemeryck.xyz/posts/2021-06-29-home_automation_wiring/"&gt;wiring&lt;/a&gt; details about basic components (relays and push buttons) and the related wiring&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mhemeryck.xyz/posts/2021-07-06-home_automation_schematics/"&gt;schematics&lt;/a&gt; how to represent the electrical wiring in a one-wire diagram and floor plan&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mhemeryck.xyz/posts/2021-07-13-home_automation_cabinet/"&gt;cabinet&lt;/a&gt; a peek inside my electrical cabinet&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mhemeryck.xyz/posts/2021-07-20-home_automation_hardware/"&gt;hardware&lt;/a&gt; details about the central hardware units I did use&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mhemeryck.xyz/posts/2021-07-27-home_automation_software/"&gt;software&lt;/a&gt; different layers of interfacing software&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mhemeryck.xyz/posts/2021-08-03-home_automation_service/"&gt;service&lt;/a&gt; bringing it all together with home assistant&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Bootstrapping a pi4</title><link>https://mhemeryck.xyz/posts/2021-02-21-bootstrapping/</link><pubDate>Sun, 21 Feb 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-02-21-bootstrapping/</guid><description>&lt;p&gt;I&amp;rsquo;ve recently bought myself a &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-400/"&gt;raspberry pi 400&lt;/a&gt;, a small form factor keyboard with a &lt;em&gt;raspberry pi 4 4GB RAM&lt;/em&gt; built into it.
In hindsight, there are probably very few scenarios in which such a computer makes actually makes sense to me, but it still looks quite nice.
I first took it for a spin using the raspberry pi OS that was on the SD card that came along with the set, but while I can acknowledge that it works quite OK out of the box, it just looks so ugly.
Henceforth, I did decide on installing &lt;a href="https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-4"&gt;arch for ARM&lt;/a&gt; on it.
Since I did notice that I need to rethink every time again what are the best tools to install on a fresh install, this is a write-up of the most important tools I would always install.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I&amp;rsquo;ve recently bought myself a &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-400/"&gt;raspberry pi 400&lt;/a&gt;, a small form factor keyboard with a &lt;em&gt;raspberry pi 4 4GB RAM&lt;/em&gt; built into it.
In hindsight, there are probably very few scenarios in which such a computer makes actually makes sense to me, but it still looks quite nice.
I first took it for a spin using the raspberry pi OS that was on the SD card that came along with the set, but while I can acknowledge that it works quite OK out of the box, it just looks so ugly.
Henceforth, I did decide on installing &lt;a href="https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-4"&gt;arch for ARM&lt;/a&gt; on it.
Since I did notice that I need to rethink every time again what are the best tools to install on a fresh install, this is a write-up of the most important tools I would always install.&lt;/p&gt;
&lt;p&gt;The key 2 tools are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.passwordstore.org/"&gt;pass&lt;/a&gt; password manager&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yadm.io/"&gt;yadm&lt;/a&gt; yet-another-dotfile-manager&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rationale behind it is the following: I use &lt;code&gt;pass&lt;/code&gt; to be able to get easy access to all my passwords and logins, &lt;code&gt;yadm&lt;/code&gt; manages all the other dotfiles for me.&lt;/p&gt;
&lt;h2 id="arch"&gt;Arch&lt;/h2&gt;
&lt;p&gt;For a &amp;ldquo;regular&amp;rdquo; arch install, see the &lt;a href="https://wiki.archlinux.org/index.php/installation_guide"&gt;arch installation guide&lt;/a&gt;.
In this case, the installation was a bit atypical since it was for an ARM platform, see &lt;a href="https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-4"&gt;arch for ARM&lt;/a&gt; for this (I did use the ARMv7 installation guide).&lt;/p&gt;
&lt;p&gt;Other noteworthy steps in the installation &amp;hellip;&lt;/p&gt;
&lt;h2 id="arch-linux-arm-noisy-boot"&gt;arch linux arm: noisy boot&lt;/h2&gt;
&lt;p&gt;Terminal would show all of the kernel audit messages; add &lt;code&gt;audit=0&lt;/code&gt; to command line options &lt;code&gt;/boot/cmdline.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Remove noisy welcome message&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm /etc/motd
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="helpers"&gt;Helpers&lt;/h2&gt;
&lt;p&gt;Some helpers to install with the subsequent steps:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -Syu --noconfirm zsh sudo which vim git openssh libfido2
pacman -S --noconfirm --needed base-devel
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="add-a-user"&gt;Add a user&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;touch /etc/skel/.zshrc
groupadd sudo
useradd -m $USER -g $GROUP -G sudo -s /usr/bin/zsh
echo &amp;quot;%sudo ALL=(ALL) NOPASSWD: ALL&amp;quot; &amp;gt; /etc/sudoers.d/$USER
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update password&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;passwd $USER
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="install-pikaur"&gt;Install &lt;code&gt;pikaur&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git clone https://aur.archlinux.org/pikaur.git
cd pikaur
makepkg -fsri
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="some-more-helpers"&gt;Some more helpers&lt;/h2&gt;
&lt;p&gt;Some more helpers I&amp;rsquo;d always like to keep around&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pikaur -S --noconfirm ctags oh-my-zsh-git ripgrep fzf bat tmux
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="git-default-config"&gt;git default config&lt;/h2&gt;
&lt;p&gt;Since we&amp;rsquo;ll be using &lt;code&gt;git&lt;/code&gt;, you&amp;rsquo;ll need to define some initial config&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global user.email &amp;lt;email&amp;gt;
git config --global user.name &amp;quot;&amp;lt;name&amp;gt;&amp;quot;
git config --global init.defaultBranch master
git config --global pull.rebase true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="gpg"&gt;&lt;code&gt;gpg&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Import stubs from yubikey&lt;/p&gt;
&lt;p&gt;Install and enable smart card reader:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pikaur -S ccid
systemctl enable pcscd
systemctl start pcscd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fetch keys from card&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --edit-card
&amp;gt; fetch
&amp;gt; quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Get the key id&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;KEYID=$(gpg --with-colons --fingerprint | awk -F: '$1 == &amp;quot;fpr&amp;quot; {print $10;}' | head -n1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update the trust of the key&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --edit-key $KEYID
&amp;gt; trust
&amp;gt; 5
&amp;gt; quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable ssh-agent using gpg-agent (temporarily; the final &lt;code&gt;zshrc&lt;/code&gt; file will contain the proper setup)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export SSH_AUTH_SOCK=&amp;quot;$(gpgconf --list-dirs agent-ssh-socket)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tell the gpg-agent to prompt on the current TTY:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg-connect-agent updatestartuptty /bye
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="pass"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Install pass&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pikaur -S pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Init pass&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass init $KEYID
pass git remote add origin git@gitlab.com:&amp;lt;repo&amp;gt;
pass git pull
pass git reset --hard origin/master
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="yadm"&gt;&lt;code&gt;yadm&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;pikaur -S yadm
yadm clone git@gitlab.com:&amp;lt;repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, I have all the minimum tools I would use for my daily work.&lt;/p&gt;
&lt;h2 id="ui"&gt;UI&lt;/h2&gt;
&lt;p&gt;Even though I would spend most of my life on the command line, I also a graphical environment to work in.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not going to delve to deeply into that, since I mostly prefer vanilla &lt;a href="https://www.gnome.org"&gt;gnome3&lt;/a&gt; for that, combined with &lt;a href="https://wiki.gnome.org/Projects/GDM"&gt;&lt;code&gt;gdm&lt;/code&gt;&lt;/a&gt; for a login manager.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;My initial goal was to find a way of completely automating my initial installs, but in the end I figured this wasn&amp;rsquo;t what I really needed, because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;distro-specific: I don&amp;rsquo;t always run on the same distro, so not everything is portable&lt;/li&gt;
&lt;li&gt;other requirements: for unattended installs (servers), I don&amp;rsquo;t want / need to copy all my secrets on there (albeit encrypted or not)&lt;/li&gt;
&lt;li&gt;outdated: for my daily driver install, I have no need to often keep on reprovisioning my install. By the time I would have a need to reprovision, a lot of the setup has probably changed, so it makes more sense to (vaguely) document some of the &lt;em&gt;basic&lt;/em&gt; steps instead of a fully automated machine image.&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Updating GPG subkeys</title><link>https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/</link><pubDate>Tue, 09 Jul 2019 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/</guid><description>&lt;p&gt;This is just a list of things to consider when updating my GPG subkeys (encryption, signing, authentication) for the particular setup I use on a daily basis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;one master key without any expiry date to generate and revoke subkeys&lt;/li&gt;
&lt;li&gt;I use &lt;a href="https://www.passwordstore.org/"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; as my password manager.
&lt;ol&gt;
&lt;li&gt;the basic idea is that it&amp;rsquo;s a command-line password manager that uses gpg under the hood for encrypting / decrypting the passwords.&lt;/li&gt;
&lt;li&gt;the passwords are encrypted with an encryption subkey&lt;/li&gt;
&lt;li&gt;pass allows managing its history with git, linking the storage on a remote gitlab / github server.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;the subkeys are only stored on my yubikey&lt;/li&gt;
&lt;li&gt;the subkeys are always valid for a period of one year&lt;/li&gt;
&lt;li&gt;the yubikey I use also has NFC, which makes it possible to use it on my android phone as well&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I recently needed to go through this process and did forget to re-encrypt &amp;hellip;&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is just a list of things to consider when updating my GPG subkeys (encryption, signing, authentication) for the particular setup I use on a daily basis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;one master key without any expiry date to generate and revoke subkeys&lt;/li&gt;
&lt;li&gt;I use &lt;a href="https://www.passwordstore.org/"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; as my password manager.
&lt;ol&gt;
&lt;li&gt;the basic idea is that it&amp;rsquo;s a command-line password manager that uses gpg under the hood for encrypting / decrypting the passwords.&lt;/li&gt;
&lt;li&gt;the passwords are encrypted with an encryption subkey&lt;/li&gt;
&lt;li&gt;pass allows managing its history with git, linking the storage on a remote gitlab / github server.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;the subkeys are only stored on my yubikey&lt;/li&gt;
&lt;li&gt;the subkeys are always valid for a period of one year&lt;/li&gt;
&lt;li&gt;the yubikey I use also has NFC, which makes it possible to use it on my android phone as well&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I recently needed to go through this process and did forget to re-encrypt &amp;hellip;&lt;/p&gt;
&lt;p&gt;The main steps are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;generate new subkeys&lt;/li&gt;
&lt;li&gt;pass: re-encrypt with &lt;em&gt;both&lt;/em&gt; sets of keys available&lt;/li&gt;
&lt;li&gt;move the new keys to the card&lt;/li&gt;
&lt;li&gt;backup the master key again and remove all keys from the local machine&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="generate-the-new-subkeys"&gt;Generate the new subkeys&lt;/h2&gt;
&lt;p&gt;First, put the master key back on the local machine, such that we can generate new subkeys.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re really paranoid, you should consider something like &lt;a href="https://tails.boum.org/"&gt;tails&lt;/a&gt; to have a safe live OS for doing all these operations.&lt;/p&gt;
&lt;p&gt;Add the key again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --import priv.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;code&gt;priv.asc&lt;/code&gt; obviously is the backup of your master key.&lt;/p&gt;
&lt;p&gt;Start generating new subkeys for that master key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --edit-key &amp;lt;foo@bar.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will now get a prompt for doing operations on the master key. Go for &lt;code&gt;addkey&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg&amp;gt;addkey
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: in case you didn&amp;rsquo;t have your master key, this will not work.&lt;/p&gt;
&lt;p&gt;You can now add your signing and encryption keys; some parameters I&amp;rsquo;d like to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4096b size&lt;/li&gt;
&lt;li&gt;1y expiration date&lt;/li&gt;
&lt;li&gt;RSA for both encryption / signing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;don&amp;rsquo;t forget to call &lt;code&gt;save&lt;/code&gt; in the end.&lt;/p&gt;
&lt;p&gt;You can now list all the keys:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg -K
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="re-encrypt-all-passwords"&gt;Re-encrypt all passwords&lt;/h2&gt;
&lt;p&gt;This is really important (I messed up big time here): before you remove any older keys, make sure that they are re-encrypted with the new subkey!&lt;/p&gt;
&lt;p&gt;List the fingerprint of the new subkey you&amp;rsquo;d like to use for re-encrypting:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --fingerprint --fingerprint &amp;lt;foo@bar.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Re-encrypt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass init &amp;lt;fingerprint subkey&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally, already push them out&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass git push
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="push-the-new-subkeys-to-the-card"&gt;Push the new subkeys to the card&lt;/h2&gt;
&lt;p&gt;Again edit the key&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --edit-key &amp;lt;foo@bar.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Select the subkey&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;key &amp;lt;N&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where N is the count in the list of keys (visually not that obvious)&lt;/p&gt;
&lt;p&gt;Add the key to the card&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;keytocard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;select the slot to save to (encryption for encryption, signing for signing &amp;hellip;)&lt;/p&gt;
&lt;p&gt;and save&lt;/p&gt;
&lt;h2 id="remove-the-keys-from-the-local-computer-again"&gt;Remove the keys from the local computer again&lt;/h2&gt;
&lt;p&gt;First, make a backup of everything again; see &lt;a href="http://www.racoonlab.com/2013/02/how-to-remove-the-private-master-key-from-your-laptop/"&gt;gpg backup tutorial&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Export your private key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg -a --export-secret-keys &amp;lt;foo@bar.com&amp;gt; &amp;gt; priv.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Export public key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg -a --export &amp;lt;foo@bar.com&amp;gt; &amp;gt; pub.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally, already push it out to a key server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --send-keys &amp;lt;fingerprint&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, delete everything (best to unplug the key card):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --delete-secret-key &amp;lt;fingerprint&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Plug the key card back in and sync&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --card-status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the keys are available albeit from the key card:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg -K
&lt;/code&gt;&lt;/pre&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><item><title>About me</title><link>https://mhemeryck.xyz/about/</link><guid>https://mhemeryck.xyz/about/</guid><description>&lt;h2 id="profile"&gt;Profile&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/about/avatar-160.png" alt="Martijn Hemeryck"&gt;&lt;/p&gt;
&lt;p&gt;I am an engineer with an electrical engineering background, originally specialized in embedded software and digital signal processing.
Over time I branched into broader backend, cloud, infrastructure, and full-stack software work.&lt;/p&gt;
&lt;p&gt;I like to analyze systems, take them apart, design them, and build them up again.
I am a developer and maker at heart, with a practical drive to solve problems end to end.&lt;/p&gt;
&lt;h2 id="engineering-focus"&gt;Engineering Focus&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Product-facing platforms where technical design, operational reliability, and domain understanding all matter.&lt;/li&gt;
&lt;li&gt;Backend and integration systems that remain understandable over time.&lt;/li&gt;
&lt;li&gt;Cloud infrastructure, CI/CD, and operational tooling that keep systems deployable and maintainable.&lt;/li&gt;
&lt;li&gt;Practical engineering across application code, infrastructure, data, operations, and documentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="selected-work"&gt;Selected Work&lt;/h2&gt;
&lt;h3 id="financial-data-platforms"&gt;Financial-Data Platforms&lt;/h3&gt;
&lt;p&gt;At Codabox by Wolters Kluwer, I work on backend systems that retrieve, process, and deliver financial documents for accountants and bookkeepers.
My work spans analysis, backend implementation, Go services, cloud infrastructure, support tooling, and production operations.&lt;/p&gt;</description><content:encoded>&lt;h2 id="profile"&gt;Profile&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://mhemeryck.xyz/about/avatar-160.png" alt="Martijn Hemeryck"&gt;&lt;/p&gt;
&lt;p&gt;I am an engineer with an electrical engineering background, originally specialized in embedded software and digital signal processing.
Over time I branched into broader backend, cloud, infrastructure, and full-stack software work.&lt;/p&gt;
&lt;p&gt;I like to analyze systems, take them apart, design them, and build them up again.
I am a developer and maker at heart, with a practical drive to solve problems end to end.&lt;/p&gt;
&lt;h2 id="engineering-focus"&gt;Engineering Focus&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Product-facing platforms where technical design, operational reliability, and domain understanding all matter.&lt;/li&gt;
&lt;li&gt;Backend and integration systems that remain understandable over time.&lt;/li&gt;
&lt;li&gt;Cloud infrastructure, CI/CD, and operational tooling that keep systems deployable and maintainable.&lt;/li&gt;
&lt;li&gt;Practical engineering across application code, infrastructure, data, operations, and documentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="selected-work"&gt;Selected Work&lt;/h2&gt;
&lt;h3 id="financial-data-platforms"&gt;Financial-Data Platforms&lt;/h3&gt;
&lt;p&gt;At Codabox by Wolters Kluwer, I work on backend systems that retrieve, process, and deliver financial documents for accountants and bookkeepers.
My work spans analysis, backend implementation, Go services, cloud infrastructure, support tooling, and production operations.&lt;/p&gt;
&lt;h3 id="home-automation"&gt;Home Automation&lt;/h3&gt;
&lt;p&gt;I designed, built, and maintain a whole-home automation setup that connects wired controls, UniPi hardware, Modbus, DALI, MQTT, Home Assistant, and custom services.
The project reflects how I approach long-lived systems across software, hardware-adjacent, and infrastructure boundaries.&lt;/p&gt;
&lt;h3 id="technical-writing"&gt;Technical Writing&lt;/h3&gt;
&lt;p&gt;I keep a technical blog about engineering projects, infrastructure, and lessons learned.
The writing is mostly practical: documenting decisions, implementation details, and the tradeoffs behind them.&lt;/p&gt;
&lt;h2 id="links"&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Full CV: &lt;a href="https://mhemeryck.xyz/cv/"&gt;web&lt;/a&gt; and &lt;a href="https://mhemeryck.xyz/cv/martijn-hemeryck-cv.pdf"&gt;PDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/mhemeryck"&gt;mhemeryck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://linkedin.com/in/martijn-hemeryck-67896245"&gt;martijn-hemeryck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Blog: &lt;a href="https://mhemeryck.xyz/posts/"&gt;Martyn&amp;rsquo;s musings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>CV</title><link>https://mhemeryck.xyz/cv/</link><guid>https://mhemeryck.xyz/cv/</guid><description>&lt;h2 id="profile"&gt;Profile&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m Martijn, a software developer with an electrical engineering background.&lt;/p&gt;
&lt;p&gt;During my studies, I specialized in embedded software and digital signal processing.&lt;/p&gt;
&lt;p&gt;Over time I branched into broader backend, cloud, infrastructure, and full-stack software development.
I focus on product-facing platforms where technical design, operational reliability, and domain understanding all matter.&lt;/p&gt;
&lt;p&gt;I like to analyze systems, take them apart, design them, and build them up again.
I am a developer and maker at heart, with a practical drive to solve problems end-to-end.&lt;/p&gt;</description><content:encoded>&lt;h2 id="profile"&gt;Profile&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m Martijn, a software developer with an electrical engineering background.&lt;/p&gt;
&lt;p&gt;During my studies, I specialized in embedded software and digital signal processing.&lt;/p&gt;
&lt;p&gt;Over time I branched into broader backend, cloud, infrastructure, and full-stack software development.
I focus on product-facing platforms where technical design, operational reliability, and domain understanding all matter.&lt;/p&gt;
&lt;p&gt;I like to analyze systems, take them apart, design them, and build them up again.
I am a developer and maker at heart, with a practical drive to solve problems end-to-end.&lt;/p&gt;
&lt;h2 id="experience"&gt;Experience&lt;/h2&gt;
&lt;h3 id="codabox-by-wolters-kluwer"&gt;&lt;a href="https://codabox.com"&gt;Codabox By Wolters Kluwer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Senior Software Engineer&lt;/strong&gt;
August 2018 - present&lt;/p&gt;
&lt;p&gt;Codabox provides accounting data for accountants and bookkeepers.
The platform integrates with source providers such as banks, payroll offices, credit card providers, and e-invoicing solutions.&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;
It handles both sourcing and delivery of accounting data, as well as the consent flows required to make those end-to-end data exchanges possible.
My work has moved across backend and frontend development, analysis, architecture, cloud infrastructure, and technical leadership on several of these systems.&lt;/p&gt;
&lt;p&gt;Selected projects and achievements:&lt;/p&gt;
&lt;h4 id="2025-2026-sourcery-and-coda-domain-tooling"&gt;2025-2026: Sourcery And CODA Domain Tooling&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;/h4&gt;
&lt;p&gt;Accountants need a full overview of their clients&amp;rsquo; bank statements data, even if the banks cannot provide it directly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Helped build services that generate historical bank statements from open-banking data.&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;Steer the (sourcery) team toward a Go-first implementation model.&lt;/li&gt;
&lt;li&gt;Built the monorepo foundation for application code, infrastructure code and CI pipelines.&lt;/li&gt;
&lt;li&gt;Designed a serverless-first AWS Lambda infrastructure foundation.&lt;/li&gt;
&lt;li&gt;Developed low-allocation CODA bank-statement parsing and generation code.&lt;/li&gt;
&lt;li&gt;Introduced LLM-assisted tooling and project context to make team workflows easier to navigate.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2023-2025-partner-sftp-and-bank-connectivity"&gt;2023-2025: Partner SFTP And Bank Connectivity&lt;/h4&gt;
&lt;p&gt;Our team needed to take direct responsibility for VPN-based banking and payroll connectivity, where reliable file exchange depends on both network setup and partner-specific validation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Onboarded and stabilized SFTP-based file exchange with banks, payroll offices, and external partners.&lt;/li&gt;
&lt;li&gt;Worked through network-level integration details such as VPNs, allowlisted IPs, credentials, partner validation, and operational handover.&lt;/li&gt;
&lt;li&gt;Provisioned connectivity and supporting infrastructure with infrastructure-as-code according to security and operational requirements.&lt;/li&gt;
&lt;li&gt;Reduced the risk of silent data loss, failed transfers, and manual troubleshooting in partner integrations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2023-caro--credit-card-statement-activation-flows"&gt;2023: CARO / Credit Card Statement Activation Flows&lt;/h4&gt;
&lt;p&gt;Codabox needed to onboard a new credit-card statement source provider while the team was moving new development toward a Go-first implementation model.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Helped migrate the implementation endpoint by endpoint from Java to Go while keeping both systems operational in production.&lt;/li&gt;
&lt;li&gt;Supported the new source-provider onboarding as part of the migration path.&lt;/li&gt;
&lt;li&gt;Helped combine business delivery and modernization instead of treating them as separate tracks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2021-2022-consent-and-mandate-signing-platform"&gt;2021-2022: Consent And Mandate Signing Platform&lt;/h4&gt;
&lt;p&gt;Codabox needed a digital mandate signing flow while onboarding a new source bank, replacing a process that still depended largely on paper mandates.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Helped design and implement the consent and mandate signing flow for clients of accountants.&lt;/li&gt;
&lt;li&gt;Integrated a third-party digital signing provider with existing Codabox systems.&lt;/li&gt;
&lt;li&gt;Supported fully digital processing for both sharing mandates.&lt;/li&gt;
&lt;li&gt;The resulting production flow was later adopted by most integrated banks and remains in use.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2018-2020-faster-coda-processing"&gt;2018-2020: Faster CODA Processing&lt;/h4&gt;
&lt;p&gt;Codabox needed to deliver bank-sourced CODA files&lt;sup id="fnref1:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; to customers before the start of the working day, but intermediate batch processing limited throughput.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reworked batch-processing steps so file processing could run in parallel.&lt;/li&gt;
&lt;li&gt;Increased throughput and helped ensure customers received their files before 8h00.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="ongoing-mycodabox-and-support-visibility"&gt;Ongoing: MyCodabox And Support Visibility&lt;/h4&gt;
&lt;p&gt;Codabox needed customer-facing and support-facing tools that made activation, mandate, subscription, and delivery states visible across operational workflows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Contributed to &lt;a href="https://www.mycodabox.com/"&gt;MyCodabox&lt;/a&gt;, the main portal accountants use to manage mandates and subscriptions.&lt;/li&gt;
&lt;li&gt;Worked on MyConsent, an onboarding tool for accountants&amp;rsquo; clients to activate integrations such as credit card statements according to &lt;a href="https://www.pcisecuritystandards.org/standards/pci-dss/"&gt;PCI&lt;/a&gt; guidelines.&lt;/li&gt;
&lt;li&gt;Did the groundwork for an &lt;a href="https://www.electronjs.org/"&gt;Electron&lt;/a&gt;-based desktop delivery client where local customer workflows required a dedicated application.&lt;/li&gt;
&lt;li&gt;Built and improved internal support tooling, including Django-admin-based and React-based interfaces.&lt;/li&gt;
&lt;li&gt;Added visibility for activation, registration, reconnect, reporting, and failure states across customer-facing and support-facing flows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="ongoing-cloud-ci-and-security-modernization"&gt;Ongoing: Cloud, CI, And Security Modernization&lt;/h4&gt;
&lt;p&gt;The team needed cloud infrastructure, CI, and security practices that kept services deployable and auditable while the platform evolved across GCP and AWS.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Worked in a DevOps model where the team runs and manages its own infrastructure.&lt;/li&gt;
&lt;li&gt;Managed infrastructure through infrastructure-as-code, mostly with Terraform.&lt;/li&gt;
&lt;li&gt;Worked mostly on GCP-based systems before leading the team&amp;rsquo;s transition toward AWS.&lt;/li&gt;
&lt;li&gt;Selected appropriate cloud technologies, from VMs and Kubernetes deployments to serverless runtimes such as GCP Cloud Run and AWS Lambda.&lt;/li&gt;
&lt;li&gt;Managed and improved CI pipelines across CircleCI, GitHub Actions, and GitLab CI.&lt;/li&gt;
&lt;li&gt;Kept production services deployable, auditable, and aligned with security expectations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="unleashed--mobile-vikings"&gt;&lt;a href="https://unleashed.be"&gt;Unleashed&lt;/a&gt; / &lt;a href="https://mobilevikings.be"&gt;Mobile Vikings&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Software Engineer / Analyst&lt;/strong&gt;
July 2016 - July 2018&lt;/p&gt;
&lt;p&gt;Unleashed was the digital product and engineering organization behind &lt;a href="https://mobilevikings.be"&gt;Mobile Vikings&lt;/a&gt;, &lt;a href="https://jimmobile.be"&gt;Jim Mobile&lt;/a&gt;, and Stievie.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;
I worked across full-stack development and analysis roles for customer-facing telecom and media products.&lt;/p&gt;
&lt;p&gt;Selected projects and responsibilities:&lt;/p&gt;
&lt;h4 id="2017-2018-marketing-automation-squad"&gt;2017-2018: Marketing Automation Squad&lt;/h4&gt;
&lt;p&gt;Mobile Vikings, Jim Mobile, and Stievie needed shared marketing automation capabilities across customer-facing telecom and media products.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Worked on cross-brand full-stack development across the three brands.&lt;/li&gt;
&lt;li&gt;Helped analyze and shape backend REST API architecture for marketing automation flows.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2016-2017-mobile-vikings-get-and-retain-squad"&gt;2016-2017: Mobile Vikings Get And Retain Squad&lt;/h4&gt;
&lt;p&gt;Mobile Vikings needed to adapt customer registration and retention flows while Belgian telecom regulation and backend platform changes reshaped core customer journeys.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Worked on prepaid registration flows after Belgian regulation required prepaid SIM cards to be linked to verified identity.&lt;/li&gt;
&lt;li&gt;Built and maintained full-stack marketing campaign features for Mobile Vikings.&lt;/li&gt;
&lt;li&gt;Helped analyze marketing backend changes for a full mobile virtual network operator (MVNO) migration and customer SIM-swap migration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="soundtalks"&gt;&lt;a href="https://www.soundtalks.com/en/"&gt;SoundTalks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Research Engineer&lt;/strong&gt;
September 2012 - July 2016&lt;/p&gt;
&lt;p&gt;SoundTalks is a KU Leuven spin-off developing acoustic monitoring technology for livestock health.
I joined as the first employee, working in a small early-stage company where research, software development, field deployment, customer contact, and commercial support were closely connected.&lt;/p&gt;
&lt;p&gt;Selected responsibilities and achievements:&lt;/p&gt;
&lt;p&gt;SoundTalks needed to turn acoustic livestock-health research into an operational product in a small early-stage company.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Led research and development work for the Pig Cough Monitor algorithm and supporting software.&lt;/li&gt;
&lt;li&gt;Handled on-site installations, maintenance, data collection, and customer-facing technical follow-up.&lt;/li&gt;
&lt;li&gt;Managed data storage and database work needed for algorithm development, validation, and product operation.&lt;/li&gt;
&lt;li&gt;Developed software practices around version control, build processes, and release management.&lt;/li&gt;
&lt;li&gt;Represented the company at technical, academic, and commercial conferences with partners and customers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ku-leuven-medical-imaging-research-center"&gt;&lt;a href="https://www.kuleuven.be/samenwerking/mirc"&gt;KU Leuven Medical Imaging Research Center&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Research Assistant&lt;/strong&gt;
September 2010 - August 2012&lt;/p&gt;
&lt;p&gt;The Medical Imaging Research Center is a KU Leuven research center applying digital signal processing, machine vision, and image-analysis techniques to medical imaging problems.
This was my first role after completing my master&amp;rsquo;s degree, combining research software development, image analysis, and teaching assistance.&lt;/p&gt;
&lt;p&gt;Selected projects and responsibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Worked on validation of a 3-D volumetric approach to evaluate local bone changes.&lt;/li&gt;
&lt;li&gt;Developed software for integrated registration and segmentation in 2-D and 3-D follow-up analyses of oral and maxillofacial surgery using cone-beam computed tomography.&lt;/li&gt;
&lt;li&gt;Worked on quantification of lung pathology based on computed tomography (CT).&lt;/li&gt;
&lt;li&gt;Worked as a teaching assistant for an undergraduate digital signal processing course.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="education"&gt;Education&lt;/h2&gt;
&lt;h3 id="msc-engineering-electrical-engineering-ku-leuven"&gt;M.Sc. Engineering: Electrical Engineering, KU Leuven&lt;/h3&gt;
&lt;p&gt;2008 - 2010&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Specialization in multimedia and signal processing.&lt;/li&gt;
&lt;li&gt;Graduated cum laude.&lt;/li&gt;
&lt;li&gt;Thesis: see [Selected Academic Work](cv#Selected Academic Work).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bsc-engineering-electrical-engineering-ku-leuven"&gt;B.Sc. Engineering: Electrical Engineering, KU Leuven&lt;/h3&gt;
&lt;p&gt;2004 - 2008&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Specialization in industrial management.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="selected-academic-work"&gt;Selected Academic Work&lt;/h2&gt;
&lt;h3 id="thesis"&gt;Thesis&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;M.Sc. thesis, with Hans Van Herck: &lt;a href="https://mhemeryck.github.io/cv/Hemeryck_Martijn_masterproef_2009-2010.pdf"&gt;&amp;ldquo;Integrated Stereo Acoustic Echo Cancellation and Speech Coding for Tele-/videoconferencing Applications&amp;rdquo;&lt;/a&gt;, KU Leuven, 2010.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="publications"&gt;Publications&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Dries Berckmans, Martijn Hemeryck, Daniel Berckmans, Erik Vranken, and Toon van Waterschoot: &amp;ldquo;Animal Sound&amp;hellip;Talks! Real-time Sound Analysis for Health Monitoring in Livestock&amp;rdquo;, Animal Environment and Welfare, 2015.&lt;/li&gt;
&lt;li&gt;Ilaria Fontana, Emanuela Tullo, Martijn Hemeryck, and Marcella Guarino: &amp;ldquo;Using Broiler Sound Frequency to Model Weight&amp;rdquo;, Animal Environment and Welfare, 2015.&lt;/li&gt;
&lt;li&gt;Martijn Hemeryck, Dries Berckmans, Erik Vranken, Emanuela Tullo, Ilaria Fontana, Marcella Guarino, and Toon van Waterschoot: &amp;ldquo;The Pig Cough Monitor in the EU-PLF project: results and multimodal data analysis&amp;rdquo;, Precision Livestock Farming &amp;lsquo;15, 2015.&lt;/li&gt;
&lt;li&gt;Martijn Hemeryck and Dries Berckmans: &amp;ldquo;Pig cough monitoring in the EU-PLF project: first results&amp;rdquo;, Precision livestock farming applications, 2014.&lt;/li&gt;
&lt;li&gt;Marko Topalovic, Vasileios Exadaktylos, Anneleen Peeters, Johan Coolen, Walter Dewever, Martijn Hemeryck, Pieter Slagmolen, Karl Janssens, Daniel Berckmans, Marc Decramer, and Wim Janssens: &lt;a href="http://dx.doi.org/10.1186/1465-9921-14-131"&gt;&amp;ldquo;Computer quantification of airway collapse on forced expiration to predict the presence of emphysema&amp;rdquo;&lt;/a&gt;, Respiratory Research, 2013.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="presentations-and-posters"&gt;Presentations And Posters&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;SoundTalks and the Pig Cough Monitor&amp;rdquo;, Flanders Agrotronics, Leuven, Belgium, 2015.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Model based image analysis for robust quantification of COPD on thorax CT&amp;rdquo;, poster, Paleis der Academiën, Brussels, Belgium, 2011.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Model based liver segmentation for surgery planning&amp;rdquo;, oral presentation, Jesús Usón Minimally Invasive Surgery Centre, Cáceres, Spain, 2011.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="selected-projects"&gt;Selected Projects&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Personal home automation system: Designed, built, and maintain a whole-home automation setup integrating wired controls, UniPi hardware, Modbus, DALI, MQTT, Home Assistant, and custom Go/Python services.
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mhemeryck/nest"&gt;nest&lt;/a&gt;: Go-based home automation codebase for consolidating personal automation services.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mhemeryck/modbridge"&gt;modbridge&lt;/a&gt;: Go service for polling Modbus and Modbus TCP devices and publishing updates over MQTT.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mhemeryck/evok2mqtt"&gt;evok2mqtt&lt;/a&gt;: Python bridge that listens to the UniPi EVOK API over websockets and publishes device events to MQTT.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mhemeryck/covers"&gt;covers&lt;/a&gt;: MQTT-based home automation helper for mapping button input events to matching cover controls.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mhemeryck/hausmaus"&gt;hausmaus&lt;/a&gt;: Rust-based home automation tooling.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mhemeryck/iac"&gt;iac&lt;/a&gt;: Terraform and Kubernetes setup for self-hosted services, including k3s, TLS automation, and service manifests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="skills"&gt;Skills&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Selected experience includes backend and systems work with Python, Go, Rust, C, C++, and shell scripting.&lt;/li&gt;
&lt;li&gt;Web and API development with Django, Flask, FastAPI, REST / RPC APIs, HTML, CSS, JavaScript, HTMX and Vue.js.&lt;/li&gt;
&lt;li&gt;Cloud and infrastructure work across AWS, GCP, Terraform, Pulumi, Kubernetes, Docker, and AWS CDK.&lt;/li&gt;
&lt;li&gt;Data and integration work involving PostgreSQL, SQL, Pub/Sub, SFTP, MQTT, Modbus, and DALI.&lt;/li&gt;
&lt;li&gt;Delivery and operations tooling including Git, CI/CD, Linux, Nix, Nushell, Vim, Helix, and LaTeX.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="certifications"&gt;Certifications&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.credly.com/badges/9cf26a0f-3ac9-475e-b679-493e624ae948/public_url"&gt;AWS Certified Solutions Architect - Associate&lt;/a&gt;, April 2026.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.credly.com/badges/21b41ca0-4671-4ad6-9da9-0d96cb74e5d9/public_url"&gt;Google Cloud Associate Cloud Engineer&lt;/a&gt;, February 2024.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.credly.com/badges/e8cc322c-c59c-4f0c-a7bd-6348c48d7b10/public_url"&gt;LFS258: Kubernetes Fundamentals&lt;/a&gt;, July 2020.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="languages"&gt;Languages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Dutch: native.&lt;/li&gt;
&lt;li&gt;English: professional working proficiency.&lt;/li&gt;
&lt;li&gt;French: intermediate working proficiency.&lt;/li&gt;
&lt;li&gt;German: basic proficiency.&lt;/li&gt;
&lt;li&gt;Japanese: basic proficiency.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="interests"&gt;Interests&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Endurance training, especially running and indoor rowing, with a focus on long-term consistency.&lt;/li&gt;
&lt;li&gt;Personal engineering projects around home automation, document handling, and self-hosted infrastructure.&lt;/li&gt;
&lt;li&gt;Technical writing and learning, including systems design, cloud architecture, Nix/NixOS, and Go.&lt;/li&gt;
&lt;li&gt;Science and physics, through books, podcasts, and general curiosity.&lt;/li&gt;
&lt;li&gt;Music and culture, with a particular interest in electronic music.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="links"&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Location: Flanders, Belgium&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/mhemeryck"&gt;mhemeryck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://linkedin.com/in/martijn-hemeryck-67896245"&gt;martijn-hemeryck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Blog: &lt;a href="https://mhemeryck.github.io/posts/"&gt;Martyn&amp;rsquo;s musings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://peppol.org/"&gt;PEPPOL&lt;/a&gt; is the European e-invoicing network.&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;&lt;a href="https://febelfin.be/en/themes/digitalization-innovation/regulations/a-coda-file-what-is-it-and-what-can-you-use-it-for"&gt;CODA&lt;/a&gt;, or &amp;ldquo;geCOdeerd DAgafschrift&amp;rdquo;, is a coded bank statement file type used in Belgium.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1: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;Open banking in this context builds on the &lt;a href="https://en.wikipedia.org/wiki/Payment_Services_Directive"&gt;Payment Services Directive&lt;/a&gt;.&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;li id="fn:4"&gt;
&lt;p&gt;Stievie was a Belgian online streaming platform and predecessor to &lt;a href="https://www.vtmgo.be/"&gt;VTM GO&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:4" 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></channel></rss>