<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://jonathanklimt.de/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jonathanklimt.de/" rel="alternate" type="text/html" /><updated>2025-12-19T13:40:16+01:00</updated><id>https://jonathanklimt.de/feed.xml</id><title type="html">Bastelblog</title><subtitle>Personal Blog</subtitle><author><name>Jonathan Klimt</name></author><entry><title type="html">The suprisingly complex technology behind Ravensburger’s King Arthur board game</title><link href="https://jonathanklimt.de/electronics/king_arthur/" rel="alternate" type="text/html" title="The suprisingly complex technology behind Ravensburger’s King Arthur board game" /><published>2025-11-23T01:00:00+01:00</published><updated>2025-11-23T17:28:34+01:00</updated><id>https://jonathanklimt.de/electronics/king_arthur</id><content type="html" xml:base="https://jonathanklimt.de/electronics/king_arthur/"><![CDATA[<p>In the early-mid-2000s, the German company Ravensburger put two games on the market, that combined classical board games with interactive electronic components: <a href="https://boardgamegeek.com/boardgame/6368/king-arthur">King Arthur</a> and <a href="https://boardgamegeek.com/boardgame/15173/die-insel">Die Insel</a> (“The Island”).
I played both of them when I was young and in a sudden nostalgia attack I got a copy of King Arthur pretty cheap off eBay.
The game isn’t considered very deep, but there is one thing about it, that still fascinated the engineer in me after all these years:
How does the “Touch-and-Play” technology behind this game work?</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/king_arthur_box_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/king_arthur_box_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Ravensburger's King-Arthur Board Game (2003)
      
    </figcaption>
  

</figure>

<p>So, in this post, I’m reverse engineering the technology behind these games a bit. But first, let me explain how they work from a player’s perspective:
The board contains a big plastic element in the shape of Merlin’s stone (King Arthur) or (somewhat) of a volcano (Die Insel), housing the electronics and a speaker.
On the board itself you have several location fields for the player figures and a few fields with actions (e.g., interact, fight) located to the side.
In both games, you have four figures in four colors available, one for each of the 1-4 players.
Besides this, the games contain other typical board game components, such as cards and tokens.</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/king_arthur_game_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/king_arthur_game_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/game_interaction_large.jpg">
        <img src="https://jonathanklimt.de/assets/images/king-arthur/game_interaction_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        King-Arthur's board: "Merlin's Stone" housing the electronic in the back; two player figures standing on some location fields. For interaction, the player has to touch the knight's head on a field and an action field on the bottom of the board.
      
    </figcaption>
  

</figure>

<p>To interact with the game, a player puts his figure on the relevant field on the board, and touches the head of the figure simultaneously with one of the action fields.
This triggers some (actually stateful) game logic and some audio output, which is the main feedback channel from the game to the player.</p>

<h2 id="incorrect-theories">Incorrect Theories</h2>

<p>And this is actually the interesting part of it, because in touching the figure and the action, the game needs to determine three parameters:</p>

<ul>
  <li>Which action was taken.</li>
  <li>The location field the figure is on.</li>
  <li>Which figure (color) is acting.</li>
</ul>

<p>Sounds simple at first, but when you think about it, it is actually not that easy.
If it was only two parameters, everything would be simple: Your body is conducting, you close an electrical circuit between the field and the action and the game knows what to do.
And indeed, some kind of electrical circuit is closed, as you can clearly see electrical traces on the board leading towards the fields and actions.
But how does the game get the third parameter?</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/traces_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/traces_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Traces on the game's board
      
    </figcaption>
  

</figure>

<p>Let’s begin with some initial (incorrect) theories I had on how this might have been implemented:</p>
<ul>
  <li>Resistance measurement: The figures could have a specific resistor built-in, and the game measures the resistance between a location field and the action field. The issue here is that the body resistance of a player is pretty high (~8 MΩ between my arms), and it varies a lot (depending on your arm length, skin moisture, …). After measuring the “resistance” of the figures (~2 MΩ) it is safe to say: This isn’t how it is done.</li>
  <li>Capacitive measurement: Placing a capacitor in a figure and measuring the impedance between two fields? This has the same issues as above and was not used either.</li>
</ul>

<p>Ok, why not ask Ravensburger directly?
I sent them my question via their website, and to my outstanding amazement, <strong>I actually got an answer by the engineer that supervised the project back then - super cool! 😎👍</strong>.
Unfortunately, he didn’t really know the details either, as the technology was done by an external company - but still very kind of him to answer!</p>

<p>So, let’s take a look at what’s inside Merlin’s stone!</p>

<h2 id="disassembly-time">Disassembly-Time</h2>

<p>Opening the plastic stone is quite simple, it is just held by 12 Torx screws.
Inside we can see the PCB and a bracket with an <a href="https://en.wikipedia.org/wiki/Elastomeric_connector">elastomeric connector</a> to connect the PCB to the traces on the game.
On the other side of the PCB we can find a switch, two seven segment displays, a touch knob (used to register players to the game), an uninteresting dip component (I think it was a shift register) and a daughter board with a <a href="https://en.wikipedia.org/wiki/Chip_on_board">blobbed</a> IC.
Behind the PCB we can find the speaker as expected.</p>

<figure class="custom">
  <div class="third">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/unbox1_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/unbox1_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/unbox2_large.jpg">
        <img src="https://jonathanklimt.de/assets/images/king-arthur/unbox2_small.jpg" />
      </a>
    
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/unbox3_large.jpg">
        <img src="https://jonathanklimt.de/assets/images/king-arthur/unbox3_small.jpg" />
      </a>
    
  

  </div>

  
    <figcaption>
      
        Disassembling "Merlin's Stone"
      
    </figcaption>
  

</figure>

<p>Ok, this doesn’t give us a direct hint on how things work, but at least we can find the name of the company that created this technology on the PCB: Innovision.
We’ll come back to this later. Maybe the figures themselves give us more insight?</p>

<p>Contrary to opening the “Stone”, the figures are actually not designed to be opened, but are plastic parts glued together.
As I want to keep the game intact, let’s first check what we can find out from the outside.
The figures contain a conductive rubbery pad at the bottom, and the head of the figure is made of some kind of conductive plastic.</p>

<p><em>Sigh</em>, I guess I’ll have to open a figure. Fortunately, I got a second set of figures for ~5€ off eBay, so let’s sacrifice the blue knight - <em>for science</em>!</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/figure_open_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/figure_open_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/knights_head_large.jpg">
        <img src="https://jonathanklimt.de/assets/images/king-arthur/knights_head_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        The inside of a player figure. There is nothing inside the head, just a solderable metal rod screwed inside.
      
    </figcaption>
  

</figure>

<p>We find another small PCB by Innovision inside and the curious thing about it is, that this PCB contains a small antenna.
Taking a second look at the “Merlin’s Stone” PCB, we can see an antenna on there as well.
And that’s actually part of the answer to the question: <strong>Each figure sends the location and action information along with the color code via near-field radio</strong>.
Looking up old versions of <a href="https://web.archive.org/web/20041212024524/http://www.innovision-group.com/">Innovision R&amp;D’s website</a> (they apparently have been acquired or so lately), this makes sense—the company is specialized on RFID and other near-field radio.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/figure_pcb_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/figure_pcb_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Player figure PCB with another blobbed IC and a small antenna.
      
    </figcaption>
  

</figure>

<p>So upon touching the figure and an action field on the board, the player closes the circuit and powers the IC inside the figure, which then sends information to the main unit.
Still, the question remains: How does the game know which action and location the player is on?
Let’s grab the oscilloscope and investigate the fields on the board!</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/measurement_field_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/measurement_field_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/measurements_large.jpg">
        <img src="https://jonathanklimt.de/assets/images/king-arthur/measurements_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Measuring the signal on an action field. There is special conductive black ink on the board.
      
    </figcaption>
  

</figure>

<p>The capture of the oscilloscope reveals:
“Merlin’s Stone” modulates a 20 Hz signal on all fields that encode the field ID.
This signal differs from field to field and the action fields have a significantly different waveform than the location fields.</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/signals1.png">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/signals1.png" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/signals2.png">
        <img src="https://jonathanklimt.de/assets/images/king-arthur/signals2.png" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Signals measured on a location field (blue) and on an action field (yellow). The two images are captures of two different action fields. It can be seen that one bit in the yellow trace differs (red arrow).
      
    </figcaption>
  

</figure>

<p>And this is the final solution to the mystery:</p>
<ul>
  <li>An individual signal is modulated to each field.</li>
  <li>Touching the figure and the action field closes the circuit and the figure’s IC is powered through the player.</li>
  <li>The IC in the figure perceives the composition of the field and action signal and can thus determine the location and action chosen.</li>
  <li>The IC in the figure sends the combination of color, location and action via near-field communication to the main unit, which triggers the game logic.</li>
</ul>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/wave_composition.svg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/wave_composition.svg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        An individual signal is modulated on each field. The composition of the signals gives a unique identification for the field and action. The PCB is powered by the signal as well.
      
    </figcaption>
  

</figure>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/king-arthur/pseudo-schematic.svg">
      <img src="https://jonathanklimt.de/assets/images/king-arthur/pseudo-schematic.svg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        By connecting the Action field and the figure, the circuit is closed, and the figure sends the color, field and action to the game.
      
    </figcaption>
  

</figure>

<h2 id="conclusion">Conclusion</h2>

<p>One could go further from here and decompose the RF communication or decode the signal for the individual fields and actions, but I’m satisfied for now.
Overall, I’m quite impressed how many smart ideas are below the surface of this at first glance simple game.
Now my mind can rest, and I can finally start to actually play the game.</p>]]></content><author><name>Jonathan Klimt</name></author><category term="Electronics" /><category term="BoardGame," /><category term="ReverseEngineering" /><summary type="html"><![CDATA[I'm reverse engineering a 20 year old partly-electrical board game to reveal some suprisingly sophisiticated technology.]]></summary></entry><entry><title type="html">Simple IPv4 to IPv6 proxy</title><link href="https://jonathanklimt.de/programming/socat-proxy/" rel="alternate" type="text/html" title="Simple IPv4 to IPv6 proxy" /><published>2024-12-27T01:00:00+01:00</published><updated>2025-03-16T18:56:48+01:00</updated><id>https://jonathanklimt.de/programming/socat-proxy</id><content type="html" xml:base="https://jonathanklimt.de/programming/socat-proxy/"><![CDATA[<p>Already a few years ago, I wanted to make my NAS reachable from the internet so that I can host a <a href="https://nextcloud.com/">Nextcloud</a> on my very own hardware, so I don’t have to move my personal data to an (untrusted) machine.
There is only one issue: My home network <a href="https://en.wikipedia.org/wiki/Carrier-grade_NAT">is only reachable via IPv6</a>, but there are so many networks outside that are IPv4 only (e.g. 2 out of 3 mobile networks in Germany).
One option would be to pay my ISP to give me an IPv4 address, but this was something like 5€ extra per month, and then I’d still have to fight downtimes due to daily changing IPv4 addresses.</p>

<h2 id="simple-proxy">Simple Proxy</h2>

<p>After a little research, I found a cheaper solution I’d like to share here: Use a cheap VPS as an IPv4 to IPv6 proxy.</p>

<p><em>Disclaimer:</em> I don’t run this exact setup myself anymore, but I’m pretty sure that it still works.</p>

<p>A VPS is a cheap virtual machine in a data center you can easily configure online. The cheapest configuration should be sufficient, as long as there is a static IPv4 included. At least a few years ago, this was often available for around 3€/month.</p>

<p>So this is what the setup will look like:</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/socat_proxy/socat_proxy.svg">
      <img src="https://jonathanklimt.de/assets/images/socat_proxy/socat_proxy.svg" style="height: 7em" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The IPv4 to IPv6 proxy setup
      
    </figcaption>
  

</figure>

<p>The figure already reveals that all software we need to run on the VPS is <a href="https://linux.die.net/man/1/socat"><code class="language-plaintext highlighter-rouge">socat</code></a>.
Socat is a stock-standard command line utility that can be installed on virtually any UNIX like operating system and is incredibly versatile.</p>

<p>To forward any traffic on a certain port to a different host, we can use the following command:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>socat TCP4-LISTEN:80,fork,su<span class="o">=</span>nobody,reuseaddr <span class="s2">"TCP6:[1234:ab12::4321]:80"</span>
</code></pre></div></div>

<p>So let’s decipher this a little bit:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">TCP4-LISTEN:80</code>: <code class="language-plaintext highlighter-rouge">socat</code> should listen to any traffic on port <code class="language-plaintext highlighter-rouge">80</code></li>
  <li><code class="language-plaintext highlighter-rouge">fork</code>: create a new process on every new connection. This is necessary to support multiple connections simultaneously.</li>
  <li><code class="language-plaintext highlighter-rouge">su=nobody</code>: Not 100% sure, but I think this changes the newly created processes to the unprivileged <code class="language-plaintext highlighter-rouge">nobody</code> user, reducing the risk of security issues.</li>
  <li><code class="language-plaintext highlighter-rouge">reuseaddr</code>: With this option, <code class="language-plaintext highlighter-rouge">socat</code> does not bind to port <code class="language-plaintext highlighter-rouge">80</code> exclusively. This is very helpful when restarting the script, as after killing a program using a socket, it takes several seconds, until it is available again.</li>
  <li><code class="language-plaintext highlighter-rouge">"TCP6:[1234:ab12:0:...:4321]:80"</code>: This tells <code class="language-plaintext highlighter-rouge">socat</code> to forward any traffic to the specified IPv6 address (e.g., <code class="language-plaintext highlighter-rouge">1234:ab12::4321</code>) on port <code class="language-plaintext highlighter-rouge">80</code>.</li>
</ul>

<h2 id="a-script-to-rule-it-all">A script to rule it all</h2>

<p>Ok, fairly simple, and for testing purposes it is sufficient to run the above command in a terminal on the VPS, but to automate this, I’ve written the following script:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#! /bin/bash</span>

<span class="nv">UPDATE_INTERVAL</span><span class="o">=</span>15 <span class="c"># check for new IPv6 addresses every 15 seconds</span>
<span class="nv">IP_ADDR_FILE</span><span class="o">=</span>/home/jonathan/ipv6address.txt

<span class="k">function </span>start_socat <span class="o">{</span>
        socat TCP4-LISTEN:80,fork,su<span class="o">=</span>nobody,reuseaddr <span class="s2">"TCP6:[</span><span class="nv">$1</span><span class="s2">]:80"</span> &amp;
        <span class="nv">ID_SOCAT_80</span><span class="o">=</span><span class="nv">$!</span>
        socat TCP4-LISTEN:443,fork,su<span class="o">=</span>nobody,reuseaddr <span class="s2">"TCP6:[</span><span class="nv">$1</span><span class="s2">]:443"</span> &amp;
        <span class="nv">ID_SOCAT_443</span><span class="o">=</span><span class="nv">$!</span>
<span class="o">}</span>

<span class="nv">SERVERADDR</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="nv">$IP_ADDR_FILE</span><span class="si">)</span>
start_socat <span class="nv">$SERVERADDR</span>

<span class="c"># recreate socat processes if necessary</span>
<span class="k">while </span><span class="nb">true</span><span class="p">;</span> <span class="k">do
        </span><span class="nv">SERVERADDR_NEW</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="nv">$IP_ADDR_FILE</span><span class="si">)</span>

        <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$SERVERADDR_NEW</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"</span><span class="nv">$SERVERADDR</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
                </span><span class="nv">SERVERADDR</span><span class="o">=</span><span class="nv">$SERVERADDR_NEW</span>
                <span class="nb">date
                echo</span> <span class="s2">"IP_difference: IP address: </span><span class="nv">$SERVERADDR</span><span class="s2"> new IP address: </span><span class="nv">$SERVERADDR_NEW</span><span class="s2">"</span>
                <span class="nb">kill</span> <span class="nv">$ID_SOCAT_80</span> <span class="nv">$ID_SOCAT_443</span>
                <span class="nb">sleep </span>5
                start_socat <span class="nv">$SERVERADDR</span>
        <span class="k">fi
        </span><span class="nb">sleep</span> <span class="nv">$UPDATE_INTERVAL</span>
<span class="k">done</span>
</code></pre></div></div>

<p>What this script does is, that it reads my NAS’ IPv6 address from a file <code class="language-plaintext highlighter-rouge">ipv6address.txt</code> and starts two <code class="language-plaintext highlighter-rouge">socat</code> processes - one for port 80 (<code class="language-plaintext highlighter-rouge">http</code>) and one for port 443 (<code class="language-plaintext highlighter-rouge">https</code>).
The remainder of the script is simply polling the file with the IPv6 address and recreates the <code class="language-plaintext highlighter-rouge">socat</code> processes with adjusted IPv6 addresses if necessary.
At the same time, a cronjob on my NAS was constantly updating the IPv6 address file via SSH, something like:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">IPv6_ADDRESS</span><span class="o">=</span><span class="si">$(</span>ip <span class="nt">-6</span> a | <span class="nb">grep</span> <span class="s1">'global'</span> | <span class="nb">grep</span> <span class="nt">-v</span> <span class="s1">'deprecated'</span>| <span class="se">\</span>
               <span class="nb">awk</span> <span class="s1">'{print $2}'</span> | <span class="nb">grep</span> <span class="nt">-v</span> <span class="s1">'^fd'</span> | <span class="nb">grep</span> <span class="nt">-v</span> <span class="s1">'^fc'</span> | <span class="se">\</span>
               <span class="nb">sed</span> <span class="s1">'s|/.*||'</span> | <span class="nb">head</span> <span class="nt">-n1</span><span class="si">)</span>
ssh user@vps-server <span class="s1">'echo IPv6_address &gt; /home/jonathan/ipv6address.txt'</span>
</code></pre></div></div>

<h2 id="systemd-service">Systemd Service</h2>

<p>Ok, last thing to do is to create a <em>systemd</em> service, so that the script above is started automatically and output is logged nicely with <em>journald</em>. Create <code class="language-plaintext highlighter-rouge">/etc/systemd/socat_forward.service</code> with the following content:</p>

<div class="language-systemd highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[Unit]</span>
<span class="nt">Description</span><span class="p">=</span>Socat IPv4 to IPv6 forward

<span class="k">[Service]</span>
<span class="nt">ExecStart</span><span class="p">=</span>/home/jonathan/socatscript.sh
<span class="nt">Restart</span><span class="p">=</span>on-failure

<span class="k">[Install]</span>
<span class="nt">WantedBy</span><span class="p">=</span>multi-user.target
</code></pre></div></div>
<p>Execute <code class="language-plaintext highlighter-rouge">systemctl enable socat_forward.service &amp;&amp; systemctl start socat_forward.service</code>, and the VPS forwards all port 80 &amp; 443 traffic to the NAS.</p>

<p>I’d be happy to hear if you are using this technique or something similar! 🙂👍</p>]]></content><author><name>Jonathan Klimt</name></author><category term="Programming" /><category term="Server," /><category term="Network" /><summary type="html"><![CDATA[A simple script that can be used to reach IPv6 only hosts via IPv4]]></summary></entry><entry><title type="html">Heat Inserts on PCBs</title><link href="https://jonathanklimt.de/electronics/heat-inserts/" rel="alternate" type="text/html" title="Heat Inserts on PCBs" /><published>2024-12-21T01:00:00+01:00</published><updated>2024-12-27T17:59:52+01:00</updated><id>https://jonathanklimt.de/electronics/heat-inserts</id><content type="html" xml:base="https://jonathanklimt.de/electronics/heat-inserts/"><![CDATA[<p><a href="https://git.rwth-aachen.de/acs/public/teaching/legos/">The LEGOS project at the institute</a> contains a number of PCBs that need to be mounted to some 3D-printed parts, and the PCB always forms the bottom of the assembly.</p>

<p>Sure, this is an easy task to solve with mounting holes, but then you’d have the screw head or a nut sticking out the bottom, ruining the slick design and maybe providing a tempting piece for users to fiddle with.
Also, one of the entities in this project contains 8 M4 threaded rods that need to be mounted on top of the PCB.</p>

<p>For this purpose, we have so far used <a href="https://www.mouser.de/c/?marcom=115916286">Würth SMD screw terminals</a>.
These are basically some round pieces of metal with a screw terminal, that can be soldered onto a PCB and provide a way to screw something to the PCB.
I believe, they are actually intended as an attachment point for some high amperage wires with ring terminals, but they serve our purpose as well.
So far so good, but there is a drawback with this approach: <strong>The terminals are fucking expensive!</strong> Seriously, they <a href="https://www.mouser.de/ProductDetail/Wurth-Elektronik/7466203?qs=IfoubJuuTRb774UUJ30heA%3D%3D">cost 2.50 € <strong>per piece</strong></a>, so 18 € total for a PCB which otherwise contains fairly cheap components.</p>

<!-- // TODO: LEGOS picture -->

<!-- <figure class="custom"
>
  <div 
    class="single"
  
  >

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/heat-inserts/Substation.svg">
      <img  src="https://jonathanklimt.de/assets/images/heat-inserts/Substation.svg"
        
      >
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The "Substation" entity from the LEGOS project. The columns in the center are actually threaded M4 rods screwed to the PCB.
      
    </figcaption>
  

</figure>
 -->

<h2 id="heat-inserts-to-the-rescue">Heat inserts to the rescue.</h2>

<p>I don’t exactly recall the genesis, but a friend of mine and I had an idea on how to reduce these costs down to a few cents: <strong>Simply use <a href="https://markforged.com/resources/blog/heat-set-inserts">brass heat-inserts</a> instead.</strong>
Heat-inserts not only occupy one of the top-places on my <em>whish-I’d-known-this-earlier</em> list when used as intended, but because they’re made of brass they can be soldered without any issues.
Oh, and they cost basically nothing - you can get them for ~0.05€ per piece on AliExpress.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/heat-inserts/heat-insert_small.jpg">
      <img src="https://jonathanklimt.de/assets/images/heat-inserts/heat-insert_small.jpg" style="max-height: 12em" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Heat inserts are super cheap and can be soldered easily
      
    </figcaption>
  

</figure>

<h2 id="evaluation">Evaluation</h2>

<p>I had some scrap PCBs lying around, and coincidentally also one with a plated hole that was the perfect footprint for a M4 insert.
So as you can see, we have tried that part, a piece of prototype board, and a normal PCB with a heat sink pad for a microcontroller.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/heat-inserts/test-setup_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/heat-inserts/test-setup_small.jpg" style="height: 16em" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The test pieces
      
    </figcaption>
  

</figure>

<p>So just add some solder paste, put it on a heat plate and -voilà- that worked pretty well.
But does it hold?</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/heat-inserts/heat-insert1_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/heat-inserts/heat-insert1_small.jpg" style="height: 15em" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/heat-inserts/heat-insert2_large.jpg">
        <img src="https://jonathanklimt.de/assets/images/heat-inserts/heat-insert2_small.jpg" style="height: 15em" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        The soldered heat-inserts hold surprisingly well.
      
    </figcaption>
  

</figure>

<p>So, that works surprisingly well. A little thrilled, we tried to push our luck even further:</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/heat-inserts/heat-insert3_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/heat-inserts/heat-insert3_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The soldered heat-inserts carrying a water bottle of about 700 grams.
      
    </figcaption>
  

</figure>

<p>We were completely blown away on how well this worked. And this was just a plain copper pad on the PCB. The insert that was sunk in the hole worked equally well (I forgot to take a photo).
With enough force, we eventually broke off the insert, but this is definitely strong enough any normal applications.
BTW, the prototype board did delaminate before the insert snapped off:</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/heat-inserts/delamination_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/heat-inserts/delamination_small.jpg" style="height: 12em" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The prototype board delaminated before the heat insert snapped off
      
    </figcaption>
  

</figure>

<h2 id="summary">Summary</h2>

<p>So <strong>TLDR</strong>: Heat-inserts are a cheap and solid way of attaching screws to a PCB.
I’d be curious to hear if you can use this technique in a project, feel free to drop me a mail if you do! 😀</p>]]></content><author><name>Jonathan Klimt</name></author><category term="Electronics" /><category term="PCB" /><summary type="html"><![CDATA[Handy trick Use heat inserts for PCB mounting]]></summary></entry><entry><title type="html">Backup Monitoring with Home Assistant</title><link href="https://jonathanklimt.de/backup-hass/" rel="alternate" type="text/html" title="Backup Monitoring with Home Assistant" /><published>2024-11-30T01:00:00+01:00</published><updated>2024-12-21T21:44:22+01:00</updated><id>https://jonathanklimt.de/backup-hass</id><content type="html" xml:base="https://jonathanklimt.de/backup-hass/"><![CDATA[<p>In a <a href="/backup_probe/">previous post</a>, I described how I built a backup device for offsite backups. That device is actually now running for 3 years. I’d have expected the old ODROID board to make some trouble, since it is now almost 9 years old, but it is running without any issues. What is causing issues is the HDD - I had to replace it twice already.
Replacing the HDD is not much of an issue, in fact, I got a warranty replacement for one of the disks. However, you have to actually notice the failure, which is not that obvious for scheduled restic run to a remote probe.</p>

<p>In could set up a Grafana/Telegraf stack for this and install some alerts, but this is a lot of work and I actually already have a monitoring system running, that can display nice time-series data and notify me conditionally: Home Assistant! So let’s integrate the probe there.</p>

<h2 id="adding-a-new-mqtt-sensor-in-home-assistant">Adding a new MQTT Sensor in Home Assistant:</h2>

<p>Probably the most straightforward communication interface to Home Assistant (HA) is most likely MQTT, and I’m already using it for some ESP-Home devices. In addition, it is pretty easy to send MQTT messages via the command line, using <code class="language-plaintext highlighter-rouge">mosquitto_pub</code>.</p>

<p>Ok, so what exactly do we need to send?
Home Assistant has a <a href="https://www.home-assistant.io/integrations/mqtt/#discovery-messages">device auto discovery mechanism for MQTT</a>, meaning that whenever a certain message is posted to a certain topic, it will automatically create a device from that message’s body.
This topic follows the schema <code class="language-plaintext highlighter-rouge">&lt;discovery_prefix&gt;/&lt;component&gt;/[&lt;node_id&gt;/]&lt;object_id&gt;/config</code>.
<code class="language-plaintext highlighter-rouge">discovery_prefix</code> defaults to <code class="language-plaintext highlighter-rouge">homeassistant</code> and a list of possible <code class="language-plaintext highlighter-rouge">component</code> values hides in the <a href="https://www.home-assistant.io/integrations/homeassistant/#device-class">device class documentation</a>. <code class="language-plaintext highlighter-rouge">node_id</code> can be ignored and as we use <code class="language-plaintext highlighter-rouge">restic_backup</code> as <code class="language-plaintext highlighter-rouge">object_id</code> we get <code class="language-plaintext highlighter-rouge">homeassistant/sensor/restic_backup/config</code> as the discovery topic.</p>

<p>The payload of this message defines the device and the device we want should simply report whether a backup is currently running or not.
Any additional logic such as <em>last run</em> can then be implemented in Home Assistant.
Honestly, I found it pretty hard to understand how this payload has to look like, and it took me quite some trial-and-error, but this is how a working autodetect payload for this scenario looks like:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Restic Backup"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"device_class"</span><span class="p">:</span><span class="w"> </span><span class="s2">"enum"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"state_topic"</span><span class="p">:</span><span class="w"> </span><span class="s2">"homeassistant/sensor/restic_backup/state"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"unique_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"restic_backup_mqtt1"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"device"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Restic Backup"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"identifiers"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"restic_backup"</span><span class="w"> </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">name</code> is obvious, and as we just report a status, <code class="language-plaintext highlighter-rouge">device_class</code> should be <code class="language-plaintext highlighter-rouge">enum</code>.</li>
  <li>The <code class="language-plaintext highlighter-rouge">state_topic</code> tells Home Assistant, where the device will report the status, and it can be rather arbitrary.</li>
  <li>If you omit the <code class="language-plaintext highlighter-rouge">unique_id</code>, you can find the sensor in the <em>Entities</em> section, but the readings will not be listed in the according <em>Device</em>.</li>
  <li>The <code class="language-plaintext highlighter-rouge">device</code> part was quite confusing to me, as this is kind of inverted logic: You don’t specify this very sensor here, but the device that this sensor gets assigned to. If you have multiple sensors with the same <code class="language-plaintext highlighter-rouge">device</code> section, they are “grouped” together.</li>
</ul>

<p>According to the documentation, there are other ways to write the payload, but, honestly, I had quite some trouble understanding the details.
Anyway, the above configuration is good enough here.</p>

<p>Let’s send this to the broker and check if we have a new device now:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> mosquitto_pub <span class="nt">-h</span> 192.168.0.25 <span class="nt">-p</span> 1883 <span class="nt">-r</span> <span class="se">\</span>
    <span class="nt">-t</span> <span class="s2">"homeassistant/sensor/restic_backup/config"</span> <span class="se">\</span>
    <span class="nt">-m</span> <span class="s1">'{ "name": "Restic Backup", "device_class": "enum",
        "state_topic": "homeassistant/sensor/restic_backup2/state",
        "unique_id": "restic_backup_mqtt1",
        "device": { "name": "Restic Backup", "identifiers": [ "restic_backup" ] } }'</span>
</code></pre></div></div>

<p>Note, that we use <code class="language-plaintext highlighter-rouge">-r</code> here so that the device is persistent in Home Assistant (at least until the Broker has a restart).
Anyway, we can now also send a message to <code class="language-plaintext highlighter-rouge">state_topic</code> and observe the status changing in the web UI:</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/ha_restic_no_status.png">
      <img src="https://jonathanklimt.de/assets/images/backup/ha_restic_no_status.png" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/ha_restic_status.png">
        <img src="https://jonathanklimt.de/assets/images/backup/ha_restic_status.png" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        The new <i>device</i> in Home Assistant. Left is before and right is after we posted to <code>state_topic</code>
      
    </figcaption>
  

</figure>

<div class="notice--info">
  
<p><strong>Note:</strong> To remove the device from Home Assistant, simply send an empty message to the discovery topic:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> mosquitto_pub <span class="nt">-h</span> 192.168.0.25 <span class="nt">-p</span> 1883 <span class="nt">-r</span> <span class="se">\</span>
    <span class="nt">-t</span> <span class="s2">"homeassistant/sensor/restic_backup/config"</span> <span class="nt">-m</span> <span class="s1">''</span>
</code></pre></div></div>

</div>

<h2 id="integration-with-restic">Integration with restic</h2>

<p>Ok, now we have a proof-of-concept. The next step is to combine this with the backups.
Fortunately, the <a href="https://github.com/lobaro/restic-backup-docker">restic container</a> I’m using <a href="https://github.com/lobaro/restic-backup-docker?tab=readme-ov-file#hooks">supports hooks</a> for executing a script before and after the backup.
But as the container doesn’t have <code class="language-plaintext highlighter-rouge">mosquitto</code> installed by default, we need to add this to the container first.
<a href="/backup_probe/#restic-setup">As described in the previous post</a>, we are using a custom Dockerfile anyway, so we just have to add one line to the file:</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> lobaro/restic-backup-docker:latest</span>
<span class="k">RUN </span><span class="nb">mkdir</span> <span class="nt">-p</span> /root/.ssh <span class="o">&amp;&amp;</span> <span class="nb">ln</span> <span class="nt">-s</span> /run/secrets/user_ssh_key /root/.ssh/id_rsa
<span class="k">RUN </span><span class="nb">chown</span> <span class="nt">-R</span> root:root /root/.ssh
<span class="k">RUN </span><span class="nb">printf</span> <span class="s2">"Host 10.13.13.5</span><span class="se">\n\t</span><span class="s2">StrictHostKeyChecking no</span><span class="se">\n</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> /root/.ssh/config
<span class="c"># Install mosquitto</span>
<span class="k">RUN </span>apk add mosquitto-clients
</code></pre></div></div>

<p>Ok, now we can finally create our hooks. We create a folder <code class="language-plaintext highlighter-rouge">hooks</code> and in this the two scripts <code class="language-plaintext highlighter-rouge">pre_backup.sh</code> and <code class="language-plaintext highlighter-rouge">post_backup.sh</code>:
They are called before/after the backup and the logic we want is quite simple:</p>
<ul>
  <li>Publish the auto-discovery message. (It doesn’t matter if it was already published, as we don’t alter it)</li>
  <li>The <code class="language-plaintext highlighter-rouge">pre_backup.sh</code> script publishes <em>Running</em> as status.</li>
  <li>The <code class="language-plaintext highlighter-rouge">post_backup.sh</code> script checks the exit code of restic to determine whether the backup was successful or not, and publishes <em>Idle</em> or <em>Error</em> as status.</li>
</ul>

<p>So this is the resulting <code class="language-plaintext highlighter-rouge">pre_backup.sh</code>:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>

mosquitto_pub <span class="nt">-h</span> <span class="nv">$MQTT_SERVER</span> <span class="nt">-p</span> <span class="nv">$MQTT_PORT</span> <span class="nt">-r</span> <span class="se">\</span>
        <span class="nt">-t</span> <span class="s2">"homeassistant/sensor/</span><span class="nv">$NAME</span><span class="s2">/config"</span> <span class="se">\</span>
        <span class="nt">-m</span> <span class="s2">"{</span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">name</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="nv">$NICE_NAME</span><span class="se">\"</span><span class="s2">, </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">device_class</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">enum</span><span class="se">\"</span><span class="s2">, </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">unique_id</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="nv">$NAME</span><span class="s2">-mqtt1</span><span class="se">\"</span><span class="s2">, </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">state_topic</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">homeassistant/sensor/</span><span class="nv">$NAME</span><span class="s2">/state</span><span class="se">\"</span><span class="s2">, </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">device</span><span class="se">\"</span><span class="s2">: { </span><span class="se">\"</span><span class="s2">name</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="nv">$NICE_NAME</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">identifiers</span><span class="se">\"</span><span class="s2">: [ </span><span class="se">\"</span><span class="nv">$NAME</span><span class="se">\"</span><span class="s2"> ] } </span><span class="se">\</span><span class="s2">
        }"</span>

mosquitto_pub <span class="nt">-h</span> <span class="nv">$MQTT_SERVER</span> <span class="nt">-p</span> <span class="nv">$MQTT_PORT</span> <span class="se">\</span>
        <span class="nt">-t</span> <span class="s2">"homeassistant/sensor/</span><span class="nv">$NAME</span><span class="s2">/state"</span> <span class="se">\</span>
        <span class="nt">-m</span> <span class="s1">'Running'</span>
</code></pre></div></div>

<p>And <code class="language-plaintext highlighter-rouge">post_backup.sh</code> looks as follows:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>

mosquitto_pub <span class="nt">-h</span> <span class="nv">$MQTT_SERVER</span> <span class="nt">-p</span> <span class="nv">$MQTT_PORT</span> <span class="nt">-r</span> <span class="se">\</span>
        <span class="nt">-t</span> <span class="s2">"homeassistant/sensor/</span><span class="nv">$NAME</span><span class="s2">/config"</span> <span class="se">\</span>
        <span class="nt">-m</span> <span class="s2">"{ </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">name</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="nv">$NICE_NAME</span><span class="se">\"</span><span class="s2">, </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">device_class</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">enum</span><span class="se">\"</span><span class="s2">, </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">unique_id</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="nv">$NAME</span><span class="s2">-mqtt1</span><span class="se">\"</span><span class="s2">, </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">state_topic</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">homeassistant/sensor/</span><span class="nv">$NAME</span><span class="s2">/state</span><span class="se">\"</span><span class="s2">, </span><span class="se">\</span><span class="s2">
            </span><span class="se">\"</span><span class="s2">device</span><span class="se">\"</span><span class="s2">: { </span><span class="se">\"</span><span class="s2">name</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="nv">$NICE_NAME</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">identifiers</span><span class="se">\"</span><span class="s2">: [ </span><span class="se">\"</span><span class="nv">$NAME</span><span class="se">\"</span><span class="s2"> ] } </span><span class="se">\</span><span class="s2">
        }"</span>

<span class="c"># Check restics return code</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-</span><span class="nv">0</span><span class="k">}</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"0"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span>mosquitto_pub <span class="nt">-h</span> <span class="nv">$MQTT_SERVER</span> <span class="nt">-p</span> <span class="nv">$MQTT_PORT</span> <span class="se">\</span>
            <span class="nt">-t</span> <span class="s2">"homeassistant/sensor/</span><span class="nv">$NAME</span><span class="s2">/state"</span> <span class="se">\</span>
            <span class="nt">-m</span> <span class="s1">'Idle'</span>
<span class="k">else
    </span>mosquitto_pub <span class="nt">-h</span> <span class="nv">$MQTT_SERVER</span> <span class="nt">-p</span> <span class="nv">$MQTT_PORT</span> <span class="se">\</span>
            <span class="nt">-t</span> <span class="s2">"homeassistant/sensor/</span><span class="nv">$NAME</span><span class="s2">/state"</span> <span class="se">\</span>
            <span class="nt">-m</span> <span class="s1">'Error'</span>
<span class="k">fi</span>
</code></pre></div></div>

<p>And we shouldn’t forget to make the scripts executable:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">chmod</span> +x hooks/pre-backup.sh hooks/post-backup.sh
</code></pre></div></div>

<p>If you wonder where we have defined <code class="language-plaintext highlighter-rouge">NAME</code>, <code class="language-plaintext highlighter-rouge">MQTT_SERVER</code>, etc.: Congratulations, you’ve been paying attention.
We set them in our <code class="language-plaintext highlighter-rouge">docker-compose.yml</code>!</p>

<p>The full compose file can be found in the <a href="/backup_probe/#final-compose-file">first post on this setup</a>, so here just the new bits:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">wireguard</span><span class="pi">:</span>
    <span class="c1"># ...</span>

  <span class="na">restic-backup</span><span class="pi">:</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">restic_nas</span>
    <span class="na">build</span><span class="pi">:</span>
      <span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
      <span class="na">dockerfile</span><span class="pi">:</span> <span class="s">Dockerfile</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">RESTIC_REPOSITORY=sftp:jonathan@10.13.13.2:/data/backup/server</span>
        <span class="s"># ...</span>
      <span class="pi">-</span> <span class="s">RESTIC_FORGET_ARGS=--keep-last 2 --keep-monthly </span><span class="m">3</span>
        <span class="c1"># The environment variables for the MQTT hook</span>
      <span class="pi">-</span> <span class="s">MQTT_SERVER=192.168.0.25</span>
      <span class="pi">-</span> <span class="s">MQTT_PORT=1883</span>
      <span class="pi">-</span> <span class="s">NAME=restic_nas</span>
      <span class="pi">-</span> <span class="s">NICE_NAME=Restic NAS</span>
    <span class="na">volumes</span><span class="pi">:</span>
        <span class="c1"># Mount the hook-scripts</span>
      <span class="pi">-</span> <span class="s">./hooks:/hooks:ro</span>
      <span class="pi">-</span> <span class="s">/zstorage/git:/data/git:ro</span>
        <span class="s"># ...</span>
    <span class="c1"># ...</span>
</code></pre></div></div>
<p>We build the compose file and manually trigger a backup to check if it is working:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> docker-compose up <span class="nt">--force-recreate</span> <span class="nt">--remove-orphans</span> <span class="nt">-d</span> <span class="nt">--build</span>
<span class="o">&gt;</span> docker <span class="nb">exec</span> <span class="nt">-ti</span> restic-backup /bin/sh
/ <span class="o">&gt;</span> /bin/backup
Starting pre-backup
<span class="c">#...</span>
</code></pre></div></div>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/ha_backup_full.png">
      <img src="https://jonathanklimt.de/assets/images/backup/ha_backup_full.png" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Successful backup in Home Assistant
      
    </figcaption>
  

</figure>

<h2 id="automations-in-home-assistant">Automations in Home Assistant</h2>

<p>Ok, almost done. All that’s left is creating an automation in HA. I’m just running a simple one, and there is room for improvement, but at least I get a warning if there was no backup or the backup threw an error:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">alias</span><span class="pi">:</span> <span class="s">Restic Backup Warning</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
<span class="na">mode</span><span class="pi">:</span> <span class="s">single</span>
<span class="na">triggers</span><span class="pi">:</span>
  <span class="c1"># Backup sensor is in "Error" state</span>
  <span class="pi">-</span> <span class="na">entity_id</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">sensor.restic_backup</span>
    <span class="na">to</span><span class="pi">:</span> <span class="s">Error</span>
    <span class="na">id</span><span class="pi">:</span> <span class="s">Backup Error</span>
    <span class="na">trigger</span><span class="pi">:</span> <span class="s">state</span>
  <span class="c1"># No backup has started in more than a day</span>
  <span class="c1"># (e.g., probe is offline, MQTT shenanigans)</span>
  <span class="pi">-</span> <span class="na">entity_id</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">sensor.restic_backup</span>
    <span class="na">to</span><span class="pi">:</span> <span class="s">Idle</span>
    <span class="na">for</span><span class="pi">:</span>
      <span class="na">hours</span><span class="pi">:</span> <span class="m">25</span>
      <span class="na">minutes</span><span class="pi">:</span> <span class="m">0</span>
      <span class="na">seconds</span><span class="pi">:</span> <span class="m">0</span>
    <span class="na">id</span><span class="pi">:</span> <span class="s">No backup</span>
    <span class="na">trigger</span><span class="pi">:</span> <span class="s">state</span>
<span class="na">conditions</span><span class="pi">:</span> <span class="pi">[]</span>
<span class="na">actions</span><span class="pi">:</span>
  <span class="c1"># Send a Signal message.</span>
  <span class="pi">-</span> <span class="na">data</span><span class="pi">:</span>
      <span class="na">message</span><span class="pi">:</span> <span class="s">There is an issue with the backups! ()</span>
    <span class="na">action</span><span class="pi">:</span> <span class="s">notify.signal</span>
</code></pre></div></div>

<h2 id="additional-sensors">Additional sensors</h2>

<p>But wait - there’s more!
As we can input basically any measurements in Home Assistant, what about the status of the probe itself?
Especially the disk usage would be very interesting for the backup probe.
So I was about to tinker some shell scripts that read CPU temperature, but then I found the <a href="https://github.com/Sennevds/system_sensors"><code class="language-plaintext highlighter-rouge">system_sensors</code></a> project, which had it already done, just better than what I’d have tinkered.</p>

<p>Ok, then let’s set it up:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> git clone https://github.com/Sennevds/system_sensors.git
<span class="o">&gt;</span> <span class="nb">sudo </span>apt-get <span class="nb">install </span>python3-dev python3-apt
<span class="o">&gt;</span> <span class="nb">cd </span>system_sensors <span class="o">&amp;&amp;</span> pip3 <span class="nb">install</span> <span class="nt">-r</span> requirements.txt
<span class="o">&gt;</span> <span class="nb">cp </span>src/settings_example.yaml src/settings.yaml
</code></pre></div></div>

<p>Now we adapt the MQTT settings, <code class="language-plaintext highlighter-rouge">deviceName</code> and other settings to our liking, and try it out with:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 src/system_sensors.py src/settings.yaml
</code></pre></div></div>

<p>We should now see a new device with plenty of sensors in Home Assistant:</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/ha_system_sensors.png">
      <img src="https://jonathanklimt.de/assets/images/backup/ha_system_sensors.png" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Sensor readings from the backup probe in Home Assistant
      
    </figcaption>
  

</figure>

<p>I leave it as an exercise to the reader to build some fancy automations with this data and end this post with a screenshot of my dashboard (yes, my NAS is also sending the status via MQTT).</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/ha_dashboard.png">
      <img src="https://jonathanklimt.de/assets/images/backup/ha_dashboard.png" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Home Assistant dashboard with backup status and the readings of the backup probe as well as the NAS
      
    </figcaption>
  

</figure>]]></content><author><name>Jonathan Klimt</name></author><category term="Odroid-XU4" /><category term="Backup" /><category term="Linux" /><summary type="html"><![CDATA[Integrating my backup probe into my monitoring platform of choice.]]></summary></entry><entry><title type="html">Simple remote backup probe</title><link href="https://jonathanklimt.de/backup_probe/" rel="alternate" type="text/html" title="Simple remote backup probe" /><published>2022-02-28T01:00:00+01:00</published><updated>2024-12-01T18:16:52+01:00</updated><id>https://jonathanklimt.de/backup_probe</id><content type="html" xml:base="https://jonathanklimt.de/backup_probe/"><![CDATA[<p>Backups.</p>

<p>Nobody likes them, few people do them, and you really only start to appreciate them once you need them but don’t have them.
I’ve set up a kind of dual-stage backup system for my computers and my home-server which is working fine so far.
There surely is room for improvement and more redundancy but nevertheless, I thought it is might be worth sharing.</p>

<h2 id="general-idea">General Idea</h2>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/overview.svg">
      <img src="https://jonathanklimt.de/assets/images/backup/overview.svg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Overview of the backup setup
      
    </figcaption>
  

</figure>

<p>I already run daily backups to my home-server via <a href="https://github.com/bit-team/backintime">Back in Time</a>. Back in Time is using <a href="https://en.wikipedia.org/wiki/Rsync">rsync</a> under the hood, provides a quite usable GUI and has the cool features, that the backups are real files and folders on the remote. But the fancy thing is, that you have separate folders for each backup, but they don’t multiply the memory need by using <a href="https://en.wikipedia.org/wiki/Hard_link">Hard-links</a> internally.
What does that mean? Let’s imagine you have a file <code class="language-plaintext highlighter-rouge">foo.txt</code> which hasn’t changed between the backups. Back In Time would then create a folder for each backup and places the file inside. You’ll end up with something like <code class="language-plaintext highlighter-rouge">2022-01-20/foo.txt</code> and <code class="language-plaintext highlighter-rouge">2022-01-21/foo.txt</code>.
The clever thing is, both file tree entries actually point to the same bytes on your hard drive and only once consume the memory.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>backups
├── 2022-01-20
│   ├── foo1.txt   &lt;------+
│   └── foo2.txt          |
└── 2022-01-21            |
    ├── foo1.txt   &lt;------+----- no changes - same file on disk
    └── foo2.txt   &lt;---- had changes - new file on disk
</code></pre></div></div>

<p>I like doing it this way in my LAN, because file restoration is really simple: You just mount the remote file system, navigate to the file and copy-paste it back where you want it. The drawback is missing encryption (even though Back In Time supports this) and no compression.</p>

<p>But this setup was missing an offsite component so far to protect the data from any kind of “misfortune” like fire, lightning or a wild horde of rhinos (better be prepared for this!). Many people seem to use cloud storage for such an application, but I didn’t like the idea of giving my most sensible data into some company’s hand (even though encrypted).
For this, I decided to use a different tool: <a href="https://restic.net/">Restic</a>.
It is a very advanced command line based backup tool which support numerous storage back-ends. One of the common applications is to back up your data to cloud storage.
In comparison to the first tool, restic has its own data storage format, and you also need restic to restore the backups again.
(Of course, both are open-source tools).</p>

<h2 id="the-probe">The Probe</h2>

<p>What I wanted was a device which I can give to a friend and tell him: “Can you please plug this to your router? Thanks!” and everything run’s automatically.
I had an Odroid-XU4 lying around for some time collecting dust, and it seems that this project is a good opportunity to put it to real use.
With it’s USB3 port it was a better choice for a storage application than e.g., a Raspberry &lt; 4.</p>

<h3 id="hardware">Hardware</h3>

<p>Besides the Odroid, I of course needed a disk, and the largest 2.5” one I could find back then was the “Seagate Barracuda ST5000LM000” with 5 TB. I also picked the shortest SATA-USB3 adapter cable I could find for a reasonable price.
(<strong>Update:</strong> The Seagate died after ~2 Years, now I’m trying a WD MyPassport, which does not require an addapter.)</p>

<p>It took me a few attempts to design the enclosure. I first tried something more compact, but then I’d end up with the HDD directly below the CPU, which was suboptimal. Also, the USB-SATA cable caused a lot of headache. But the final design is ok and I only need this once, so…</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/cad1.png">
      <img src="https://jonathanklimt.de/assets/images/backup/cad1.png" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/cad2.png">
        <img src="https://jonathanklimt.de/assets/images/backup/cad2.png" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        CAD drawing of the enclosure with the Odroid (gray) and the HDD (blue/violet)
      
    </figcaption>
  

</figure>

<p>The HDD is located in a kind of pan, which I filled with silicone I had lying around. The small bracket you can see above the HDD keep it secured in the custom bed.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/probe1_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/backup/probe1_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The printed probe
      
    </figcaption>
  

</figure>

<p>My original idea was to use some push rod mechanism for the Odroid’s button, but this didn’t work out (and I accidentally destroyed the button on the PCB), so instead I soldered the red button with some wires to the board, which worked fine.</p>

<h3 id="software">Software</h3>

<p class="notice--danger"><strong>Disclaimer:</strong> It’s been a while since I set up the whole thing, so I couldn’t check all these commands upon writing again</p>

<p>So besides WireGuard, the probe isn’t really running any special software. The starting point is an <a href="https://www.armbian.com/odroid-xu4/#kernels-archive-all">Armbian-Focal</a> (Ubuntu based) installation on that machine with ssh access and pubkey authentication. I assume that you are familiar with setting up such a system.</p>

<h4 id="external-disk">External Disk:</h4>

<p>The external disk is configured with <a href="https://en.wikipedia.org/wiki/Btrfs">Btrfs</a>. Of course, you can use different file systems, but I felt like this is the way to go.</p>

<p>To create the Btrfs partition on the disk (obviously, replace the aaaaa-bbb-… id with the one for your drive):</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># create the mountpoint</span>
<span class="o">&gt;</span> <span class="nb">mkdir</span> /data
<span class="c"># create the filesystem on the external disk</span>
<span class="o">&gt;</span> mkfs.btrfs /dev/disk/by-uuid/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
</code></pre></div></div>

<p>To automatically mount the disk, the disk has to be added to to <code class="language-plaintext highlighter-rouge">/etc/fstab</code> as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UUID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee /data btrfs defaults,compress,autodefrag,inode_cache  0   1
</code></pre></div></div>

<p>Now might be a good time for a reboot.</p>

<p>You can verify that everything worked with the following command:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> mount <span class="nt">-l</span> | <span class="nb">grep</span> /data
<span class="c"># should be like:</span>
/dev/sda1 on /data <span class="nb">type </span>btrfs <span class="o">(</span>rw,relatime,compress<span class="o">=</span>zlib:3,...<span class="o">)</span>
</code></pre></div></div>

<h4 id="unattended-upgrades">Unattended upgrades</h4>

<p>As the probe won’t be maintained actively, it might be a good idea to enable <em>unattended-upgrades</em>. 
Unattended upgrades is already installed, we just have to enable it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo </span>dpkg-reconfigure <span class="nt">--priority</span><span class="o">=</span>low unattended-upgrades
<span class="c"># ...</span>
<span class="c"># Check if it worked:</span>
<span class="o">&gt;</span> apt-config dump APT::Periodic::Unattended-Upgrade
APT::Periodic::Unattended-Upgrade <span class="s2">"1"</span><span class="p">;</span>
</code></pre></div></div>
<p class="notice--warning"><strong>Warning:</strong> beware that a power-outage during such an upgrade might render your installation useless. However, the backups *shouldn’t* be affected by this.</p>

<h4 id="power-tweaking-of-the-odroid-xu4">Power tweaking of the Odroid-XU4</h4>

<p>The idle power use of a device running 24/7 is actually relevant. I measured the Odroid and it draws ~3.5W. A single watt you can save sums up to ~9 kWh over a year. It’s not that much but 2.5€ is at least something, especially if you give it away and let your friends pay the bill.</p>

<p>You can play around with the <code class="language-plaintext highlighter-rouge">/sys</code> filesystem and tweak the frequencies and governors of the system:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Limit the frequency of the first 4 cores to 1GHz</span>
<span class="o">&gt;</span> <span class="nb">echo </span>1000000 <span class="o">&gt;</span> /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
<span class="c"># Disable CPUs 4-7</span>
<span class="o">&gt;</span> <span class="nb">echo </span>0 <span class="o">&gt;</span> /sys/devices/system/cpu/cpu4/online
</code></pre></div></div>

<p>I found, that the minimum frequency is set to 600 MHz, which can be reduced to 200 MHz with <code class="language-plaintext highlighter-rouge">scaling_min_freq</code> (check <code class="language-plaintext highlighter-rouge">scaling_available_frequencies</code>).</p>

<p>The changes made with the commands above are not persistent.
Multiple ways exist to make them persistent and I used the <code class="language-plaintext highlighter-rouge">sysfsutils</code> package and modified <code class="language-plaintext highlighter-rouge">/etc/sysfs.conf</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>devices/system/cpu/cpu0/cpufreq/scaling_governor = ondemand
devices/system/cpu/cpu0/cpufreq/scaling_min_freq = 200000
devices/system/cpu/cpu4/cpufreq/scaling_governor = ondemand
devices/system/cpu/cpu4/cpufreq/scaling_min_freq = 200000
</code></pre></div></div>

<p>You can also check the <code class="language-plaintext highlighter-rouge">/boot/boot.ini</code> to do more tweaks like clocking down the RAM.</p>

<h2 id="wireguard-server-setup">WireGuard Server Setup</h2>

<p>Ok, the Odroid is running for now and now we have to go back to the home-server and set up the WireGuard server.
Not that I’m a Docker evangelist, but I found it actually pretty handy to use containers for the services on the home-server.
So I also utilized it for the VPN and the backup routine, even though you surely can set it up without docker as well.</p>

<p><strong>Please note:</strong> It is important that this machine is reachable from outside your network. Otherwise you’d need another server which is and acts as a VPN master.</p>

<p>So I’m using the <a href="https://github.com/linuxserver/docker-wireguard">linuxserver.io Dockerfile</a> for this.
Basically I’m mostly copying their <a href="https://github.com/linuxserver/docker-wireguard#docker-compose-recommended-click-here-for-more-info"><code class="language-plaintext highlighter-rouge">docker_compose.yaml</code></a>.
I only adapted the <code class="language-plaintext highlighter-rouge">SERVERURL</code> to the one my home-server is using and of course made sure that the selected <code class="language-plaintext highlighter-rouge">SERVERPORT</code> port is reachable.</p>

<p>Running <code class="language-plaintext highlighter-rouge">docker-compose up -d</code> should be sufficient to start the server now!</p>

<p>If you take a look at the folder selected for the WireGuard config, you’ll see a number of folders <code class="language-plaintext highlighter-rouge">peer1</code>, <code class="language-plaintext highlighter-rouge">peer2</code> … with predefined configs for the clients of the vpn - convenient, isn’t it?</p>

<h2 id="wireguard-client-setup">WireGuard Client Setup</h2>

<p>We now go back to the backup probe and install WireGuard:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> apt <span class="nb">install </span>wireguard
</code></pre></div></div>

<p>Easy! Now copy the content of one of the <code class="language-plaintext highlighter-rouge">peerX.conf</code> files mentioned before into <code class="language-plaintext highlighter-rouge">/etc/wireguard/wg0.conf</code>:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Interface]</span>
<span class="c"># The new wireguard IP of the probe</span>
<span class="py">Address</span> <span class="p">=</span> <span class="mf">10.13</span><span class="err">.</span><span class="mf">13.2</span>
<span class="py">PrivateKey</span> <span class="p">=</span> <span class="err">XXXXXXXXX</span>
<span class="py">ListenPort</span> <span class="p">=</span> <span class="mi">51820</span>
<span class="py">DNS</span> <span class="p">=</span> <span class="mf">10.13</span><span class="err">.</span><span class="mf">13.1</span>

<span class="nn">[Peer]</span>
<span class="py">PublicKey</span> <span class="p">=</span> <span class="err">YYYYYYYYY</span>
<span class="c"># My server's url</span>
<span class="py">Endpoint</span> <span class="p">=</span> <span class="err">my.very-own.url.org:</span><span class="mi">51820</span>
<span class="py">AllowedIPs</span> <span class="p">=</span> <span class="mf">10.13</span><span class="err">.</span><span class="mf">13.0</span><span class="err">/</span><span class="mi">24</span>
<span class="py">PersistentKeepalive</span> <span class="p">=</span> <span class="mi">25</span>
</code></pre></div></div>

<p class="notice--warning"><strong>Important:</strong> The <code class="language-plaintext highlighter-rouge">PersistentKeepalive</code> is required, as the Probe is most likely behind a NAT and thus not reachable from outside my friend’s home network.
With that parameter set, the probe sends a keepalive package to the server every 25 seconds, which keeps the connection open and enables us to ssh and backup anytime.</p>

<p>Verify that it works with:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> wg-quick up wg0
<span class="c"># ...</span>
</code></pre></div></div>

<p>Now we check on the home-server or another machine in the same WireGuard network if this works:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> ping 10.13.13.2 <span class="c"># the IP set in the peerX.conf</span>
PING 10.13.13.2 <span class="o">(</span>10.13.13.2<span class="o">)</span> 56<span class="o">(</span>84<span class="o">)</span> bytes of data.
64 bytes from 10.13.13.2: <span class="nv">icmp_seq</span><span class="o">=</span>1 <span class="nv">ttl</span><span class="o">=</span>63 <span class="nb">time</span><span class="o">=</span>59.1 ms
64 bytes from 10.13.13.2: <span class="nv">icmp_seq</span><span class="o">=</span>2 <span class="nv">ttl</span><span class="o">=</span>63 <span class="nb">time</span><span class="o">=</span>44.3 ms
<span class="c"># nice!</span>

<span class="o">&gt;</span> ssh jonathan@10.13.13.2
<span class="c"># ...</span>
</code></pre></div></div>

<p>At the moment, VPN connection is lost after a reboot. But this can be changed with systemd. We just have to enable the corresponding service:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> systemctl <span class="nb">enable </span>wg-quick@wg0
<span class="o">&gt;</span> systemctl start wg-quick@wg0 <span class="c"># (Otherwise a reboot is required)</span>
</code></pre></div></div>

<p>Now is a good time to reboot the probe and check if the setup works so far.</p>

<div class="notice--info">
  
<p><strong>DNS Issue Fix:</strong> If you address your host directly via an IP address, you can skip this step. But if you have set a DNS record for the server, you’ll probably run into an error here.
The problem is, that the WireGuard service now tries to establish a connection to the home-server before the DNS resolver is fully running. Therefore, the initial connection attempt fails, and the normal service configuration doesn’t include reconnects in such a case.
The <em>dirty hack</em> I did and which worked surprisingly well so far is, to wait for the domain to be reachable before the connection.
To do so, modify the systemd service file at <code class="language-plaintext highlighter-rouge">/lib/systemd/system/wg-quick@.service</code> and add the following line to the <code class="language-plaintext highlighter-rouge">[Service]</code> section:</p>

<div class="language-systemd highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">ExecStartPre</span><span class="p">=</span>/bin/bash -c 'until host my.very-own.url.org; do sleep 1; done'
</code></pre></div></div>

</div>

<h3 id="restic-setup">Restic Setup</h3>

<p>You could now install <a href="https://github.com/restic/restic#backends">various cloud storage systems</a> as back-ends on the probe, but the most simple one at this point and for this use case is probably <a href="https://en.wikipedia.org/wiki/SSHFS">SFTP/SSHFS</a>.
We use the Dockerfile <a href="https://github.com/lobaro/restic-backup-docker">provided by lobaro</a>.
However, with the plain Dockerfile, we would have to store the password for the probe in plaintext in the compose file, and it would also appear in the logs. So we extend the Dockerfile a bit and utilize <a href="https://docs.docker.com/engine/swarm/secrets/">Docker secrets</a> to mount an ssh key in the container, so we can use passwordless authentication.</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> lobaro/restic-backup-docker:latest</span>
<span class="c"># secrets are mounted at /run/secrets. Here we link the secret into the container's ssh config:</span>
<span class="k">RUN </span><span class="nb">mkdir</span> <span class="nt">-p</span> /root/.ssh <span class="o">&amp;&amp;</span> <span class="nb">ln</span> <span class="nt">-s</span> /run/secrets/user_ssh_key /root/.ssh/backup_probe_key
<span class="c"># Ensure correct permissions. SSH won't work if these are incorrect.</span>
<span class="k">RUN </span><span class="nb">chown</span> <span class="nt">-R</span> root:root /root/.ssh
<span class="c"># Disable the "trust host message" in the container.</span>
<span class="k">RUN </span><span class="nb">printf</span> <span class="s2">"Host 10.13.13.2</span><span class="se">\n\t</span><span class="s2">StrictHostKeyChecking no</span><span class="se">\n</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> /root/.ssh/config
</code></pre></div></div>

<p>If you haven’t done before, at the latest now we need an ssh key to log into the probe:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># (Optional:) Generate a new key:</span>
<span class="o">&gt;</span> ssh-keygen <span class="nt">-t</span> ed25519 <span class="nt">-C</span> <span class="s2">"your_email@example.com"</span>
<span class="c"># Copy the key (here: backup_probe_key) to the probe:</span>
<span class="o">&gt;</span> ssh-copy-id <span class="nt">-i</span> ~/.ssh/backup_probe_key <span class="nt">-o</span> <span class="nv">PreferredAuthentications</span><span class="o">=</span>password <span class="nt">-o</span> <span class="nv">PubkeyAuthentication</span><span class="o">=</span>no jonathan@10.13.13.2
</code></pre></div></div>

<p>Ok, now we are ready to tackle the restic configuration:
First we want to create a <code class="language-plaintext highlighter-rouge">.env</code> file to hold our backup’s password (the <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> is really not the best place for these).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RESTIC_PASSWORD=asdfasdfasdf123412341234
</code></pre></div></div>

<p class="notice--info">Note: A handy way to generate passwords is <code class="language-plaintext highlighter-rouge">pwgen</code>: <code class="language-plaintext highlighter-rouge">pwgen -n 30 1</code> generates a 30 character alphanumeric password.</p>

<p>Ok, now so here’s the section of the <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> with the actual backup configuration:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.3"</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">restic-backup</span><span class="pi">:</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">wireguard</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">restic-backup</span>
    <span class="na">build</span><span class="pi">:</span>
      <span class="c1"># use the local Dockerfile</span>
      <span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
      <span class="na">dockerfile</span><span class="pi">:</span> <span class="s">Dockerfile</span>
    <span class="na">secrets</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">user_ssh_key</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="c1"># repository location: /data/backup/server on the probe</span>
      <span class="pi">-</span> <span class="s">RESTIC_REPOSITORY=sftp:jonathan@10.13.13.2:/data/backup/server</span>
      <span class="c1"># The name of the ssh key inside the container (not really necessary)</span>
      <span class="pi">-</span> <span class="s">RESTIC_KEY_HINT=backup_probe_key</span>
      <span class="c1"># Every second day at 12:00</span>
      <span class="pi">-</span> <span class="s">BACKUP_CRON= 0 12 2-30/2 * *</span>
      <span class="c1"># keep the last 2 backups plus one for the last 3 months.</span>
      <span class="pi">-</span> <span class="s">RESTIC_FORGET_ARGS=--keep-last 2 --keep-monthly </span><span class="m">3</span>
    <span class="na">env_file</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">.env</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="c1"># Backup the folders /zstorage/web and /home/jonathan</span>
      <span class="pi">-</span> <span class="s">/zstorage/git:/data/git:ro</span>
      <span class="pi">-</span> <span class="s">/home/jonathan/:/data/home:ro</span>
      <span class="c1"># ...</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="c1"># Same network as the wireguard container so we can access the probe</span>
    <span class="na">network_mode</span><span class="pi">:</span> <span class="s">service:wireguard</span>

<span class="na">secrets</span><span class="pi">:</span>
  <span class="na">user_ssh_key</span><span class="pi">:</span>
    <span class="c1"># create the secret for the ssh key (here: .ssh/backup_probe_key)</span>
    <span class="na">file</span><span class="pi">:</span> <span class="s">/home/jonathan/.ssh/backup_probe_key</span>
</code></pre></div></div>

<p class="notice--info">Note the addition of <code class="language-plaintext highlighter-rouge">:ro</code> in the <code class="language-plaintext highlighter-rouge">volumes</code> section to mount the folders as read-only. This ensures that anything wonky on your remote, backup configuration or in the wireguard network at least can’t change your files.</p>

<h3 id="final-compose-file">Final compose file</h3>

<p>All that is left is to combine the two sections of the compose file and run <code class="language-plaintext highlighter-rouge">docker-compose up -d</code>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.3"</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">wireguard</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/linuxserver/wireguard</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">wireguard</span>
    <span class="na">cap_add</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">NET_ADMIN</span>
      <span class="pi">-</span> <span class="s">SYS_MODULE</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">PUID=1000</span>
      <span class="pi">-</span> <span class="s">PGID=1000</span>
      <span class="pi">-</span> <span class="s">TZ=Europe/Berlin</span>
      <span class="pi">-</span> <span class="s">SERVERURL=my.very-own.url.org</span>
      <span class="pi">-</span> <span class="s">SERVERPORT=51820</span>
      <span class="pi">-</span> <span class="s">PEERS=5</span>
      <span class="pi">-</span> <span class="s">PEERDNS=auto</span>
      <span class="pi">-</span> <span class="s">INTERNAL_SUBNET=10.13.13.0</span>
      <span class="pi">-</span> <span class="s">ALLOWEDIPS=10.13.13.0/24</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/home/jonathan/wireguard/config:/config</span>
      <span class="pi">-</span> <span class="s">/lib/modules:/lib/modules</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">51820:51820/udp</span>
    <span class="na">sysctls</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">net.ipv4.conf.all.src_valid_mark=1</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>

  <span class="na">restic-backup</span><span class="pi">:</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">wireguard</span>
    <span class="na">container_name</span><span class="pi">:</span> <span class="s">restic-backup</span>
    <span class="na">build</span><span class="pi">:</span>
      <span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
      <span class="na">dockerfile</span><span class="pi">:</span> <span class="s">Dockerfile</span>
    <span class="na">secrets</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">user_ssh_key</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">RESTIC_REPOSITORY=sftp:jonathan@10.13.13.2:/data/backup/server</span>
      <span class="pi">-</span> <span class="s">RESTIC_KEY_HINT=backup_probe_key</span>
      <span class="pi">-</span> <span class="s">BACKUP_CRON= 0 12 2-30/2 * *</span>
      <span class="pi">-</span> <span class="s">RESTIC_FORGET_ARGS=--keep-last 2 --keep-monthly </span><span class="m">3</span>
    <span class="na">env_file</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">.env</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/zstorage/git:/data/git:ro</span>
      <span class="pi">-</span> <span class="s">/home/jonathan/:/data/home:ro</span>
      <span class="c1"># ...</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">network_mode</span><span class="pi">:</span> <span class="s">service:wireguard</span>

<span class="na">secrets</span><span class="pi">:</span>
  <span class="na">user_ssh_key</span><span class="pi">:</span>
    <span class="na">file</span><span class="pi">:</span> <span class="s">/home/jonathan/.ssh/backup_probe_key</span>
</code></pre></div></div>

<h2 id="checking-the-backup">Checking the Backup</h2>

<p>Almost as important as creating a backup: Checking that you can restore from it.
One way is, to mount the backup, so you can navigate it with a normal file manager:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">mkdir</span> /tmp/restic
<span class="o">&gt;</span> restic <span class="nt">-r</span> sftp:jonathan@10.13.13.2:/data/backup/server mount /tmp/restic
</code></pre></div></div>

<p>After entering your <code class="language-plaintext highlighter-rouge">RESTIC_PASSWORD</code>, you should find your backup at <code class="language-plaintext highlighter-rouge">/tmp/restic</code>.</p>

<p><strong>Happy Backuping!</strong></p>]]></content><author><name>Jonathan Klimt</name></author><category term="Odroid-XU4" /><category term="Backup" /><category term="Linux" /><summary type="html"><![CDATA[For offsite backups of my home-server, I've created a small probe based on a single-board computer]]></summary></entry><entry><title type="html">Upgrade of my Artillery Genius Heat Bed</title><link href="https://jonathanklimt.de/3d-printing/genius-heatbed/" rel="alternate" type="text/html" title="Upgrade of my Artillery Genius Heat Bed" /><published>2021-12-03T01:00:00+01:00</published><updated>2024-12-01T18:16:52+01:00</updated><id>https://jonathanklimt.de/3d-printing/genius-heatbed</id><content type="html" xml:base="https://jonathanklimt.de/3d-printing/genius-heatbed/"><![CDATA[<h2 id="artillery-genius-heatbed-upgrade">Artillery Genius Heatbed Upgrade</h2>

<p>I own an <a href="https://artillery3d.com/products/artillery-genius-3d-printer-kit-220220250mm-print-size-with-ultra-quiet-stepper-motor-tft-touch-screen">Artillery Genius</a> 3D printer since now more than 1.5 years, and now the temperature sensor’s cable broke. This is unfortunate, because I’ve read multiple times that a strain relief for this cable is an upgrade you should definitely print. But I’ve constantly ignored it.
Ok, lesson learned. (BTW: The ribbon cable of this printer also needs a strain relief!)</p>

<p>As a hotfix, I replaced the broken part of the cable, which surprisingly worked for a few prints but is certainly something from the <em>do-not-try-this-at-home</em> category. Be reminded that the heating bed of this printer operates at 230V!
Looking at the picture, you will surely come to the same conclusion I did: This won’t last very long.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/repair_2_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/genius-heatbed/repair_2_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        A quick and dirty repair of the broken thermistor cable
      
    </figcaption>
  

</figure>

<h2 id="the-upgrade">The Upgrade</h2>

<p>You can just buy a new heat mat for ~35€, but since I’m on it anyway, I decided to directly go for an upgrade of the complete heat bed.</p>

<p>The stock glass surface is just fine, but the heat-matte is not doing a good job in heating the plate evenly. You can see from the following picture that the corners, above the mounting screws, the bed is significantly cooler than in the center.
No problem for small prints, but this has ruined a few larger prints already.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/heated_before.jpg">
      <img src="https://jonathanklimt.de/assets/images/genius-heatbed/heated_before.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The stock bed of the Genius after a few minutes
      
    </figcaption>
  

</figure>

<p>It is rather difficult to find a different heat mat, at least if you don’t want to import something from the USA or China.
The only thing I found was the <a href="https://princore.de/products/artillery-genius-silikonheizmatte-220v-300watt">Princore Genius Heat Mat</a> from a German shop.
There is more choice for aluminum beds, so I chose the following for no particular reason: <a href="https://www.abs-3d.de/druckbetten/druckbetten-nicht-magnetisch/artillery/626/artillery-genius-aluminium-druckbett?number=ABS-Genuis-Set2">ABS Maschinenschutz Heat Bed</a></p>

<p>The assembly was mostly straightforward. You need to shorten the cables and have to pay attention not to damage anything. Some remarks on this:</p>

<ul>
  <li>It is advisable to use <a href="https://en.wikipedia.org/wiki/Electrical_connector#Ring_and_spade_connectors">ring terminals</a> to attach the new wires to the printer.</li>
  <li>⚠️  <strong>Only do this if you know what you are doing! 230V are dangerous!</strong> ⚠️</li>
  <li>⚠️  <strong>The bed MUST be connected to ground</strong> ⚠️</li>
</ul>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/printer_after_blank_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/genius-heatbed/printer_after_blank_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/printer_after_large.jpg">
        <img src="https://jonathanklimt.de/assets/images/genius-heatbed/printer_after_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        The Genius with the new heat bed. Right: Without and Left: with the GFK sheet
      
    </figcaption>
  

</figure>

<p>Ok, so does this improve the situation? I’m not doing a scientific level analysis here, but I do have some results:</p>

<p>First, I wanted to know if the mat heats more evenly than the stock one which has the cross shape heating characteristic.
So I’ve taken a thermal camera picture of the bottom of the heat bed without any isolation:</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/new_bottom.jpg">
      <img src="https://jonathanklimt.de/assets/images/genius-heatbed/new_bottom.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The bottom of the aluminum bed with the Princore heat mat and without any isolation
      
    </figcaption>
  

</figure>

<p>It’s a bit hard to judge if the mat heats up more evenly, or we do mostly see the effects of the metal plate dissipating the heat more evenly, but it looks better than before!</p>

<p>But what about the obviously more important surface temperature?</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/heatup_after.jpg">
      <img src="https://jonathanklimt.de/assets/images/genius-heatbed/heatup_after.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/heatup_after_3.jpg">
        <img src="https://jonathanklimt.de/assets/images/genius-heatbed/heatup_after_3.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        The top of the new bed with the GFK sheet during heat up and after a few minutes
      
    </figcaption>
  

</figure>

<p>I’d say, this looks quite nice. Definitely better than before! The plate also seems to be sticky enough, but I only did one short test-print so far.</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/first_print_top_large.jpg">
      <img src="https://jonathanklimt.de/assets/images/genius-heatbed/first_print_top_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/first_print_bot_large.jpg">
        <img src="https://jonathanklimt.de/assets/images/genius-heatbed/first_print_bot_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        First simple test print. In the printer (left) and the bottom of it (right).
      
    </figcaption>
  

</figure>

<p>The bottom is <em>very</em> smooth. You see small gaps, which I’ll tackle later on.
So for now, in all I’m rather pleased. Is it worth the 100€? Hmm, time will tell. Let’s see how long this will last. But if you don’t want to spend the money, be sure to print and attach a strain relief, so your bed will last longer than mine!</p>

<ul>
  <li><a href="https://www.thingiverse.com/thing:4389200">This one for the bed</a>. You have to attach it to the metal plate which is holding the bed screws.</li>
  <li>I have printed <a href="https://www.thingiverse.com/thing:4100016">this relief for the X-axis ribbon cable</a>, but it needed some manual adaption since it is made for the Sidewinder X1 and not for the Genius.</li>
</ul>

<p>Happy Printing 🖨</p>]]></content><author><name>Jonathan Klimt</name></author><category term="3D-Printing" /><category term="3D-Printer" /><category term="Artillery Genius" /><category term="Upgrade" /><summary type="html"><![CDATA[I've upgraded the heat bed of my Artillery Genius with a metal one and checked if the temperature is distributed more evenly.]]></summary></entry><entry><title type="html">Rust on STM32: Getting started</title><link href="https://jonathanklimt.de/electronics/programming/embedded-rust/rust-on-stm32-2/" rel="alternate" type="text/html" title="Rust on STM32: Getting started" /><published>2020-07-22T02:00:00+02:00</published><updated>2024-12-01T18:16:52+01:00</updated><id>https://jonathanklimt.de/electronics/programming/embedded-rust/rust-on-stm32-2</id><content type="html" xml:base="https://jonathanklimt.de/electronics/programming/embedded-rust/rust-on-stm32-2/"><![CDATA[<p><em>(Thumbnail: <a href="https://github.com/rust-embedded/wg/tree/master/assets">Rust Embedded WG Logo</a> - <a href="https://creativecommons.org/licenses/by/4.0/">CC-BY</a>)</em></p>

<h2 id="preface">Preface</h2>

<p>I didn’t found any time and motivation to write something here, but I guess it’s time for an updated version of <a href="/electronics/programming/embedded-rust/rust-STM32F103-blink/">my previous post on Rust on the STM32F1</a>
I plan to make more shorter and more modular posts, but to get started, here is a short tutorial on how to get started:</p>

<p>🎉 Blink an LED in Rust on a cheap microcontroller. 🎉</p>

<h2 id="what-you-need">What you need</h2>

<ul>
  <li>A PC with an installed <a href="https://www.rust-lang.org/learn/get-started">Rust Toolchain</a> (which also should involve <code class="language-plaintext highlighter-rouge">cargo</code>). I only run Linux, but the following is likely to run on MacOS as well, and maybe even on Windows.</li>
  <li>A Board with an STM32F103 microcontroller.
  The most common dev-boards with this controllers are probably the “BluePills” which you usually can get for less than 2€ (e.g. on <a href="https://www.aliexpress.com/item/STM32F103C8T6-ARM-STM32-Minimum-System-Development-Board-Module-For-Arduino/32278016818.html">Aliexpress</a>).
  We use a BluePill, but other Boards should work very similar.
  <em>Attention:</em> Especially on Aliexpress there exists an incredible amount of counterfeit chips.
  If it does not work, this might be the reason.</li>
  <li>An Programmer.
  Among the cheapest programmers you can get for the STM chips is a ST-Link V2 Clone (<a href="https://www.aliexpress.com/item/1PCS-ST-LINK-Stlink-ST-Link-V2-Mini-STM8-STM32-Simulator-Download-Programmer-Programming-With-Cover/32920031233.html">AliExpress</a>).
  The Programmer itself is a clone and often contains an “CS32” chip instead of an STM32. Again: Might be a source of trouble.</li>
</ul>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005342_edit.jpg">
      <img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005342_edit_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005543_edit.jpg">
        <img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005543_edit_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        BluePill, Blackpill and a ST-Link clone connected to the BluePill
      
    </figcaption>
  

</figure>

<h2 id="software-preparation">Software Preparation</h2>

<p>At first, let’s make sure we have an up-to-date compiler and cargo:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> rustup update
</code></pre></div></div>

<p>Then we’ll install the cross-toolchain for the STM32F1, which runs a <code class="language-plaintext highlighter-rouge">thumbv7m-none-eabi</code> ARM core:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> rustup target <span class="nb">install </span>thumbv7m-none-eabi
</code></pre></div></div>

<p>For now, we use <a href="https://github.com/probe-rs/cargo-flash"><code class="language-plaintext highlighter-rouge">cargo flash</code></a> to program the microcontroller.
We can comfortably install this tool via cargo:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> cargo <span class="nb">install </span>cargo-flash
</code></pre></div></div>

<p>And finally we create a new Rust project in a directory of our choice with</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> cargo init rusty-blink
</code></pre></div></div>

<p>Before we write any code, we configure the project, so that it compiles for our <code class="language-plaintext highlighter-rouge">thumbv7m-none-eabi</code> target by default and uses the correct linker script (more on that in a second). We do this by creating a file <code class="language-plaintext highlighter-rouge">.cargo/config</code> in our project root.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># .cargo/config</span>
<span class="nn">[build]</span>
<span class="c"># Always compile for the instruction set of the STM32F1</span>
<span class="py">target</span> <span class="p">=</span> <span class="s">"thumbv7m-none-eabi"</span>

<span class="c"># use the Tlink.x scrip from the cortex-m-rt crate</span>
<span class="py">rustflags</span> <span class="p">=</span> <span class="p">[</span> <span class="s">"-C"</span><span class="p">,</span> <span class="py">"link-arg</span><span class="p">=</span><span class="err">-Tlink.x</span><span class="s">"]</span><span class="err">
</span></code></pre></div></div>

<p>The next we need is the linker script which tells the linker about the memory layout of the chip. It must lay in the project root and is called <code class="language-plaintext highlighter-rouge">memory.x</code>. For the STM32F103C8 this looks as follows:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* memory.x - Linker script for the STM32F103C8T6 */</span>
<span class="n">MEMORY</span>
<span class="p">{</span>
  <span class="cm">/* Flash memory begins at 0x80000000 and has a size of 64kB*/</span>
  <span class="n">FLASH</span> <span class="o">:</span> <span class="n">ORIGIN</span> <span class="o">=</span> <span class="mh">0x08000000</span><span class="p">,</span> <span class="n">LENGTH</span> <span class="o">=</span> <span class="mi">64</span><span class="n">K</span>
  <span class="cm">/* RAM begins at 0x20000000 and has a size of 20kB*/</span>
  <span class="n">RAM</span> <span class="o">:</span> <span class="n">ORIGIN</span> <span class="o">=</span> <span class="mh">0x20000000</span><span class="p">,</span> <span class="n">LENGTH</span> <span class="o">=</span> <span class="mi">20</span><span class="n">K</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ok, enough preparation, lets get to Rust/Cargo!</p>

<h2 id="the-blinky-program">The Blinky Program</h2>

<p>Of course, you need a <code class="language-plaintext highlighter-rouge">Cargo.toml</code> file to configure your project. There is already one present in your project folder, but we need to add some dependencies and configurations:
(These are current versions as of mid July 2020).</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cargo.toml</span>
<span class="nn">[package]</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2018"</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"blinky-rust"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>

<span class="nn">[profile.release]</span>
<span class="py">opt-level</span> <span class="p">=</span> <span class="s">'z'</span> <span class="c"># turn on maximum optimizations. We only have 64kB</span>
<span class="py">lto</span> <span class="p">=</span> <span class="kc">true</span>      <span class="c"># Link-time-optimizations for further size reduction</span>

<span class="nn">[dependencies]</span>
<span class="py">cortex-m</span> <span class="p">=</span> <span class="s">"^0.6.3"</span>      <span class="c"># Access to the generic ARM peripherals</span>
<span class="py">cortex-m-rt</span> <span class="p">=</span> <span class="s">"^0.6.12"</span>  <span class="c"># Startup code for the ARM Core</span>
<span class="py">embedded-hal</span> <span class="p">=</span> <span class="s">"^0.2.4"</span>  <span class="c"># Access to generic embedded functions (`set_high`)</span>
<span class="py">panic-halt</span> <span class="p">=</span> <span class="s">"^0.2.0"</span>    <span class="c"># Panic handler</span>

<span class="c"># Access to the stm32f103 HAL.</span>
<span class="nn">[dependencies.stm32f1xx-hal]</span>
<span class="c"># Bluepill contains a 64kB flash variant which is called "medium density"</span>
<span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"stm32f103"</span><span class="p">,</span> <span class="s">"rt"</span><span class="p">,</span> <span class="s">"medium"</span><span class="p">]</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"^0.6.1"</span>
</code></pre></div></div>

<p>And finally: Here is a simple blink program! Don’t be afraid, it only looks that long because I added the explanatory comments. Without it’s only ~30 lines 😉.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs</span>

<span class="c1">// std and main are not available for bare metal software</span>
<span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>

<span class="k">use</span> <span class="nn">cortex_m_rt</span><span class="p">::</span><span class="n">entry</span><span class="p">;</span> <span class="c1">// The runtime</span>
<span class="k">use</span> <span class="nn">embedded_hal</span><span class="p">::</span><span class="nn">digital</span><span class="p">::</span><span class="nn">v2</span><span class="p">::</span><span class="n">OutputPin</span><span class="p">;</span> <span class="c1">// the `set_high/low`function</span>
<span class="k">use</span> <span class="nn">stm32f1xx_hal</span><span class="p">::{</span><span class="nn">delay</span><span class="p">::</span><span class="n">Delay</span><span class="p">,</span> <span class="n">pac</span><span class="p">,</span> <span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">};</span> <span class="c1">// STM32F1 specific functions</span>
<span class="nd">#[allow(unused_imports)]</span>
<span class="k">use</span> <span class="n">panic_halt</span><span class="p">;</span> <span class="c1">// When a panic occurs, stop the microcontroller</span>

<span class="c1">// This marks the entrypoint of our application. The cortex_m_rt creates some</span>
<span class="c1">// startup code before this, but we don't need to worry about this</span>
<span class="nd">#[entry]</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="c1">// Get handles to the hardware objects. These functions can only be called</span>
    <span class="c1">// once, so that the borrowchecker can ensure you don't reconfigure</span>
    <span class="c1">// something by accident.</span>
    <span class="k">let</span> <span class="n">dp</span> <span class="o">=</span> <span class="nn">pac</span><span class="p">::</span><span class="nn">Peripherals</span><span class="p">::</span><span class="nf">take</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">cp</span> <span class="o">=</span> <span class="nn">cortex_m</span><span class="p">::</span><span class="nn">Peripherals</span><span class="p">::</span><span class="nf">take</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// GPIO pins on the STM32F1 must be driven by the APB2 peripheral clock.</span>
    <span class="c1">// This must be enabled first. The HAL provides some abstractions for</span>
    <span class="c1">// us: First get a handle to the RCC peripheral:</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">rcc</span> <span class="o">=</span> <span class="n">dp</span><span class="py">.RCC</span><span class="nf">.constrain</span><span class="p">();</span>
    <span class="c1">// Now we have access to the RCC's registers. The GPIOC can be enabled in</span>
    <span class="c1">// RCC_APB2ENR (Prog. Ref. Manual 8.3.7), therefore we must pass this</span>
    <span class="c1">// register to the `split` function.</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">gpioc</span> <span class="o">=</span> <span class="n">dp</span><span class="py">.GPIOC</span><span class="nf">.split</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">rcc</span><span class="py">.apb2</span><span class="p">);</span>
    <span class="c1">// This gives us an exclusive handle to the GPIOC peripheral. To get the</span>
    <span class="c1">// handle to a single pin, we need to configure the pin first. Pin C13</span>
    <span class="c1">// is usually connected to the Bluepills onboard LED.</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">led</span> <span class="o">=</span> <span class="n">gpioc</span><span class="py">.pc13</span><span class="nf">.into_push_pull_output</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">gpioc</span><span class="py">.crh</span><span class="p">);</span>

    <span class="c1">// Now we need a delay object. The delay is of course depending on the clock</span>
    <span class="c1">// frequency of the microcontroller, so we need to fix the frequency</span>
    <span class="c1">// first. The system frequency is set via the FLASH_ACR register, so we</span>
    <span class="c1">// need to get a handle to the FLASH peripheral first:</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">flash</span> <span class="o">=</span> <span class="n">dp</span><span class="py">.FLASH</span><span class="nf">.constrain</span><span class="p">();</span>
    <span class="c1">// Now we can set the controllers frequency to 8 MHz:</span>
    <span class="k">let</span> <span class="n">clocks</span> <span class="o">=</span> <span class="n">rcc</span><span class="py">.cfgr</span><span class="nf">.sysclk</span><span class="p">(</span><span class="mi">8</span><span class="nf">.mhz</span><span class="p">())</span><span class="nf">.freeze</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">flash</span><span class="py">.acr</span><span class="p">);</span>
    <span class="c1">// The `clocks` handle ensures that the clocks are now configured and gives</span>
    <span class="c1">// the `Delay::new` function access to the configured frequency. With</span>
    <span class="c1">// this information it can later calculate how many cycles it has to</span>
    <span class="c1">// wait. The function also consumes the System Timer peripheral, so that no</span>
    <span class="c1">// other function can access it. Otherwise the timer could be reset during a</span>
    <span class="c1">// delay.</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">delay</span> <span class="o">=</span> <span class="nn">Delay</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">cp</span><span class="py">.SYST</span><span class="p">,</span> <span class="n">clocks</span><span class="p">);</span>

    <span class="c1">// Now, enjoy the lightshow!</span>
    <span class="k">loop</span> <span class="p">{</span>
        <span class="n">led</span><span class="nf">.set_high</span><span class="p">()</span><span class="nf">.ok</span><span class="p">();</span>
        <span class="n">delay</span><span class="nf">.delay_ms</span><span class="p">(</span><span class="mi">1_000_u16</span><span class="p">);</span>
        <span class="n">led</span><span class="nf">.set_low</span><span class="p">()</span><span class="nf">.ok</span><span class="p">();</span>
        <span class="n">delay</span><span class="nf">.delay_ms</span><span class="p">(</span><span class="mi">1_000_u16</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s build it!</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> cargo build <span class="nt">--release</span>
   Compiling semver-parser v0.7.0
   Compiling typenum v1.12.0
   <span class="o">[</span>...]
   Compiling cortex-m-rt-macros v0.1.8
   Compiling stm32f1xx-hal v0.6.1
   Compiling blinky-rust v0.1.0 <span class="o">(</span>/home/xxx/xxx/Rust/embedded/rusty-blink<span class="o">)</span>
    Finished release <span class="o">[</span>optimized] target<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>34.63s
</code></pre></div></div>

<p>For the blink example you can leave out the <code class="language-plaintext highlighter-rouge">--release</code>, but when you compile a more complicated program you will run out of device memory fast without optimization enabled.</p>

<p>My resulting program takes 864 bytes of memory (2,8kB without LTO). To check this, you have to convert the compiler output (an <em>elf</em> file) to hex/binary.
You can for example use <code class="language-plaintext highlighter-rouge">arm-none-eabi-objcopy -O binary target/thumbv7m-none-eabi/release/blinky-rust blinky-rust.bin</code> and then <code class="language-plaintext highlighter-rouge">ls</code>.</p>

<p>Now you are ready to flash the controller with <code class="language-plaintext highlighter-rouge">cargo flash</code>.
It should auto-detect your ST-Link and use it to program the microcontroller.
You only have to specify the target chip (<code class="language-plaintext highlighter-rouge">stm32f103C8</code>).</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> cargo flash <span class="nt">--chip</span> stm32f103C8 <span class="nt">--release</span>
    Finished release <span class="o">[</span>optimized] target<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.02s
    Flashing /home/xxx/xxx/Rust/embedded/rusty-blink/target/thumbv7m-none-eabi/release/blinky-rust
        WARN probe_rs::architecture::arm::core::m4 <span class="o">&gt;</span> Reason <span class="k">for </span>halt has changed, old reason was Halted<span class="o">(</span>Request<span class="o">)</span>, new reason is Exception
        WARN probe_rs::architecture::arm::core::m4 <span class="o">&gt;</span> Reason <span class="k">for </span>halt has changed, old reason was Halted<span class="o">(</span>Breakpoint<span class="o">)</span>, new reason is Request
        WARN probe_rs::architecture::arm::core::m4 <span class="o">&gt;</span> Reason <span class="k">for </span>halt has changed, old reason was Halted<span class="o">(</span>Request<span class="o">)</span>, new reason is Exception
     Erasing sectors ✔ <span class="o">[</span>00:00:00] <span class="o">[</span><span class="c">#############################]   1.00KB/  1.00KB @   1.49KB/s (eta 0s )</span>
 Programming pages   ✔ <span class="o">[</span>00:00:01] <span class="o">[</span><span class="c">#############################]   1.00KB/  1.00KB @     583B/s (eta 0s )</span>
</code></pre></div></div>
<p>(The warnings can be ignored, <code class="language-plaintext highlighter-rouge">cargo flash</code> is still a young project and has some faults.)</p>

<p>You should now see the blinking onboard LED!</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005642_edit.jpg">
      <img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005642_edit_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Blinking LED on BluePill
      
    </figcaption>
  

</figure>

<h2 id="im-hyped-whats-next">I’m hyped! What’s next?</h2>

<p>Good you asked! I prepared some link’s for you:</p>
<ul>
  <li><a href="https://docs.rs/stm32f1xx-hal/latest/stm32f1xx_hal/index.html">The <code class="language-plaintext highlighter-rouge">stm32f1xx_hal</code>s documentation</a></li>
  <li><a href="https://github.com/stm32-rs/stm32f1xx-hal/tree/master/examples">Plenty of examples for the <code class="language-plaintext highlighter-rouge">stm32f1xx_hal</code></a></li>
  <li>You can find this example’s code on <a href="https://gitlab.com/jounathaen-projects/embedded-rust/blinky-rust">Gitlab</a></li>
</ul>

<p>I also plan to do some more posts on this, but you know… time and motivation.
If you have any questions or want to remind me to write some more posts, feel free to contact me! 🙂👍</p>]]></content><author><name>Jonathan Klimt</name></author><category term="Electronics" /><category term="Programming" /><category term="Embedded-Rust" /><category term="STM32" /><category term="STM32F103" /><category term="BluePill" /><category term="Rust" /><category term="LED" /><category term="Linux" /><category term="toolchain" /><summary type="html"><![CDATA[Getting started with embedded Rust. Blinking LED on Bluepill. (Thumbnail: Rust Embedded WG - [CC-BY](https://creativecommons.org/licenses/by/4.0/))]]></summary></entry><entry><title type="html">Rust on STM32: Blinking an LED</title><link href="https://jonathanklimt.de/electronics/programming/embedded-rust/rust-STM32F103-blink/" rel="alternate" type="text/html" title="Rust on STM32: Blinking an LED" /><published>2019-02-06T01:00:00+01:00</published><updated>2024-12-01T18:16:52+01:00</updated><id>https://jonathanklimt.de/electronics/programming/embedded-rust/rust-STM32F103-blink</id><content type="html" xml:base="https://jonathanklimt.de/electronics/programming/embedded-rust/rust-STM32F103-blink/"><![CDATA[<p><br /></p>

<hr />
<hr />

<p><strong>DEPRECATED: I have created a more up-to-date version of this article. You can find it <a href="/electronics/programming/embedded-rust/rust-on-stm32-2/">here</a> !</strong></p>

<hr />
<hr />

<p><br /></p>

<p><strong>GOAL:</strong> Blink the onboard LED on a BluePill board using Rust on a Linux Mint (Ubuntu based) machine.</p>

<p><strong>Update/Information:</strong> The rustc version I used is <code class="language-plaintext highlighter-rouge">rustc 1.31.1</code> (stable) and you need to install the ARM target with <code class="language-plaintext highlighter-rouge">rustup target add thumbv7m-none-eabi</code> beforehand.</p>

<p>I also put the code on <a href="https://gitlab.com/jounathaen/stm32_blink">Gitlab</a>.</p>

<h2 id="preface">Preface</h2>

<p>I have mainly used Atmel microcontrollers so far, but they are rather expensive, slow and have a totally confusing pinout. (And I think, they have no real future).
So I wanted to use a better controller and setup the flow to program it on my Linux Mint machine (should work on Ubuntu as well).
<strong>I assume, that the reader already has at least a little knowledge in Rust, Linux, and Microcontrollers</strong></p>

<p>One of the possibly cheapest and general purpose boards is the STM32F103 board aka “BluePill”.
These sell for less than 2€ on <a href="https://www.aliexpress.com/item/STM32F103C8T6-ARM-STM32-Minimum-System-Development-Board-Module-For-Arduino/32278016818.html">AliExpress</a>,
but I’d recommend you the more up-to-date version, the “BlackPill” (<a href="https://www.aliexpress.com/item/STM32F103C8T6-ARM-STM32-Minimum-Development-Board-Module-MCU-Core-Board-MicroUSB-for-Arduino-Diy-Kit/32969629826.html">AliExpress</a>), because the BluePill usually has a wrong resistor which causes trouble when using the USB port.
Besides a slightly different pinout and the BlackPill being one row larger than the BluePill, they are basically the same.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005342_edit.jpg">
      <img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005342_edit_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        BluePill and BlackPill
      
    </figcaption>
  

</figure>

<h2 id="setting-up-the-cargo-project">Setting up the Cargo Project</h2>

<p>First, we initialize a new Cargo project</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> cargo init stm32_blink
<span class="o">&gt;</span> <span class="nb">cd </span>stm32_blink
</code></pre></div></div>

<p>We modify our <code class="language-plaintext highlighter-rouge">Cargo.toml</code> file as follows to include the required libraries:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"stm32_blink"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2018"</span>

<span class="nn">[profile.release]</span>
<span class="c"># optimize for size ('z' would optimize even more)</span>
<span class="py">opt-level</span> <span class="p">=</span> <span class="s">'s'</span>
<span class="c"># link with link time optimization (lto).</span>
<span class="py">lto</span> <span class="p">=</span> <span class="kc">true</span>
<span class="c"># enable debugging in release mode.</span>
<span class="py">debug</span> <span class="p">=</span> <span class="kc">true</span>

<span class="nn">[dependencies]</span>
<span class="c"># Gives us access to the STM32F1 registers</span>
<span class="py">stm32f1</span> <span class="o">=</span> <span class="p">{</span><span class="py">version</span> <span class="p">=</span> <span class="s">"0.6.0"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"stm32f103"</span><span class="p">,</span> <span class="s">"rt"</span><span class="p">]}</span>
<span class="c"># provides startup code for the ARM CPU</span>
<span class="py">cortex-m-rt</span> <span class="p">=</span> <span class="s">"0.6.7"</span>
<span class="c"># provides access to low level ARM CPU registers (used for delay)</span>
<span class="py">cortex-m</span> <span class="p">=</span> <span class="s">"0.5.8"</span>
<span class="c"># provies a panic-handler (halting cpu)</span>
<span class="c"># (required when not using stdlib)</span>
<span class="py">panic-halt</span> <span class="p">=</span> <span class="s">"0.2.0"</span>
</code></pre></div></div>

<p>I the <code class="language-plaintext highlighter-rouge">cortex-m-rt</code> crate also requires a <code class="language-plaintext highlighter-rouge">memory.x</code> file, which specifies the memory layout of the board (See the <a href="https://docs.rs/cortex-m-rt/0.6.7/cortex_m_rt/#memoryx">documentation</a>).
The documentation already provides the file for the BluePill as an example, so we only have to create the file <code class="language-plaintext highlighter-rouge">memory.x</code> with the following content:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/* Linker script for the STM32F103C8T6 */
MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 64K
  RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
</code></pre></div></div>

<p>We can now compile the program using <code class="language-plaintext highlighter-rouge">cargo rustc --target thumbv7m-none-eabi -- -C link-arg=-Tlink.x</code>, but that would be a bit inconvenient.
Therefore, we create the file <code class="language-plaintext highlighter-rouge">.cargo/config</code> (and the folder <code class="language-plaintext highlighter-rouge">.cargo</code>) with the following content (based on <a href="https://github.com/rust-embedded/cortex-m-quickstart/blob/master/.cargo/config">this file</a>):</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[build]</span>
<span class="c"># Instruction set of Cortex-M3 (used in BluePill)</span>
<span class="py">target</span> <span class="p">=</span> <span class="s">"thumbv7m-none-eabi"</span>

<span class="py">rustflags</span> <span class="p">=</span> <span class="p">[</span>
  <span class="c"># use the Tlink.x scrip from the cortex-m-rt crate</span>
  <span class="s">"-C"</span><span class="p">,</span> <span class="py">"link-arg</span><span class="p">=</span><span class="err">-Tlink.x</span><span class="s">",</span><span class="err">
</span><span class="p">]</span>
</code></pre></div></div>

<p>Hoooray, now we can compile Rust code into an ARM <em>elf</em> file!
(However, the code we have does not compile yet)</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> cargo build <span class="nt">--release</span>
<span class="o">&gt;</span> file target/thumbv7m-none-eabi/release/stm32_blink
target/thumbv7m-none-eabi/release/stm32_blink:
ELF 32-bit LSB executable, ARM, EABI5 version 1 <span class="o">(</span>SYSV<span class="o">)</span>,
statically linked, with debug_info, not stripped
</code></pre></div></div>

<h2 id="the-blink-program">The Blink Program</h2>
<p>This is the content of the <code class="language-plaintext highlighter-rouge">src/main.rs</code> file:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// std and main are not available for bare metal software</span>
<span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>

<span class="k">extern</span> <span class="k">crate</span> <span class="n">stm32f1</span><span class="p">;</span>
<span class="k">extern</span> <span class="k">crate</span> <span class="n">panic_halt</span><span class="p">;</span>
<span class="k">extern</span> <span class="k">crate</span> <span class="n">cortex_m_rt</span><span class="p">;</span>

<span class="k">use</span> <span class="nn">cortex_m_rt</span><span class="p">::</span><span class="n">entry</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">stm32f1</span><span class="p">::</span><span class="n">stm32f103</span><span class="p">;</span>

<span class="c1">// use `main` as the entry point of this application</span>
<span class="nd">#[entry]</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
    <span class="c1">// get handles to the hardware</span>
    <span class="k">let</span> <span class="n">peripherals</span> <span class="o">=</span> <span class="nn">stm32f103</span><span class="p">::</span><span class="nn">Peripherals</span><span class="p">::</span><span class="nf">take</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">gpioc</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">peripherals</span><span class="py">.GPIOC</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">rcc</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">peripherals</span><span class="py">.RCC</span><span class="p">;</span>

    <span class="c1">// enable the GPIO clock for IO port C</span>
    <span class="n">rcc</span><span class="py">.apb2enr</span><span class="nf">.write</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="n">w</span><span class="nf">.iopcen</span><span class="p">()</span><span class="nf">.set_bit</span><span class="p">());</span>
    <span class="n">gpioc</span><span class="py">.crh</span><span class="nf">.write</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="k">unsafe</span><span class="p">{</span>
        <span class="n">w</span><span class="nf">.mode13</span><span class="p">()</span><span class="nf">.bits</span><span class="p">(</span><span class="mi">0b11</span><span class="p">);</span>
        <span class="n">w</span><span class="nf">.cnf13</span><span class="p">()</span><span class="nf">.bits</span><span class="p">(</span><span class="mi">0b00</span><span class="p">)</span>
    <span class="p">});</span>

    <span class="k">loop</span><span class="p">{</span>
        <span class="n">gpioc</span><span class="py">.bsrr</span><span class="nf">.write</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="n">w</span><span class="nf">.bs13</span><span class="p">()</span><span class="nf">.set_bit</span><span class="p">());</span>
        <span class="nn">cortex_m</span><span class="p">::</span><span class="nn">asm</span><span class="p">::</span><span class="nf">delay</span><span class="p">(</span><span class="mi">2000000</span><span class="p">);</span>
        <span class="n">gpioc</span><span class="py">.brr</span><span class="nf">.write</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="n">w</span><span class="nf">.br13</span><span class="p">()</span><span class="nf">.set_bit</span><span class="p">());</span>
        <span class="nn">cortex_m</span><span class="p">::</span><span class="nn">asm</span><span class="p">::</span><span class="nf">delay</span><span class="p">(</span><span class="mi">2000000</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><strong>Explanation:</strong>
<code class="language-plaintext highlighter-rouge">#![no_std]</code> is required, because we build a bare-metal application but the standard library requires an operating system.
<code class="language-plaintext highlighter-rouge">#![no_main]</code> tells the compiler that we don’t use the default <code class="language-plaintext highlighter-rouge">main</code> function with the argument vector and return type.
This wouldn’t make sense, since we don’t have an OS or other kind of runtime which would call the function and handle the return value.
Instead, the <code class="language-plaintext highlighter-rouge">cortex_m_rt</code> crate contains a minimal runtime and the <code class="language-plaintext highlighter-rouge">#[entry]</code> macro, which specifies our custom entry function (<code class="language-plaintext highlighter-rouge">fn main () -&gt; ! {}</code>), which we call <code class="language-plaintext highlighter-rouge">main</code> as well (don’t be confused).
More information about bare metal software in Rust can be found for example in the <a href="https://os.phil-opp.com/">“Writing an OS in Rust”</a> Blog.</p>

<p>Inside the <code class="language-plaintext highlighter-rouge">main</code> function we create a handle to the peripheral object, which “owns” all the peripherals and registers with <code class="language-plaintext highlighter-rouge">stm32f103::Peripherals::take()</code>.
<code class="language-plaintext highlighter-rouge">rcc</code> and <code class="language-plaintext highlighter-rouge">gpioc</code> are handles to the “reset and clock control” and GPIO register bank C (the onboard LED on my BluePill is connected to pin C13 but this might differ).</p>

<p>To use a pin as output we have to enable the clock for that specific GPIO pin bank by setting the <em>IOPCEN</em> bit in the <em>APB2EN</em> register.
Writing a byte in a peripheral register using the <code class="language-plaintext highlighter-rouge">stm32f1</code> crate is done by calling the <code class="language-plaintext highlighter-rouge">write</code> function on that register struct (<code class="language-plaintext highlighter-rouge">rcc.apb2enr.write()</code>).
This function takes a <a href="https://doc.rust-lang.org/std/ops/trait.FnOnce.html"><code class="language-plaintext highlighter-rouge">FnOnce</code></a> function as an argument.
The parameter which is passed to this function is in the first case of type <code class="language-plaintext highlighter-rouge">gpioc::crh::W</code> which implements functions to access the single bit fields in that very register (here: <code class="language-plaintext highlighter-rouge">w.iopcen()</code>).
This in turn has a function <code class="language-plaintext highlighter-rouge">set_bit</code>, which is used to set the bit in that hardware register and thus enabling the clock for the GPIO C bank.</p>

<p>The same applies for the <em>CRH</em> register, which is  used to configure the pin as an output pin at highest speed (<em>MODE</em>=0b11 and <em>CNF</em>=00).
The <code class="language-plaintext highlighter-rouge">bits</code> function of the <code class="language-plaintext highlighter-rouge">mode13</code> and <code class="language-plaintext highlighter-rouge">cnf13</code> bit field is unsafe, but I can’t tell you why.
Therefore, we have to mark the functions inside the closure as unsafe as well.</p>

<p><strong>Hint:</strong>
To get a documentation of the stm32f1 crate, generate it yourself with <code class="language-plaintext highlighter-rouge">cargo doc --open</code></p>

<p>In the infinite loop, we write to the <em>Port bit set/reset register (BSRR)</em> and the <em>Port bit reset register (BRR)</em> to set pin C13 to high respectively low.</p>

<p>The <a href="https://docs.rs/cortex-m/0.5.8/cortex_m/asm/fn.delay.html"><code class="language-plaintext highlighter-rouge">delay</code> function</a> <em>busy waits</em> for at least the given number of cycles. The number here was just randomly taken and represents around 250ms.
The better approach would be to utilize the onboard timers, but for this simple example, <code class="language-plaintext highlighter-rouge">delay</code> is sufficient.</p>

<h2 id="creating-the-binary">Creating the Binary</h2>
<p>For the next steps, we need an ARM toolchain installed on our system.
On Linux Mint (Ubuntu), this can be done with:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo </span>apt <span class="nb">install </span>binutils-arm-none-eabi
</code></pre></div></div>
<p><em>Update:</em> I use the toolchain from the Ubuntu package manager. The toolchain which is installed with the thumbv7m target can also be <a href="https://github.com/rust-embedded/cargo-binutils">invoked using Cargo</a> (I’ll try this next time).</p>

<h4 id="optional-disassembly"><em>Optional:</em> Disassembly</h4>
<p>We can now view the disassembly of the <em>elf</em> file:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> arm-none-eabi-objdump <span class="nt">--disassemble</span> <span class="se">\</span>
  target/thumbv7m-none-eabi/release/stm32_blink | less
</code></pre></div></div>
<p><br /></p>

<p>So far, we only operated with the <em>elf</em> file.
This format does not only contain the binary code but also some headers and stuff (see <a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format">Wikipedia</a>).
This is useful, when another software like an operating system or bootloader launches the file.
However, to run the program bare-metal we don’t need an <em>elf</em> file, but rather a <em>bin</em> file.
This is an image of the software which can be written byte-by-byte into the memory of the microcontroller.</p>

<p>An <em>elf</em> file can be converted into a <em>bin</em> file with <code class="language-plaintext highlighter-rouge">objcopy</code>.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> arm-none-eabi-objcopy <span class="nt">-O</span> binary <span class="se">\</span>
  target/thumbv7m-none-eabi/release/stm32_blink stm32_blink.bin
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">stm32_blink.bin</code> is now ready to flash.</p>

<h2 id="flashing-the-binary">Flashing the Binary</h2>

<p>There are multiple ways to program the board, for example using the UART, using a USB bootloader (like the Arduino has), or using a programmer like an ST-Link.
I’m using a ST-Link-V2 clone, which are almost as cheap as the Pill itself (<a href="https://www.aliexpress.com/item/1PCS-ST-LINK-Stlink-ST-Link-V2-Mini-STM8-STM32-Simulator-Download-Programmer-Programming-With-Cover/32920031233.html">AliExpress</a>).
You can even <a href="https://medium.com/@paramaggarwal/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c">flash one yourself</a> out of a BluePill/BlackPill.</p>

<p>Connect the 4 pins <em>SWDIO</em>, <em>GND</em>, <em>SWCLK</em>, and <em>3.3V</em> on the programmer with the corresponding pins on the BluePill and plug it into your PC.
<strong>Important:</strong> Disconnect any other power supply from the board, otherwise you can damage the board or in the worst case your PC.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005543_edit.jpg">
      <img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005543_edit_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Connection between the ST-Link and the BluePill
      
    </figcaption>
  

</figure>

<p>There are other ways to program the board with the ST-Link, such as <a href="https://github.com/rogerclarkmelbourne/Arduino_STM32/wiki/Programming-an-STM32F103XXX-with-a-generic-%22ST-Link-V2%22-programmer-from-Linux">openocd</a>,
but I will use the <a href="https://github.com/texane/stlink">open-source stlink</a> tools.
Unfortunately, this software is not available via the apt package manager, therefore we have to <a href="https://github.com/texane/stlink/blob/master/doc/compiling.md">compile it from source</a>.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># in a directory of your choice:</span>
<span class="o">&gt;</span> <span class="nb">sudo </span>apt <span class="nb">install </span>libusb-1.0 libusb-1.0-0-dev
<span class="o">&gt;</span> git clone https://github.com/texane/stlink
<span class="o">&gt;</span> <span class="nb">cd </span>stlink
<span class="o">&gt;</span> make all
</code></pre></div></div>

<p>This creates the executables <code class="language-plaintext highlighter-rouge">st-flash</code> and <code class="language-plaintext highlighter-rouge">st-info</code>, which reside now in <code class="language-plaintext highlighter-rouge">build/Release/</code>.
There are different approaches to “install” these executables.
I will now copy them into a system folder.
However, it may be desirable to only copy them into a folder in your home directory which is added to <code class="language-plaintext highlighter-rouge">$PATH</code> or simply prefix all commands with the full path to the executable instead.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo cp </span>build/Release/st-<span class="o">{</span>flash,info<span class="o">}</span> <span class="se">\</span>
  build/Release/src/gdbserver/st-util /usr/local/bin
<span class="o">&gt;</span> which st-info
/usr/local/bin/st-info
</code></pre></div></div>

<h4 id="optional-udev-rules"><em>Optional:</em> Udev Rules</h4>
<p>To access the ST-Link without root permissions, copy the udev rules from the source code to <code class="language-plaintext highlighter-rouge">/etc/udev/rules.d/</code></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo cp </span>etc/udev/rules.d/<span class="k">*</span>.rules /etc/udev/rules.d
<span class="o">&gt;</span> <span class="nb">sudo </span>udevadm control <span class="nt">--reload-rules</span> <span class="o">&amp;&amp;</span> udevadm trigger
</code></pre></div></div>

<p><br />
We can verify the connection:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> st-info <span class="nt">--descr</span>
F1 Medium-density device
</code></pre></div></div>

<h4 id="optional-erase-existing-softwarebootloaders"><em>Optional:</em> Erase existing software/bootloaders:</h4>
<p>I had a stm32duino bootloader running on my BluePill, which lead to errors like <code class="language-plaintext highlighter-rouge">WARN common.c: unknown chip id! 0x5fa0004</code> upon flashing.
To fix that, I had to erase the chip. This is done by executing the following command <em>right after pressing the reset button</em> on the board:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> st-flash erase
</code></pre></div></div>
<p><br /></p>

<p>To flash the <em>bin</em> file, we execute:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> st-flash write stm32_blink.bin 0x8000000
st-flash 1.5.1-12-g30de1b3
2019-02-07T00:23:15 INFO common.c: Loading device parameters....
<span class="c">#[...]</span>
2019-02-07T00:23:15 INFO common.c: Flash written and verified!
  jolly good!
</code></pre></div></div>
<p><strong>Success!</strong></p>

<h3 id="summary-of-commands-to-build-and-flash-the-software">Summary of Commands to Build and Flash the Software:</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> cargo build <span class="nt">--release</span>
<span class="o">&gt;</span> arm-none-eabi-objcopy <span class="nt">-O</span> binary <span class="se">\</span>
  target/thumbv7m-none-eabi/release/stm32_blink stm32_blink.bin
<span class="o">&gt;</span> st-flash write stm32_blink.bin 0x8000000
</code></pre></div></div>

<p><strong>That’s it!</strong> Now the LED on the board is blinking!</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005642_edit.jpg">
      <img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005642_edit_small.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        Blinking LED on BluePill
      
    </figcaption>
  

</figure>]]></content><author><name>Jonathan Klimt</name></author><category term="Electronics" /><category term="Programming" /><category term="Embedded-Rust" /><category term="STM32" /><category term="STM32F103" /><category term="BluePill" /><category term="Rust" /><category term="LED" /><category term="Linux" /><category term="st-flash" /><category term="toolchain" /><summary type="html"><![CDATA[Using Rust to blink a LED on a STM32F103 aka "BluePill" on Linux Mint/Ubuntu]]></summary></entry><entry><title type="html">How to (not) Change the Keyboard of a MSI-GS30</title><link href="https://jonathanklimt.de/electronics/msi-keyboard/" rel="alternate" type="text/html" title="How to (not) Change the Keyboard of a MSI-GS30" /><published>2019-01-11T01:00:00+01:00</published><updated>2025-01-26T11:34:59+01:00</updated><id>https://jonathanklimt.de/electronics/msi-keyboard</id><content type="html" xml:base="https://jonathanklimt.de/electronics/msi-keyboard/"><![CDATA[<p>I use the US-international keyboard layout since a few years now, as the German layout sucks when it comes to programming.
But my laptop (MSI-GS30) still has a German layout.
Most of the time, I use the laptop inside a docking station, but as I was traveling the last few weeks I decided to replace the keyboard with a US-version one.</p>

<p>On AliExpress I found <a href="https://www.aliexpress.com/item/New-notebook-keyboard-for-MSI-GS40-GS32-GS30-GS43VR-6QE-233RU-6RE-007RU-RUSSIAN-SPANISH-Thailand/32912704270.html">a replacement part</a> for around 50€.
It arrived soon (about 1 week) and after I payed additional 10€ as import VAT, so the total costs were about 65€.</p>

<figure class="custom">
  <div class="single">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-46-43_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-46-43_small_edit.jpg" />
    </a>
  

  

  

  </div>

  
    <figcaption>
      
        The new keyboard
      
    </figcaption>
  

</figure>

<h3 id="step-1---open-the-backside-of-the-laptop">Step 1 - Open the Backside of the Laptop</h3>

<p>The first step was to open the laptop.</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-46-34_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-46-34_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-48-21_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-48-21_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Opening the laptop
      
    </figcaption>
  

</figure>

<h3 id="step-2---disassembly-of-the-laptop">Step 2 - Disassembly of the Laptop</h3>

<p>Then I removed the battery and the fan to remove the mainboard.
Unfortunately, two screws of the mainboard are located below the CPU’s heatpipes.
So I had to remove the cooler as well.</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-58-00_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-58-00_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-59-51_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13-59-51_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Disassembly of the laptop
      
    </figcaption>
  

</figure>

<h3 id="step-3---realize-that-you-fucked-up-and-revert-everything">Step 3 - Realize that you fucked up and revert everything</h3>

<p>At this point I found out, that you can’t remove the keyboard because it is riveted to the case.
Great! So I had do reassemble the Laptop.
Removing the CPU-cooler was totally unnecessary. I applied some more heat-conducting paste to the cooler, but I only had the cheap one at home.
This works, but I ordered some better one to ensure my CPU stays cool (which is a major issue with this laptop).</p>

<p>However, I can still change the layout by removing the key-caps and replacing them with the ones from the US-keyboard.</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T14-18-09_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T14-18-09_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T14-41-43_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T14-41-43_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Removing the key caps from the laptop
      
    </figcaption>
  

</figure>

<p>When you remove the caps you have to be very careful, the “scissors”-mechanism break extremely easy.
In the whole project I broke two of them. (But I’ve still got a whole keyboard full of replacement parts 🤷)</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T15-14-22_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T15-14-22_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T15-18-54_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T15-18-54_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Putting on the US layout keys and final product
      
    </figcaption>
  

</figure>

<p>Summary: I now have a nice US layout on my laptop, it has cost a lot of money for a rare use case and I have to apply new heat-conducting paste to my cooler 👍.</p>]]></content><author><name>Jonathan Klimt</name></author><category term="Electronics" /><category term="MSI-GS30" /><category term="keyboard" /><category term="repair" /><category term="layout" /><summary type="html"><![CDATA[Changing the physical keyboard of a MSI-GS30 from german to US]]></summary></entry><entry><title type="html">Gallery Safety Barrier</title><link href="https://jonathanklimt.de/woodwork/saftey-barrier/" rel="alternate" type="text/html" title="Gallery Safety Barrier" /><published>2018-11-15T01:00:00+01:00</published><updated>2024-12-01T18:16:50+01:00</updated><id>https://jonathanklimt.de/woodwork/saftey-barrier</id><content type="html" xml:base="https://jonathanklimt.de/woodwork/saftey-barrier/"><![CDATA[<p>A friend of mine, Laura, lives in a flat with a kind of gallery.
The landlord build in a kitchen and a bath in the formerly one large room and now there is some space above that, where the bed is located.
Unfortunately there is no ceiling or something similar and sleeping next to a 2.5m abyss is apparently a bit uncomfortable.
So Laura wanted to build some kind of safety barrier.
Laura designed a X shaped barrier with three “pillars” and so I made some rough sketches in Inkscape.
I also tried some other shapes, but I couldn’t convice Laura, so we agreed on a refinement of her design.
(It is simmilar to the first item in the sketches.)</p>

<figure class="custom">
  <div class="single">

  
    <img src="https://jonathanklimt.de/assets/images/safety-barrier/LauraGeländerFachwerk_Skizzen.svg" />
  

  

  

  </div>

  
    <figcaption>
      
        Sketches for the Barrier
      
    </figcaption>
  

</figure>

<p>Dirk came around and joined the handicraft group.
So we went out and bought the wood for it.</p>

<p>One long post (8cm * 8 cm I think), two boards for the cross-bars, and two for the upper and lower mount.</p>

<p>First, we cut the long post into the three pieces and laid everything out to draw the cut lines for the cross-bars.
(It was a bit of a challenge, that the floor of the gallery was not parallel to the ceiling).</p>
<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_172925_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_172925_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_174043_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_174043_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Laying out the pieces to the floor to draw the cut lines
      
    </figcaption>
  

</figure>

<p>We decided to cut slots into the posts and stick the cross bars into it.
This avoids the need for brackets and is probably more stable.
I cut the slots with my router, Dirk cleaned up the slots with a chisel and Laura sanded the posts.
I wanted to create a router jig, but I forgot the wood to do it, so we used scrap wood and clamps to create some guides for the router.
It was not super precise, but as the cross bars overlap the cuts a little bit, this was OK.</p>

<figure class="custom">
  <div class="third">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_152151_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_152151_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_133500_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_133500_small.jpg" />
      </a>
    
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_133504_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_133504_small.jpg" />
      </a>
    
  

  </div>

  
    <figcaption>
      
        Cutting the Slots for the Cross-Bars
      
    </figcaption>
  

</figure>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_185806_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_185806_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_185820_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_185820_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Cleaning and Testing the Slots for the Cross-Bars
      
    </figcaption>
  

</figure>

<p>Then we could assemble it and put it in place to validate it.</p>
<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_170842_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_170842_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_171027_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_171027_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Testing the whole thing
      
    </figcaption>
  

</figure>

<p>Laura doesn’t like the optics of light wood, so she painted it dark.
Once the paint was dry, we drilled the holes into the ceiling and the floor (which was super loud and one drill broke) and screwed it in.</p>

<figure class="custom">
  <div class="half">

  
    <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181018_213302_medium.jpg">
      <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181018_213302_small.jpg" />
    </a>
  

  
    
      <a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181018_203830_medium.jpg">
        <img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181018_203830_small.jpg" />
      </a>
    
  

  

  </div>

  
    <figcaption>
      
        Final Product
      
    </figcaption>
  

</figure>]]></content><author><name>Jonathan Klimt</name></author><category term="Woodwork" /><category term="router" /><category term="flat" /><category term="wood" /><summary type="html"><![CDATA[Building a safety barrier for the gallery in a friends student apartment]]></summary></entry></feed>