Thomas shares makeshttps://www.thouters.be/2024-01-29T21:10:00+01:00Virtual Solar/Grid power meters2024-01-29T21:10:00+01:002024-01-29T21:10:00+01:00Thomas Langewouterstag:www.thouters.be,2024-01-29:/VirtualSolarGridPowerMeters.html<!-- vim:ft=rst:spell:spelllang=en -->
<style> .thlaaligncenter { align: center; background: rgba(255, 255, 255, 1); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} </style><div class="figure align-center">
<object class="thlaaligncenter" data="https://www.thouters.be/img/VirtualSolarGridPowerMeters/blockpower.svg" type="image/svg+xml">block diagram</object>
</div>
<p>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.</p>
<p>I created home assistant template sensors to come up with these power numbers, and
added integrators to obtain energy meters.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<style> .thlaaligncenter { align: center; background: rgba(255, 255, 255, 1); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} </style><div class="figure align-center">
<object class="thlaaligncenter" data="https://www.thouters.be/img/VirtualSolarGridPowerMeters/blockpower.svg" type="image/svg+xml">block diagram</object>
</div>
<p>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.</p>
<p>I created home assistant template sensors to come up with these power numbers, and
added integrators to obtain energy meters.</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title">Table of Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#splitting-the-power" id="id1">Splitting the power</a><ul>
<li><a class="reference internal" href="#scenario-s" id="id2">Scenario's</a></li>
</ul>
</li>
<li><a class="reference internal" href="#home-assistant-entities" id="id3">Home assistant entities</a><ul>
<li><a class="reference internal" href="#working-around-an-integration-sensor-caveat" id="id4">Working around an integration sensor caveat</a></li>
<li><a class="reference internal" href="#template-sensor" id="id5">Template sensor</a></li>
<li><a class="reference internal" href="#energy-meters" id="id6">Energy meters</a></li>
</ul>
</li>
<li><a class="reference internal" href="#limitations-and-conclusion" id="id7">Limitations and conclusion</a></li>
</ul>
</div>
<div class="section" id="splitting-the-power">
<h2><a class="toc-backref" href="#id1">Splitting the power</a></h2>
<div class="section" id="scenario-s">
<h3><a class="toc-backref" href="#id2">Scenario's</a></h3>
<p>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:</p>
<ul class="simple">
<li>Grid + Solar</li>
<li>Grid only</li>
<li>Solar only</li>
</ul>
<p>Every consumer (house, car) and producer (solar, grid) is represented by an arrow.
The following abbreviations are used:</p>
<ul class="simple">
<li>X: The device, the car in this case</li>
<li>H: Devices in the Home</li>
<li>S: Solar production</li>
</ul>
<div class="figure align-center">
<object class="thlaaligncenter" data="https://www.thouters.be/img/VirtualSolarGridPowerMeters/powervectors.svg" type="image/svg+xml">block diagram</object>
<p class="caption">Scenario's of production and consumption</p>
</div>
<p>With every scenario plotted, the parts of interest can be marked:</p>
<ul class="simple">
<li>XS: Device used solar power</li>
<li>XG: Device used grid power</li>
</ul>
</div>
</div>
<div class="section" id="home-assistant-entities">
<h2><a class="toc-backref" href="#id3">Home assistant entities</a></h2>
<div class="section" id="working-around-an-integration-sensor-caveat">
<h3><a class="toc-backref" href="#id4">Working around an integration sensor caveat</a></h3>
<p>The <a class="reference external" href="https://www.home-assistant.io/integrations/integration/">Riemann sum integral integration</a> in Home Assistant can be used
to turn the power measured by a home assistant entity into an energy measurement.</p>
<p>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.</p>
<p>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.</p>
<div class="figure align-center">
<img alt="block diagram" src="https://www.thouters.be/img/VirtualSolarGridPowerMeters/integrationerror-marked.png" />
<p class="caption">integration sensor output with error marked.</p>
</div>
<p>A workaround that I found online for this is to update the sensor every second and
add a dummy attribute containing the current time.</p>
</div>
<div class="section" id="template-sensor">
<h3><a class="toc-backref" href="#id5">Template sensor</a></h3>
<p>Here is a listing of the template sensor code as I use it:</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">trigger</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">time_pattern</span><span class="w"></span>
<span class="w"> </span><span class="nt">minutes</span><span class="p">:</span><span class="w"> </span><span class="s">"/1"</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">state</span><span class="w"></span>
<span class="w"> </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">sensor.electricity_meter_power_production</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">sensor.electricity_meter_power_consumption</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">sensor.solaredge_ac_power</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">sensor.sdm630_total_system_power</span><span class="w"></span>
<span class="w"> </span><span class="nt">not_from</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"unknown"</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"unavailable"</span><span class="w"></span>
<span class="w"> </span><span class="nt">not_to</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"unknown"</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"unavailable"</span><span class="w"></span>
<span class="w"> </span><span class="nt">sensor</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">car_grid_power</span><span class="w"></span>
<span class="w"> </span><span class="nt">unique_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">car_grid_power</span><span class="w"></span>
<span class="w"> </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">W</span><span class="w"></span>
<span class="w"> </span><span class="nt">device_class</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">power</span><span class="w"></span>
<span class="w"> </span><span class="nt">state_class</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">measurement</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">></span><span class="w"></span>
<span class="w"> </span><span class="no">{% set production = states('sensor.electricity_meter_power_production')|float %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% set consumption = states('sensor.electricity_meter_power_consumption')|float %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% set g = consumption - production %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% set s = states('sensor.solaredge_ac_power')|float /1000 %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% set d = states('sensor.sdm630_total_system_power')|float /1000%}</span><span class="w"></span>
<span class="w"> </span><span class="no">{%- if d < 0.0001 -%}</span><span class="w"></span>
<span class="w"> </span><span class="no">0.000</span><span class="w"></span>
<span class="w"> </span><span class="no">{%- elif g > 0 and g < d -%}</span><span class="w"></span>
<span class="w"> </span><span class="no">{# grid+solar #}</span><span class="w"></span>
<span class="w"> </span><span class="no">{{ '%0.3f' | format(g) }}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% elif g >= 0 %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{# grid only #}</span><span class="w"></span>
<span class="w"> </span><span class="no">{{ '%0.3f' | format(d) }}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% else %}</span><span class="w"></span>
<span class="w"> </span><span class="no">0.000</span><span class="w"></span>
<span class="w"> </span><span class="no">{% endif %}</span><span class="w"></span>
<span class="w"> </span><span class="nt">attributes</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">dummy</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">now().minute</span><span class="nv"> </span><span class="s">}}"</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">car_solar_power</span><span class="w"></span>
<span class="w"> </span><span class="nt">unique_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">car_solar_power</span><span class="w"></span>
<span class="w"> </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">W</span><span class="w"></span>
<span class="w"> </span><span class="nt">device_class</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">power</span><span class="w"></span>
<span class="w"> </span><span class="nt">state_class</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">measurement</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">></span><span class="w"></span>
<span class="w"> </span><span class="no">{% set production = states('sensor.electricity_meter_power_production')|float %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% set consumption = states('sensor.electricity_meter_power_consumption')|float %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% set g = consumption - production %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% set s = states('sensor.solaredge_ac_power')|float /1000 %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% set d = states('sensor.sdm630_total_system_power')|float /1000%}</span><span class="w"></span>
<span class="w"> </span><span class="no">{%- if d < 0.0001 -%}</span><span class="w"></span>
<span class="w"> </span><span class="no">0.000</span><span class="w"></span>
<span class="w"> </span><span class="no">{%- elif g > 0 and g < d -%}</span><span class="w"></span>
<span class="w"> </span><span class="no">{# grid+solar #}</span><span class="w"></span>
<span class="w"> </span><span class="no">{{ '%0.3f' | format(d-g) }}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% elif g >= 0 %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{# grid only #}</span><span class="w"></span>
<span class="w"> </span><span class="no">0.000</span><span class="w"></span>
<span class="w"> </span><span class="no">{%- else -%}</span><span class="w"></span>
<span class="w"> </span><span class="no">{# solar only #}</span><span class="w"></span>
<span class="w"> </span><span class="no">{{ '%0.3f' | format(d) }}</span><span class="w"></span>
<span class="w"> </span><span class="no">{%- endif %}</span><span class="w"></span>
<span class="w"> </span><span class="nt">attributes</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">dummy</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">now().minute</span><span class="nv"> </span><span class="s">}}"</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="energy-meters">
<h3><a class="toc-backref" href="#id6">Energy meters</a></h3>
<p>And these are integrated into energy meters by the following old-style sensors:</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">integration</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">total_car_grid_energy</span><span class="w"></span>
<span class="w"> </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">sensor.car_grid_power</span><span class="w"></span>
<span class="w"> </span><span class="nt">round</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">2</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">integration</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">total_car_solar_energy</span><span class="w"></span>
<span class="w"> </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">sensor.car_solar_power</span><span class="w"></span>
<span class="w"> </span><span class="nt">round</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">2</span><span class="w"></span>
</pre></div>
</div>
</div>
<div class="section" id="limitations-and-conclusion">
<h2><a class="toc-backref" href="#id7">Limitations and conclusion</a></h2>
<p>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.</p>
<p>I hope someone finds this useful and can reuse it or use it as inspiration.</p>
</div>
Solar Matched Electric Heating2023-12-27T20:10:00+01:002023-12-27T20:10:00+01:00Thomas Langewouterstag:www.thouters.be,2023-12-27:/SolarMatchedElectricHeating.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/heatdump2.png"/>
<p class="caption">A heater power-controlled to consume all excess solar production!</p>
</div>
<p>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.</p>
<p>This would make it possible to maximize our solar self consumption, which is
required to get a decent return on your solar investment.</p>
<p>For us, injecting to the electricity grid is giving away power,
we have to pay about 6x as much compared to what the utility pays us for returning energy to the grid.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/heatdump2.png" />
<p class="caption">A heater power-controlled to consume all excess solar production!</p>
</div>
<p>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.</p>
<p>This would make it possible to maximize our solar self consumption, which is
required to get a decent return on your solar investment.</p>
<p>For us, injecting to the electricity grid is giving away power,
we have to pay about 6x as much compared to what the utility pays us for returning energy to the grid.</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title">Table of Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#high-level-overview" id="id1">High level overview</a></li>
<li><a class="reference internal" href="#electrics" id="id2">Electrics</a><ul>
<li><a class="reference internal" href="#power-controller" id="id3">Power controller</a></li>
</ul>
</li>
<li><a class="reference internal" href="#siemens-logo-aq-and-home-assistant" id="id4">Siemens Logo AQ and Home assistant</a><ul>
<li><a class="reference internal" href="#logo-sketch" id="id5">Logo sketch</a></li>
<li><a class="reference internal" href="#modbus-hub-configuration" id="id6">Modbus Hub configuration</a></li>
<li><a class="reference internal" href="#input-number" id="id7">Input Number</a></li>
<li><a class="reference internal" href="#automation" id="id8">Automation</a></li>
</ul>
</li>
<li><a class="reference internal" href="#power-optimizer" id="id9">Power optimizer</a><ul>
<li><a class="reference internal" href="#trial-run" id="id10">Trial run</a></li>
</ul>
</li>
<li><a class="reference internal" href="#final-thoughts-on-all-of-this" id="id11">Final thoughts on all of this</a><ul>
<li><a class="reference internal" href="#integration-into-an-existing-heating-system" id="id12">Integration into an existing heating system</a></li>
<li><a class="reference internal" href="#cost-considerations" id="id13">Cost considerations</a></li>
<li><a class="reference internal" href="#cost-benefit" id="id14">Cost-benefit</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="high-level-overview">
<h2><a class="toc-backref" href="#id1">High level overview</a></h2>
<p>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.</p>
<style> .thlaaligncenter { align: center; background: rgba(255, 255, 255, 1); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} </style><div class="figure align-center">
<object class="thlaaligncenter" data="https://www.thouters.be/img/SolarMatchedElectricHeating/block2.svg" type="image/svg+xml">block diagram</object>
<p class="caption">Block diagram</p>
</div>
<p>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.</p>
<p>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.</p>
<p>I previously bought a Siemens Logo logic controller with analog output module and
a lighting dimmer I can control with it.</p>
<div class="figure align-center">
<img alt="SiemensLogo" src="https://www.thouters.be/img/SolarMatchedElectricHeating/siemenslogo.png" />
<p class="caption">Siemens logo logic controller</p>
</div>
<p>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.</p>
</div>
<div class="section" id="electrics">
<h2><a class="toc-backref" href="#id2">Electrics</a></h2>
<div class="figure align-center">
<img alt="SiemensLogo" src="https://www.thouters.be/img/SolarMatchedElectricHeating/testsetup.jpg" />
<p class="caption">Test setup (convection heater not visible)</p>
</div>
<div class="section" id="power-controller">
<h3><a class="toc-backref" href="#id3">Power controller</a></h3>
<p>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.</p>
<div class="figure align-center">
<img alt="Carlo" src="https://www.thouters.be/img/SolarMatchedElectricHeating/dimmer.png" />
<p class="caption">Carlo GAVAZZI power controller</p>
</div>
<p>The power controller has an analog voltage input as control signal.</p>
</div>
</div>
<div class="section" id="siemens-logo-aq-and-home-assistant">
<h2><a class="toc-backref" href="#id4">Siemens Logo AQ and Home assistant</a></h2>
<p>The simplest way to interface the logo with home assistant is by
enabling a modbus server in the Logo.</p>
<p>After doing so, the modbus register map can be accessed given an off-by-one is respected :-).</p>
<div class="figure align-center">
<img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/modbus-address-space.png" />
<p class="caption">Modbus Address space</p>
</div>
<div class="section" id="logo-sketch">
<h3><a class="toc-backref" href="#id5">Logo sketch</a></h3>
<p>The Logo runs a simple sketch that will continuously
set its analog output (AQ) to the value of an analog variable (AM)</p>
<div class="figure align-center">
<img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/logosketch.png" />
<p class="caption">Logo sketch</p>
</div>
</div>
<div class="section" id="modbus-hub-configuration">
<h3><a class="toc-backref" href="#id6">Modbus Hub configuration</a></h3>
<p>You need to configure the modbus device in the configuration.yaml's modbus section.</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"logo1"</span><span class="w"></span>
<span class="w"> </span><span class="nt">delay</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">5</span><span class="w"></span>
<span class="w"> </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">5</span><span class="w"></span>
<span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">tcp</span><span class="w"></span>
<span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="s">"192.168.2.28"</span><span class="w"></span>
<span class="w"> </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">502</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="input-number">
<h3><a class="toc-backref" href="#id7">Input Number</a></h3>
<p>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.</p>
<p>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.</p>
<div class="highlight"><pre><span></span><span class="nt">heatdump_level_variable</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">heatdump level</span><span class="w"></span>
<span class="w"> </span><span class="nt">initial</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">0</span><span class="w"></span>
<span class="w"> </span><span class="nt">min</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">0</span><span class="w"></span>
<span class="w"> </span><span class="nt">max</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">2000</span><span class="w"></span>
<span class="w"> </span><span class="nt">step</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1</span><span class="w"></span>
<span class="w"> </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">W</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="automation">
<h3><a class="toc-backref" href="#id8">Automation</a></h3>
<p>After setting up this automation, changing the input number slider
will change the Logo's analog output.</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">modbusupdate</span><span class="w"></span>
<span class="w"> </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s">"Push</span><span class="nv"> </span><span class="s">input_number</span><span class="nv"> </span><span class="s">value</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">modbus</span><span class="nv"> </span><span class="s">register"</span><span class="w"></span>
<span class="w"> </span><span class="nt">trigger</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">state</span><span class="w"></span>
<span class="w"> </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">input_number.heatdump_level_variable</span><span class="w"></span>
<span class="w"> </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[]</span><span class="w"></span>
<span class="w"> </span><span class="nt">action</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">modbus.write_register</span><span class="w"></span>
<span class="w"> </span><span class="nt">data</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">hub</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">logo1</span><span class="w"></span>
<span class="w"> </span><span class="nt">unit</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1</span><span class="w"></span>
<span class="w"> </span><span class="nt">address</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">528</span><span class="w"></span>
<span class="w"> </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">int(states('input_number.heatdump_level_variable')|int</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">1000/2000)</span><span class="nv"> </span><span class="s">}}"</span><span class="w"></span>
</pre></div>
</div>
</div>
<div class="section" id="power-optimizer">
<h2><a class="toc-backref" href="#id9">Power optimizer</a></h2>
<p>I'm very fed up with YAML automations in home assistant. I tried <a class="reference external" href="https://hacs-pyscript.readthedocs.io/en/latest/">pyscript</a> which
is very promising, but this time I wanted to give <a class="reference external" href="https://appdaemon.readthedocs.io/en/latest/">appdaemon</a> a try.</p>
<p>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.</p>
<p>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!</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">HelloWorld</span><span class="p">(</span><span class="n">hass</span><span class="o">.</span><span class="n">Hass</span><span class="p">):</span>
<span class="sd">""" Note: All units are watts """</span>
<span class="n">consumption</span> <span class="o">=</span> <span class="n">Decimal</span><span class="p">(</span><span class="s2">"0"</span><span class="p">)</span>
<span class="n">production</span> <span class="o">=</span> <span class="n">Decimal</span><span class="p">(</span><span class="s2">"0"</span><span class="p">)</span>
<span class="n">heater_max_power</span> <span class="o">=</span> <span class="n">Decimal</span><span class="p">(</span><span class="s2">"2000"</span><span class="p">)</span>
<span class="n">heater_min_power</span> <span class="o">=</span> <span class="n">Decimal</span><span class="p">(</span><span class="s2">"10"</span><span class="p">)</span>
<span class="n">current_heater_power</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">listen_state</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">on_measurement_update</span><span class="p">,</span> <span class="s2">"sensor.electricity_meter_power_consumption"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">listen_state</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">on_measurement_update</span><span class="p">,</span> <span class="s2">"sensor.electricity_meter_power_production"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">set_heater_power</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">watt_value</span><span class="p">):</span>
<span class="sd">""" Calculate and set the available heater power needed """</span>
<span class="n">watt_value_clipped</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">watt_value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">heater_max_power</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_heater</span><span class="p">(</span><span class="n">watt_value_clipped</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">set_heater</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_value</span><span class="p">(</span><span class="s2">"input_number.heatdump_level_variable"</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">calculate_next_heater_power</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">grid_power</span><span class="p">,</span> <span class="n">old_heater_power</span><span class="p">):</span>
<span class="sd">""" grid power: negative is consumption """</span>
<span class="c1"># don't let the value go negative</span>
<span class="n">heater_power</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">grid_power</span> <span class="o">+</span> <span class="n">old_heater_power</span><span class="p">)</span>
<span class="k">if</span> <span class="n">heater_power</span> <span class="o">>=</span> <span class="bp">self</span><span class="o">.</span><span class="n">heater_min_power</span><span class="p">:</span>
<span class="k">return</span> <span class="n">heater_power</span>
<span class="k">return</span> <span class="mi">0</span>
<span class="k">def</span> <span class="nf">on_measurement_update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">old</span><span class="p">,</span> <span class="n">new</span><span class="p">,</span> <span class="n">cb_args</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">entity</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">new</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="c1"># A very hackish way to add consumption and production :-(</span>
<span class="k">if</span> <span class="s2">"production"</span> <span class="ow">in</span> <span class="n">entity</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">production</span> <span class="o">=</span> <span class="n">Decimal</span><span class="p">(</span><span class="n">new</span><span class="p">)</span><span class="o">*</span><span class="mi">1000</span>
<span class="k">if</span> <span class="s2">"consumption"</span> <span class="ow">in</span> <span class="n">entity</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">consumption</span> <span class="o">=</span> <span class="n">Decimal</span><span class="p">(</span><span class="n">new</span><span class="p">)</span><span class="o">*</span><span class="mi">1000</span>
<span class="n">grid_power</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">production</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">consumption</span>
<span class="bp">self</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="sa">f</span><span class="s2">"power is </span><span class="si">{</span><span class="n">grid_power</span><span class="si">}</span><span class="s2"> "</span><span class="p">)</span>
<span class="n">heater_power</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">calculate_next_heater_power</span><span class="p">(</span><span class="n">grid_power</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">current_heater_power</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">current_heater_power</span> <span class="o">=</span> <span class="n">heater_power</span>
<span class="bp">self</span><span class="o">.</span><span class="n">set_heater_power</span><span class="p">(</span><span class="n">heater_power</span><span class="p">)</span>
</pre></div>
<div class="section" id="trial-run">
<h3><a class="toc-backref" href="#id10">Trial run</a></h3>
<p>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:</p>
<ul class="simple">
<li>I was doing live changes to the system at the time, so there are gaps and spikes.</li>
<li>The big blocks of grid consumption can be explained by the house's electrical convection heaters running to heat the house.</li>
<li>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).</li>
<li>The net grid usage apeared pretty stable at 0W at this point, the meter's kWh readings also stayed constant.</li>
</ul>
<div class="figure align-center">
<img alt="placeholder" src="https://www.thouters.be/img/SolarMatchedElectricHeating/history2.png" />
<p class="caption">History data during the experiment</p>
</div>
</div>
</div>
<div class="section" id="final-thoughts-on-all-of-this">
<h2><a class="toc-backref" href="#id11">Final thoughts on all of this</a></h2>
<div class="section" id="integration-into-an-existing-heating-system">
<h3><a class="toc-backref" href="#id12">Integration into an existing heating system</a></h3>
<p>To fit this into an existing heating system, a circuit like
this could be used:</p>
<div class="figure">
<object class="thlaaligncenter" data="https://www.thouters.be/img/SolarMatchedElectricHeating/hack.svg" type="image/svg+xml">integration idea</object>
<p class="caption">Conceptual schematic</p>
</div>
<p>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.</p>
<p>The second analog out is used to provide a 'full power' signal to the power
controller.</p>
<p>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.</p>
</div>
<div class="section" id="cost-considerations">
<h3><a class="toc-backref" href="#id13">Cost considerations</a></h3>
<p>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).</p>
<p>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.</p>
<p>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.</p>
<p>The required netfilter to minimize electro-magnetic interference also costs 110€.
Which brings the minimal BOM 250€. The filter also has a current draw of 7W 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.</p>
</div>
<div class="section" id="cost-benefit">
<h3><a class="toc-backref" href="#id14">Cost-benefit</a></h3>
<p>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.</p>
<p>For every kWh I immediately use to heat the house, I save over 0.25€. So imagine
a total setup cost of 300€ (I added 50€ there for a small Rittal enclosure and esphome producing 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.</p>
<p>Anyway, any situation will differ, so if you consider a similar aproach, enjoy the napkin math 🧐.</p>
</div>
</div>
Rainwater Pump and filter box2023-12-19T13:30:00+01:002023-12-19T13:30:00+01:00Thomas Langewouterstag:www.thouters.be,2023-12-19:/RainWaterPump.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPump/filterfront.jpg"/>
</div>
<p>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.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPump/filterfront.jpg" />
</div>
<p>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.</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title">Table of Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#pump-housing" id="id1">Pump housing</a></li>
<li><a class="reference internal" href="#pump" id="id2">Pump</a></li>
<li><a class="reference internal" href="#filter" id="id3">Filter</a></li>
<li><a class="reference internal" href="#tools" id="id4">Tools</a></li>
<li><a class="reference internal" href="#filter-routing-matrix" id="id5">Filter Routing matrix</a></li>
<li><a class="reference internal" href="#tracing" id="id6">Tracing</a></li>
<li><a class="reference internal" href="#leaky-filter-issues" id="id7">Leaky filter issues</a></li>
</ul>
</div>
<div class="section" id="pump-housing">
<h2><a class="toc-backref" href="#id1">Pump housing</a></h2>
<p>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.</p>
<div class="figure align-center">
<img alt="pump box" src="https://www.thouters.be/img/RainWaterPump/housing.jpg" />
</div>
<p>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.</p>
<p>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.</p>
</div>
<div class="section" id="pump">
<h2><a class="toc-backref" href="#id2">Pump</a></h2>
<p>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.</p>
<p>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.</p>
<div class="figure align-center">
<img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPump/pumptop.jpg" />
</div>
</div>
<div class="section" id="filter">
<h2><a class="toc-backref" href="#id3">Filter</a></h2>
<p>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.</p>
<div class="figure align-center">
<img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPump/filterfront.jpg" />
</div>
</div>
<div class="section" id="tools">
<h2><a class="toc-backref" href="#id4">Tools</a></h2>
<p>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.</p>
</div>
<div class="section" id="filter-routing-matrix">
<h2><a class="toc-backref" href="#id5">Filter Routing matrix</a></h2>
<p>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.</p>
<style> .whiten { background: rgba(255, 255, 255, 0.3); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} </style><div class="figure align-center">
<object class="whiten" data="https://www.thouters.be/img/RainWaterPump/pump.svg" type="image/svg+xml">pipestand</object>
</div>
<p>Inputs and outputs:</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">A:</th><td class="field-body">To the rainwater tank's floating oneway valve</td>
</tr>
<tr class="field"><th class="field-name">B:</th><td class="field-body">Coupling to connect a backup water tank during tank maintenance/cleaning</td>
</tr>
<tr class="field"><th class="field-name">C:</th><td class="field-body">Garden tap/hose on shed wall.</td>
</tr>
<tr class="field"><th class="field-name">D:</th><td class="field-body">Pressure line to house.</td>
</tr>
<tr class="field"><th class="field-name">E:</th><td class="field-body">freeze drain</td>
</tr>
</tbody>
</table>
<p>Valves:</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">1:</th><td class="field-body">Shutoff of rainwater tank</td>
</tr>
<tr class="field"><th class="field-name">2:</th><td class="field-body">Shutoff of maintenance supply</td>
</tr>
<tr class="field"><th class="field-name">3:</th><td class="field-body">Filter input shutoff</td>
</tr>
<tr class="field"><th class="field-name">4:</th><td class="field-body">Filter output shutoff</td>
</tr>
<tr class="field"><th class="field-name">5:</th><td class="field-body">House shutoff</td>
</tr>
<tr class="field"><th class="field-name">6:</th><td class="field-body">Unfiltered water to garden tap</td>
</tr>
<tr class="field"><th class="field-name">7:</th><td class="field-body">Filtered water to garden tap</td>
</tr>
<tr class="field"><th class="field-name">8:</th><td class="field-body">Garden tap winter shutoff</td>
</tr>
<tr class="field"><th class="field-name">9:</th><td class="field-body">Garden tap drain point</td>
</tr>
<tr class="field"><th class="field-name">10:</th><td class="field-body">Garden tap.</td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="tracing">
<h2><a class="toc-backref" href="#id6">Tracing</a></h2>
<p>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.</p>
</div>
<div class="section" id="leaky-filter-issues">
<h2><a class="toc-backref" href="#id7">Leaky filter issues</a></h2>
<p>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!</p>
<p>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...</p>
</div>
First steps with Rust on the stm322023-08-03T20:10:00+02:002023-08-03T20:10:00+02:00Thomas Langewouterstag:www.thouters.be,2023-08-03:/FirstStepsWithRustOnStm32.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="placeholder" src="https://www.thouters.be/img/FirstStepsWithRustOnStm32/bluepill.jpg"/>
<p class="caption">A stm32 based traffic light I built for fun (and my kids) years ago</p>
</div>
<p>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.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="placeholder" src="https://www.thouters.be/img/FirstStepsWithRustOnStm32/bluepill.jpg" />
<p class="caption">A stm32 based traffic light I built for fun (and my kids) years ago</p>
</div>
<p>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.</p>
<div class="section" id="hello-world-blinky">
<h2>Hello World blinky</h2>
<p>To start off simple, I followed instructions on <a class="reference external" href="https://github.com/stm32-rs/stm32f1xx-hal">https://github.com/stm32-rs/stm32f1xx-hal</a>
to build the blink example program.</p>
</div>
<div class="section" id="flashing-using-black-magic-probe">
<h2>Flashing Using Black magic probe</h2>
<div class="section" id="connecting-the-device">
<h3>Connecting the device</h3>
<p>To connect the STM32 black magic probe to the Device under test, the usual connections
are required:</p>
<ul class="simple">
<li>B8 = SWDCLK</li>
<li>B9 = SWDIO</li>
<li>Vcc = Vcc</li>
<li>GND = GND</li>
</ul>
</div>
<div class="section" id="installing-a-working-gdb">
<h3>Installing a working GDB</h3>
<p>To avoid hitting <a class="reference external" href="https://github.com/blackmagic-debug/blackmagic/issues/929">this compatibility bug</a> I hit with <cite>gdb-multiarch</cite>, I installed
the first older (8.3) GDB that came to my mind:</p>
<pre class="literal-block">
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
</pre>
<p>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.</p>
<pre class="literal-block">
thomas@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 <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(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 "file" 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::<impl cortex_m::peripheral::SYST>::has_wrapped (self=0x8002a3b <stm32f1xx_hal::timer::counter::SysCounterHz::wait+22>)
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::<impl cortex_m::peripheral::SYST>::has_wrapped (self=0x8002a3b <stm32f1xx_hal::timer::counter::SysCounterHz::wait+22>)
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: "/home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.7/src/peripheral/syst.rs", line number: 0, symbol: "???"
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: "/home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.7/src/peripheral/syst.rs", line number: 0, symbol: "???"
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();
(
</pre>
</div>
</div>
<div class="section" id="turning-it-into-a-traffic-light">
<h2>Turning it into a traffic light</h2>
<p>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.</p>
<p>For <a class="reference external" href="https://github.com/thouters/bluepill-verkeerslicht">my original traffic light gimmic</a>, 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.</p>
<p>To be continued...</p>
</div>
OpenPlank Proof Of Concept2023-01-11T20:10:00+01:002023-01-11T20:10:00+01:00Thomas Langewouterstag:www.thouters.be,2023-01-11:/OpenPlankPoc.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/poc-standup.jpg"/>
<p class="caption">The first (crude) prototype with sloppy veneer edges</p>
</div>
<p>In November I saw a cool device at an event.
The device was presented as an unobtrusive way to interact with technology.
Unlike usual 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.</p>
<p>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.</p>
<p>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.</p>
<p>I call it the OpenPlank. Check it out...</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/poc-standup.jpg" />
<p class="caption">The first (crude) prototype with sloppy veneer edges</p>
</div>
<p>In November I saw a cool device at an event.
The device was presented as an unobtrusive way to interact with technology.
Unlike usual 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.</p>
<p>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.</p>
<p>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.</p>
<p>I call it the OpenPlank. Check it out...</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title">Table of Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#led-display" id="id1">LED Display</a><ul>
<li><a class="reference internal" href="#hub75-interface" id="id2">HUB75 interface</a></li>
</ul>
</li>
<li><a class="reference internal" href="#input" id="id3">Input</a><ul>
<li><a class="reference internal" href="#touch-layer-circuit-board" id="id4">Touch layer circuit board</a></li>
<li><a class="reference internal" href="#touch-digitizer" id="id5">Touch digitizer</a></li>
<li><a class="reference internal" href="#device-input-backup-plan" id="id6">Device input backup plan</a></li>
</ul>
</li>
<li><a class="reference internal" href="#chassis" id="id7">Chassis</a><ul>
<li><a class="reference internal" href="#double-size-chassis" id="id8">Double size chassis</a></li>
<li><a class="reference internal" href="#mechanical-assembly" id="id9">Mechanical assembly</a></li>
</ul>
</li>
<li><a class="reference internal" href="#user-interface" id="id10">User Interface</a><ul>
<li><a class="reference internal" href="#simulator" id="id11">Simulator</a></li>
</ul>
</li>
<li><a class="reference internal" href="#circuit" id="id12">Circuit</a></li>
<li><a class="reference internal" href="#demo-video" id="id13">Demo video</a></li>
<li><a class="reference internal" href="#design-files" id="id14">Design Files</a><ul>
<li><a class="reference internal" href="#poc-touch-test-demo" id="id15">POC touch test/demo</a></li>
<li><a class="reference internal" href="#pc-simulator" id="id16">PC Simulator</a></li>
<li><a class="reference internal" href="#chassis-design-files" id="id17">Chassis design files</a></li>
</ul>
</li>
<li><a class="reference internal" href="#work-in-progress" id="id18">Work in progress</a></li>
</ul>
</div>
<div class="section" id="led-display">
<h2><a class="toc-backref" href="#id1">LED Display</a></h2>
<p>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.</p>
<div class="figure align-center">
<img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/hub75panels.jpg" />
</div>
<p>I opted for a P2.5 display since that would work with the idea I had for the touch input.</p>
<p>These displays can be daisy-chained; they can be connected together and driven as
a single larger display.</p>
<div class="section" id="hub75-interface">
<h3><a class="toc-backref" href="#id2">HUB75 interface</a></h3>
<p>I found and used the <a class="reference external" href="https://github.com/2dom/PxMatrix">PxMatrix</a> 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.
<a class="reference external" href="https://www.sparkfun.com/news/2650">For more information about HUB75 display matrices, you can read this sparkfun article</a> or search the web.</p>
</div>
</div>
<div class="section" id="input">
<h2><a class="toc-backref" href="#id3">Input</a></h2>
<p>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.</p>
<p>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.</p>
<div class="figure align-center">
<img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/zeef.jpg" />
</div>
<p>This worked, so I quickly hacked together a design in KiCAD.</p>
<div class="section" id="touch-layer-circuit-board">
<h3><a class="toc-backref" href="#id4">Touch layer circuit board</a></h3>
<p>I made this design simple and quick, its main features are:</p>
<ul class="simple">
<li>The 1.2mm holes are aligned with the LED's</li>
<li>It has a 10mm bezel around the display that can be used as glue surface.</li>
<li>There is no bezel on right side so two boards can be put next to each other seamlessly</li>
<li>there are 2.54mm pitch SMT connectors on back to plug the MPR121 boards onto.</li>
</ul>
<div class="figure align-center">
<img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/touchlayerv1.png" />
</div>
<p>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.</p>
<div class="figure align-center">
<img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/pcboverlayed.jpg" />
</div>
</div>
<div class="section" id="touch-digitizer">
<h3><a class="toc-backref" href="#id5">Touch digitizer</a></h3>
<p>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.</p>
<p>As an alternative I found a software library to <a class="reference external" href="https://github.com/HCI-Lab-Saarland/MultiTouchKit">do touch using only a simple analog mux</a>
(documentation for it <a class="reference external" href="https://github.com/HCI-Lab-Saarland/MultiTouchKitDoc">can be found here</a>). This has some special microcontroller
requirements, but could still be an option.</p>
</div>
<div class="section" id="device-input-backup-plan">
<h3><a class="toc-backref" href="#id6">Device input backup plan</a></h3>
<p>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.</p>
</div>
</div>
<div class="section" id="chassis">
<h2><a class="toc-backref" href="#id7">Chassis</a></h2>
<p>As initial prototype I designed a chassis <em>(plural chassis /-iz/ from French châssis,
the load-bearing framework of an artificial object)</em> that holds a single display.
The chassis has:</p>
<ul class="simple">
<li>a 1mm bezel around the touch PCB which keeps it in place for alignment when glueing.</li>
<li>10mm glue contact bezel with the touch PCB</li>
<li>openings for the touch PCB's connectors.</li>
<li>slots to insert discs behind the LED board to keep it in place but easy to remove.</li>
</ul>
<div class="figure align-center">
<img alt="chassis prototype" src="https://www.thouters.be/img/OpenPlankPoc/chassis1.png" />
</div>
<div class="section" id="double-size-chassis">
<h3><a class="toc-backref" href="#id8">Double size chassis</a></h3>
<p>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.</p>
<div class="figure align-center">
<img alt="double sized chassis prototype" src="https://www.thouters.be/img/OpenPlankPoc/doublesize.png" />
</div>
</div>
<div class="section" id="mechanical-assembly">
<h3><a class="toc-backref" href="#id9">Mechanical assembly</a></h3>
<p>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.</p>
<div class="figure align-center">
<img alt="back" src="https://www.thouters.be/img/OpenPlankPoc/clamped.jpg" />
</div>
<p>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.</p>
<p>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).</p>
<p>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.</p>
<p>After glueing the PCB onto the chassis body, the wood veneer (I used
<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">Cricut maple veneer</a>) 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.</p>
</div>
</div>
<div class="section" id="user-interface">
<h2><a class="toc-backref" href="#id10">User Interface</a></h2>
<p>I plan to use <a class="reference external" href="https://lvgl.io/">LGVL - Light and Versatile Graphics Library</a> to make
the user interface as generic as possible and to reuse things like swipe detection and widgets.</p>
<p>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.</p>
<p>I found a nice <a class="reference external" href="https://maldus512.medium.com/porting-littlevgl-for-a-monochrome-display-6c7be58851ce">article on using LVGL with monochrome displays</a> which makes
it look easy.</p>
<div class="section" id="simulator">
<h3><a class="toc-backref" href="#id11">Simulator</a></h3>
<p>While waiting for parts, I started working on a PC simulator
which renders the LVGL framebuffer pixels on an SDL surface as round pixels.</p>
<p>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.</p>
<p>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.</p>
<div class="figure align-center">
<img alt="led matrix" src="https://www.thouters.be/img/OpenPlankPoc/sim2.png" />
</div>
</div>
</div>
<div class="section" id="circuit">
<h2><a class="toc-backref" href="#id12">Circuit</a></h2>
<p>This circuit is based on the PxMatrix wiring, along
with a simple I2C bus for the MPR121 touch modules.</p>
<div class="figure align-center">
<img alt="schematic" src="https://www.thouters.be/img/OpenPlankPoc/schematicv1.jpg" />
</div>
<div class="figure align-center">
<img alt="back" src="https://www.thouters.be/img/OpenPlankPoc/back1.jpg" />
</div>
</div>
<div class="section" id="demo-video">
<h2><a class="toc-backref" href="#id13">Demo video</a></h2>
<p>As you can see, input works! It's coarse but functional.</p>
<span class="videobox">
<video width='100%' height='480' preload='none' controls poster='https://www.thouters.be/img/OpenPlankPoc/demo-snap-cropped.png'>
<source src='https://www.thouters.be/img/OpenPlankPoc/demo.mp4' type='video/mp4; '/>
</video>
</span></div>
<div class="section" id="design-files">
<h2><a class="toc-backref" href="#id14">Design Files</a></h2>
<div class="section" id="poc-touch-test-demo">
<h3><a class="toc-backref" href="#id15">POC touch test/demo</a></h3>
<p>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.</p>
<p><a class="reference external" href="https://github.com/thouters/pio-openplank-t7mini-input-poc">https://github.com/thouters/pio-openplank-t7mini-input-poc</a></p>
</div>
<div class="section" id="pc-simulator">
<h3><a class="toc-backref" href="#id16">PC Simulator</a></h3>
<p>This platformio project uses a hacked version of the <a class="reference external" href="https://github.com/lvgl/lv_drivers/">lv_drivers</a> SDL backend
for LVGL to render the framebuffer pixels as 10x10px circles on a background image of a wooden plank.</p>
<p><a class="reference external" href="https://github.com/thouters/pio_lv_ledmatrixemu">https://github.com/thouters/pio_lv_ledmatrixemu</a></p>
</div>
<div class="section" id="chassis-design-files">
<h3><a class="toc-backref" href="#id17">Chassis design files</a></h3>
<p>This OpenSCAD code models the chassis of the OpenPlank</p>
<p><a class="reference external" href="https://github.com/thouters/openplank-chassis">https://github.com/thouters/openplank-chassis</a></p>
<p>Chassis STL object</p>
<p><a class="reference external" href="https://www.printables.com/model/373360-openplank-mk0-12-chassis">https://www.printables.com/model/373360-openplank-mk0-12-chassis</a></p>
</div>
</div>
<div class="section" id="work-in-progress">
<h2><a class="toc-backref" href="#id18">Work in progress</a></h2>
<p>You can follow me on Mastodon to read updates on this project.</p>
</div>
Rainwater Tank Plumbing2022-09-11T12:15:00+02:002022-09-11T12:15:00+02:00Thomas Langewouterstag:www.thouters.be,2022-09-11:/RainWaterPlumbing.html<!-- vim:ft=rst:spell:spelllang=en -->
<style> .whiten { background: rgba(255, 255, 255, 0.3); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} </style><div class="figure align-center">
<object class="whiten" data="https://www.thouters.be/img/RainWaterPlumbing/tank-annotated.svg" type="image/svg+xml">pipestand</object>
</div>
<p>After having the rainwater tank installed and connected to the roof,
there was still quite some plumbing missing. The filter and calmed inlet
add up to hundreds of euros. In my case the way the tubes were
connected to the tank made off the shelve filters unusable, so I opted
to DIY everything.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<style> .whiten { background: rgba(255, 255, 255, 0.3); max-width: 90%; display: block; margin-left: auto; margin-right: auto; border-radius: 0.3em;} </style><div class="figure align-center">
<object class="whiten" data="https://www.thouters.be/img/RainWaterPlumbing/tank-annotated.svg" type="image/svg+xml">pipestand</object>
</div>
<p>After having the rainwater tank installed and connected to the roof,
there was still quite some plumbing missing. The filter and calmed inlet
add up to hundreds of euros. In my case the way the tubes were
connected to the tank made off the shelve filters unusable, so I opted
to DIY everything.</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title">Table of Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#general-impression" id="id1">General impression</a></li>
<li><a class="reference internal" href="#dirt-and-insect-filter" id="id2">dirt and insect filter</a></li>
<li><a class="reference internal" href="#calmed-inlet" id="id3">Calmed inlet</a></li>
<li><a class="reference internal" href="#pump" id="id4">Pump</a></li>
<li><a class="reference internal" href="#distribution-filter" id="id5">Distribution filter</a></li>
<li><a class="reference internal" href="#room-for-improvement" id="id6">Room for improvement</a></li>
</ul>
</div>
<div class="section" id="general-impression">
<h2><a class="toc-backref" href="#id1">General impression</a></h2>
<p>You can ignore the red tube sticking out of the overflow, it's a part I cut off and
put there out of the way.</p>
<div class="figure align-center">
<img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPlumbing/somedirt.jpg" />
</div>
<p>The garden hose to add tapwater to the tank is visible.</p>
</div>
<div class="section" id="dirt-and-insect-filter">
<h2><a class="toc-backref" href="#id2">dirt and insect filter</a></h2>
<p>Since I don't like long print times and don't mind using the dremel, drill and handsaw, I started from an existing piece of PVC from the hardware store.
I used a rainwater pipe collector and attached a 110mm pipe end plug. I used the dremel to create an opening for the
water to flow in.</p>
<p>The filter assembly sits on the vertical pipe to the calmed inlet.</p>
<div class="figure align-center">
<img alt="filter with some dirt" src="https://www.thouters.be/img/RainWaterPlumbing/filterbody.jpg" />
</div>
<p>I designed a filter screen to put into the body, wrapped with nylon stocking it filters out dirt from entering
the tank together with the water.</p>
<div class="figure align-center">
<img alt="pipestand" src="https://www.thouters.be/img/RainWaterPlumbing/filter.png" />
</div>
<p>Additionally, the top of the filter assembly top should be covered with a filter too to prevent
insects from entering the tank.</p>
<p>I also created a 86mm to 80mm pipe adapter.</p>
<p>As for future improvements: ideally the grid would be sloping downwards from the inlet, so dirt would more easily flow away from the inlet.</p>
</div>
<div class="section" id="calmed-inlet">
<h2><a class="toc-backref" href="#id3">Calmed inlet</a></h2>
<p>The purpose of the calmed inlet is twofold:</p>
<ul class="simple">
<li>Water should be added at the botom of the tank to distribute oxygen evenly</li>
<li>The sediment settling at the bottom of the tank should not be disturbed with water currents, so there should be a barrier around the inlet.</li>
</ul>
<div class="figure align-center">
<img alt="pipestand" src="https://www.thouters.be/img/RainWaterPlumbing/pipestand.png" />
</div>
<p>This stand allows the tube to rest on the bottom, standing in a shortened (cut off) bucket.</p>
<div class="figure align-center">
<img alt="pipestand" src="https://www.thouters.be/img/RainWaterPlumbing/pipestand-photo.jpg" />
</div>
</div>
<div class="section" id="pump">
<h2><a class="toc-backref" href="#id4">Pump</a></h2>
<p>I bought a DAB Jetinox 82 with control-d pressure controller. This will be installed
in the garden shed and will have to be insulated and made frost-proof.</p>
<p>I'm considering if circulating water back to the tank would suffice to keep the
pump up to temperature. I bought a flow sensor with pt100 temperature sensor
in the line I can use to sample the temperature of the pump. I will add
tracing to be sure.</p>
</div>
<div class="section" id="distribution-filter">
<h2><a class="toc-backref" href="#id5">Distribution filter</a></h2>
<p>I bought a Honeywell honeywell ff60 triplex filter, this is to be installed inside
since the pump is low to the floor and it will be easier to service the filter inside.</p>
</div>
<div class="section" id="room-for-improvement">
<h2><a class="toc-backref" href="#id6">Room for improvement</a></h2>
<p>I plan to add a float level switch in the filter assembly to signal filter clogs.</p>
<p>My setup is missing a first flush diverter. This would have had to be designed in, it still can if the garden shed roof is ever connected.</p>
</div>
Rainwatertank Level Sensor2022-07-17T22:15:00+02:002022-07-17T22:15:00+02:00Thomas Langewouterstag:www.thouters.be,2022-07-17:/RainWaterTankLevelSensor.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="rain water level sensor principle" src="https://www.thouters.be/img/RainWaterTankLevelSensor/complete-short.jpg"/>
<p class="caption">Mechanical sensor parts assembled with a very short sensor tube to fit the picture.</p>
</div>
<p>This project idea scored 3/3 stars, combining electronics, water and
creating something from scratch.
I couldn't resist building my own <em>reed switch array water level sensor</em>.</p>
<p>This kind of sensor has a floating part moving up and down a sensor tube.</p>
<p>It's not an off the shelve solution to sensing water levels,
buying a water pressure sensor or an ultrasonic level sensor would probably
be easier, but DIY is fun!</p>
<p>My goal was to do some electronics and 3D design, 3D print, experiment and
get a circuit board fabricated.
I'd build a sensor using easily obtainable parts whenever possible.
The electronics should be easy to build at home.</p>
<p>I will use it to monitor and log the water level in Home Assistant.
Getting to automate the adding of a minimal amount of tap water
to the tank during the summer will be a useful feature.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="rain water level sensor principle" src="https://www.thouters.be/img/RainWaterTankLevelSensor/complete-short.jpg" />
<p class="caption">Mechanical sensor parts assembled with a very short sensor tube to fit the picture.</p>
</div>
<p>This project idea scored 3/3 stars, combining electronics, water and
creating something from scratch.
I couldn't resist building my own <em>reed switch array water level sensor</em>.</p>
<p>This kind of sensor has a floating part moving up and down a sensor tube.</p>
<p>It's not an off the shelve solution to sensing water levels,
buying a water pressure sensor or an ultrasonic level sensor would probably
be easier, but DIY is fun!</p>
<p>My goal was to do some electronics and 3D design, 3D print, experiment and
get a circuit board fabricated.
I'd build a sensor using easily obtainable parts whenever possible.
The electronics should be easy to build at home.</p>
<p>I will use it to monitor and log the water level in Home Assistant.
Getting to automate the adding of a minimal amount of tap water
to the tank during the summer will be a useful feature.</p>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/completed.jpg" />
<p class="caption">completed sensor</p>
</div>
<p>Other design goals I came up with include:</p>
<ul class="simple">
<li>The working range of the water level shall be between 10cm and 1m50.</li>
<li>I will use as many components as possible that I have on hand in my workshop.</li>
<li>The sensor shall output a signal at all times when powered.</li>
<li>There shall be no programmable electronics in the sensor.</li>
<li>Lean towards using industrial interface standards.</li>
</ul>
<p>So I ordered some reed magnet switches and started prototyping...</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title">Table of Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#principle" id="id1">Principle</a></li>
<li><a class="reference internal" href="#reed-chain-configuration" id="id2">Reed chain configuration</a><ul>
<li><a class="reference internal" href="#three-terminal-variable-resistor" id="id3">Three terminal variable resistor</a></li>
<li><a class="reference internal" href="#two-terminal-variable-resistor" id="id4">Two terminal variable resistor</a></li>
</ul>
</li>
<li><a class="reference internal" href="#electronics" id="id5">Electronics</a><ul>
<li><a class="reference internal" href="#sensor-interface" id="id6">Sensor interface</a></li>
<li><a class="reference internal" href="#design-parameters" id="id7">Design parameters</a></li>
<li><a class="reference internal" href="#possible-circuits" id="id8">Possible circuits</a></li>
<li><a class="reference internal" href="#my-circuit-operation" id="id9">My circuit: operation</a></li>
</ul>
</li>
<li><a class="reference internal" href="#mechanics" id="id10">Mechanics</a><ul>
<li><a class="reference internal" href="#reed-ladder" id="id11">Reed ladder</a></li>
<li><a class="reference internal" href="#enclosure" id="id12">Enclosure</a></li>
<li><a class="reference internal" href="#floater" id="id13">Floater</a></li>
<li><a class="reference internal" href="#stand" id="id14">stand</a></li>
</ul>
</li>
<li><a class="reference internal" href="#reed-ladder-assembly" id="id15">Reed ladder assembly</a><ul>
<li><a class="reference internal" href="#d-printed-fixture-preparation" id="id16">3D printed fixture preparation</a></li>
<li><a class="reference internal" href="#component-preparation" id="id17">component preparation</a></li>
<li><a class="reference internal" href="#soldering" id="id18">soldering</a></li>
<li><a class="reference internal" href="#connecting-the-fixtures" id="id19">connecting the fixtures</a></li>
</ul>
</li>
<li><a class="reference internal" href="#final-assembly" id="id20">Final assembly</a></li>
<li><a class="reference internal" href="#installation" id="id21">Installation</a></li>
<li><a class="reference internal" href="#initial-home-assistant-integration" id="id22">Initial Home assistant integration</a></li>
<li><a class="reference internal" href="#long-term-home-assistant-integration" id="id23">Long term Home assistant integration</a></li>
</ul>
</div>
<div class="section" id="principle">
<h2><a class="toc-backref" href="#id1">Principle</a></h2>
<p>A reed chain level sensor is a large sliding variable resistor really.
However, instead of acting continuous like a potentiometer, its resistance can only
change by a constant step each time, one for each reed switch in it.</p>
<div class="figure align-center">
<img alt="rain water level sensor principle" src="https://www.thouters.be/img/RainWaterTankLevelSensor/mechpoc1.jpg" />
<p class="caption">Water level sensor prototype with exposed reed chain</p>
</div>
<p>The reed chain level sensor separates electronics from the liquid
environment by enclosing them in a tube. The reed switches are operated
via a magnet that can slide across the tube.</p>
<div class="figure align-center">
<img alt="reed switch level activation" src="https://www.thouters.be/img/RainWaterTankLevelSensor/levelswitch-activation.jpg" />
<p class="caption">Sensing a one dimensional position with a reed switch</p>
</div>
<p>To have a continuous output signal, I need to have at least one of the reed switches
closed when the floater is around the tube. This means the choice of reed switch
spacing and magnet field strength will be closing two switches when the floater is
crossing over.</p>
<p>Closing at least one reed switch is no issue using neodymium magnets.</p>
<p>The circuit has to be designed in a way that allows two switches to be closed at the same
time.</p>
<p>If we configure the reed chain as a multi-resistor voltage divider of which each reed switch
couples one of the nodes to an output, we would be shorting out a resistor.
(However, shorting out a relatively small piece of the chain could be acceptable)</p>
<p>So I considered two possible chain configurations.</p>
<p>Another thing to keep in mind is hysteresis, when the magnet moves across the
shaft, the reed switch will return to open on a point lower than it closed
close it since the magnetic circuit will require less magnetic flux to
hold than to close. This will decrease precision with regular rainfall.</p>
</div>
<div class="section" id="reed-chain-configuration">
<h2><a class="toc-backref" href="#id2">Reed chain configuration</a></h2>
<div class="section" id="three-terminal-variable-resistor">
<h3><a class="toc-backref" href="#id3">Three terminal variable resistor</a></h3>
<p>If we apply a voltage over a resistor chain with reed switches on each
junction, we get a voltage representing the magnet's relative position
at the reed switch normally open output.</p>
<p>If we connect the reed switches together, we can sample this position.</p>
<p>Since one of my design goals was to have at least one reed activated,
there will be overlap. To avoid shorting out a resistor and increasing precision,
we can interleave the reed switch connections to two outputs.</p>
<div class="figure align-center">
<img alt="two terminal" src="https://www.thouters.be/img/RainWaterTankLevelSensor/threeterminal-ladder.png" />
</div>
<p>Since this implies a more complicated reed chain and more complicated electronics,
I moved on to investigate a two terminal variable resistor.</p>
</div>
<div class="section" id="two-terminal-variable-resistor">
<h3><a class="toc-backref" href="#id4">Two terminal variable resistor</a></h3>
<p>This is a very simple setup that will output a resistance between 0 ohm and
the series resistance of all resistors.</p>
<div class="figure align-center">
<img alt="two terminal" src="https://www.thouters.be/img/RainWaterTankLevelSensor/twoterminal-ladder.png" />
</div>
</div>
</div>
<div class="section" id="electronics">
<h2><a class="toc-backref" href="#id5">Electronics</a></h2>
<div class="section" id="sensor-interface">
<h3><a class="toc-backref" href="#id6">Sensor interface</a></h3>
<p>I decided to design a three-wire 4-20mA current loop converter with a current sourcing output.</p>
<p>This is an industry standard interface for sensors that has been around for many decades.
A 3-wire sensor is connected via three wires and the nature of the constant current output signal
compensates for voltage drops across the wires and junctions along the factory floor.</p>
<p>It's fairly easy to understand. To use a three wire current loop sensor:</p>
<ul class="simple">
<li>You feed the device a supply voltage over it's supply wire and ground wire</li>
<li>The device outputs a current between its output pin and its ground wire.</li>
</ul>
<div class="figure align-center">
<img alt="two terminal" src="https://www.thouters.be/img/RainWaterTankLevelSensor/principle1.png" />
</div>
<p>This makes it easy to interface with an ADC that has a common ground with the device,
The constant current signal can be converted to a constant voltage by
adding a resistor (Rsense). The value of the resistor determines the voltage range
you will get. To convert the maximum output current of 20mA to a 3v3 signal,
you can use ohm's law to find the resistor to use; R=U/I = 3.3/0.02 = 165 Ohm.</p>
</div>
<div class="section" id="design-parameters">
<h3><a class="toc-backref" href="#id7">Design parameters</a></h3>
<p>My sensor will output 4-20mA. This means the working range of 16mA
will need to be divided over a number of reed switches.
I figured 40 switches over the 1.5 meters would be a workable accuracy,
this number will always a trade of between</p>
<ul class="simple">
<li>Having build and solder N resistor+reed switch steps by hand</li>
<li>Resistor tolerances of 1% making too small steps useless.</li>
<li>combining resistors of preferred numbers series (see <a class="reference external" href="https://en.wikipedia.org/wiki/E_series_of_preferred_numbers">E-series</a> values) to closely match calculated values</li>
</ul>
</div>
<div class="section" id="possible-circuits">
<h3><a class="toc-backref" href="#id8">Possible circuits</a></h3>
<p>To realize a circuit with a constant current output I considered a simple zener diode + common emitter (transistor)
current source as well as using a three terminal voltage regulator like a 78L05. The former's would
be sensitive to temperature changes and in the latter the quiescence current would complicate calculations.</p>
<p>Since I had TL431 voltage references (on reel) that can be used as accurate 2.5V shunting voltage regulators,
I opted to use those instead.</p>
<div class="figure align-center">
<img alt="TL431 + BC547 current source" src="https://www.thouters.be/img/RainWaterTankLevelSensor/tl431-bjt.png" />
</div>
<p>If I would use the TL431 as current source with my reed level sensor variable resistor,
2.5V at 16mA would mean the full scale of the resistor ladder would be 2.5V/0.016A = 156.25 Ohms.
This low resistance would have to be divided up into even lower resistors in a ladder.
That would be inconvenient (However I think I could work around this if I were to feed the TL431's vref feedback loop via a voltage divider.)</p>
<p>The main drawback with this circuit is that the resistance to current conversion would not be linear,
since current = voltage / resistance and the voltage is of course constant.</p>
</div>
<div class="section" id="my-circuit-operation">
<h3><a class="toc-backref" href="#id9">My circuit: operation</a></h3>
<p>To turn the reed ladder's resistance into a linear current, I added an intermediate step.</p>
<div class="figure align-center">
<img alt="two terminal" src="https://www.thouters.be/img/RainWaterTankLevelSensor/principle2.png" />
</div>
<p>The TL431 voltage reference U1 is turned into a fixed current source by adding a transistor Q1.
U1 will sink the right amount of current to have Q1's emitter pin be at 2.5V.</p>
<p>The fact that The voltage over R1+R6 will be 2.5V means the current through it will be
I=U/R = 2.5/1.25k = 2mA.</p>
<p>I'll create a transconductance amplifier, which is a current sourcing current-source controlled by
the voltage drop over R2 + R5.</p>
<p>R5 will set the minimum output current (4mA) of the circuit by providing a minimum voltage
for the transconductance amplifier formed by U2A. U2A will mirror the voltage over R2+R5 over R4,
(the significant part of) the current going through R4 will also flow to the circuit output,
which will make the circuit output behave as a current source.</p>
<div class="figure align-center">
<img alt="two terminal" src="https://www.thouters.be/img/RainWaterTankLevelSensor/three-wire-source.png" />
</div>
<p>One design boundary to consider is the opamp can ony operate a few volts from its power supply rail.
Since the transconductance amplifier configuration is working against the positive supply rail,
I used the second opamp to create a voltage supply rail that is only a 2/3 fraction of Vin.
This only costs an extra transistor since the LM358 has two opamps in the package.</p>
<div class="figure align-center">
<img alt="voltage source" src="https://www.thouters.be/img/RainWaterTankLevelSensor/three-wire-source-vsource.png" />
<p class="caption">using an opamp to create a voltage lower than Vin.</p>
</div>
<p>After prototyping the circuit on perfboard, it was easily turned into
a simple PCB and exported with KiCAD to something JLCPCB can work with.</p>
<div class="figure align-center">
<img alt="voltage source" src="https://www.thouters.be/img/RainWaterTankLevelSensor/pcbv1-x.png" />
<p class="caption">first revision PCB</p>
</div>
<p>You can find the design files <a class="reference external" href="https://github.com/thouters/resistance_to_currentloop">in my resistance_to_currentloop github repository</a></p>
</div>
</div>
<div class="section" id="mechanics">
<h2><a class="toc-backref" href="#id10">Mechanics</a></h2>
<p>I wanted to use items from the hardware store as well as 3d printed items.</p>
<div class="section" id="reed-ladder">
<h3><a class="toc-backref" href="#id11">Reed ladder</a></h3>
<p>The reed ladder circuit is hand soldered and supported by 3D printed fixtures:</p>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/capsule.png" />
</div>
<p>The fixtures provide mechanical support to the reed switches and
resistors, which are connected with solder joints.</p>
<p>Additionally:</p>
<ul class="simple">
<li>they can be clicked together, allowing the chain to be assembled in modules</li>
<li>they make soldering easy by holding the reed switches into place</li>
</ul>
</div>
<div class="section" id="enclosure">
<h3><a class="toc-backref" href="#id12">Enclosure</a></h3>
<p>I use 16mm (outer diameter) PVC electricity tubing. This means the fixtures need to fit
into the 13mm (inner diameter) tube. The PCB with electronics also has to fit the tube,
hence the slim form factor.</p>
<p>To make a watertight seal between the wire and the tube, I use grommets.
A M25 grommet can clamp onto the tube, a smaller one onto the cable.
I designed a cylinder with tapered M25 and M12 holes on the ends.
The cylinder has metric thread already in the design, and the grommets screw right in.</p>
<div class="figure align-center">
<img alt="grommet joiner" src="https://www.thouters.be/img/RainWaterTankLevelSensor/joiner.png" />
<p class="caption">Grommet joiner</p>
</div>
<p>The bottom of the tube is supposed to be glued to an end cap with PVC glue,
but these are not widely available, so I'll have to improvise.</p>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/mechpoc3.jpg" />
<p class="caption">Assembled grommet joiner</p>
</div>
<p>The mechanical design files can be found <a class="reference external" href="https://github.com/thouters/reedlevelsensor">in my reedlevelsensor github repository</a></p>
</div>
<div class="section" id="floater">
<h3><a class="toc-backref" href="#id13">Floater</a></h3>
<p>Instead of using a sphere shaped floater, I opted for a sphere cut in half so the floater would be faster to print.
The top side has a cut-out for two ring shaped neodymium magnets.</p>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/floaterrender2.png" />
<p class="caption">A render of the floater</p>
</div>
</div>
<div class="section" id="stand">
<h3><a class="toc-backref" href="#id14">stand</a></h3>
<p>I created a simple stand that helps the sensor stand upright. This makes
testing easy and allows the sensor to be pulled out and placed back into
the tank easily.</p>
</div>
</div>
<div class="section" id="reed-ladder-assembly">
<h2><a class="toc-backref" href="#id15">Reed ladder assembly</a></h2>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/chainpart.jpg" />
<p class="caption">part of the resistor chain.</p>
</div>
<div class="section" id="d-printed-fixture-preparation">
<h3><a class="toc-backref" href="#id16">3D printed fixture preparation</a></h3>
<ul class="simple">
<li>remove any overhangs</li>
<li>remove stringing</li>
<li>remove hangovers in reed openings</li>
</ul>
</div>
<div class="section" id="component-preparation">
<h3><a class="toc-backref" href="#id17">component preparation</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://www.aliexpress.com/item/4000773848015.html">reed switches (Aliexpress)</a>: cut the legs in half</li>
<li><a class="reference external" href="https://www.aliexpress.com/item/1005001627995396.html">resistors 20 Ohm 1% (Aliexpress)</a>: cut the leads so each side has a length of resistor as leg</li>
<li>Tinned solid core copper wire</li>
</ul>
<p>I used tinned solid core copper wire I got years ago
from a scrapyard that used to be part of a wire harness.
After stripping off the insulation I straightened the wire by
giving it a yank with two pliers (a bank vise, plier and hammer also work).</p>
</div>
<div class="section" id="soldering">
<h3><a class="toc-backref" href="#id18">soldering</a></h3>
<p>First a little terminology</p>
<ul class="simple">
<li>common wire side; wire that all the reeds will short to</li>
<li>signal wire: wire side with the series resistors</li>
</ul>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/stappen.jpg" />
</div>
<p>① Cut the wire to length, and inserted it into the holes
they should stick out a bit (5mm) at each side. For now I'll call these
wires running both through the fixture the bus bars.</p>
<p>② Next I insert the reed switches, so that they hang on top of the bus bars.</p>
<p>③ Solder both ends of the reeds to the bus bars.</p>
<p>④ On the signal wire side,</p>
<ul class="simple">
<li>cut the wire halfway the reed.</li>
<li>Cut the signal wire downstream for a bit over the length of a resistor to create a gap to solder the resistor into the signal wire chain.</li>
<li>Put some solder on the ends of the cuts.</li>
<li>Solder the resistors in the openings.</li>
</ul>
<p>⑤ Clip the common and signal wire so there are no conductors in the holes
at the opposite sides of each module.</p>
</div>
<div class="section" id="connecting-the-fixtures">
<h3><a class="toc-backref" href="#id19">connecting the fixtures</a></h3>
<p>After soldering all the fixtures, they can be clicked together,
taking care that all reeds are in the same orientation and the signal side
and common wire sides align.</p>
<p>When clicked together, a small piece of wire is inserted and soldered
into the common and signal wires.</p>
</div>
</div>
<div class="section" id="final-assembly">
<h2><a class="toc-backref" href="#id20">Final assembly</a></h2>
<p>The complete assembly is composed of:</p>
<ul class="simple">
<li>16mm electricity tube</li>
<li>end cap to plug the tube</li>
<li>reed fixtures, interleaved flipped and not flipped versions, chained together</li>
<li>an empty fixture to provide some vertical offset and short together the bus.</li>
</ul>
<p>Care should be taken to align the fixtures correctly:</p>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/connections.png" />
<p class="caption">do, don't</p>
</div>
<p>To achieve this, the fixture has two versions, the reed switches are 180degrees
rotated.</p>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/endpart.jpg" />
<p class="caption">electronics at the end of the tube</p>
</div>
</div>
<div class="section" id="installation">
<h2><a class="toc-backref" href="#id21">Installation</a></h2>
<p>I made a terminal block that fits the tube, the metal parts are
used from damaged pcb terminal blocks.</p>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/connected.jpg" />
<p class="caption">sensor connected to cable</p>
</div>
<p>After closing the grommets the tube is lowered into the tank:</p>
<div class="figure align-center">
<img alt="tube with train" src="https://www.thouters.be/img/RainWaterTankLevelSensor/installed.jpg" />
<p class="caption">the sensor installed in the tank</p>
</div>
</div>
<div class="section" id="initial-home-assistant-integration">
<h2><a class="toc-backref" href="#id22">Initial Home assistant integration</a></h2>
<p>To check the behavior of the sensor, I used a simple esphome to bring
the signal into home assistant for logging.</p>
<p>I had ordered this simple current to voltage converter with offset and gain setting.</p>
<div class="figure align-center">
<img alt="current converter" src="https://www.thouters.be/img/RainWaterTankLevelSensor/currentconverter.jpg" />
</div>
<p>To have a quick solution, I hooked this up straight into an esp32/esp8266's ADC.</p>
<p>However I need to replace it with a simple resistor and add some capacitors since
there is quite some noise visible.</p>
<div class="highlight"><pre><span></span><span class="nt">substitutions</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">kot</span><span class="w"></span>
<span class="nt">esphome</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">${hostname}</span><span class="w"></span>
<span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ESP8266</span><span class="w"></span>
<span class="w"> </span><span class="nt">board</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nodemcuv2</span><span class="w"></span>
<span class="nt">wifi</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="kt">!secret</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ssid</span><span class="w"></span>
<span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="kt">!secret</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">wlan</span><span class="w"></span>
<span class="w"> </span><span class="nt">domain</span><span class="p">:</span><span class="w"> </span><span class="kt">!secret</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">domain</span><span class="w"></span>
<span class="c1"># Enable logging</span><span class="w"></span>
<span class="nt">logger</span><span class="p">:</span><span class="w"></span>
<span class="c1"># Enable Home Assistant API</span><span class="w"></span>
<span class="nt">api</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="kt">!secret</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">api</span><span class="w"></span>
<span class="nt">ota</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="kt">!secret</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ota</span><span class="w"></span>
<span class="nt">sensor</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">adc</span><span class="w"></span>
<span class="w"> </span><span class="nt">pin</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">A0</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"rainwater</span><span class="nv"> </span><span class="s">tank"</span><span class="w"></span>
<span class="w"> </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="s">"mdi:cup-water"</span><span class="w"></span>
<span class="w"> </span><span class="nt">update_interval</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">2s</span><span class="w"></span>
<span class="w"> </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s">"%"</span><span class="w"></span>
<span class="w"> </span><span class="nt">accuracy_decimals</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1</span><span class="w"></span>
<span class="w"> </span><span class="nt">filters</span><span class="p">:</span><span class="w"></span>
<span class="c1"># 3.3V = 20mA</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">multiply</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">6.06</span><span class="w"></span>
<span class="c1">#now in mA, 4-20mA</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">offset</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">4</span><span class="w"></span>
<span class="c1"># now 0-16mA</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">multiply</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">6.25</span><span class="w"></span>
<span class="c1"># now 0-100</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">lambda</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">return 100 - x;</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">sliding_window_moving_average</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">window_size</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">30</span><span class="w"></span>
<span class="w"> </span><span class="nt">send_every</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">30</span><span class="w"></span>
<span class="c1">#losing 10cm of 1.5m, of 7500L</span><span class="w"></span>
<span class="c1"># - multiply: 70</span><span class="w"></span>
<span class="c1"># now in liters</span><span class="w"></span>
</pre></div>
<p>This can be up and running with simple commands (if a secrets.yaml with secrets is present next to it).</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">esphome compile kot.yaml</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">esphome upload kot.yaml</span><span class="w"></span>
</pre></div>
<div class="figure align-center">
<img alt="hass1" src="https://www.thouters.be/img/RainWaterTankLevelSensor/secondgraph.jpg" />
<p class="caption">Home assistant history (first sensor step observed)</p>
</div>
</div>
<div class="section" id="long-term-home-assistant-integration">
<h2><a class="toc-backref" href="#id23">Long term Home assistant integration</a></h2>
<p>I'm planning to to sense everything via modbus, so i got one of these current
loop to modbus converters to see if it is usable:</p>
<div class="figure align-center">
<img alt="current converter" src="https://www.thouters.be/img/RainWaterTankLevelSensor/cc-modbus.png" />
</div>
<p>I plan to try the modbus integration and a raspberry pi running <a class="reference external" href="https://github.com/3cky/mbusd">mbusd</a> or ESP32 with a
serial to TCP server.</p>
</div>
(dir)VimDiff as GIT difftool2022-03-19T21:00:00+01:002022-03-19T21:00:00+01:00Thomas Langewouterstag:www.thouters.be,2022-03-19:/GitVimDiff.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="diff screenshot" src="https://www.thouters.be/img/GitVimDiff/screenshot-pimped.jpg"/>
<p class="caption">Tig blame + VIM showing all files of a GIT commit in diff-tabs using <tt class="docutils literal">dirvimdiff</tt></p>
</div>
<p>I configured GIT's <tt class="docutils literal">difftool</tt> command to show the changed files as diff-split file tabs I can easily cycle through
by pressing <tt class="docutils literal">gt</tt>.</p>
<p>This makes for a good terminal-based meld/beyond compare/... alternative.</p>
<p>Combined with <a class="reference external" href="https://jonas.github.io/tig/">Tig</a>, it allows to quickly inspect a GIT commit's modifications in file context,
let's take a look at the quick and simple steps to set it up.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="diff screenshot" src="https://www.thouters.be/img/GitVimDiff/screenshot-pimped.jpg" />
<p class="caption">Tig blame + VIM showing all files of a GIT commit in diff-tabs using <tt class="docutils literal">dirvimdiff</tt></p>
</div>
<p>I configured GIT's <tt class="docutils literal">difftool</tt> command to show the changed files as diff-split file tabs I can easily cycle through
by pressing <tt class="docutils literal">gt</tt>.</p>
<p>This makes for a good terminal-based meld/beyond compare/... alternative.</p>
<p>Combined with <a class="reference external" href="https://jonas.github.io/tig/">Tig</a>, it allows to quickly inspect a GIT commit's modifications in file context,
let's take a look at the quick and simple steps to set it up.</p>
<div class="section" id="the-history-of-dirvimdiff">
<h2>The history of DirVimDiff</h2>
<p>After 12+ years of perforce on the job, I get to use GIT on the main codebase again.
Back then, I adapted a script called <a class="reference external" href="https://github.com/vim-scripts/svnvimdiff/blob/master/svnvimdiff">svnvimdiff</a> to show GIT commits.
I extended it to show an entire commit in a VIM window with every file in a tab.
I called it Gitvimdiff, but actually it doesn't work all that great, so these days I opt to use my <a class="reference external" href="https://github.com/thouters/scriptbox/blob/master/dirvimdiff">dirvimdiff</a> as git difftool.
Also, I once wrote a python script to do the same with perforce changelists.</p>
<p>Dirvimdiff is a quite simple hack at the time of writing, it converts the output of <tt class="docutils literal">diff</tt>
to a VIM-script that opens tabs with diff-splits of changed files. Then it launches Vim.</p>
</div>
<div class="section" id="setting-up-dirvimdiff-vimdiff-in-git">
<h2>Setting up dirvimdiff / vimdiff in GIT</h2>
<p>First, download <a class="reference external" href="https://github.com/thouters/scriptbox/blob/master/dirvimdiff">dirvimdiff</a> from my github <a class="reference external" href="https://github.com/thouters/scriptbox">scriptbox</a> repository, and put it somewhere
in your shell search <tt class="docutils literal">$PATH</tt>.</p>
<p>Next, add these chunks to your <tt class="docutils literal"><span class="pre">~/.gitconfig</span></tt> file:</p>
<div class="highlight"><pre><span></span><span class="k">[diff]</span><span class="w"></span>
<span class="w"> </span><span class="na">tool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">vimdiff</span><span class="w"></span>
<span class="k">[difftool "dirvimdiff"]</span><span class="w"></span>
<span class="w"> </span><span class="na">cmd</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dirvimdiff $LOCAL $REMOTE</span><span class="w"></span>
<span class="k">[alias]</span><span class="w"></span>
<span class="w"> </span><span class="na">vimdiff</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">difftool --dir-diff --no-symlinks --tool=dirvimdiff</span><span class="w"></span>
</pre></div>
<p>Now you can use <tt class="docutils literal">git vimdiff</tt> next to <tt class="docutils literal">git difftool</tt>:</p>
<div class="highlight"><pre><span></span>thomas@roper thouters.be % git difftool HEAD~1
(triggers tool in [diff] -> launch vimdiff one by one)
thomas@roper thouters.be % git vimdiff HEAD~1
(triggers the [alias] -> launch dirvimdiff, one VIM with tabs)
</pre></div>
<div class="figure align-center">
<img alt="diff screenshot" src="https://www.thouters.be/img/GitVimDiff/screenshot-dirvimdiff-commit.png" />
<p class="caption">VIM showing all files of a GIT commit in diff-tabs using <tt class="docutils literal">dirvimdiff</tt></p>
</div>
</div>
<div class="section" id="configuring-tig">
<h2>Configuring Tig</h2>
<p><a class="reference external" href="https://jonas.github.io/tig/">Tig</a> is an ncurses-based text-mode interface for git. It functions mainly as a GIT repository browser, but can also
assist in staging changes for commit at chunk level and act as a pager for output from various GIT commands.</p>
<div class="figure align-center">
<img alt="diff screenshot" src="https://www.thouters.be/img/GitVimDiff/screenshot-tig-blame.png" />
<p class="caption">Walking through GIT commits using Tig blame. Hit F5 to launch <tt class="docutils literal">dirvimdiff</tt>.</p>
</div>
<p>To use the dirvimdiff based difftool in tig, you can add this to your <tt class="docutils literal"><span class="pre">~/.tigrc</span></tt>:</p>
<pre class="literal-block">
bind status <F4> !git difftool -y %(commit) %(file)
bind generic <F5> !git vimdiff %(commit)~1 %(commit)
</pre>
</div>
Podcast downloading with Python and systemd2022-02-03T21:00:00+01:002022-02-03T21:00:00+01:00Thomas Langewouterstag:www.thouters.be,2022-02-03:/PodcastDownloadingWithPythonAndSystemd.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="pimped terminal screenshot" src="https://www.thouters.be/img/PodcastDownloading/screenshot-pimped.jpg"/>
</div>
<p>My <a class="reference external" href="https://www.willy.radio/programmas/slacker-station">favorite radio show</a> airs on Sunday afternoon, and often can't listen to it live.
Fortunately the radio station offers a podcast feed so I can catch up and listen
to old shows.
I set up a systemd user timer (cronjob) on my Linux computer that runs
<a class="reference external" href="https://pypi.org/project/getpodcast/">getpodcast</a> to download updates.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="pimped terminal screenshot" src="https://www.thouters.be/img/PodcastDownloading/screenshot-pimped.jpg" />
</div>
<p>My <a class="reference external" href="https://www.willy.radio/programmas/slacker-station">favorite radio show</a> airs on Sunday afternoon, and often can't listen to it live.
Fortunately the radio station offers a podcast feed so I can catch up and listen
to old shows.
I set up a systemd user timer (cronjob) on my Linux computer that runs
<a class="reference external" href="https://pypi.org/project/getpodcast/">getpodcast</a> to download updates.</p>
<div class="section" id="getpodcast-configuration">
<h2>Getpodcast configuration</h2>
<p>Getpodcast is pretty effective python module, you can create a small python script
that contains some simple configuration details and if needed a post-download hook.</p>
<div class="highlight"><pre><span></span>pip3 install getpodcast
</pre></div>
<p>In this example I added a post-download trigger that adds the new file to a m3u playlist,
but you can also use <tt class="docutils literal">os.system</tt> to trigger a command that adds the file to a music player queue.</p>
<div class="highlight"><pre><span></span><span class="ch">#! /usr/bin/env python3</span>
<span class="c1"># podcast_task.py</span>
<span class="kn">import</span> <span class="nn">getpodcast</span>
<span class="kn">import</span> <span class="nn">pathlib</span>
<span class="k">def</span> <span class="nf">add_to_playlist</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="sd">""" Add new files to a playlist """</span>
<span class="n">newfilename</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">kwargs</span><span class="p">[</span><span class="s2">"newfilename"</span><span class="p">])</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">"new.m3u"</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">'utf-8'</span><span class="p">)</span> <span class="k">as</span> <span class="n">playlist</span><span class="p">:</span>
<span class="n">playlist</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">newfilename</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="n">opt</span> <span class="o">=</span> <span class="n">getpodcast</span><span class="o">.</span><span class="n">options</span><span class="p">(</span>
<span class="c1">#date_from='2021-01-01',</span>
<span class="n">root_dir</span><span class="o">=</span><span class="s1">'.'</span><span class="p">,</span>
<span class="n">hooks</span><span class="o">=</span><span class="s1">'validated=add_to_playlist'</span>
<span class="p">)</span>
<span class="n">podcasts</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"Slacker Station"</span><span class="p">:</span> <span class="s2">"https://www.omnycontent.com/d/playlist/c292b3ac-094e-4616-a956-a99800ed54e9/c2b3611e-0597-4e7e-aabb-ac36008131d7/97e83ee0-349f-4444-b17f-ac360081f03e/podcast.rss"</span><span class="p">,</span>
<span class="s2">"De M Show"</span><span class="p">:</span> <span class="s2">"https://www.omnycontent.com/d/playlist/c292b3ac-094e-4616-a956-a99800ed54e9/28068cad-eb0d-46a1-8b77-abdd00f622c4/bbb709cc-4190-48af-aa70-ac2800e1dfdb/podcast.rss"</span>
<span class="p">}</span>
<span class="n">getpodcast</span><span class="o">.</span><span class="n">getpodcast</span><span class="p">(</span><span class="n">podcasts</span><span class="p">,</span> <span class="n">opt</span><span class="p">)</span>
</pre></div>
<p>After verifying the the script does what it is supposed to do in the command shell,
it's time to automate it and run it as a systemd service.</p>
</div>
<div class="section" id="systemd-configuration">
<h2>Systemd configuration</h2>
<p>Systemd is typically used to manage system wide services, but it can also be used
for more personal stuff that runs under the local user account.</p>
<p>To do just that, we can create <tt class="docutils literal"><span class="pre">~/.config/systemd/user</span></tt> and put some files there.</p>
<p>Systemd normally only activates these user jobs when the user is logged in. Since I'm using this on
a headless server, I need to enable lingering on the account:</p>
<div class="highlight"><pre><span></span>loginctl enable-linger username
</pre></div>
<p>Two configuration files are needed, a service that describes what to do, and a
timer that triggers the service.</p>
<div class="highlight"><pre><span></span><span class="c1"># ~/.config/systemd/user/mypodcaster.service</span><span class="w"></span>
<span class="k">[Unit]</span><span class="w"></span>
<span class="na">Description</span><span class="o">=</span><span class="s">run getpodcast script</span><span class="w"></span>
<span class="na">Wants</span><span class="o">=</span><span class="s">mypodcaster.timer</span><span class="w"></span>
<span class="k">[Service]</span><span class="w"></span>
<span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span><span class="w"></span>
<span class="na">ExecStart</span><span class="o">=</span><span class="s">/home/thomas/podcasts/podcast_task.py --run</span><span class="w"></span>
<span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/home/thomas/podcasts</span><span class="w"></span>
<span class="k">[Install]</span><span class="w"></span>
<span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span><span class="w"></span>
</pre></div>
<div class="highlight"><pre><span></span><span class="c1"># ~/.config/systemd/user/mypodcaster.timer</span><span class="w"></span>
<span class="k">[Unit]</span><span class="w"></span>
<span class="na">Description</span><span class="o">=</span><span class="s">Run podcast service one a day</span><span class="w"></span>
<span class="na">Requires</span><span class="o">=</span><span class="s">mypodcaster.service</span><span class="w"></span>
<span class="k">[Timer]</span><span class="w"></span>
<span class="na">Unit</span><span class="o">=</span><span class="s">mypodcaster.service</span><span class="w"></span>
<span class="na">OnCalendar</span><span class="o">=</span><span class="s">*-*-* 02:00:00</span><span class="w"></span>
<span class="k">[Install]</span><span class="w"></span>
<span class="na">WantedBy</span><span class="o">=</span><span class="s">timers.target</span><span class="w"></span>
</pre></div>
<p>To have systemd pick up the newly created configuration, we need to trigger daemon-reload:</p>
<div class="highlight"><pre><span></span>systemctl --user daemon-reload
</pre></div>
</div>
<div class="section" id="final-check">
<h2>Final check</h2>
<p>It's a good idea to check the systemd service before leaving it to the timer:</p>
<div class="highlight"><pre><span></span>thomas@elektra:~$ journalctl --user -f -u mypodcaster <span class="p">&</span>
thomas@elektra:~$ systemctl --user start mypodcaster
</pre></div>
<p>You can view the output later on using <tt class="docutils literal">journalctl</tt>:</p>
<div class="highlight"><pre><span></span>thomas@elektra:~$ journalctl --user -u mypodcaster<span class="p">|</span>tail
Feb <span class="m">03</span> <span class="m">20</span>:10:13 elektra getpodcasts.py<span class="o">[</span><span class="m">1801309</span><span class="o">]</span>: File: ./De M Show/2020/2020.08.31 De M-show met Marcel Vanthilt <span class="o">(</span><span class="m">288</span> 16u<span class="o">)</span>.mp3:
Feb <span class="m">03</span> <span class="m">20</span>:10:13 elektra getpodcasts.py<span class="o">[</span><span class="m">1801309</span><span class="o">]</span>: Status:
Feb <span class="m">03</span> <span class="m">20</span>:10:13 elektra getpodcasts.py<span class="o">[</span><span class="m">1801309</span><span class="o">]</span>: Downloading ...
Feb <span class="m">03</span> <span class="m">20</span>:10:13 elektra getpodcasts.py<span class="o">[</span><span class="m">1801309</span><span class="o">]</span>: Download <span class="nb">complete</span>
Feb <span class="m">03</span> <span class="m">20</span>:10:13 elektra getpodcasts.py<span class="o">[</span><span class="m">1801309</span><span class="o">]</span>: File validated
Feb <span class="m">03</span> <span class="m">20</span>:10:13 elektra systemd<span class="o">[</span><span class="m">801650</span><span class="o">]</span>: mypodcaster.service: Succeeded.
Feb <span class="m">03</span> <span class="m">20</span>:10:13 elektra systemd<span class="o">[</span><span class="m">801650</span><span class="o">]</span>: Finished run getpodcast script.
Feb <span class="m">04</span> <span class="m">02</span>:00:06 elektra systemd<span class="o">[</span><span class="m">801650</span><span class="o">]</span>: Starting run getpodcast script...
Feb <span class="m">04</span> <span class="m">02</span>:18:06 elektra systemd<span class="o">[</span><span class="m">801650</span><span class="o">]</span>: mypodcaster.service: Succeeded.
Feb <span class="m">04</span> <span class="m">02</span>:18:06 elektra systemd<span class="o">[</span><span class="m">801650</span><span class="o">]</span>: Finished run getpodcast script.
</pre></div>
<p>Enjoy listening...</p>
</div>
<div class="section" id="credits">
<h2>Credits</h2>
<p>Thanks to <a class="reference external" href="https://troet.cafe/@lamitpObuS">@lamitpobus</a> for sharing <a class="reference external" href="https://gist.github.com/SubOptimal/addb65cb10b8846f3edefd4a8100a315">a script to create fancy screenshot thubnails</a>!</p>
</div>
MR1 'robot' platform build2021-12-09T22:15:00+01:002021-12-29T22:15:00+01:00Thomas Langewouterstag:www.thouters.be,2021-12-09:/MR1Build.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="photo of the print" src="https://www.thouters.be/img/MR1Build/cover.jpg"/>
</div>
<p>I printed this miniature cart/robot to show my son I can use my 3D printer and
electronics hobby to build things he too can enjoy.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="photo of the print" src="https://www.thouters.be/img/MR1Build/cover.jpg" />
</div>
<p>I printed this miniature cart/robot to show my son I can use my 3D printer and
electronics hobby to build things he too can enjoy.</p>
<p>While I was minding my son at home while his kindergarten group was in
quarantine, he asked me what a certain piece of electronics was for.
He pointed to a reflection sensor that I was re-purposing for <a class="reference external" href="https://www.thouters.be/HassEnergyOfBinaryHeater.html">a project I was working on</a>.</p>
<p>To show him how the sensor behaved and as an example of what it is useful for,
I ended up hacking together this small demonstration cart out of scrap 3D
prints and the electronics I had on hand.</p>
<div class="figure align-center">
<img alt="photo of the print" src="https://www.thouters.be/img/MR1Build/sensor_demonstration.jpg" />
</div>
<p>The simple circuit would switch off the motors when the cart was about
to drive off a table. My son was excited about building moving carts of course,
but wanted a more beautiful piece to play with.</p>
<p>I had a look on the usual prusaprinters.org and Thingiverse, and found <a class="reference external" href="https://www.thingiverse.com/thing:1562530">a nice
chassis on thingiverse</a> which I printed in red DevilDesign PLA.
The chassis has a cute shape and lots of gaps to embed electronics and sensors
into.</p>
<p>I used <a class="reference external" href="https://www.thingiverse.com/thing:2800717">the weels from a different wheely-robot</a>
they made a nice fit, and add a fresh visual touch to the body.</p>
<p>I printed the exterior gripping part of the wheel using DevilDesign TPU, which
is a flexible plastic. I plan to re-slice and print them to apply the
PrusaSlicer fuzzy skin effect to increase grip.</p>
<p>Next up is building some kind of a controller for it, which I plan to do with
an ESP32-S2 and CircuitPython!</p>
Heater-on-light tracking using Home Assistant2021-11-09T22:15:00+01:002021-11-09T22:15:00+01:00Thomas Langewouterstag:www.thouters.be,2021-11-09:/HassEnergyOfBinaryHeater.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="hacked circuit" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/cover.jpg"/>
</div>
<p>Living in a house with Aterno resistive electric heating, I wanted to get a feel
of how our thermostat settings and outside temperature impacts our electricity bill.</p>
<p>I did not want to change the electric installation to add invasive interfacing
like current sensing, and hacked a ZigBee door sensor to report on the heater's
status LED.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="hacked circuit" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/cover.jpg" />
</div>
<p>Living in a house with Aterno resistive electric heating, I wanted to get a feel
of how our thermostat settings and outside temperature impacts our electricity bill.</p>
<p>I did not want to change the electric installation to add invasive interfacing
like current sensing, and hacked a ZigBee door sensor to report on the heater's
status LED.</p>
<div class="figure align-center">
<img alt="home assistant energy tab" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/energytab1.png" />
</div>
<div class="contents topic" id="contents">
<p class="topic-title">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#hardware" id="id1">Hardware</a><ul>
<li><a class="reference internal" href="#prototype" id="id2">Prototype</a></li>
<li><a class="reference internal" href="#definitive-hardware" id="id3">Definitive hardware</a></li>
</ul>
</li>
<li><a class="reference internal" href="#home-assistant-configuration" id="id4">Home assistant configuration</a><ul>
<li><a class="reference internal" href="#inverting-the-signal" id="id5">Inverting the signal</a></li>
<li><a class="reference internal" href="#integrating-over-time" id="id6">Integrating over time</a></li>
<li><a class="reference internal" href="#applying-power-usage-of-the-heater" id="id7">Applying power usage of the heater</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="hardware">
<h2><a class="toc-backref" href="#id1">Hardware</a></h2>
<div class="section" id="prototype">
<h3><a class="toc-backref" href="#id2">Prototype</a></h3>
<p>I improvised a first version of the circuit with parts I had at hand:</p>
<div class="figure align-center">
<img alt="arduino optical sensor module" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/parts.jpg" />
</div>
<ul class="simple">
<li>A light dependent resistor (LDR), which yields about 1.7kOhm when the LED hits it, and > 10kOhm in the relative dark.</li>
<li>A Xiaomi Aqara 'zigbee' door & window contact sensor (MCCGQ11LM), of which I also use several to track doors and the doorbell.</li>
<li>A TCRT5000 optical sensor module, typically used in educational line following robots.</li>
<li>Double AA battery holder with wires (to replace the 1620 coin cell)</li>
</ul>
<p>Hooking up the LDR straight over the reed switch did not work, so I performed
some experiments with using a LED as light sensor together with a Darlington
transistor pair. The ZigBee module's input is quite sensitive, so I decided to
try a comparator and ignore the circuit's battery life for now (the circuit
draws 0.9mA, which could last 2.5months on LR6 AA batteries).</p>
<p>I had a TCRT5000 optical sensor module, which among other things had the
required LM393 and a variable resistor on a small pcb. Perfect!
I removed the sensor module's LEDS and replaced the phototransistor by the LDR,
leaving only the LM393 comparator and some resistors and a cap.</p>
<p>The comparator compares the voltage over the LDR against a reference voltage
produced by a potentiometer, and if the LDR's voltage drops below that, it
pulls its output pin to ground. The door sensor's reed switch also shorts the
ZigBee microcontroller's I/O pin to ground, so I simply had to connect the two.</p>
<p>For testing purposes, I improvised a little fixture to align the LDR with the
radiator's LED by reusing 3D printed scrap prototype cut to form, and fixed it
using double sided tape to the radiator's control box. This proved to be very
unreliable so I designed an alignment jig which I <a class="reference external" href="https://www.thouters.be/PrusaMk3s.html">3D printed</a>:</p>
<div class="figure align-center">
<img alt="alignment jig" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/alignment.png" />
</div>
<p>The next step is to turn the alignment jig into an enclosure that holds the
AA batteries and circuit boards.</p>
</div>
<div class="section" id="definitive-hardware">
<h3><a class="toc-backref" href="#id3">Definitive hardware</a></h3>
<p>I used a SOP8 to DIP adapter PCB to break out the LM393 to a more workable footprint.
I added through-hole and surface mount resistors to provide a fixed voltage divider.
I also switched the positive and negative inputs of the comparator so no extra
template entity to invert the signal is needed in Home Assistant.</p>
<div class="figure align-center">
<img alt="comparator photo 1" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/comp1.jpg" />
</div>
<div class="figure align-center">
<img alt="comparator photo 2" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/comp2.jpg" />
</div>
<p>I designed an enclosure around the LED alignment jig I had made earlier.</p>
<div class="figure align-center">
<img alt="render1" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/aternocasing.png" />
</div>
<div class="figure align-center">
<img alt="render2" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/aternocasing2.png" />
</div>
<p>This has room for two AA batteries and the two circuit boards.</p>
<div class="figure align-center">
<img alt="bottom" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/bottom.jpg" />
</div>
<div class="figure align-center">
<img alt="open" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/open.jpg" />
</div>
</div>
</div>
<div class="section" id="home-assistant-configuration">
<h2><a class="toc-backref" href="#id4">Home assistant configuration</a></h2>
<div class="section" id="inverting-the-signal">
<h3><a class="toc-backref" href="#id5">Inverting the signal</a></h3>
<p>When the radiator is heating, the comparator shunts the reed switch, resulting in a 'closed' door.
Since that is the idle state of the binary sensor, I invert it with a template sensor in <tt class="docutils literal">binary_sensors.yaml</tt>:</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">template</span><span class="w"></span>
<span class="w"> </span><span class="nt">sensors</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">heaterliving_contact_inverted</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s">'Resistive</span><span class="nv"> </span><span class="s">Heating'</span><span class="w"></span>
<span class="w"> </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">>-</span><span class="w"></span>
<span class="w"> </span><span class="no">{{ is_state('binary_sensor.heaterliving_contact','off') }}</span><span class="w"></span>
<span class="w"> </span><span class="nt">icon_template</span><span class="p">:</span><span class="w"> </span><span class="s">'mdi:radiator'</span><span class="w"></span>
</pre></div>
<p>This creates a nice entity in home assistant:</p>
<div class="figure align-center">
<img alt="inverted binary sensor" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/binary.png" />
</div>
</div>
<div class="section" id="integrating-over-time">
<h3><a class="toc-backref" href="#id6">Integrating over time</a></h3>
<p>A new history_stats integration is set up in <tt class="docutils literal">sensors.yaml</tt> to track the total on time:</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">history_stats</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Heatingtime</span><span class="w"></span>
<span class="w"> </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">binary_sensor.heaterliving_contact_inverted</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s">'on'</span><span class="w"></span>
<span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">time</span><span class="w"></span>
<span class="w"> </span><span class="nt">start</span><span class="p">:</span><span class="w"> </span><span class="s">'{{</span><span class="nv"> </span><span class="s">now().replace(hour=0,</span><span class="nv"> </span><span class="s">minute=0,</span><span class="nv"> </span><span class="s">second=0)</span><span class="nv"> </span><span class="s">}}'</span><span class="w"></span>
<span class="w"> </span><span class="nt">end</span><span class="p">:</span><span class="w"> </span><span class="s">'{{</span><span class="nv"> </span><span class="s">now()</span><span class="nv"> </span><span class="s">}}'</span><span class="w"></span>
</pre></div>
<div class="figure align-center">
<img alt="time tracking binary sensor" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/timetracking.png" />
</div>
</div>
<div class="section" id="applying-power-usage-of-the-heater">
<h3><a class="toc-backref" href="#id7">Applying power usage of the heater</a></h3>
<p>Energy is time x power, so we need to multiply the heating-time-sensor's value (in hours) with the radiators average 4.8kW (actually two radiators of 3kW and 1,8kW).
To get a more accurate value, the actual power the radiator draws should be determined.
This is done using yet another template sensor, in <tt class="docutils literal">sensors.yaml</tt>:</p>
<!-- Large radiator draws 12A at 225V warm = 2.7kW -->
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">template</span><span class="w"></span>
<span class="w"> </span><span class="nt">sensors</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">living_energy_total</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">device_class</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">energy</span><span class="w"></span>
<span class="w"> </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">kWh</span><span class="w"></span>
<span class="w"> </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">></span><span class="w"></span>
<span class="w"> </span><span class="no">{% if is_number(states('sensor.Heatingtime')) %}</span><span class="w"></span>
<span class="w"> </span><span class="no">{{ states('sensor.Heatingtime')|float * 4.8 }}</span><span class="w"></span>
<span class="w"> </span><span class="no">{% else %}</span><span class="w"></span>
<span class="w"> </span><span class="no">None</span><span class="w"></span>
<span class="w"> </span><span class="no">{% endif %}</span><span class="w"></span>
<span class="w"> </span><span class="w w-Error"> </span><span class="nt">attribute_templates</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">last_reset</span><span class="p">:</span><span class="w"> </span><span class="s">'{{</span><span class="nv"> </span><span class="s">now().replace(hour=0,</span><span class="nv"> </span><span class="s">minute=0,</span><span class="nv"> </span><span class="s">second=0)</span><span class="nv"> </span><span class="s">}}'</span><span class="w"></span>
<span class="w"> </span><span class="nt">state_class</span><span class="p">:</span><span class="w"> </span><span class="s">'total_increasing'</span><span class="w"></span>
</pre></div>
<div class="figure align-center">
<img alt="energy tracked template sensor" src="https://www.thouters.be/img/HassEnergyOfBinaryHeater/energy-template.png" />
</div>
</div>
</div>
Snapcast Bluetooth speaker on Debian Sid2021-10-05T22:15:00+02:002021-10-05T22:15:00+02:00Thomas Langewouterstag:www.thouters.be,2021-10-05:/BluetoothSpeakerOnDebianSid.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="photo of the bluetooth speaker and dongle" src="https://www.thouters.be/img/BluetoothSpeakerOnDebianSid/cover.jpg"/>
</div>
<p>I use a Raspberry Pi to drive two sets of speakers using Snapcast.
To maximize the range of my Bluetooth speaker, I bought a Realtek
RTL8761B-based USB Bluetooth dongle with a large antenna.</p>
<p>To get the Bluetooth dongle to work with the latest kernel drivers and
bluez-alsa, I had to abandon Raspbian Linux and use the latest Debian Linux
instead.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="photo of the bluetooth speaker and dongle" src="https://www.thouters.be/img/BluetoothSpeakerOnDebianSid/cover.jpg" />
</div>
<p>I use a Raspberry Pi to drive two sets of speakers using Snapcast.
To maximize the range of my Bluetooth speaker, I bought a Realtek
RTL8761B-based USB Bluetooth dongle with a large antenna.</p>
<p>To get the Bluetooth dongle to work with the latest kernel drivers and
bluez-alsa, I had to abandon Raspbian Linux and use the latest Debian Linux
instead.</p>
<p>To use the Bluetooth speaker as an addition to <a class="reference external" href="https://www.thouters.be/HassMultiRoomAudio.html">my Home Assistant controlled Multi-room audio set-up</a>,
I added a shell script that exposes Bluetooth connection status reporting and triggers over MQTT.</p>
<div class="contents topic" id="table-of-contents">
<p class="topic-title">Table of Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#linux-installation" id="id1">Linux Installation</a></li>
<li><a class="reference internal" href="#bluetooth-configuration" id="id2">Bluetooth configuration</a></li>
<li><a class="reference internal" href="#bluetoothspeaker2mqtt-installation" id="id3">bluetoothspeaker2mqtt installation</a></li>
<li><a class="reference internal" href="#home-assistant-integration" id="id4">Home assistant integration</a></li>
<li><a class="reference internal" href="#running-a-second-snapclient-on-the-line-out" id="id5">Running a second snapclient on the line out</a></li>
</ul>
</div>
<div class="section" id="linux-installation">
<h2><a class="toc-backref" href="#id1">Linux Installation</a></h2>
<p>I flashed a pre-installed Debian 10 image from <a class="reference external" href="https://raspi.debian.net/tested-images/">https://raspi.debian.net/tested-images/</a>
I downloaded the xz-compressed image of the 2021.8.23 build of '11 (Bullseye) Release' that was 'tested with raspberry pi 2B'.</p>
<div class="highlight"><pre><span></span><span class="go">apt install sudo vim screen</span>
<span class="go">adduser pi</span>
<span class="go">/sbin/adduser username sudo</span>
<span class="go">hostnamectl set-hostname living</span>
</pre></div>
<p>Next up was to follow the instructions to upgrade to Debian unstable (Sid) : <a class="reference external" href="https://dnuka.github.io/upgrading-debian-to-unstable.html">https://dnuka.github.io/upgrading-debian-to-unstable.html</a></p>
<div class="highlight"><pre><span></span><span class="go">vi /etc/apt/sources.list</span>
<span class="go">apt update</span>
<span class="go">apt-get dist-upgrade</span>
<span class="go">apt autoremove</span>
<span class="go">reboot</span>
</pre></div>
<p>Install the audio related tools:</p>
<div class="highlight"><pre><span></span>sudo apt install snapclient
sudo apt install bluez bluez-alsa-utils alsa-utils
</pre></div>
<p>And the dependencies used by the bash script:</p>
<div class="highlight"><pre><span></span>sudo apt install mosquitto-clients
sudo apt install expect
</pre></div>
<p>Follow the instructions of <a class="reference external" href="https://linuxreviews.org/Realtek_RTL8761B">https://linuxreviews.org/Realtek_RTL8761B</a> to install the Bluetooth chipset firmware:</p>
<div class="highlight"><pre><span></span>su
<span class="nb">cd</span> /lib/firmware/
mkdir rtl_bt
<span class="nb">cd</span> rtl_bt
wget https://raw.githubusercontent.com/Realtek-OpenSource/android_hardware_realtek/rtk1395/bt/rtkbt/Firmware/BT/rtl8761b_config -O rtl8761b_config.bin
wget https://raw.githubusercontent.com/Realtek-OpenSource/android_hardware_realtek/rtk1395/bt/rtkbt/Firmware/BT/rtl8761b_fw -O rtl8761b_fw.bin
reboot
</pre></div>
</div>
<div class="section" id="bluetooth-configuration">
<h2><a class="toc-backref" href="#id2">Bluetooth configuration</a></h2>
<p>Pair the Bluetooth speaker to the Pi:</p>
<div class="highlight"><pre><span></span>sudo bluetoothctl
scan
pair <MAC>
connect <MAC>
</pre></div>
<p>Add to <tt class="docutils literal">/etc/asound.conf</tt> (and change the device address to your speaker's):</p>
<div class="highlight"><pre><span></span>root@debian:~# cat /etc/asound.conf
<span class="c1">#pcm.!default "bluealsa"</span>
<span class="c1">#ctl.!default "bluealsa"</span>
defaults.bluealsa.service <span class="s2">"org.bluealsa"</span>
defaults.bluealsa.device <span class="s2">"aa:bb:cc:dd:ee:ff"</span>
defaults.bluealsa.profile <span class="s2">"a2dp"</span>
defaults.bluealsa.delay <span class="m">0</span>
</pre></div>
<p>Configure the snapclient arguments to use the right device. With the latest
version of bluealsa the mixer is exposed and can be controlled both over the
network (via snapclient) or via the physical buttons on the speaker.</p>
<div class="highlight"><pre><span></span>root@debian:~# cat /etc/default/snapclient-bluetooth
<span class="nv">SNAPCLIENT_OPTS</span><span class="o">=</span><span class="s2">"-h 1.2.3.4 --hostID keuken --mixer hardware:\"JBL Charge 4 - A2DP\" -s bluealsa --latency 0"</span>
</pre></div>
<p>Set up a systemd service for it. We won't have it start on startup, but rather have a shell script activate it when needed.</p>
<div class="highlight"><pre><span></span>root@debian:~# cat /etc/systemd/system/snapclient-bluetooth.service
<span class="o">[</span>Unit<span class="o">]</span>
<span class="nv">Description</span><span class="o">=</span>Snapcast client
<span class="nv">After</span><span class="o">=</span>network.target sound.target
<span class="nv">Wants</span><span class="o">=</span>avahi-daemon.service
<span class="o">[</span>Service<span class="o">]</span>
<span class="nv">EnvironmentFile</span><span class="o">=</span>-/etc/default/snapclient-bluetooth
<span class="nv">Type</span><span class="o">=</span>simple
<span class="nv">ExecStart</span><span class="o">=</span>/usr/bin/snapclient <span class="nv">$SNAPCLIENT_OPTS</span>
<span class="nv">Restart</span><span class="o">=</span>always
<span class="o">[</span>Install<span class="o">]</span>
<span class="nv">WantedBy</span><span class="o">=</span>multi-user.target
</pre></div>
</div>
<div class="section" id="bluetoothspeaker2mqtt-installation">
<h2><a class="toc-backref" href="#id3">bluetoothspeaker2mqtt installation</a></h2>
<p>Install my scripts from <a class="reference external" href="https://github.com/thouters/bluetoothspeaker2mqtt">https://github.com/thouters/bluetoothspeaker2mqtt</a> - where up to date instructions are available.</p>
<div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="c1"># put into $HOME/.config/mosquitto_pub onto (separate lines, no spaces at the ends!)</span>
<span class="c1"># -h hostname</span>
<span class="c1"># -u mqtt</span>
<span class="c1"># -P mqttpassword</span>
<span class="c1"># apt install expect mosquitto-clients # unbuffer, mosquitto_*</span>
<span class="nb">set</span> -e
<span class="nv">MAC</span><span class="o">=</span><span class="k">$(</span>sed -n <span class="s1">'/device/s/[^"]*"\([^"]*\)"/\1/p'</span> /etc/asound.conf<span class="k">)</span>
: <span class="s2">"</span><span class="si">${</span><span class="nv">TOPIC_STATE</span><span class="p">:=bluetoothspeaker2mqtt/myspeaker/state</span><span class="si">}</span><span class="s2">"</span>
: <span class="s2">"</span><span class="si">${</span><span class="nv">TOPIC_SET</span><span class="p">:=bluetoothspeaker2mqtt/myspeaker/set</span><span class="si">}</span><span class="s2">"</span>
: <span class="s2">"</span><span class="si">${</span><span class="nv">SNAPCLIENT_SERVICE</span><span class="p">:=snapclient-bluetooth</span><span class="si">}</span><span class="s2">"</span>
: <span class="s2">"</span><span class="si">${</span><span class="nv">STARTSTOP_SNAPCLIENT</span><span class="p">:=yes</span><span class="si">}</span><span class="s2">"</span>
: <span class="s2">"</span><span class="si">${</span><span class="nv">BUTTONWATCHER_SERVICE</span><span class="p">:=bluetoothspeaker-buttonwatcher</span><span class="si">}</span><span class="s2">"</span>
: <span class="s2">"</span><span class="si">${</span><span class="nv">STARTSTOP_BUTTONWATCHER</span><span class="p">:=yes</span><span class="si">}</span><span class="s2">"</span>
: <span class="s2">"</span><span class="si">${</span><span class="nv">POST_CONNECT_SETTLE_TIME</span><span class="p">:=1</span><span class="si">}</span><span class="s2">"</span>
<span class="k">function</span> bluetooth_event_listener<span class="o">()</span>
<span class="o">{</span>
<span class="nv">MATCH</span><span class="o">=</span><span class="m">0</span>
<span class="o">(</span> <span class="k">while</span> true<span class="p">;</span> <span class="k">do</span> sleep <span class="m">1</span><span class="p">;</span> <span class="k">done</span><span class="o">)</span> <span class="p">|</span> unbuffer bluetoothctl <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r line
<span class="k">do</span>
<span class="c1"># A little state machine: scan the output for the device MAC</span>
<span class="nb">echo</span> <span class="s2">"<recv> </span><span class="nv">$line</span><span class="s2">"</span>
<span class="k">if</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span> <span class="p">|</span> grep -qE <span class="s2">"Device ([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})"</span>
<span class="k">then</span>
<span class="k">if</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span> <span class="p">|</span> grep -q <span class="s2">"Device </span><span class="si">${</span><span class="nv">MAC</span><span class="si">}</span><span class="s2">"</span>
<span class="k">then</span>
<span class="nb">echo</span> <span class="s2">"Info about the device detected"</span>
<span class="nv">MATCH</span><span class="o">=</span><span class="m">1</span>
<span class="k">else</span>
<span class="nv">MATCH</span><span class="o">=</span><span class="m">0</span>
<span class="k">fi</span>
<span class="k">fi</span>
<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$MATCH</span><span class="s2">"</span> <span class="o">=</span> <span class="m">1</span> <span class="o">]]</span>
<span class="k">then</span>
<span class="k">if</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span> <span class="p">|</span>grep -q <span class="s2">"Connected: yes"</span>
<span class="k">then</span>
<span class="nb">echo</span> Connected!
mosquitto_pub -t <span class="s2">"</span><span class="si">${</span><span class="nv">TOPIC_STATE</span><span class="si">}</span><span class="s2">"</span> -m on
sleep <span class="s2">"</span><span class="nv">$POST_CONNECT_SETTLE_TIME</span><span class="s2">"</span>
<span class="o">[[</span> <span class="s2">"</span><span class="nv">$STARTSTOP_SNAPCLIENT</span><span class="s2">"</span> <span class="o">=</span> yes <span class="o">]]</span> <span class="o">&&</span> systemctl start <span class="s2">"</span><span class="si">${</span><span class="nv">SNAPCLIENT_SERVICE</span><span class="si">}</span><span class="s2">"</span>
<span class="o">[[</span> <span class="s2">"</span><span class="nv">$STARTSTOP_BUTTONWATCHER</span><span class="s2">"</span> <span class="o">=</span> yes <span class="o">]]</span> <span class="o">&&</span> systemctl start <span class="s2">"</span><span class="si">${</span><span class="nv">BUTTONWATCHER_SERVICE</span><span class="si">}</span><span class="s2">"</span>
<span class="k">fi</span>
<span class="k">if</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span> <span class="p">|</span>grep -q <span class="s2">"Connected: no"</span>
<span class="k">then</span>
<span class="nb">echo</span> Disconnected!
mosquitto_pub -t <span class="s2">"</span><span class="si">${</span><span class="nv">TOPIC_STATE</span><span class="si">}</span><span class="s2">"</span> -m off
<span class="o">[[</span> <span class="s2">"</span><span class="nv">$STARTSTOP_SNAPCLIENT</span><span class="s2">"</span> <span class="o">=</span> yes <span class="o">]]</span> <span class="o">&&</span> systemctl stop <span class="s2">"</span><span class="si">${</span><span class="nv">SNAPCLIENT_SERVICE</span><span class="si">}</span><span class="s2">"</span>
<span class="o">[[</span> <span class="s2">"</span><span class="nv">$STARTSTOP_BUTTONWATCHER</span><span class="s2">"</span> <span class="o">=</span> yes <span class="o">]]</span> <span class="o">&&</span> systemctl stop <span class="s2">"</span><span class="si">${</span><span class="nv">BUTTONWATCHER_SERVICE</span><span class="si">}</span><span class="s2">"</span>
<span class="k">fi</span>
<span class="k">fi</span>
<span class="k">done</span>
<span class="o">}</span>
<span class="k">function</span> bluetoothctl_cmd<span class="o">()</span>
<span class="o">{</span>
<span class="nb">echo</span> <span class="s2">"<send> </span><span class="nv">$1</span><span class="s2">"</span>
<span class="nb">printf</span> <span class="s2">"%s\n\n"</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="p">|</span> bluetoothctl
<span class="o">}</span>
<span class="k">function</span> mqtt_listener<span class="o">()</span>
<span class="o">{</span>
mosquitto_sub -t <span class="s2">"</span><span class="si">${</span><span class="nv">TOPIC_SET</span><span class="si">}</span><span class="s2">"</span> -v <span class="p">|</span><span class="k">while</span> <span class="nb">read</span> -r message
<span class="k">do</span>
<span class="nv">topic</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$message</span><span class="s2">"</span><span class="p">|</span>cut -d<span class="s1">' '</span> -f <span class="m">1</span><span class="k">)</span>
<span class="nv">payload</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$message</span><span class="s2">"</span><span class="p">|</span>cut -d<span class="s1">' '</span> -f <span class="m">2</span><span class="k">)</span>
<span class="nb">echo</span> <span class="s2">"<mqtt recv> topic </span><span class="si">${</span><span class="nv">topic</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">payload</span><span class="si">}</span><span class="s2">"</span>
<span class="k">case</span> <span class="nv">$topic</span> <span class="k">in</span>
<span class="s2">"</span><span class="si">${</span><span class="nv">TOPIC_SET</span><span class="si">}</span><span class="s2">"</span><span class="o">)</span>
<span class="k">case</span> <span class="nv">$payload</span> <span class="k">in</span>
on<span class="o">)</span>
bluetoothctl_cmd <span class="s2">"connect </span><span class="nv">$MAC</span><span class="s2">"</span>
<span class="p">;;</span>
off<span class="o">)</span>
bluetoothctl_cmd <span class="s2">"disconnect </span><span class="nv">$MAC</span><span class="s2">"</span>
<span class="p">;;</span>
*<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"invalid command </span><span class="nv">$payload</span><span class="s2">"</span>
<span class="p">;;</span>
<span class="k">esac</span>
<span class="p">;;</span>
*<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"Not implemented </span><span class="nv">$topic</span><span class="s2">"</span>
<span class="p">;;</span>
<span class="k">esac</span>
<span class="k">done</span>
<span class="o">}</span>
bluetooth_event_listener <span class="p">&</span>
mqtt_listener <span class="p">&</span>
<span class="nb">wait</span>
</pre></div>
<p>Use the provided systemd service files:</p>
<div class="highlight"><pre><span></span><span class="k">[Unit]</span><span class="w"></span>
<span class="na">Description</span><span class="o">=</span><span class="s">expose bluetoothspeaker on mqtt</span><span class="w"></span>
<span class="na">Requires</span><span class="o">=</span><span class="s">bluealsa.service</span><span class="w"></span>
<span class="na">After</span><span class="o">=</span><span class="s">bluealsa.service</span><span class="w"></span>
<span class="k">[Service]</span><span class="w"></span>
<span class="na">Type</span><span class="o">=</span><span class="s">simple</span><span class="w"></span>
<span class="na">EnvironmentFile</span><span class="o">=</span><span class="s">-/etc/bluetoothspeaker2mqtt.conf</span><span class="w"></span>
<span class="na">User</span><span class="o">=</span><span class="s">pi</span><span class="w"></span>
<span class="na">ExecStart</span><span class="o">=</span><span class="s">/home/pi/bluetoothspeaker2mqtt.sh</span><span class="w"></span>
<span class="k">[Install]</span><span class="w"></span>
<span class="na">WantedBy</span><span class="o">=</span><span class="s">bluetooth.target</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="home-assistant-integration">
<h2><a class="toc-backref" href="#id4">Home assistant integration</a></h2>
<p>This switch configuration can be used to trigger bluetooth connect or disconnect:</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mqtt</span><span class="w"></span>
<span class="w"> </span><span class="nt">unique_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">bluetooth_speaker_link</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"Bluetooth</span><span class="nv"> </span><span class="s">link"</span><span class="w"></span>
<span class="w"> </span><span class="nt">state_topic</span><span class="p">:</span><span class="w"> </span><span class="s">"box/state"</span><span class="w"></span>
<span class="w"> </span><span class="nt">command_topic</span><span class="p">:</span><span class="w"> </span><span class="s">"box/set"</span><span class="w"></span>
<span class="w"> </span><span class="nt">payload_on</span><span class="p">:</span><span class="w"> </span><span class="s">"on"</span><span class="w"></span>
<span class="w"> </span><span class="nt">payload_off</span><span class="p">:</span><span class="w"> </span><span class="s">"off"</span><span class="w"></span>
<span class="w"> </span><span class="nt">state_on</span><span class="p">:</span><span class="w"> </span><span class="s">"on"</span><span class="w"></span>
<span class="w"> </span><span class="nt">state_off</span><span class="p">:</span><span class="w"> </span><span class="s">"off"</span><span class="w"></span>
<span class="w"> </span><span class="nt">optimistic</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">false</span><span class="w"></span>
<span class="w"> </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">0</span><span class="w"></span>
<span class="w"> </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">false</span><span class="w"></span>
<span class="w"> </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mdi:bluetooth-audio</span><span class="w"></span>
</pre></div>
<p>And this binary_sensor can be used to view the state of the connection, and trigger automations
based on it:</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mqtt</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"box"</span><span class="w"></span>
<span class="w"> </span><span class="nt">state_topic</span><span class="p">:</span><span class="w"> </span><span class="s">"box/status"</span><span class="w"></span>
<span class="w"> </span><span class="nt">payload_on</span><span class="p">:</span><span class="w"> </span><span class="s">"on"</span><span class="w"></span>
<span class="w"> </span><span class="nt">payload_off</span><span class="p">:</span><span class="w"> </span><span class="s">"off"</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="running-a-second-snapclient-on-the-line-out">
<h2><a class="toc-backref" href="#id5">Running a second snapclient on the line out</a></h2>
<p>I use the default snapclient initscripts to run a snapclient to a wired set of speakers.</p>
<p>Unfortunately I hit a bug that breaks the line out of the Pi 2: <a class="reference external" href="https://github.com/raspberrypi/linux/issues/4108">https://github.com/raspberrypi/linux/issues/4108</a>
I worked around this by switching to a USB soundcard dongle that I already had.</p>
<div class="highlight"><pre><span></span>root@debian:~# cat /etc/default/snapclient
<span class="nv">SNAPCLIENT_OPTS</span><span class="o">=</span><span class="s2">"-h 1.2.3.4 --hostID living --mixer hardware:Speaker -s plughw:CARD=Device,DEV=0"</span>
</pre></div>
</div>
Home Assistant Multi room audio setup2021-09-09T22:00:00+02:002021-09-09T22:00:00+02:00Thomas Langewouterstag:www.thouters.be,2021-09-09:/HassMultiRoomAudio.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="concept" src="https://www.thouters.be/img/HassMultiRoomAudio/concept1.png"/>
</div>
<p>I was able to cook up a fun audio setup controlled and automated with Home Assistant!
It features</p>
<ul class="simple">
<li>Multiple room synchronised audio speakers (fixed/analog, portable Bluetooth devices and via app on phone or tablet)</li>
<li>Playback of internet radio, Spotify streaming and local audio files.</li>
<li>Sound notifications for Doorbell, garden gate and other sensors and <a class="reference external" href="https://www.thouters.be/HassHandsfreeAlarm.html">home alarm</a> events.</li>
</ul>
<p>In my setup, a docker container running on the Home Assistant machine takes
care of retrieving audio from Spotify, internet radio streams and local
storage.</p>
<p>Physical devices like raspberry pi's or an Android phone/tablet use a snapcast
client to use drive speakers.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="concept" src="https://www.thouters.be/img/HassMultiRoomAudio/concept1.png" />
</div>
<p>I was able to cook up a fun audio setup controlled and automated with Home Assistant!
It features</p>
<ul class="simple">
<li>Multiple room synchronised audio speakers (fixed/analog, portable Bluetooth devices and via app on phone or tablet)</li>
<li>Playback of internet radio, Spotify streaming and local audio files.</li>
<li>Sound notifications for Doorbell, garden gate and other sensors and <a class="reference external" href="https://www.thouters.be/HassHandsfreeAlarm.html">home alarm</a> events.</li>
</ul>
<p>In my setup, a docker container running on the Home Assistant machine takes
care of retrieving audio from Spotify, internet radio streams and local
storage.</p>
<p>Physical devices like raspberry pi's or an Android phone/tablet use a snapcast
client to use drive speakers.</p>
<div class="section" id="overview">
<h2>Overview</h2>
<p>Adding sound to a smart home used to be a thing of fancy smart-home solutions
sold with their own audio server modules that are wired up to multi-room
speaker set-ups. Nowadays modular cloud connected voice assistants bring music
and notifications to peoples homes.</p>
<p>If you can live without the fancy voice control and enjoy setting up some Linux
services, then adding some of the more classic audio features is a doable task,
soon to become easier due to a home assistant add-on.</p>
<p>During 2020's work-from-home regime I discovered radio, I listen extensively to
radio and Spotify when I'm at home. I wanted to mix in audio notifications like
the door bell and a garden gate sensor. This turned out to be easily mixed
together using Snapcast.</p>
<p>To listen to music in the rooms next to our living room with an amp/speaker
setup, I bought a Bluetooth speaker, figuring I could feed it music via a
raspberry pi while at home, and still use it with my phone when in the garden
or on the road.</p>
<div class="figure align-center">
<img alt="concept" src="https://www.thouters.be/img/HassMultiRoomAudio/lovelace.png" />
<p class="caption">Home assistant integration</p>
</div>
<p>Being able to move the speaker along around the house while it's not tied to a
particular smart phone (that sometimes walks off) is lovely.</p>
<p>I just had to install and configure existing software, a docker container runs
the <a class="reference external" href="http://skarnet.org/software/s6/">S6 service supervisor</a> using <a class="reference external" href="https://github.com/just-containers/s6-overlay/">s6-overlay</a>, like home assistant add-ons. It manages</p>
<ul class="simple">
<li>the Snapcast audio server, which starts <tt class="docutils literal">librespot</tt> for Spotify playback</li>
<li>a MPD daemon to play music and internet radio</li>
<li>a MPD daemon to play notifications</li>
</ul>
<p>The Snapcast server has a meta source type that will switch between audio sources
based on priority which is very powerful yet simple concept. I set it up to
prefer notifications over Spotify over music,</p>
<p>The Bluetooth speaker is integrated into home assistant using scripts and
a MQTT template 'switch', showing its connection status in home assistant and
providing Bluetooth disconnect and connect triggers when operating the switch
in home assistant.</p>
</div>
<div class="section" id="snapcast">
<h2>Snapcast</h2>
<p>The Snapcast website explains it best:</p>
<p>Snapcast is a multi-room client-server audio player, where all clients are time
synchronized with the server to play perfectly synced audio. It's not a
standalone player, but an extension that turns your existing audio player into
a Sonos-like multi-room solution.</p>
<div class="figure align-center">
<img alt="concept" src="https://www.thouters.be/img/HassMultiRoomAudio/snapcast.png" />
<p class="caption">Snapcast overview (from the snapcast github page)</p>
</div>
<p>Audio is captured by the server and routed to the connected clients. Several
players can feed audio to the server in parallel and clients can be grouped to
play the same audio stream. One of the most generic ways to use Snapcast is in
conjunction with the music player daemon (MPD) or Mopidy.</p>
<p>The Home Assistant Snapcast platform allows you to control Snapcast from Home Assistant.</p>
<p>To add Snapcast to your installation, add the following to your <tt class="docutils literal">configuration.yaml</tt> file:</p>
<div class="highlight"><pre><span></span><span class="c1"># Example configuration.yaml entry</span><span class="w"></span>
<span class="nt">media_player</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">snapcast</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">host</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">YOUR_IP_ADDRESS</span><span class="w"></span>
</pre></div>
<div class="section" id="configuration">
<h3>Configuration</h3>
<p>The Snapserver is configured to accept audio from the MPD's provided at the same sample format of librespot.</p>
<p>The meta source does magic, it switches between sources based on the listed order.</p>
<div class="highlight"><pre><span></span><span class="k">[stream]</span><span class="w"></span>
<span class="na">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">pipe:///tmp/snapfifo?name=Music&sampleformat=44100:16:2</span><span class="w"></span>
<span class="na">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">pipe:///tmp/mpd-notify?name=Notify&sampleformat=44100:16:2</span><span class="w"></span>
<span class="na">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">spotify:///librespot?name=Spotify&bitrate=320&enable-volume-normalisation&sampleformat=44100:16:2</span><span class="w"></span>
<span class="na">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">meta:///Notify/Spotify/Music?name=Mixed&sampleformat=44100:16:2</span><span class="w"></span>
</pre></div>
</div>
</div>
<div class="section" id="mpd-music-player-daemon">
<h2>MPD Music player daemon</h2>
<p>The musicPD is an old-school tool, I believe I was already using it ~15 years ago. It is remarkably
elegant, has myriad applications, a nice network protocol and support for lots of stuff.</p>
<div class="section" id="playing-notifications">
<h3>Playing notifications</h3>
<p>To play a notification, the usual home assistant service can be called to play audio on the dedicated MPD:</p>
<div class="highlight"><pre><span></span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">media_player.play_media</span><span class="w"></span>
<span class="nt">data</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">media_player.mpd_notification</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">media_content_type</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">music</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">media_content_id</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">bark.wav</span><span class="w"></span>
</pre></div>
<p>After adding the audio files to the music directory, don't forget to update the MPD database.</p>
<p>I had some difficulties with the first few seconds of audio disappearing, which
I worked around until digging deeper. I used <tt class="docutils literal">sox</tt> to add a few seconds of silence:</p>
<div class="highlight"><pre><span></span>sox -n -r <span class="m">44100</span> -c <span class="m">2</span> silence.wav trim <span class="m">0</span>.0 <span class="m">1</span>.5
sox silence.wav bark.wav delayedbark.wav
</pre></div>
</div>
<div class="section" id="mpd-configuration">
<h3>MPD Configuration</h3>
<p>The MPD's are configured to have unique state, database and music paths.
They feed their data into their own FIFO (named pipe), and are set to the same (non-standard) sample
frequency librespot uses to avoid re-sampling by the Snapcast server.</p>
<div class="highlight"><pre><span></span><span class="n">audio_output</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="s">"fifo"</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="s">"my pipe"</span><span class="w"></span>
<span class="w"> </span><span class="n">path</span><span class="w"> </span><span class="s">"/tmp/snapfifo"</span><span class="w"></span>
<span class="w"> </span><span class="cp">#format "48000:16:2"</span>
<span class="w"> </span><span class="n">format</span><span class="w"> </span><span class="s">"44100:16:2"</span><span class="w"></span>
<span class="w"> </span><span class="n">mixer_type</span><span class="w"> </span><span class="s">"software"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="mpd-home-assistant-integration">
<h3>MPD Home Assistant integration</h3>
<p>Both MPD's have an entry in the Home Assistant <tt class="docutils literal">configuration.yaml</tt>:</p>
<div class="highlight"><pre><span></span><span class="c1"># Example configuration.yaml entry</span><span class="w"></span>
<span class="nt">media_player</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mpd</span><span class="w"></span>
<span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">IP_ADDRESS</span><span class="w"></span>
<span class="w"> </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">PORT</span><span class="w"></span>
</pre></div>
<p>They run on the same host, but a different port.</p>
</div>
</div>
<div class="section" id="librespot">
<h2>Librespot</h2>
<p>Librespot allows the Snapcast source to appear as a Spotify speaker on the local network.
It announces itself using multicast DNS and automatically appears to local Spotify players.</p>
<p>Very little configuration at all (just the name it has to announce).</p>
<p>Wonderful piece of kit <3.</p>
</div>
<div class="section" id="raspberry-pi-with-fixed-speaker">
<h2>Raspberry pi with fixed speaker</h2>
<p>The only remarkable thing here is that I use the hardware mixer, this allows
control of the master volume via Snapcast.</p>
<div class="highlight"><pre><span></span>pi@living:~ $ cat /etc/default/snapclient
<span class="c1"># Start the client, used only by the init.d script</span>
<span class="nv">START_SNAPCLIENT</span><span class="o">=</span><span class="nb">true</span>
<span class="c1"># Additional command line options that will be passed to snapclient</span>
<span class="c1"># note that user/group should be configured in the init.d script or the systemd unit file</span>
<span class="c1"># For a list of available options, invoke "snapclient --help"</span>
<span class="nv">SNAPCLIENT_OPTS</span><span class="o">=</span><span class="s2">"-h 192.168.x.xxx --hostID living --mixer hardware"</span>
</pre></div>
<p>On Raspberry pi 2 with Ubuntu 21.01 with a HDMI screen connected, some extra configuration was needed:</p>
<div class="highlight"><pre><span></span><span class="nv">SNAPCLIENT_OPTS</span><span class="o">=</span><span class="s2">"-h 192.168.x.xxx --hostID living --mixer hardware:Headphone -s plughw:CARD=Headphones,DEV=0"</span>
</pre></div>
</div>
<div class="section" id="raspberry-pi-with-bluetooth-speaker">
<h2>Raspberry pi with Bluetooth speaker</h2>
<p>I found <a class="reference external" href="https://www.whirlwind.nl/nieuw/bericht/bluetooth-speaker-koppelen-aan-snapcast-op-raspberry-pi">a nice guide (dutch only) on using a bluetooth speaker with snapcast on the raspberry pi</a>, which guided me to install <a class="reference external" href="https://github.com/bablokb/pi-btaudio">https://github.com/bablokb/pi-btaudio</a> .</p>
<div class="figure align-center">
<img alt="photo of the bluetooth speaker and dongle" src="https://www.thouters.be/img/BluetoothSpeakerOnDebianSid/cover.jpg" />
</div>
<p>I ended up <a class="reference external" href="https://www.thouters.be/BluetoothSpeakerOnDebianSid.html">creating my own helper scripts and wrote a separate article about this</a>.
This configuration of software packages and scripts supports:</p>
<ul class="simple">
<li>starting/stopping snapclient and a Bluetooth button event watcher.</li>
<li>network volume control of the Bluetooth speaker</li>
<li>re-initiating Bluetooth connection from home assistant</li>
</ul>
</div>
<div class="section" id="home-assistant-add-on">
<h2>Home assistant add-on</h2>
<p>I had a bit of a bad experience trying to create a Snapcast Home Assistant add-on.
I built a container image based on the example and added the Snapcast server.</p>
<p>My local Add-on did not show up on the Supervisor tab (using { "image": }).</p>
<p>When I omitted the local image reference, the supervisor tried to build it for the wrong
architecture (ARM7 instead of aarch64/arm8).</p>
<p>A long-term goal is to package this as an add-on, for now I run everything as a container
on my main server.</p>
</div>
<div class="section" id="more-information">
<h2>More information</h2>
<p>I still haven't published info on my snapclient-server-side docker container. TODO...</p>
</div>
openHASP touch display build2021-02-07T21:00:00+01:002021-02-07T21:00:00+01:00Thomas Langewouterstag:www.thouters.be,2021-02-07:/HaspLvglBuild.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="openHASP running in a 3D printed case" src="https://www.thouters.be/img/HaspLvglBuild/desk.jpg"/>
<p class="caption">openHASP running in a 3D printed case</p>
</div>
<p>I discovered <a class="reference external" href="https://github.com/HASwitchPlate/openHASP">openHASP</a> while I was searching for projects
integrating touch interfaces with Home Assistant.</p>
<p>It's easy to wire up a cheap ESP microcontroller board and TFT display module,
and by loading this opensource firmware you can turn it into a network
connected touch control panel and control devices and display things.</p>
<p>When I bumped into a nice <a class="reference external" href="https://www.thingiverse.com/thing:2172068">2.8" TFT Desktop stand enclosure</a> on thingiverse,
I remembered I had an unused 2.8" ILI9341 display with resistive touch layer
<a class="reference external" href="https://www.thouters.be/SpiTftDeviceTree.html">from a previous project</a>.</p>
<p>Connecting the dots... I had all the necessary parts in my workshop, this would
make a nice project to make over a weekend evening.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="openHASP running in a 3D printed case" src="https://www.thouters.be/img/HaspLvglBuild/desk.jpg" />
<p class="caption">openHASP running in a 3D printed case</p>
</div>
<p>I discovered <a class="reference external" href="https://github.com/HASwitchPlate/openHASP">openHASP</a> while I was searching for projects
integrating touch interfaces with Home Assistant.</p>
<p>It's easy to wire up a cheap ESP microcontroller board and TFT display module,
and by loading this opensource firmware you can turn it into a network
connected touch control panel and control devices and display things.</p>
<p>When I bumped into a nice <a class="reference external" href="https://www.thingiverse.com/thing:2172068">2.8" TFT Desktop stand enclosure</a> on thingiverse,
I remembered I had an unused 2.8" ILI9341 display with resistive touch layer
<a class="reference external" href="https://www.thouters.be/SpiTftDeviceTree.html">from a previous project</a>.</p>
<p>Connecting the dots... I had all the necessary parts in my workshop, this would
make a nice project to make over a weekend evening.</p>
<div class="section" id="openhasp-features">
<h2>OpenHASP Features</h2>
<p>The main job of openHASP is to display a user interface on a TFT display,
and expose it over MQTT, the serial port or a socket.</p>
<p>This makes it ideal to control devices over WiFi and display state and status.</p>
<p>The interface is based on the concept of pages, each is screen-sized, and
can be called onto the screen. Page 0 is special and can be used as an overlay
with e.g. time and/or navigation tabs.</p>
<p>The pages are defined using lines of JSON, and pushed over MQTT or uploaded
over the built-in HTTP web server.</p>
<p>The state of the widgets can be modified over MQTT topics, and when a user
operates the touch interface, MQTT messages are published.</p>
</div>
<div class="section" id="bill-of-materials">
<h2>Bill of materials</h2>
<p>I used a TTGO Mini32 module, which can be found on aliexpress for about 7 Euro including shipping.
The TFT would be something like "240x320 2.8" SPI TFT LCD Touch Panel Module ILI9341 with Touch Pen",
currently available for about 10 Euro including shipping.</p>
</div>
<div class="section" id="hardware">
<h2>Hardware</h2>
<p>I kicked things off by printing the enclosure, since this requires the least
amount of effort and provides tangible motivation to solve any roadblocks I
would encounter.</p>
<p>Downloading a file, <a class="reference external" href="https://www.thouters.be/PrusaMk3s.html">slicing it and printing it on m Prusa Mk3s</a> is
routine to me, and a few hours later, I had something that fitted the TFT
display perfectly. Thank you for sharing your design, smily77!</p>
<div class="figure align-center">
<img alt="openHASP running in a 3D printed case" src="https://www.thouters.be/img/HaspLvglBuild/back.jpg" />
<p class="caption">The 3D printed enclosure from the back</p>
</div>
<p>I used a TTGO Mini32 ESP32 board, since I didn't want to cheap out on
processing resources. The enclosure had a support for the Wemos D1 mini
however, so I had to use a knife and pliers to remove some plastic to fit the
board.</p>
<p>I fixed the MINI32 board by using two small screws into two holes I pre-drilled
into the enclosure using a screwdriver-style hand drill and a <2mm drill bit
(well worth having that cheap tool in your toolbox!).</p>
<p>I soldered wire wrapping wires between the TFT and ESP32 boards, and fixed the
ESP32 board in place. (The wires I used are a bit long considering 27Mhz SPI,
but no issues seen)</p>
<div class="figure align-center">
<img alt="openHASP running in a 3D printed case" src="https://www.thouters.be/img/HaspLvglBuild/bottom.jpg" />
<p class="caption">TTGO Mini32 wired to the screen</p>
</div>
<p>I hot-glued the display into place, making sure not to put any stress onto the
resistive touch layer.</p>
</div>
<div class="section" id="software">
<h2>Software</h2>
<div class="section" id="building-a-custom-firmware">
<h3>Building a custom firmware</h3>
<p>I followed the <a class="reference external" href="https://haswitchplate.github.io/openHASP-docs/#compiling">instructions to build openHASP</a>, but used a modified <tt class="docutils literal">platformio.ini</tt>:</p>
<div class="highlight"><pre><span></span><span class="o">[</span>platformio<span class="o">]</span>
<span class="nv">extra_configs</span> <span class="o">=</span>
thomas_secrets.ini
user_setups/esp32/thomas.ini
</pre></div>
<p>Upload using</p>
<div class="highlight"><pre><span></span>pio run -t upload --upload-port<span class="o">=</span>/dev/ttyUSB0
</pre></div>
<p>The <tt class="docutils literal">thomas_secrets.ini</tt> file contains predefinitions for my WIFI and MQTT setup. The
<tt class="docutils literal">custom user_setups ini</tt> file contains modifications to use the ILI9341 display with
my own wiring:</p>
<div class="highlight"><pre><span></span><span class="k">[env:d1-mini-esp32_ili9341]</span><span class="w"></span>
<span class="na">extends</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">esp32</span><span class="w"></span>
<span class="na">board</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">wemos_d1_mini32</span><span class="w"></span>
<span class="na">build_flags</span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="na">${env.build_flags}</span><span class="w"></span>
<span class="w"> </span><span class="na">${esp32.build_flags}</span><span class="w"></span>
<span class="w"> </span><span class="na">-D HASP_MODEL</span><span class="o">=</span><span class="s">"ESP32 D1 Mini"</span><span class="w"></span>
<span class="w"> </span><span class="na">-D ILI9341_DRIVER</span><span class="o">=</span><span class="s">1</span><span class="w"></span>
<span class="w"> </span><span class="na">-D USER_SETUP_LOADED</span><span class="o">=</span><span class="s">1</span><span class="w"></span>
<span class="w"> </span><span class="na">${touch.xpt2046}</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_ROTATION</span><span class="o">=</span><span class="s">0 ; 0=0, 1=90, 2=180 or 3=270 degree</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_WIDTH</span><span class="o">=</span><span class="s">240</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_HEIGHT</span><span class="o">=</span><span class="s">320</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_MISO</span><span class="o">=</span><span class="s">19 ;// (leave TFT SDO disconnected if other SPI devices share MISO)</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_MOSI</span><span class="o">=</span><span class="s">23</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_SCLK</span><span class="o">=</span><span class="s">18</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_CS</span><span class="o">=</span><span class="s">26 ;// Chip select control pin</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_DC</span><span class="o">=</span><span class="s">16 ;// Data Command control pin</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_RST</span><span class="o">=</span><span class="s">17 ;// Reset pin (could connect to RST pin)</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TFT_BCKL</span><span class="o">=</span><span class="s">5 ;None, configurable via web UI (e.g. 2 for D4)</span><span class="w"></span>
<span class="w"> </span><span class="na">-D SUPPORT_TRANSACTIONS</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TOUCH_CS</span><span class="o">=</span><span class="s">22</span><span class="w"></span>
<span class="w"> </span><span class="na">-D TOUCH_DRIVER</span><span class="o">=</span><span class="s">2046 ; XPT2606 Resistive touch panel driver</span><span class="w"></span>
<span class="w"> </span><span class="na">-D SPI_FREQUENCY</span><span class="o">=</span><span class="s">27000000</span><span class="w"></span>
<span class="w"> </span><span class="na">-D SPI_TOUCH_FREQUENCY</span><span class="o">=</span><span class="s">2500000</span><span class="w"></span>
<span class="w"> </span><span class="na">-D SPI_READ_FREQUENCY</span><span class="o">=</span><span class="s">16000000</span><span class="w"></span>
<span class="c1">;endregion</span><span class="w"></span>
<span class="c1">;endregion</span><span class="w"></span>
<span class="c1">;region -- Library options -------------------------------</span><span class="w"></span>
<span class="na">lib_deps</span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="na">${env.lib_deps}</span><span class="w"></span>
<span class="w"> </span><span class="na">${esp32.lib_deps}</span><span class="w"></span>
<span class="na">lib_ignore</span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="na">${env.lib_ignore}</span><span class="w"></span>
<span class="w"> </span><span class="na">${esp32.lib_ignore}</span><span class="w"></span>
<span class="c1">;endregion</span><span class="w"></span>
</pre></div>
<p>It was certainly not rocket science to make these adjustments, luckily the
openHASP developers were very helpful with the more daunting questions I had when
bringing up this project without reading all the documentation.</p>
</div>
<div class="section" id="hello-world">
<h3>Hello world</h3>
<p>openHASP supports most <a class="reference external" href="https://docs.lvgl.io/latest/en/html/widgets/index.html">LVGL widgets</a> I played with when getting to know LVGL during my <a class="reference external" href="https://www.thouters.be/MoodControlPoc.html">Mood Control</a> project.
I picked the example 'Dash UI page` from the documentation to play with once board bring-up was finished and
WiFi and my MQTT server were connected.</p>
<p>It's easy to interact with the display over MQTT.</p>
<p>I used these mosquitto commands to modify the display's widget state:</p>
<p>To update the temperature arc 'dial' to 20 degrees:</p>
<div class="highlight"><pre><span></span>mosquitto_pub -h hass -u <span class="nv">$MQTT_USER</span> -P <span class="nv">$MQTT_PASS</span> -t hasp/plate_c45378/command/p1b2.value -m <span class="s2">"20"</span>
</pre></div>
<p>To update the humidity arc indicator text to 90%</p>
<div class="highlight"><pre><span></span>mosquitto_pub -h hass -u <span class="nv">$MQTT_USER</span> -P <span class="nv">$MQTT_PASS</span> -t hasp/plate_c45378/command/p1b3.value_str -m <span class="s2">"90%"</span>
</pre></div>
</div>
<div class="section" id="making-it-useful">
<h3>Making it useful</h3>
<p>I intend to use this build as a replacement-on-steroids for my <a class="reference external" href="https://www.thouters.be/HomeAssistantMediaRemote.html">IKEA hockey
puck control</a> that I use with home assistant, but that's for another day.</p>
</div>
<div class="section" id="shout-out">
<h3>Shout-out</h3>
<p>A big thank-you to the people creating HASP and openHASP, the 3D design and
all free-software home automation projects!</p>
</div>
</div>
lighting scene / mood control proof of concept2021-01-23T21:00:00+01:002021-01-24T21:00:00+01:00Thomas Langewouterstag:www.thouters.be,2021-01-23:/MoodControlPoc.html<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="twatch running lighting scene selector" src="https://www.thouters.be/img/MoodControlPoc/handheld.jpg"/>
<p class="caption">Lilygo twatch 2020 running simple lighting scene (mood) control proof of concept</p>
</div>
<p>I discovered a lovely smart watch development platform; the Lilygo t-watch 2020.
It's an ESP32 based hackable watch, with a small capacitive touch display.</p>
<p>I had already played with the idea of building a small home automation control
and status display that fits into the existing switch cover plate, so clicked
the order button and leveraged the open source watch firmware to quickly hack
together a proof of concept.</p>
<!-- vim:ft=rst:spell:spelllang=en -->
<div class="figure align-center">
<img alt="twatch running lighting scene selector" src="https://www.thouters.be/img/MoodControlPoc/handheld.jpg" />
<p class="caption">Lilygo twatch 2020 running simple lighting scene (mood) control proof of concept</p>
</div>
<p>I discovered a lovely smart watch development platform; the Lilygo t-watch 2020.
It's an ESP32 based hackable watch, with a small capacitive touch display.</p>
<p>I had already played with the idea of building a small home automation control
and status display that fits into the existing switch cover plate, so clicked
the order button and leveraged the open source watch firmware to quickly hack
together a proof of concept.</p>
<div class="section" id="concept">
<h2>Concept</h2>
<p>I created a small demonstration video to show you how it works.</p>
<span class="videobox">
<video width='100%' height='480' preload='none' controls poster='https://www.thouters.be/img/MoodControlPoc/demo.jpg'>
<source src='https://www.thouters.be/img/MoodControlPoc/demo.mp4' type='video/mp4; '/>
</video>
</span><p>The user interface features 5 tiles, each showing the icon of a scene, as a vertical list.
You can navigate the list by swiping the UI up and down. The active lighting scene is
highlighted in bright green (same color scheme as my <a class="reference external" href="https://www.thouters.be/HomeAssistantTouchDisplayOne.html">main home automation touchscreen</a>).
Activating a scene can be done by ticking it.</p>
<p>The state of the control is synchronised with home assistant via MQTT.</p>
</div>
<div class="section" id="the-watch">
<h2>The watch</h2>
<div class="section" id="watch-hardware">
<h3>Watch hardware</h3>
<p>The watch features everything to make it a good remote control, except perhaps
battery capacity. It's a bit small to use as such, but perhaps a bar shaped
enclosure and larger lithium battery could fix that.</p>
<div class="figure align-center">
<img alt="twatch features" src="https://www.thouters.be/img/MoodControlPoc/watch.jpg" />
<p class="caption">Lilygo twatch 2020 feature sheet</p>
</div>
</div>
<div class="section" id="stock-firmware">
<h3>Stock firmware</h3>
<p>The stock firmware codebase that comes with the watch is pretty nice, it features</p>
<ul class="simple">
<li>A modular codebase</li>
<li>use of the Arduino framework</li>
<li>platformio configuration files</li>
<li>use of libraries to make all hardware work</li>
<li>use of the embedded <a class="reference external" href="https://lvgl.io/">Light and Versatile Graphics Library</a> as GUI toolkit</li>
<li>Example applications</li>
</ul>
</div>
</div>
<div class="section" id="mechanical-integration">
<h2>Mechanical integration</h2>
<p>I hope to integrate the display into a blind faceplate.</p>
<div class="figure align-center">
<img alt="twatch running lighting scene selector" src="https://www.thouters.be/img/MoodControlPoc/fitinframe.jpg" />
<p class="caption">90's niko switchplate formfactor</p>
</div>
<p>I'm aiming for the more modern cover plates, which should be possible.
But fitting it in the older (1990's style?) plates will be easier
since it won't require removing the display from the chassis.</p>
<div class="figure align-center">
<img alt="Niko wall socket" src="https://www.thouters.be/img/MoodControlPoc/wallsocket.jpg" />
<p class="caption">Modern Niko wall socket</p>
</div>
<p>I tried to remove the display panel from the watch a few times
by applying heat to soften the glue. This is how smartphone
glass panels are removed. That did not work with the amount
of heat I dared applying. At least I didn't burn the display.</p>
</div>
<div class="section" id="proof-of-concept-development">
<h2>Proof of concept development</h2>
<p>I pushed the hacked watch firmware to
<a class="reference external" href="http://www.github.com/thouters/MoodControlPoc">http://www.github.com/thouters/MoodControlPoc</a> in case you want to have a look
or replicate this.</p>
<p>As of 2020/12/24, it can be called 'proof of concept', with these issues still present:</p>
<ul class="simple">
<li>The watch still sleeps, and after waking it up, it has to reconnect to WiFi and the MQTT broker</li>
<li>you can still uncheck the icons (implemented as checkable image buttons).</li>
<li>the green color (state indicator) (as server updates are received) is not reflected on the icons</li>
</ul>
<div class="section" id="adding-icon-images">
<h3>Adding icon images</h3>
<p>I have 5 lighting scenes, and each has an associated icon.</p>
<style> .blacken {background-color:black} </style><div class="figure align-center">
<img alt="spotlight png" class="blacken" src="https://www.thouters.be/img/MoodControlPoc/track-light.png" />
<p class="caption">track-light icon converted to png and bucket-filled to white</p>
</div>
<p>I followed these steps to get them into the firmware</p>
<ul class="simple">
<li>download the SVG vector art from the material design icons website</li>
<li>import into gimp at 180x180 resolution</li>
<li>bucket-fill the black parts to white</li>
<li>export as png</li>
<li>use <tt class="docutils literal">img_conv.core.php</tt> to convert it to a c array</li>
<li>change the <tt class="docutils literal">include <span class="pre">"../lvgl/lvgl.h"</span></tt> line to <tt class="docutils literal">include "lvgl/lvgl.h"</tt></li>
</ul>
<p>I used <a class="reference external" href="https://github.com/lvgl/lv_utils">https://github.com/lvgl/lv_utils</a> to convert the images to LVGL-supported C arrays:</p>
<div class="highlight"><pre><span></span>php img_conv_core.php <span class="s2">"name=book_open_page_variant&img=book-open-page-variant.png&format=c_array&cf=true_color_alpha"</span>
</pre></div>
</div>
</div>
<div class="section" id="development-testing">
<h2>development testing</h2>
<p>to verify the watch sends status updates</p>
<div class="highlight"><pre><span></span>mosquitto_sub -h hass -u mqtt -P mqttpass -t twatch -v
mosquitto_pub -h hass -u mqtt -P mqttpass -t twatch -m <span class="s2">"TV"</span>
</pre></div>
</div>
<div class="section" id="integration-with-home-assistant">
<h2>Integration with Home Assistant</h2>
<p>I created two automations in Home Assistant that feed back the state of the
watch UI and the state of the input_select to each other.</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">moodliving_mqtt_out</span><span class="w"></span>
<span class="w"> </span><span class="nt">trigger</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">state</span><span class="w"></span>
<span class="w"> </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">input_select.mood_living</span><span class="w"></span>
<span class="w"> </span><span class="nt">action</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mqtt.publish</span><span class="w"></span>
<span class="w"> </span><span class="nt">data_template</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="s">"twatch"</span><span class="w"></span>
<span class="w"> </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">states('input_select.mood_living')</span><span class="nv"> </span><span class="s">}}"</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">moodliving_mqtt_in</span><span class="w"></span>
<span class="w"> </span><span class="nt">trigger</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mqtt</span><span class="w"></span>
<span class="w"> </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="s">"twatch"</span><span class="w"></span>
<span class="w"> </span><span class="nt">action</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">input_select.select_option</span><span class="w"></span>
<span class="w"> </span><span class="nt">data</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">input_select.mood_living</span><span class="w"></span>
<span class="w"> </span><span class="nt">option</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">trigger.payload</span><span class="nv"> </span><span class="s">}}"</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="existing-opensource-touch-ui-s">
<h2>Existing opensource touch UI's</h2>
<p>If I were to take this project to the next level, building upon the
work of existing open source home automation projects is essential.</p>
<div class="section" id="esphome-firmware">
<h3>ESPHOME firmware</h3>
<p>I already use <a class="reference external" href="https://esphome.io/">ESPHome</a> a lot, for <a class="reference external" href="https://www.thouters.be/Standingstats.html">my standing desk tracking</a>, <a class="reference external" href="https://www.thouters.be/EspHomeLedLighting.html">LED strip lighting</a>.
It's a project that leverages code generation to enable a lot of sensors
and actuators to work with DIY hardware.</p>
<p>ESPHome already supports graphical displays, and it would require adding
LVGL support and support for generating templates with LVGL instructions
to generate UI's.</p>
</div>
<div class="section" id="hasp-home-automation-switch-plate">
<h3>HASP Home Automation switch plate</h3>
<p><a class="reference external" href="http://www.haswitchplate.com">HA SwitchPlate</a> is an established project that uses Nextion human-machine interface (HMI) modules
together with ESP micro's to build a touchscreen. The UI's look a bit
dated and setting it up quite involving.</p>
<p><a class="reference external" href="https://fvanroie.github.io/hasp-docs/">HASP Open Hardware edition</a> is a fork that gets rid of the proprietary
touchscreen modules and uses the LVGL as well.</p>
</div>
</div>