<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Thomas makes &amp; shares:</title><link href="https://www.thouters.be/" rel="alternate"/><link href="https://www.thouters.be/feeds/all.atom.xml" rel="self"/><id>https://www.thouters.be/</id><updated>2025-12-21T11:50:00+01:00</updated><entry><title>Our custom round home control panels</title><link href="https://www.thouters.be/LilygoTrgbOpenHasp.html" rel="alternate"/><published>2025-12-21T11:50:00+01:00</published><updated>2025-12-21T11:50:00+01:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2025-12-21:/LilygoTrgbOpenHasp.html</id><summary type="html">
&lt;p&gt;This affordable ESP32 based touch panel with IPS capacitive screen made me reconsider not
putting home automation screens in the house.&lt;/p&gt;
&lt;p&gt;Its round shape is perfect for showing power curves on its circumference and
makes it slightly more organic than the rectangular light boxes we
immediately recognise as phones or computers.&lt;/p&gt;
&lt;p&gt;I ended up designing and 3D printing an enclosure for it and combining OpenHASP with some python code to integrate it with the Home Assistant setup.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;!-- :image: https://www.thouters.be/img/LilygoTrgbOpenHasp/0-cover.jpg --&gt;
&lt;!-- :post_id: 109672257020969515 --&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/LilygoTrgbOpenHasp/0-cover.jpg" /&gt;
&lt;p class="caption"&gt;Side and front view of the touch screen. Arc lines indicate 3kW of solar is going to the car, while 2.5kW is pulled from the grid.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;This affordable ESP32 based touch panel with IPS capacitive screen made me reconsider not
putting home automation screens in the house.&lt;/p&gt;
&lt;p&gt;Its round shape is perfect for showing power curves on its circumference and
makes it slightly more organic than the rectangular light boxes we
immediately recognise as phones or computers.&lt;/p&gt;
&lt;p&gt;I ended up designing and 3D printing an enclosure for it and combining OpenHASP with some python code to integrate it with the Home Assistant setup.&lt;/p&gt;

&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;This page is still a work in progress, I already wanted to share this
project and will add more details and source code references in the future.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#impressions" id="toc-entry-1"&gt;Impressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#architecture" id="toc-entry-2"&gt;Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#mechanics" id="toc-entry-3"&gt;Mechanics&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#assembly" id="toc-entry-4"&gt;Assembly&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#preparation" id="toc-entry-5"&gt;Preparation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#mounting-the-display-module" id="toc-entry-6"&gt;Mounting the display module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#installation" id="toc-entry-7"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#firmware" id="toc-entry-8"&gt;Firmware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#openhasp-design" id="toc-entry-9"&gt;OpenHASP design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#home-assistant-automations" id="toc-entry-10"&gt;Home Assistant Automations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#appdaemon-controls" id="toc-entry-11"&gt;AppDaemon Controls&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#a-bit-more-details-on-the-power-arc" id="toc-entry-12"&gt;A bit more details on the Power Arc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="impressions"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Impressions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://github.com/Xinyuan-LilyGO/LilyGo-T-RGB"&gt;Lilygo T-RGB-2.8 device&lt;/a&gt; is about the right size to blend into the
row of existing light switches on our kitchen wall.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/LilygoTrgbOpenHasp/0-impression2.jpg" /&gt;
&lt;p class="caption"&gt;Photo of the panel installed on the kitchen wall next to the sink.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The IPS resolution is the basis of a proper picture frame, and adding
translucent shortcut icons over the picture allows it serve form as well as function.
The display module's capacitive touch screen and PSRAM memory give it the feel and performance of a high end product.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/LilygoTrgbOpenHasp/powergauge.jpg" /&gt;
&lt;p class="caption"&gt;The panel is showing power use on a needle gauge, in the middle some the power of some individual devices is shown.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Some of the core house infrastructure only has proper control via phone Apps, like
the upstairs Airco indoor units, the central ventilation (a C+ extraction system actually),
the car charger and car (preheat shortcuts).  A wall panel would also allow easy control of
motorized window shutters on different floors and show a glance on the actual energy consumption.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/LilygoTrgbOpenHasp/mediaplayer.jpg" /&gt;
&lt;p class="caption"&gt;The panel is showing a media player controlling the living room speakers.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="architecture"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Architecture&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://www.openhasp.com/"&gt;OpenHASP&lt;/a&gt; firmware is configured by hand and the widgets are defined using a
custom crafted &lt;a class="reference external" href="https://www.openhasp.com/0.7.0/design/pages/#pagesjsonl"&gt;pages.jsonl&lt;/a&gt; file that defines the page and widget layout.&lt;/p&gt;
&lt;p&gt;I use &lt;a class="reference external" href="https://appdaemon.readthedocs.io/"&gt;AppDaemon&lt;/a&gt; to host python scripts that react to MQTT and Home Assistant websocket events.
AppDeamon allows you to spin up instances of automation crafted in Python using a &lt;a class="reference external" href="https://yaml.org/"&gt;YAML&lt;/a&gt; file.
In my case I created media player and thermostat controller classes that
are referenced in the YAML file for each thermostat control that appears on the
touchscreens.&lt;/p&gt;
&lt;p&gt;In some sense it mimics the MVC architecture:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Model: Home Assistant offering a data model (state) and allows service calls to trigger device actions.&lt;/li&gt;
&lt;li&gt;View: OpenHASP presents &lt;a class="reference external" href="https://lvgl.io/"&gt;LVGL&lt;/a&gt; widgets and sending input events to the local MQTT server.&lt;/li&gt;
&lt;li&gt;Controller: the AppDaemon automation in Python is updating Home Assistant and OpenHASP widget state&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="mechanics"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Mechanics&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I created a 3D design using OpenSCAD that fits onto a Niko switch coverplate.
It looks like the display is part of the switch plate (to me at least) and
since the display is very thin, it allows me to install the display without
creating extra holes in the wall.&lt;/p&gt;
&lt;p&gt;This mechanical adaptor piece which I call the 'saddle' (since it sits on top of a curved surface) features&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;ventilation holes&lt;/li&gt;
&lt;li&gt;alignment studs for the display backlight and Niko coverplate&lt;/li&gt;
&lt;li&gt;a slight recess on the top for double sided adhesive tape.&lt;/li&gt;
&lt;li&gt;the volume of the Niko coverplate substracted&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Its diameter matches the display so that no extra border is visible around the display.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/LilygoTrgbOpenHasp/adaptor.png" /&gt;
&lt;p class="caption"&gt;Display 'saddle'&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You can find the files here:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
        &lt;td&gt;
&lt;a class="nohl" href="https://www.thouters.be/img/LilygoTrgbOpenHasp/saddle.scad"&gt;
&lt;img border="0" src="https://www.thouters.be/img/Downloads/openscad.png" alt="icon" width="32" height="32"/&gt; saddle.scad&lt;/a&gt;
        &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
        &lt;td&gt;
&lt;a class="nohl" href="https://www.thouters.be/img/LilygoTrgbOpenHasp/saddle.stl"&gt;
&lt;img border="0" src="https://www.thouters.be/img/Downloads/stl.png" alt="icon"/&gt; saddle.stl&lt;/a&gt;
        &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;div class="section" id="assembly"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Assembly&lt;/a&gt;&lt;/h3&gt;
&lt;div class="section" id="preparation"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Preparation&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Start with a new Niko switch coverplate that is one size bigger than
the one that is currently installed, the saddle is placed onto it.&lt;/p&gt;
&lt;p&gt;A marker is used to mark a cut-line. After marking, a Dremel or Stanley knife can be used
to remove the excess plastic. Superglue is used to fix the saddle onto the Niko plate.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/LilygoTrgbOpenHasp/1-score.jpg" /&gt;
&lt;p class="caption"&gt;marking where to cut the plastic&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Next up is soldering power leads to the circuit board.&lt;/p&gt;
&lt;p&gt;Pieces of double sided adhesive tape are placed around the inner edge of the saddle.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/LilygoTrgbOpenHasp/aligner.png" /&gt;
&lt;p class="caption"&gt;Assembly alignment jig.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="mounting-the-display-module"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Mounting the display module&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;I designed this alignment ring to easily mount the display onto the saddle:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
        &lt;td&gt;
&lt;a class="nohl" href="https://www.thouters.be/img/LilygoTrgbOpenHasp/aligner.scad"&gt;
&lt;img border="0" src="https://www.thouters.be/img/Downloads/openscad.png" alt="icon"/&gt; aligner.scad&lt;/a&gt;
        &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
        &lt;td&gt;
&lt;a class="nohl" href="https://www.thouters.be/img/LilygoTrgbOpenHasp/aligner.stl"&gt;
&lt;img border="0" src="https://www.thouters.be/img/Downloads/stl.png" alt="icon"/&gt; aligner.stl&lt;/a&gt;
        &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;p&gt;The display is placed onto the alignment ring and the saddle will slide into
the ring and stick to it when both pieces are mated together. The backlight
cover will fit exactly between the alignment studs visible at the bottom, so
that the alignment angle is also perfect.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/LilygoTrgbOpenHasp/3-assembled.jpg" /&gt;
&lt;p class="caption"&gt;display in place.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="installation"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Installation&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;During installation onto the wall the power leads are connected to a 5V power
supply behind the light switch.  Next the switches are fixed back into the wall
box. Some extra slack in the leads will allow the assembly to be dangling
on the side while installing.&lt;/p&gt;
&lt;p&gt;Once the lightswitches are back in place, the switch cover assembly is snap-pushed onto the switches. Easy!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="firmware"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Firmware&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The display is being configured over I2C by bit banging I2C using SPI over an I2C port expander chip.
It looks like a strange decision but the display is driven in a parallel configuration which requires a lot of pins.
This leaves I2C as only free external interface, which still makes it possible to connect some sensors or other peripherals.&lt;/p&gt;
&lt;p&gt;Other hardware is also doing this so porting OpenHASP to this board was a matter of adding the
right register configuration values from the SDK examples.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/HASwitchPlate/openHASP/pull/958"&gt;https://github.com/HASwitchPlate/openHASP/pull/958&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="openhasp-design"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;OpenHASP design&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I created a python script so I can generate the &lt;tt class="docutils literal"&gt;pages.jsonl&lt;/tt&gt; file from a &lt;a class="reference external" href="https://yaml.org/"&gt;YAML&lt;/a&gt; file.
YAML is more readable and you can use the &lt;a class="reference external" href="https://yaml.org/type/merge.html"&gt;merge key&lt;/a&gt;  to reuse parts of the
file. If you don't like long files you can still put your existing JSON lines
in the YAML to start with, since JSON is valid YAML.&lt;/p&gt;
&lt;p&gt;The conversion script allows me to do some pre-processing magic like aligning widgets on a circular path,
and the merge key &lt;tt class="docutils literal"&gt;&amp;lt;&amp;lt;:&lt;/tt&gt; is used to put YAML reference labels (&lt;tt class="docutils literal"&gt;&amp;amp;circlebutton&lt;/tt&gt;) on duplicated blocks to
copy and paste (&lt;tt class="docutils literal"&gt;*circlebutton&lt;/tt&gt;) the content later on.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Home&lt;/span&gt;
    &lt;span class="n"&gt;bg_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;#0000000&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;swipe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;circle_arrange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;screen_diameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;
      &lt;span class="n"&gt;edge_clearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
      &lt;span class="n"&gt;widget_diameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
      &lt;span class="n"&gt;text_font_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
      &lt;span class="n"&gt;offsets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;
        &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;kids&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;png&lt;/span&gt;
        &lt;span class="n"&gt;auto_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;swipe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;circlebutton&lt;/span&gt;
          &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt;
          &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
          &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
          &lt;span class="n"&gt;bg_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;#444444&amp;quot;&lt;/span&gt;
          &lt;span class="n"&gt;border_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;#FFFFFF&amp;quot;&lt;/span&gt;
          &lt;span class="n"&gt;border_width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
          &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
          &lt;span class="n"&gt;text_font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
          &lt;span class="n"&gt;text_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;#FFFFFF&amp;quot;&lt;/span&gt;
          &lt;span class="n"&gt;bg_opa&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt;
        &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; \&lt;span class="n"&gt;uF40B&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;330&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
      &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;circlebutton&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;music&lt;/span&gt;
        &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p4&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; \&lt;span class="n"&gt;uE75A&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that OpenHASP supports 12 pages by default, add a higher value like &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-D&lt;/span&gt; HASP_NUM_PAGES=30&lt;/tt&gt; to your .ini file.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="home-assistant-automations"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Home Assistant Automations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have an automation that turns on the backlight of the display when the Siemens LOGO PLC reports the wired movement sensor detects movement.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="appdaemon-controls"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;AppDaemon Controls&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point I have several controllers that I use to control Home Assistant via the OpenHASP panel:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Thermostat control&lt;/li&gt;
&lt;li&gt;Media player control&lt;/li&gt;
&lt;li&gt;Power Arc control&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.home-assistant.io/integrations/input_select/"&gt;input select&lt;/a&gt; to dropdown box synchronizer&lt;/li&gt;
&lt;li&gt;Periodic chores checklist backed by Grocy data.&lt;/li&gt;
&lt;li&gt;Biggest power consumers visualizer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I should bundle these up in a repository and share them. Let me know if you are interested.&lt;/p&gt;
&lt;div class="section" id="a-bit-more-details-on-the-power-arc"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;A bit more details on the Power Arc&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's look at the code of one of the automations I made, the power arc.&lt;/p&gt;
&lt;p&gt;This automation will update the length and color of arc lines like explained on my &lt;a class="reference external" href="https://www.thouters.be/PowerGauge.html#the-idea"&gt;PowerGauge page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To make the design a bit slicker, I use overlapping arcs indicating the power level of e.g. grid and solar power.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;powerdial_round1_homepage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;powerdial&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;PowerDial&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;lwt_topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;hasp/round1/LWT&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;device_topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;round1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;arcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;100&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;#FIXME: explicitly mention argument names instead of using a list&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sensor.total_power_consumption&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ffff00&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;101&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;102&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sensor.solaredge_ac_power&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#00ff00&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sensor.electricity_meter_power_consumption&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1000&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff0000&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;110&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;111&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;complement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sensor.electricity_meter_power_production&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1000&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#ff00ff&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sensor.car_solar_power_2&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p p-Indicator"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#1affff&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The python code is fairly straightforward.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt; &lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;     &lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;span class="linenos"&gt; 4&lt;/span&gt;     &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;span class="linenos"&gt; 5&lt;/span&gt;     &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;span class="linenos"&gt; 6&lt;/span&gt;     &lt;span class="n"&gt;complement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;     &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;     &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;     &lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;
&lt;span class="linenos"&gt;12&lt;/span&gt;     &lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;span class="linenos"&gt;13&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;widget_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;14&lt;/span&gt;         &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watts&lt;/span&gt;
&lt;span class="linenos"&gt;15&lt;/span&gt;         &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;complement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;16&lt;/span&gt;             &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
&lt;span class="linenos"&gt;17&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;18&lt;/span&gt;
&lt;span class="linenos"&gt;19&lt;/span&gt; &lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="linenos"&gt;20&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArcWidget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;21&lt;/span&gt;     &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="linenos"&gt;22&lt;/span&gt;     &lt;span class="n"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dial&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="linenos"&gt;23&lt;/span&gt;
&lt;span class="linenos"&gt;24&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PowerDial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HassHaspWidget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;25&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="linenos"&gt;26&lt;/span&gt;&lt;span class="sd"&gt;     This layers multi color arcs on top of each other.&lt;/span&gt;
&lt;span class="linenos"&gt;27&lt;/span&gt;&lt;span class="sd"&gt;     Suggested use:&lt;/span&gt;
&lt;span class="linenos"&gt;28&lt;/span&gt;&lt;span class="sd"&gt;     The first is from left to right, and will show grid use and solar&lt;/span&gt;
&lt;span class="linenos"&gt;29&lt;/span&gt;&lt;span class="sd"&gt;     The second is from right to left, and will show grid injection and battery charge power&lt;/span&gt;
&lt;span class="linenos"&gt;30&lt;/span&gt;&lt;span class="sd"&gt;     of these pairs, the one with the smallest value will be in the foreground so at a glance&lt;/span&gt;
&lt;span class="linenos"&gt;31&lt;/span&gt;&lt;span class="sd"&gt;     the power level of each entity can be estimated.&lt;/span&gt;
&lt;span class="linenos"&gt;32&lt;/span&gt;&lt;span class="sd"&gt;     &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="linenos"&gt;33&lt;/span&gt;     &lt;span class="n"&gt;arcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ArcWidget&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="linenos"&gt;34&lt;/span&gt;
&lt;span class="linenos"&gt;35&lt;/span&gt;     &lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;span class="linenos"&gt;36&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;use_random_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;37&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;use_random_values&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;38&lt;/span&gt;
&lt;span class="linenos"&gt;39&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;arc_color_topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;40&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hasp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/command/p&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id_&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.line_color10&amp;quot;&lt;/span&gt;
&lt;span class="linenos"&gt;41&lt;/span&gt;
&lt;span class="linenos"&gt;42&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;arc_value_topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;43&lt;/span&gt;         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hasp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/command/p&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id_&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.val&amp;quot;&lt;/span&gt;
&lt;span class="linenos"&gt;44&lt;/span&gt;
&lt;span class="linenos"&gt;45&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recalculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;46&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Order the arcs so the shortest one rendered using the one in the foreground &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="linenos"&gt;47&lt;/span&gt;         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;48&lt;/span&gt;             &lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sensors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watts&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;complement&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;49&lt;/span&gt;
&lt;span class="linenos"&gt;50&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;51&lt;/span&gt;         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;52&lt;/span&gt;             &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;widget_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;53&lt;/span&gt;                 &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mqtt_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arc_value_topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widget_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;54&lt;/span&gt;                 &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mqtt_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arc_color_topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;widget_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;55&lt;/span&gt;
&lt;span class="linenos"&gt;56&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mqtt_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;57&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mqtt/publish&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;58&lt;/span&gt;
&lt;span class="linenos"&gt;59&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cb_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;60&lt;/span&gt;         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;61&lt;/span&gt;             &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;62&lt;/span&gt;                 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;63&lt;/span&gt;                     &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;64&lt;/span&gt;                 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use_random_values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;65&lt;/span&gt;                     &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;
&lt;span class="linenos"&gt;66&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recalculate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;67&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;68&lt;/span&gt;
&lt;span class="linenos"&gt;69&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hass_subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;70&lt;/span&gt;         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;71&lt;/span&gt;             &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="linenos"&gt;72&lt;/span&gt;                 &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listen_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;73&lt;/span&gt;
&lt;span class="linenos"&gt;74&lt;/span&gt;     &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="linenos"&gt;75&lt;/span&gt;         &lt;span class="n"&gt;HassHaspWidget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="linenos"&gt;76&lt;/span&gt;         &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;scale&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="linenos"&gt;77&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="linenos"&gt;78&lt;/span&gt;         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;arcs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="linenos"&gt;79&lt;/span&gt;             &lt;span class="n"&gt;complement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;complement&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="linenos"&gt;80&lt;/span&gt;             &lt;span class="n"&gt;sensors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="linenos"&gt;81&lt;/span&gt;                 &lt;span class="n"&gt;Dial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="linenos"&gt;82&lt;/span&gt;                     &lt;span class="n"&gt;entity_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="linenos"&gt;83&lt;/span&gt;                     &lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="linenos"&gt;84&lt;/span&gt;                     &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="linenos"&gt;85&lt;/span&gt;                     &lt;span class="n"&gt;complement&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;complement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;86&lt;/span&gt;                     &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;87&lt;/span&gt;                     &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
&lt;span class="linenos"&gt;88&lt;/span&gt;                 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sensor&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sensors&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="linenos"&gt;89&lt;/span&gt;             &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arcs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;ArcWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ids&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="linenos"&gt;90&lt;/span&gt;
&lt;span class="linenos"&gt;91&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mqtt_subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;92&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hass_subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="linenos"&gt;93&lt;/span&gt;         &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="openhasp"/><category term="embedded"/><category term="homeassistant"/></entry><entry><title>Modbus Current Clamps for your breaker box</title><link href="https://www.thouters.be/CurrentClamps.html" rel="alternate"/><published>2025-09-06T22:15:00+02:00</published><updated>2025-09-06T22:15:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2025-09-06:/CurrentClamps.html</id><summary type="html">
&lt;p&gt;To feed my power optimization obsession, I already &lt;a class="reference external" href="https://www.thouters.be/HassEnergyOfBinaryHeater.html"&gt;built some strange devices&lt;/a&gt; and &lt;a class="reference external" href="https://www.thouters.be/SolarMatchedElectricHeating.html"&gt;did some interesting experiments&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With a new electricity panel in place, I really needed to add current monitoring to 16 of the circuits to keep an eye on things.&lt;/p&gt;
&lt;p&gt;I like Modbus for wired sensors for its simplicity, and found this reasonably priced device that seemed to be a good solution.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;div class="section" id="introduction"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Introduction&lt;/a&gt;&lt;/h2&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/CurrentClamps/currentclamps.png" /&gt;
&lt;p class="caption"&gt;Modbus current measurement device&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;To feed my power optimization obsession, I already &lt;a class="reference external" href="https://www.thouters.be/HassEnergyOfBinaryHeater.html"&gt;built some strange devices&lt;/a&gt; and &lt;a class="reference external" href="https://www.thouters.be/SolarMatchedElectricHeating.html"&gt;did some interesting experiments&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With a new electricity panel in place, I really needed to add current monitoring to 16 of the circuits to keep an eye on things.&lt;/p&gt;
&lt;p&gt;I like Modbus for wired sensors for its simplicity, and found this reasonably priced device that seemed to be a good solution.&lt;/p&gt;

&lt;p&gt;The HDXXAXXA16GK-D has 16 screw terminal inputs to connect the current transformers that it ships with.
It takes 8-28VDC power supply and has the usual A/B RS485 Modbus-RTU connections.&lt;/p&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#introduction" id="toc-entry-1"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#concept" id="toc-entry-2"&gt;Concept&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#configuring-the-device" id="toc-entry-3"&gt;Configuring the device&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#installing-the-device" id="toc-entry-4"&gt;Installing the device&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#home-assistant-configuration" id="toc-entry-5"&gt;Home assistant configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#visualisation-of-power-use-in-home-assistant" id="toc-entry-6"&gt;Visualisation of power use in Home Assistant&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="concept"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Concept&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Home Assistant can track the energy consumption in your house, but it needs sensors to do so.
In Belgium, the obvious way to start is to tap in to the grid utility meter, which has a P1 port,
this is a serial port that dumps the meter data every 5 seconds or so.&lt;/p&gt;
&lt;p&gt;Your solar production can be tracked by reading the inverter data, either via Modbus or via an API,
in my setup, the solaredge inverter is read via the Solaredge Modbus integration over TCP/IP.&lt;/p&gt;
&lt;p&gt;If you want to add accurate tracking of individual devices, a nice and easy way is to use a smartplug,
which is low entry and easy to install. But if you want to track the power consumption of your entire house,
it may be safer to use current clamps on the main circuits in your electrical panel. This way you can
avoid depending on the electrical reliability of the smartplugs, especially for high power devices like
electrical boilers or heaters.&lt;/p&gt;
&lt;p&gt;Installing actual energy meters per circuit would be more accurate, but&lt;/p&gt;
&lt;ol class="loweralpha simple"&gt;
&lt;li&gt;are very expensive&lt;/li&gt;
&lt;li&gt;they would take up a lot of space.&lt;/li&gt;
&lt;li&gt;are harder to retrofit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I opted to use proper modbus energy meters for the heatpumps and EVSE (car charge point) though,
since I want proper tracking for them, and energy meters take power factor into account and
have a better sampling rate.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/CurrentClamps/hassenergy.png" /&gt;
&lt;p class="caption"&gt;Colorful Home Assistant energy graphs&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Since we are interested in power and energy, and the current clamps only measure current, we need to do some calculations in home assistant.
We can use the P1 grid meter's reported line voltages and combine the data in home assistant template sensors.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuring-the-device"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Configuring the device&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I always trial-run a modbus device on desk with a USB to RS485 adapter before installing it in the panel.&lt;/p&gt;
&lt;p&gt;For starters, we need to assign the device a unique Modbus address using &lt;a class="reference external" href="https://pypi.org/project/modbus-cli/"&gt;modbus-cli&lt;/a&gt; (and perhaps run &lt;tt class="docutils literal"&gt;pip install modbus_cli&lt;/tt&gt; first)
since the device is connected together with several other modbus devices on the same RS485 bus.&lt;/p&gt;
&lt;p&gt;To set the slave address to 4:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
modbus /dev/ttyUSB0 -p 2 -b 9600 -P n -s 1 3=4
&lt;/pre&gt;
&lt;p&gt;You can also try to read some of the registers to see if the device is working. To read the version register:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
modbus /dev/ttyUSB0 -p 2 -b 9600 -P n -s 4 0
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="installing-the-device"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Installing the device&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The device is installed in the electrical panel, powered by a 24VDC power supply, and connected to the RS485 bus.
The 16 supplied current clamps are connected to the 16 channels, and clamped around the live wire of each circuit.
The device can be mounted on a DIN rail, but it's quite big to fit the cabinet that I have, so i used the
flanges on the side to screw it to the backplate of the cabinet.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="first admonition-title"&gt;Warning&lt;/p&gt;
&lt;p class="last"&gt;Make sure to turn off the power in the electrical panel before working in it, and if you are not
comfortable doing so, hire a professional electrician.
Working in an electrical panel can be dangerous and even deadly.
Always follow local regulations and guidelines.
I am not responsible for any damage or injury caused by actions taken based on this information.
This is not a step-by-step guide, just a description of what I did.
Your situation may be different, so adapt accordingly.
Always prioritize safety and compliance with local laws.
If in doubt, consult a professional electrician.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="first admonition-title"&gt;Warning&lt;/p&gt;
&lt;p class="last"&gt;The current clamps are transformers, and the secondary side should never be open-circuited while current is flowing in the primary side.
This can cause high voltages and damage the device or cause injury.  Always turn off the circuit in question before connecting
the clamp's measurement output wires to the meter.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="home-assistant-configuration"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Home assistant configuration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To keep the configuration concise, I used only two yaml entries, one for the version register (as a sanity check),
and another one with a slave_count of 16 to read all the channels in one go.  Home assistant can then even use a single modbus
command, to speed things up.&lt;/p&gt;
&lt;p&gt;These are the additions to &lt;tt class="docutils literal"&gt;modbus.yaml&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;clamps_version&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;4&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;data_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uint16&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;clamps_channel&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;clamps_channel&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;4&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;#off by one so channel_1 is actually channel one&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;7&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.01&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;data_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uint16&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;device_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;current&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;unit_of_measurement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;A&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;slave_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;16&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In &lt;tt class="docutils literal"&gt;template.yaml&lt;/tt&gt; a template entity is created to calculate the power consumption of the circuit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;time_pattern&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.clamps_channel_1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.sdm630_phase_1_line_to_neutral_volts&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;not_from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unavailable&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;not_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unavailable&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;car_clamp_power&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;unique_id&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;car_clamp_power&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;unit_of_measurement&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;W&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;device_class&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;power&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;state_class&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;measurement&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;state&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set current = states(&amp;#39;sensor.clamps_channel_1&amp;#39;)|float %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set voltage = states(&amp;#39;sensor.sdm630_phase_1_line_to_neutral_volts&amp;#39;)|float %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set power = current*voltage*3 %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{{ &amp;#39;%0.3f&amp;#39; | format(power) }}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;attributes:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;dummy: &amp;quot;{{ now().minute }}&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in &lt;tt class="docutils literal"&gt;sensor.yaml&lt;/tt&gt; an energy meter that integrates the power of the circuit over time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;integration&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;car_clamp_energy&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.car_clamp_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="visualisation-of-power-use-in-home-assistant"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Visualisation of power use in Home Assistant&lt;/a&gt;&lt;/h2&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/PowerGauge/homeassistant-powerflow.png" /&gt;
&lt;p class="caption"&gt;Home assistant power visualisation using bar-card and custom template card.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Home assistant has built-in energy tracking and visualisation, but no overview of the
power use.&lt;/p&gt;
&lt;p&gt;To have such a nice live visualisation, I set up a home assistant
dashboard that displays the real time power usage. This dashboard uses the &lt;a class="reference external" href="https://github.com/flixlix/power-flow-card-plus"&gt;config-template-card&lt;/a&gt; with &lt;a class="reference external" href="https://github.com/custom-cards/bar-card"&gt;bar-card&lt;/a&gt; and the
&lt;a class="reference external" href="https://github.com/iantrich/config-template-card"&gt;power-flow-card-plus&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I use the following YAML configuration entered in the dashboard editor since this custom card can not be configured via
custom UI dialog.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;custom:config-template-card&lt;/span&gt;
&lt;span class="nt"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.warmtepomp_int_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Heatpump resistor&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.car_grid_power_2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Laadpaal grid&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.car_solar_power_2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Laadpaal solar&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.tuinhuis_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Tuinhuis&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.nikoplug_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Ventilatie&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.badkamer_chauffage_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Badkamer&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.kitchen_1_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Keuken ww&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.droogkast_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Droogkast&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.wasmachine_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Wasmachine&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.fornuis_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Fornuis&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.kitchen_2_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Keuken links&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.oven_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Oven&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.sdm120_1_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Warmtepomp&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.sdm120_2_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Airco&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.veranda_power&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Veranda&lt;/span&gt;
&lt;span class="nt"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.airco_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.warmtepomp_int_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.boiler_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.tuinhuis_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.car_solar_power_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.car_grid_power_2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.nikoplug_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.badkamer_chauffage_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.kitchen_1_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.droogkast_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.wasmachine_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.fornuis_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.kitchen_2_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.oven_power&lt;/span&gt;
&lt;span class="nt"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;custom:bar-card&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|-&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;${ vars.filter(v =&amp;gt; {&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;let ent = states[v.entity];&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;if(ent === undefined || ent.state === undefined) {&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;console.warn(`Power meter: Entity ${v.entity} not found`);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;else if(ent.state === &amp;#39;unknown&amp;#39;) {&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;console.warn(`Power meter: Entity ${v.entity} state is unknown`);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;else if(isNaN(ent.state)) {&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;console.warn(`Power meter: Entity ${v.entity} state is not a number`);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="no"&gt;else return Number(ent.state) &amp;gt; 5 || true;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;}).sort((v1,v2) =&amp;gt; states[v2.entity].state - states[v1.entity].state)}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;right&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;entity_row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;&amp;gt;-&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;${ Math.max(...vars.map(v =&amp;gt; states[v.entity]).filter(e =&amp;gt; !!e).map(e =&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="no"&gt;e.state).filter(n =&amp;gt; !isNaN(n))) }&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;20px&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;vertical&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mdi:flash&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;off&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;outside&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;inside&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;inside&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#a1a1a18a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#3ea8328a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#85a8328a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;50&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#a8a4328a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;50&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;200&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#a887328a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;200&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;500&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#a867328a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;500&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1000&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#a846328a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1000&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3000&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#a832328a&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3000&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#states&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{\n&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;margin:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1px;\n}\nbar-card-name,\nbar-card-value&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{\n&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;font-size:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0.9rem;\n&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;color:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;#ffffffaa;\n&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;font-weight:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bold;\n}\nbar-card-value\t{\n&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;font-weight:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bolder;\n}\nbar-card-indicator&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{\n&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;margin-top:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4px;\n&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;transform:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scaleY(-1);\n}&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="power"/><category term="modbus"/><category term="homeassistant"/></entry><entry><title>www.thouters.be facelift 2025</title><link href="https://www.thouters.be/ThoutersDotBeAnno2025.html" rel="alternate"/><published>2025-07-05T17:45:00+02:00</published><updated>2025-07-05T17:45:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2025-07-05:/ThoutersDotBeAnno2025.html</id><summary type="html">
&lt;p&gt;Halfway 2025 I gave my homepage a facelift by putting on my web developer hat and doing some HTML and CSS work.&lt;/p&gt;
&lt;p&gt;I wanted to have a summary of all customisations I have done to the Pelican configuration, so I wrote this post about it.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;style&gt; .centerimg {  max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} &lt;/style&gt;
&lt;style&gt; .borderimg {  max-width: 90%; display: block; border-style: dashed; border-width: 1px; border-color: white; margin-left: auto; margin-right: auto; border-radius: 0.3em;} &lt;/style&gt;&lt;div class="figure align-center"&gt;
&lt;object class="centerimg" data="https://www.thouters.be/img/ThoutersDotBeAnno2025/pelican-logo.svg" type="image/svg+xml"&gt;test&lt;/object&gt;
&lt;p class="caption"&gt;Pelican static site generator logo&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Halfway 2025 I gave my homepage a facelift by putting on my web developer hat and doing some HTML and CSS work.&lt;/p&gt;
&lt;p&gt;I wanted to have a summary of all customisations I have done to the Pelican configuration, so I wrote this post about it.&lt;/p&gt;

&lt;div class="section" id="snapshot-photo"&gt;
&lt;h2&gt;Snapshot photo&lt;/h2&gt;
&lt;p&gt;For the record, here is a screenshot of the website at the time of writing;&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" class="borderimg" src="https://www.thouters.be/img/ThoutersDotBeAnno2025/screenshot.png" /&gt;
&lt;p class="caption"&gt;thouters.be website screenshot in 2025&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-codebase"&gt;
&lt;h2&gt;The codebase&lt;/h2&gt;
&lt;p&gt;I set up &lt;a class="reference external" href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; some years ago since I use and prefer &lt;a class="reference external" href="https://docutils.sourceforge.io/rst.html"&gt;reStructuredText&lt;/a&gt; over markdown,
and like the fact that Pelican is written in Python and customisable. Each post has its own file
in the repository, and a makefile triggers an upload to my web server using &lt;tt class="docutils literal"&gt;rsync&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-theme"&gt;
&lt;h2&gt;The theme&lt;/h2&gt;
&lt;p&gt;I used the &lt;a class="reference external" href="https://github.com/jody-frankowski/blue-penguin"&gt;Blue Penguin&lt;/a&gt; theme by &lt;a class="reference external" href="https://tyler-carr.com/"&gt;Tyler Carr&lt;/a&gt; as basis for the website style, but heavily
modified it over the years. The &lt;a class="reference external" href="https://github.com/tcarwash/blue-penguin-dark"&gt;Blue Penguin Dark&lt;/a&gt; theme exists nowadays, I don't exactly remember if I
used the dark theme, or if I darkened it myself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="flexible-box-layout-cards-from-ghost-s-casper-theme"&gt;
&lt;h2&gt;Flexible Box layout cards from Ghost's Casper theme&lt;/h2&gt;
&lt;p&gt;I've (jealously) been eyeballing some homepages that use Ghost, and they all use a &lt;a class="reference external" href="https://tympanus.net/codrops/css_reference/flexbox/"&gt;flexible box layout&lt;/a&gt;
(ccs &lt;tt class="docutils literal"&gt;display: flex;&lt;/tt&gt;) combined with some slick visual design.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://https://blog.brixit.nl/"&gt;Martijn Braam&lt;/a&gt; created a jinja HTML template to render his website with such a design and shared in the &lt;a class="reference external" href="https://git.sr.ht/~martijnbraam/spook"&gt;spook&lt;/a&gt;
repository's &lt;tt class="docutils literal"&gt;static_casper&lt;/tt&gt; folder.  I could also find the HTML style file (CSS) in the repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="infinite-scrolling"&gt;
&lt;h2&gt;Infinite scrolling&lt;/h2&gt;
&lt;p&gt;When websites have a lot of data to display they typically paginate/limit the data that is
presented when the webpage loads. Remember the Gooooooooogle at the bottom of your search results,
with every o representing one of the result pages you can click? Nowadays you typically get a 'load more' at the
bottom of a webpage, or a webpage that you can seem to scroll indefinitely on (e.g. an algorithm firehose like xitter or Tiktok).&lt;/p&gt;
&lt;p&gt;On my front-page, you used to have to click a next page / previous page to navigate the large list of posts,
but by adding &lt;a class="reference external" href="https://infinite-scroll.com/"&gt;a small piece of ready made javascript&lt;/a&gt;, the content of the next pages gets loaded into the
front-page when scrolling downwards.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://www.thouters.be/theme/js/infinite-scroll.pkgd.min.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.post-feed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;infScroll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InfiniteScroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// options&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.next_page&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.post&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;js-only-message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;block&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="visually-attractive-summaries"&gt;
&lt;h2&gt;Visually attractive summaries&lt;/h2&gt;
&lt;p&gt;The ghost theme's article card features a title, a summary and a picture.&lt;/p&gt;
&lt;p&gt;These can be extracted from your posts by some plugins.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;featured_image&lt;/tt&gt; plugin can extract the first image from an article and
exposes it in the template.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;PLUGINS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;featured_image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The summary itself can be retrieved by the &lt;tt class="docutils literal"&gt;summary&lt;/tt&gt; plugin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;PLUGINS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;summary&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By default the &lt;tt class="docutils literal"&gt;summary&lt;/tt&gt; plugin extracts the first paragraph, but
by using markers like &lt;tt class="docutils literal"&gt;PELICAN_BEGIN_SUMMARY&lt;/tt&gt; in your content you
can pick a snipet of the intro section instead.&lt;/p&gt;
&lt;p&gt;I typically begin my posts with metadata, an image and summary markers:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
:author: Thomas Langewouters
:description: website stuff
:date: 2025-04-10 17:45
:tags: homepage, etching, diycircuitboard, kicad
:slug: ThoutersDotBeAnno2025
:image: https://www.thouters.be/img/ThoutersDotBeAnno2025/pelican-logo.svg


.. figure:: {static}img/ArticleName/image.png
   :alt: image
   :align: center

   description of the image that is used as featured image.


.. PELICAN_BEGIN_SUMMARY

some introduction that also serves as summary


.. PELICAN_END_SUMMARY

.. contents:: Table of Contents
&lt;/pre&gt;
&lt;div class="section" id="better-fitting-cover-images"&gt;
&lt;h3&gt;Better fitting cover images&lt;/h3&gt;
&lt;p&gt;My articles usually start with an image/photo of the project.  The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;featured-image&lt;/span&gt;&lt;/tt&gt; plugin picks this
up and no additional work is required.&lt;/p&gt;
&lt;p&gt;The ghost theme uses an &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;object-fit:&lt;/span&gt; cover&lt;/tt&gt; property for the image in the post preview cards, which
makes it clip the image if the photo is a square instead of a wide rectangle.&lt;/p&gt;
&lt;p&gt;Sometimes this is not what you want. the &lt;tt class="docutils literal"&gt;:image:&lt;/tt&gt; metadata tag can be used
to set a custom one that has different aspect ratio and doesn't clip the image beyond recognition.&lt;/p&gt;
&lt;p&gt;The article listing also features a reading time, provided by the &lt;em&gt;reading time plugin&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="stripping-link-tags-from-the-summaries"&gt;
&lt;h3&gt;Stripping link tags from the summaries&lt;/h3&gt;
&lt;p&gt;The summaries are displayed on the front page in link target (an anchor hyperlink tag; &lt;tt class="docutils literal"&gt;&amp;lt;a href=&lt;/tt&gt;)
so they can be clicked.  To avoid the inconsistency nested links would bring, the summaries themselves can't contain links themselves.&lt;/p&gt;
&lt;p&gt;I implemented an extra template filter to do this stripping in the jinja_filters plugin (you can find it in &lt;a class="reference external" href="https://github.com/pelican-plugins/jinja-filters/pull/25"&gt;this pull request&lt;/a&gt;,
a the time of writing it's not yet merged)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;PLUGINS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pelican.plugins.jinja_filters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And used like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;div class=&amp;quot;post-card-excerpt&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.summary&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;unwrap_links&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="visitor-comments"&gt;
&lt;h2&gt;Visitor comments&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://carlschwan.eu/"&gt;Carl Schwan&lt;/a&gt; had an interesting idea, he uses a static site generator as well, and
that means the website can't accept and store data, like for visitor comments.&lt;/p&gt;
&lt;p&gt;Handling and storing the comments would even expose you to GDPR requests and require
cookie banners. It's better if you can delegate this to a third party service.&lt;/p&gt;
&lt;p&gt;So static websites usually use a third party service like &lt;a class="reference external" href="https://github.com/utterance/utterances"&gt;github utterances&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/"&gt;Carl's idea was to create a mastodon toot per article&lt;/a&gt; (a toot is a social media post, 'tweet') , and
have people discuss and reply to that specific toot on the mastodon (fediverse) social
network.  &lt;a class="reference external" href="https://danielpecos.com/2022/12/25/mastodon-as-comment-system-for-your-static-blog/"&gt;Other people have made similar implementations&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;This works fine if your reading audience has a mastodon account, and great if they find their
way to your website via the mastodon toot that you are using as the discussion board.&lt;/p&gt;
&lt;p&gt;I adapted his javascript code to fit my website style.&lt;/p&gt;
&lt;p&gt;It's not so accessible than other options like discus though, visitors need to have a fediverse
account, and most people simply don't have an account.&lt;/p&gt;
&lt;!-- https://github.com/dpecos/mastodon-comments?tab=readme-ov-file --&gt;
&lt;/div&gt;
&lt;div class="section" id="pagination-paths"&gt;
&lt;h2&gt;Pagination paths&lt;/h2&gt;
&lt;p&gt;I don't like subfolders for tags or index pages, so I changed this pelican setting:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;# example pagination pattern
&lt;span class="w"&gt; &lt;/span&gt;PAGINATION_PATTERNS = (
&lt;span class="w"&gt; &lt;/span&gt;    (1, &amp;#39;{url}&amp;#39;, &amp;#39;{save_as}&amp;#39;),
&lt;span class="gd"&gt;-    (2, &amp;#39;{base_name}/page/{number}/&amp;#39;, &amp;#39;{base_name}/page/{number}/index.html&amp;#39;),&lt;/span&gt;
&lt;span class="gi"&gt;+    (2, &amp;#39;{base_name}/page-{number}-&amp;#39;, &amp;#39;{base_name}/page-{number}-index.html&amp;#39;),&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="devcontainer"&gt;
&lt;h2&gt;Devcontainer&lt;/h2&gt;
&lt;p&gt;Devcontainers are a nice way to create a reproducible development environment
that has all the necessary packages to render your pelican homepage:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Pelican Static Site Generator&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;image&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mcr.microsoft.com/devcontainers/python:1-3.12-bullseye&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Features to add to the dev container. More info: https://containers.dev/features.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;features&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ghcr.io/devcontainers/features/git:1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ghcr.io/devcontainers/features/github-cli:1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Use &amp;#39;forwardPorts&amp;#39; to make a list of ports inside the container available locally.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;forwardPorts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Use &amp;#39;postCreateCommand&amp;#39; to run commands after the container is created.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;postCreateCommand&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pip install --user -r requirements.txt &amp;amp;&amp;amp; pip install --user -r pelican-plugins/requirements.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Configure tool-specific properties.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;customizations&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;vscode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;extensions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ms-python.python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ms-python.flake8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ms-python.black-formatter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bradlc.vscode-tailwindcss&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;formulahendry.auto-rename-tag&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ms-vscode.vscode-json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;redhat.vscode-yaml&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;yzhang.markdown-all-in-one&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;settings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;python.defaultInterpreterPath&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/local/bin/python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;python.linting.enabled&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;python.linting.flake8Enabled&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;python.formatting.provider&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;black&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;files.associations&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;*.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;*.j2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jinja-html&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;remoteUser&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vscode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Add environment variables&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;containerEnv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;PELICAN_THEME_PATH&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;${containerWorkspaceFolder}/theme&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Add workspace mounts for better performance&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mounts&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;source=${localWorkspaceFolder}/output,target=${containerWorkspaceFolder}/output,type=bind,consistency=cached&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;source=${localWorkspaceFolder}/output_drafts,target=${containerWorkspaceFolder}/output_drafts,type=bind,consistency=cached&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;source=${localWorkspaceFolder}/output_local,target=${containerWorkspaceFolder}/output_local,type=bind,consistency=cached&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Add useful tasks&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;portsAttributes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;8000&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;label&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Pelican Dev Server&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;onAutoForward&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;notify&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It needs a &lt;tt class="docutils literal"&gt;requirements.txt&lt;/tt&gt; file with a list of python packages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Pelican and core dependencies&lt;/span&gt;
&lt;span class="na"&gt;pelican[markdown]&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4.8.0&lt;/span&gt;
&lt;span class="na"&gt;invoke&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2.0.0&lt;/span&gt;
&lt;span class="na"&gt;livereload&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2.6.3&lt;/span&gt;

&lt;span class="c1"&gt;# Plugin dependencies&lt;/span&gt;
&lt;span class="na"&gt;Markdown&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3.4.0&lt;/span&gt;
&lt;span class="na"&gt;Pillow&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;9.0.0&lt;/span&gt;
&lt;span class="na"&gt;beautifulsoup4&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4.11.0&lt;/span&gt;
&lt;span class="na"&gt;pelican-sitemap&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;pelican-featured-image&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1.1.0&lt;/span&gt;
&lt;span class="na"&gt;pelican-seo&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1.4.0&lt;/span&gt;
&lt;span class="na"&gt;pelican-neighbors&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1.2.0&lt;/span&gt;
&lt;span class="na"&gt;pelican-readtime&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0.2.1&lt;/span&gt;

&lt;span class="c1"&gt;# Local plugins (editable installs)&lt;/span&gt;
&lt;span class="na"&gt;-e ./jinja-filters&lt;/span&gt;

&lt;span class="c1"&gt;# Development tools&lt;/span&gt;
&lt;span class="na"&gt;flake8&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5.0.0&lt;/span&gt;
&lt;span class="na"&gt;black&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;22.0.0&lt;/span&gt;

&lt;span class="c1"&gt;# Additional useful packages for static site generation&lt;/span&gt;
&lt;span class="na"&gt;jinja2&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3.1.0&lt;/span&gt;
&lt;span class="na"&gt;python-dateutil&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2.8.0&lt;/span&gt;
&lt;span class="na"&gt;feedgenerator&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2.0.0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build the devcontainer with devpod up (potentially add --ide=none):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;devpod&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--devcontainer-path&lt;span class="o"&gt;=&lt;/span&gt;.devcontainer/devcontainer.json
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now you should be able to use this command (Makefile rule is shown) to generate your HTML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;devpod&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--command&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pelican -s pelicanconf_publish.py&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or add &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-rl&lt;/span&gt;&lt;/tt&gt; to have pelican serve the HTML locally as preview and for e.g. proofreading
your drafts articles with a custom configuration file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;devpod&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--command&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pelican -rls pelicanconf_drafts.py&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The custom configuration files makes the articles not marked as published explicitly published:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pelicanconf.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;SITEURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.thouters.be/&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;SITENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Thomas shares DRAFTS&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;OUTPUT_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;output_drafts&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;RELATIVE_URLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;DEFAULT_METADATA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;published&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="todo-wish-list"&gt;
&lt;h2&gt;TODO/wish list&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;I still need to check which plugin for displaying a photo album with thumbnails will work best for me.&lt;/li&gt;
&lt;li&gt;When I have a project with PDF attachements, some simple macro to show a box with pdf icon and filename would be nice to have.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="homepage"/><category term="webdev"/></entry><entry><title>Rainwatertank Level Pressure Probe</title><link href="https://www.thouters.be/RainWaterTankLevelPressureProbe.html" rel="alternate"/><published>2025-06-30T20:00:00+02:00</published><updated>2025-06-30T20:00:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2025-06-30:/RainWaterTankLevelPressureProbe.html</id><summary type="html">
&lt;p&gt;A few years ago, I designed and built my own &lt;a class="reference external" href="https://www.thouters.be/RainWaterTankLevelSensor.html"&gt;rainwater tank level sensor&lt;/a&gt; for
fun. Unfortunately the unthinkable happened: the grommet sealing the cable
inlet leaked and its insides got wet...  I had to fix it or get an other one.&lt;/p&gt;
&lt;p&gt;My brother had installed a submersible Pressure Transducer in his rainwater
tank, and rather than improving my old project I chose to use his aproach and
get an off the shelve sensor to measure the tank level and use the saved time
for other fun projects.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="rain water level sensor principle" src="https://www.thouters.be/img/RainWaterTankLevelPressureProbe/levelsensor.png" /&gt;
&lt;p class="caption"&gt;Pressure based water level sensor&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;A few years ago, I designed and built my own &lt;a class="reference external" href="https://www.thouters.be/RainWaterTankLevelSensor.html"&gt;rainwater tank level sensor&lt;/a&gt; for
fun. Unfortunately the unthinkable happened: the grommet sealing the cable
inlet leaked and its insides got wet...  I had to fix it or get an other one.&lt;/p&gt;
&lt;p&gt;My brother had installed a submersible Pressure Transducer in his rainwater
tank, and rather than improving my old project I chose to use his aproach and
get an off the shelve sensor to measure the tank level and use the saved time
for other fun projects.&lt;/p&gt;

&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#principle" id="toc-entry-1"&gt;Principle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#current-loop-to-modbus" id="toc-entry-2"&gt;Current loop to Modbus&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#configuring-and-testing" id="toc-entry-3"&gt;Configuring and testing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#automations" id="toc-entry-4"&gt;Automations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="principle"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Principle&lt;/a&gt;&lt;/h2&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="modbus adc module" src="https://www.thouters.be/img/RainWaterTankLevelPressureProbe/principle.jpg" /&gt;
&lt;p class="caption"&gt;Pressure level transmitter principle.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The weight of the water column creates a pressure difference which
the transmitter module converts to an electric current value.&lt;/p&gt;
&lt;p&gt;This sensor is a drop-in replacement for my own &lt;a class="reference external" href="https://www.thouters.be/RainWaterTankLevelSensor.html"&gt;rainwater tank level sensor&lt;/a&gt;,
so hooking the sensor up is exactly the same.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;In the article I wrote about the sensor I designed I explain how the current loop principle works, check it out for
more juicy details.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="current-loop-to-modbus"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Current loop to Modbus&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I bought a very crappy Analog to digital conversion module on Aliexpress:&lt;/p&gt;
&lt;p&gt;It's a 6€ &amp;quot;4-20MA RS485 Voltage Current Analog Collector ADC Modbus RTU 4-Channel N4AIA04 Voltage Current Acquisition Module&amp;quot;,
which I plan to replace with a device of my own design somewhere in the future.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="modbus adc module" src="https://www.thouters.be/img/RainWaterTankLevelPressureProbe/adc.png" /&gt;
&lt;p class="caption"&gt;Analog (4-20mA and 0-10V) to Modbus converter&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The modbus protocol is quite simple. It defines some commands to query devices on a shared serial bus.
This is the same serial encoding you find on FTDI USB to serial cables and Arduino UART pins, although
the electrical connection works half-duplex, only one device talks at the same time. The electrical signals
are set up in a differential way, this makes it possible to have a long bus cable of a twisted wire pair,
with devices connected onto this cable at any point of the cable.&lt;/p&gt;
&lt;p&gt;The modbus commands are fairly simple, and can be implemented using modbus libraries on python or embedded
platforms like the arduino. For our purposes we need a PC modbus client to configure our modbus device, and
the home assistant integration to send read register commands to the device to read the current register's value.&lt;/p&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;N4AIA04 manual.pdf&lt;/tt&gt; lists the commands and other device details.&lt;/p&gt;
&lt;div class="section" id="configuring-and-testing"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Configuring and testing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First we need to set the Modbus address to be an unique value using &lt;a class="reference external" href="https://pypi.org/project/modbus-cli/"&gt;modbus-cli&lt;/a&gt; (and perhaps run &lt;tt class="docutils literal"&gt;pip install modbus_cli&lt;/tt&gt;
first):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;modbus&lt;span class="w"&gt;  &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyUSB0&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;14&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Read current values&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;modbus&lt;span class="w"&gt;  &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyUSB0&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next we need some Homeassistant configuration, first we
set the details in &lt;tt class="docutils literal"&gt;modbus.yaml&lt;/tt&gt; on how to read the sensor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;level_current&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;6&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;data_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uint16&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;unit_of_measurement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mA&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;device_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;current&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And add a template sensor(&lt;tt class="docutils literal"&gt;template.yaml&lt;/tt&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;time_pattern&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.filtered_level_current&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;not_from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unavailable&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;not_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unavailable&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rainwater_level&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;rainwater_level&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;unit_of_measurement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;device_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;signal_strength&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;state_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;measurement&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set l = states(&amp;#39;sensor.filtered_level_current&amp;#39;)|float %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set level = (((l - 4)/16) * 100 ) %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{{ &amp;#39;%0.3f&amp;#39; | format(level) }}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;now().minute&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To overcome the noise of the crappy ADC Modbus module, I added a low pass filter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;filter&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;filtered_level_current&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.level_current&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;time_simple_moving_average&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;window_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;01:00&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="automations"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Automations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What's next is to add an automation that sends me an alert when the water level gets too low.
For the time being my obsession about the data and plots keeps me informed, but at one point it will grow old and the
toilet might stop flushing suddenly...&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="modbus"/><category term="homeassistant"/><category term="watertank"/></entry><entry><title>Power indicator Gauge</title><link href="https://www.thouters.be/PowerGauge.html" rel="alternate"/><published>2025-06-16T23:00:00+02:00</published><updated>2025-06-16T23:00:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2025-06-16:/PowerGauge.html</id><summary type="html">
&lt;p&gt;My &lt;a class="reference external" href="https://www.thouters.be/HaspLvglBuild.html"&gt;openHASP control panels&lt;/a&gt; have been eye catchers on my desk for years now.
I've been using the &lt;a class="reference external" href="https://www.openhasp.com/0.7.0/hardware/wireless-tag/wt32-sc01-plus/"&gt;WT32-SC01 Plus&lt;/a&gt; one for over a year to keep an eye on the electric power
use in our house.&lt;/p&gt;
&lt;p&gt;I designed a fancy power dial with extra arc indicators on the outside and a dynamic list of the biggest power consumers
in the middle..&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;style&gt; .svgconcept {  align: center; background: rgba(255, 255, 255, 1); max-width: 75%; display: block; margin-left: auto; margin-right: auto; margin-top: 0.3em; padding: 10px; border-radius: 0.3em;} &lt;/style&gt;&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/PowerGauge/gaugeplant.jpg" /&gt;
&lt;p class="caption"&gt;Openhasp device&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;My &lt;a class="reference external" href="https://www.thouters.be/HaspLvglBuild.html"&gt;openHASP control panels&lt;/a&gt; have been eye catchers on my desk for years now.
I've been using the &lt;a class="reference external" href="https://www.openhasp.com/0.7.0/hardware/wireless-tag/wt32-sc01-plus/"&gt;WT32-SC01 Plus&lt;/a&gt; one for over a year to keep an eye on the electric power
use in our house.&lt;/p&gt;
&lt;p&gt;I designed a fancy power dial with extra arc indicators on the outside and a dynamic list of the biggest power consumers
in the middle..&lt;/p&gt;

&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-idea" id="toc-entry-1"&gt;The idea&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#implementation" id="toc-entry-2"&gt;Implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#hardware" id="toc-entry-3"&gt;Hardware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#appdaemon-automation" id="toc-entry-4"&gt;Appdaemon automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#openhasp-design" id="toc-entry-5"&gt;OpenHASP design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#home-assistant-dashboard-equivalent" id="toc-entry-6"&gt;Home assistant dashboard equivalent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#chores" id="toc-entry-7"&gt;Chores&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="the-idea"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;The idea&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I had this idea of using a classic needle gauge to show the momentary power level,
and add a few colored arcs to the sides to visualize these things:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Momentary total power consumed by all devices&lt;/li&gt;
&lt;li&gt;In green, the solar production arcs up from the left side, if this arc matches the needle, or goes above it, all is good.&lt;/li&gt;
&lt;li&gt;In red, the power used from the grid, ideally no red line appears at all!&lt;/li&gt;
&lt;li&gt;In magenta, the power injected to the grid arcs from the right side back to the left. If its length is equal to the green arc, we're injecting all solar production.&lt;/li&gt;
&lt;li&gt;In turquoise, the &lt;em&gt;solar&lt;/em&gt; power used for charging the car is shown arcing from the right. If there's more solar than the car can handle, a piece of magenta might pop over it at the bottom.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="figure align-center"&gt;
&lt;object class="svgconcept" data="https://www.thouters.be/img/PowerGauge/concept.svg" type="image/svg+xml"&gt;placeholder&lt;/object&gt;
&lt;p class="caption"&gt;Concept diagram&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Here are some early screenshots of the prototypes:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/PowerGauge/combined.png" /&gt;
&lt;p class="caption"&gt;Screenshots&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I added a top 4 of the biggest consumers along with progress bars representing their
relative demand (including colors, car solar use is blue!). I got this idea from Sean Blanchfield's
blog post on &lt;a class="reference external" href="https://seanblanchfield.com/2022/05/real-time-device-power-meters-in-home-assistant"&gt;Real Time Device Power Meter for Home Assistant&lt;/a&gt;.  I have the same view on my Home assistant
dashboard using the widget mentioned on that post, and it's so nice it shouldn't be missing from my openHASP page!&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/PowerGauge/raspberrypi.jpg" /&gt;
&lt;p class="caption"&gt;Raspberrypi running openHASP showing the consumers.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="implementation"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Implementation&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="section" id="hardware"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Hardware&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I had WT32-SC01 board, it's a nice display with a capacitive touchscreen layer, way better than the resistive ones
on my previous builds.  I wanted to use the same shaped enclosure as my my other &lt;a class="reference external" href="https://www.thouters.be/HaspLvglBuild.html"&gt;openhasp builds&lt;/a&gt;
and found this &lt;a class="reference external" href="https://www.printables.com/model/836975-wt32-sc01-plus-stand-with-socket"&gt;WT32-SC01-Plus Stand With Socket&lt;/a&gt; enclosure on printables.com.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="appdaemon-automation"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Appdaemon automation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This appdaemon script monitors the homeassistant entities, and will publish messages to the MQTT server
to update the openHASP widget state to reflect the new values.&lt;/p&gt;
&lt;p&gt;The appdaemon apps.yaml creates an instance of the automation and sets the parameters. Note that in the YAML snipet below there are some YAML merge keys and reference labels
to reuse parts of the yaml configuration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;powerconsumers_desk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;*plate_desk&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;&amp;amp;powerconsumers&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;powerconsumers&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;PowerConsumers&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;widget_start_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;42&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;4&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;&amp;amp;power_sensors&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.sdm120_2_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Airco&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.warmtepomp_int_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;WP Weerstand&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.sdm120_1_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Warmtepomp&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.tuinhuis_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Tuinhuis&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.car_solar_power_2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Laadpaal Zon&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.car_grid_power_2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Laadpaal Net&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.nikoplug_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Ventilatie&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.badkamer_chauffage_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Badkamer&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.kitchen_1_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Keuken-ww&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.kitchen_2_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Keuken l&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.droogkast_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Droogkast&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.wasmachine_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Wasmachine&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.veranda_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Veranda&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.fornuis_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Kookvuur&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.oven_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Oven&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;sensor.berging_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Diepvries&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;force_bar_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Laadpaal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Zon&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#008b8b&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The implementation in Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;appdaemon.plugins.hass.hassapi&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;hass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;mqttapi&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;mqtt&lt;/span&gt;

&lt;span class="n"&gt;default_colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#6f706f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#0c7600&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#537600&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#767200&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#765500&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#763500&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#761400&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#760000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PowerConsumers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hass&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mqtt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mqtt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;force_bar_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;force_bar_color&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,{})&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;widget_start_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;widget_start_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;widgets&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;device_topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;device_topic&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sensors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lwt_topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lwt_topic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;page&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;colors&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;default_colors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;colors must be a list or tuple&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list entry &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; should be a list or tuple&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list entry &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; should be a number&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list entry &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; should be a number&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list entry &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; should be a string&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list entry &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; should be a HTML color&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;list entry &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; should be a HTML color&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cb_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;got update for  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;redrawing!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;highest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
        &lt;span class="n"&gt;to_show&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
        &lt;span class="n"&gt;to_show&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_show&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_show&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publish_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sensors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;highest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ZeroDivisionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watt_to_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;watt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;watt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;watt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hasp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/command/p&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widget_start_id&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.val&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mqtt/publish&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;force_bar_color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;force_bar_color&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watt_to_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mqtt/publish&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hasp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/command/p&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widget_start_id&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.bg_color10&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mqtt/publish&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hasp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/command/p&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widget_start_id&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mqtt/publish&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hasp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/command/p&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widget_start_id&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.text&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;W&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mqtt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_plugin_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MQTT&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validate_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entity_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sensors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;can&amp;#39;t retrieve entity &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; on start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listen_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entity_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mqtt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listen_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mqtt_message_received_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;MQTT_MESSAGE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lwt_topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mqtt_message_received_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;#topic,message&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;MQTT_MESSAGE&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MQTT &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; **&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;topic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payload&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="openhasp-design"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;OpenHASP design&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is the list of OpenHASP widgets that make up the plate's 'page' that displays the power gauge,
it consists of JSON lines, one line per widget:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;{&amp;quot;page&amp;quot;:2,&amp;quot;comment&amp;quot;:&amp;quot;---------- Page 2 ----------&amp;quot;, &amp;quot;bg_color&amp;quot;:&amp;quot;#000000&amp;quot;, &amp;quot;swipe&amp;quot;:1}
{&amp;quot;id&amp;quot;:13,&amp;quot;obj&amp;quot;:&amp;quot;gauge&amp;quot;,&amp;quot;x&amp;quot;:20,&amp;quot;y&amp;quot;:20,&amp;quot;w&amp;quot;:260,&amp;quot;h&amp;quot;:260, &amp;quot;min&amp;quot;:0,&amp;quot;max&amp;quot;:10000, &amp;quot;val&amp;quot;:2000, &amp;quot;critical_value&amp;quot;: 10000, &amp;quot;scale_end_color&amp;quot;: &amp;quot;#FF0000&amp;quot;, &amp;quot;format&amp;quot;: 3, &amp;quot;bg_color&amp;quot;:&amp;quot;000000&amp;quot;, &amp;quot;scale_grad_color&amp;quot;:&amp;quot;#ffffff&amp;quot;, &amp;quot;scale_end_color&amp;quot;:&amp;quot;#ffffff&amp;quot;, &amp;quot;line_color10&amp;quot;:&amp;quot;#ffff44&amp;quot;,&amp;quot;line_width10&amp;quot;:5,&amp;quot;line_opa&amp;quot;:255, &amp;quot;line_color&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;line_color60&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;scale_grad_color&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;scale_grad_color60&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;scale_end_color60&amp;quot;:&amp;quot;#ffffff&amp;quot;, &amp;quot;bg_color10&amp;quot;:&amp;quot;#ffff44&amp;quot;, &amp;quot;border_color20&amp;quot;:&amp;quot;#000000&amp;quot;}
{&amp;quot;page&amp;quot;:2,&amp;quot;id&amp;quot;:17,&amp;quot;obj&amp;quot;:&amp;quot;arc&amp;quot;,&amp;quot;x&amp;quot;:6,&amp;quot;y&amp;quot;:10,&amp;quot;w&amp;quot;:270,&amp;quot;h&amp;quot;:290,&amp;quot;min&amp;quot;:0,&amp;quot;max&amp;quot;:10000,&amp;quot;val&amp;quot;:2000,&amp;quot;border_side&amp;quot;:0,&amp;quot;type&amp;quot;:0,&amp;quot;rotation&amp;quot;:0,&amp;quot;start_angle&amp;quot;:135,&amp;quot;end_angle&amp;quot;:45,&amp;quot;adjustable&amp;quot;:&amp;quot;false&amp;quot;,&amp;quot;line_width&amp;quot;:21,&amp;quot;line_width10&amp;quot;:10,&amp;quot;line_color10&amp;quot;:&amp;quot;#00ff00&amp;quot;,&amp;quot;line_color20&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;line_opa30&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_opa&amp;quot;:0,&amp;quot;pad_top20&amp;quot;:5,&amp;quot;pad_bottom20&amp;quot;:5,&amp;quot;pad_left20&amp;quot;:5,&amp;quot;pad_right20&amp;quot;:5,&amp;quot;pad_bottom&amp;quot;:5,&amp;quot;pad_left&amp;quot;:5,&amp;quot;pad_right&amp;quot;:5}
{&amp;quot;page&amp;quot;:2,&amp;quot;id&amp;quot;:20,&amp;quot;obj&amp;quot;:&amp;quot;arc&amp;quot;,&amp;quot;x&amp;quot;:16,&amp;quot;y&amp;quot;:10,&amp;quot;w&amp;quot;:270,&amp;quot;h&amp;quot;:270,&amp;quot;min&amp;quot;:0,&amp;quot;max&amp;quot;:10000,&amp;quot;val&amp;quot;:3000,&amp;quot;border_side&amp;quot;:0,&amp;quot;type&amp;quot;:0,&amp;quot;rotation&amp;quot;:0,&amp;quot;start_angle&amp;quot;:135,&amp;quot;end_angle&amp;quot;:45,&amp;quot;adjustable&amp;quot;:&amp;quot;false&amp;quot;,&amp;quot;line_width&amp;quot;:21,&amp;quot;line_width10&amp;quot;:10,&amp;quot;line_color10&amp;quot;:&amp;quot;#ffff00&amp;quot;,&amp;quot;line_color20&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;line_opa30&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_opa&amp;quot;:0,&amp;quot;pad_top20&amp;quot;:5,&amp;quot;pad_bottom20&amp;quot;:5,&amp;quot;pad_left20&amp;quot;:5,&amp;quot;pad_right20&amp;quot;:5,&amp;quot;pad_bottom&amp;quot;:5,&amp;quot;pad_left&amp;quot;:5,&amp;quot;pad_right&amp;quot;:5}
{&amp;quot;page&amp;quot;:2,&amp;quot;id&amp;quot;:18,&amp;quot;obj&amp;quot;:&amp;quot;arc&amp;quot;,&amp;quot;x&amp;quot;:16,&amp;quot;y&amp;quot;:10,&amp;quot;w&amp;quot;:270,&amp;quot;h&amp;quot;:270,&amp;quot;min&amp;quot;:0,&amp;quot;max&amp;quot;:10000,&amp;quot;val&amp;quot;:500,&amp;quot;border_side&amp;quot;:0,&amp;quot;type&amp;quot;:0,&amp;quot;rotation&amp;quot;:0,&amp;quot;start_angle&amp;quot;:135,&amp;quot;end_angle&amp;quot;:45,&amp;quot;adjustable&amp;quot;:&amp;quot;false&amp;quot;,&amp;quot;line_width&amp;quot;:21,&amp;quot;line_width10&amp;quot;:10,&amp;quot;line_color10&amp;quot;:&amp;quot;#ff0000&amp;quot;,&amp;quot;line_color20&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;line_opa30&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_opa&amp;quot;:0,&amp;quot;pad_top20&amp;quot;:5,&amp;quot;pad_bottom20&amp;quot;:5,&amp;quot;pad_left20&amp;quot;:5,&amp;quot;pad_right20&amp;quot;:5,&amp;quot;pad_bottom&amp;quot;:5,&amp;quot;pad_left&amp;quot;:5,&amp;quot;pad_right&amp;quot;:5}
{&amp;quot;page&amp;quot;:2,&amp;quot;id&amp;quot;:25,&amp;quot;obj&amp;quot;:&amp;quot;arc&amp;quot;,&amp;quot;x&amp;quot;:16,&amp;quot;y&amp;quot;:10,&amp;quot;w&amp;quot;:270,&amp;quot;h&amp;quot;:270,&amp;quot;min&amp;quot;:0,&amp;quot;max&amp;quot;:10000,&amp;quot;val&amp;quot;:0,&amp;quot;border_side&amp;quot;:0,&amp;quot;type&amp;quot;:2,&amp;quot;rotation&amp;quot;:0,&amp;quot;start_angle&amp;quot;:135,&amp;quot;end_angle&amp;quot;:45,&amp;quot;adjustable&amp;quot;:&amp;quot;false&amp;quot;,&amp;quot;line_width&amp;quot;:21,&amp;quot;line_width10&amp;quot;:10,&amp;quot;line_color10&amp;quot;:&amp;quot;#008b8b&amp;quot;,&amp;quot;line_color20&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;line_opa30&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_opa&amp;quot;:0,&amp;quot;pad_top20&amp;quot;:5,&amp;quot;pad_bottom20&amp;quot;:5,&amp;quot;pad_left20&amp;quot;:5,&amp;quot;pad_right20&amp;quot;:5,&amp;quot;pad_bottom&amp;quot;:5,&amp;quot;pad_left&amp;quot;:5,&amp;quot;pad_right&amp;quot;:5}
{&amp;quot;page&amp;quot;:2,&amp;quot;id&amp;quot;:19,&amp;quot;obj&amp;quot;:&amp;quot;arc&amp;quot;,&amp;quot;x&amp;quot;:16,&amp;quot;y&amp;quot;:10,&amp;quot;w&amp;quot;:270,&amp;quot;h&amp;quot;:270,&amp;quot;min&amp;quot;:0,&amp;quot;max&amp;quot;:10000,&amp;quot;val&amp;quot;:0,&amp;quot;border_side&amp;quot;:0,&amp;quot;type&amp;quot;:2,&amp;quot;rotation&amp;quot;:0,&amp;quot;start_angle&amp;quot;:135,&amp;quot;end_angle&amp;quot;:45,&amp;quot;adjustable&amp;quot;:&amp;quot;false&amp;quot;,&amp;quot;line_width&amp;quot;:21,&amp;quot;line_width10&amp;quot;:10,&amp;quot;line_color10&amp;quot;:&amp;quot;#ff00ff&amp;quot;,&amp;quot;line_color20&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;line_opa30&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_opa&amp;quot;:0,&amp;quot;pad_top20&amp;quot;:5,&amp;quot;pad_bottom20&amp;quot;:5,&amp;quot;pad_left20&amp;quot;:5,&amp;quot;pad_right20&amp;quot;:5,&amp;quot;pad_bottom&amp;quot;:5,&amp;quot;pad_left&amp;quot;:5,&amp;quot;pad_right&amp;quot;:5}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:16,&amp;quot;x&amp;quot;:0,&amp;quot;y&amp;quot;:220,&amp;quot;w&amp;quot;:300,&amp;quot;h&amp;quot;:24,&amp;quot;text&amp;quot;:&amp;quot;\uF40B kW&amp;quot;,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;text_color&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;value_font&amp;quot;:22,&amp;quot;bg_color&amp;quot;:&amp;quot;#000000&amp;quot;,&amp;quot;bg_opa&amp;quot;: &amp;quot;0&amp;quot;,&amp;quot;radius&amp;quot;:0,&amp;quot;border_side&amp;quot;:0}
{&amp;quot;page&amp;quot;:2,&amp;quot;id&amp;quot;:60,&amp;quot;obj&amp;quot;:&amp;quot;obj&amp;quot;,&amp;quot;x&amp;quot;:75,&amp;quot;y&amp;quot;:75,&amp;quot;w&amp;quot;:150,&amp;quot;h&amp;quot;:150,&amp;quot;radius&amp;quot;:1000,&amp;quot;border_color&amp;quot;:&amp;quot;#000000&amp;quot;,&amp;quot;bg_opa10&amp;quot;:188,&amp;quot;bg_color10&amp;quot;:&amp;quot;#000000&amp;quot;}

{&amp;quot;obj&amp;quot;:&amp;quot;bar&amp;quot;,&amp;quot;id&amp;quot;:42,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:110,&amp;quot;w&amp;quot;:140,&amp;quot;h&amp;quot;:4,   &amp;quot;border_width&amp;quot;:0,&amp;quot;val&amp;quot;:70,&amp;quot;bg_color10&amp;quot;:&amp;quot;#a83232&amp;quot;, &amp;quot;bg_color&amp;quot;:&amp;quot;#111111&amp;quot;,&amp;quot;radius10&amp;quot;:5,&amp;quot;radius00&amp;quot;:5}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:43,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:89,&amp;quot;w&amp;quot;:139,&amp;quot;h&amp;quot;:20,&amp;quot;bg_color&amp;quot;:&amp;quot;#000000&amp;quot;,&amp;quot;border_color&amp;quot;:&amp;quot;#C7BAA7&amp;quot;,&amp;quot;border_width&amp;quot;:0,&amp;quot;text&amp;quot;:&amp;quot;Warmtepomp hitte&amp;quot;,&amp;quot;text_font&amp;quot;:16,&amp;quot;align&amp;quot;:&amp;quot;left&amp;quot;}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:44,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:90,&amp;quot;w&amp;quot;:140,&amp;quot;h&amp;quot;:20,&amp;quot;bg_color&amp;quot;:&amp;quot;#000000&amp;quot;,&amp;quot;border_color&amp;quot;:&amp;quot;#C7BAA7&amp;quot;,&amp;quot;border_width&amp;quot;:0,&amp;quot;text&amp;quot;:&amp;quot;10W&amp;quot;,&amp;quot;text_font&amp;quot;:16,&amp;quot;align&amp;quot;:&amp;quot;right&amp;quot;}

{&amp;quot;obj&amp;quot;:&amp;quot;bar&amp;quot;,&amp;quot;id&amp;quot;:45,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:140,&amp;quot;w&amp;quot;:140,&amp;quot;h&amp;quot;:4,   &amp;quot;border_width&amp;quot;:0,&amp;quot;val&amp;quot;:50,&amp;quot;bg_color10&amp;quot;:&amp;quot;#3ea832&amp;quot;, &amp;quot;bg_color&amp;quot;:&amp;quot;#111111&amp;quot;,&amp;quot;radius10&amp;quot;:5,&amp;quot;radius00&amp;quot;:5}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:46,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:120,&amp;quot;w&amp;quot;:139,&amp;quot;h&amp;quot;:20,&amp;quot;bg_color&amp;quot;:&amp;quot;#111111&amp;quot;,&amp;quot;border_color&amp;quot;:&amp;quot;#C7BAA7&amp;quot;,&amp;quot;border_width&amp;quot;:0,&amp;quot;text&amp;quot;:&amp;quot;Thuinhuis&amp;quot;,&amp;quot;text_font&amp;quot;:16,&amp;quot;align&amp;quot;:&amp;quot;left&amp;quot;}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:47,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:120,&amp;quot;w&amp;quot;:140,&amp;quot;h&amp;quot;:20,&amp;quot;bg_color&amp;quot;:&amp;quot;#000000&amp;quot;,&amp;quot;border_color&amp;quot;:&amp;quot;#C7BAA7&amp;quot;,&amp;quot;border_width&amp;quot;:0,&amp;quot;text&amp;quot;:&amp;quot;20W&amp;quot;,&amp;quot;text_font&amp;quot;:16,&amp;quot;align&amp;quot;:&amp;quot;right&amp;quot;}

{&amp;quot;obj&amp;quot;:&amp;quot;bar&amp;quot;,&amp;quot;id&amp;quot;:48,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:170,&amp;quot;w&amp;quot;:150,&amp;quot;h&amp;quot;:4,   &amp;quot;border_width&amp;quot;:0,&amp;quot;val&amp;quot;:20,&amp;quot;bg_color10&amp;quot;:&amp;quot;#a8a432&amp;quot;, &amp;quot;bg_color&amp;quot;:&amp;quot;#111111&amp;quot;,&amp;quot;radius10&amp;quot;:5,&amp;quot;radius00&amp;quot;:5}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:49,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:150,&amp;quot;w&amp;quot;:139,&amp;quot;h&amp;quot;:20,&amp;quot;bg_color&amp;quot;:&amp;quot;#111111&amp;quot;,&amp;quot;border_color&amp;quot;:&amp;quot;#C7BAA7&amp;quot;,&amp;quot;border_width&amp;quot;:0,&amp;quot;text&amp;quot;:&amp;quot;Keuken&amp;quot;,&amp;quot;text_font&amp;quot;:16,&amp;quot;align&amp;quot;:&amp;quot;left&amp;quot;}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:50,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:150,&amp;quot;w&amp;quot;:140,&amp;quot;h&amp;quot;:20,&amp;quot;bg_color&amp;quot;:&amp;quot;#000000&amp;quot;,&amp;quot;border_color&amp;quot;:&amp;quot;#C7BAA7&amp;quot;,&amp;quot;border_width&amp;quot;:0,&amp;quot;text&amp;quot;:&amp;quot;30W&amp;quot;,&amp;quot;text_font&amp;quot;:16,&amp;quot;align&amp;quot;:&amp;quot;right&amp;quot;}

{&amp;quot;obj&amp;quot;:&amp;quot;bar&amp;quot;,&amp;quot;id&amp;quot;:51,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:200,&amp;quot;w&amp;quot;:150,&amp;quot;h&amp;quot;:4,   &amp;quot;border_width&amp;quot;:0,&amp;quot;val&amp;quot;:20,&amp;quot;bg_color10&amp;quot;:&amp;quot;#a8a432&amp;quot;, &amp;quot;bg_color&amp;quot;:&amp;quot;#111111&amp;quot;,&amp;quot;radius10&amp;quot;:5,&amp;quot;radius00&amp;quot;:5}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:52,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:180,&amp;quot;w&amp;quot;:139,&amp;quot;h&amp;quot;:20,&amp;quot;bg_color&amp;quot;:&amp;quot;#111111&amp;quot;,&amp;quot;border_color&amp;quot;:&amp;quot;#C7BAA7&amp;quot;,&amp;quot;border_width&amp;quot;:0,&amp;quot;text&amp;quot;:&amp;quot;Keuken&amp;quot;,&amp;quot;text_font&amp;quot;:16,&amp;quot;align&amp;quot;:&amp;quot;left&amp;quot;}
{&amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:53,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:180,&amp;quot;w&amp;quot;:140,&amp;quot;h&amp;quot;:20,&amp;quot;bg_color&amp;quot;:&amp;quot;#000000&amp;quot;,&amp;quot;border_color&amp;quot;:&amp;quot;#C7BAA7&amp;quot;,&amp;quot;border_width&amp;quot;:0,&amp;quot;text&amp;quot;:&amp;quot;30W&amp;quot;,&amp;quot;text_font&amp;quot;:16,&amp;quot;align&amp;quot;:&amp;quot;right&amp;quot;}

{&amp;quot;page&amp;quot;:2,&amp;quot;id&amp;quot;:61,&amp;quot;obj&amp;quot;:&amp;quot;obj&amp;quot;,&amp;quot;x&amp;quot;:0,&amp;quot;y&amp;quot;:0,&amp;quot;w&amp;quot;:320,&amp;quot;h&amp;quot;:420,&amp;quot;border_opa&amp;quot;:0, &amp;quot;border_color&amp;quot;:&amp;quot;#000000&amp;quot;,&amp;quot;bg_opa10&amp;quot;:0,&amp;quot;bg_color10&amp;quot;:&amp;quot;#ffffff&amp;quot;, &amp;quot;swipe&amp;quot;:1}

{&amp;quot;page&amp;quot;:0,&amp;quot;comment&amp;quot;:&amp;quot;---------- All pages ----------&amp;quot;,&amp;quot;swipe&amp;quot;:1}
{&amp;quot;page&amp;quot;:0,&amp;quot;obj&amp;quot;:&amp;quot;btn&amp;quot;,&amp;quot;id&amp;quot;:1,&amp;quot;x&amp;quot;:0,  &amp;quot;y&amp;quot;:420,&amp;quot;w&amp;quot;:80,&amp;quot;h&amp;quot;:50,&amp;quot;opacity&amp;quot;:255,&amp;quot;text&amp;quot;:&amp;quot;\uE75A&amp;quot;,&amp;quot;radius&amp;quot;:0,&amp;quot;border_width&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_color&amp;quot;:&amp;quot;#444444&amp;quot;,&amp;quot;text_font&amp;quot;:24,&amp;quot;action&amp;quot;:&amp;quot;p1&amp;quot;,&amp;quot;text_color&amp;quot;:&amp;quot;#ffffff&amp;quot;}
{&amp;quot;page&amp;quot;:0,&amp;quot;obj&amp;quot;:&amp;quot;btn&amp;quot;,&amp;quot;id&amp;quot;:2,&amp;quot;x&amp;quot;:80,&amp;quot;y&amp;quot;:420,&amp;quot;w&amp;quot;:80,&amp;quot;h&amp;quot;:50,&amp;quot;opacity&amp;quot;:255,&amp;quot;text&amp;quot;:&amp;quot;\uF40B&amp;quot;,&amp;quot;radius&amp;quot;:0,&amp;quot;border_width&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_color&amp;quot;:&amp;quot;#44444&amp;quot;,&amp;quot;text_font&amp;quot;:24,&amp;quot;text_color&amp;quot;:&amp;quot;#ffffff&amp;quot;, &amp;quot;action&amp;quot;:&amp;quot;p2&amp;quot;}
{&amp;quot;page&amp;quot;:0,&amp;quot;obj&amp;quot;:&amp;quot;btn&amp;quot;,&amp;quot;id&amp;quot;:3,&amp;quot;x&amp;quot;:160,&amp;quot;y&amp;quot;:420,&amp;quot;w&amp;quot;:80,&amp;quot;h&amp;quot;:50,&amp;quot;opacity&amp;quot;:255,&amp;quot;text&amp;quot;:&amp;quot;\uE210&amp;quot;,&amp;quot;radius&amp;quot;:0,&amp;quot;border_width&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_color&amp;quot;:&amp;quot;#444444&amp;quot;,&amp;quot;text_font&amp;quot;:24,&amp;quot;text_color&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;action&amp;quot;:&amp;quot;p3&amp;quot;}
{&amp;quot;page&amp;quot;:0,&amp;quot;obj&amp;quot;:&amp;quot;btn&amp;quot;,&amp;quot;id&amp;quot;:5,&amp;quot;x&amp;quot;:240,&amp;quot;y&amp;quot;:420,&amp;quot;w&amp;quot;:80,&amp;quot;h&amp;quot;:50,&amp;quot;opacity&amp;quot;:255,&amp;quot;text&amp;quot;:&amp;quot;\uE2DA&amp;quot;,&amp;quot;radius&amp;quot;:0,&amp;quot;border_width&amp;quot;:&amp;quot;0&amp;quot;,&amp;quot;bg_color&amp;quot;:&amp;quot;#444444&amp;quot;,&amp;quot;text_font&amp;quot;:24,&amp;quot;text_color&amp;quot;:&amp;quot;#ffffff&amp;quot;,&amp;quot;action&amp;quot;:&amp;quot;p4&amp;quot;}

{&amp;quot;page&amp;quot;:0, &amp;quot;obj&amp;quot;:&amp;quot;label&amp;quot;,&amp;quot;id&amp;quot;:4,&amp;quot;x&amp;quot;:0,&amp;quot;y&amp;quot;:0,&amp;quot;w&amp;quot;:300,&amp;quot;h&amp;quot;:30,&amp;quot;text&amp;quot;:&amp;quot;?\uE2DA&amp;quot;,&amp;quot;value_font&amp;quot;:22,&amp;quot;bg_color&amp;quot;:&amp;quot;#000000&amp;quot;, &amp;quot;text_color&amp;quot;:&amp;quot;#ffffff&amp;quot;, &amp;quot;bg_opa&amp;quot;: 0,&amp;quot;radius&amp;quot;:0,&amp;quot;border_side&amp;quot;:0, &amp;quot;align&amp;quot;:&amp;quot;right&amp;quot;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="home-assistant-dashboard-equivalent"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Home assistant dashboard equivalent&lt;/a&gt;&lt;/h2&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/PowerGauge/homeassistant-powerflow.png" /&gt;
&lt;p class="caption"&gt;Home assistant power visualisation using bar-card and custom template card.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Check out &lt;a class="reference external" href="https://www.thouters.be/CurrentClamps.html"&gt;my article on the modbus current clamps&lt;/a&gt; to see how I measure the power on several of the breaker circuits of my house and how to add a similar bar card visualisation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="chores"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Chores&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I also added a page to the openHASP displaying the periodical house chores tracked in Grocy. This deserves its own article actually!&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/PowerGauge/chores.jpg" /&gt;
&lt;p class="caption"&gt;chores&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="solar"/><category term="3dprinting"/><category term="homeassistant"/><category term="openhasp"/></entry><entry><title>Rainwatertank Usage meter</title><link href="https://www.thouters.be/RainWaterTankUsageMeter.html" rel="alternate"/><published>2025-04-18T23:15:00+02:00</published><updated>2025-04-18T23:15:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2025-04-18:/RainWaterTankUsageMeter.html</id><summary type="html">
&lt;p&gt;I integrated a 7€ YF-B7 water flow sensor into my home
assistant setup using an off the shelve 35€ modbus based pulse counter.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;style&gt; .rollershutterbuttons {  align: center; background: rgba(255, 255, 255, 1); max-width: 50%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} &lt;/style&gt;&lt;div class="figure align-center"&gt;
&lt;img alt="empty" class="rollershutterbuttons" src="https://www.thouters.be/img/RainWaterTankUsageMeter/waterusage.jpg" /&gt;
&lt;p class="caption"&gt;Home assistant graph of water usage, the lightest shade is rain water measured by DIY solution, the darkest shade reported by the tap water
utility meter over P1 connection.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;I integrated a 7€ YF-B7 water flow sensor into my home
assistant setup using an off the shelve 35€ modbus based pulse counter.&lt;/p&gt;

&lt;div class="section" id="the-sensor"&gt;
&lt;h2&gt;The sensor&lt;/h2&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="flow sensor picture" src="https://www.thouters.be/img/RainWaterTankUsageMeter/flowsensor.jpg" /&gt;
&lt;p class="caption"&gt;The flow sensor connected to the rain water pump.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I bought from a &amp;quot;YF-B7 Water Flow Sensor with Temperature Sensor - Brass - G1/2&amp;quot; from tinytronics.nl.
This device uses a magnetic propeller sitting in the water feed to trigger pulses in a hall effect
sensor.&lt;/p&gt;
&lt;p&gt;A summary of its specifications:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
Water flow sensor:
    Supply voltage: 5-18V DC
    Signal voltage: 4.7V (with 5V DC power supply)
    Connector: JST-SM 3p female
    Pulse frequency per L/min: 11Hz*
    Duty cycle pulse: 50% ±10%
    Measuring range: 1-25L/min
    Accuracy: ±3%*
Temperature sensor:
    Type: NTC
    Resistance: 50KΩ
    Beta value: 3950
    Connector: JST-SM 2p female
    Thread: M8
Water connection: G1/2&amp;quot;
Maximum water pressure: 17.5bar
Working temperature: 1-100°C
Suitable liquids: Water**
Material: Brass
Dimensions: See images

Pinout (water flow sensor):

Red: Vcc/plus (supply voltage)
Black: GND/min
Yellow: Signal
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="pulse-counter"&gt;
&lt;h2&gt;Pulse counter&lt;/h2&gt;
&lt;p&gt;I have several sensors in my garden shed, and they are all read via
a 2 wire RS485 serial bus.&lt;/p&gt;
&lt;p&gt;I figured I could use an off the shelve pulse counter to keep track
of the water use. This way the more complex real-time tracking part can be kept
close to the sensor since modbus is probably too slow and it keeps things simple.&lt;/p&gt;
&lt;p&gt;The €34.89 &amp;quot;WJ150-485 1 Way Encoder Pulse Signal Counter or 2 Way DI High-speed Counter Modbus RTU Module&amp;quot; from
Aliexpress was a good fit, it had good documentation and I got it to work in an hour.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="rain water level sensor principle" src="https://www.thouters.be/img/RainWaterTankUsageMeter/counter.jpg" /&gt;
&lt;p class="caption"&gt;Mechanical sensor parts assembled with a very short sensor tube to fit the picture.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In set-up mode, which is enabled by connecting a wire to one of the module's screw terminals,
the module can receive ASCII commands, so a modbus RTU client is not even necessary.&lt;/p&gt;
&lt;p&gt;I sent this command to it using a serial terminal to set module address from '01' to '05', module type '00', baudrate at 9600 ('6'):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mo"&gt;01050006&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After a powercycle I checked if I could read the pulse count, and after blowing on the water flow meter,
the count register increased!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;thomas@roper&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="w"&gt; &lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;modbus&lt;span class="w"&gt;  &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyUSB0&lt;span class="w"&gt;  &lt;/span&gt;h@32/&lt;span class="se"&gt;\&amp;lt;&lt;/span&gt;I
Parsed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;registers&lt;span class="w"&gt; &lt;/span&gt;definitions&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;files
&lt;span class="m"&gt;32&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2819&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0xb03
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="home-assistant-setup"&gt;
&lt;h2&gt;Home assistant setup&lt;/h2&gt;
&lt;p&gt;I added this modbus.yaml entry:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;water_counter&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;water_counter&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;32&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;word&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;data_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;uint32&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And a template sensor to convert the pulse count to liters (template.yaml):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;time_pattern&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.water_counter&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;not_from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unavailable&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;not_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unavailable&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;regenwater&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;regenwater&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;unit_of_measurement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;L&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;device_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;water&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;state_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;total_increasing&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set count = states(&amp;#39;sensor.water_counter&amp;#39;) | float %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{{ &amp;#39;%0.3f&amp;#39; | format(count*6.0/(4*660)) }}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;now().minute&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now I can view pretty bar charts in the Home Assistant Energy overview.&lt;/p&gt;
&lt;p&gt;The accuracy might be rough, but I did some napkin math and it seems to be ballpark-accurate.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="modbus"/><category term="homeassistant"/><category term="watertank"/></entry><entry><title>Laser engraved and etched circuit boards!</title><link href="https://www.thouters.be/LaserEngravedAndEtchedDiyCircuitBoards.html" rel="alternate"/><published>2025-04-10T17:45:00+02:00</published><updated>2025-04-10T17:45:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2025-04-10:/LaserEngravedAndEtchedDiyCircuitBoards.html</id><summary type="html">
&lt;p&gt;A laser engraver can also make it easy to prepare bare circuit boards for etching at home though!
I'll explain how I do this, and how it makes creating PCB's very easy if you
have an etch tank in your lab.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/etching.jpg" /&gt;
&lt;p class="caption"&gt;etch tank&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Nowadays diode laser engravers are powerful enough to cut through a few millimeters of wood
and you can get a fairly large one for under 200 euros.&lt;/p&gt;
&lt;p&gt;Cutting wood into shapes always was a makerspace thing I loved to have in my home lab, but
it didn't justify buying one.&lt;/p&gt;

&lt;p&gt;A laser engraver can also make it easy to prepare bare circuit boards for etching at home though!
I'll explain how I do this, and how it makes creating PCB's very easy if you
have an etch tank in your lab.&lt;/p&gt;

&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#concept" id="toc-entry-1"&gt;Concept&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#why-roll-your-own-pcb-s" id="toc-entry-2"&gt;Why roll your own PCB's?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#diy-pcb-making-comparison" id="toc-entry-3"&gt;DIY PCB making comparison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#required-tools" id="toc-entry-4"&gt;Required tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#required-consumables" id="toc-entry-5"&gt;Required consumables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#filing-squaring-the-pcb-stock" id="toc-entry-6"&gt;Filing/squaring the PCB stock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#painting-the-pcb-stock" id="toc-entry-7"&gt;Painting the PCB stock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#prepare-the-design-for-engraving" id="toc-entry-8"&gt;Prepare the design for engraving&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#design-rules" id="toc-entry-9"&gt;Design rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#panellisation" id="toc-entry-10"&gt;Panellisation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#generate-svg-files" id="toc-entry-11"&gt;Generate SVG files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#generate-bitmaps" id="toc-entry-12"&gt;Generate bitmaps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#use-boardloom-to-create-a-lightburn-project" id="toc-entry-13"&gt;Use boardloom to create a Lightburn project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#manually-create-a-lightburn-project" id="toc-entry-14"&gt;Manually create a LightBurn project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#prepare-the-machine-for-engraving" id="toc-entry-15"&gt;Prepare the machine for engraving&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-copper-layers" id="toc-entry-16"&gt;The copper layers&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#engrave-the-board-s-copper-layers" id="toc-entry-17"&gt;Engrave the board's copper layers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#etch-the-board" id="toc-entry-18"&gt;Etch the board&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-solder-mask" id="toc-entry-19"&gt;The solder mask&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#a-simple-solution-for-through-hole-boards" id="toc-entry-20"&gt;A simple solution for through hole boards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#using-uv-curable-paint-laser-cleaning-pads" id="toc-entry-21"&gt;Using UV curable paint, laser cleaning pads&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-silk-screen" id="toc-entry-22"&gt;The silk screen&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#design-rules-1" id="toc-entry-23"&gt;Design rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#lightburn-design-preparation" id="toc-entry-24"&gt;Lightburn design preparation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#painting-and-drying" id="toc-entry-25"&gt;Painting and drying&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#drilling-holes" id="toc-entry-26"&gt;Drilling holes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-solderpaste-stencil" id="toc-entry-27"&gt;The solderpaste stencil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-test-project" id="toc-entry-28"&gt;The test project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#time-to-get-creative" id="toc-entry-29"&gt;Time to get creative!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#related-articles" id="toc-entry-30"&gt;Related articles&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#lasers-and-saws" id="toc-entry-31"&gt;Lasers and Saws&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#forourgood" id="toc-entry-32"&gt;ForOurGood&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#presentation-may-2025" id="toc-entry-33"&gt;Presentation May 2025&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="concept"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Concept&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Engraving wood and creating wooden boxes is nice. I love even more the idea of being able to create
prototype PCB circuit boards at home quickly and easily.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/sculpfuns9.jpg" /&gt;
&lt;p class="caption"&gt;Sculpfun S9 diode laser engraver.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I figured the engraving process can be used to
burn away paint to expose it for etching, getting rid of the tedious
photo-transfer or toner-transfer masking method which makes alignment for
dual sided boards a chore. YouTube showed me several people had the same idea
already, and I could learn a lot from them.&lt;/p&gt;
&lt;p&gt;Being scared of sharks and lasers, but considering I already had some quality eye protection and a small room where I
could set up a webcam to watch the engraving process with, I bought the thing!&lt;/p&gt;
&lt;p&gt;&lt;a class="reference internal" href="#one-of-these-youtube-videos"&gt;One of these youtube videos&lt;/a&gt; shows a clever engraving fixture for the circuit board,
making it easy to do two-sided boards.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;This is still a new project, and on the first attempt I had some issues with
engraving accuracy which might be caused by belts not tight enough and the
frame not being aligned correctly. I'll update the pictures when I solve these
issues.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="why-roll-your-own-pcb-s"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Why roll your own PCB's?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nowadays Chinese boardhouses offer cheap prices and fast delivery times.
However, it's still several days waiting time, and when you're iterating
on a design with a tight deadline and making lots of (beginner) mistakes,
this can create a lot of stress.&lt;/p&gt;
&lt;p&gt;Next to that, creating your own PCB's adds the artisanal touch to your project.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="diy-pcb-making-comparison"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;DIY PCB making comparison&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is always some work involved creating your own circuit boards. I think
my method is however relatively convenient compared to other processes.&lt;/p&gt;
&lt;br/&gt;&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="33%" /&gt;
&lt;col width="33%" /&gt;
&lt;col width="33%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Process&lt;/th&gt;
&lt;th class="head"&gt;Advantages&lt;/th&gt;
&lt;th class="head"&gt;disadvantages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Photo transfer&lt;/td&gt;
&lt;td&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;&lt;em&gt;€&lt;/em&gt; cheap&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;🖨 printer needed, transparencies&lt;/li&gt;
&lt;li&gt;photo resist coated board needed&lt;/li&gt;
&lt;li&gt;🔦 UV exposure equipment&lt;/li&gt;
&lt;li&gt;🧪 developing step&lt;/li&gt;
&lt;li&gt;🧪 Chemical etching&lt;/li&gt;
&lt;li&gt;👀 manual alignment of layers&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Toner transfer&lt;/td&gt;
&lt;td&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;&lt;em&gt;€&lt;/em&gt; cheap&lt;/li&gt;
&lt;li&gt;single sided pcbs can use toner transfer as silkscreen&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;🖨 Laser printer required&lt;/li&gt;
&lt;li&gt;🔥 Clothes iron needed&lt;/li&gt;
&lt;li&gt;messy paper soaking&lt;/li&gt;
&lt;li&gt;🧪 Chemical etching&lt;/li&gt;
&lt;li&gt;👀 manual alignment of layers&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Fiber laser&lt;/td&gt;
&lt;td&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;no need to etch, copper is vaporized completely&lt;/li&gt;
&lt;li&gt;can laser away top coating to get soldermask effect&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;&lt;em&gt;€&lt;/em&gt; Expensive laser machine needed&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;em&gt;Thouters.be method™&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;👀 Easy alignment of front/back layers&lt;/li&gt;
&lt;li&gt;no transparency mask needed for Soldermask UV exposure&lt;/li&gt;
&lt;li&gt;easy Silkscreen solution&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&lt;ul class="first last simple"&gt;
&lt;li&gt;&lt;em&gt;€&lt;/em&gt; Diode laser machine needed + Lightburn license&lt;/li&gt;
&lt;li&gt;👓 be safe when using the LASER!&lt;/li&gt;
&lt;li&gt;Two coats of paint needed (but feedstock can be prepared up front)&lt;/li&gt;
&lt;li&gt;🧪 Chemical etching&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="required-tools"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Required tools&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A laser engraver. I used a Sculpfun S9, but a CNC3018 with laser addon might be a better option.&lt;/li&gt;
&lt;li&gt;A saw or something else to cut the PCB's with; either a acryl cutting knife, plate shear or fine saw will do&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;If you want to do vias or through-hole components&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;PCB drills&lt;/li&gt;
&lt;li&gt;some power tool to drill PCB holes with (a hand tool can also work for larger holes)&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;If you want to do vias&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;A riveting tool (a 4 euro center punch hole marker will work fine)&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;An etch tank&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;can be as simple as a plastic box/tray with water + iron-3-phosphate&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://verstraten-elektronica.blogspot.com/p/bouw-een-print-etsbak.html?m=1"&gt;(you can DIY)&lt;/a&gt; a more fancy device&lt;/li&gt;
&lt;li&gt;have it as small as possible to minimize the amount of chemicals, and keep in mind that some chemicals need heating/air bubbling.&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="required-consumables"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Required consumables&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Blank (dual sided) copper clad FR4 board (I used 7cmx5cm PCB blanks from aliexpress, note they measured 71mmx51mm)&lt;/li&gt;
&lt;li&gt;Etch Masking paint: matte black spray paint (I used the cheapest one I could find from the local home improvement store)&lt;/li&gt;
&lt;li&gt;Regular paper painters tape&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;Solder Mask paint, either&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;industry standard UV curable soldermask paint + a piece of transparency or glass to press onto the paint&lt;/li&gt;
&lt;li&gt;heat resistant (black) spray paint (I used the cheapest one I could find from the local home improvement store)&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;Silk screen paint: white UV curable soldermask paint (or acrylic crafts paint, cheap one from a color assortment I had around).&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;If you want to do vias&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;through-hole 'via rivets' (length 2.5mm, m0.9 500pcs 3eur from Aliexpress)&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;To avoid via's&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;1206 size 0Ohm smd resistors to jump across 2 traces&lt;/li&gt;
&lt;li&gt;0805 size 0Ohm smd resistors to jump across 1 track.&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;An etching chemical (I use &lt;a class="reference external" href="https://www.reichelt.com/be/en/shop/product/etchant_sodium_persulphate_600_g_only_suitable_for_pcb_layout-23424?nbc=1"&gt;sodium persulphate from reichelt&lt;/a&gt; - needs heating but etches very well)&lt;/li&gt;
&lt;li&gt;optionally: 150T silkscreen printing mesh to spread the UV paint more evenly&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="filing-squaring-the-pcb-stock"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Filing/squaring the PCB stock&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The boards need to be fairly exact dimension-wise, and also need to have nice right angles.
To make it easy to prepare them, I made &lt;a class="reference external" href="https://www.printables.com/model/1329909-parametric-to-size-perpendicular-filing-jig"&gt;3D printed parametric jig&lt;/a&gt; to clamp the PCB stock
and easily file the pcb to size.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/pcb-straight.png" /&gt;
&lt;p class="caption"&gt;Parametric to-size perpendicular filing jig&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You can slide a pcb into this template from two sides, in the middle it has an opening so you can
see that the pcb edge is completely against the template.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/filingtemplate.jpg" /&gt;
&lt;p class="caption"&gt;filing jig with pcb inserted&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I have one that is 1mm to large, and one of the exact dimensions, the larger one is used
to create the first right angle between two adjecent sides, the second exact one will trim
the other two sufficiently to perfection.&lt;/p&gt;
&lt;p&gt;Use it this way:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Change the openscad file to have the board dimensions, set the oversize value to 1 (mm). Or, if the bare pcb is very trapezoidal, you may need to file away more than 1mm to get the sides perpendicular and a larger value may be required.&lt;/li&gt;
&lt;li&gt;generate and print the two jigs&lt;/li&gt;
&lt;li&gt;Put the pcbstock in the largest jig and make sure one side of the pcb is perfectly aligned with the inside of the jig&lt;/li&gt;
&lt;li&gt;Clamp the jig and file off the parts sticking out. You now have one 90degree angle on the board.&lt;/li&gt;
&lt;li&gt;rotate the pcb 180 degrees, and put the pcb in the jig that is exactly the target size&lt;/li&gt;
&lt;li&gt;clamp the jig and file off the parts sticking out&lt;/li&gt;
&lt;li&gt;the pcb should now be perpendicular and to size.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="painting-the-pcb-stock"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Painting the PCB stock&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Prepare a piece of cardboard sheet with holes to fit several PCB's in to hold the PCB's when painting.
I used the the array feature in lightburn to replicate a rectangle in a grid.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;lightly sand with steel wool, clean with isopropyl alcohol&lt;/li&gt;
&lt;li&gt;place the PCB's in the carboard sheet with holding squares. Put the cardboard in a cardboard box to catch excess paintdrops and to angle the sheet.&lt;/li&gt;
&lt;li&gt;Spraypaint the PCB's on both sides&lt;/li&gt;
&lt;li&gt;let dry, 15min in the sun will do.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="prepare-the-design-for-engraving"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Prepare the design for engraving&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="design-rules"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Design rules&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Set the via and drill hole size to something you can drill and rivet.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;minimum clearance: 0.3mm&lt;/li&gt;
&lt;li&gt;minimum track width: 0.8mm&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The minimum track with is a bit wider than SMD footprint pads for SOT-23 and SOT-8, but those
can be used without issue.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="panellisation"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Panellisation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Optionally, you can panellize several boards to etch several designs on one piece
of feedstock. To do that:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Create a new separate kicad project.&lt;/li&gt;
&lt;li&gt;open the board editor  and choosing &amp;quot;Append board&amp;quot;.&lt;/li&gt;
&lt;li&gt;Group everything in the board (right click selection, group) and select 'create from selection' 'create array'.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;The array intervals should include the board size, so measure those first and add some inter-board spacing for sawing later on.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="generate-svg-files"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;Generate SVG files&lt;/a&gt;&lt;/h3&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/export-svg.png" /&gt;
&lt;p class="caption"&gt;Kicad export settings&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="generate-bitmaps"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;Generate bitmaps&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I created this bash script I call &lt;cite&gt;prepare.sh&lt;/cite&gt; that uses the ImageMagick
'convert' command line tool to do the boring work of inverting, mirroring and
centering the design on the pcb feedstock. Somehow the svg's from KiCAD don't
seem to render correctly in LightBurn.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-ex
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt; board size x (mm)&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt; board size y (mm)&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;input
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;output
&lt;span class="nv"&gt;border_cm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.05
&lt;span class="nv"&gt;x_mm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;y_mm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;infile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;
&lt;span class="nv"&gt;outfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$cm&lt;/span&gt;&lt;span class="s2"&gt; - substrate size (rectangle)&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;dpi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dpi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;dpi
&lt;span class="nv"&gt;x_pixels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;print(int(round(&lt;/span&gt;&lt;span class="nv"&gt;$x_mm&lt;/span&gt;&lt;span class="s2"&gt;* 0.1* &lt;/span&gt;&lt;span class="nv"&gt;$dpi&lt;/span&gt;&lt;span class="s2"&gt; / 2.54)))&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;y_pixels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;print(int(round(&lt;/span&gt;&lt;span class="nv"&gt;$y_mm&lt;/span&gt;&lt;span class="s2"&gt;* 0.1* &lt;/span&gt;&lt;span class="nv"&gt;$dpi&lt;/span&gt;&lt;span class="s2"&gt; / 2.54)))&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;border_pixels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;print(int(round(&lt;/span&gt;&lt;span class="nv"&gt;$border_cm&lt;/span&gt;&lt;span class="s2"&gt;* 1.0* &lt;/span&gt;&lt;span class="nv"&gt;$dpi&lt;/span&gt;&lt;span class="s2"&gt; / 2.54)))&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pixels:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$y_pixels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$y_pixels&lt;/span&gt;

&lt;span class="c1"&gt;#render as bitmap&lt;/span&gt;
inkscape&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$infile&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--export-filename&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;exported.png&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dpi&lt;/span&gt;
&lt;span class="c1"&gt;#remove transparency&lt;/span&gt;
convert&lt;span class="w"&gt; &lt;/span&gt;exported.png&lt;span class="w"&gt; &lt;/span&gt;-format&lt;span class="w"&gt; &lt;/span&gt;png&lt;span class="w"&gt; &lt;/span&gt;-background&lt;span class="w"&gt; &lt;/span&gt;white&lt;span class="w"&gt; &lt;/span&gt;-alpha&lt;span class="w"&gt; &lt;/span&gt;background&lt;span class="w"&gt; &lt;/span&gt;-alpha&lt;span class="w"&gt; &lt;/span&gt;off&lt;span class="w"&gt; &lt;/span&gt;nontransparent.png

&lt;span class="c1"&gt;#invet colors&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$infile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;*_Cu*&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;inverted
&lt;span class="w"&gt;        &lt;/span&gt;convert&lt;span class="w"&gt; &lt;/span&gt;nontransparent.png&lt;span class="w"&gt; &lt;/span&gt;-channel&lt;span class="w"&gt; &lt;/span&gt;RGB&lt;span class="w"&gt; &lt;/span&gt;-negate&lt;span class="w"&gt; &lt;/span&gt;inverted.png
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;*&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;#do nothing&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;nontransparent.png&lt;span class="w"&gt; &lt;/span&gt;inverted.png
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;

convert&lt;span class="w"&gt; &lt;/span&gt;inverted.png&lt;span class="w"&gt; &lt;/span&gt;-gravity&lt;span class="w"&gt; &lt;/span&gt;center&lt;span class="w"&gt; &lt;/span&gt;-extent&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;x_pixels&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;x&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;y_pixels&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-background&lt;span class="w"&gt; &lt;/span&gt;white&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$outfile&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$infile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;*-B_*&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;mirrored
&lt;span class="w"&gt;        &lt;/span&gt;convert&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$outfile&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-flop&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$outfile&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;

&lt;span class="c1"&gt;#cleanup&lt;/span&gt;
rm&lt;span class="w"&gt; &lt;/span&gt;exported.png
rm&lt;span class="w"&gt; &lt;/span&gt;nontransparent.png
rm&lt;span class="w"&gt; &lt;/span&gt;inverted.png
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This wrapper script then calls the previous script for each svg file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-ex
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;*.svg
&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;./prepare.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;51&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;71&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;%%.*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;.png
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="use-boardloom-to-create-a-lightburn-project"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-13"&gt;Use boardloom to create a Lightburn project&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Creating a lightburn project, importing 6 layers and manually drawing the contours cut layer
takes a lot of time, and I don't like manual GUI work. Fortunately the LightBurn file format
is XML based and very easy to understand.  I created a python program I call &lt;a class="reference external" href="https://github.com/thouters/boardloom"&gt;Boardloom&lt;/a&gt;
that creates a project file based on a simple configuration file and the KiCAD output.&lt;/p&gt;
&lt;p&gt;This allows you to keep your laser settings across projects and save a lot of time.
Here is an example of a configuration file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;input_files_prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pir-24v-&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;output_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pir-24v.lbrn2&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;50&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;70&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;workarea_x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;120&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;workarea_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;110&lt;/span&gt;
&lt;span class="nt"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;board_contour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;handles_dia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;line_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;max_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;100&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;speed_mm_sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;F_Cu&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Copper&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Front&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;mirror_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;max_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;speed_mm_sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;dither_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;threshold&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;line_interval_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.0847&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;B_Cu&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Copper&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Back&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;mirror_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;max_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;80&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;speed_mm_sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;10&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;dither_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;threshold&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;line_interval_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.0847&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;F_Mask&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Soldermask&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Front&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;center_on_board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;line_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;max_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;20&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;speed_mm_sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;B_Mask&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Soldermask&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Back&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;center_on_board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;line_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;mirror_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;max_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;20&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;speed_mm_sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;F_Silkscreen&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Silkscreen&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Front&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;mirror_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;max_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;33&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;speed_mm_sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;dither_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;threshold&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;line_interval_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.0847&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;B_Silkscreen&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Silkscreen&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Back&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;mirror_y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;max_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;33&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;speed_mm_sec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;dither_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;threshold&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;line_interval_mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0.0847&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After running boardloom, you can open the file in LightBurn and start lasering right away,
the LightBurn UI will look like this:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/boardloom-project.png" /&gt;
&lt;p class="caption"&gt;LightBurn screenshot with the PCB project layers.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="manually-create-a-lightburn-project"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-14"&gt;Manually create a LightBurn project&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Create a contour layer and draw a rectangle the size of the board&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;Set the contour coordinates&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;Important here is that you do this so that the laser head will move out of place when it is done so you can access the board area easily. I use x=80, y=110.&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;Create a handles layer with cutouts to grab under the board to remove it (note the half circles on my cardboard photos).&lt;/li&gt;
&lt;li&gt;Switch to a new layer by clicking the colored layer numbers on the bottom side of the screen&lt;/li&gt;
&lt;li&gt;Drag a png to the application canvas&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;In the image/layer settings&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;disable bidirectional scanning&lt;/li&gt;
&lt;li&gt;set DPI to 300&lt;/li&gt;
&lt;li&gt;set image mode to threshold&lt;/li&gt;
&lt;li&gt;set speed to 10&lt;/li&gt;
&lt;li&gt;set power to 100%&lt;/li&gt;
&lt;li&gt;you can click set defaults to avoid setting these every time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;Repeat the steps for the png images for F_Cu and B_Cu (and F_Mask, B_Mask if you will use soldermask)&lt;/li&gt;
&lt;li&gt;Align all layer images by setting the XPos and YPos to be consistent with the contour rectangle&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/lightburn.png" /&gt;
&lt;p class="caption"&gt;lightburn layers&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="prepare-the-machine-for-engraving"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-15"&gt;Prepare the machine for engraving&lt;/a&gt;&lt;/h3&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="first admonition-title"&gt;Warning&lt;/p&gt;
&lt;ul class="last simple"&gt;
&lt;li&gt;Always use adequate eye protection LASER glasses 👓 when working with LASER machines, or you WILL ruin your eyes, LASERs are not toys!&lt;/li&gt;
&lt;li&gt;Use mechanical ventilation/extraction! Vaporized paint is not healthy!&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Cut a piece of cardboard&lt;/li&gt;
&lt;li&gt;Fix the cardboard on the worksurface, &lt;a class="reference external" href=":https://www.printables.com/model/861049-honeycomb-pin-for-laser-engravers"&gt;I used these 3d printed pins&lt;/a&gt; enlarged in PrusaSlicer at 138% size to hold the cardboard onto my honeycomb bed.&lt;/li&gt;
&lt;li&gt;Laser-cut out the contour of the PCB stock and handle holes out of the cardboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-copper-layers"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-16"&gt;The copper layers&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="engrave-the-board-s-copper-layers"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-17"&gt;Engrave the board's copper layers&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Make sure that in the cuts/layers sidebar, the only output switch is the layer you want to cut/engrave&lt;/li&gt;
&lt;li&gt;engrave side 1&lt;/li&gt;
&lt;li&gt;flip the board&lt;/li&gt;
&lt;li&gt;engrave side 2 (mirrored vertically by the script)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that with these settings, the example board of a few square centimeters takes ~20minutes. There is room for experimentation to optimize these settings.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/lasering.png" /&gt;
&lt;p class="caption"&gt;lasering in progress&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="etch-the-board"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-18"&gt;Etch the board&lt;/a&gt;&lt;/h3&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="first admonition-title"&gt;Warning&lt;/p&gt;
&lt;p class="last"&gt;Always use adequate eye protection 👓 when working with ACID.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Drill a hole in the board to hang it in the etching tank.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/etched-front.jpeg" /&gt;
&lt;p class="caption"&gt;etched front side&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/etched-back.jpeg" /&gt;
&lt;p class="caption"&gt;etched back side&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solder-mask"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-19"&gt;The solder mask&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Solder mask protects the copper traces from corrosion and heat.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Remove all etch mask paint (eg. Wipe the board with isopropyl alcohol)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are two options here, a simple one that puts paper tape covering over
the solderpads and coats the entire board with spraypaint, after drying the tape is removed
to expose the pads. In the second option we will coat the entire pcb with UV
curable PCB soldermask paint, and clean the paint off the pads with the LASER.&lt;/p&gt;
&lt;div class="section" id="a-simple-solution-for-through-hole-boards"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-20"&gt;A simple solution for through hole boards&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When not doing SMD components, it is possible to use a transparent pcb coating
spraypaint like &lt;a class="reference external" href="https://www.kontaktchemie.com/koc/KOCproductdetail.csp?division=&amp;amp;product=PLASTIK%2070&amp;amp;ilang=en&amp;amp;plang=en"&gt;KONTACT CHEMIE PLASTIK 70&lt;/a&gt;.  This paint can be soldered through,
but we can also cover the pads with painters tape before spray painting.&lt;/p&gt;
&lt;p&gt;You can also use black heat resistant spraypaint, but you won't see the traces.&lt;/p&gt;
&lt;p&gt;To begin with, cover the necessary board area with painters tape.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/soldermask-tape-applied.jpg" /&gt;
&lt;p class="caption"&gt;board covered with tape (improvised picture with mismatching design files)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We will cut the contours of the copper areas of the solder pads; they need to stay clean of paint.&lt;/p&gt;
&lt;p&gt;If not using boardloom;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Add the soldermask image to the project if not done already, and make sure it is aligned.&lt;/li&gt;
&lt;li&gt;right-click the image and select Trace image.&lt;/li&gt;
&lt;li&gt;Use the 'offset shape' (Alt-O) function to add a 0.5mm clearance around the pad, check the 'delete original shape' box.&lt;/li&gt;
&lt;li&gt;Set settings to 5mm/sec 40% power (white tape) or 20% (yellow tape)&lt;/li&gt;
&lt;li&gt;Laser the image.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/trace.png" /&gt;
&lt;p class="caption"&gt;trace dialog&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/offset.png" /&gt;
&lt;p class="caption"&gt;trace dialog&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/soldermask-paintmask-applied.jpg" /&gt;
&lt;p class="caption"&gt;paint mask applied for the soldermask layer&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/soldermask-painted.jpg" /&gt;
&lt;p class="caption"&gt;heat resistant spraypaint applied&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;After the spraypaint has dried I use a spudger from a silicone seal kit set to wipe the extending pieces of
tape off the board.  Any firm but soft plastic (so it does not scratch the paint) can be used for this.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/soldermask-final.jpg" /&gt;
&lt;p class="caption"&gt;soldermask masking tape removed&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You can also use green UV soldermask, in that case I burn the contours of the tape again at 20%,5mm/s before I peel off the tape using tweezers:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/first-uv-attempt.jpg" /&gt;
&lt;p class="caption"&gt;First attempt with UV soldermask and acrylic silkscreen paint&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="using-uv-curable-paint-laser-cleaning-pads"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-21"&gt;Using UV curable paint, laser cleaning pads&lt;/a&gt;&lt;/h3&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/applied.jpg" /&gt;
&lt;p class="caption"&gt;Applying solder mask paint using a 3D printed embroidery ring.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I had some leftover pieces of mosquitto frame extrusions and I quickly modeled a simple corner piece in #openscad
so i could build a picture frame sized frame.
It seems to do seem to keep an ok-ish tension on the screen printing mesh, better than the
embroidery ring does, but I had to add some paper tape to the mesh to make it just as thick as the flyscreen it is
designed to work with.&lt;/p&gt;
&lt;p&gt;I found a spring loaded pcb holder on the internet, which I figured would go neatly under it, but I ended up
designing my own which uses threaded rods cut to lenght, and has magnets to hold the pcb holder down onto to the worksurface.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/coatingframe.jpg" /&gt;
&lt;p class="caption"&gt;Using the clamp and flyscreen frame&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Laser cleaning the pads happens at 5mm/s &amp;#64; 20%&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-silk-screen"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-22"&gt;The silk screen&lt;/a&gt;&lt;/h2&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/silkscreen-result.jpg" /&gt;
&lt;p class="caption"&gt;Silkscreen test result&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="design-rules-1"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-23"&gt;Design rules&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For text I used 1.5mm font size, 1.0mm was barely readable.
If you want to make sure lines are readable, Use a 0.2mm width.
If you use soldermask paint that hides tracks, it's best that you also export the 'Fab' layers
which contains outlines of components. However the KICAD footprints overlay the lines on top of pads
sometimes. You can try modifying the footprints and removing those if you tweak the footprint to make the
lines thicker.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="lightburn-design-preparation"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-24"&gt;Lightburn design preparation&lt;/a&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Add the silkscreen image to the project&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;Double check the design rules enforced that no silk screen got on the solder pads.&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;You could also use the 'boolean subtract' function to fix this in lightburn&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;right click the image and select Trace image.&lt;/li&gt;
&lt;li&gt;On the Cuts/Layers sidebar, select 'Fill' mode for this layer&lt;/li&gt;
&lt;li&gt;&lt;dl class="first docutils"&gt;
&lt;dt&gt;Double-click the layer in the Cuts/layers list to bring up the 'Cut settings editor'&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="first last"&gt;
&lt;li&gt;Set speed to 5 mm/sec&lt;/li&gt;
&lt;li&gt;Set Max Power to 30% (black spraypaint) or 20% (green uv soldermask)&lt;/li&gt;
&lt;li&gt;Disable bi-directional fill&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="painting-and-drying"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-25"&gt;Painting and drying&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Apply painters paper tape to the board and engrave the image.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/silkscreen-painting.jpg" /&gt;
&lt;p class="caption"&gt;Applying paint&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Next we add the acrylic paint, and spread it using a spatula.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/silkscreen-painted.jpg" /&gt;
&lt;p class="caption"&gt;Applying paint&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I put it on my Prusa 3D printer heatbed at 85 degrees for 15minutes, but I'm not sure
what the perfect drying process is.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;When using white UV paint, you can apply a coat of uv paint using silk screen printing mesh,
dry it on your 3d printer and user your laser to cure the paint (detailed instructions to be determined and added here).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="drilling-holes"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-26"&gt;Drilling holes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I already had a Dremel 4200, and 3D printed &lt;a class="reference external" href="https://www.printables.com/model/308219-dremel-drill-press"&gt;this drill press fixture from printables.com&lt;/a&gt;:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/drillpress.jpg" /&gt;
&lt;p class="caption"&gt;3D printed drill press&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I designed &lt;a class="reference external" href="https://www.printables.com/model/1326917-pipe-mounting-flange"&gt;a flange to mount the vertical tube on a wooden plank&lt;/a&gt; so I could skip the 24+hour print of the base,
the flange only took 3hours to print.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solderpaste-stencil"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-27"&gt;The solderpaste stencil&lt;/a&gt;&lt;/h2&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;I'm experimenting with doing a solderpaste stencil using the same paper tape method as used for the other non-copper layers, stay tuned!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-test-project"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-28"&gt;The test project&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wanted to try this process out with a small PCB, and already had a simple project in mind.
This small circuit drops 24V DC to 5V so a hobby motion sensor (which capacitors are not rated for 24V)
can be powered from the house's domotics power supply. The motion sensor outputs a logic high level
of 3v3 which needs to be translated to 24V, which is done with a pair of transistors.
The motion sensor 2.54mm pin header is included, along with 5.08mm pitch screw terminals.
A few via's are put onto the board, although they may not be functionally necessary.&lt;/p&gt;
&lt;p&gt;For the via's brass rivets will be used, which i will solder, so i wanted some heat stops
around the pads. To save time if this can be set in KICAD, I just added a few 2.54mm pin headers&lt;/p&gt;
&lt;p&gt;The png output is shown here:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/tryout-schematic.png" /&gt;
&lt;p class="caption"&gt;Simple project to try out the pcb fabbing process&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/soldered-pcb.jpg" /&gt;
&lt;p class="caption"&gt;The populated end result&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/pir-24v-B_Cu.png" /&gt;
&lt;p class="caption"&gt;B_Cu&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/pir-24v-F_Cu.png" /&gt;
&lt;p class="caption"&gt;F_Cu&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/pir-24v-B_Mask.png" /&gt;
&lt;p class="caption"&gt;B_MaskCu&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/pir-24v-F_Mask.png" /&gt;
&lt;p class="caption"&gt;F_Mask&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="time-to-get-creative"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-29"&gt;Time to get creative!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here is a picture of the second board design I etched, there is a hand drawn part as well,
done with permanent marker. DIY etching opens up this possibility!&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/copper.jpg" /&gt;
&lt;p class="caption"&gt;the morse crab&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="related-articles"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-30"&gt;Related articles&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="lasers-and-saws"&gt;
&lt;span id="one-of-these-youtube-videos"&gt;&lt;/span&gt;&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-31"&gt;Lasers and Saws&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;'Lasers and Saws' has this nice video &lt;a class="reference external" href="https://youtu.be/jGk1g3JwxSI?si=SIFFQlM6sU24zjxe"&gt;https://youtu.be/jGk1g3JwxSI?si=SIFFQlM6sU24zjxe&lt;/a&gt; - I think I got the
cardboard idea from them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="forourgood"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-32"&gt;ForOurGood&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;ForOurGood on youtube has this awesome video series on DIY'ing using a CNC3018 with laser addon: &lt;a class="reference external" href="https://youtu.be/zn12kizaSsg"&gt;https://youtu.be/zn12kizaSsg&lt;/a&gt;
I'm a bit sceptic about laser-burning the pads. His flow with the CNC machine calibration seems a bit overly time consuming,
but if you have a lot of through hole components or vias, using a CNC can save you time since it can drill the holes&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="presentation-may-2025"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-33"&gt;Presentation May 2025&lt;/a&gt;&lt;/h2&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="test" src="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/talk.jpg" /&gt;
&lt;p class="caption"&gt;Thumnail of the talk video&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I gave a presentation about this on Newline.gent, which was recorded and can be viewed at: &lt;a class="reference external" href="https://www.youtube.com/watch?v=GN7vuh23kpM"&gt;https://www.youtube.com/watch?v=GN7vuh23kpM&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can download the presentation here:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
        &lt;td&gt;
&lt;a class="nohl" href="https://www.thouters.be/img/LaserEngraveAndEtchDiyCircuitBoards/newline2025-rolling-your-own-pcbs-nohandouts.pdf"&gt;
&lt;img border="0" src="https://www.thouters.be/img/Downloads/pdf.png" alt="icon"/&gt; newline2025-rolling-your-own-pcbs-nohandouts.pdf&lt;/a&gt;
        &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
</content><category term="misc"/><category term="laserengraver"/><category term="etching"/><category term="diycircuitboard"/><category term="kicad"/></entry><entry><title>DIY Heatpump drip tray</title><link href="https://www.thouters.be/DiyHeatpumpCondensorDripTray.html" rel="alternate"/><published>2024-09-05T21:10:00+02:00</published><updated>2024-09-05T21:10:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2024-09-05:/DiyHeatpumpCondensorDripTray.html</id><summary type="html">
&lt;p&gt;Our heatpump outdoor unit was installed over our driveway, and we walk under it on the way to our back door.
The installer did not place a capture tray, but I want to avoid water dripping and pooling on the
driveway during the heating season so we don't get a slippery ice spot.  So I built a capture tray.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;!-- :image: https://www.thouters.be/img/OpenPlankPoc/cover.jpg --&gt;
&lt;!-- :post_id: 109672257020969515 --&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/preview.jpg" /&gt;
&lt;p class="caption"&gt;My drip tray, installed.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;An outdoor heatpump unit pulls water from the air since moisture condenses on its cold radiator. In very cold
temperatures it forms ice, which is suddenly thawed all at once in a 'defrost cycle'.&lt;/p&gt;

&lt;p&gt;Our heatpump outdoor unit was installed over our driveway, and we walk under it on the way to our back door.
The installer did not place a capture tray, but I want to avoid water dripping and pooling on the
driveway during the heating season so we don't get a slippery ice spot.  So I built a capture tray.&lt;/p&gt;

&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#possible-solutions" id="toc-entry-1"&gt;Possible solutions&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#buy-a-drip-tray-accessory-from-the-heatpump-manufacturer" id="toc-entry-2"&gt;Buy a drip tray accessory from the heatpump manufacturer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#install-a-plastic-rain-gutter" id="toc-entry-3"&gt;Install a plastic rain gutter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#use-a-plastic-box-lid" id="toc-entry-4"&gt;Use a plastic box lid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#my-solution" id="toc-entry-5"&gt;My solution&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#requirements" id="toc-entry-6"&gt;Requirements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#design" id="toc-entry-7"&gt;Design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#sheet-plastic" id="toc-entry-8"&gt;Sheet plastic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#mounting" id="toc-entry-9"&gt;Mounting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#assembly-process" id="toc-entry-10"&gt;Assembly process&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#installation" id="toc-entry-11"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#design-files" id="toc-entry-12"&gt;Design Files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="possible-solutions"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Possible solutions&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="buy-a-drip-tray-accessory-from-the-heatpump-manufacturer"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Buy a drip tray accessory from the heatpump manufacturer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Online prices are around 400 euros. Kind of expensive.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="install-a-plastic-rain-gutter"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Install a plastic rain gutter&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The L shape of the exposed radiator together with a hole in the middle would make
building an F shape of rain gutter an impractical build, which would be inconvenient
to mount under the outdoor unit.  Plus, it would look a bit strange, and grey.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-a-plastic-box-lid"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Use a plastic box lid&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I don't expect to find any box lid really rigid and flat at the same time, since they are mostly
injection molded into a rigid shape.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="my-solution"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;My solution&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="requirements"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Requirements&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To avoid resonance because of the compressor vibrations, and avoid storm winds making it move will have to be a solid
construction.&lt;/p&gt;
&lt;p&gt;It should not freeze shut, so water should not pool together, if I mount the tray under angles I won't have that issue.&lt;/p&gt;
&lt;p&gt;I chose to build a tray myself and came up with a simple mounting mechanism.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="design"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Design&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I measured the area I want to cover under the unit and made a simple OpenSCAD
design of cylindrical spacers that are cut through with a board positioned under
x and y angles.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/renderscreenshot.png" /&gt;
&lt;p class="caption"&gt;OpenSCAD render of the construction&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The cylindrical spacers will be mounted to the mounting brackets the unit sits on
using threaded rods cut to length.&lt;/p&gt;
&lt;p&gt;I printed them in PETG on my Prusa 3D printer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sheet-plastic"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Sheet plastic&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I bought a sheet of 5mm thick white PVC foam board of 1m by 0.5m.  It cost me 18EUR.
This foam board is easily cuttable with a stanley knife and can be glued with pvc glue or superglue.&lt;/p&gt;
&lt;p&gt;I was able to cut out all necessary parts out of this single board.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/lekbak-sheet.png" /&gt;
&lt;p class="caption"&gt;The cutting pattern of the PVC foam board&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="mounting"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Mounting&lt;/a&gt;&lt;/h3&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/rods.jpg" /&gt;
&lt;p class="caption"&gt;The mounting rods&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I bought a 1m piece of 8mm threaded stainless steel rod together
with some nuts and washers.  I cut it into parts 17cm long.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="assembly-process"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Assembly process&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To glue the parts together, both sides were&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;degreased&lt;/li&gt;
&lt;li&gt;rubbed with the pvc glue bottle's glue brush.&lt;/li&gt;
&lt;li&gt;held together using glue clamps and pieces of metal from my shed for half an hour&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/glue1.jpg" /&gt;
&lt;p class="caption"&gt;Glueing in progress&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/assembly.jpg" /&gt;
&lt;p class="caption"&gt;The assembly of parts&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I cut a rectangular piece of foamboard to reinforce the drain wall.
The drain hole was offset by 2mm to the bottom corner so the water
would flow out easily.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/fitpipe.jpg" /&gt;
&lt;p class="caption"&gt;Fitting the runoff pipe&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/fitpipe2.jpg" /&gt;
&lt;p class="caption"&gt;Fitting the runoff pipe&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The last parts were glued together.
Next the location of the mounting holes was marked, and
round spacers cut from scrap were glued onto the mounting holes.
This would function as a barrier preventing the water to exit.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/glue2.jpg" /&gt;
&lt;p class="caption"&gt;More glueing&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I should have offset the hole in two directions, you can see there
is a small corner where water can pool. No worries, I had a tube
of bathroom silecone already open, so i applied silecone over the inside
welds.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/runoff.jpg" /&gt;
&lt;p class="caption"&gt;The runoff from the top&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You can already see the angle and length differences of the spacer cylinders:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/side.jpg" /&gt;
&lt;p class="caption"&gt;Drip tray seen on its side&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/assembled.jpg" /&gt;
&lt;p class="caption"&gt;The assembly resting on a table&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="installation"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;Installation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Finally the tray was mounted and a hole was drilled in the rain pipe,
and a piece of airco drain pipe is used to connect the two.
When time comes for maintenance, the tube can be easily disconnected
to avoid cleaning chemicals flowing to the rain water tank.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/final1.jpg" /&gt;
&lt;p class="caption"&gt;A final picture of the tray installed in place&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/DiyHeatpumpCondensorDripTray/final2.jpg" /&gt;
&lt;p class="caption"&gt;The underside of the unit&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="design-files"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;Design Files&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The cylindrical supports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// render in higher detail&lt;/span&gt;
&lt;span class="nx"&gt;$fn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;lekbak_base_x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;940&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;lekbak_base_y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;lekbak_base_z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// actually 5mn but I glued a 5mm washer on mounting spots&lt;/span&gt;
&lt;span class="nx"&gt;lekbak_lowsize_z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;mounthole_left_x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;150&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;mounthole_x_interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;mounthole_right_x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mounthole_x_interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;190&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;mounthole_wall_y_earth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;mounthole_front_y_earth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;250&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;z_drop_on_x_size&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;z_drop_on_y_size&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;angle_x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;asin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z_drop_on_x_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lekbak_base_x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;angle_y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;asin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z_drop_on_y_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lekbak_base_y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// show the values&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angle_x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angle_y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// the base board under angles&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;lekbak_base_z&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;lekbak_base_x&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;lekbak_base_y&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;angle_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;angle_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lekbak_base_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lekbak_base_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lekbak_base_z&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nx"&gt;center&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// for visualisation purposes&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;green&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// supports to be cut with the base board&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;supports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;union&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mounthole_right_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mounthole_wall_y_earth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mounthole_front_y_earth&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mounthole_right_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mounthole_front_y_earth&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mounthole_left_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mounthole_wall_y_earth&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mounthole_left_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mounthole_front_y_earth&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z_drop_on_x_size&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;8.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// cut the base out of the supports&lt;/span&gt;
&lt;span class="nx"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;supports&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// disable for final render&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Files:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="img/DiyHeatpumpCondensorDripTray/lekbak-supports.stl"&gt;Stl file: lekbak-supports.stl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="img/DiyHeatpumpCondensorDripTray/poging2.3mf"&gt;Prusa project: poging2.3mf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="openscad"/><category term="diy"/></entry><entry><title>Custom motion sensor wall mount</title><link href="https://www.thouters.be/MotionSensorBlindplate.html" rel="alternate"/><published>2024-06-13T20:10:00+02:00</published><updated>2024-06-13T20:10:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2024-06-13:/MotionSensorBlindplate.html</id><summary type="html">
&lt;p&gt;Motion sensors; the simplest kind of automation, and the most useful. Especially during night,
when you're finding your way to the smallest room in the house. I could not find
a sensor I liked however, and also did not want to spend a lot of money on something ugly.
So I designed and 3D printed my own.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;!-- :image: https://www.thouters.be/img/OpenPlankPoc/cover.jpg --&gt;
&lt;!-- :post_id: 109672257020969515 --&gt;
&lt;style&gt; .rollershutterbuttons {  align: center; background: rgba(255, 255, 255, 1); max-width: 50%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} &lt;/style&gt;
&lt;style&gt; .schematic {  align: center; background: rgba(255, 255, 255, 1); max-width: 75%; display: block; margin-left: auto; margin-right: auto; margin-top: 0.3em; padding: 10px; border-radius: 0.3em;} &lt;/style&gt;
&lt;style&gt; .schematicfull {  align: center; background: rgba(255, 255, 255, 1); max-width: 100%; display: block; margin-left: auto; margin-right: auto; margin-top: 0.3em; padding: 10px; border-radius: 0.3em;} &lt;/style&gt;&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/MotionSensorBlindplate/cover.jpg" /&gt;
&lt;p class="caption"&gt;A motion sensor with pushbutton behind it.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Motion sensors; the simplest kind of automation, and the most useful. Especially during night,
when you're finding your way to the smallest room in the house. I could not find
a sensor I liked however, and also did not want to spend a lot of money on something ugly.
So I designed and 3D printed my own.&lt;/p&gt;

&lt;p&gt;A friend of mine built a motion sensor wallplate like this, with the exception of using glue to stick it together.
I designed and 3D printed a slightly more complicated contraption, which can be pushed to
bring the room light up from night setting to full brightness.&lt;/p&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#components" id="toc-entry-1"&gt;Components&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-motion-sensor" id="toc-entry-2"&gt;The motion sensor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#the-wall-plate" id="toc-entry-3"&gt;The wall plate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#a-fixed-sensor-mount" id="toc-entry-4"&gt;A fixed sensor mount&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#sensor-mount-with-integrated-button" id="toc-entry-5"&gt;Sensor mount with integrated button&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#design-files" id="toc-entry-6"&gt;Design files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="components"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Components&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="the-motion-sensor"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;The motion sensor&lt;/a&gt;&lt;/h3&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/MotionSensorBlindplate/sensor.jpg" /&gt;
&lt;p class="caption"&gt;HC-SR501 'Arduino/hobby' Motion sensor&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This simple sensor can be bought for about 4.2€ and accepts
voltages up to 24V. Double check the input capacitor rating though,
mine was only rated for ~15V, so I replaced mine with a brand-name capacitor rated a lot higher than 24V.&lt;/p&gt;
&lt;p&gt;Its output is a 3v3 logic signal. This can be used to drive a (power) transistor
which in turn can drive a 24V relay or PLC digital input.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-wall-plate"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;The wall plate&lt;/a&gt;&lt;/h3&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/MotionSensorBlindplate/blindplate.jpg" /&gt;
&lt;p class="caption"&gt;Wall plate (blindplaat)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Our switches and sockets are made by Niko. They also manufacture
a 'blank' plate that can be used as a placeholder to fit an extra
switch or power socket at a later time.&lt;/p&gt;
&lt;p&gt;The blankplate has mounting holes on the back and costs also about 4.2€.
That makes it ideal for creating DIY controls.&lt;/p&gt;
&lt;p&gt;I designed and printed a drill guide to easily mark the center and used a step drill
to create the big hole to fit the motion sensor through.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="a-fixed-sensor-mount"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;A fixed sensor mount&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I created this PIR mounting plate initially, and it worked perfectly.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/MotionSensorBlindplate/pironly.jpg" /&gt;
&lt;p class="caption"&gt;Motion sensor mount&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="sensor-mount-with-integrated-button"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Sensor mount with integrated button&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When adding a night mode to the lighting system, I needed an override
button to turn on the full brightness in case it is ever needed.&lt;/p&gt;
&lt;p&gt;I designed this new mount plate that adds small switches.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/MotionSensorBlindplate/buttonguide.jpg" /&gt;
&lt;p class="caption"&gt;Button guide and PIR mount parts&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I added a bit of lubricant where both parts make contact and slide.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/MotionSensorBlindplate/buttonguide-back.jpg" /&gt;
&lt;p class="caption"&gt;Seen from the side, with screw touching blindplate&lt;/p&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/MotionSensorBlindplate/buttonguide-inserted.jpg" /&gt;
&lt;p class="caption"&gt;Switches visible from the side&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="design-files"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Design files&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can find the STL and OpenSCAD design files at &lt;a class="reference external" href="https://www.printables.com/model/911623-motion-sensor-blindplaat-mount"&gt;https://www.printables.com/model/911623-motion-sensor-blindplaat-mount&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="3dprinting"/><category term="electronics"/></entry><entry><title>Rollershutters control panel</title><link href="https://www.thouters.be/RollerShutters.html" rel="alternate"/><published>2024-06-12T20:10:00+02:00</published><updated>2024-06-12T20:10:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2024-06-12:/RollerShutters.html</id><summary type="html">
&lt;p&gt;When we renovated our living room, we upgraded the window roller shutters with electric motors.
While we usually only open and close all of them together, we still want to be able to set them
to individual positions. I decided to make a central control panel with 6 physical buttons
and implement some logic in the Siemens Logo controller to control the motors.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;!-- :post_id: 109672257020969515 --&gt;
&lt;style&gt; .rollershutterbuttons {  align: center; background: rgba(255, 255, 255, 1); max-width: 50%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} &lt;/style&gt;
&lt;style&gt; .schematic {  align: center; background: rgba(255, 255, 255, 1); max-width: 75%; display: block; margin-left: auto; margin-right: auto; margin-top: 0.3em; padding: 10px; border-radius: 0.3em;} &lt;/style&gt;
&lt;style&gt; .schematicfull {  align: center; background: rgba(255, 255, 255, 1); max-width: 100%; display: block; margin-left: auto; margin-right: auto; margin-top: 0.3em; padding: 10px; border-radius: 0.3em;} &lt;/style&gt;&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" class="rollershutterbuttons" src="https://www.thouters.be/img/RollerShutters/knoppen.png" /&gt;
&lt;p class="caption"&gt;Shutter control panel&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;When we renovated our living room, we upgraded the window roller shutters with electric motors.
While we usually only open and close all of them together, we still want to be able to set them
to individual positions. I decided to make a central control panel with 6 physical buttons
and implement some logic in the Siemens Logo controller to control the motors.&lt;/p&gt;

&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#goal" id="toc-entry-1"&gt;Goal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#parts" id="toc-entry-2"&gt;Parts&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#buttons" id="toc-entry-3"&gt;Buttons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#motor" id="toc-entry-4"&gt;Motor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#controller" id="toc-entry-5"&gt;Controller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#relays" id="toc-entry-6"&gt;Relays&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#circuit" id="toc-entry-7"&gt;Circuit&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#high-voltage-drivers" id="toc-entry-8"&gt;High voltage drivers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#input" id="toc-entry-9"&gt;Input&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#siemens-logo-logic" id="toc-entry-10"&gt;Siemens Logo logic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="goal"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Goal&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My goal was to create a simple control panel that makes the following things easy:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Open and close all rollers with a single button push (⇩/⇧)&lt;/li&gt;
&lt;li&gt;Open and close individual shutters with two (or more) button pushes (roller button(s) + ⇩/⇧)&lt;/li&gt;
&lt;li&gt;Stop the movement by hitting the ⇧ or⇩ button again.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The power is cut to the motors after a timeout.&lt;/p&gt;
&lt;p&gt;I drew a state diagram illustrating the operation:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;object class="schematic" data="https://www.thouters.be/img/RollerShutters/rollen.svg" type="image/svg+xml"&gt;placeholder&lt;/object&gt;
&lt;p class="caption"&gt;State diagram.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="parts"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Parts&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="buttons"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Buttons&lt;/a&gt;&lt;/h3&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/RollerShutters/nikoscaled.jpeg" /&gt;
&lt;p class="caption"&gt;6 way Pushbutton&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The 6 way Niko button pad from the product family I bought the rest of our switching gear from was very expensive
(80€). The plastic button cover available in many colors is only a few euro's.  That opens experimentation options, and
I plan to laser engrave the icons on top at one point.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="motor"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Motor&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The roller motors are controlled with two 230V signals, one to run up, one to run down.
Only one of these should receive line voltage, or the motor stalls.
The motor has internal end stops which are calibrated on installation time, so it
can be left powered.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/RollerShutters/somfy.png" /&gt;
&lt;p class="caption"&gt;Motor diagram&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="controller"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Controller&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The central Siemens mini-PLC controls the motors:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/RollerShutters/siemenslogo.png" /&gt;
&lt;p class="caption"&gt;Siemens logo PLC&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I always buy the 24V version with transistor outputs, and use interface relays.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="relays"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Relays&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Schneider Harmony (24V coil voltage) interface relays control the mains voltage.&lt;/p&gt;
&lt;p&gt;I originally planned to use one of these hooked up to the neutral of each motor, and one for each direction:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/RollerShutters/rxg.png" /&gt;
&lt;p class="caption"&gt;single pole make and break relay&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;By having one single pole relay per motor, I planned to reduce the number of outputs
from 8 to 6, and occupy minimal cabinet space.  However, leaving some motors neutral floating while having the control
lines connected to other motors draws current through the floating motors, which makes that configuration unusable.&lt;/p&gt;
&lt;p&gt;For that reason I had to use the dual pole variant as roller selector. By The way these relays can be operated
manually and locked into position in case of 24V PSU failure.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/RollerShutters/rxm.png" /&gt;
&lt;p class="caption"&gt;dual pole make and break relay&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="circuit"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Circuit&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="high-voltage-drivers"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;High voltage drivers&lt;/a&gt;&lt;/h3&gt;
&lt;div class="figure align-center"&gt;
&lt;object class="schematicfull" data="https://www.thouters.be/img/RollerShutters/relayschematic.svg" type="image/svg+xml"&gt;placeholder&lt;/object&gt;
&lt;p class="caption"&gt;Relay schematic, example with only two rollers&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This relay circuit makes it impossible to connect both direction lines to the line voltage.
The up direction is prefered in case both directon relays are energized.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="input"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Input&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The human interface is as simple as can be, the 6 momentary button switches are feeding 24V to the LOGO! inputs when pushed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="siemens-logo-logic"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Siemens Logo logic&lt;/a&gt;&lt;/h3&gt;
&lt;div class="figure align-center"&gt;
&lt;object class="schematicfull" data="https://www.thouters.be/img/RollerShutters/logo3.svg" type="image/svg+xml"&gt;placeholder&lt;/object&gt;
&lt;p class="caption"&gt;Logo configuration diagram&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The LOGO! configuration is pretty simple, it consists of these parts:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A SET/RESET flipflop with pulse input per relay&lt;/li&gt;
&lt;li&gt;The B054 timer triggers the flipflop RESET inputs 30seconds after a button push, clearing all state.&lt;/li&gt;
&lt;li&gt;B058/B059/B060 set all roller selectors if the ⇧ or ⇩ button is pressed and no roller is selected.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="siemenslogo"/><category term="electric"/><category term="homeassistant"/></entry><entry><title>Virtual Solar/Grid power meters</title><link href="https://www.thouters.be/VirtualSolarGridPowerMeters.html" rel="alternate"/><published>2024-01-29T21:10:00+01:00</published><updated>2024-01-29T21:10:00+01:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2024-01-29:/VirtualSolarGridPowerMeters.html</id><summary type="html">
&lt;p&gt;I want to keep counters for how much solar panel energy and grid energy my car uses.
I have power meters for the Solar, Grid and the car.  The power the rest of the house
uses is however unknown, and measured by the grid meter.
Determining the car's part of grid and solar power requires some calculations.&lt;/p&gt;
&lt;p&gt;I created home assistant template sensors to come up with these power numbers, and
added integrators to obtain energy meters. The energy flow plus card can show these
in real time on the Home assistant dashboard.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;style&gt; .thlaaligncenter {  align: center; background: rgba(255, 255, 255, 1); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} &lt;/style&gt;&lt;div class="figure align-center"&gt;
&lt;object class="thlaaligncenter" data="https://www.thouters.be/img/VirtualSolarGridPowerMeters/blockpower.svg" type="image/svg+xml"&gt;block diagram&lt;/object&gt;
&lt;/div&gt;

&lt;p&gt;I want to keep counters for how much solar panel energy and grid energy my car uses.
I have power meters for the Solar, Grid and the car.  The power the rest of the house
uses is however unknown, and measured by the grid meter.
Determining the car's part of grid and solar power requires some calculations.&lt;/p&gt;
&lt;p&gt;I created home assistant template sensors to come up with these power numbers, and
added integrators to obtain energy meters. The energy flow plus card can show these
in real time on the Home assistant dashboard.&lt;/p&gt;

&lt;div class="figure align-center"&gt;
&lt;img alt="energyflow" src="https://www.thouters.be/img/VirtualSolarGridPowerMeters/energyflow.png" /&gt;
&lt;/div&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#splitting-the-power" id="toc-entry-1"&gt;Splitting the power&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#scenario-s" id="toc-entry-2"&gt;Scenario's&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#home-assistant-entities" id="toc-entry-3"&gt;Home assistant entities&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#working-around-an-integration-sensor-caveat" id="toc-entry-4"&gt;Working around an integration sensor caveat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#template-sensor" id="toc-entry-5"&gt;Template sensor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#energy-meters" id="toc-entry-6"&gt;Energy meters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#limitations-and-conclusion" id="toc-entry-7"&gt;Limitations and conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="splitting-the-power"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Splitting the power&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="scenario-s"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Scenario's&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To end up with two virtual power meters, we need a formula for the solar and grid part of the power mix.
Since we are dealing with an unknown part of House power use, I drew vector diagrams for each scenario
that is possible:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Grid + Solar&lt;/li&gt;
&lt;li&gt;Grid only&lt;/li&gt;
&lt;li&gt;Solar only&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every consumer (house, car) and producer (solar, grid) is represented by an arrow.
The following abbreviations are used:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;X: The device, the car in this case&lt;/li&gt;
&lt;li&gt;H: Devices in the Home&lt;/li&gt;
&lt;li&gt;S: Solar production&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="figure align-center"&gt;
&lt;object class="thlaaligncenter" data="https://www.thouters.be/img/VirtualSolarGridPowerMeters/powervectors.svg" type="image/svg+xml"&gt;block diagram&lt;/object&gt;
&lt;p class="caption"&gt;Scenario's of production and consumption&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;With every scenario plotted, the parts of interest can be marked:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;XS: Device used solar power&lt;/li&gt;
&lt;li&gt;XG: Device used grid power&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="home-assistant-entities"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Home assistant entities&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="working-around-an-integration-sensor-caveat"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Working around an integration sensor caveat&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://www.home-assistant.io/integrations/integration/"&gt;Riemann sum integral integration&lt;/a&gt;  in Home Assistant can be used
to turn the power measured by a home assistant entity into an energy measurement.&lt;/p&gt;
&lt;p&gt;I am observing an issue with this however. The algorithm will calculate the area of a geometric shape
of the input samples amplitudes and the time between the samples and add these finite sums.&lt;/p&gt;
&lt;p&gt;If the power value stays zero for a while the next input value will cause it
to report an amount of energy was used in the period that the power value was zero.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="block diagram" src="https://www.thouters.be/img/VirtualSolarGridPowerMeters/integrationerror-marked.png" /&gt;
&lt;p class="caption"&gt;integration sensor output with error marked.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;A workaround that I found online for this is to update the sensor every second and
add a dummy attribute containing the current time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="template-sensor"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Template sensor&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here is a listing of the template sensor code as I use it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;time_pattern&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.electricity_meter_power_production&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.electricity_meter_power_consumption&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.solaredge_ac_power&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.sdm630_total_system_power&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;not_from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unavailable&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;not_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unknown&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unavailable&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;car_grid_power&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;car_grid_power&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;unit_of_measurement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;W&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;device_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;power&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;state_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;measurement&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set production = states(&amp;#39;sensor.electricity_meter_power_production&amp;#39;)|float %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set consumption = states(&amp;#39;sensor.electricity_meter_power_consumption&amp;#39;)|float %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set g = consumption - production %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set s = states(&amp;#39;sensor.solaredge_ac_power&amp;#39;)|float /1000 %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set d = states(&amp;#39;sensor.sdm630_total_system_power&amp;#39;)|float /1000%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{%- if d &amp;lt; 0.0001 -%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;0.000&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{%- elif g &amp;gt; 0 and g &amp;lt; d -%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{# grid+solar #}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{{ &amp;#39;%0.3f&amp;#39; | format(g) }}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% elif g &amp;gt;= 0 %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{# grid only #}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{{ &amp;#39;%0.3f&amp;#39; | format(d) }}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% else %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;0.000&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% endif %}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;now().minute&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;car_solar_power&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;car_solar_power&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;unit_of_measurement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;W&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;device_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;power&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;state_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;measurement&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set production = states(&amp;#39;sensor.electricity_meter_power_production&amp;#39;)|float %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set consumption = states(&amp;#39;sensor.electricity_meter_power_consumption&amp;#39;)|float %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set g = consumption - production %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set s = states(&amp;#39;sensor.solaredge_ac_power&amp;#39;)|float /1000 %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% set d = states(&amp;#39;sensor.sdm630_total_system_power&amp;#39;)|float /1000%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{%- if d &amp;lt; 0.0001 -%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;0.000&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{%- elif g &amp;gt; 0 and g &amp;lt; d -%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{# grid+solar #}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{{ &amp;#39;%0.3f&amp;#39; | format(d-g) }}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{% elif g &amp;gt;= 0 %}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{# grid only #}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;0.000&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{%- else -%}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{# solar only #}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{{ &amp;#39;%0.3f&amp;#39; | format(d) }}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;{%- endif  %}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;now().minute&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="energy-meters"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Energy meters&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;And these are integrated into energy meters by the following old-style sensors:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;integration&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;total_car_grid_energy&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.car_grid_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;

&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;integration&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;total_car_solar_energy&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sensor.car_solar_power&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="limitations-and-conclusion"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Limitations and conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The code I shared can only be used to track one device in your house in its current form, but I think it is useful nonetheless.&lt;/p&gt;
&lt;p&gt;I hope someone finds this useful and can reuse it or use it as inspiration.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="solar"/><category term="energy"/><category term="homeassistant"/></entry><entry><title>Solar Matched Electric Heating</title><link href="https://www.thouters.be/SolarMatchedElectricHeating.html" rel="alternate"/><published>2023-12-27T20:10:00+01:00</published><updated>2023-12-27T20:10:00+01:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2023-12-27:/SolarMatchedElectricHeating.html</id><summary type="html">
&lt;p&gt;I built a prototype test setup that makes sure every Watt produced by our
solar panels is used by an electric heater.  It uses an industrial power
controller and some code to match the heater's power usage to
whatever power is not used by the house.&lt;/p&gt;
&lt;p&gt;This would make it possible to maximize our solar self consumption, which is
required to get a decent return on your solar investment.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/heatdump2.png" /&gt;
&lt;p class="caption"&gt;A heater power-controlled to consume all excess solar production!&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;I built a prototype test setup that makes sure every Watt produced by our
solar panels is used by an electric heater.  It uses an industrial power
controller and some code to match the heater's power usage to
whatever power is not used by the house.&lt;/p&gt;
&lt;p&gt;This would make it possible to maximize our solar self consumption, which is
required to get a decent return on your solar investment.&lt;/p&gt;

&lt;p&gt;For us, injecting to the electricity grid is giving away power,
we have to pay about 15x as much compared to what the utility pays us for returning energy to the grid.&lt;/p&gt;
&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#high-level-overview" id="toc-entry-1"&gt;High level overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#electrics" id="toc-entry-2"&gt;Electrics&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#power-controller" id="toc-entry-3"&gt;Power controller&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#siemens-logo-aq-and-home-assistant" id="toc-entry-4"&gt;Siemens Logo AQ and Home assistant&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#logo-sketch" id="toc-entry-5"&gt;Logo sketch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#modbus-hub-configuration" id="toc-entry-6"&gt;Modbus Hub configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#input-number" id="toc-entry-7"&gt;Input Number&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#automation" id="toc-entry-8"&gt;Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#power-optimizer" id="toc-entry-9"&gt;Power optimizer&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#trial-run" id="toc-entry-10"&gt;Trial run&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#final-thoughts-on-all-of-this" id="toc-entry-11"&gt;Final thoughts on all of this&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#integration-into-an-existing-heating-system" id="toc-entry-12"&gt;Integration into an existing heating system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#cost-considerations" id="toc-entry-13"&gt;Cost considerations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#cost-benefit" id="toc-entry-14"&gt;Cost-benefit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="high-level-overview"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;High level overview&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pending renovation works adding a heat pump, our heating is still
handled by existing electric convection heaters. The thermostat runs these
at a full power of 5kW which poorly matches the solar production on a winter
day where we might get a average 1 to 2 kW but produce enough energy in total
to keep the house warm.&lt;/p&gt;
&lt;p&gt;Note: As of 2024 we heat using a heatpump!&lt;/p&gt;
&lt;style&gt; .thlaaligncenter {  align: center; background: rgba(255, 255, 255, 1); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} &lt;/style&gt;&lt;div class="figure align-center"&gt;
&lt;object class="thlaaligncenter" data="https://www.thouters.be/img/SolarMatchedElectricHeating/block2.svg" type="image/svg+xml"&gt;block diagram&lt;/object&gt;
&lt;p class="caption"&gt;Block diagram&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We have a 'DSMR' digital utility meter that reports the grid consumption and injection every
few seconds over a serial line.
I integrated it into home assistant, which allows its
data to be observed in the energy and history dashboards, and to be used in automations.&lt;/p&gt;
&lt;p&gt;The meter calculates the net power in and out per AC (sine) cycle, so using a solid
state relay to duty cycle the heater is not an option, we need a power controller
that will create an exact average load every 50Hz cycle.&lt;/p&gt;
&lt;p&gt;I previously bought a Siemens Logo logic controller with analog output module and
a lighting dimmer I can control with it.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="SiemensLogo" src="https://www.thouters.be/img/SolarMatchedElectricHeating/siemenslogo.png" /&gt;
&lt;p class="caption"&gt;Siemens logo logic controller&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I already prototyped controlling the Logo's analog output from Home Assistant using Modbus over TCP,
and realized I could re-use this setup and replace the dimmer with an industrial power controller performing phase angle
power control.&lt;/p&gt;
&lt;p&gt;Note: the open energy monitor project has good documentation on what they call &lt;a class="reference external" href="https://docs.openenergymonitor.org/pv-diversion/index.html"&gt;PV Energy Diversion&lt;/a&gt;, they describe
an implementation using a microcontroller.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="electrics"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Electrics&lt;/a&gt;&lt;/h2&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="SiemensLogo" src="https://www.thouters.be/img/SolarMatchedElectricHeating/testsetup.jpg" /&gt;
&lt;p class="caption"&gt;Test setup (convection heater not visible)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="power-controller"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Power controller&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I bought a 'RGC1P23V30ED Solid State Contactor, 85-265Vac, 30A Carlo GAVAZZI'.
This device can perform phase angle power control by firing its SCR at a certain
angle of each AC sine wave. This is the way classic filament lights are dimmed.
I think it's reasonably priced at 138€ for industrial gear.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Carlo" src="https://www.thouters.be/img/SolarMatchedElectricHeating/dimmer.png" /&gt;
&lt;p class="caption"&gt;Carlo GAVAZZI power controller&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The power controller has an analog voltage input as control signal.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="siemens-logo-aq-and-home-assistant"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Siemens Logo AQ and Home assistant&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The simplest way to interface the logo with home assistant is by
enabling a modbus server in the Logo.&lt;/p&gt;
&lt;p&gt;After doing so, the modbus register map can be accessed given an off-by-one is respected :-).&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/modbus-address-space.png" /&gt;
&lt;p class="caption"&gt;Modbus Address space&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="logo-sketch"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Logo sketch&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Logo runs a simple sketch that will continuously
set its analog output (AQ) to the value of an analog variable (AM)&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/logosketch.png" /&gt;
&lt;p class="caption"&gt;Logo sketch&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="modbus-hub-configuration"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Modbus Hub configuration&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You need to configure the modbus device in the configuration.yaml's modbus section.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;logo1&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;5&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;tcp&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;192.168.2.28&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;502&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="input-number"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Input Number&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To set the heater power, I created an 'input number' which triggers
an automation to perform a modbus write. I set the scale to the
heater's maximum output power, and the unit to Watts so this can
be plotted nicely.&lt;/p&gt;
&lt;p&gt;Note that this is not the actual power. Since we have a feedback loop in the
automation below, all solar production will be consumed but this value will not
be exactly the heater power since it's not a measurement.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;heatdump_level_variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;heatdump level&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;2000&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;unit_of_measurement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;W&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="automation"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Automation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After setting up this automation, changing the input number slider
will change the Logo's analog output.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;modbusupdate&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;input_number&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;modbus&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;register&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;input_number.heatdump_level_variable&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;modbus.write_register&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;hub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;logo1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;528&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;int(states(&amp;#39;input_number.heatdump_level_variable&amp;#39;)|int&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1000/2000)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="power-optimizer"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Power optimizer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm very fed up with YAML automations in home assistant. I tried &lt;a class="reference external" href="https://hacs-pyscript.readthedocs.io/en/latest/"&gt;pyscript&lt;/a&gt; which
is very promising, but this time I wanted to give &lt;a class="reference external" href="https://appdaemon.readthedocs.io/en/latest/"&gt;appdaemon&lt;/a&gt; a try.&lt;/p&gt;
&lt;p&gt;I created this simple automation which has an  obvious issue: that the power
consumption and production values from a DSMR telegram are fed sequentially to
the power_update() callback via state update events, which will cause
it to produce an output based on old and new data, resulting in bad output.&lt;/p&gt;
&lt;p&gt;During my (short) testing I did not observe any weird behavior caused by this.
I just simply coded it and it started doing its thing!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloWorld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hass&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hass&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Note: All units are watts &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;consumption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;heater_max_power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2000&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;heater_min_power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;10&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;current_heater_power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listen_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_measurement_update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sensor.electricity_meter_power_consumption&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listen_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_measurement_update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sensor.electricity_meter_power_production&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_heater_power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;watt_value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Calculate and set the available heater power needed &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;watt_value_clipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;watt_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heater_max_power&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_heater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;watt_value_clipped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_heater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;input_number.heatdump_level_variable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_next_heater_power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grid_power&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old_heater_power&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; grid power: negative is consumption &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="c1"&gt;# don&amp;#39;t let the value go negative&lt;/span&gt;
        &lt;span class="n"&gt;heater_power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;grid_power&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;old_heater_power&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;heater_power&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heater_min_power&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;heater_power&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_measurement_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cb_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# A very hackish way to add consumption and production :-(&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;production&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;consumption&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;consumption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;
        &lt;span class="n"&gt;grid_power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;consumption&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;power is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;grid_power&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;heater_power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calculate_next_heater_power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid_power&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_heater_power&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_heater_power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;heater_power&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_heater_power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heater_power&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="trial-run"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;Trial run&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I took a screenshot of the operating values during the experiment since it's
the only way to 'demo' this.  A few notes on this graph:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;I was doing live changes to the system at the time, so there are gaps and spikes.&lt;/li&gt;
&lt;li&gt;The big blocks of grid consumption can be explained by the house's electrical convection heaters running to heat the house.&lt;/li&gt;
&lt;li&gt;The 'heatdump level' needs to be multiplied by two, it was not actually in Watts, but per-mille at this point in time(the range of the analog out).&lt;/li&gt;
&lt;li&gt;The net grid usage apeared pretty stable at 0W at this point, the meter's kWh readings also stayed constant.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/history2.png" /&gt;
&lt;p class="caption"&gt;History data during the experiment&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="final-thoughts-on-all-of-this"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;Final thoughts on all of this&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="integration-into-an-existing-heating-system"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;Integration into an existing heating system&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To fit this into an existing heating system, a circuit like
this could be used:&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;object class="thlaaligncenter" data="https://www.thouters.be/img/SolarMatchedElectricHeating/hack.svg" type="image/svg+xml"&gt;integration idea&lt;/object&gt;
&lt;p class="caption"&gt;Conceptual schematic&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Here an existing thermostat would ensure a minimum ambient temperature,
while a new one (possibly the cooling output of the thermostat) would
cut the control signal to the heater to prevent things getting to hot on sunny
days.&lt;/p&gt;
&lt;p&gt;The second analog out is used to provide a 'full power' signal to the power
controller.&lt;/p&gt;
&lt;p&gt;If you want to create a fully continuous heating system, you can feed
an analog value to this analog output and have it sent to the power controller
by setting the first thermostat setpoint very high.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="cost-considerations"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-13"&gt;Cost considerations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Siemens components cost me about 250€ which is quite expensive but
actual professional equipment (it will be used as home automation
controller for the simple reliable things).&lt;/p&gt;
&lt;p&gt;There are probably cheaper Digital-to-analog options possible that can be
combined with an ESPHome or raspberry pi to reduce cost. I bought a cheap off
the shelve PWM to 0-10V module I'm going to experiment with.&lt;/p&gt;
&lt;p&gt;The RGC1P23V30ED power controller cost 138€.  I looked at other power
controllers, but the RGC1P23V30ED looked like the most reliable and best bang
for the buck.&lt;/p&gt;
&lt;p&gt;The required netfilter to minimize electro-magnetic interference also costs 110€.
Which brings the minimal bill of materials to 250€. The filter also has a current draw of several watts continuously.
The power controller and filter also require a separate electricity
cabinet that is deeper than the normal Belgian breaker boxes, likely a metal one
that can heatsink the filter and power controller at the same time.&lt;/p&gt;
&lt;p&gt;Don't forget to &lt;a class="reference external" href="https://docs.openenergymonitor.org/pv-diversion/background/overload-protection-of-mains-electrical-circuits.html"&gt;include a suitable fuse&lt;/a&gt; to protect the power controller,
the power controller datasheet provides specifications for this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="cost-benefit"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-14"&gt;Cost-benefit&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you have a gas heating system and have the same energy provider as I do,
the return on injecting a kWh to the grid is the same as the cost of a kWh gas,
I'm not sure what the additional taxes or distribution costs are for gas, but if
there are any additional costs, you would be better off running an electric heater with it on cold days.&lt;/p&gt;
&lt;p&gt;For every kWh I immediately use to heat the house, I save over 0.28€. So imagine
a total setup cost of 300€ (I added 50€ there for a &lt;a class="reference external" href="https://www.vevor.nl/elektrische-behuizing-c_10749/vevor-stalen-elektrische-kast-elektrische-behuizing-8-x-8-x-6-inch-koolstofstaal-ip65-p_010596273424"&gt;small metal enclosure&lt;/a&gt; and esphome to generate 0-10V),
this thing would need to burn 1.2MWh of solar to break even. I don't know how much
solar production we will get in the first half of the year, so can't estimate how soon
the hardware would pay itself back.&lt;/p&gt;
&lt;p&gt;Anyway, any situation will differ, so if you consider a similar aproach, enjoy the napkin math 🧐.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="solar"/><category term="energy"/><category term="python"/><category term="homeassistant"/><category term="siemenslogo"/></entry><entry><title>Rainwater Pump and filter box</title><link href="https://www.thouters.be/RainWaterPump.html" rel="alternate"/><published>2023-12-19T13:30:00+01:00</published><updated>2023-12-19T13:30:00+01:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2023-12-19:/RainWaterPump.html</id><summary type="html">
&lt;p&gt;I chose to place our our rainwater tank's pump and filter in the garden shed
slightly away from the main house. This allowed for a cheaper
pump outside the tank and minimizes in-house technical equipment and noise.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPump/filterfront.jpg" /&gt;
&lt;/div&gt;

&lt;p&gt;I chose to place our our rainwater tank's pump and filter in the garden shed
slightly away from the main house. This allowed for a cheaper
pump outside the tank and minimizes in-house technical equipment and noise.&lt;/p&gt;

&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pump-housing" id="toc-entry-1"&gt;Pump housing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pump" id="toc-entry-2"&gt;Pump&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#filter" id="toc-entry-3"&gt;Filter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#tools" id="toc-entry-4"&gt;Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#filter-routing-matrix" id="toc-entry-5"&gt;Filter Routing matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#tracing" id="toc-entry-6"&gt;Tracing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#leaky-filter-issues" id="toc-entry-7"&gt;Leaky filter issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="pump-housing"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;Pump housing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The pump is located in an old garden shed, which is open to the elements.
To protect it from freezing temperatures, I prototyped a box out of trash ceiling
planks and my dad helped turn that into a a sturdy box using scrap wood obtained
while deconstructing our attic space.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="pump box" src="https://www.thouters.be/img/RainWaterPump/housing.jpg" /&gt;
&lt;/div&gt;
&lt;p&gt;The front panel of the housing is held in place using nuts over two threaded rods
so it can be easily removed to clean the filter.&lt;/p&gt;
&lt;p&gt;There is plenty of space in the middle to still fit a back up tank with float
valve to switch over the system to drinking water when the tank runs dry.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pump"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;Pump&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I bought a DAB Jetinox 82 pump with control-d pressure controller.
I added a sand filter at the suction side, but in I wouldn't add one if I were to do this over.&lt;/p&gt;
&lt;p&gt;I had a lot of issues with this filter drawing air, and would have already removed it if it
was trivial to do. I will probably remove it rather sooner since its placement deep inside
the box means the top panels have to be removed to sevice this filter.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPump/pumptop.jpg" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="filter"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Filter&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I bought a Honeywell honeywell ff60 triplex filter. I fitted it inside the pump box
so the water traveling the long pipe to the house can be as clean as possible. This way
a minimal amount of dirt will settle inside the pipe.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPump/filterfront.jpg" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tools"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Tools&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I had to join a lot of valves together and bought a spool of Locktite 55 PTFE sealing cord. Worth the investment since
you can untighten the joint a bit after tightening it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="filter-routing-matrix"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Filter Routing matrix&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At the front of the box I mounted an elaborate and over engineered combination of valves,
T junctions and other things.  This allows me to re-route the water when the filter needs
servicing or if I want filtered water on the garden tap. It also allows to cut off and
drain the garden tap so it does not break from freezing.&lt;/p&gt;
&lt;style&gt; .whiten {  background: rgba(255, 255, 255, 0.3); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} &lt;/style&gt;&lt;div class="figure align-center"&gt;
&lt;object class="whiten" data="https://www.thouters.be/img/RainWaterPump/pump.svg" type="image/svg+xml"&gt;pipestand&lt;/object&gt;
&lt;/div&gt;
&lt;p&gt;Inputs and outputs:&lt;/p&gt;
&lt;table class="docutils field-list" frame="void" rules="none"&gt;
&lt;col class="field-name" /&gt;
&lt;col class="field-body" /&gt;
&lt;tbody valign="top"&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;A:&lt;/th&gt;&lt;td class="field-body"&gt;To the rainwater tank's floating oneway valve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;B:&lt;/th&gt;&lt;td class="field-body"&gt;Coupling to connect a backup water tank during tank maintenance/cleaning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;C:&lt;/th&gt;&lt;td class="field-body"&gt;Garden tap/hose on shed wall.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;D:&lt;/th&gt;&lt;td class="field-body"&gt;Pressure line to house.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;E:&lt;/th&gt;&lt;td class="field-body"&gt;freeze drain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Valves:&lt;/p&gt;
&lt;table class="docutils field-list" frame="void" rules="none"&gt;
&lt;col class="field-name" /&gt;
&lt;col class="field-body" /&gt;
&lt;tbody valign="top"&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;1:&lt;/th&gt;&lt;td class="field-body"&gt;Shutoff of rainwater tank&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;2:&lt;/th&gt;&lt;td class="field-body"&gt;Shutoff of maintenance supply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;3:&lt;/th&gt;&lt;td class="field-body"&gt;Filter input shutoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;4:&lt;/th&gt;&lt;td class="field-body"&gt;Filter output shutoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;5:&lt;/th&gt;&lt;td class="field-body"&gt;House shutoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;6:&lt;/th&gt;&lt;td class="field-body"&gt;Unfiltered water to garden tap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;7:&lt;/th&gt;&lt;td class="field-body"&gt;Filtered water to garden tap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;8:&lt;/th&gt;&lt;td class="field-body"&gt;Garden tap winter shutoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;9:&lt;/th&gt;&lt;td class="field-body"&gt;Garden tap drain point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="field"&gt;&lt;th class="field-name"&gt;10:&lt;/th&gt;&lt;td class="field-body"&gt;Garden tap.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="tracing"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Tracing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have 2 meters of 10W/m tracing cable to prevent the pump and filter from freezing
if temperatures were to dip under 0C for a long time.
I still need to add a thermostat that activates the heating element.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="leaky-filter-issues"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Leaky filter issues&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The triplex filter got leaky in december after having the pump turned off and the system presure
dropped to connect a toilet to the rainwater system.
The plastic lock rings came really loose, I had to apply a quarter turn to tighten them up!&lt;/p&gt;
&lt;p&gt;I already observed the filter leaking a bit during the summer after dropping the pressure,
and I'm not sure what I can do about this...&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="makes"/><category term="water"/></entry><entry><title>First steps with Rust on the stm32</title><link href="https://www.thouters.be/FirstStepsWithRustOnStm32.html" rel="alternate"/><published>2023-08-03T20:10:00+02:00</published><updated>2023-08-03T20:10:00+02:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2023-08-03:/FirstStepsWithRustOnStm32.html</id><summary type="html">
&lt;p&gt;Since the Rust programming language is all the rage, I started exploring
this programming language during the summer. These are my notes on what I needed
to do to set up a working development environment using the hardware I
already had in my lab.&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="placeholder" src="https://www.thouters.be/img/FirstStepsWithRustOnStm32/bluepill.jpg" /&gt;
&lt;p class="caption"&gt;A stm32 based traffic light I built for fun (and my kids) years ago&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Since the Rust programming language is all the rage, I started exploring
this programming language during the summer. These are my notes on what I needed
to do to set up a working development environment using the hardware I
already had in my lab.&lt;/p&gt;

&lt;div class="section" id="hello-world-blinky"&gt;
&lt;h2&gt;Hello World blinky&lt;/h2&gt;
&lt;p&gt;To start off simple, I followed instructions on &lt;a class="reference external" href="https://github.com/stm32-rs/stm32f1xx-hal"&gt;https://github.com/stm32-rs/stm32f1xx-hal&lt;/a&gt;
to build the blink example program.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="flashing-using-black-magic-probe"&gt;
&lt;h2&gt;Flashing Using Black magic probe&lt;/h2&gt;
&lt;div class="section" id="connecting-the-device"&gt;
&lt;h3&gt;Connecting the device&lt;/h3&gt;
&lt;p&gt;To connect the STM32 black magic probe to the Device under test, the usual connections
are required:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;B8 = SWDCLK&lt;/li&gt;
&lt;li&gt;B9 = SWDIO&lt;/li&gt;
&lt;li&gt;Vcc = Vcc&lt;/li&gt;
&lt;li&gt;GND = GND&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="installing-a-working-gdb"&gt;
&lt;h3&gt;Installing a working GDB&lt;/h3&gt;
&lt;p&gt;To avoid hitting &lt;a class="reference external" href="https://github.com/blackmagic-debug/blackmagic/issues/929"&gt;this compatibility bug&lt;/a&gt; I hit with &lt;cite&gt;gdb-multiarch&lt;/cite&gt;, I installed
the first older (8.3) GDB that came to my mind:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
wget -P /tmp --progress=dot:giga https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2
sudo mkdir -p /opt/TOOL_ARMGCCEMB
sudo tar -xf /tmp/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2 -C /opt/TOOL_ARMGCCEMB
rm /tmp/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2
sudo apt install libncurses5
&lt;/pre&gt;
&lt;p&gt;I could then successfully run the commands found on the black magic probe website.
A quick look through the project folder revealed the .elf program file.
As you can see, GDB can step through the code.&lt;/p&gt;
&lt;pre class="literal-block"&gt;
thomas&amp;#64;roper ~ % /opt/TOOL_ARMGCCEMB/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb
GNU gdb (GNU Tools for Arm Embedded Processors 9-2019-q4-major) 8.3.0.20190709-git
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type &amp;quot;show copying&amp;quot; and &amp;quot;show warranty&amp;quot; for details.
This GDB was configured as &amp;quot;--host=x86_64-linux-gnu --target=arm-none-eabi&amp;quot;.
Type &amp;quot;show configuration&amp;quot; for configuration details.
For bug reporting instructions, please see:
&amp;lt;http://www.gnu.org/software/gdb/bugs/&amp;gt;.
Find the GDB manual and other documentation resources online at:
    &amp;lt;http://www.gnu.org/software/gdb/documentation/&amp;gt;.

For help, type &amp;quot;help&amp;quot;.
Type &amp;quot;apropos word&amp;quot; to search for commands related to &amp;quot;word&amp;quot;.
(gdb) target extended-remote /dev/ttyACM0
Remote debugging using /dev/ttyACM0
(gdb) monitor swdp_scan
Target voltage: Not Detected
Available Targets:
No. Att Driver
 1      STM32F1 medium density
(gdb) att 1
Attaching to Remote target
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the &amp;quot;file&amp;quot; command.
0x08000b54 in ?? ()
(gdb) file /home/thomas/pets/verkeerslicht-rust/target/
.rustc_info.json    CACHEDIR.TAG        debug/              thumbv7m-none-eabi/
(gdb) file /home/thomas/pets/verkeerslicht-rust/target/thumbv7m-none-eabi/
CACHEDIR.TAG  debug/
(gdb) file /home/thomas/pets/verkeerslicht-rust/target/thumbv7m-none-eabi/debug/
.cargo-lock           .fingerprint/         build/                deps/                 examples/             incremental/          verkeerslicht-rust    verkeerslicht-rust.d
(gdb) file /home/thomas/pets/verkeerslicht-rust/target/thumbv7m-none-eabi/debug/verkeerslicht-rust
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from /home/thomas/pets/verkeerslicht-rust/target/thumbv7m-none-eabi/debug/verkeerslicht-rust...
(gdb) load
Loading section .vector_table, size 0x130 lma 0x8000000
Loading section .text, size 0x2d78 lma 0x8000130
Loading section .rodata, size 0x9cc lma 0x8002eb0
Start address 0x8000130, load size 14452
Transfer rate: 11 KB/sec, 903 bytes/write.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/thomas/pets/verkeerslicht-rust/target/thumbv7m-none-eabi/debug/verkeerslicht-rust
^C
Program received signal SIGINT, Interrupt.
0x08000c82 in cortex_m::peripheral::syst::&amp;lt;impl cortex_m::peripheral::SYST&amp;gt;::has_wrapped (self=0x8002a3b &amp;lt;stm32f1xx_hal::timer::counter::SysCounterHz::wait+22&amp;gt;)
    at /home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.7/src/peripheral/syst.rs:136
136         }
(gdb) bt
#0  0x08000c82 in cortex_m::peripheral::syst::&amp;lt;impl cortex_m::peripheral::SYST&amp;gt;::has_wrapped (self=0x8002a3b &amp;lt;stm32f1xx_hal::timer::counter::SysCounterHz::wait+22&amp;gt;)
    at /home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.7/src/peripheral/syst.rs:136
#1  0x08002a3a in stm32f1xx_hal::timer::counter::SysCounterHz::wait (self=0x20004f84) at /home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32f1xx-hal-0.10.0/src/timer/counter.rs:270
#2  0x08000a52 in verkeerslicht_rust::__cortex_m_rt_main () at src/main.rs:49
#3  0x080008f2 in main () at src/main.rs:19
(gdb) l 0
file: &amp;quot;/home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.7/src/peripheral/syst.rs&amp;quot;, line number: 0, symbol: &amp;quot;???&amp;quot;
1       //! SysTick: System Timer
2
3       use volatile_register::{RO, RW};
4
5       use crate::peripheral::SYST;
6
7       /// Register block
8       #[repr(C)]
9       pub struct RegisterBlock {
10          /// Control and Status
file: &amp;quot;/home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.7/src/peripheral/syst.rs&amp;quot;, line number: 0, symbol: &amp;quot;???&amp;quot;
1       //! SysTick: System Timer
2
3       use volatile_register::{RO, RW};
4
5       use crate::peripheral::SYST;
6
7       /// Register block
8       #[repr(C)]
9       pub struct RegisterBlock {
10          /// Control and Status
(gdb) n
stm32f1xx_hal::timer::counter::SysCounterHz::wait (self=0x20004f84) at /home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32f1xx-hal-0.10.0/src/timer/counter.rs:273
273                 Err(nb::Error::WouldBlock)
(gdb)
270             if self.tim.has_wrapped() {
(gdb)
275         }
(gdb)
verkeerslicht_rust::__cortex_m_rt_main () at src/main.rs:49
49              block!(timer.wait()).unwrap();
(gdb)
Note: automatically using hardware breakpoints for read-only addresses.
49              block!(timer.wait()).unwrap();
(
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="turning-it-into-a-traffic-light"&gt;
&lt;h2&gt;Turning it into a traffic light&lt;/h2&gt;
&lt;p&gt;Armed with VIM ALE configured with rust-analyzer and the stm32 hal code, my next steps are to convert
this simple program to Rust, trying to map the code using Rust concepts I knew already.&lt;/p&gt;
&lt;p&gt;For &lt;a class="reference external" href="https://github.com/thouters/bluepill-verkeerslicht"&gt;my original traffic light gimmic&lt;/a&gt;, I quickly whipped up a truth table as multi dimensional C array.
I'll have to see how to elegantly implement the logic to call gpio set_high and set_low() functions.&lt;/p&gt;
&lt;p&gt;To be continued...&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="stm32"/><category term="rust"/></entry><entry><title>OpenPlank Proof Of Concept</title><link href="https://www.thouters.be/OpenPlankPoc.html" rel="alternate"/><published>2023-01-11T20:10:00+01:00</published><updated>2023-01-11T20:10:00+01:00</updated><author><name>Thomas Langewouters</name></author><id>tag:www.thouters.be,2023-01-11:/OpenPlankPoc.html</id><summary type="html">
&lt;p&gt;In November 2022 I saw a cool Internet Of Things controller at a Matter event.
They were marketing it as an unobtrusive way to interact with technology.
Unlike the usual glass surface finish of touch screen devices, it looks like a wooden plank
that is sensitive to touch and uses a LED pixel matrix behind the wood to visualize icons and text.&lt;/p&gt;
&lt;p&gt;Since the device wasn't available for sale, I wondered how easy it would be to
DIY a (much) simpler version that I could use for some of the same tasks.&lt;/p&gt;
&lt;p&gt;I worked on this for a few hours each week, and by the end of December I had
this simple proof of concept, making use of cheap parts and easy to assemble.&lt;/p&gt;
&lt;p&gt;I call it the OpenPlank. Check it out...&lt;/p&gt;
</summary><content type="html">&lt;!-- vim:ft=rst:spell:spelllang=en --&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/poc-standup.jpg" /&gt;
&lt;p class="caption"&gt;The first (crude) prototype with sloppy veneer edges&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;In November 2022 I saw a cool Internet Of Things controller at a Matter event.
They were marketing it as an unobtrusive way to interact with technology.
Unlike the usual glass surface finish of touch screen devices, it looks like a wooden plank
that is sensitive to touch and uses a LED pixel matrix behind the wood to visualize icons and text.&lt;/p&gt;
&lt;p&gt;Since the device wasn't available for sale, I wondered how easy it would be to
DIY a (much) simpler version that I could use for some of the same tasks.&lt;/p&gt;
&lt;p&gt;I worked on this for a few hours each week, and by the end of December I had
this simple proof of concept, making use of cheap parts and easy to assemble.&lt;/p&gt;
&lt;p&gt;I call it the OpenPlank. Check it out...&lt;/p&gt;

&lt;div class="contents topic" id="table-of-contents"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Table of Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference internal" href="#led-display" id="toc-entry-1"&gt;LED Display&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#hub75-interface" id="toc-entry-2"&gt;HUB75 interface&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#input" id="toc-entry-3"&gt;Input&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#touch-layer-circuit-board" id="toc-entry-4"&gt;Touch layer circuit board&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#touch-digitizer" id="toc-entry-5"&gt;Touch digitizer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#device-input-backup-plan" id="toc-entry-6"&gt;Device input backup plan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#chassis" id="toc-entry-7"&gt;Chassis&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#double-size-chassis" id="toc-entry-8"&gt;Double size chassis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#mechanical-assembly" id="toc-entry-9"&gt;Mechanical assembly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#user-interface" id="toc-entry-10"&gt;User Interface&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#simulator" id="toc-entry-11"&gt;Simulator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#circuit" id="toc-entry-12"&gt;Circuit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#demo-video" id="toc-entry-13"&gt;Demo video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#design-files" id="toc-entry-14"&gt;Design Files&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="reference internal" href="#poc-touch-test-demo" id="toc-entry-15"&gt;POC touch test/demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#pc-simulator" id="toc-entry-16"&gt;PC Simulator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#chassis-design-files" id="toc-entry-17"&gt;Chassis design files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a class="reference internal" href="#work-in-progress" id="toc-entry-18"&gt;Work in progress&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="led-display"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1"&gt;LED Display&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started this project by looking at the options for the LED display that were available.
I found that there are Chinese LED matrixes on the market that you go by the
search term 'HUB75 P2.5', where the P2.5 stands for the pitch between
the LED pixels(in this case 2.5mm). The most pixel-dense displays I found have a 1mm pitch,
and there are more coarse displays with eg 5mm pitch.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/hub75panels.jpg" /&gt;
&lt;/div&gt;
&lt;p&gt;I opted for a P2.5 display since that would work with the idea I had for the touch input.&lt;/p&gt;
&lt;p&gt;These displays can be daisy-chained; they can be connected together and driven as
a single larger display.&lt;/p&gt;
&lt;div class="section" id="hub75-interface"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-2"&gt;HUB75 interface&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I found and used the &lt;a class="reference external" href="https://github.com/2dom/PxMatrix"&gt;PxMatrix&lt;/a&gt; library, which was easy to set up
and to get working.  I found out that the display I had has a 1/16 configuration.
The display is driven using a SPI host and the signals are wired in such a way
that it is configured as one large shift register.
&lt;a class="reference external" href="https://www.sparkfun.com/news/2650"&gt;For more information about HUB75 display matrices, you can read this sparkfun article&lt;/a&gt; or search the web.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="input"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3"&gt;Input&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To avoid a complicated touch layer design, I wanted to check if a circuit board with
vertical copper strips could provide a simple one dimensional (along the X axis) input.&lt;/p&gt;
&lt;p&gt;I designed a cover with 'sieve' hole pattern grid and printed it.
I then cut and applied a few strips of copper tape to see if a touch controller I
had on stock would sense the touch when the veneer was held before it.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/zeef.jpg" /&gt;
&lt;/div&gt;
&lt;p&gt;This worked, so I quickly hacked together a design in KiCAD.&lt;/p&gt;
&lt;div class="section" id="touch-layer-circuit-board"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-4"&gt;Touch layer circuit board&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I made this design simple and quick, its main features are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The 1.2mm holes are aligned with the LED's&lt;/li&gt;
&lt;li&gt;It has a 10mm bezel around the display that can be used as glue surface.&lt;/li&gt;
&lt;li&gt;There is no bezel on right side so two boards can be put next to each other seamlessly&lt;/li&gt;
&lt;li&gt;there are 2.54mm pitch SMT connectors on back to plug the MPR121 boards onto.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/touchlayerv1.png" /&gt;
&lt;/div&gt;
&lt;p&gt;Once consideration with this setup is that the holes need to be
aligned with great care, since the RGB LED pixels are made out
of three LED dies that are slightly offset. Misalignment would
block one of the colors partly. Fortunately the veneer diffuses
the light and makes it less of an issue.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/pcboverlayed.jpg" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="touch-digitizer"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-5"&gt;Touch digitizer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I started with MPR121's since I already had a few of these boards
from a previous project. They can be arranged in a grid too to
create a 5x7 or 4x8 grid.&lt;/p&gt;
&lt;p&gt;As an alternative I found a software library to &lt;a class="reference external" href="https://github.com/HCI-Lab-Saarland/MultiTouchKit"&gt;do touch using only a simple analog mux&lt;/a&gt;
(documentation for it &lt;a class="reference external" href="https://github.com/HCI-Lab-Saarland/MultiTouchKitDoc"&gt;can be found here&lt;/a&gt;). This has some special microcontroller
requirements, but could still be an option.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="device-input-backup-plan"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-6"&gt;Device input backup plan&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In case the touchscreen approach would not work, I also have some gesture sensor I can use
(APDS-9960 Gesture sensor if I recall correctly). I may add it anyway to automatically wake up the device
when a hand gets near it.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="chassis"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-7"&gt;Chassis&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As initial prototype I designed a chassis &lt;em&gt;(plural chassis /-iz/ from French châssis,
the load-bearing framework of an artificial object)&lt;/em&gt; that holds a single display.
The chassis has:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;a 1mm bezel around the touch PCB which keeps it in place for alignment when glueing.&lt;/li&gt;
&lt;li&gt;10mm glue contact bezel with the touch PCB&lt;/li&gt;
&lt;li&gt;openings for the touch PCB's connectors.&lt;/li&gt;
&lt;li&gt;slots to insert discs behind the LED board to keep it in place but easy to remove.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="chassis prototype" src="https://www.thouters.be/img/OpenPlankPoc/chassis1.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="double-size-chassis"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-8"&gt;Double size chassis&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A chassis around two displays would be too big for my Prusa MK3's printbed,
so I designed one that uses Butterfly joints to connect both parts.
This fits together perfectly with the butterfly joints, and feels quite solid.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="double sized chassis prototype" src="https://www.thouters.be/img/OpenPlankPoc/doublesize.png" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="mechanical-assembly"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-9"&gt;Mechanical assembly&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I printed a plastic block a bit smaller than the display to apply pressure to the
touch PCB from the back when clamping for glueing, and four brackets that
hold the bezel's Y dimension together.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="back" src="https://www.thouters.be/img/OpenPlankPoc/clamped.jpg" /&gt;
&lt;/div&gt;
&lt;p&gt;I used a transparent contact glue, applied with a conference badge as spatula
but a credit card sized plastic card would also work I think.&lt;/p&gt;
&lt;p&gt;Both parts are plastered with the glue using the spatula, left to dry for 10 minutes
and afterwards put together and rubbed to remove air (check youtube for video's on how to apply veneer).&lt;/p&gt;
&lt;p&gt;Before applying glue it is important to use painter's masking tape to cover
surfaces that should not get glue onto them, as well as the backside
of the touch board so glue does not get pushed trough the holes.&lt;/p&gt;
&lt;p&gt;After glueing the PCB onto the chassis body, the wood veneer (I used
&lt;a class="reference external" href="https://cricut.com/en-us/materials/material-type/maker-only-materials/wood/natural-wood-veneer---maple%C2%A0/2007068.html"&gt;Cricut maple veneer&lt;/a&gt;) is glued to the PCB, trimming the edges must
be done carefully, since you can easily cut off too much, as I did
with my first prototype.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="user-interface"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-10"&gt;User Interface&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I plan to use &lt;a class="reference external" href="https://lvgl.io/"&gt;LGVL - Light and Versatile Graphics Library&lt;/a&gt; to make
the user interface as generic as possible and to reuse things like swipe detection and widgets.&lt;/p&gt;
&lt;p&gt;It will need to run in a monochrome mode, with a small framebuffer and
tiny fonts. It may also require some rewriting of the rendering code
that renders widgets too large by default.
Writing some glue code to feed LVGL's pixel output to the HUB75 driver
will also be needed.&lt;/p&gt;
&lt;p&gt;I found a nice &lt;a class="reference external" href="https://maldus512.medium.com/porting-littlevgl-for-a-monochrome-display-6c7be58851ce"&gt;article on using LVGL with monochrome displays&lt;/a&gt; which makes
it look easy.&lt;/p&gt;
&lt;div class="section" id="simulator"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-11"&gt;Simulator&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While waiting for parts, I started working on a PC simulator
which renders the LVGL framebuffer pixels on an SDL surface as round pixels.&lt;/p&gt;
&lt;p&gt;The simulator is still in early stages, since the electronic and mechanical parts
arrived quickly and writing some proof of concept software for it only took a few minutes.&lt;/p&gt;
&lt;p&gt;It should also include downsampling of touch events to match the coarse touch PCB.
This way the user interface can be a realistic representation of the hardware.
I plan to also use it to check if this setup is good enough to use slider widgets.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/sim2.png" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="circuit"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-12"&gt;Circuit&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This circuit is based on the PxMatrix wiring, along
with a simple I2C bus for the MPR121 touch modules.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="schematic" src="https://www.thouters.be/img/OpenPlankPoc/schematicv1.jpg" /&gt;
&lt;/div&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="back" src="https://www.thouters.be/img/OpenPlankPoc/back1.jpg" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="demo-video"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-13"&gt;Demo video&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As you can see, input works! It's coarse but functional.&lt;/p&gt;
&lt;span class="videobox"&gt;
        &lt;video width='100%' height='480' preload='none' controls poster='https://www.thouters.be/img/OpenPlankPoc/demo-snap-cropped.png'&gt;
           &lt;source src='https://www.thouters.be/img/OpenPlankPoc/demo.mp4'  type='video/mp4; '/&gt;
        &lt;/video&gt;
&lt;/span&gt;&lt;/div&gt;
&lt;div class="section" id="design-files"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-14"&gt;Design Files&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="poc-touch-test-demo"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-15"&gt;POC touch test/demo&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This platformio project uses the adafruit graphics API of the PxMatrix library
to light up verical bars of the openplank touch PCB when they are touched.
The Adafruit MPR121 library is used to read out two MPR121 chips.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/thouters/pio-openplank-t7mini-input-poc"&gt;https://github.com/thouters/pio-openplank-t7mini-input-poc&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pc-simulator"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-16"&gt;PC Simulator&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This platformio project uses a hacked version of the &lt;a class="reference external" href="https://github.com/lvgl/lv_drivers/"&gt;lv_drivers&lt;/a&gt; SDL backend
for LVGL to render the framebuffer pixels as 10x10px circles on a background image of a wooden plank.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/thouters/pio_lv_ledmatrixemu"&gt;https://github.com/thouters/pio_lv_ledmatrixemu&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="chassis-design-files"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#toc-entry-17"&gt;Chassis design files&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This OpenSCAD code models the chassis of the OpenPlank&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/thouters/openplank-chassis"&gt;https://github.com/thouters/openplank-chassis&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Chassis STL object&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.printables.com/model/373360-openplank-mk0-12-chassis"&gt;https://www.printables.com/model/373360-openplank-mk0-12-chassis&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="work-in-progress"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-18"&gt;Work in progress&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can follow me on Mastodon to read updates on this project.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/><category term="ledmatrix"/><category term="hub75"/><category term="LED"/><category term="touch"/><category term="diy"/></entry></feed>