<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Long-term Memory</title><link href="https://blog.dest-unreach.be/" rel="alternate"/><link href="https://blog.dest-unreach.be/feed" rel="self"/><id>https://blog.dest-unreach.be/</id><updated>2026-05-07T00:00:00+02:00</updated><subtitle>A collection of note-to-self's</subtitle><entry><title>Controlling SMA inverters via ModBus</title><link href="https://blog.dest-unreach.be/2026/05/07/SMA_Modbus/" rel="alternate"/><published>2026-05-07T00:00:00+02:00</published><updated>2026-05-07T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2026-05-07:/2026/05/07/SMA_Modbus/</id><summary type="html">&lt;p&gt;Harnessing the power of solar energy has become an important step toward a sustainable and energy-efficient home.
An key component are solar inverters and home battery systems,
which work together to convert sunlight into usable electricity and store excess energy for later use.
Controlling these systems effectively can maximize energy …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Harnessing the power of solar energy has become an important step toward a sustainable and energy-efficient home.
An key component are solar inverters and home battery systems,
which work together to convert sunlight into usable electricity and store excess energy for later use.
Controlling these systems effectively can maximize energy savings,
but can also optimize the cost/profit when combined with a dynamic energy contract.&lt;/p&gt;
&lt;p&gt;Most vendors provide some form of energy management system with their home batteries.
It typically uses solar surplus power to charge the battery,
and uses battery power to cover periods with solar deficit.
But what if you want to something more fancy?&lt;/p&gt;
&lt;p&gt;I have a system from &lt;a href="https://www.sma.de/"&gt;SMA&lt;/a&gt;.
Their Energy Management System (EMS) is called the &lt;a href="https://www.sma.de/en/products/energy-management/sunny-home-manager"&gt;Home Manager&lt;/a&gt;,
and it works fairly well, but also has some downsides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[+] It does basic PV surplus to battery and PV deficit from battery&lt;/li&gt;
&lt;li&gt;[+] It supports "peak shaving" to optimize around the "capacity tariff"
   (a tariff in Flanders that is related to your peak power demand as opposed to energy consumption).&lt;/li&gt;
&lt;li&gt;[-] Management of your settings is only possible through the SMA cloud via the internet,
   and it takes about a minute for changes to be applied.&lt;/li&gt;
&lt;li&gt;[-] The peak shaving algorithm is suboptimal. The peaks are calculated as total energy per 15 minutes.
   This means that you can go over your peak during the last 5 minutes if you were under in the first 10 minutes.
   The HM does not use this opportunity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Luckily, you can also ditch the Home Manager, and control the inverters yourself via Modbus.
Here is what I found out about that.&lt;/p&gt;
&lt;h2 id="sunny-boy"&gt;&lt;a class="toclink" href="#sunny-boy"&gt;Sunny Boy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My first inverter is a &lt;a href="https://www.sma.de/en/products/solarinverters/sunny-boy-30-36-40-50-60"&gt;Sunny Boy&lt;/a&gt;.
SMA publishes the Modbus registers in the Downloads section, so that made things a lot easier.
One thing to note is that most writeable parameters have a "Warning against cyclical writing".
Since these parameters are committed to Flash every time you change them,
doeing so often will wear out the storage of the inverter.&lt;/p&gt;
&lt;p&gt;There are four registers that seem interesting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;40016, Normalized active power limitation by PV system control, &lt;code&gt;Inverter.WModCfg.WCtlComCfg.WNom&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;40023, Normalized active power limitation by PV system control, &lt;code&gt;Inverter.WModCfg.WCtlComCfg.WNomPrc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;40149, Active power setpoint, &lt;code&gt;Inverter.WModCfg.WCtlComCfg.WSpt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;44425, Active power setpoint, &lt;code&gt;Inverter.WModCfg.WCtlComCfg.WSpt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On my machine, both "Active power setpoint" registers didn't seem to do anything.
Writing to 40023 however does work. Here is a Python snippet to curtail to 1% production:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pyModbusTCP.client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModbusClient&lt;/span&gt;

&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;192.0.2.1&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;unit_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;reg_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;   Encode a value into a list of integers to write to sequential registers.&lt;/span&gt;
&lt;span class="sd"&gt;   &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;big&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;big&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;reg_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;   Decode a series of registers into the corresponding value.&lt;/span&gt;
&lt;span class="sd"&gt;   &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;reg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;big&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;reg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;big&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;   

&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ModbusClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;unit_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auto_open&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;grid_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reg_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_holding_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30775&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Current AC power: &lt;/span&gt;&lt;span class="si"&gt;{grid_out}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;curtail_percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_multiple_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40023&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reg_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curtail_percent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the response isn't immediate. This means care should be taken to avoid oscillating.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step response" src="https://blog.dest-unreach.be/2026/05/07/SMA_Modbus/sma_modbus/sb36-step-response.png"&gt;&lt;/p&gt;
&lt;p&gt;But if you give the inverter a couple of seconds to stabilize (3 seconds for the graph below),
the control is surprisingly linear.
The dips starting at 25% are clouds ruining my test. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Power modulation curve" src="https://blog.dest-unreach.be/2026/05/07/SMA_Modbus/sma_modbus/sb36-modulation.png"&gt;&lt;/p&gt;
&lt;p&gt;And it doesn't seem to be possible to modulate entirely down to 0%:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Power modulation curve, low end" src="https://blog.dest-unreach.be/2026/05/07/SMA_Modbus/sma_modbus/sb36-modulation-zoom.png"&gt;&lt;/p&gt;
&lt;h2 id="sunny-boy-smart-energy"&gt;&lt;a class="toclink" href="#sunny-boy-smart-energy"&gt;Sunny Boy Smart Energy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The second machine is a hybrid invertor, the &lt;a href="https://www.sma.de/en/products/hybrid-inverters/sunny-boy-smart-energy"&gt;Sunny Boy Smart Energy (SBSE)&lt;/a&gt;.
Besides solar panels, it also has a battery attached for energy storage.
This means that I want to have control over battery charging and discharging, as well as PV curtailment.
Here also, you can find the Modbus registers in the downloads section. &lt;/p&gt;
&lt;p&gt;It took a bit of experimenting, but the following registers seem to work for curtailing and battery management:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;41431 and 41433: Maximum/Minimum active power,
   &lt;code&gt;Setpoint.PlantControl.Inverter.WModCfg.WCtlComCfg.WSptMax&lt;/code&gt; and &lt;code&gt;...WSptMin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;41467 and 41469: Max/Min setpoint value for active power of storage,
   &lt;code&gt;Setpoint.PlantControl.Bat.WCtlCom.WSptMax&lt;/code&gt; and &lt;code&gt;...WSptMin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both are marked as "Device Control Object", so it should be safe to write to those often.
The second pair of registers (41467 and 41469) seem to control the generation power of the battery.
So positive values mean discharging, negative values mean changing.
When being controlled by the Home Manager, I can see that the WSptMin stays at -15000 W,
while the WSptMax is constantly modified to match the current situation.
When it's sunny it goes down to -1000 W (charging the battery),
when a cloud takes the sunlight away, it goes up to +500 W (discharging the battery).&lt;/p&gt;
&lt;p&gt;The following Python snipped will set your battery to charge at 500 W.
Note to disconnect your Home Manager, since it will overwrite this setting immediately.
Also note that the inverter may have a timeout and fallback configured,
so it may stop doing what you sent after some time. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# ... Same boilerplate as above ...&lt;/span&gt;

&lt;span class="n"&gt;bat_ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reg_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_holding_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;31393&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bat_dis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reg_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_holding_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;31395&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bat_max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reg_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_holding_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41467&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;bat_min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reg_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_holding_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41469&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Battery power: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bat_ch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; W ch, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bat_dis&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; W disch = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bat_dis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bat_ch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; W from battery; setpoint [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bat_min&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bat_max&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;storage_power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_multiple_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41467&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;reg_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_power&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;reg_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_power&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The other pair of registers (41431 and 41433) control the output of the whole inverter towards the grid.
Here also, positive values mean energy is being pushed towards the grid,
negative values means the inverter is consuming power.
Typically, the Home Manager leaves these at -5000 W and 5000 W for my 5kW unit,
although I've seen it put the WSptMax lower.&lt;/p&gt;
&lt;p&gt;Since the settings are ranges, the inverter typically does "the right thing".
For example, with 700W of solar, and a set maximum of 500W grid export,
the inverter will put excess PV into the battery.
Note that these control changes happen slowly, over about 5 seconds.&lt;/p&gt;
&lt;p&gt;&lt;img alt="SBSE5 control response" src="https://blog.dest-unreach.be/2026/05/07/SMA_Modbus/sma_modbus/sbse5-bat-disch.png"&gt;&lt;/p&gt;
&lt;p&gt;Requesting the battery to discharge at 1.5kW, while limiting the grid output to 1kW causes the inverter to reduce the
battery discharge and keep within the 1kW grid limit.
This is expected, since the output from the battery is still within the configured limits of -15kW and +1.5kW.
Here the control slope is limited to about 23W/s.&lt;/p&gt;
&lt;p&gt;&lt;img alt="SBSE5 control response" src="https://blog.dest-unreach.be/2026/05/07/SMA_Modbus/sma_modbus/sbse5-bat-disch-grid-limit.png"&gt;&lt;/p&gt;
&lt;p&gt;We can also give conflicting instructions. 
Say you command "discharge the battery between -15kW and -2kW" (i.e. charge at 2kW),
and you limit "grid output between -500W and +5kW" (i.e. max 500W consumption).
If there is not enough solar power, the inverter will initially follow the battery constraint,
but after about 30 seconds gently fall back to get within the grid constraints.&lt;/p&gt;
&lt;p&gt;&lt;img alt="SBSE5 control response" src="https://blog.dest-unreach.be/2026/05/07/SMA_Modbus/sma_modbus/sbse5-bat-grid-conflict.png"&gt;&lt;/p&gt;
&lt;p&gt;Let's say you are on a Dynamic Tariff and there are highly negative prices around midday.
You may want to discharge the battery now (while you are still getting paid for putting energy on the grid),
so that you can recharge it later from grid power when the prices are negative
(and thus you are getting paid to consume energy).
To drain your battery now, you want to command "discharge between +5kW and +5kW".
But there is no direct control over PV power,
so the actual discharge rate is limited by the 5kW grid output minus the PV production.
So if you really want to discharge your battery,
you need to walk up to the inverter and disconnect the PV arrays.&lt;/p&gt;
&lt;p&gt;Around midday that same hypothetical day, you want to configure for maximum grid usage.
Also here, since there is no direct control over PV generation, you are limited.
But since the battery can charge up to 10kW, the limit will be the energy storage capacity of the battery,
and not the grid power:
I tested "discharge between -15kW and -6kW" (i.e. charge) along with "grid export between -5kW and -4.5kW" (i.e. import).
This resulted in a 6kW charge, but only a 2.5kW grid import (+3.5kW from solar).
So the inverter ignored the grid control,
but this still allows you to pull in maximum 5kW from the grid as long as your battery has place to put the energy.&lt;/p&gt;</content><category term="energy"/></entry><entry><title>Replacing Lightroom</title><link href="https://blog.dest-unreach.be/2025/02/01/replacing-Lightroom/" rel="alternate"/><published>2025-02-01T00:00:00+01:00</published><updated>2025-02-01T00:00:00+01:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2025-02-01:/2025/02/01/replacing-Lightroom/</id><summary type="html">&lt;p&gt;We've been using Adobe Lightroom for the last 12 years to manage our family pictures.
I have enjoyed using it, but lately am getting more and more annoyed with Adobe's practices.
First there was &lt;a href="https://www.lightroomqueen.com/lightroom-classic-end-life/"&gt;the push to the cloud with the CC-series of their products&lt;/a&gt;.
And although you can still …&lt;/p&gt;</summary><content type="html">&lt;p&gt;We've been using Adobe Lightroom for the last 12 years to manage our family pictures.
I have enjoyed using it, but lately am getting more and more annoyed with Adobe's practices.
First there was &lt;a href="https://www.lightroomqueen.com/lightroom-classic-end-life/"&gt;the push to the cloud with the CC-series of their products&lt;/a&gt;.
And although you can still run Lightroom "Classic", I can't shake the feeling that Adobe isn't going to stop pulling me to their Cloud.
Next was &lt;a href="https://www.pcmag.com/news/adobe-sparks-backlash-over-ai-terms-that-let-it-access-view-your-content"&gt;the uproar of Adobe using my photo's to train their AI&lt;/a&gt;.
And although this turned out to be false, here again, I feel like they secretly would want to.
Then there's &lt;a href="https://finance.yahoo.com/news/the-us-has-sued-adobe-for-early-termination-fees-and-making-subscriptions-hard-to-cancel-165808358.html?guccounter=1"&gt;the absurd cancellation fees story&lt;/a&gt;. 
And finally, €18/month isn't really expensive, but I'm not getting €18 worth of value out of it.
We manage our holiday pictures, a couple of times a year;
we're not doing wedding photography 5 times a week.
So I set out to look for an alternative, starting with with the hard part: figuring out what we actually want and need.&lt;/p&gt;
&lt;p&gt;One of the nice things about Lightroom, is that it integrates the "management" and the "editing" function into one tool,
letting you seamlessly switch between developing and searching.
And although there are cases where quickly switching between these two is useful,
for example when creating a physical photo-album, most of the time I'm in one mode exclusively.
At the end of an event, I usually import the media and groom them.
This includes selecting the best picture from a burst, cropping down, adjusting the exposure.
When we have lots of pictures, e.g. from a longer holiday, I use the 1–5 stars to extract the highlights without making our family sit through a 350-picture slide-show.&lt;/p&gt;
&lt;p&gt;One thing that Lightroom (Classic) lacking in, was accessibility of the library.
Since it's a stand alone desktop app, you can only access your pictures on that single machine.
And while this was perfectly acceptable in 2012, nowadays, I would like to access my complete photo library from my smartphone while on the go, without actually having to have all pictures on my phone.
Privacy and control are very important to me, so I don't want to put my pictures in some cloud.
But I do want to have my pictures exposed via a web-interface and optionally an app.
This comes with the security risk that I have to secure this web access myself, but I believe I have the needed expertise to do so.&lt;/p&gt;
&lt;p&gt;Historically, our family pictures are stored as files in a directory tree, one directory per event, organized by year/month.
Really old events have only JPEGs.
Around 2012 we got a DSLR, so we switched to using RAW-files.
Since we've been using Lightroom, we usually didn't bother exporting JPEGs, and just kept working with the RAW files.
Looking back, this may not be the best way to do things: all edits we've made (crops, exposure adjustments, ...) as well as the selections (stacks, stars) are stored by Lightroom and are not (easily) transferable to whatever software we're migrating to. 
So I intend to change our workflow to export JPEGs from every event and use these instead, while keeping the RAW files tucked a level deeper.&lt;/p&gt;
&lt;p&gt;The library management software I'm looking for should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be self-hosted&lt;/li&gt;
&lt;li&gt;Have a web-interface to view photos &amp;amp; movies.&lt;/li&gt;
&lt;li&gt;Support the existing library of pictures, as-is, on disk, preferably read-only.
   This means support for JPEG/JFIF, HEIC/HEIF and Canon CR2 and CR3 raw files, as well as MPEG-TS and MP4 video's&lt;/li&gt;
&lt;li&gt;Be able to handle a significant library size. We currently have over 160k media items, and well over 2TB in data&lt;/li&gt;
&lt;li&gt;Have a faceted search tool to find pictures&lt;/li&gt;
&lt;li&gt;AI and/or Machine Learning for face detection, recognition and grouping&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nice to haves are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ability to share/export an album&lt;/li&gt;
&lt;li&gt;Have some form of user management, with the ability to share media between users&lt;/li&gt;
&lt;li&gt;AI and/or Machine Learning to extract descriptive metadata of a picture to enable "Smart search"&lt;/li&gt;
&lt;li&gt;The ability to show pictures on a map, based on the embedded geotagging. Bonus points if it can handle the format that Lightroom uses to put geotags in XMPs and JPEGs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the editing part, I don't think we're power users. What we need is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compatibility with our DSLRs (Canon)&lt;/li&gt;
&lt;li&gt;Lossless editing. This will probably be software-specific&lt;/li&gt;
&lt;li&gt;Cropping &amp;amp; rotating&lt;/li&gt;
&lt;li&gt;Exposure, contrast and white balance adjustment; Ideally picture-wide as well as localized&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Others are nice to have, but not really hard requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Minor touch-ups, mostly spot removal&lt;/li&gt;
&lt;li&gt;Lens correction, vignetting correction&lt;/li&gt;
&lt;li&gt;HDR&lt;/li&gt;
&lt;li&gt;Panorama stitching&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-contenders"&gt;&lt;a class="toclink" href="#the-contenders"&gt;The contenders&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For the library management I tried both &lt;a href="https://www.photoprism.app/"&gt;PhotoPrism&lt;/a&gt; and &lt;a href="https://immich.app/"&gt;Immich&lt;/a&gt;.
I tried to import my library and tried to do some simulated activities such as find a particular photo in the library, and show an album to an (imagined) family member.
And although PhotoPrism didn't lack in any way, I liked Immich better.
I'm only using the &lt;a href="https://immich.app/docs/guides/external-library/"&gt;External Library&lt;/a&gt; mode for now to access my historic photos.
There's also &lt;a href="https://apps.nextcloud.com/apps/memories"&gt;Nextcloud Memories&lt;/a&gt; that seems similar, but I haven't tried that yet.
Check out &lt;a href="https://github.com/meichthys/foss_photo_libraries"&gt;Meichthys's comparison&lt;/a&gt; for a full feature comparison.&lt;/p&gt;
&lt;p&gt;For editing, I'm currently trying &lt;a href="https://skylum.com/luminar"&gt;Luminar Neo&lt;/a&gt; and &lt;a href="https://www.darktable.org/"&gt;DarkTable&lt;/a&gt;.&lt;/p&gt;</content><category term="sysadmin"/></entry><entry><title>Shapez.io machines</title><link href="https://blog.dest-unreach.be/2024/08/31/shapez/" rel="alternate"/><published>2024-08-31T00:00:00+02:00</published><updated>2024-08-31T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2024-08-31:/2024/08/31/shapez/</id><summary type="html">&lt;p&gt;&lt;a href="https://shapez.io/"&gt;Shapez.io&lt;/a&gt; is a factory simulation game where you need to design a factory to produce a given shape.
The needed shapes increase in difficulty in the first 25 levels, but stay constant after that.
Obviously, some factory designs are reusable, so I documented my factory-parts here.&lt;/p&gt;
&lt;p&gt;IF you want …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://shapez.io/"&gt;Shapez.io&lt;/a&gt; is a factory simulation game where you need to design a factory to produce a given shape.
The needed shapes increase in difficulty in the first 25 levels, but stay constant after that.
Obviously, some factory designs are reusable, so I documented my factory-parts here.&lt;/p&gt;
&lt;p&gt;IF you want to use any of these designs, you may find the mod &lt;a href="https://mod.io/g/shapez/m/blueprint-strings"&gt;blueprint strings&lt;/a&gt; useful.
It allows you to copy-paste buildings from/to the Shapez game as strings.
Also check out the &lt;a href="https://shapezio.fandom.com/wiki/Efficient_Designs"&gt;wiki&lt;/a&gt; for other designs that I didn't figure out on my own.&lt;/p&gt;</content><category term="games"/></entry><entry><title>Podman on ZFS</title><link href="https://blog.dest-unreach.be/2024/01/03/podman-on-zfs/" rel="alternate"/><published>2024-01-03T00:00:00+01:00</published><updated>2024-01-03T00:00:00+01:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2024-01-03:/2024/01/03/podman-on-zfs/</id><summary type="html">&lt;p&gt;When running rootless &lt;a href="https://podman.io/"&gt;podman&lt;/a&gt; on a ZFS mount, it defaults to the &lt;code&gt;vfs&lt;/code&gt; storage driver.
And while this works, it causes a huge waste of disk space by duplicating files for every layer.
Using &lt;code&gt;overlay&lt;/code&gt; solves this when running rootfull, but this is &lt;a href="https://github.com/openzfs/zfs/issues/8648"&gt;currently not supported&lt;/a&gt; on ZFS.
It should …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When running rootless &lt;a href="https://podman.io/"&gt;podman&lt;/a&gt; on a ZFS mount, it defaults to the &lt;code&gt;vfs&lt;/code&gt; storage driver.
And while this works, it causes a huge waste of disk space by duplicating files for every layer.
Using &lt;code&gt;overlay&lt;/code&gt; solves this when running rootfull, but this is &lt;a href="https://github.com/openzfs/zfs/issues/8648"&gt;currently not supported&lt;/a&gt; on ZFS.
It should arrive with ZFS 2.2, but that will probably only be available in &lt;a href="https://www.debian.org/releases/"&gt;Debian&lt;/a&gt; 13 Trixie somewhere in 2025.
However, we should have the same functionality with the FUSE-variant &lt;code&gt;fuse-overlay&lt;/code&gt;, with an additional performance penalty for the userspace roundtrip.&lt;/p&gt;
&lt;p&gt;For example, the current &lt;a href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt; image in &lt;code&gt;vfs&lt;/code&gt; mode takes up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;unshare&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;.local/share/containers

$&lt;span class="w"&gt; &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;system&lt;span class="w"&gt; &lt;/span&gt;reset
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;ghcr.io/home-assistant/home-assistant:2023.12.4
Trying&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;ghcr.io/home-assistant/home-assistant:2023.12.4...
Getting&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;signatures
Copying&lt;span class="w"&gt; &lt;/span&gt;blob&lt;span class="w"&gt; &lt;/span&gt;2651e446c136&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
Copying&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;003e99ae19&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
Writing&lt;span class="w"&gt; &lt;/span&gt;manifest&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;destination
Storing&lt;span class="w"&gt; &lt;/span&gt;signatures
003e99ae19b0c68a9cbb0c28e68cf843e12513287d1945c1b0b3d1e0eb246973

real&lt;span class="w"&gt;    &lt;/span&gt;16m1.174s
user&lt;span class="w"&gt;    &lt;/span&gt;0m58.612s
sys&lt;span class="w"&gt;     &lt;/span&gt;1m2.829s

$&lt;span class="w"&gt; &lt;/span&gt;du&lt;span class="w"&gt; &lt;/span&gt;-chs&lt;span class="w"&gt; &lt;/span&gt;.local/share/containers
19G&lt;span class="w"&gt;     &lt;/span&gt;.local/share/containers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Switching to the &lt;code&gt;overlay&lt;/code&gt; storage driver is a huge win, both in time and in storage space:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;.config/containers/storage.conf
&lt;span class="o"&gt;[&lt;/span&gt;storage&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;overlay&amp;quot;&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;storage.options.overlay&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;mount_program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/bin/fuse-overlayfs&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;#mountopt = &amp;quot;noacl&amp;quot;  # see below&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;unshare&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;.local/share/containers

$&lt;span class="w"&gt; &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;system&lt;span class="w"&gt; &lt;/span&gt;reset
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;

$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;ghcr.io/home-assistant/home-assistant:2023.12.4
Trying&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;ghcr.io/home-assistant/home-assistant:2023.12.4...
Getting&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;signatures
Copying&lt;span class="w"&gt; &lt;/span&gt;blob&lt;span class="w"&gt; &lt;/span&gt;2651e446c136&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
Copying&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;003e99ae19&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
Writing&lt;span class="w"&gt; &lt;/span&gt;manifest&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;destination
Storing&lt;span class="w"&gt; &lt;/span&gt;signatures
003e99ae19b0c68a9cbb0c28e68cf843e12513287d1945c1b0b3d1e0eb246973

real&lt;span class="w"&gt;    &lt;/span&gt;1m41.778s
user&lt;span class="w"&gt;    &lt;/span&gt;0m33.646s
sys&lt;span class="w"&gt;     &lt;/span&gt;0m9.357s
$&lt;span class="w"&gt; &lt;/span&gt;du&lt;span class="w"&gt; &lt;/span&gt;-chs&lt;span class="w"&gt; &lt;/span&gt;.local/share/containers
&lt;span class="m"&gt;2&lt;/span&gt;.4G&lt;span class="w"&gt;    &lt;/span&gt;.local/share/containers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="acl-hickup"&gt;&lt;a class="toclink" href="#acl-hickup"&gt;ACL hickup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;But this doesn't seem to work for all images.
The &lt;a href="https://grafana.com/grafana/"&gt;Grafana&lt;/a&gt; image fails with a rather unspecific &lt;code&gt;Operation not supported&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;docker.io/grafana/grafana-oss:10.0.10
Trying&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;docker.io/grafana/grafana-oss:10.0.10...
Getting&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;signatures
Copying&lt;span class="w"&gt; &lt;/span&gt;blob&lt;span class="w"&gt; &lt;/span&gt;bf463c6d6fd9&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
Copying&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;e5b83aa02b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
Writing&lt;span class="w"&gt; &lt;/span&gt;manifest&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;destination
Storing&lt;span class="w"&gt; &lt;/span&gt;signatures
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;msg&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;exec container process `/run.sh`: Operation not supported&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;level&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;error&amp;quot;&lt;/span&gt;,&lt;span class="s2"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;2024-01-03T08:12:03.063449Z&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After some looking around, I found &lt;a href="https://github.com/containers/podman/issues/11213"&gt;these&lt;/a&gt; &lt;a href="https://github.com/containers/fuse-overlayfs/issues/367"&gt;two&lt;/a&gt; GitHub issue that seems very similar.
The problem described there was that the underlying ZFS file system had ACLs disabled, and the container tried to use them.
And indeed, I have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# zfs get aclmode /tank/podman&lt;/span&gt;
NAME&lt;span class="w"&gt;         &lt;/span&gt;PROPERTY&lt;span class="w"&gt;  &lt;/span&gt;VALUE&lt;span class="w"&gt;        &lt;/span&gt;SOURCE
tank/podman&lt;span class="w"&gt;  &lt;/span&gt;aclmode&lt;span class="w"&gt;   &lt;/span&gt;discard&lt;span class="w"&gt;      &lt;/span&gt;default
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Adding the &lt;code&gt;noacl&lt;/code&gt; mount option to &lt;code&gt;storage.conf&lt;/code&gt; seems to solve that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;.config/containers/storage.conf
&lt;span class="o"&gt;[&lt;/span&gt;storage&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;overlay&amp;quot;&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;storage.options.overlay&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;mount_program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/bin/fuse-overlayfs&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;mountopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;noacl&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="system-wide"&gt;&lt;a class="toclink" href="#system-wide"&gt;System-wide&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next, I wanted to configure this system-wide so all users would benefit from this.
I thought it would be as simple as moving the config file from &lt;code&gt;~/.config/containers/storage.conf&lt;/code&gt; to &lt;code&gt;/etc/containers/storage.conf&lt;/code&gt;, but that does not seem the case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;docker.io/grafana/grafana-oss:10.0.10
Error:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;overlay&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;supported&lt;span class="w"&gt; &lt;/span&gt;over&lt;span class="w"&gt; &lt;/span&gt;zfs,&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;mount_program&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;required:&lt;span class="w"&gt; &lt;/span&gt;backing&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;system&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;unsupported&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;graph&lt;span class="w"&gt; &lt;/span&gt;driver
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Podman complains about the missing &lt;code&gt;mount_program&lt;/code&gt; option, even though it is configured.
I haven't figured out why yet... To be continued&lt;/p&gt;
&lt;h2 id="building-images"&gt;&lt;a class="toclink" href="#building-images"&gt;Building images&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I also had some issues when building container images using &lt;code&gt;podman build&lt;/code&gt;.
They resulted in an "Operation Not Supported" error when creating a layer.&lt;/p&gt;
&lt;p&gt;I haven't figured out what exactly went wrong, but using &lt;code&gt;buildah&lt;/code&gt; directly does work...&lt;/p&gt;</content><category term="linux"/></entry><entry><title>Configuring WLED to wipe-on the LEDs on startup</title><link href="https://blog.dest-unreach.be/2023/11/22/WLED_wipe_on/" rel="alternate"/><published>2023-11-22T00:00:00+01:00</published><updated>2023-11-22T00:00:00+01:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2023-11-22:/2023/11/22/WLED_wipe_on/</id><summary type="html">&lt;p&gt;&lt;a href="https://kno.wled.ge/"&gt;WLED&lt;/a&gt; is an open source project for controlling LED-strips.
I wanted my LED strip to "wipe on" when powering on, similar to the first half of the "Wipe" effect, but staying on after that.
These are my notes.&lt;/p&gt;
&lt;p&gt;Searching around for similar idea's, I found a &lt;a href="https://kno.wled.ge/advanced/custom-features/"&gt;usermod&lt;/a&gt; for a &lt;a href="https://github.com/Aircoookie/WLED/tree/main/usermods/Animated_Staircase"&gt;stairway …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://kno.wled.ge/"&gt;WLED&lt;/a&gt; is an open source project for controlling LED-strips.
I wanted my LED strip to "wipe on" when powering on, similar to the first half of the "Wipe" effect, but staying on after that.
These are my notes.&lt;/p&gt;
&lt;p&gt;Searching around for similar idea's, I found a &lt;a href="https://kno.wled.ge/advanced/custom-features/"&gt;usermod&lt;/a&gt; for a &lt;a href="https://github.com/Aircoookie/WLED/tree/main/usermods/Animated_Staircase"&gt;stairway&lt;/a&gt;.
That module did much more than I needed, but it got me started.
In the end, it looked like my desired effect was possible using &lt;a href="https://kno.wled.ge/features/presets/"&gt;Presets and Playlist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I created two presets, a "wipe" and a "solid", and chained these together with a playlist.
The playlist has a single entry, the "wipe" preset, and is configured to &lt;em&gt;not&lt;/em&gt; repeat, and end on the "solid" preset.
Getting the timing right was a bit of a journey: I needed to convert "effect speed" into "seconds to halfway".
Luckily, open source code makes things easy.
&lt;a href="https://github.com/Aircoookie/WLED/blob/main/wled00/FX.cpp#L163"&gt;The source code&lt;/a&gt; shows the used formula: 
&lt;code&gt;cycleTime = 750 + (255 - SEGMENT.speed)*150&lt;/code&gt;.
This means that a speed of 253 gives a cycleTime of 1050ms.
So by setting the playlist duration of the wipe preset to 0.5 seconds, I got the desired result.&lt;/p&gt;
&lt;p&gt;Another thing to fix is that I wanted the wipe animation to always start at the beginning.
This can be fixed by including &lt;code&gt;"tb": 0&lt;/code&gt; in the &lt;a href="https://kno.wled.ge/interfaces/json-api/"&gt;preset JSON&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that you want do &lt;em&gt;disable&lt;/em&gt; the &lt;code&gt;Turn LEDs on after power up/reset&lt;/code&gt;.
When you leave this on, the LEDs first go to on, and only then start the animation.&lt;/p&gt;
&lt;p&gt;For reference, here is the relevant part of my &lt;code&gt;preset.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;on&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;transition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;tb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mainseg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;seg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;271&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;grp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;of&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;on&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;frz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;cct&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;col&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;152&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;fx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;sx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;253&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ix&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;pal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;sel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;rev&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mi&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;o1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;o2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;o3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;si&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;m12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wipe&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;on&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;transition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mainseg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;seg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;271&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;grp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;spc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;of&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;on&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;frz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;bri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;cct&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;col&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;152&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;fx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;sx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ix&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;pal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;c3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;sel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;rev&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;mi&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;o1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;o2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;o3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;si&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;m12&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;default on&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;playlist&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;ps&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;dur&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;transition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;repeat&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;end&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;r&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;on&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;power on animation&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="electronics"/></entry><entry><title>Specs of the SK6812 LED strip</title><link href="https://blog.dest-unreach.be/2023/08/31/sk6812/" rel="alternate"/><published>2023-08-31T00:00:00+02:00</published><updated>2023-08-31T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2023-08-31:/2023/08/31/sk6812/</id><summary type="html">&lt;p&gt;I was considering a SK6812 strip for a project of mine, but I found it very hard to find information online about the power consumption.
So I ordered a test strip to do the measurements myself.
The SK6812 is an individually addressable LED strip.
I got the RGBW variant, where …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was considering a SK6812 strip for a project of mine, but I found it very hard to find information online about the power consumption.
So I ordered a test strip to do the measurements myself.
The SK6812 is an individually addressable LED strip.
I got the RGBW variant, where each dot has a Red, Green, Blue and White LED that can be controlled.
The protocol used is similar to the WS2811/WS2812/WS2813 series, but reading 32 bits per LED instead of 24 bits.
This is what I found.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The supply voltage is 5V&lt;/li&gt;
&lt;li&gt;A 5m, 300 LED strip consumes about 70mA in idle condition, or 0.23mA/LED&lt;/li&gt;
&lt;li&gt;My ESP32-based controller requires another 150mA, or a bit more when it's actively refreshing the LEDs&lt;/li&gt;
&lt;li&gt;For the LED current, I measured the current for 1, 2, 5, 10, 20 and 40 LEDs, and determined the slope.
   The Red LED averages at about 7.98mA/LED at full brightness; Green is at 8.11mA/LED; Blue is 7.98mA/LED; White is 16.11mA/LED&lt;/li&gt;
&lt;li&gt;100% RGB gives 23.06mA/LED, 100% RGBW gives 38.9mA/LED&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With these currents and only 5V to start from, voltage drop is a significant problem.
Based on some measurements, I estimate the resistance between 2 LEDs to be around 6.2mΩ.
The voltage drop increases approximately with the square of the number of LEDs, so this problem grows quickly!&lt;/p&gt;
&lt;p&gt;I measured what minimum voltage is needed to avoid fading.
To do that, I folded the strip to put the first &amp;amp; last LED next to each other and turned them on full brightness.
Next, I pulsed the middle LEDs and watched for a brightness difference between the first LED (getting the full supply voltage)
and last LED (getting the full voltage drop because of the middle LEDs).
I increased the number of pulsing LEDs until I could see the last LED pulse as well.
I was slightly surprised to find that I could drop down to 3.4V without noticing any dimming; I was expecting a higher threshold.
When the voltage dropped to 3.37V (1 more pulsing LED in the middle), the fading was just noticeable.&lt;/p&gt;
&lt;p&gt;So for my 5m 60LED/m strip, this means I need power injected for every 1.9m.
Since you can power from either end, I can stretch this up to 3.8m before I need to inject power in the middle of a run.
Note that these numbers assume you can inject 5V at that point.
If the 5V power supply is not located right next to the strip, you should account for the voltage drop in the supply cables as well.
To sidestep that problem, I'm thinking of using a 12V (or 24V) power supply, and put a 12V to 5V buck converter right next to the strip.&lt;/p&gt;
&lt;h2 id="color-output"&gt;&lt;a class="toclink" href="#color-output"&gt;Color output&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I measured the LEDs color with the Opple Light Master 3:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Red (x, y)=(0.6532, 0.2984)&lt;/li&gt;
&lt;li&gt;Green (x, y)=(0.1637, 0.5288)&lt;/li&gt;
&lt;li&gt;Blue (x, y)=(0.1609, 0.0709)&lt;/li&gt;
&lt;li&gt;White (x, y)=(0.4181, 0.4259), CCT=3500K, ∆uv=0.019, so slightly greenish&lt;/li&gt;
&lt;li&gt;100% RGBW (x, y)=(0.3407, 0.3217), CCT=5100K, ∆uv=-0.014, so slightly purplish&lt;/li&gt;
&lt;li&gt;100%W + 55%R + 57%B corrects for the greenish tint and yields (x, y)=(0.4072, 0.3951), CCT=3500K, ∆uv=0.001&lt;/li&gt;
&lt;/ul&gt;</content><category term="electronics"/></entry><entry><title>Kerbal Space Program Grand Tour</title><link href="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/" rel="alternate"/><published>2023-07-05T00:00:00+02:00</published><updated>2023-07-05T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2023-07-05:/2023/07/05/kerbal_grand_tour/</id><summary type="html">&lt;p&gt;I wanted a challenge, so I decided to try a "Grand Tour" of the Kerbolar system in &lt;a href="https://www.kerbalspaceprogram.com/"&gt;Kerbal Space Program&lt;/a&gt;.
A "Grand Tour" is a single mission where you visit all bodies in the Kerbolar system.
The definition of "visit" varies from fly-by to manned landing, depending on how challenging …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I wanted a challenge, so I decided to try a "Grand Tour" of the Kerbolar system in &lt;a href="https://www.kerbalspaceprogram.com/"&gt;Kerbal Space Program&lt;/a&gt;.
A "Grand Tour" is a single mission where you visit all bodies in the Kerbolar system.
The definition of "visit" varies from fly-by to manned landing, depending on how challenging you want things to be.&lt;/p&gt;
&lt;p&gt;I decided to try the "manned landing"-variant in a fully reusable craft,
except for Eve (and Jool and Kerbol, since you can't land there).&lt;/p&gt;
&lt;p&gt;Going around the entire Kerbol system requires about 20km/s of ∆v.
That's a lot.
So I decided to equip my ship with an &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Convert-O-Tron_250"&gt;ISRU&lt;/a&gt; and mine extra fuel along the way.&lt;/p&gt;
&lt;h2 id="eve-moho"&gt;&lt;a class="toclink" href="#eve-moho"&gt;Eve – Moho&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Eve–Moho hop is the most challenging hop ∆v-wise.
A fairly good transfer window still &lt;a href="https://alexmoon.github.io/ksp/#/Eve/100/Moho/100/false/optimal/false/3/200"&gt;requires&lt;/a&gt;
3480m/s from Low Eve Orbit to Low Moho Orbit, plus an additional ~1000m/s for landing.
By departing from Gilly and diving into Eve's gravity well to maximize the Oberth effect,
I was able to reduce this to &lt;a href="/static/ksp-tools/moon-departure.html#t=Moho&amp;amp;t0l=34644673.71225&amp;amp;t0h=35292885.412250005&amp;amp;dtl=1081884.375&amp;amp;dth=1837884.375&amp;amp;f=Eve&amp;amp;o=%7B%22sma%22%3A31499999.99999989%2C%22e%22%3A0.55%2C%22argp%22%3A0.17453292519943295%2C%22inc%22%3A0.20943951023931953%2C%22lon_an%22%3A1.3962634015954636%2C%22ma0%22%3A0.9%7D&amp;amp;apo=266817"&gt;around 2600m/s&lt;/a&gt;,
depending on the chosen transfer window.&lt;/p&gt;
&lt;p&gt;This particular transfer requires a low-TWR burn from Gilly to dive into Eve of about 530m/s.
At Eve periapsis, we need to do an escape burn of 930m/s, at reasonably high TWR,
since we lose Oberth effect fast when this burn takes too long.
The capture burn is 870m/s, also at high TWR.
Finally we need to circularize the orbits around Moho with an additional 320m/s, but that can be low-TWR and split between orbits.
Landing can start out at low TWR, but should end up well above 2.7m/s² (Moho's surface gravity).&lt;/p&gt;
&lt;h2 id="tylo"&gt;&lt;a class="toclink" href="#tylo"&gt;Tylo&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Landing on Tylo is hard, with a beefy 2300m/s of speed to bleed off and no atmosphere to help with the breaking.
Tylo also has a surface gravity of 7.85m/s², so to do some actual breaking,
we're looking at at least 10m/s² (TWR=1.3) of thrust, but preferably 16m/s² (TWR=2) or more.&lt;/p&gt;
&lt;p&gt;Getting to Tylo is easiest from Pol, where it's easy to refuel.
The trip &lt;a href="https://alexmoon.github.io/ksp/#/Pol/20/Tylo/20/false/optimal/false/1/1"&gt;needs&lt;/a&gt; approximately 1000m/s of ∆v:
The Pol escape burn of 215m/s can be done at low TWR, since the low gravity of Pol doesn't yield much Oberth-effect gains.
Arriving at Tylo, we only need about 42m/s at a periapsis of 20kmAGL to remain in orbits around Tylo.
Circularizing needs another 800m/s, but we can split that out over multiple orbits.&lt;/p&gt;
&lt;h2 id="the-ship"&gt;&lt;a class="toclink" href="#the-ship"&gt;The ship&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the above constraints in mind, I set out to design a spaceship.
I started with a spaceplane design, but couldn't get enough ∆v packed into it.
I guess the "dead mass" of the gears, wings and control surfaces adds up.
In addition, I needed an extra set of downward pointing engines for landing on bodies without atmosphere,
adding to the dead mass.
So I ended up with this Single Stage To Orbit (SSTO) rocket:&lt;/p&gt;
&lt;p&gt;&lt;img alt="My Single Stage To Orbit Rocket" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/sstor.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The rocket consists of a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk1-3_Command_Pod"&gt;Mk1-3 Command Pod&lt;/a&gt;, topped with a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Clamp-O-Tron_Shielded_Docking_Port"&gt;Shielded Docking Port&lt;/a&gt;.
Underneath is the &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Convert-O-Tron_250"&gt;Convert-O-Tron&lt;/a&gt; and a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk3_to_2.5m_Adapter"&gt;2.5m to Mk3 Adapter&lt;/a&gt;.
On the adapter are 4 &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/A.I.R.B.R.A.K.E.S"&gt;Airbrakes&lt;/a&gt; to assist in orienting the rocket engines-first during atmospheric re-entry,
as well as 4 &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Vernor_Engine"&gt;Vernor RCS engines&lt;/a&gt; to keep the rocket upright at touchdown.
The 8 &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk2-R_Radial-Mount_Parachute"&gt;parachutes&lt;/a&gt; are attached here as well.
Next up is the &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk3_Cargo_Bay_CRG-25"&gt;short Mk3 cargo bay&lt;/a&gt; with an &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Radial_Holding_Tank"&gt;ore tank&lt;/a&gt;,
the &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/M4435_Narrow-Band_Scanner"&gt;Narrow-band Scanner&lt;/a&gt; to pinpoint landing locations,
as well as a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Communotron_88-88"&gt;comm dish&lt;/a&gt; to be able to access KerbNet.
I also packed a bit of &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Advanced_Reaction_Wheel_Module,_Large"&gt;extra torque&lt;/a&gt; to manoeuvre this thing a bit better.
Next are fuel tanks: A &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk3_Rocket_Fuel_Fuselage"&gt;Mk3 Rocket Fuel&lt;/a&gt;, a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk3_Liquid_Fuel_Fuselage"&gt;Mk3 Liquid Fuel&lt;/a&gt; and 4 &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Rockomax_Jumbo-64_Fuel_Tank"&gt;Jumbo-64&lt;/a&gt; tanks.
The central column has the &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/LV-N_%22Nerv%22_Atomic_Rocket_Motor"&gt;NERV&lt;/a&gt; engine, the 4 side tanks are fitted with a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/S3_KS-25_%22Vector%22_Liquid_Fuel_Engine"&gt;Vector&lt;/a&gt; engine.
At the bottom I added a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/%27Drill-O-Matic_Junior%27_Mining_Excavator"&gt;Drill-O-Matic Junior&lt;/a&gt; on a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/1P2_Hydraulic_Cylinder"&gt;piston&lt;/a&gt; to be able to reach the ground.
Power is generated by a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/PB-NUK_Radioisotope_Thermoelectric_Generator"&gt;PB-NUK&lt;/a&gt; for normal operations,
4 &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Gigantor_XL_Solar_Array"&gt;Gigantors&lt;/a&gt; and a &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Fuel_Cell_Array"&gt;Fuel Cell Array&lt;/a&gt; during mining.
Cooling is provided by 4 &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Thermal_Control_System_(small)"&gt;small Thermal Control Systems&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It has &lt;a href="/static/ksp-tools/multiengine.html#b=[{&amp;quot;dv&amp;quot;%3A530%2C&amp;quot;engines&amp;quot;%3A[{&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+\&amp;quot;Nerv\&amp;quot;&amp;quot;}]}%2C{&amp;quot;dv&amp;quot;%3A930%2C&amp;quot;engines&amp;quot;%3A[{&amp;quot;number&amp;quot;%3A&amp;quot;4&amp;quot;%2C&amp;quot;type&amp;quot;%3A&amp;quot;S3+KS-25+\&amp;quot;Vector\&amp;quot;&amp;quot;}%2C{&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+\&amp;quot;Nerv\&amp;quot;&amp;quot;}]}%2C{&amp;quot;dv&amp;quot;%3A870%2C&amp;quot;engines&amp;quot;%3A[{&amp;quot;number&amp;quot;%3A&amp;quot;4&amp;quot;%2C&amp;quot;type&amp;quot;%3A&amp;quot;S3+KS-25+\&amp;quot;Vector\&amp;quot;&amp;quot;}%2C{&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+\&amp;quot;Nerv\&amp;quot;&amp;quot;}]}%2C{&amp;quot;dv&amp;quot;%3A320%2C&amp;quot;engines&amp;quot;%3A[{&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+\&amp;quot;Nerv\&amp;quot;&amp;quot;}]}%2C{&amp;quot;dv&amp;quot;%3A1000%2C&amp;quot;engines&amp;quot;%3A[{&amp;quot;number&amp;quot;%3A&amp;quot;4&amp;quot;%2C&amp;quot;type&amp;quot;%3A&amp;quot;S3+KS-25+\&amp;quot;Vector\&amp;quot;&amp;quot;}%2C{&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+\&amp;quot;Nerv\&amp;quot;&amp;quot;}]}]&amp;amp;m0=57.507&amp;amp;f={&amp;quot;lf&amp;quot;%3A19895%2C&amp;quot;ox&amp;quot;%3A18205%2C&amp;quot;air&amp;quot;%3A0%2C&amp;quot;mono&amp;quot;%3A0%2C&amp;quot;sf&amp;quot;%3A0%2C&amp;quot;xe&amp;quot;%3A0%2C&amp;quot;el&amp;quot;%3A0%2C&amp;quot;ore&amp;quot;%3A0}"&gt;enough fuel&lt;/a&gt;
to do the Eve–Moho leg described above, with 1000m/s to spare.
The Pol–Tylo leg also has &lt;a href="/static/ksp-tools/multiengine.html#b=%5B%7B&amp;quot;dv&amp;quot;%3A212%2C&amp;quot;engines&amp;quot;%3A%5B%7B&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+%5C&amp;quot;Nerv%5C&amp;quot;&amp;quot;%7D%5D%7D%2C%7B&amp;quot;dv&amp;quot;%3A42%2C&amp;quot;engines&amp;quot;%3A%5B%7B&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+%5C&amp;quot;Nerv%5C&amp;quot;&amp;quot;%7D%5D%7D%2C%7B&amp;quot;dv&amp;quot;%3A800%2C&amp;quot;engines&amp;quot;%3A%5B%7B&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+%5C&amp;quot;Nerv%5C&amp;quot;&amp;quot;%7D%5D%7D%2C%7B&amp;quot;dv&amp;quot;%3A3803.411453341944%2C&amp;quot;engines&amp;quot;%3A%5B%7B&amp;quot;number&amp;quot;%3A1%2C&amp;quot;type&amp;quot;%3A&amp;quot;LV-N+%5C&amp;quot;Nerv%5C&amp;quot;&amp;quot;%7D%2C%7B&amp;quot;number&amp;quot;%3A&amp;quot;4&amp;quot;%2C&amp;quot;type&amp;quot;%3A&amp;quot;S3+KS-25+%5C&amp;quot;Vector%5C&amp;quot;&amp;quot;%7D%5D%7D%5D&amp;amp;m0=57.507&amp;amp;f=%7B&amp;quot;lf&amp;quot;%3A19895%2C&amp;quot;ox&amp;quot;%3A18205%2C&amp;quot;air&amp;quot;%3A0%2C&amp;quot;mono&amp;quot;%3A0%2C&amp;quot;sf&amp;quot;%3A0%2C&amp;quot;xe&amp;quot;%3A0%2C&amp;quot;el&amp;quot;%3A0%2C&amp;quot;ore&amp;quot;%3A0%7D"&gt;enough fuel&lt;/a&gt;,
and leaves us with 3700m/s for the 2300m/s landing.
When using all engines, we can start our landing burn at 18.7m/s² (TWR=2.4), increasing to 70m/s² (TWR=8.9) when empty.
When fully re-fueled, we still get 16.4m/s² (TWR=2.1) for takeoff.&lt;/p&gt;
&lt;h2 id="the-trip"&gt;&lt;a class="toclink" href="#the-trip"&gt;The Trip&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My trip around the solar system obviously started at the KSC on Kerbin. From there I went to:&lt;/p&gt;
&lt;p&gt;Minmus
&lt;img alt="Minmus" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/02-minmus.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Mun
&lt;img alt="Mun" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/03-mun.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Back to Minmus to refuel for the trip to Gilly
&lt;img alt="Gilly" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/05-gilly.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Moho
&lt;img alt="Moho" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/06-moho.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Gilly again
&lt;img alt="Gilly" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/07-gilly.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Ike
&lt;img alt="Ike" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/08-ike.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Duna
&lt;img alt="Duna" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/09-duna.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Ike again
&lt;img alt="Ike" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/10-ike.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Dres
&lt;img alt="Dres" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/11-dres.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Pol
&lt;img alt="Pol" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/12-pol.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Tylo
&lt;img alt="Tylo" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/13-tylo.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Bop
&lt;img alt="Bop" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/14-bop.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Val
&lt;img alt="Vall" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/15-vall.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Laythe
&lt;img alt="Laythe" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/16-laythe.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Back to Pol for a refuel and a really long wait for a transfer window to Eeloo
&lt;img alt="Eeloo" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/18-eeloo.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Then straight to Minmus, which was on the edge of the ∆v capabilities of the ship.
&lt;img alt="Minmus" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/19-minmus.jpg"&gt;&lt;/p&gt;
&lt;p&gt;And finally back home on Kerbin, after about 45 years of travel.
&lt;img alt="Kerbin" src="https://blog.dest-unreach.be/2023/07/05/kerbal_grand_tour/kerbal_grand_tour/20-kerbin.jpg"&gt;&lt;/p&gt;</content><category term="games"/></entry><entry><title>Flight Simulator bush trips</title><link href="https://blog.dest-unreach.be/2023/06/12/Flight_Simulator_bush_trips/" rel="alternate"/><published>2023-06-12T00:00:00+02:00</published><updated>2023-06-12T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2023-06-12:/2023/06/12/Flight_Simulator_bush_trips/</id><summary type="html">&lt;p&gt;&lt;a href="https://www.flightsimulator.com/"&gt;Microsoft Flight Simulator&lt;/a&gt; provides "bush trips": a set of VFR flights across an area with nice scenery.
When flying these bush trips, you have to rely on the description only, as no navigation aids are available.
To me, as a non-native speaker, the descriptions aren't always very clear.&lt;/p&gt;
&lt;p&gt;So I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://www.flightsimulator.com/"&gt;Microsoft Flight Simulator&lt;/a&gt; provides "bush trips": a set of VFR flights across an area with nice scenery.
When flying these bush trips, you have to rely on the description only, as no navigation aids are available.
To me, as a non-native speaker, the descriptions aren't always very clear.&lt;/p&gt;
&lt;p&gt;So I decided to prepare each flight a bit better by having a SkyVector map ready.
This allows me to cross-reference landmarks to reconcile my current position on the map.&lt;/p&gt;
&lt;h2 id="alaska-bush-trip-unalaska-to-kulik-lake"&gt;&lt;a class="toclink" href="#alaska-bush-trip-unalaska-to-kulik-lake"&gt;Alaska bush trip: Unalaska to Kulik Lake&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://flight.fandom.com/wiki/Microsoft_Flight_Simulator_(2020)/Bush_Trips/Unalaska_to_Kulik_Lake"&gt;This trip&lt;/a&gt; takes you along the Alaska islands in an &lt;a href="https://flight.fandom.com/wiki/CubCrafters_XCub"&gt;XCub&lt;/a&gt;.
These hops are based on &lt;a href="https://earth.google.com/earth/d/1EmGqZbVZddD55HBgh6jnLFQXjMrHRNCq?usp=sharing"&gt;the Google Earth path&lt;/a&gt; by &lt;a href="https://flight.fandom.com/wiki/User:Thadius856"&gt;Thadius856&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=54.04396935000848,-166.05598068158972&amp;amp;chart=301&amp;amp;zoom=1&amp;amp;fpl=%20PADU%205354N16627W%205353N16621W%205355N16617W%205358N16612W%205359N16606W%205404N16555W%205410N16550W%20PAUT"&gt;Tom Madsen (Dutch Harbor) Airport, PADU – Akutan Airport, PAUT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=54.32533384540808,-165.17710876380963&amp;amp;chart=301&amp;amp;zoom=1&amp;amp;fpl=%20PAUT%205408N16531W%205405N16523W%205404N16517W%205405N16511W%205407N16506W%205408N16500W%205409N16455W%205411N16451W%205413N16448W%205424N16445W%205425N16451W%205429N16453W%20PACS"&gt;Akutan Airport, PAUT – Cape Sarichef Airport, PACS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=54.715340034077826,-164.12626647858707&amp;amp;chart=301&amp;amp;zoom=1&amp;amp;fpl=%20PACS%205440N16442W%205439N16429W%205442N16422W%205443N16417W%205443N16357W%205442N16347W%205438N16346W%205437N16335W%205445N16320W%20PAKF"&gt;Cape Sarichef Airport, PACS – False Pass Airport, PAKF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=55.027294000506814,-163.06670379541475&amp;amp;chart=301&amp;amp;zoom=1&amp;amp;fpl=%20PAKF%205452N16320W%205456N16318W%205459N16318W%205507N16318W%205511N16305W%205511N16252W%20PACD"&gt;False Pass Airport, PAKF – Cold Bay Airport, PACD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=55.56079654164309,-161.9431457509102&amp;amp;chart=301&amp;amp;zoom=3&amp;amp;fpl=%20PACD%205516N16241W%205515N16231W%205507N16221W%205507N16214W%205510N16205W%205514N16157W%205519N16203W%205520N16208W%205523N16208W%205527N16207W%205539N16219W%205554N16142W%205557N16126W%20PAOU"&gt;Cold Bay Airport, PACD – Nelson Lagoon Airport, PAOU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=55.827927387283424,-160.1605224597821&amp;amp;chart=301&amp;amp;zoom=2&amp;amp;fpl=%20PAOU%205601N16101W%205600N16052W%205556N16049W%205553N16048W%205543N16040W%205542N16035W%205540N16025W%205538N16017W%205540N16008W%205549N15951W%205551N15941W%205559N15930W%205601N15919W%20PAPE"&gt;Nelson Lagoon Airport, PAOU – Perryville Airport, PAPE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=56.081500612374825,-158.9692840563866&amp;amp;chart=301&amp;amp;zoom=1&amp;amp;fpl=%20PAPE%205555N15903W%205601N15851W%205606N15856W%205609N15850W%20A79"&gt;Perryville Airport, PAPE – Chignik Lake Airport, A79&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=56.608993969879464,-158.44812011592404&amp;amp;chart=301&amp;amp;zoom=2&amp;amp;fpl=%20A79%205621N15830W%205629N15807W%205634N15811W%205637N15815W%205641N15818W%205648N15841W%20PAPH"&gt;Chignik Lake Airport, A79 – Port Heiden Airport, PAPH&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=57.27102472608631,-158.102600096371&amp;amp;chart=301&amp;amp;zoom=2&amp;amp;fpl=%20PAPH%205719N15810W%205734N15741W%20PAPN"&gt;Port Heiden Airport, PAPH – Pilot Point Airport, PAPN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=57.88108206549591,-157.05541992052366&amp;amp;chart=301&amp;amp;zoom=2&amp;amp;fpl=%20PAPN%205734N15723W%205734N15700W%205736N15650W%205743N15636W%205751N15632W%205802N15651W%205812N15716W%20PAII"&gt;Pilot Point Airport, PAPN – Egegik Airport, PAII&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=58.44658300489315,-157.09213256701034&amp;amp;chart=301&amp;amp;zoom=1&amp;amp;fpl=%20PAII%205817N15732W%205842N15705W%20PAKN"&gt;Egegik Airport, PAII – King Salmon Airport, PAKN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://skyvector.com/?ll=58.831373006884284,-155.87223815775369&amp;amp;chart=301&amp;amp;zoom=1&amp;amp;fpl=%20PAKN%205841N15626W%205843N15614W%205842N15609W%205845N15602W%205846N15554W%205847N15540W%205853N15540W%205859N15532W%20PAKL"&gt;King Salmon Airport, PAKN – Kulik Lake Airport, PAKL&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content><category term="games"/></entry><entry><title>In Search for a new Monitoring Solution</title><link href="https://blog.dest-unreach.be/2023/01/07/monitoring_solution/" rel="alternate"/><published>2023-01-07T00:00:00+01:00</published><updated>2023-01-07T00:00:00+01:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2023-01-07:/2023/01/07/monitoring_solution/</id><summary type="html">&lt;p&gt;I'm currently running all of my monitoring through &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt;, backed with &lt;a href="https://www.influxdata.com/products/"&gt;InfluxDB&lt;/a&gt; for the long-term storage of selected metrics.
But recently, I've been limited in functionality when creating graphs in &lt;a href="https://grafana.com/oss/"&gt;Grafana&lt;/a&gt;.
And although I like Prometheus, it looks like my use-case is outside the domain of Prometheus.&lt;/p&gt;
&lt;h2 id="use-case"&gt;&lt;a class="toclink" href="#use-case"&gt;Use Case&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm currently running all of my monitoring through &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt;, backed with &lt;a href="https://www.influxdata.com/products/"&gt;InfluxDB&lt;/a&gt; for the long-term storage of selected metrics.
But recently, I've been limited in functionality when creating graphs in &lt;a href="https://grafana.com/oss/"&gt;Grafana&lt;/a&gt;.
And although I like Prometheus, it looks like my use-case is outside the domain of Prometheus.&lt;/p&gt;
&lt;h2 id="use-case"&gt;&lt;a class="toclink" href="#use-case"&gt;Use Case&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I want to monitor my home's energy consumption throughout the year.
The metrics I have available are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An energy &lt;a href="https://prometheus.io/docs/concepts/metric_types/#counter"&gt;counter&lt;/a&gt; for gas, updated every few minutes.
    Since these are counters, they may reset to 0 at any time, and this should be accounted for when graphing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Graph of the input metric with annotations" src="https://blog.dest-unreach.be/2023/01/07/monitoring_solution/monitoring/input.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The energy price. This metric is not known real-time, and is very coarse: 1 datapoint per day.
    Depending on the contract, it may be known up to 3 months in advance, or only 1 month after the consumption.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Graph of the energy price" src="https://blog.dest-unreach.be/2023/01/07/monitoring_solution/monitoring/price.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would like to create the following graphs:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Desired output graph" src="https://blog.dest-unreach.be/2023/01/07/monitoring_solution/monitoring/result.png"&gt;&lt;/p&gt;
&lt;p&gt;To make this graph, I need several features from the time series database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handle counter resets correctly.&lt;/li&gt;
&lt;li&gt;Calculate back-and-forth between the cumulative counter and the derived rate:
    To convert from energy to price, the current unit price needs to be applied to the marginal usage in the corresponding time period.
    You can't just multiply the counter value with the price as that would create a discontinuity.&lt;/li&gt;
&lt;li&gt;Be able to draw cumulative/running sums. I.e. start at zero at the left edge.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="prometheus"&gt;&lt;a class="toclink" href="#prometheus"&gt;Prometheus&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since Prometheus is my current monitoring solution, I first tried to get it working with Prometheus.
I covered &lt;a href="https://blog.dest-unreach.be/2020/08/16/cumulative-graphs-prometheus/"&gt;the cumulative part&lt;/a&gt; of the problem before.
That solution can't handle counter resets, but the biggest hurdle is incorporating the price:
it &lt;a href="https://stackoverflow.com/questions/57112964/simple-cumulative-increase-in-prometheus"&gt;seems&lt;/a&gt;
that there is no way to have Prometheus calculate a cumulative sum.&lt;/p&gt;
&lt;h2 id="influxdb"&gt;&lt;a class="toclink" href="#influxdb"&gt;InfluxDB&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since I'm already using InfluxDB as long-term storage anyway, the next logical step was to skip Prometheus and interact directly with InfluxDB.
I'm fairly new to the Flux language, but the query below seems to give the results I want:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;date&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;earlyStartTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeRangeStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Make&lt;/span&gt; &lt;span class="n"&gt;sure&lt;/span&gt; &lt;span class="n"&gt;we&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="n"&gt;gas_price&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;gas_price&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;least&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;once&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;

&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;earlyStartTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeRangeStop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_measurement&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gas&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_field&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;volume_m3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_measurement&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gas_price&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_field&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;price_EUR_per_m3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;join&lt;/span&gt; &lt;span class="n"&gt;series&lt;/span&gt; &lt;span class="n"&gt;into&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;single&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pivot&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pivot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rowKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;columnKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_measurement&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;valueColumn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;_value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usePrevious&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gas_price&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="n"&gt;gas_price&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonNegative&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;convert&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="n"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nonNegative&lt;/span&gt; &lt;span class="n"&gt;detects&lt;/span&gt; &lt;span class="n"&gt;resets&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeRangeStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeRangeStop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;crop&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                                           &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;difference&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gas&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gas_price&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;aggregateWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;every&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windowPeriod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;createEmpty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;We&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;longer&lt;/span&gt; &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="n"&gt;dense&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cumulativeSum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="linux"/></entry><entry><title>Rootless podman on Debian Bullseye</title><link href="https://blog.dest-unreach.be/2023/01/03/rootless_podman_on_debian_bullseye/" rel="alternate"/><published>2023-01-03T00:00:00+01:00</published><updated>2023-01-03T00:00:00+01:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2023-01-03:/2023/01/03/rootless_podman_on_debian_bullseye/</id><summary type="html">&lt;p&gt;My home server already uses LXC-containers.
I use them to keep services isolated, so that I can upgrade my mail server without causing issues to my &lt;a href="https://nextcloud.com/"&gt;Nextcloud&lt;/a&gt; setup.
But these containers contain a full OS inside them.
And while this works, it's a bit of work to upgrade a handful …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My home server already uses LXC-containers.
I use them to keep services isolated, so that I can upgrade my mail server without causing issues to my &lt;a href="https://nextcloud.com/"&gt;Nextcloud&lt;/a&gt; setup.
But these containers contain a full OS inside them.
And while this works, it's a bit of work to upgrade a handful of containers every few months.
Docker-style containers should be a better fit for this single-service-per-container setup.&lt;/p&gt;
&lt;p&gt;After reading up a bit, I decided to go for &lt;a href="https://podman.io/"&gt;podman&lt;/a&gt;.
I was mostly convinced by the ability to run rootless, but it seems Docker is getting that as well.&lt;/p&gt;
&lt;h2 id="rootless-networking"&gt;&lt;a class="toclink" href="#rootless-networking"&gt;Rootless networking&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In my LXC setup, every container gets its own &lt;code&gt;veth&lt;/code&gt;-based network interface.
On the host, these &lt;code&gt;veth&lt;/code&gt;-interfaces are bridged together with the physical network card.
That way, every container gets its own MAC and IP(v6) address.&lt;/p&gt;
&lt;p&gt;But since we're running as a normal user, podman can't create a veth-pair.
It was very interesting to learn (how podman solved this)&lt;a href="https://mcastelino.medium.com/slirp4netns-how-does-it-work-5c0bd31200ce"&gt;slirp4netns&lt;/a&gt; with some &lt;code&gt;netns&lt;/code&gt; and &lt;code&gt;tap&lt;/code&gt; magic!&lt;/p&gt;
&lt;p&gt;The result isn't exactly equivalent to what I had on the LXC-side:
the containers do have their own IP, but it's not exposed to the outside world.
I found (a mailing list post describing how to roll my own)&lt;a href="https://lists.podman.io/archives/list/podman@lists.podman.io/thread/W6MCYO6RY5YFRTSUDAOEZA7SC2EFXRZE/"&gt;manual-netns&lt;/a&gt;, but I haven't figured out yet how to combine this with DHCP...&lt;/p&gt;
&lt;h2 id="compose"&gt;&lt;a class="toclink" href="#compose"&gt;Compose&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Podman has its own &lt;code&gt;docker-compose&lt;/code&gt; equivalent.
Unsurprisingly, it's called &lt;code&gt;podman-compose&lt;/code&gt;, but is otherwise very similar.&lt;/p&gt;
&lt;p&gt;I didn't find a ready-made way to auto-start a service,
so I rolled my own systemd service which I put in &lt;code&gt;~/.config/systemd/user/podman-compose.service&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Rootless pod (podman-compose)&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="na"&gt;RemainAfterExit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%h&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/podman-compose up -d --remove-orphans&lt;/span&gt;
&lt;span class="na"&gt;ExecStop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/podman-compose down&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will run &lt;code&gt;compose.yaml&lt;/code&gt; from the home directory of the user.&lt;/p&gt;</content><category term="linux"/></entry><entry><title>The Unifi Protect G4 Doorbell</title><link href="https://blog.dest-unreach.be/2022/06/12/unifi-g4-doorbell/" rel="alternate"/><published>2022-06-12T00:00:00+02:00</published><updated>2022-06-12T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2022-06-12:/2022/06/12/unifi-g4-doorbell/</id><summary type="html">&lt;p&gt;I've been dragging my feet for years to get a video doorbell.
I wanted a doorbell that "just works" as a doorbell, even if the WiFi is down,
and I didn't want my video footage to be stored in the cloud,
but only accessible when on the local network,
either …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been dragging my feet for years to get a video doorbell.
I wanted a doorbell that "just works" as a doorbell, even if the WiFi is down,
and I didn't want my video footage to be stored in the cloud,
but only accessible when on the local network,
either directly, or via VPN.
Apparently, these are fairly high requirements nowadays...
I settled for the &lt;a href="https://store.ui.com/products/uvc-g4-doorbell"&gt;Unifi Protect G4 Doorbell&lt;/a&gt;.
I wanted to get the Pro-variant, but that one still hasn't left Early Access.&lt;/p&gt;
&lt;p&gt;There were some issues, though.
The first one is that this doorbell works on 16V AC, and requires the chime to also work with that.
While 16V may be the standard in the US, here where I live, most chimes are 6V.
They'll probably work with 16V, but not for a very long time.&lt;/p&gt;
&lt;h2 id="the-chime"&gt;&lt;a class="toclink" href="#the-chime"&gt;The Chime&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You don't &lt;em&gt;need&lt;/em&gt; to connect a chime.
You can go all in digital and only use your phone to notify you someone is at the door.
But if you do, you need to connect a special white chime-box adapter in series with the doorbell:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Chime box adapter" src="https://blog.dest-unreach.be/2022/06/12/unifi-g4-doorbell/unifi-g4-doorbell/chime-box-adapter.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Presumably, this box would output 16V AC when the door bell button is pressed.
So I needed to find out what exactly is being output from this box, and how I can convert it to the correct voltage.&lt;/p&gt;
&lt;p&gt;Contrary to my expectations, there was some output present while the system was in idle:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Idle state output" src="https://blog.dest-unreach.be/2022/06/12/unifi-g4-doorbell/unifi-g4-doorbell/idle.png"&gt;&lt;/p&gt;
&lt;p&gt;The signal is non-sinusoidal, but 50Hz periodic and about 5.5Vpeak-to-peak.
When I pressed the button, nothing happened.&lt;/p&gt;
&lt;p&gt;In the Doorbell's settings, you can choose which chime is attached.
By default, it's configured to "None".
The other settings are "Mechanical" and "Digital".
When selecting Digital, you can choose a duration between 1 and 10 seconds.&lt;/p&gt;
&lt;p&gt;So I choose "Mechanical", and pushed the button again.
Now something did happen: the output of the chime box adaptor jumped up to 21Vrms AC and stayed there for many seconds,
but the Doorbell shut down because it lost power. It recovered after 30seconds or so.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mechanical chime, unloaded" src="https://blog.dest-unreach.be/2022/06/12/unifi-g4-doorbell/unifi-g4-doorbell/chime, unloaded.png"&gt;&lt;/p&gt;
&lt;p&gt;It looks like the chime box adapter outputs the full output of the transformer to the chime connections,
leaving no power for the doorbell itself.
I'm assuming that the voltage is supposed to be shared between the chime and the doorbell during this period,
but since I have no chime connected (yet), all voltage falls over my oscilloscope and nothing remains for the camera.&lt;/p&gt;
&lt;p&gt;So I decided to load the output with a (second) transformer to step down the voltage from 21Vrms AC to something in the neighbourhood of 6~10V.
This seems to have done the trick:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mechanical chime, loaded with inductor" src="https://blog.dest-unreach.be/2022/06/12/unifi-g4-doorbell/unifi-g4-doorbell/mechanical chime, loaded with inductor.png"&gt;&lt;/p&gt;
&lt;p&gt;The chime box output full voltage for around 430ms, and resets to its idle state,
leaving the camera enough power to keep on working.&lt;/p&gt;</content><category term="reverse-engineering"/></entry><entry><title>Reverse engineering the Mitsubishi heat-pump WiFi adapter</title><link href="https://blog.dest-unreach.be/2022/06/04/mitsubishi-wifi-adapter/" rel="alternate"/><published>2022-06-04T00:00:00+02:00</published><updated>2022-06-04T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2022-06-04:/2022/06/04/mitsubishi-wifi-adapter/</id><summary type="html">&lt;p&gt;Recently we had a heat pump (A/C, air conditioner, AirCon) installed.
The indoor units came with the usual IR remote,
but they also had a built-in WiFi module along with a smartphone app to control them from anywhere.
I'm not a big fan of connecting home appliances to clouds …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently we had a heat pump (A/C, air conditioner, AirCon) installed.
The indoor units came with the usual IR remote,
but they also had a built-in WiFi module along with a smartphone app to control them from anywhere.
I'm not a big fan of connecting home appliances to clouds for a variety of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Running a cloud service requires a continuous stream of money to keep the service running.
   So either you will have to pay some sort of subscription, or the service will (eventually) stop working.
   There is also the possibility that the manufacturer is very altruistic, but that isn't very likely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If my appliance is connected to the internet, the manufacturer can choose to alter its functionality.
   &lt;a href="https://thenextweb.com/news/update-brainwashes-microwaves-thinking-theyre-steam-ovens"&gt;Or make it stop working entirely&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Connecting things to the cloud makes them more vulnerable to attacks.
   Right now, an attacker needs to be within infrared range of my unit (a few meters) to control it.
   Once the device phones home to its cloud, an attacker can potentially control thousands of units from their couch.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don't get me wrong. I love when appliances offer some sort of API to control them externally.
And I see why a cloud-service is a nice addition for a lot of people.
But I prefer local control.&lt;/p&gt;
&lt;h2 id="the-mitsubishi-wifi-adapter"&gt;&lt;a class="toclink" href="#the-mitsubishi-wifi-adapter"&gt;The Mitsubishi WiFi adapter&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My heat pump came with a MAC-577IF2-E adapter built-in.
So I set out to try and locally control the unit via WiFi.
If you're in a hurry, I'll save you some time: &lt;em&gt;I didn't succeed&lt;/em&gt;.
But here is what I did find out, in the hope it can be useful for other people.&lt;/p&gt;
&lt;p&gt;I started by looking around to interesting projects:
I found the &lt;a href="https://github.com/ncaunt/meldec"&gt;meldec&lt;/a&gt; GitHub project that can decode the messages sent to MELcloud.
Another related project I found was &lt;a href="https://github.com/SwiCago/HeatPump"&gt;HeatPump&lt;/a&gt;,
but that focuses on communicating directly with the unit over its serial interface.&lt;/p&gt;
&lt;p&gt;Update 2025-07-28: &lt;a href="https://github.com/ashleigh-hopkins"&gt;Ash Hopkins&lt;/a&gt; did some really amazing work
on this and &lt;a href="https://github.com/pymitsubishi"&gt;figured it all out&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="first-discoveries"&gt;&lt;a class="toclink" href="#first-discoveries"&gt;First discoveries&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For setup, I switched the adapter into Access Point mode.
After associating with it using the SSID &amp;amp; WPA-PSK printed on the box,
I could connect to a web-server to configure the network settings.
The page was fairly simple:&lt;/p&gt;
&lt;p&gt;&lt;img alt="network setup screen" src="https://blog.dest-unreach.be/2022/06/04/mitsubishi-wifi-adapter/mitsubishi/network.png"&gt;&lt;/p&gt;
&lt;p&gt;Once the details were filled in, it connected to my home WiFi network just fine.
The MAC address of the unit stats with &lt;code&gt;70:61:BE&lt;/code&gt;, which is assigned to &lt;code&gt;Wistron Neweb Corporation&lt;/code&gt;.
After requesting an IP via DHCP, it started to phone home, of course.
First thing it did was a DNS-lookup for &lt;code&gt;production.receiver.melcloud.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first request to that domain is a plain-text HTTP POST-request to &lt;code&gt;/synchro&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;POST /synchro HTTP/1.1
Host: production.receiver.melcloud.com:80
Accept: */*
User-Agent: MAC-577IF-E
Pragma:  no-cache
Content-type:  text/plain; charset=UTF-8
Content-Length: 68

&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&amp;lt;LSV&amp;gt;&amp;lt;SYNCHRO&amp;gt;&amp;lt;/SYNCHRO&amp;gt;&amp;lt;/LSV&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The response contained the current datetime:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;HTTP/1.0 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Date: Sat, 4 Jun 2022 09:47:47 GMT
Server: Microsoft-IIS/10.0
X-Robots-Tag: noindex, nofollow, noimageindex

&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&amp;lt;CSV&amp;gt;&amp;lt;SYNCHRO&amp;gt;&amp;lt;DATE&amp;gt;2022/06/04 09:47:47&amp;lt;/DATE&amp;gt;&amp;lt;/SYNCHRO&amp;gt;&amp;lt;/CSV&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So far so good.
The next connection, however, was an encrypted HTTPS request to the same domain.
&lt;code&gt;production.receiver.melcloud.com&lt;/code&gt; presents an HTTPS certificate that is signed by &lt;code&gt;MELCloud Root Authority&lt;/code&gt;,
and thus not trusted by browsers (by default).
I tried to redirect this request to my own server, presenting a look-alike certificate chain.
But the device refused with an &lt;code&gt;Unknown CA&lt;/code&gt; error:
it didn't like my own spoofed CA and wanted to see the actual Mitsubishi CA.
Normally, I consider this a good thing for security, but in this particular case it's a bummer...&lt;/p&gt;
&lt;h2 id="inbound"&gt;&lt;a class="toclink" href="#inbound"&gt;Inbound&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There also seems to be a webserver listening on the device itself on TCP port 80.
But according to &lt;a href="https://nmap.org/"&gt;nmap&lt;/a&gt;, there is nothing else listening on TCP (all other ports are connection-refused).
Note that a normal nmap-scan crashes the web server, so you won't see it as open on a consecutive scan.&lt;/p&gt;
&lt;p&gt;But the web server seems to be locked down: requests to &lt;code&gt;/&lt;/code&gt; require authentication, which I don't have/know:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt; GET / HTTP/1.1
&amp;gt; Host: IP-address-of-unit-omitted-for-privacy
&amp;gt; User-Agent: curl/7.79.1
&amp;gt; Accept: */*
&amp;gt;

&amp;lt; HTTP/1.1 401 Unauthorized
&amp;lt; Content-Type: text/plain
&amp;lt; Content-Length: 22
&amp;lt; WWW-Authenticate: Basic realm=&amp;quot;generic&amp;quot;
&amp;lt;
&amp;lt; Authorization Required
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Some URLs do work without authentication: &lt;code&gt;/license&lt;/code&gt;, &lt;code&gt;/common.css&lt;/code&gt;, &lt;code&gt;/javaScript.js&lt;/code&gt; work.
Others return a 404 instead of a 401.&lt;/p&gt;
&lt;h2 id="digging-deeper"&gt;&lt;a class="toclink" href="#digging-deeper"&gt;Digging deeper&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Someone &lt;a href="https://github.com/ambiot/amb1_sdk/blob/master/doc/UM0034%20Realtek%20Ameba-1%20memory%20layout.pdf"&gt;posted the firmware&lt;/a&gt; to GitHub. Time to brush off my reverse engineering skills.&lt;/p&gt;
&lt;p&gt;Based on a simple &lt;code&gt;strings&lt;/code&gt; of the firmware, I found &lt;code&gt;C:\sdk-ameba-v4.0c\component\common\mbed\targets\hal\rtl8195a\gpio_api.c&lt;/code&gt;.
So it looks like the hardware may be an &lt;a href="https://www.amebaiot.com/en/control-mcu/"&gt;Ameba 1&lt;/a&gt;.
This houses an ARM Cortex M3 CPU clocked at 166MHz with 2.5MB of SRAM and built-in WiFi (2.4GHz only).
I used &lt;a href="https://github.com/radareorg/radare2"&gt;radare2&lt;/a&gt; to disassemble the binary,
and kept the &lt;a href="https://users.ece.utexas.edu/~valvano/EE345M/CortexM3InstructionSet.pdf"&gt;ARM Cortex M3 Instruction Set Reference&lt;/a&gt; nearby.
It looks like the SDK for this SoC is available &lt;a href="https://github.com/ambiot/amb1_sdk"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Flash layout section of &lt;a href="https://github.com/ambiot/amb1_sdk/blob/master/doc/UM0034%20Realtek%20Ameba-1%20memory%20layout.pdf"&gt;the memory layout PDF from the SDK&lt;/a&gt; contains some information on the boot loader process.
From the description, it seems that an image starts with a 4-byte length, a 4-byte address, and 8 bytes of &lt;code&gt;0xffffffffffffffff&lt;/code&gt;.
The Memory Mapping section of &lt;a href="https://file.elecfans.com/web1/M00/BA/E6/o4YBAF6ijEqAQpDBACLQAF2ggPY084.pdf"&gt;the RTL8195A data sheet&lt;/a&gt; tells what address is mapped to what hardware.
Based on that info, the firmware contains two sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A first section of 0x4aefc = 306_940 bytes, to be loaded at 0x10006000, which is in SRAM&lt;/li&gt;
&lt;li&gt;A second section of 0x10cea4 = 1_101_476 bytes, to be loaded at 0x30000000, which is in SDRAM&lt;/li&gt;
&lt;li&gt;The final 4 bytes of the file. Maybe a CRC of some sort?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While scrolling through the &lt;code&gt;strings&lt;/code&gt; output, the function at &lt;code&gt;0x3001de60&lt;/code&gt; caught my eye:
It's the only place to reference the string &lt;code&gt;401 Unauthorized&lt;/code&gt;.
The only place where this function is referenced, is around &lt;code&gt;0x30028984&lt;/code&gt;.
This section loads the string &lt;code&gt;Authorization&lt;/code&gt;, and calls a function (I'm guessing to find this header).
Next, it checks if the returned value (?) starts with &lt;code&gt;Basic&lt;/code&gt;, and passes the rest of the value to &lt;code&gt;0x30028820&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x30028820&lt;/code&gt; seems to be the "verify authorization"-function.
It seems to iterate through a linked list of entries, checking the path (?).
If the entry matches, the base64-auth-value is compared to another attribute of the entry.
If it matches, the "return a 401"-part is skipped.
Interestingly, the comparison is done by first &lt;code&gt;memcmp()&lt;/code&gt;ing, and later verifying that &lt;code&gt;strlen()&lt;/code&gt; is equal.
The actual &lt;code&gt;memcpy()&lt;/code&gt; implementation is in ROM, but it's possible this is vulnerable to a timing side channel
(although doing timing side channels over WiFi is hard...).&lt;/p&gt;
&lt;p&gt;Unfortunately, this function just checks the auth-value against a pre-computed list.
So I still need to find the code that generates this list...
The list is loaded from &lt;code&gt;*(*(0x30029130)+0x18)&lt;/code&gt;.
&lt;code&gt;0x30029130&lt;/code&gt; contains &lt;code&gt;0x1004f134&lt;/code&gt;, but it may be overwritten by the time we get here.
&lt;code&gt;0x1004f134+0x18&lt;/code&gt; = &lt;code&gt;0x1004f14c&lt;/code&gt; contains &lt;code&gt;0x00000000&lt;/code&gt; at bootup.
This will definitely be overwritten, since otherwise no authentication will work (the linked list has 0 entries).
And I know that &lt;code&gt;/config&lt;/code&gt; can be accessed with username &lt;code&gt;user&lt;/code&gt; and the Key-password printed on the device.&lt;/p&gt;
&lt;h2 id="trying-another-approach"&gt;&lt;a class="toclink" href="#trying-another-approach"&gt;Trying another approach&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;0x300696bc&lt;/code&gt; looks interesting: it seems to contain a list of paths &amp;amp; usernames.
The entries seem to have the odd size of 0xe7=231 bytes.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path \ user&lt;/th&gt;
&lt;th&gt;user&lt;/th&gt;
&lt;th&gt;root&lt;/th&gt;
&lt;th&gt;suser&lt;/th&gt;
&lt;th&gt;admin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/network&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/unitinfo&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/service&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/server&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/update&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/updateFinish&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/updateFail&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/default&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/analyze&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/apinfo&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/config&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/smart&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/adapter_image2.gif&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/javaScript.js&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/common.css&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/license&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Based on &lt;code&gt;/license&lt;/code&gt;, I'm assuming "no user listed" means "unauthenticated".&lt;/p&gt;</content><category term="reverse-engineering"/></entry><entry><title>Backups</title><link href="https://blog.dest-unreach.be/2022/05/04/backup/" rel="alternate"/><published>2022-05-04T00:00:00+02:00</published><updated>2022-05-04T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2022-05-04:/2022/05/04/backup/</id><summary type="html">&lt;p&gt;Recently I was rethinking my off-site backup strategy.
Ever since Amazon AWS launched their &lt;a href="https://aws.amazon.com/blogs/aws/new-amazon-s3-storage-class-glacier-deep-archive/"&gt;Glacier Deep Archive&lt;/a&gt;
storage tier, it looked really interesting for backup use-cases.
For starters, it's super cheap to keep your bytes around:
$1.01376/TiB/month in most regions &lt;a href="https://aws.amazon.com/s3/pricing/"&gt;as of this writing&lt;/a&gt;.
I haven't found …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently I was rethinking my off-site backup strategy.
Ever since Amazon AWS launched their &lt;a href="https://aws.amazon.com/blogs/aws/new-amazon-s3-storage-class-glacier-deep-archive/"&gt;Glacier Deep Archive&lt;/a&gt;
storage tier, it looked really interesting for backup use-cases.
For starters, it's super cheap to keep your bytes around:
$1.01376/TiB/month in most regions &lt;a href="https://aws.amazon.com/s3/pricing/"&gt;as of this writing&lt;/a&gt;.
I haven't found any other storage proposition this cheap.
Of course there is some fine print, but it matches my backup use-case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you are charged for at least 180 days of storage, even if you delete the objects before that&lt;/li&gt;
&lt;li&gt;you are charged for some overhead on top of the stored objects.
   Especially for smaller objects, this can add up significantly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But all in all, it still was very attractive.&lt;/p&gt;
&lt;h2 id="requirements"&gt;&lt;a class="toclink" href="#requirements"&gt;Requirements&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Based on discussions I had, it seems I have a fairly beefy list of requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bringing the backup up-to-date should be cheap.
   This means I don't want to update the full content every single time.
   I prefer to only upload what has changed (rsync-style). &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keeping the backup around should be cheap.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restoring the backup may be expensive, either in time, money or both. 
   Since this is not my first backup, I intend to never need it.
   So paying several hundred euro/dollar to get my most valuable files back is fine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restoring should be "easy".
   Since I'll be using this backup in a disaster recovery mode,
   I prefer to be able to restore crucial files without the need for any particular software,
   just relying on standard tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Backups need to be client-side encrypted, preferably with auditable/trusted tools, independent of the backup tool.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I looked around for existing projects and/or products, but didn't find one that checked all my boxes.
Especially the "client-side encrypted" seemed like a high bar.
So I decided to roll my own.&lt;/p&gt;
&lt;h2 id="design"&gt;&lt;a class="toclink" href="#design"&gt;Design&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;S3 natively supports versioning of objects.
By default, you see the latest version of an object.
Or, if the last version is a "delete marker", the object is hidden by default.
But you can request previous versions explicitly if you need them.
In addition, I use &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html"&gt;S3 Lifecycle management&lt;/a&gt;
to remove old versions after a configured amount of time. &lt;/p&gt;
&lt;p&gt;This setup ensures the "easy restore"-requirement:
The S3 web console gives me access to the most recent backup content,
but allows access to previous versions if needed.&lt;/p&gt;
&lt;p&gt;The client-side encryption is a bit more challenging:
the backup-tool needs to figure out if a particular file has changed since the previous backup or not.
You can't just encrypt the file again, and compare that, since encrypting the same file twice is not guaranteed to give the same result.
My solution is to add metadata to the S3-object: the size of the plaintext file, and a hash of the plaintext file.
The size is included because it's very cheap to check: if a file's size has changed, the file has changed and needs uploading.
The hash is more expensive to calculate, but will spot changes even when the file size is the same.
I know there is an astronomically small chance that a changed file will not result in a changed hash, but I'm taking my chances.&lt;/p&gt;
&lt;p&gt;To make things more efficient, I also included a client-side cache of the S3 content.
Listing the objects in an S3 bucket is fairly efficient, but getting the metadata requires a HEAD-call per object.
At 71 seconds per 1000 calls, which is what I practically get, this takes way to long for a 200k-object backup.
Not to mention the monetary cost of doing these calls every single time.
But since this data is cached locally, care should be taken to never modify the bucket directly.
This would cause the cache to be incorrect, and may result in bad backups.&lt;/p&gt;
&lt;h2 id="small-files"&gt;&lt;a class="toclink" href="#small-files"&gt;Small files&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the things I want to backup are my git repositories.
Git has the habit of creating lots of small files.
And while the above design supports small files, it gets slow and expensive:
Uploading ten 1-byte files takes way longer than uploading a single 10-byte file.
And since there is an additional 40kB overhead per object (some of it billed at standard S3 pricing),
a single-byte file is (relatively speaking) expensive.&lt;/p&gt;
&lt;p&gt;So I wanted to ZIP together smaller files and upload the ZIP instead.
This is where it gets tricky: you want both very small ZIPs and very large ZIPs to cover contradicting needs.
On one hand, you want the ZIPs to be as large as possible, to maximize the efficiency-gains.
On the other hand, you want the ZIPs to be as small as possible, since a change to a single file in the ZIP requires the upload of the complete new ZIP.&lt;/p&gt;
&lt;p&gt;From a restore point of view, you also want the ZIPs to be "logical".
Given a filename and a point in time, I want to be able to cherry-pick that file from the backup with as little overhead as possible.
I want to either find the file itself in the backup, or should be able to tell fairly easy which ZIP contains the given file.&lt;/p&gt;
&lt;p&gt;I went through several variants of grouping-logic.
My current implementation takes a configurable threshold as input.
Files that are larger than the threshold are stored to S3 directly.
Smaller files are grouped based on their filenames, where the algorithm tries to find the longest prefix that will result in a ZIP of at least the required size. So given the following files and a threshold of 1 MiB:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;large-file&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;MiB&lt;/span&gt;
&lt;span class="na"&gt;.git&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;KiB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;KiB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;KiB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;MiB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It will pass through &lt;code&gt;large-file&lt;/code&gt; and &lt;code&gt;.git/d&lt;/code&gt;, but will ZIP the other files in the &lt;code&gt;.git/&lt;/code&gt; folder together as &lt;code&gt;.git/*~.zip&lt;/code&gt;.
To find a file for restore, I know that the file will be either directly visible on S3, or be in first ZIP-file I encounter by removing characters from the end of the filename I'm looking for.&lt;/p&gt;</content><category term="sysadmin"/></entry><entry><title>Surviving Mars</title><link href="https://blog.dest-unreach.be/2022/04/11/surviving_mars/" rel="alternate"/><published>2022-04-11T00:00:00+02:00</published><updated>2022-04-11T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2022-04-11:/2022/04/11/surviving_mars/</id><content type="html">&lt;p&gt;I discovered another game that gives me an excuse to write silly calculator modules: &lt;a href="https://www.paradoxinteractive.com/games/surviving-mars/about"&gt;Surviving Mars&lt;/a&gt;. Some random notes and a few tools I wrote, or interesting tools I found online:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/static/surviving-mars-tools/colonists.html"&gt;Population calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/surviving-mars-tools/electricity.html"&gt;Electricity calculator (work in progress)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="games"/></entry><entry><title>Kerbal Space Program</title><link href="https://blog.dest-unreach.be/2021/08/25/kerbal_space_program/" rel="alternate"/><published>2021-08-25T00:00:00+02:00</published><updated>2021-08-25T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2021-08-25:/2021/08/25/kerbal_space_program/</id><summary type="html">&lt;p&gt;I am usually late to proverbial parties, but I think I’ve outdone myself by discovering the computer game &lt;a href="https://www.kerbalspaceprogram.com/"&gt;Kerbal Space Program&lt;/a&gt; roughly 10 years after it first became playable. It’s the kind of game that triggers my “I’ll optimize this by writing a script”, so here are …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am usually late to proverbial parties, but I think I’ve outdone myself by discovering the computer game &lt;a href="https://www.kerbalspaceprogram.com/"&gt;Kerbal Space Program&lt;/a&gt; roughly 10 years after it first became playable. It’s the kind of game that triggers my “I’ll optimize this by writing a script”, so here are a few of the tools I wrote, or interesting tools I found online:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://forum.kerbalspaceprogram.com/index.php?/topic/87463-173-community-delta-v-map-27/page/10/#comment-2581323"&gt;∆v chart by Kowgan&lt;/a&gt;
  (&lt;a href="https://blog.dest-unreach.be/2021/08/25/kerbal_space_program/ksp/dv-chart.png"&gt;local copy&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://alexmoon.github.io/ksp/"&gt;Alex Moon's Launch Window Planner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://krafpy.github.io/KSP-MGA-Planner/"&gt;A multi-gravity assist planner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kerbal-transfer-illustrator.netlify.app/"&gt;Another planner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://meyerweb.com/eric/ksp/resonant-orbits/"&gt;Eric Meyer's Resonant Orbit Calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.jacktex.eu/software/ksp_parachutes.php"&gt;Parachute planner by JackTex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/electricity.html"&gt;Electricity generation &amp;amp; storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/engines.html"&gt;Engine selection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/commnet-link-budget.html"&gt;CommNet link budget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/commnet-line-of-sight.html"&gt;CommNet line of sight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/mining.html"&gt;Mining&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/orbits.html"&gt;Orbit transfer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/moon-departure.html"&gt;Interplanetary departure from a moon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/planner.html"&gt;Mission planner&lt;/a&gt; (work in progress)&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/multiengine.html"&gt;Multi-engine planner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static/ksp-tools/resource-conversion.html"&gt;Resource conversion calculator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And some random notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When launching from KSC to rendez-vous with something in a circular orbits at 100km altitude:
    For vertical launches with "my usual ascend profile",
    launch when the target is 26º before the KSC position (viewable as Phase Angle).
    For spaceplane launches (with &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/CR-7_R.A.P.I.E.R._Engine"&gt;RAPIERs&lt;/a&gt; and 10º pitch up all the way),
    launch when the target is 16º before the KSC.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When starting from a 100km circular orbits around Kerbin, a deorbit burn down to Pe=40km will touch down 152º later. Craft consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk1-3_Command_Pod"&gt;Mk3-1 Command module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.kerbalspaceprogram.com/wiki/RC-L01_Remote_Guidance_Unit"&gt;RC-L01&lt;/a&gt; (for storing science and remote control of probes)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Heat_Shield_(2.5m)"&gt;Large Heath Shield&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk16-XL_Parachute"&gt;Mk16-XL Parachute&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2 × &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Mk2-R_Radial-Mount_Parachute"&gt;Mk2-R Radial-Mount Parachute&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For interplanetary travel, you need ~930m/s of Δv to leave Kerbin &lt;a href="https://wiki.kerbalspaceprogram.com/wiki/Sphere_of_influence"&gt;SoI&lt;/a&gt; starting from a 100kmAGL orbits,
  and an additional 100~1000m/s to reach other planets.
  Design for a high enough TWR to do this in a reasonable time.
  Multi-impulse departures are possible:
  as long as the apoapsis is &amp;lt; 8.9MmASL (9.5Mm),
  the Mun will not alter your orbits.
  The below table tries to keep burns &amp;lt;2 minutes until we cross the orbits of the Mun.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TWR [G&lt;sub&gt;Kerbin&lt;/sub&gt;]&lt;/th&gt;
&lt;th&gt;Burns for escape&lt;/th&gt;
&lt;th&gt;Burns for 2100m/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.79&lt;/td&gt;
&lt;td&gt;53s&lt;/td&gt;
&lt;td&gt;120s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.5&lt;/td&gt;
&lt;td&gt;63s&lt;/td&gt;
&lt;td&gt;56s + 87s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;95s&lt;/td&gt;
&lt;td&gt;84s + 130s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;111s + 15s&lt;/td&gt;
&lt;td&gt;111s + 174s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.696&lt;/td&gt;
&lt;td&gt;120s + 16s&lt;/td&gt;
&lt;td&gt;120s + 186s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;120s + 47s + 23s&lt;/td&gt;
&lt;td&gt;120s + 47s + 261s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;2×120s + 94s + 45s&lt;/td&gt;
&lt;td&gt;2×120s + 94s + 522s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;6×120s + 115s + 113s&lt;/td&gt;
&lt;td&gt;6×120s + 115s + 1305s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Most engines produce a constant thrust while on.
    Its fuel consumption scales with the time the engine is on.
    But the energy scales with the distance the force is applied.
    This means that running an engine at a higher speed, yields more kinetic energy for the same amount of fuel.
    This is known as the &lt;a href="https://en.wikipedia.org/wiki/Oberth_effect"&gt;Oberth effect&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When starting out at Minmus, this means that it could be beneficial to first spend some ∆v to dive into Kerbin,
and burn at periapsis for the escape trajectory.
Escaping from Kerbin from a Minmus orbits requires at least ~115m/s.
The boundary seems to be 274.1m/s: for ∆v's above that, it's optimal to
first burn 228.1m/s to put our Kerbin periapsis at 70km (just above the atmosphere),
and burn the rest of our ∆v there.
For a 500m/s total ∆v, this boosts our hyperbolic excess velocity from 670.1m/s (when expended at Minmus orbits)
to 1294.8m/s when diving first and spending the remaining ∆v near Kerbin.
This is the case for all trajectories to other planets.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hyperbolic Excess Velocity after burn(s) from Minmus Orbit" src="https://blog.dest-unreach.be/2021/08/25/kerbal_space_program/ksp/hev-minmus.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The orbits of Minmus has an inclination of 6°.
    Ideally, we want to launch in this orbital plane to avoid plane-change maneuvers.
    Launching into a 6° inclined orbits can be done from launch sites with a latitude of less than 6°, i.e. the Kerbal Space Center.
    The Longitude of the ascending node of Minmus’s orbits is 78°. 
    So we can either launch slightly northbound when KSC passes 78° right ascension, 
    or slightly southwards when KSC passes 258°.
    In-game, it’s not easy to read out the current right ascension.
    We can, however see the Longitude of the Ascending Node (LAN) of our current orbits in the second orbital info pane:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Orbital pane 2" src="https://blog.dest-unreach.be/2021/08/25/kerbal_space_program/ksp/orbit-2.png"&gt;&lt;/p&gt;
&lt;p&gt;Since KSC is in the southern hemisphere, our current position is 90° before our ascending node.
In other words: launch slightly south (97°) when LAN reads 348°, or slightly north (83°) when LAN reads 168°.
You need to eyeball these directions, especially at the beginning of the launch, when mostly vertical, where the Heading-indicator is very sensitive.
I do my gravity turns to the right. 
In that case, you can align the 90° line on the navball with the longer tick mark of the G-meter for 83° departures.
97° departures should aim for the letter G in the G-meter.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Navball aimed for 83° departure" src="https://blog.dest-unreach.be/2021/08/25/kerbal_space_program/ksp/navball-83E.png"&gt;
&lt;img alt="Navball aimed for 83° departure" src="https://blog.dest-unreach.be/2021/08/25/kerbal_space_program/ksp/navball-97E.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Launch windows for the first few years:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Departure Date&lt;/th&gt;
&lt;th&gt;Arrival Date&lt;/th&gt;
&lt;th&gt;Destination&lt;/th&gt;
&lt;th&gt;∆v (m/s)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Y1 D98&lt;/td&gt;
&lt;td&gt;Y1 D64&lt;/td&gt;
&lt;td&gt;Moho&lt;/td&gt;
&lt;td&gt;5165&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y1 D196&lt;/td&gt;
&lt;td&gt;Y4 D138&lt;/td&gt;
&lt;td&gt;Jool&lt;/td&gt;
&lt;td&gt;5043&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y1 D231&lt;/td&gt;
&lt;td&gt;Y2 D75&lt;/td&gt;
&lt;td&gt;Duna&lt;/td&gt;
&lt;td&gt;1678&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y1 D242&lt;/td&gt;
&lt;td&gt;Y6 D227&lt;/td&gt;
&lt;td&gt;Eeloo&lt;/td&gt;
&lt;td&gt;3635&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y1 D269&lt;/td&gt;
&lt;td&gt;Y1 D405&lt;/td&gt;
&lt;td&gt;Moho&lt;/td&gt;
&lt;td&gt;5031&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y1 D390&lt;/td&gt;
&lt;td&gt;Y3 D93&lt;/td&gt;
&lt;td&gt;Dres&lt;/td&gt;
&lt;td&gt;3526&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y2 D86&lt;/td&gt;
&lt;td&gt;Y2 D243&lt;/td&gt;
&lt;td&gt;Moho&lt;/td&gt;
&lt;td&gt;4833&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y2 D160&lt;/td&gt;
&lt;td&gt;Y2 D357&lt;/td&gt;
&lt;td&gt;Eve&lt;/td&gt;
&lt;td&gt;2741&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y2 D242&lt;/td&gt;
&lt;td&gt;Y5 D304&lt;/td&gt;
&lt;td&gt;Jool&lt;/td&gt;
&lt;td&gt;4978&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y2 D245&lt;/td&gt;
&lt;td&gt;Y2 D386&lt;/td&gt;
&lt;td&gt;Moho&lt;/td&gt;
&lt;td&gt;5242&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y2 D257&lt;/td&gt;
&lt;td&gt;Y7 D20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Eeloo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3556&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y2 D393&lt;/td&gt;
&lt;td&gt;Y3 D86&lt;/td&gt;
&lt;td&gt;Moho&lt;/td&gt;
&lt;td&gt;5748&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y3 D83&lt;/td&gt;
&lt;td&gt;Y3 D225&lt;/td&gt;
&lt;td&gt;Moho&lt;/td&gt;
&lt;td&gt;4481&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y3 D120&lt;/td&gt;
&lt;td&gt;Y4 D283&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dres&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3072&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y3 D270&lt;/td&gt;
&lt;td&gt;Y7 D63&lt;/td&gt;
&lt;td&gt;Eeloo&lt;/td&gt;
&lt;td&gt;3644&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y3 D275&lt;/td&gt;
&lt;td&gt;Y6 D216&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Jool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4902&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y3 D305&lt;/td&gt;
&lt;td&gt;Y4 D160&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Duna&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1645&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y3 D366&lt;/td&gt;
&lt;td&gt;Y4 D63&lt;/td&gt;
&lt;td&gt;Moho&lt;/td&gt;
&lt;td&gt;5695&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y3 D401&lt;/td&gt;
&lt;td&gt;Y4 D162&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Eve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2560&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y4 D84&lt;/td&gt;
&lt;td&gt;Y4 D207&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Moho&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4179&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y4 D226&lt;/td&gt;
&lt;td&gt;Y6 D108&lt;/td&gt;
&lt;td&gt;Dres&lt;/td&gt;
&lt;td&gt;3348&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y4 D289&lt;/td&gt;
&lt;td&gt;Y5 D40&lt;/td&gt;
&lt;td&gt;Moho&lt;/td&gt;
&lt;td&gt;5383&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y4 D294&lt;/td&gt;
&lt;td&gt;Y7 D365&lt;/td&gt;
&lt;td&gt;Eeloo&lt;/td&gt;
&lt;td&gt;3943&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Y4 D306&lt;/td&gt;
&lt;td&gt;Y7 D159&lt;/td&gt;
&lt;td&gt;Jool&lt;/td&gt;
&lt;td&gt;4976&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content><category term="games"/></entry><entry><title>Monitoring network bandwidth of individual hosts from the router</title><link href="https://blog.dest-unreach.be/2020/11/28/monitoring_hosts_from_a_router/" rel="alternate"/><published>2020-11-28T00:00:00+01:00</published><updated>2020-11-28T00:00:00+01:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2020-11-28:/2020/11/28/monitoring_hosts_from_a_router/</id><summary type="html">&lt;p&gt;I live in a part of the world where broadband internet subscriptions come with a monthly data cap.
The datacap is relatively large (750GB/month), but streaming 4k content will get you there!
So I wanted to keep taps on my data usage.
My router is the ideal place to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I live in a part of the world where broadband internet subscriptions come with a monthly data cap.
The datacap is relatively large (750GB/month), but streaming 4k content will get you there!
So I wanted to keep taps on my data usage.
My router is the ideal place to monitor this: all traffic needs to pass through there anyway.
And since it runs &lt;a href="https://openwrt.org/"&gt;OpenWRT&lt;/a&gt;, I can easily add the component I need.&lt;/p&gt;
&lt;h1 id="interface-level-statistics"&gt;&lt;a class="toclink" href="#interface-level-statistics"&gt;Interface-level statistics&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The first step is relatively easy.
The Linux kernel already keeps a counter for each network interface that counts the number of bytes it has sent &amp;amp; received.
These counters are exposed through the &lt;code&gt;ifconfig&lt;/code&gt; command, but also through the &lt;code&gt;/proc/net/dev&lt;/code&gt; "file".&lt;/p&gt;
&lt;p&gt;I'm using &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; as my monitoring tool, so I wrote a simple shell-script to expose these numbers as prometheus metrics.
I should note that I'm not very fluent in Prometheus metric naming, so I might have picked something "wrong".&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wan&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;STATS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;pppoe&lt;span class="w"&gt; &lt;/span&gt;/proc/net/dev&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;network_bytes_total{interface=\&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;\&amp;quot;,direction=\&amp;quot;rx\&amp;quot;} &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$STATS&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;network_packets_total{interface=\&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;\&amp;quot;,direction=\&amp;quot;rx\&amp;quot;} &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$STATS&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $3}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;network_bytes_total{interface=\&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;\&amp;quot;,direction=\&amp;quot;tx\&amp;quot;} &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$STATS&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $10}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;network_packets_total{interface=\&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;\&amp;quot;,direction=\&amp;quot;tx\&amp;quot;} &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$STATS&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{print $11}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To expose these metrics to Prometheus, I converted the above snippet into a CGI-script.
This sounds more complicated than it is.
I just added these lines to the top, and linked in into &lt;code&gt;/www/cgi-bin/metrics&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type: text/plain; version=0.0.4&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1 id="host-level-statistics"&gt;&lt;a class="toclink" href="#host-level-statistics"&gt;Host-level statistics&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Adding metrics for individual hosts turned out to be a lot more complicated than I anticipated.
In the good old days, one could simply use an &lt;a href="https://en.wikipedia.org/wiki/Iptables"&gt;iptables&lt;/a&gt;-table that matched all 254 IP-addresses of the subnet, and call it a day.
But we don't live in the good old days anymore, I live in a time where I have native IPv6 connectivity!&lt;/p&gt;
&lt;p&gt;IPv6 complicated this idea a lot.
Not only is it unfeasable to create a table of all 18446744073709551616 IPv6 addresses in the subnet,
&lt;a href="https://tools.ietf.org/html/rfc3041"&gt;IPv6 Privacy Extensions&lt;/a&gt; means that there is no way to combine the different addresses for a particular host...
The only identifier that is usable in a &lt;a href="https://en.wikipedia.org/wiki/Dual-stack#Dual-stack_IP_implementation"&gt;Dual-Stack&lt;/a&gt; environment with &lt;a href="https://tools.ietf.org/html/rfc3041"&gt;IPv6 Privacy Extensions&lt;/a&gt; is the Ethernet &lt;a href="https://en.wikipedia.org/wiki/MAC_address"&gt;MAC address&lt;/a&gt; of the host.&lt;/p&gt;
&lt;p&gt;Once I realised I should use the MAC addresses, the rest seemed simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;iptables&lt;span class="w"&gt; &lt;/span&gt;-N&lt;span class="w"&gt; &lt;/span&gt;accounting
iptables&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;FORWARD&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;accounting
iptables&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;accounting&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;mac&lt;span class="w"&gt; &lt;/span&gt;--mac-source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:00:5E:01:02:03&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;RETURN
iptables&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;accounting&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;mac&lt;span class="w"&gt; &lt;/span&gt;--mac-destination&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:00:5E:01:02:03&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;RETURN
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Only, that doesn't work...
There is no &lt;code&gt;--mac-destination&lt;/code&gt; filter.
The reason is pretty simple: we are still in IP-land, so the kernel does not know yet what MAC-address it will sent the frame to.
You could get around this by adding a complete &lt;a href="https://ebtables.netfilter.org/"&gt;ebtables&lt;/a&gt; setup, but that seems like the "wrong" solution to track IP-level counters.&lt;/p&gt;
&lt;p&gt;The solution is rather complicated.
And I couldn't have done it without help from &lt;a href="https://superuser.com/questions/977997/mark-packets-with-iptables-by-destination-mac-address/1228588#1228588"&gt;this SuperUser answer&lt;/a&gt;.
Use the connection tracking logic to carry the MAC-information over to the return packets:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# CONNMARK only works in the mangle table, so we&amp;#39;ll do the accounting there&lt;/span&gt;
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-N&lt;span class="w"&gt; &lt;/span&gt;accounting_out
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-N&lt;span class="w"&gt; &lt;/span&gt;accounting_in

iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;FORWARD&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;CONNMARK&lt;span class="w"&gt; &lt;/span&gt;--restore-mark&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Restore the saved mark from the CONNTRACK table into the IP-level MARK&lt;/span&gt;
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;FORWARD&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;pppoe&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;account_in&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# do accounting based on the MARK&lt;/span&gt;
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;FORWARD&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;pppoe&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;account_out&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;# do accounting based on MAC, and set MARK&lt;/span&gt;
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;FORWARD&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;CONNMARK&lt;span class="w"&gt; &lt;/span&gt;--save-mark&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;# save IP-level MARK into CONNTRACK table&lt;/span&gt;

&lt;span class="c1"&gt;# Assign unique integer IDs to each host:&lt;/span&gt;
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;account_out&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;mac&lt;span class="w"&gt; &lt;/span&gt;--mac-source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:00:5E:01:02:03&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;comment&lt;span class="w"&gt; &lt;/span&gt;--comment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;desktop&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;MARK&lt;span class="w"&gt; &lt;/span&gt;--set-mark&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;account_in&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;mark&lt;span class="w"&gt; &lt;/span&gt;--mark&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;comment&lt;span class="w"&gt; &lt;/span&gt;--comment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;desktop&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;RETURN

iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;account_out&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;mac&lt;span class="w"&gt; &lt;/span&gt;--mac-source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;:00:5E:04:05:06&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;comment&lt;span class="w"&gt; &lt;/span&gt;--comment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;desktop&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;MARK&lt;span class="w"&gt; &lt;/span&gt;--set-mark&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-A&lt;span class="w"&gt; &lt;/span&gt;account_in&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;mark&lt;span class="w"&gt; &lt;/span&gt;--mark&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;comment&lt;span class="w"&gt; &lt;/span&gt;--comment&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;desktop&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;RETURN

&lt;span class="c1"&gt;# repeat for ip6tables&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Some things that I figured out while trying to get this to work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There is a CONNMARK target that allows you to directly save the mark to the conntrack-table.
  This would allow you to skip the &lt;code&gt;--save-mark&lt;/code&gt; rule.
  This works in &lt;code&gt;iptables&lt;/code&gt;, but does not work in &lt;code&gt;ip6tables&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;restoring a CONNMARK mark to the IP-layer only works in the &lt;code&gt;mangle&lt;/code&gt; table.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Exposing these counters to prometheus isn't very difficult, but the resulting script is rather long and ugly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-vnL&lt;span class="w"&gt; &lt;/span&gt;account_in&lt;span class="w"&gt; &lt;/span&gt;--exact&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s%\s*\([0-9]\+\)\s\+\([0-9]\+\)\s\+.*/\* \(.*\) \*/.*%host_packets_total{ipv=&amp;quot;4&amp;quot;,direction=&amp;quot;in&amp;quot;,host=&amp;quot;\3&amp;quot;} \1\nhost_bytes_total{ipv=&amp;quot;4&amp;quot;,direction=&amp;quot;in&amp;quot;,host=&amp;quot;\3&amp;quot;} \2%p&amp;#39;&lt;/span&gt;
iptables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-vnL&lt;span class="w"&gt; &lt;/span&gt;account_out&lt;span class="w"&gt; &lt;/span&gt;--exact&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s%\s*\([0-9]\+\)\s\+\([0-9]\+\)\s\+.*/\* \(.*\) \*/.*%host_packets_total{ipv=&amp;quot;4&amp;quot;,direction=&amp;quot;out&amp;quot;,host=&amp;quot;\3&amp;quot;} \1\nhost_bytes_total{ipv=&amp;quot;4&amp;quot;,direction=&amp;quot;out&amp;quot;,host=&amp;quot;\3&amp;quot;} \2%p&amp;#39;&lt;/span&gt;
ip6tables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-vnL&lt;span class="w"&gt; &lt;/span&gt;account_in&lt;span class="w"&gt; &lt;/span&gt;--exact&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s%\s*\([0-9]\+\)\s\+\([0-9]\+\)\s\+.*/\* \(.*\) \*/.*%host_packets_total{ipv=&amp;quot;6&amp;quot;,direction=&amp;quot;in&amp;quot;,host=&amp;quot;\3&amp;quot;} \1\nhost_bytes_total{ipv=&amp;quot;6&amp;quot;,direction=&amp;quot;in&amp;quot;,host=&amp;quot;\3&amp;quot;} \2%p&amp;#39;&lt;/span&gt;
ip6tables&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;mangle&lt;span class="w"&gt; &lt;/span&gt;-vnL&lt;span class="w"&gt; &lt;/span&gt;account_out&lt;span class="w"&gt; &lt;/span&gt;--exact&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s%\s*\([0-9]\+\)\s\+\([0-9]\+\)\s\+.*/\* \(.*\) \*/.*%host_packets_total{ipv=&amp;quot;6&amp;quot;,direction=&amp;quot;out&amp;quot;,host=&amp;quot;\3&amp;quot;} \1\nhost_bytes_total{ipv=&amp;quot;6&amp;quot;,direction=&amp;quot;out&amp;quot;,host=&amp;quot;\3&amp;quot;} \2%p&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="monitoring"/></entry><entry><title>Cumulative graphs in Prometheus</title><link href="https://blog.dest-unreach.be/2020/08/16/cumulative-graphs-prometheus/" rel="alternate"/><published>2020-08-16T00:00:00+02:00</published><updated>2020-08-16T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2020-08-16:/2020/08/16/cumulative-graphs-prometheus/</id><summary type="html">&lt;p&gt;I've been experimenting a bit with &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; and &lt;a href="https://grafana.com/"&gt;Grafana&lt;/a&gt;.
It allows you to make beautiful graphs from the metric data
using relatively simple expressions.
But one of the graphs that I had in mind,
turned out to be more difficult.
These are my notes.&lt;/p&gt;
&lt;p&gt;I have a metric that counts …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been experimenting a bit with &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; and &lt;a href="https://grafana.com/"&gt;Grafana&lt;/a&gt;.
It allows you to make beautiful graphs from the metric data
using relatively simple expressions.
But one of the graphs that I had in mind,
turned out to be more difficult.
These are my notes.&lt;/p&gt;
&lt;p&gt;I have a metric that counts the electrical energy my solar panels have produced: &lt;code&gt;energy_out_watthour_total&lt;/code&gt;.
(There is some debate whether &lt;code&gt;watthour&lt;/code&gt; is "right". Some people argue that it should be Joules, but that is not the point of this post.)
Since this is a counter (&lt;code&gt;_total&lt;/code&gt; suffix), Prometheus does all the magic to compensate if this counter would reset to zero.
This also means that the absolute value of the counter is irrelevant, only the changes are meaningful.&lt;/p&gt;
&lt;p&gt;If you are only interested in the changes, &lt;code&gt;rate(metric)&lt;/code&gt; will give you exactly that:
it will calculate the rate of change (for this case, this will yield the average power in Watt between every two datapoints).
But I wanted a cumulative view: how much energy have I produced over this day, this week, ...
For a given day, this give a &lt;a href="https://en.wikipedia.org/wiki/Sigmoid_function"&gt;sigmoid&lt;/a&gt;-like curve:
(I have removed the actual values for privacy)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cumulative energy" src="https://blog.dest-unreach.be/2020/08/16/cumulative-graphs-prometheus/prometheus/cum-1d.png"&gt;&lt;/p&gt;
&lt;p&gt;The corresponding power-graph looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Power" src="https://blog.dest-unreach.be/2020/08/16/cumulative-graphs-prometheus/prometheus/power-1d.png"&gt;&lt;/p&gt;
&lt;p&gt;Both graphs show the same information, and for a single day, they don't differ much.
But when comparing e.g. today to yesterday, I find a cumulative view much easier to read:
are we ahead/behind "schedule" compared to yesterday? Especially when the power is very spikey, I find cumulative graphs much easier to interpret.
Take a look for yourself:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cumulative energy vs yesterday" src="https://blog.dest-unreach.be/2020/08/16/cumulative-graphs-prometheus/prometheus/cum-2d.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Power vs yesterday" src="https://blog.dest-unreach.be/2020/08/16/cumulative-graphs-prometheus/prometheus/power-2d.png"&gt;&lt;/p&gt;
&lt;h1 id="promql"&gt;&lt;a class="toclink" href="#promql"&gt;PromQL&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Next up: getting this idea of a cumulative graph translated into a Grafana dashboard.
The basic idea is not too hard. I want to plot &lt;code&gt;energy_out_watthour_total - ${energy_out_watthour_total_at_the_beginning_of_this_graph}&lt;/code&gt;.
Turns out that last part isn't so easy:
There is no easy way to have Grafana query Prometheus for the value at a particular point in time.
Or, at least I didn't find that way.&lt;/p&gt;
&lt;p&gt;So I reached out to the #prometheus IRC channel on FreeNode. With the help of &lt;code&gt;SuperQ&lt;/code&gt; and &lt;code&gt;roidelapluie&lt;/code&gt;, we figured out some tools to help us:
Grafana exposes a few &lt;a href="https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/"&gt;variables&lt;/a&gt; that you can use inside your query.
Especially the &lt;code&gt;$__from&lt;/code&gt; variable looks interesting for this case.&lt;/p&gt;
&lt;p&gt;Next, we figured out a few ways that didn't work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;metric_total - metric_total offset (time() - $__from)&lt;/code&gt; doesn't work, since offset requires a constant offset, not something dynamic&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;scalar(metric_total and on() time() == vector(1597579200))&lt;/code&gt; doesn't work for multiple reasons:
   you'd need to get the timestamp exactly right, or use a range that covers exactly 1 datapoint.
   And, since prometheus only evaluates the metrics within the time window that is it about to graph,
   this may yield no points at all if hte timestamp is outside that range.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What does work is this construction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;metric_total
-&lt;span class="w"&gt; &lt;/span&gt;max_over_time(
&lt;span class="w"&gt;  &lt;/span&gt;(
&lt;span class="w"&gt;    &lt;/span&gt;metric_total&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;on()&lt;span class="w"&gt; &lt;/span&gt;vector(time())&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$__from&lt;/span&gt;/1000
&lt;span class="w"&gt;  &lt;/span&gt;)[&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;__range_s&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;s:&lt;span class="nv"&gt;$__interval&lt;/span&gt;]
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The inner &lt;code&gt;and&lt;/code&gt; construction gives the metric up until the start of the graph.
The range vector selection allows us to gather up data from before the start of the graph.
The actual values are less important, as long as it scoops up at least one datapoint.
The &lt;code&gt;max_over_time()&lt;/code&gt; takes the highest value it sees. Since this is a counter,
this effectively means taking the most recent value.&lt;/p&gt;
&lt;p&gt;There is an edge case when a reset occurs within this window. We can solve that by using&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;metric_total
-&lt;span class="w"&gt; &lt;/span&gt;avg_over_time(
&lt;span class="w"&gt;    &lt;/span&gt;(
&lt;span class="w"&gt;      &lt;/span&gt;metric_total&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;on()&lt;span class="w"&gt; &lt;/span&gt;vector(time())&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;=&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$__from&lt;/span&gt;/1000&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$__from&lt;/span&gt;/1000+&lt;span class="nv"&gt;$__interval_ms&lt;/span&gt;/1000
&lt;span class="w"&gt;    &lt;/span&gt;)[&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;__range_s&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;s:&lt;span class="nv"&gt;$__interval&lt;/span&gt;]
&lt;span class="w"&gt;  &lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This narrows the time-frame that is considered, and uses &lt;code&gt;avg_over_time()&lt;/code&gt; to average out the remaining points.
Hopefully, there will only one point.
The downside of this expression is that it doesn't work for my solar production:
during the night, the invertor goes into a sleep-state, and I can't get any metrics from it.
This means that, depending on the actual time-window of the graph, the start-time may have no data, failing the query.&lt;/p&gt;
&lt;h1 id="grafana"&gt;&lt;a class="toclink" href="#grafana"&gt;Grafana&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I've struggled a bit on the finishing touches in Grafana, so I'll note them down here as well.
Since solar panels only work during daylight, it makes no sense to waste space on the night hours.
I found that plotting from &lt;code&gt;now/d+5h&lt;/code&gt; to &lt;code&gt;now/d-90m&lt;/code&gt; works best for my location.
This plots the current day from 05:00 until 22:30 (local/browser time).&lt;/p&gt;</content><category term="monitoring"/></entry><entry><title>Half-duplex UART (RS485) on ESP32</title><link href="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/" rel="alternate"/><published>2020-04-26T00:00:00+02:00</published><updated>2020-04-26T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2020-04-26:/2020/04/26/uart-half-duplex-esp32/</id><summary type="html">&lt;p&gt;I'm a happy owner of a few &lt;a href="https://en.wikipedia.org/wiki/ESP32"&gt;ESP32 microcontrollers&lt;/a&gt;. These are similar to the Arduino, but have WiFi &amp;amp; Bluetooth built in, which makes them very versatile. These chips come with &lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/index.html"&gt;a lot of peripherals&lt;/a&gt;, including three &lt;a href="https://en.wikipedia.org/wiki/UART"&gt;UART&lt;/a&gt;s. These allow offloading the actual bit-banging of the pins to dedicated hardware …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm a happy owner of a few &lt;a href="https://en.wikipedia.org/wiki/ESP32"&gt;ESP32 microcontrollers&lt;/a&gt;. These are similar to the Arduino, but have WiFi &amp;amp; Bluetooth built in, which makes them very versatile. These chips come with &lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/index.html"&gt;a lot of peripherals&lt;/a&gt;, including three &lt;a href="https://en.wikipedia.org/wiki/UART"&gt;UART&lt;/a&gt;s. These allow offloading the actual bit-banging of the pins to dedicated hardware, freeing up the main processor for more useful work.&lt;/p&gt;
&lt;p&gt;Normally, UARTs work in full-duplex mode: they can send and receive simultaneously over separate wires. (Obviously, you can operate it in simplex mode as well, by only sending or receiving.) Half-duplex means that the device can both send and receive, but not simultaneously. This post is about getting this half-duplex mode to work, which was more tricky than it should have been.&lt;/p&gt;
&lt;p&gt;Half-duplex is used by &lt;a href="https://en.wikipedia.org/wiki/RS-485"&gt;RS-485&lt;/a&gt;, but also &lt;a href="https://en.wikipedia.org/wiki/Open_Collector"&gt;open-drain&lt;/a&gt; (or open-collector in &lt;a href="https://en.wikipedia.org/wiki/Bipolar_junction_transistor"&gt;bipolar&lt;/a&gt; terminology) buses are inherently half-duplex. The ESP's documentation refers to its half-duplex provisions by the RS-485 name.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#challenges"&gt;Challenges&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#test-setup"&gt;Test setup&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#full-duplex-mode"&gt;Full duplex mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#rs485-modes"&gt;RS485 modes&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#uart_mode_rs485_half_duplex"&gt;UART_MODE_RS485_HALF_DUPLEX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#uart_mode_rs485_collision_detect"&gt;UART_MODE_RS485_COLLISION_DETECT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#uart_mode_rs485_app_ctrl"&gt;UART_MODE_RS485_APP_CTRL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#custom-rs485-modes"&gt;Custom RS485 modes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="challenges"&gt;&lt;a class="toclink" href="#challenges"&gt;Challenges&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Half-duplex works just like full duplex when only one device is transmitting at a time. So for simple request-response protocols with only a single pair of endpoints, this usuall doesn't matter. It becomes more challenging when multiple devices may initiate a conversation:&lt;/p&gt;
&lt;p&gt;Before starting a transmission, the device should first check if the bus is free. I.e. if there is no-one else transmitting at that time. If the bus is busy, the device should hold off its transmission until it is free.&lt;/p&gt;
&lt;p&gt;Even during a transmission, the device should monitor the bus to see if it can see its own transmission correctly. Ideally, it should be able to detect a "collision", and retransmit in that case.&lt;/p&gt;
&lt;h2 id="test-setup"&gt;&lt;a class="toclink" href="#test-setup"&gt;Test setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To perform the test, I tested a single ESP32 against itself: UART2 is the device under test, UART1 is used as a reference, and kept in full-duplex mode. The half-duplex magic is done by abusing the second core the ESP32 offers: I looped back the Tx and Rx pins to GPIO pins and logically ANDed them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;gpio_set_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPIO_MODE_INPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// connected to UART1 Tx&lt;/span&gt;
&lt;span class="n"&gt;gpio_set_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPIO_MODE_INPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// connected to UART2 Tx&lt;/span&gt;
&lt;span class="n"&gt;gpio_set_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPIO_MODE_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// connected to UART1 &amp;amp; UART2 Rx&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(;;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gpio_get_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_27&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gpio_get_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_26&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// idle level is 1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// if either Tx asserts 0, output 0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;gpio_set_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The test consists of 4 parts (&lt;a href="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/rs485/half_duplex_test.c"&gt;source code&lt;/a&gt;):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Receive only&lt;/li&gt;
&lt;li&gt;Transmit only&lt;/li&gt;
&lt;li&gt;Transmit while bus is busy&lt;/li&gt;
&lt;li&gt;Transmit with collision&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first two parts are trivial, but are included for completeness. The third test should reveal if the device waits for the bus to become available before starting a transmission. The forth test will always result in corruption, but the sender should report this.&lt;/p&gt;
&lt;h3 id="full-duplex-mode"&gt;&lt;a class="toclink" href="#full-duplex-mode"&gt;Full duplex mode&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As a reference test, I set up UART2 similar to UART1:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;uart_config_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uart_config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baud_rate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_bits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_DATA_8_BITS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parity&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_PARITY_DISABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop_bits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_STOP_BITS_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flow_ctrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_HW_FLOWCTRL_DISABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;ESP_ERROR_CHECK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uart_param_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UART_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;uart_config&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ESP_ERROR_CHECK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uart_set_pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UART_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_PIN_NO_CHANGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_PIN_NO_CHANGE&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ESP_ERROR_CHECK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uart_driver_install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UART_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As expected, part 1 &amp;amp; 2 returned the expected data; During test 3, UART2 failed to wait until the bus was free (as expected), which garbled the resulting output: (0) is UART1's output (reference), (1) is UART2's Tx (DUT), (2) is the resulting Rx signal given to both UARTs.&lt;/p&gt;
&lt;p&gt;&lt;img alt="transmit with busy bus, full duplex" src="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/rs485/fdx-busy-tx.png"&gt;&lt;/p&gt;
&lt;p&gt;Instead of receiving the "CCCC" (sent by UART1) or the "dd" (sent by UART2), garbage was received:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;03 80 a8 e8                                       |....|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Test 4 failed in the expected way as well. UART2 was transmitting "eeee", while UART1 sent "F" simultaniously, colliding in the first byte.&lt;/p&gt;
&lt;p&gt;&lt;img alt="collision while transmitting, full duplex" src="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/rs485/fdx-col.png"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;04 65 65 65                                       |.eee|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="rs485-modes"&gt;&lt;a class="toclink" href="#rs485-modes"&gt;RS485 modes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The ESP's documentation is very sparse on its half-duplex capabilities. The &lt;a href="https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf"&gt;technical reference manual&lt;/a&gt; mentions the "RS485 mode configuration" register, but no further explanation is given. The &lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/"&gt;ESP-IDF&lt;/a&gt; programming guide goes in to a bit more detail, describing the &lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#interface-connection-options"&gt;different modes&lt;/a&gt; to connect to a 485 bus. Its API provides three modes that look interesting: UART_MODE_RS485_HALF_DUPLEX, UART_MODE_RS485_COLLISION_DETECT and UART_MODE_RS485_APP_CTRL.    &lt;/p&gt;
&lt;h4 id="uart_mode_rs485_half_duplex"&gt;&lt;a class="toclink" href="#uart_mode_rs485_half_duplex"&gt;UART_MODE_RS485_HALF_DUPLEX&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;In RS485_HALF_DUPLEX mode, contrary to my expectations, nothing happened: test 1 &amp;amp; 2 still passed, test 3 &amp;amp; 4 still failed in a very similar way.&lt;/p&gt;
&lt;p&gt;Test 3 failed to wait for the bus to free up, and returned garbage data:&lt;/p&gt;
&lt;p&gt;&lt;img alt="transmit with busy bus, RS485_HALF_DUPLEX" src="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/rs485/rs485hdx-busy-tx.png"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;03 80 a8 e8                                       |....|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Test 4 got corrupted as well.&lt;/p&gt;
&lt;p&gt;&lt;img alt="collision while transmitting, RS485_HALF_DUPLEX" src="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/rs485/rs485hdx-col.png"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;04 65 65 65                                       |.eee|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In this mode, you can use the &lt;code&gt;uart_get_collision_flag()&lt;/code&gt; function to see if there was a collision. It returned &lt;code&gt;false&lt;/code&gt; for test 1 (as expected), but &lt;code&gt;true&lt;/code&gt; for tests 2, 3 and 4 (only 4 was expected). &lt;/p&gt;
&lt;h4 id="uart_mode_rs485_collision_detect"&gt;&lt;a class="toclink" href="#uart_mode_rs485_collision_detect"&gt;UART_MODE_RS485_COLLISION_DETECT&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;This setting paniced. I haven't figured out why (yet).&lt;/p&gt;
&lt;h4 id="uart_mode_rs485_app_ctrl"&gt;&lt;a class="toclink" href="#uart_mode_rs485_app_ctrl"&gt;UART_MODE_RS485_APP_CTRL&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;RS485_APP_CTRL mode was indistinguishable from HALF_DUPLEX in my tests:&lt;/p&gt;
&lt;p&gt;Test 3 failed to wait for the bus to free up, and returned garbage data:&lt;/p&gt;
&lt;p&gt;&lt;img alt="transmit with busy bus, RS485_APP_CTRL" src="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/rs485/rs485appctl-busy-tx.png"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;03 80 a8 e8                                       |....|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Test 4 got corrupted as well.&lt;/p&gt;
&lt;p&gt;&lt;img alt="collision while transmitting, RS485_APP_CTL" src="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/rs485/rs485appctl-col.png"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;04 65 65 65                                       |.eee|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;uart_get_collision_flag()&lt;/code&gt; returned &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="custom-rs485-modes"&gt;&lt;a class="toclink" href="#custom-rs485-modes"&gt;Custom RS485 modes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;From the tests above, it is clear that neither of the provided modes perform the desired functionality. Time to dig in deeper.&lt;/p&gt;
&lt;p&gt;The ESP-IDF SDK is nothing more than a set of provided library functions that abstract away the register manipulations. &lt;a href="https://github.com/espressif/esp-idf/blob/143d26aa49df524e10fb8e41a71d12e731b9b71d/components/driver/uart.c#L1480"&gt;uart_set_mode()&lt;/a&gt; for example manipulates the &lt;code&gt;UART_RS485_CONF_REG&lt;/code&gt; register (accessed as &lt;code&gt;UART[uart_num]-&amp;gt;rs485_conf&lt;/code&gt; in ESP-IDF). The code shows that all three modes activate RS485 mode (&lt;code&gt;UART[uart_num]-&amp;gt;rs485_conf.en = 1&lt;/code&gt;), but they differ in the settings of the other flags.&lt;/p&gt;
&lt;p&gt;What surprised me, is that all three modes set 
&lt;code&gt;UART_RS485RXBY_TX_EN&lt;/code&gt;/&lt;code&gt;rx_busy_tx_en&lt;/code&gt; flag: &lt;code&gt;enable the RS-485 transmitter to send data, when the RS-485 receiver line is busy&lt;/code&gt;. As soon as that was turned off, things started looking better: UART2 waited for the bus to free up before transmitting its "dd" message, which resulted in the correct sequence being received. The collision flag was still wrong (it indicated collisions in test 2 &amp;amp; 3 as well).&lt;/p&gt;
&lt;p&gt;&lt;img alt="transmit with busy bus, rx_busy_tx_dis" src="https://blog.dest-unreach.be/2020/04/26/uart-half-duplex-esp32/rs485/rxbusy-busy-tx.png"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;43 43 43 43 64 64                                 |CCCCdd|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Another flag that is available is the &lt;code&gt;UART_RS485TX_RX_EN&lt;/code&gt;/&lt;code&gt;tx_rx_en&lt;/code&gt;, which will &lt;code&gt;enable the transmitter’s output signal loop back to the receiver’s input signal&lt;/code&gt;. The &lt;code&gt;APP_CTRL&lt;/code&gt; mode (which I used as a starting point) leaves this flag off. I expected this to suppress receiving my own transmissions, but instead, it returned a &lt;code&gt;0x00&lt;/code&gt;-byte for every byte transmitted. So let's try setting this to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This seems to be the magic combination: Tx waits until the bus is free, and the collision flag yields the expected result (only &lt;code&gt;true&lt;/code&gt; for test 4). It only introduces a small problem:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;E (1398) uart: uart_get_collision_flag(1568): wrong mode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;uart_get_collision_flag()&lt;/code&gt; reads out the collision state as reported by the SDK. And the SDK doesn't &lt;a href="https://github.com/espressif/esp-idf/blob/143d26aa49df524e10fb8e41a71d12e731b9b71d/components/driver/uart.c#L997-L1007"&gt;track&lt;/a&gt; the collisions when in &lt;code&gt;APP_CTRL&lt;/code&gt;. But nothing is stopping us to do that ourselves: The hardware sets the &lt;code&gt;UART_RS485_CLASH_INT_RAW&lt;/code&gt; bit in the &lt;code&gt;UART_INT_RAW_REG&lt;/code&gt; register. This can trigger an actual interrupt if enabled, but we are only interested in finding out &lt;em&gt;if&lt;/em&gt; a clash happened, not exactly &lt;em&gt;when&lt;/em&gt; it happened. So we can simply read out this register, and clear it before the next transmission:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;UART2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int_clr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rs485_clash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// clear bit&lt;/span&gt;
&lt;span class="n"&gt;uart_write_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UART_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;whatever&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;collision_happened&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int_raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rs485_clash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For my purpose, I wanted the transmision to be kept back if the bus is busy. I also wanted to have accurate collision reporting. The following code sets this up for me:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;uart_config_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uart_config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baud_rate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_bits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_DATA_8_BITS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parity&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_PARITY_DISABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop_bits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_STOP_BITS_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flow_ctrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_HW_FLOWCTRL_DISABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;ESP_ERROR_CHECK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uart_param_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UART_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;uart_config&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ESP_ERROR_CHECK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uart_set_pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UART_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPIO_NUM_17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_PIN_NO_CHANGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_PIN_NO_CHANGE&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ESP_ERROR_CHECK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uart_driver_install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UART_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;uart_set_mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UART_NUM_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART_MODE_RS485_APP_CTRL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;UART2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rs485_conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rx_busy_tx_en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// don&amp;#39;t send while receiving =&amp;gt; collision avoidance&lt;/span&gt;
&lt;span class="n"&gt;UART2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rs485_conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tx_rx_en&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// loopback (1), so collision detection works&lt;/span&gt;

&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;did_collision_happen_since_last_check&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UART2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int_raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rs485_clash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;UART2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int_clr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rs485_clash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// clear bit&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="microcontroller"/></entry><entry><title>Raspberry pi 4 as broadband router</title><link href="https://blog.dest-unreach.be/2020/02/21/raspberry-pi-4-router/" rel="alternate"/><published>2020-02-21T00:00:00+01:00</published><updated>2020-02-21T00:00:00+01:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2020-02-21:/2020/02/21/raspberry-pi-4-router/</id><summary type="html">&lt;p&gt;&lt;a href="https://2007.blog.dest-unreach.be/2014/03/05/raspberry-pi-as-broadband-router/"&gt;A few years ago&lt;/a&gt;, I already considered using a Raspberry Pi as a broadband router. Back then, its first generation wasn't fast enough. This changed with the 4th generation of the Raspberry pi.&lt;/p&gt;
&lt;p&gt;The test setup was similar to the setup I use previously:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two beefy laptops on the ends …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://2007.blog.dest-unreach.be/2014/03/05/raspberry-pi-as-broadband-router/"&gt;A few years ago&lt;/a&gt;, I already considered using a Raspberry Pi as a broadband router. Back then, its first generation wasn't fast enough. This changed with the 4th generation of the Raspberry pi.&lt;/p&gt;
&lt;p&gt;The test setup was similar to the setup I use previously:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two beefy laptops on the ends&lt;/li&gt;
&lt;li&gt;A VLAN-capable switch to connect everything&lt;/li&gt;
&lt;li&gt;The Raspberry pi "&lt;a href="https://en.wikipedia.org/wiki/One-armed_router"&gt;on a stick&lt;/a&gt;" to route traffic from one VLAN to the other&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was happy with the results. Ping latency was 0.834 ±0.075 ms.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ iperf -c 192.168.23.231 -p 5001 -t 60 -i 5
------------------------------------------------------------
Client connecting to 192.168.23.231, TCP port 5001
TCP window size:  289 KByte (default)
------------------------------------------------------------
[  4] local 192.168.10.20 port 60887 connected with 192.168.23.231 port 5001
[ ID] Interval       Transfer     Bandwidth
[  4]  0.0- 5.0 sec   510 MBytes   855 Mbits/sec
[  4]  5.0-10.0 sec   514 MBytes   862 Mbits/sec
[  4] 10.0-15.0 sec   513 MBytes   860 Mbits/sec
[  4] 15.0-20.0 sec   512 MBytes   860 Mbits/sec
[  4] 20.0-25.0 sec   513 MBytes   860 Mbits/sec
[  4] 25.0-30.0 sec   514 MBytes   862 Mbits/sec
[  4] 30.0-35.0 sec   516 MBytes   866 Mbits/sec
[  4] 35.0-40.0 sec   515 MBytes   864 Mbits/sec
[  4] 40.0-45.0 sec   514 MBytes   863 Mbits/sec
[  4] 45.0-50.0 sec   506 MBytes   849 Mbits/sec
[  4] 50.0-55.0 sec   498 MBytes   835 Mbits/sec
[  4] 55.0-60.0 sec   506 MBytes   849 Mbits/sec
[  4]  0.0-60.0 sec  5.99 GBytes   857 Mbits/sec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The bidirectional flows performed worse, as expected. But I had expected a bit better results:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ iperf -c 192.168.23.231 -d -p 5001 -t 60 -i 5
------------------------------------------------------------
Server listening on TCP port 5001
TCP window size: -1.00 Byte (default)
------------------------------------------------------------
------------------------------------------------------------
Client connecting to 192.168.23.231, TCP port 5001
TCP window size:  273 KByte (default)
------------------------------------------------------------
[  5] local 192.168.10.20 port 60891 connected with 192.168.23.231 port 5001
[  6] local 192.168.10.20 port 5001 connected with 192.168.23.231 port 36352
[ ID] Interval       Transfer     Bandwidth
[  5]  0.0- 5.0 sec  82.2 MBytes   138 Mbits/sec
[  6]  0.0- 5.0 sec   187 MBytes   313 Mbits/sec
[  5]  5.0-10.0 sec  71.4 MBytes   120 Mbits/sec
[  6]  5.0-10.0 sec   163 MBytes   273 Mbits/sec
[  5] 10.0-15.0 sec  75.8 MBytes   127 Mbits/sec
[  6] 10.0-15.0 sec   177 MBytes   296 Mbits/sec
[  6] 15.0-20.0 sec   177 MBytes   297 Mbits/sec
[  5] 15.0-20.0 sec  79.6 MBytes   134 Mbits/sec
[  5] 20.0-25.0 sec  66.0 MBytes   111 Mbits/sec
[  6] 20.0-25.0 sec   187 MBytes   313 Mbits/sec
[  5] 25.0-30.0 sec  70.6 MBytes   118 Mbits/sec
[  6] 25.0-30.0 sec   200 MBytes   335 Mbits/sec
[  6] 30.0-35.0 sec   174 MBytes   291 Mbits/sec
[  5] 30.0-35.0 sec  75.2 MBytes   126 Mbits/sec
[  5] 35.0-40.0 sec  74.0 MBytes   124 Mbits/sec
[  6] 35.0-40.0 sec   185 MBytes   310 Mbits/sec
[  5] 40.0-45.0 sec  65.2 MBytes   109 Mbits/sec
[  6] 40.0-45.0 sec   171 MBytes   287 Mbits/sec
[  5] 45.0-50.0 sec  67.0 MBytes   112 Mbits/sec
[  6] 45.0-50.0 sec   173 MBytes   291 Mbits/sec
[  5] 50.0-55.0 sec  65.0 MBytes   109 Mbits/sec
[  6] 50.0-55.0 sec   163 MBytes   273 Mbits/sec
[  5] 55.0-60.0 sec  69.2 MBytes   116 Mbits/sec
[  5]  0.0-60.0 sec   861 MBytes   120 Mbits/sec
[  6] 55.0-60.0 sec   166 MBytes   279 Mbits/sec
[  6]  0.0-60.0 sec  2.07 GBytes   297 Mbits/sec
[SUM]  0.0-60.0 sec  2.25 GBytes   323 Mbits/sec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="linux"/></entry><entry><title>Reading CPU temperature and Fan speeds from the command line in macOs 10.14 Mojave</title><link href="https://blog.dest-unreach.be/2019/09/27/macos_temp_fan_cli/" rel="alternate"/><published>2019-09-27T00:00:00+02:00</published><updated>2019-09-27T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2019-09-27:/2019/09/27/macos_temp_fan_cli/</id><summary type="html">&lt;p&gt;I was looking to read out the current temperature and fan speed of my MacBook Pro running macOS 10.14 Mojave. I prefered build-in tools over shady apps. After some searching, I found the &lt;code&gt;powermetrics&lt;/code&gt; tool (which needs &lt;code&gt;sudo&lt;/code&gt; privileges). By default, it outputs lots of details every 5 seconds …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was looking to read out the current temperature and fan speed of my MacBook Pro running macOS 10.14 Mojave. I prefered build-in tools over shady apps. After some searching, I found the &lt;code&gt;powermetrics&lt;/code&gt; tool (which needs &lt;code&gt;sudo&lt;/code&gt; privileges). By default, it outputs lots of details every 5 seconds:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Machine model: MacBookPro15,1
SMC version: Unknown
EFI version: 220.99.0
OS version: 18G103
Boot arguments:
Boot time: Fri Sep 27 13:23:38 2019



*** Sampled system activity (Fri Sep 27 14:05:02 2019 +0200) (5015.24ms elapsed) ***

*** Running tasks ***

Name                               ID     CPU ms/s  User%  Deadlines (&amp;lt;2 ms, 2-5 ms)  Wakeups (Intr, Pkg idle)
kernel_task                        0      28.82     0.00   0.00    0.00               673.63  212.91
WindowServer                       183    67.37     61.86  29.70   10.57              46.45   15.35
CalendarAgent                      321    32.20     82.65  0.00    0.00               0.20    0.20
[...]
ALL_TASKS                          -2     766.12    80.97  101.09  14.16              1125.97 384.23


**** Battery and backlight usage ****

Backlight level: 791 (range 0-1024)
Keyboard Backlight level: 0 (off 0 on range 32-512)


**** Network activity ****

out: 30.71 packets/s, 11892.96 bytes/s
in:  31.30 packets/s, 18962.61 bytes/s


**** Disk activity ****

read: 1.00 ops/s 4.08 KBytes/s
write: 7.58 ops/s 78.40 KBytes/s

****  Interrupt distribution ****

CPU 0:
    Vector 0x46(SMC): 12.96 interrupts/sec
    Vector 0x72(IGPU): 17.95 interrupts/sec
    Vector 0x74(GFX0): 418.52 interrupts/sec
    Vector 0x75(HDAU): 3.79 interrupts/sec
    Vector 0x76(XHC1): 143.36 interrupts/sec
    Vector 0x79(ANS2): 6.58 interrupts/sec
    Vector 0x86(ARPT): 7.58 interrupts/sec
    Vector 0x8c(IOBC): 0.80 interrupts/sec
    Vector 0xdd(TMR): 195.40 interrupts/sec
    Vector 0xde(IPI): 112.86 interrupts/sec
    Vector 0xdf(PMI): 0.20 interrupts/sec
CPU 1:
    Vector 0xdd(TMR): 1.20 interrupts/sec
    Vector 0xde(IPI): 35.09 interrupts/sec
[...]
CPU 15:
    Vector 0xdd(TMR): 0.60 interrupts/sec
    Vector 0xde(IPI): 4.39 interrupts/sec



**** Processor usage ****

Intel energy model derived package power (CPUs+GT+SA): 9.62W

LLC flushed residency: 44.5%

System Average frequency as fraction of nominal: 118.43% (2723.96 Mhz)
Package 0 C-state residency: 47.56% (C2: 47.56% C3: 0.00% C6: 0.00% C7: 0.00% C8: 0.00% C9: 0.00% C10: 0.00% )

Performance Limited Due to:
CPU LIMIT TURBO_ATTENUATION
CPU LIMIT SPARE_IA_14
CPU/GPU Overlap: 1.38%
Cores Active: 49.10%
GPU Active: 1.51%
Avg Num of Cores Active: 0.83

Core 0 C-state residency: 74.42% (C3: 3.57% C6: 0.00% C7: 70.85% )

CPU 0 duty cycles/s: active/idle [&amp;lt; 16 us: 618.91/176.66] [&amp;lt; 32 us: 148.35/20.34] [&amp;lt; 64 us: 119.44/226.11] [&amp;lt; 128 us: 138.18/270.18] [&amp;lt; 256 us: 155.53/166.69] [&amp;lt; 512 us: 67.20/161.31] [&amp;lt; 1024 us: 40.08/87.73] [&amp;lt; 2048 us: 21.33/90.32] [&amp;lt; 4096 us: 5.78/65.40] [&amp;lt; 8192 us: 1.00/51.24] [&amp;lt; 16384 us: 1.00/1.20] [&amp;lt; 32768 us: 0.20/0.00]
CPU Average frequency as fraction of nominal: 103.26% (2375.02 Mhz)

[...]

CPU 15 duty cycles/s: active/idle [&amp;lt; 16 us: 57.82/4.98] [&amp;lt; 32 us: 0.40/4.39] [&amp;lt; 64 us: 0.60/5.58] [&amp;lt; 128 us: 0.20/5.58] [&amp;lt; 256 us: 0.60/5.98] [&amp;lt; 512 us: 0.20/6.18] [&amp;lt; 1024 us: 0.20/5.78] [&amp;lt; 2048 us: 0.20/1.99] [&amp;lt; 4096 us: 0.40/2.19] [&amp;lt; 8192 us: 0.00/4.59] [&amp;lt; 16384 us: 0.00/3.79] [&amp;lt; 32768 us: 0.00/2.79]
CPU Average frequency as fraction of nominal: 113.15% (2602.45 Mhz)

**** GPU usage ****

GPU 0 name IntelIG
GPU 0 C-state residency: 98.66% (0.08%, 98.58%)
GPU 0 P-state residency: 1200MHz: 0.00%, 1150MHz: 0.00%, 1100MHz: 0.00%, 1050MHz: 0.00%, 1000MHz: 0.00%, 950MHz: 0.00%, 900MHz: 0.00%, 850MHz: 0.00%, 800MHz: 0.00%, 750MHz: 0.00%, 700MHz: 0.00%, 650MHz: 0.00%, 600MHz: 0.00%, 550MHz: 0.00%, 500MHz: 0.00%, 450MHz: 0.00%, 400MHz: 0.00%, 350MHz: 0.62%
GPU 0 average active frequency as fraction of nominal (350.00Mhz): 100.00% (350.00Mhz)
GPU 0 HW average active frequency   : 0.00
GPU 0 GPU Busy                      : 1.34%
GPU 0 DC6 Residency                 : 0.00%
GPU 0 [PSR] GPU + TCON are Off      : 0.00%
GPU 0 [PSR] Only GPU is On          : 100.00%
GPU 0 [PSR] Only TCON is On         : 0.00%
GPU 0 [PSR] GPU + TCON are On       : 0.00%
GPU 0 [PSR] StateMachine Bypass     : 100.00%
GPU 0 [PSR] StateMachine FIFO       : 0.00%
GPU 0 [PSR] StateMachine Others     : 0.00%
GPU 0 DPB Strong On                 : 0.00%
GPU 0 DPB Weak On                   : 0.00%
GPU 0 PPFM on                       : 0.00%
GPU 0 Throttle High Priority(%): 0
GPU 0 Throttle NormalHi Priority(%): 0
GPU 0 Throttle Normal Priority(%): 0
GPU 0 Throttle Low Priority(%): 0
GPU 0 Slice switch                  : 0 (0.00/second)
GPU 0 DC6 Exit Reason - Flip: 0 (0.00/second)
GPU 0 DC6 Exit Reason - Register: 0 (0.00/second)
GPU 0 DC6 Exit Reason - Gamma: 0 (0.00/second)
GPU 0 DC6 Exit Reason - Interrupt: 0 (0.00/second)
GPU 0 DC6 Exit Reason - Cursor: 0 (0.00/second)
GPU 0 DC6 Exit Reason - Render: 0 (0.00/second)
GPU 0 FB Test Case 0


**** SMC sensors ****

CPU Thermal level: 19
GPU Thermal level: 0
IO Thermal level: 0
Fan: 2001.43 rpm
CPU die temperature: 65.77 C
GPU die temperature: 58.00 C
CPU Plimit: 0.00
GPU Plimit: 0.00
Number of prochots: 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For scripted execution, you can limit the output in several ways. If you are only interested in the current temperature and fan speed, &lt;code&gt;sudo powermetrics -n 1 -i 1 --samplers smc&lt;/code&gt; returns these without 5 seconds of waiting.&lt;/p&gt;</content><category term="macOs"/></entry><entry><title>404to302 Lambda@Edge</title><link href="https://blog.dest-unreach.be/2019/09/22/404to302_lae/" rel="alternate"/><published>2019-09-22T00:00:00+02:00</published><updated>2019-09-22T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2019-09-22:/2019/09/22/404to302_lae/</id><summary type="html">&lt;p&gt;I discovered the &lt;a href="https://4042302.org/"&gt;404to302&lt;/a&gt; concept after &lt;a href="https://twitter.com/aral/status/949966935344762880"&gt;a tweet&lt;/a&gt; by &lt;a href="https://ar.al/"&gt;Aral Balkan&lt;/a&gt;, and immediately liked the idea. It solves a hard problem (keeping permalinks alive and working) in a fairly easy way: simply archive the old site, and avoid re-using the same permalink again. If you permalinks contain a date of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I discovered the &lt;a href="https://4042302.org/"&gt;404to302&lt;/a&gt; concept after &lt;a href="https://twitter.com/aral/status/949966935344762880"&gt;a tweet&lt;/a&gt; by &lt;a href="https://ar.al/"&gt;Aral Balkan&lt;/a&gt;, and immediately liked the idea. It solves a hard problem (keeping permalinks alive and working) in a fairly easy way: simply archive the old site, and avoid re-using the same permalink again. If you permalinks contain a date of some sort, this is very easy.&lt;/p&gt;
&lt;p&gt;Since I've moved to host my static site on &lt;a href="https://aws.amazon.com/s3/"&gt;Amazon S3&lt;/a&gt;, I needed to add this functionality myself. Luckily, &lt;a href="https://aws.amazon.com/cloudfront/"&gt;Amazon CloudFront&lt;/a&gt; allows you to run custom code when handling a request: &lt;a href="https://aws.amazon.com/lambda/edge/"&gt;Lambda@Edge&lt;/a&gt;. For this 404to302 concept, I hooked in to the "origin response" phase, inspecting every response for possible 404's. If the response is a 404, it looks up if a fallback URL is configured on the CloudFront distribution for this request, and returns a 302 to that location if present.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/niobos/404to302-lae"&gt;The code&lt;/a&gt; is available on GitHub.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Switching to a static website</title><link href="https://blog.dest-unreach.be/2019/09/21/switching-to-static-website/" rel="alternate"/><published>2019-09-21T00:00:00+02:00</published><updated>2019-09-21T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2019-09-21:/2019/09/21/switching-to-static-website/</id><summary type="html">&lt;p&gt;After over 12 years of using WordPress, I switched to a static site generator. Here's the long story.&lt;/p&gt;</summary><content type="html">&lt;p&gt;After over 12 years of using &lt;a href="https://wordpress.org/"&gt;WordPress&lt;/a&gt;, I got tired of keeping up-to-date with all the patches. Wordpress itself was fairly easy to keep up-to-date thanks to the auto-updater. But it doesn't stop there: PHP, MySQL, Apache, ...&lt;/p&gt;
&lt;p&gt;So I reconsidered what I used my blog for. It's mostly a way for me to document my experiments, hoping that the information would be useful for someone. Often, that someone is me, a few years down the line. While I did appreciate some useful comments now and then, the fight against comment-spam was tiring as well. So I'm not too sad that I'd loose that functionality.&lt;/p&gt;
&lt;p&gt;Which brings me to picking a static site generator. Unlike the "dynamic" CMSes, there are a lot more players in the "static site generation" market. I explored different scenario's:&lt;/p&gt;
&lt;p&gt;At first, I wanted to import the entire archive of my WordPress blog into the new tool. That turns out to be close to impossible. There &lt;a href="https://import.jekyllrb.com/docs/wordpress/"&gt;are&lt;/a&gt; &lt;a href="https://docs.getpelican.com/en/3.6.3/importer.html"&gt;tools&lt;/a&gt; that claim to do so, but it never worked out: Images being linked to the wrong URL, layout shifting all around, ...&lt;/p&gt;
&lt;p&gt;I was about to admit defeat when I discovered the &lt;a href="https://4042302.org/"&gt;404to302&lt;/a&gt; idea. The idea is as simple as it is briliant: modify the webserver to convert a 404 (File not found) into a 302 (redirect) to the previous version of the site. This way, you no longer need to import all previous content, but all old (perma)links simply keep on working (albeit with an additional redirect step)! This removed one big item from my requirement list: being able to import the previous content.&lt;/p&gt;
&lt;p&gt;I ended up picking &lt;a href="https://getpelican.com/"&gt;Pelican&lt;/a&gt;. It's slightly less popular compared to &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; and &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;, but it's written in a language that I have experience in: Python. I initially tried to get Jekyll to work, but failed to get all required Gems installed on my system.&lt;/p&gt;</content><category term="misc"/></entry><entry><title>Receiving NOAA images with RTL-SDR</title><link href="https://blog.dest-unreach.be/2019/08/25/receiving-noaa/" rel="alternate"/><published>2019-08-25T00:00:00+02:00</published><updated>2019-08-25T00:00:00+02:00</updated><author><name>Niobos</name></author><id>tag:blog.dest-unreach.be,2019-08-25:/2019/08/25/receiving-noaa/</id><summary type="html">&lt;p&gt;During summer brake, I set out for a tech adventure together with my son. We wanted to do weather predictions ourselves. Or at least pretend to do so.&lt;/p&gt;
&lt;p&gt;What sparked the idea, was a &lt;a href="https://twitter.com/rtlsdrblog/status/1163702625402904576"&gt;beautiful tweet from rtl-sdr.com&lt;/a&gt;. Receiving GOES satellites was a bit of a high bar for …&lt;/p&gt;</summary><content type="html">&lt;p&gt;During summer brake, I set out for a tech adventure together with my son. We wanted to do weather predictions ourselves. Or at least pretend to do so.&lt;/p&gt;
&lt;p&gt;What sparked the idea, was a &lt;a href="https://twitter.com/rtlsdrblog/status/1163702625402904576"&gt;beautiful tweet from rtl-sdr.com&lt;/a&gt;. Receiving GOES satellites was a bit of a high bar for a first attempt, so I started out with receiving NOAA images. Contrary to the GOES satellites, NOAA's are in Low Earth Orbit and have an analogue downlink at 137MHz, which does not require a dish and amplifier to receive.&lt;/p&gt;
&lt;p&gt;I followed the guide I found in the &lt;a href="https://noaa-apt.mbernardi.com.ar/guide.html"&gt;NOAA_APT&lt;/a&gt; GitHub repo. We started out by building a very simple V-dipole antenna out of some leftover 1.5mm² electrical wiring. A V-dipole is not the best antenna to receive a circular polarized signal, but let's first see if we get this to work. I connected the two dipole wires to a short piece of coax to connect it to the &lt;a href="https://en.wikipedia.org/wiki/Belling-Lee_connector"&gt;Belling-Lee&lt;/a&gt; connector that fits my &lt;a href="https://www.rtl-sdr.com/about-rtl-sdr/"&gt;RTL-SDR&lt;/a&gt; dongle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="our V-dipole antenna" src="https://blog.dest-unreach.be/2019/08/25/receiving-noaa/noaa/antenna.jpg"&gt;&lt;/p&gt;
&lt;p&gt;There used to be a lot of NOAA-satellites active, but as of this writing, only three satellites remain in service: NOAA-15, NOAA-18 and NOAA-19. These satellites are in polar orbits around the earth, completing one revolution every 102 minutes. But since the earth is rotating underneath them, they fly over different parts of the planet every time. Figuring out when the satellite will be overhead requires a degree in orbital mechanics. Or the use of some software, such as &lt;a href="http://gpredict.oz9aec.net/"&gt;gpredict&lt;/a&gt;. It will calculate where the satellites are at this moment (or an arbitrary moment), and tell you when the next pass will be at your location (or any location, for that matter). It also plots the "ground track" of the satellites, which shows that the satellite visits different parts of the planet on every orbits.&lt;/p&gt;
&lt;p&gt;&lt;img alt="ground track of NOAA-18" src="https://blog.dest-unreach.be/2019/08/25/receiving-noaa/noaa/NOAA-18-ground.png"&gt;&lt;/p&gt;
&lt;p&gt;To increase my odds, I picked a pass with very high elevation. The higher the satellite is above the horizon, the stronger its signal can be received. And since I had no idea what to expect, I could use every advantage available. gpredict predicted a pass with 85º elevation for today, so I made sure I was ready to receive "something": Antenna oriented for a North-South trajectory, flat on a plastic table in our garden. Things go quick: in 15 minutes, the satellite rises above the horizon, flies overhead, and disappears on the other horizon.&lt;/p&gt;
&lt;p&gt;&lt;img alt="polar plot of satellite position" src="https://blog.dest-unreach.be/2019/08/25/receiving-noaa/noaa/NOAA-18-polar.png"&gt;&lt;/p&gt;
&lt;p&gt;I had &lt;a href="http://gqrx.dk/"&gt;GQRX&lt;/a&gt; open, tuned at 137.872500MHz, 300kHz bandwidth. I figured that I'd want the signal a bit away from DC to avoid trouble demodulating. NOAA-18 transmits at 137.9125MHz, with an RF bandwidth of 34kHz, which gives me 23kHz room above DC to accommodate any callibration issues and doppler shift. I enabled the FM demodulator, but all I heard was noise. But the waterfall already showed some promise.&lt;/p&gt;
&lt;p&gt;&lt;img alt="screenshot of GQRX during early acquisition" src="https://blog.dest-unreach.be/2019/08/25/receiving-noaa/noaa/gqrx-early.png"&gt;&lt;/p&gt;
&lt;p&gt;A bit of patience allowed the satellite to climb higher above the horizon, and that yielded a stronger signal. I could hear the expected 2.4kHz beeping and "tic-toc" sound of the sync signals.&lt;/p&gt;
&lt;p&gt;&lt;img alt="screenshot of GQRX at peak" src="https://blog.dest-unreach.be/2019/08/25/receiving-noaa/noaa/gqrx-full.png"&gt;&lt;/p&gt;
&lt;p&gt;The next step was a bit of a disappointment. I wanted to demodulate the FM signal sitting at ~40kHz to baseband using a command line tool. But I didn't find any tool to do this. So I took the easy way out and simply replayed the recording in GQRX, recording the demodulated output to a WAV-file. One note if you intend to use &lt;a href="https://wxtoimgrestored.xyz/"&gt;WXtoImg&lt;/a&gt;: GQRX records in 16bit 48kHz, while WXtoImg requires 11kHz. Luckily, &lt;a href="https://noaa-apt.mbernardi.com.ar/guide.html"&gt;NOAA_APT&lt;/a&gt; is happy to accept pretty much anything.&lt;/p&gt;
&lt;p&gt;The result was more impressive that I expected. It was a relatively cloudless day, so you could clearly see the coastlines of Denmark and Great Britain, especially on the Infrared channel (right image). Based on the &lt;a href="https://noaa-apt.mbernardi.com.ar/how-it-works.html#about-apt-images"&gt;description of the APT signal&lt;/a&gt;, the image contains one visible spectrum image (channel 1, 580nm - 680nm, left), and an infrared image (channel 4, 10.30µm - 11.30µm, right). &lt;/p&gt;
&lt;p&gt;&lt;img alt="noaa-18 image" src="https://blog.dest-unreach.be/2019/08/25/receiving-noaa/noaa/output.png"&gt;&lt;/p&gt;</content><category term="sdr"/></entry></feed>