<?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>Unipi on mhemeryck</title><link>https://mhemeryck.xyz/tags/unipi/</link><description>Recent content in Unipi on mhemeryck</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 27 Jul 2021 00:00:00 +0000</lastBuildDate><atom:link href="https://mhemeryck.xyz/tags/unipi/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>