<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Anton A. Jerey</title><link>https://jerey.at/</link><description>Homepage</description><language>en</language><atom:link href="https://jerey.at/rss.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Fri, 24 Jul 2026 00:00:00 +0000</lastBuildDate><generator>jerey.at static build</generator><image><url>https://jerey.at/assets/me.webp</url><title>Anton A. Jerey</title><link>https://jerey.at/</link></image><item><title>Farewell Volvo, Hello Ioniq 5</title><link>https://jerey.at/posts/volvo-2-ioniq-5/</link><guid isPermaLink="true">https://jerey.at/posts/volvo-2-ioniq-5/</guid><pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate><description>How a failed inspection and a roadside breakdown pushed me from a 24-year-old Volvo to an electric Ioniq 5.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/Volvo2Ioniq/IoniqGarage2.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/Volvo2Ioniq/IoniqGarage2.webp" alt="Farewell Volvo, Hello Ioniq 5" /><p>I am driving back from lunch, enjoying every kilometer in <a href="https://jerey.at/posts/volvo/">my trusty Volvo</a>. It makes a new squeaky noise, but the mechanic said it should be fine. The repairs are piling up, though, and I can feel that our time together is coming to an end. What I do not know yet is that this drive back to the office is the last real run the Volvo and I will share.</p>
<h2 id="the-end">The End<a class="headerlink" href="#the-end" title="Permanent link">#</a></h2>
<p>In Austria there are mandatory inspections (§57a) for every car. Things like the brakes and the tires are checked.
In past years this was always an anxious time, since the car got so old that I did not always have the highest hopes of passing the inspection.
This year was no exception, but this time my gut feeling was different. I got used to the car making various sounds but there were new ones that were more persistent.
I brought it to a workshop, told them to first evaluate if it is worth it and then do the inspection.
Well there were several parts causing problems. Passing the inspection would have cost 2,000 €, but it was certain that the inspection next year would again be expensive. Also the winter tires were at the end of their life, so that was another cost factor.
I decided that our days together were done. It was surprisingly sad since the car had been a trusty companion for 13 years.
But at the same time: I want a reliable and safe car for my family and me.</p>
<p><img alt="Volvo Dial" src="https://jerey.at/posts/assets/personal/Volvo2Ioniq/VolvoTacho.webp" loading="lazy" decoding="async" width="1084" height="1042"></p>
<p>The good part was that I got the news about four months before the car would become illegal for the road. That is plenty of time to search for a car.
So far I had only owned two cars, and both had been found and recommended to me by others.</p>
<p>I had begun the search and already made appointments for a test drive when it happened: After lunch with a friend I wanted to get back to the office and broke down in the middle of the road. Roadside assistance took one hour to realize they had the wrong location in their system. And then another hour since there was a higher priority incident. In the end, both the car and I were brought home and the new car search became way more urgent.</p>
<p><img alt="Volvo Back Home" src="https://jerey.at/posts/assets/personal/Volvo2Ioniq/VolvoFarewell2.webp" loading="lazy" decoding="async" width="1275" height="865"></p>
<h2 id="the-search">The Search<a class="headerlink" href="#the-search" title="Permanent link">#</a></h2>
<p>I quickly realized that pre-owned cars are surprisingly expensive nowadays.
Having owned an old car, I also had some hard rules for the next one: Less than 100,000 km on the clock and less than eight years old.
Also it would have to fit my family including the stroller while doing so without much hassle.
Of course you can disassemble the stroller quite a bit, but I do not want to do that every time.</p>
<h3 id="the-problems">The Problems<a class="headerlink" href="#the-problems" title="Permanent link">#</a></h3>
<p>The problem with used cars is figuring out the exact model and feature set.
Apart from the model name, car manufacturers often have specific version names. This part is still somewhat easy to figure out online as this is often mentioned.
The problem comes with facelifts. Some car models have a light facelift and then a big facelift. Knowing which car is offered often does not reveal that.
You have to rely on the research and the model year to get the right version.
This also brings up the next problem: How do you compare the same car model twice when the features differ a bit? Comparing the distance the cars have covered and their age is easy. But how much is a tow hitch worth?
Or: Can this specific model in this specific edition in this specific facelift of this specific construction year be fitted with a tow hitch?
Those are the tough questions to answer.</p>
<h3 id="the-fun">The Fun<a class="headerlink" href="#the-fun" title="Permanent link">#</a></h3>
<p>I enjoyed the research itself: writing down information, watching tons of YouTube videos and learning a lot along the way. Seeing how far features had moved on from a 24-year-old car was fascinating, and for once I got to choose exactly which ones my next car had to have.
Apart from the age and mileage, I had a few features in mind. A proper phone integration, for one: on the Volvo it had taken me ages to retrofit something properly, and even then it was far from perfect. Adaptive cruise control was another. And that was the quiet pleasure of the whole search - even buying used, I could write down exactly what the car had to have and then go looking for it.</p>
<h2 id="the-cars">The Cars<a class="headerlink" href="#the-cars" title="Permanent link">#</a></h2>
<h3 id="volvo">Volvo<a class="headerlink" href="#volvo" title="Permanent link">#</a></h3>
<p>The first thought was an obvious one: Another Volvo.
My first Volvo had lasted for 24 years - how could this one not be obvious?
Well in my family many drive Volvos and especially the newer versions. But they have had a fair share of troubles - often software related.
Another major issue is the Volvo prices, especially for the higher-tier ones (XC60, V90, V90XC, XC90).
So to find one reasonably priced, I would have to drop one of the hard requirements: Age and/or mileage.</p>
<h3 id="skoda-superb">Skoda Superb<a class="headerlink" href="#skoda-superb" title="Permanent link">#</a></h3>
<p>Another car I thought was fantastic is the Skoda Superb.
From the reports I read about it online, it pretty much is like the V70: Tons of space, comfortable to drive and fairly fuel efficient. Looking at adults sitting in the back row was shocking: So much leg room.
It also has some cool gimmicks, like an umbrella stored in the driver's door.
I found out that the third generation of this model received a facelift in 07/2019.
That was the model I was looking for.
Apparently many people were after this kind of car. I was almost shocked by the prices for the given mileage.
This is also when I realized that the used market in Germany was much cheaper than in Austria (~20%). But importing the car to Austria would again result in extra cost (temporary insurance, import fees, ...).</p>
<h3 id="skoda-octavia">Skoda Octavia<a class="headerlink" href="#skoda-octavia" title="Permanent link">#</a></h3>
<p>In parallel to the Superb, I also checked the best-selling car in Austria in 2025: The Skoda Octavia. It is the slightly smaller version of the Superb.
Here there was a facelift in 2021. But I quickly realized that it wasn't much cheaper than the Superb. Since I considered the Superb to be more "premium", I then dismissed the Octavia.</p>
<h3 id="hyundai-ioniq-5">Hyundai Ioniq 5<a class="headerlink" href="#hyundai-ioniq-5" title="Permanent link">#</a></h3>
<p>A car I had always found awesome-looking is the Hyundai Ioniq 5.</p>
<p><img alt="Hyundai Ioniq 5" src="https://jerey.at/posts/assets/personal/Volvo2Ioniq/IoniqFront.webp" loading="lazy" decoding="async" width="1245" height="933"></p>
<p>A friend of mine has had an electric car for several years and is super satisfied with it.
Usually when you tell someone you are considering buying an electric car, you will hear all the horror stories about it. But often those people do not even have experience with these cars.
So I researched it thoroughly. At the start I did not even know what counts as normal consumption for an electric motor; for a diesel I knew 5 l/100 km was a good value.
One special horror story is the "broken battery". But it became quickly obvious that you can generate a battery certificate stating the state of health of the car down to each cell.
Apart from that, electric cars are less maintenance-heavy than combustion engines.
That left the classic objection - low range paired with long charging times - as the one question that would not go away. I will come back to it where it belongs: the decision.</p>
<p>With the horror stories out of the way, I decided to look into the car properly.
This is where I ran into a problem: Invest 1,000 € more and you get this much more. And another 1,000 € more and you get that feature.
I also found out that, apart from the various editions (Basic, Uniq, ...), it also had the facelift issue mentioned before: There was a smaller facelift for the 2023 models and another one even later.
So I checked prices for the 2023 version, since the long range version had 77.4 kWh battery capacity instead of 72.6 kWh.
This is when I found a great deal in Germany. Here the import was easier, since cars newer than three years fell into a category where no inspection (§57a) was needed first. Also, since it was electric and used there were no real fees. The only necessary part was adding it to a general database so the insurance companies in Austria could insure it. That was around 186 €.
The same car in the same version but pre-facelift would have cost 30% more in Austria.
I drove to Germany with my father to have a look at it and give it a test drive.
The appointment was coincidentally the day after my Volvo broke down.
It is always advisable to bring along another person to check out a car. My grandfather was a mechanic, so my father learned a lot from him.
And another person doesn't have this "Oh this might be my new car" gut feeling.
So great questions came from my father: Can this car be retrofitted with a tow hitch? I hadn't considered this since my Volvo had one and I hardly ever used it.
A car this new also means I would think twice before loading house-construction materials into it, to avoid scuffing the interior. A trailer would solve that problem.</p>
<p>The first test drive was almost anticlimactic. I had done so much research on the car due to the price that there weren't many things to discover.
The only thing I discovered was the lack of some features. But this is when I realized that most YouTubers will review the highest tier of the car.
The car had no power outlet, no seat cooling and no electrically adjustable seats.
Nothing bad, but I did like the idea of those features.</p>
<h2 id="the-decision">The Decision<a class="headerlink" href="#the-decision" title="Permanent link">#</a></h2>
<p>With the Volvo now dead, finding a replacement suddenly gained real urgency. It had come down to two real contenders - the Superb and the Ioniq - and before I trusted my gut, I wanted to let my inner engineer loose and prove on paper that the car I was leaning towards would not be a reckless choice.</p>
<h3 id="the-two-contenders">The Two Contenders<a class="headerlink" href="#the-two-contenders" title="Permanent link">#</a></h3>
<p>To judge them fairly I also lined up my outgoing Volvo V70 D5 as a benchmark - <a href="https://jerey.at/posts/volvo/">the diesel estate that had served me for over a decade</a> and crossed 300,000 km. On paper the three could hardly be more different, which is exactly what made the choice interesting.</p>
<p>The Ioniq 5 was the base trim, but in its Long Range form. That means the bigger 77.4 kWh battery, rated at 481 km WLTP. The car uses a single rear motor with 228 hp and 350 Nm, and an 800V architecture. It can even power external devices through Vehicle-to-Load. The boot holds 527 liters, plus a 58 liter frunk up front.</p>
<p>The Superb was the 1.5 TSI ACT Combi in Ambition trim: a 150 hp petrol engine, a manual gearbox and front-wheel drive. Its trump card is space. The boot starts at 660 liters and opens up to 1,950 with the rear seats folded, in a car that still weighs about 365 kg less than the battery-laden Ioniq.</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th></th>
<th>Hyundai Ioniq 5</th>
<th>Skoda Superb</th>
<th>Volvo V70 (benchmark)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Trim</td>
<td>Base model, Long Range (77.4 kWh)</td>
<td>1.5 TSI ACT Ambition (estate)</td>
<td>V70 II D5 (estate)</td>
</tr>
<tr>
<td>Powertrain</td>
<td>Electric</td>
<td>Petrol</td>
<td>Diesel</td>
</tr>
<tr>
<td>Transmission</td>
<td>Single-speed automatic</td>
<td>6-speed manual</td>
<td>5-speed manual</td>
</tr>
<tr>
<td>Peak power</td>
<td>228 hp / 168 kW</td>
<td>150 hp / 110 kW</td>
<td>163 hp / 120 kW</td>
</tr>
<tr>
<td>Continuous power (30 min)</td>
<td>76 hp / 56 kW</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>0-100 km/h</td>
<td>7.4 s</td>
<td>8.8 s</td>
<td>9.8 s</td>
</tr>
<tr>
<td>Battery / fuel tank</td>
<td>77.4 kWh battery</td>
<td>66 l petrol</td>
<td>70 l diesel</td>
</tr>
<tr>
<td>Consumption</td>
<td>approx. 15.5 kWh/100 km</td>
<td>approx. 6.5 l/100 km</td>
<td>approx. 6 l/100 km</td>
</tr>
<tr>
<td>Range (long run)</td>
<td>approx. 480 km</td>
<td>approx. 1,000 km</td>
<td>approx. 1,150 km</td>
</tr>
<tr>
<td>Boot, seats up</td>
<td>527 l (+ 58 l frunk)</td>
<td>660 l</td>
<td>485 l</td>
</tr>
<tr>
<td>Boot, seats folded</td>
<td>1,587 l</td>
<td>1,950 l</td>
<td>1,641 l</td>
</tr>
<tr>
<td>Weight</td>
<td>approx. 1,960 kg</td>
<td>1,595 kg</td>
<td>1,596 kg</td>
</tr>
<tr>
<td>First registered</td>
<td>10/2023</td>
<td>07/2020</td>
<td>02/2002</td>
</tr>
<tr>
<td>Mileage</td>
<td>approx. 41,800 km</td>
<td>approx. 72,250 km</td>
<td>approx. 307,000 km</td>
</tr>
<tr>
<td>Previous owners</td>
<td>1</td>
<td>3</td>
<td>-</td>
</tr>
<tr>
<td>Warranty</td>
<td>Vehicle 5 yrs (to 2028), battery 8 yrs (to 2031 or 160,000 km) + 1 yr dealer</td>
<td>1 yr dealer + 21-day money-back</td>
<td>-</td>
</tr>
</tbody>
</table></div>
<blockquote>
<p>Measurement note: There is no single standard boot volume measurement covering all three: the Ioniq and Superb use the modern VDA method, while the 2002 Volvo predates it and is quoted in DIN (to the window line), so the seats-up row is only roughly comparable. The truly like-for-like row is the seats-folded maximum, all measured to the roof - and there the long Volvo (1,641 l) actually overtakes the Ioniq (1,587 l), with the Superb well ahead of both.</p>
</blockquote>
<h3 id="can-it-go-the-distance">Can it go the distance?<a class="headerlink" href="#can-it-go-the-distance" title="Permanent link">#</a></h3>
<p>The harshest row in that table is the range. On a long run the Ioniq manages around 480 km, where the Superb does about 1,000 km and the old Volvo even 1,150 km on a tank. Pair that with the fear of long charging times and you have the classic objection I had parked earlier - and on paper it looks damning. But most of it rests on the charging time, and this is where the 800V system quietly wins the argument. Charging from 10 to 80 percent takes around 18 minutes at a fast charger. A petrol stop is rarely the two-minute affair people picture either: I never refueled the diesel on the motorway, where fuel is the most expensive, so topping up meant leaving for a cheaper station off the motorway - real money saved, but time and a detour spent. Add that up and the gap to an 18-minute charge shrinks.</p>
<p>There is also the driver to think about. After a couple of hours on the motorway the Ioniq wants a charge, and a break does me good anyway. Even truck drivers are made to stop - by law they have to rest after four and a half hours behind the wheel. It is also just enough time for a coffee, which regular readers know I take <a href="https://jerey.at/posts/espresso-maker/">fairly seriously</a>. Far from lost time.</p>
<p>None of this is to pretend that topping up in three minutes and driving straight on would not be more convenient - it would. But the charging breaks are nowhere near the ordeal they are made out to be. And there is one thing the diesel never offered: because I charge at home, I set off every morning with a full battery. With the Volvo, a full tank always meant a detour to the petrol station first.</p>
<h3 id="what-it-costs-to-run">What it costs to run<a class="headerlink" href="#what-it-costs-to-run" title="Permanent link">#</a></h3>
<p>One detail genuinely surprised me. In Austria the motor-related insurance tax for an electric car is calculated from its continuous (30-minute) power, not the headline peak figure. The Ioniq's registration papers list only 56 kW / 76 hp of continuous power, even though it peaks at 168 kW / 228 hp. So the car that is far more powerful on paper gets taxed almost like a small one, while the 150 hp Superb is taxed on its full output plus a CO2 component. The more I dug into the running costs, the more the gap between these two narrowed.</p>
<p>To put numbers on it, I worked out the yearly running cost over 10,000 km, counting only the two things that differ cleanly: the Austrian motor tax and the energy. Insurance premiums depend on personal factors, so I left them out. The motor tax is hard to pin to the cent, but a fair estimate is around 320 €/year for the Ioniq (thanks to the 76 hp rule above) and around 700 €/year for the Superb. Over 10,000 km the Ioniq draws roughly 1,550 kWh, the Superb roughly 650 liters.</p>
<p>Realistically I always charge at home at about 0.15 €/kWh, so the Ioniq costs me around 550 €/year in tax and energy. The Superb would have run to about 1,675 €/year at 1.50 €/l and right around 2,000 €/year at 2.00 €/l. So the electric car saves me somewhere between 1,100 and 1,450 € a year on tax and energy alone.</p>
<h3 id="does-it-pay-off-over-time">Does it pay off over time?<a class="headerlink" href="#does-it-pay-off-over-time" title="Permanent link">#</a></h3>
<p>That yearly saving is real money, so the obvious question is whether it justifies paying more for the Ioniq in the first place. It cost me roughly 7,000 € more than the Superb. Plotting the cumulative cost of both over time shows where the lines cross:</p>
<figure class="chart">
<svg viewBox="0 0 760 440" role="img" aria-labelledby="cctitle" xmlns="http://www.w3.org/2000/svg" style="max-width:100%;height:auto;font-family:inherit">
<title id="cctitle">Cumulative cost of ownership: energy plus motor tax over time</title>
<line x1="70" y1="384.0" x2="656" y2="384.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="388.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">0k</text>
<line x1="70" y1="328.0" x2="656" y2="328.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="332.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">4k</text>
<line x1="70" y1="272.0" x2="656" y2="272.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="276.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">8k</text>
<line x1="70" y1="216.0" x2="656" y2="216.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="220.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">12k</text>
<line x1="70" y1="160.0" x2="656" y2="160.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="164.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">16k</text>
<line x1="70" y1="104.0" x2="656" y2="104.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="108.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">20k</text>
<line x1="70" y1="48.0" x2="656" y2="48.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="52.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">24k</text>
<line x1="70.0" y1="48" x2="70.0" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="70.0" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">0</text>
<line x1="167.7" y1="48" x2="167.7" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="167.7" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">2</text>
<line x1="265.3" y1="48" x2="265.3" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="265.3" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">4</text>
<line x1="363.0" y1="48" x2="363.0" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="363.0" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">6</text>
<line x1="460.7" y1="48" x2="460.7" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="460.7" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">8</text>
<line x1="558.3" y1="48" x2="558.3" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="558.3" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">10</text>
<line x1="656.0" y1="48" x2="656.0" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="656.0" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">12</text>
<rect x="306.4" y="48" width="68.4" height="336.0" fill="currentColor" fill-opacity="0.05"/>
<text x="340.5" y="42" fill="currentColor" fill-opacity="0.75" font-size="11" text-anchor="middle">break-even (home charging)</text>
<polygon points="70.0,286.0 656.0,193.1 656.0,76.0" fill="#2563eb" fill-opacity="0.15"/>
<polygon points="70.0,384.0 656.0,102.6 656.0,48.0" fill="#ea580c" fill-opacity="0.15"/>
<polyline points="70.0,286.0 656.0,193.1" fill="none" stroke="#1d4ed8" stroke-width="2.5"/>
<polyline points="70.0,286.0 656.0,154.1" fill="none" stroke="#3b82f6" stroke-width="2.5"/>
<polyline points="70.0,286.0 656.0,76.0" fill="none" stroke="#93c5fd" stroke-width="2.5"/>
<polyline points="70.0,384.0 656.0,102.6" fill="none" stroke="#c2410c" stroke-width="2.5"/>
<polyline points="70.0,384.0 656.0,75.2" fill="none" stroke="#f97316" stroke-width="2.5"/>
<polyline points="70.0,384.0 656.0,48.0" fill="none" stroke="#fdba74" stroke-width="2.5"/>
<polyline points="656.0,48.0 665.0,48.0" fill="none" stroke="#fdba74" stroke-width="1.5"/>
<text x="669.0" y="51.0" fill="currentColor" font-size="11">2.00 €/l</text>
<polyline points="656.0,75.2 665.0,75.2" fill="none" stroke="#f97316" stroke-width="1.5"/>
<text x="669.0" y="78.2" fill="currentColor" font-size="11">1.75 €/l</text>
<polyline points="656.0,76.0 665.0,88.2" fill="none" stroke="#93c5fd" stroke-width="1.5"/>
<text x="669.0" y="91.2" fill="currentColor" font-size="11">0.60 €/kWh</text>
<polyline points="656.0,102.6 665.0,102.6" fill="none" stroke="#c2410c" stroke-width="1.5"/>
<text x="669.0" y="105.6" fill="currentColor" font-size="11">1.50 €/l</text>
<polyline points="656.0,154.1 665.0,154.1" fill="none" stroke="#3b82f6" stroke-width="1.5"/>
<text x="669.0" y="157.1" fill="currentColor" font-size="11">0.30 €/kWh</text>
<polyline points="656.0,193.1 665.0,193.1" fill="none" stroke="#1d4ed8" stroke-width="1.5"/>
<text x="669.0" y="196.1" fill="currentColor" font-size="11">0.15 €/kWh</text>
<line x1="70" y1="48" x2="70" y2="384" stroke="currentColor" stroke-opacity="0.5"/>
<line x1="70" y1="384" x2="656" y2="384" stroke="currentColor" stroke-opacity="0.5"/>
<text x="363.0" y="426" fill="currentColor" font-size="13" text-anchor="middle">Years of ownership</text>
<text x="18" y="216.0" fill="currentColor" font-size="13" text-anchor="middle" transform="rotate(-90 18 216.0)">Cumulative cost (€)</text>
<rect x="84" y="62" width="26" height="10" fill="#2563eb" fill-opacity="0.25" stroke="#1d4ed8" stroke-width="2"/>
<text x="118" y="71" fill="currentColor" font-size="12">Ioniq 5  (electricity, +7,000 € upfront)</text>
<rect x="84" y="82" width="26" height="10" fill="#ea580c" fill-opacity="0.25" stroke="#c2410c" stroke-width="2"/>
<text x="118" y="91" fill="currentColor" font-size="12">Superb  (petrol)</text>
</svg>
<figcaption>Cumulative cost of energy and motor tax over time, at 10,000 km a year, including the Ioniq's 7,000 € purchase premium. Each band spans the cheapest to the dearest energy price; the Superb sits higher and climbs steeper because fuel is the larger cost. Charging at home pays the premium back in about five to six years, while public-only charging barely breaks even within twelve.</figcaption>
</figure>

<p>So on energy and tax alone the Ioniq overtakes the Superb after roughly five to six years, and every year beyond that is pure saving.</p>
<p>Then there is maintenance, which I had underestimated. An electric car has no oil changes, no spark plugs, no timing belt and no clutch, and because it brakes mostly by recuperation the pads and discs last far longer. Consumer Reports puts EV maintenance at about half that of a comparable petrol car, roughly 4,600 USD saved over a car's life. Counted conservatively at a few hundred euros a year, that pulls the break-even point forward to somewhere around four to five years.</p>
<p>Is that on its own a reason to spend 7,000 € more? Honestly, only just. Over a short ownership it would never pay back, and the sum ignores depreciation and the small risk of an expensive EV repair. But I usually keep my cars for many years - I had the Volvo from the end of 2013 until early 2026 - and on a ten-year horizon the Ioniq saves something like 11,000 to 14,500 € in tax and energy, well past the 7,000 € premium, with several thousand euros left over before maintenance even enters the picture. So the money was never why I bought it, but it is a solid argument that the extra spend was not just my heart talking.</p>
<p>None of this is solid ground, though. The calculation is useful for getting a feel for the numbers, but it is fragile. Electricity tariffs move around, and fuel prices change constantly - right as I was buying the Ioniq, oil prices jumped sharply when the conflict involving Iran escalated. A single year of cheap petrol or expensive electricity can tilt the whole picture. So I treated the figures as a sanity check, not a promise.</p>
<p>All of that assumed my own fairly modest 10,000 km a year. The purchase premium and the motor tax do not care how much you drive, but the energy and fuel costs scale directly with distance - so a higher-mileage driver reaches the crossover much sooner:</p>
<figure class="chart">
<svg viewBox="0 0 760 440" role="img" aria-labelledby="cctitle20k" xmlns="http://www.w3.org/2000/svg" style="max-width:100%;height:auto;font-family:inherit">
<title id="cctitle20k">Cumulative cost of ownership at 20,000 km a year: energy plus motor tax over time</title>
<line x1="70" y1="384.0" x2="656" y2="384.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="388.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">0k</text>
<line x1="70" y1="316.8" x2="656" y2="316.8" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="320.8" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">8k</text>
<line x1="70" y1="249.6" x2="656" y2="249.6" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="253.6" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">16k</text>
<line x1="70" y1="182.4" x2="656" y2="182.4" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="186.4" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">24k</text>
<line x1="70" y1="115.2" x2="656" y2="115.2" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="119.2" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">32k</text>
<line x1="70" y1="48.0" x2="656" y2="48.0" stroke="currentColor" stroke-opacity="0.12"/>
<text x="62" y="52.0" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="end">40k</text>
<line x1="70.0" y1="48" x2="70.0" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="70.0" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">0</text>
<line x1="167.7" y1="48" x2="167.7" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="167.7" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">2</text>
<line x1="265.3" y1="48" x2="265.3" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="265.3" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">4</text>
<line x1="363.0" y1="48" x2="363.0" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="363.0" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">6</text>
<line x1="460.7" y1="48" x2="460.7" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="460.7" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">8</text>
<line x1="558.3" y1="48" x2="558.3" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="558.3" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">10</text>
<line x1="656.0" y1="48" x2="656.0" y2="384" stroke="currentColor" stroke-opacity="0.06"/>
<text x="656.0" y="404" fill="currentColor" fill-opacity="0.7" font-size="12" text-anchor="middle">12</text>
<rect x="205.9" y="48" width="47.4" height="336.0" fill="currentColor" fill-opacity="0.05"/>
<text x="229.6" y="42" fill="currentColor" fill-opacity="0.75" font-size="11" text-anchor="middle">break-even (home charging)</text>
<polygon points="70.0,325.2 656.0,246.1 656.0,105.5" fill="#2563eb" fill-opacity="0.15"/>
<polygon points="70.0,384.0 656.0,116.9 656.0,51.4" fill="#ea580c" fill-opacity="0.15"/>
<polyline points="70.0,325.2 656.0,246.1" fill="none" stroke="#1d4ed8" stroke-width="2.5"/>
<polyline points="70.0,325.2 656.0,199.2" fill="none" stroke="#3b82f6" stroke-width="2.5"/>
<polyline points="70.0,325.2 656.0,105.5" fill="none" stroke="#93c5fd" stroke-width="2.5"/>
<polyline points="70.0,384.0 656.0,116.9" fill="none" stroke="#c2410c" stroke-width="2.5"/>
<polyline points="70.0,384.0 656.0,84.1" fill="none" stroke="#f97316" stroke-width="2.5"/>
<polyline points="70.0,384.0 656.0,51.4" fill="none" stroke="#fdba74" stroke-width="2.5"/>
<polyline points="656.0,51.4 665.0,51.4" fill="none" stroke="#fdba74" stroke-width="1.5"/>
<text x="669.0" y="54.4" fill="currentColor" font-size="11">2.00 €/l</text>
<polyline points="656.0,84.1 665.0,84.1" fill="none" stroke="#f97316" stroke-width="1.5"/>
<text x="669.0" y="87.1" fill="currentColor" font-size="11">1.75 €/l</text>
<polyline points="656.0,105.5 665.0,100.5" fill="none" stroke="#93c5fd" stroke-width="1.5"/>
<text x="669.0" y="103.5" fill="currentColor" font-size="11">0.60 €/kWh</text>
<polyline points="656.0,116.9 665.0,121.9" fill="none" stroke="#c2410c" stroke-width="1.5"/>
<text x="669.0" y="124.9" fill="currentColor" font-size="11">1.50 €/l</text>
<polyline points="656.0,199.2 665.0,199.2" fill="none" stroke="#3b82f6" stroke-width="1.5"/>
<text x="669.0" y="202.2" fill="currentColor" font-size="11">0.30 €/kWh</text>
<polyline points="656.0,246.1 665.0,246.1" fill="none" stroke="#1d4ed8" stroke-width="1.5"/>
<text x="669.0" y="249.1" fill="currentColor" font-size="11">0.15 €/kWh</text>
<line x1="70" y1="48" x2="70" y2="384" stroke="currentColor" stroke-opacity="0.5"/>
<line x1="70" y1="384" x2="656" y2="384" stroke="currentColor" stroke-opacity="0.5"/>
<text x="363.0" y="426" fill="currentColor" font-size="13" text-anchor="middle">Years of ownership</text>
<text x="18" y="216.0" fill="currentColor" font-size="13" text-anchor="middle" transform="rotate(-90 18 216.0)">Cumulative cost (€)</text>
<rect x="84" y="62" width="26" height="10" fill="#2563eb" fill-opacity="0.25" stroke="#1d4ed8" stroke-width="2"/>
<text x="118" y="71" fill="currentColor" font-size="12">Ioniq 5  (electricity, +7,000 € upfront)</text>
<rect x="84" y="82" width="26" height="10" fill="#ea580c" fill-opacity="0.25" stroke="#c2410c" stroke-width="2"/>
<text x="118" y="91" fill="currentColor" font-size="12">Superb  (petrol)</text>
</svg>
<figcaption>The same comparison at 20,000 km a year. Charging at home pays the 7,000 € premium back in under four years - roughly half the time it takes at 10,000 km - and even public charging closes most of the gap by year twelve. The more you drive, the stronger the case for the Ioniq gets.</figcaption>
</figure>

<h3 id="why-i-chose-the-ioniq-5">Why I chose the Ioniq 5<a class="headerlink" href="#why-i-chose-the-ioniq-5" title="Permanent link">#</a></h3>
<p>On paper the Superb was the easier, safer choice. It was already registered in Austria, so there was no import to organize. It ran on technology I knew inside out, and it was in many ways a direct descendant of my Volvo: a big, comfortable estate that quietly does everything. Picking it would have been the low-risk, low-effort decision.</p>
<p>One thing nagged at me, though: three previous owners on a car only five years old. That is a lot of hands for such a young vehicle, and it is exactly the kind of detail that makes you wonder what each of them knew that I didn't. The more owners a car has had, the harder it gets to trace when any damage happened, or whether a fault was quietly passed down the line. On its own not a dealbreaker, but a yellow flag - especially next to the Ioniq's single, careful owner.</p>
<p>The Ioniq, on the other hand, was simply the car I wanted. It is the one I had admired since it first appeared, and the research had already taken the air out of the horror stories. The long warranties did the rest - the battery covered into 2031 and the car itself into 2028 - which made stepping into unfamiliar technology feel a lot less risky. On top of that came everything the numbers above already showed: charging breaks I can live with, lower running costs and less maintenance.</p>
<p>It also quietly ticked the list I had set out with. It sat comfortably inside my age and mileage limits, took the stroller without a fight, and came with the two features I had wished for: proper phone integration, which the Volvo only ever managed clumsily, and adaptive cruise control, which the old car never had at all. The things it lacked - a power outlet, seat cooling, electric seats - were all comforts I had never had in the first place.</p>
<p><img alt="Hyundai Ioniq 5 Thule" src="https://jerey.at/posts/assets/personal/Volvo2Ioniq/IoniqThule.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>So the Superb was the sensible echo of the car I was saying goodbye to, and the Ioniq was a deliberate step into something new. The running-cost math gave me permission to do it - the rest was simply wanting the car.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p><img alt="Hyundai Ioniq 5 Volvo V70" src="https://jerey.at/posts/assets/personal/Volvo2Ioniq/VolvoIoniq2.webp" loading="lazy" decoding="async" width="1129" height="513"></p>
<p>Swapping a 24-year-old Volvo for a brand-new kind of car was a bigger leap than the spreadsheets made it look. The numbers helped me find my footing, but in the end I bought the Ioniq because I wanted it, and let the running costs reassure me that it was not a reckless choice. A few months in, it has been exactly that: quiet, effortless, and cheap to run straight from the wall at home. The Volvo was an old friend I trusted without thinking; the Ioniq still has to earn that. So far, it is well on its way.</p>]]></content:encoded></item><item><title>Four Quick Python Projects</title><link>https://jerey.at/posts/quick-python-projects/</link><guid isPermaLink="true">https://jerey.at/posts/quick-python-projects/</guid><pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate><description>Four small Python tools I built to solve everyday problems - and the lessons that snuck in along the way.</description><media:thumbnail url="https://jerey.at/posts/assets/project/TravelWeather.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/project/TravelWeather.webp" alt="Four Quick Python Projects" /><p>Improving as a developer is important, but finding good ways to do it is not always obvious. At the same time, programmers have a habit of automating problems instead of just solving them. Somewhere between those two observations I found a sweet spot: small projects that solve a real, personal problem while sharpening the basics along the way.</p>
<p>The four projects below are nothing groundbreaking. They were never meant to be. But each of them taught me something I still use today, and writing them was more fun than doing the same task by hand would ever have been.</p>
<h2 id="travel-weather">Travel Weather<a class="headerlink" href="#travel-weather" title="Permanent link">#</a></h2>
<p>While packing for a multi-stop road trip, I wanted to know the weather forecast for every destination at once. Looking up city A, then city B, then city C is a five-minute job - boring, but doable. Of course, packing and clicking through forecasts back to back is no fun, so I wrote a small Python tool instead.</p>
<p>For the data I used <a href="https://openweathermap.org">OpenWeather</a>, which has a free tier and a clean API. The tool takes a list of locations and dates and prints a compact forecast for the whole trip.</p>
<div class="highlight"><pre><span></span><code>uv<span class="w"> </span>run<span class="w"> </span>python<span class="w"> </span>-m<span class="w"> </span>travel_weather<span class="w"> </span>--weather<span class="w"> </span><span class="se">\</span>
<span class="w">    </span><span class="s2">&quot;Salzburg:2026-04-28:+1&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w">    </span><span class="s2">&quot;Bridgetown:2026-04-29&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w">    </span><span class="s2">&quot;Kiruna:2026-04-29:+1&quot;</span>
</code></pre></div>

<p>would print something like</p>
<div class="highlight"><pre><span></span><code>Weather forecast for city: Salzburg
Start date: 2026-04-28
End date: 2026-04-29
2026-04-28: min 12.1°C, max 17.0°C, mostly ☁️ clouds
  12:00   16.9°C ☀️ clear sky
  15:00   17.0°C ☁️ scattered clouds
  18:00   13.7°C 🌦️ light rain
  21:00   12.1°C ☁️ overcast clouds
2026-04-29: min 4.4°C, max 14.2°C, mostly ☁️ clouds
  00:00    8.5°C 🌦️ light rain
  03:00    8.6°C 🌦️ light rain
  06:00    6.7°C 🌦️ light rain
  09:00    7.7°C ☁️ overcast clouds
  12:00   13.2°C ☁️ overcast clouds
  15:00   14.2°C ☁️ scattered clouds
  18:00    8.0°C ⛅️ few clouds
  21:00    4.4°C 🌙 clear sky
Summary for Salzburg: Min Temp: 4.4°C, Max Temp: 17.0°C Layer up - it&#39;s a grey one. Bundle up with a 🧣, 🧤 and a warm jacket.
-----------------------------------------------------------------------------------------------------------------------------

Weather forecast for city: Bridgetown
Start date: 2026-04-29
End date: 2026-04-29
2026-04-29: min 26.0°C, max 27.9°C, mostly ☁️ clouds
  00:00   26.5°C ☁️ scattered clouds
  03:00   26.3°C ☁️ broken clouds
  06:00   26.0°C ☁️ broken clouds
  09:00   26.0°C ☁️ overcast clouds
  12:00   26.9°C ☁️ broken clouds
  15:00   27.9°C ☀️ clear sky
  18:00   27.9°C ⛅️ few clouds
  21:00   27.3°C ☁️ overcast clouds
Summary for Bridgetown: Min Temp: 26.0°C, Max Temp: 27.9°C Layer up - it&#39;s a grey one. Pick lightweight, breathable layers.
---------------------------------------------------------------------------------------------------------------------------

Weather forecast for city: Kiruna
Start date: 2026-04-29
End date: 2026-04-30
2026-04-29: min -5.6°C, max -0.2°C, mostly ☁️ clouds
  00:00   -5.6°C ☁️ broken clouds
  03:00   -5.2°C ☁️ overcast clouds
  06:00   -3.4°C ☁️ overcast clouds
  09:00   -2.9°C ❄️ light snow
  12:00   -1.7°C ❄️ light snow
  15:00   -0.2°C ☁️ overcast clouds
  18:00   -0.5°C ☁️ overcast clouds
  21:00   -0.9°C ☁️ overcast clouds
2026-04-30: min -0.7°C, max 4.5°C, mostly ☁️ clouds
  00:00   -0.3°C ☁️ broken clouds
  03:00   -0.7°C ☁️ overcast clouds
  06:00    1.5°C ☁️ overcast clouds
  09:00    4.1°C ☁️ overcast clouds
  12:00    4.5°C ☁️ overcast clouds
  15:00    3.0°C ☁️ overcast clouds
  18:00    3.4°C ☁️ overcast clouds
  21:00    2.3°C ☁️ overcast clouds
Summary for Kiruna: Min Temp: -5.6°C, Max Temp: 4.5°C A layered outfit suits today&#39;s clouds. Pull out the warm jacket, 🧣 and 🧤.
--------------------------------------------------------------------------------------------------------------------------------

Summary for all cities:
Warmest City (Bridgetown): Dress in layers. Pick lightweight, breathable layers.
Driest City (Bridgetown): Dress in layers. Lightweight and breathable - you&#39;ll thank yourself.
Coldest City (Kiruna): Stick to layered clothing today. Bundle up with a 🧣, 🧤 and a warm jacket.
Wettest City (Salzburg): Dress in layers. Layers help - the temperature will swing.
--------------------------------------------------
</code></pre></div>

<p>What stuck with me from this one was working with HTTP APIs from Python for the first time.</p>
<blockquote>
<p>This was actually one of the first projects I used AI with. I thought it was a nice idea to add text for the clothing and of course AI added emojis.</p>
</blockquote>
<h2 id="meta-flight-search">Meta Flight Search<a class="headerlink" href="#meta-flight-search" title="Permanent link">#</a></h2>
<p>Finding cheap flights gets harder the more flexible you are. I often knew roughly when I wanted to travel but had several possible origin airports, no fixed destination and a flexible return date. Web-based flight search engines hit their limits quickly under that kind of input.</p>
<p>So I solved it the same way I solved Travel Weather: a Python tool, this time on top of the <a href="https://tequila.kiwi.com/">Kiwi Tequila</a> API. It takes a list of origin airports, a list of destinations, an acceptable travel window, a stopover limit and returns the cheapest combinations it can find.</p>
<p>It also taught me to think about API budgets. Two origins, two destinations, two start dates and two end dates already produce 16 calls. Adding a third destination jumps it to 24. That number grows fast - the relationship is plain enough:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span><span class="w"> </span><span class="nf">count_api_calls</span><span class="p">(</span>
    <span class="n">origins</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
    <span class="n">destinations</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
    <span class="n">start_dates</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
    <span class="n">return_dates</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">origins</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">destinations</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">start_dates</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">return_dates</span><span class="p">)</span>
</code></pre></div>

<p>One multiplication, four inputs - and each added option multiplies the cost rather than adding to it. That alone shaped every later flag: anything that lets the user narrow the search space earns its place.</p>
<p>I also tried to make the tool more approachable by wrapping it in a small web UI using <a href="https://github.com/pywebio/PyWebIO">PyWebIO</a>. Building a CLI that is both powerful and friendly is genuinely hard, and that experiment was a useful reminder of why most "quick" tools stay command-line only.</p>
<p>The real lesson here was about constraints. Without them, the cheapest flight is usually a 30-hour itinerary with three layovers - technically the answer to "find the cheapest flights", but not what I actually wanted. That is why the CLI options grew over time, with the stopover limit being the obvious example. I never used the tool as much as I had hoped, mainly because my travel volume dropped not long after - but designing around real-world constraints, rather than just the API's, has stayed with me.</p>
<h2 id="ticktick-to-joplin">TickTick to Joplin<a class="headerlink" href="#ticktick-to-joplin" title="Permanent link">#</a></h2>
<p>For years I have been using <a href="https://ticktick.com">TickTick</a> as my todo app. While looking into <a href="https://jerey.at/posts/synology-ds720/">self-hosting</a> more of my own tooling, I found <a href="https://github.com/laurent22/joplin">Joplin</a>, which doubles as a note-taking app and stores everything as plain Markdown. The same instinct that led me to replace my old <a href="https://jerey.at/posts/image-2-text/">Image to Text</a> tool with <a href="https://jerey.at/posts/paperless-ngx/">Paperless-ngx</a> was at play here: I liked the idea of owning my data in a portable format.</p>
<p>Joplin happily imports folders of generic Markdown but not TickTick's specific export format, so I wrote a small Python converter to bridge the two.</p>
<p>Most of the work was not the wiring between formats. It was the small ambushes hiding in the data. All-day reminders, for example, were exported as UTC midnight. After converting to Vienna time that lands in the small hours of the night, and a reminder firing then helps no one - so all-day items had to be pinned to a sensible hour instead:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">datetime</span><span class="w"> </span><span class="kn">import</span> <span class="n">time</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">zoneinfo</span><span class="w"> </span><span class="kn">import</span> <span class="n">ZoneInfo</span>

<span class="n">local</span> <span class="o">=</span> <span class="n">utc_dt</span><span class="o">.</span><span class="n">astimezone</span><span class="p">(</span><span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;Europe/Vienna&quot;</span><span class="p">))</span>

<span class="k">if</span> <span class="n">local</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">==</span> <span class="n">time</span><span class="p">(</span><span class="mi">0</span><span class="p">):</span>
    <span class="n">local</span> <span class="o">=</span> <span class="n">local</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">hour</span><span class="o">=</span><span class="mi">9</span><span class="p">)</span>
</code></pre></div>

<p>Every TickTick row had a small surprise like this - the kind of detail that never shows up in an export spec but breaks immediately the moment a user tries to live with the result.</p>
<p>The converter did its job, and the project taught me a lot about taking messy exported data, reshaping it and handing it to another tool in the form it expects. The migration itself, on the other hand, never really happened. I never set a hard cut-off date after which new tasks had to land in Joplin instead of TickTick, and without that deadline, the old habit won.</p>
<h2 id="person-event-statistics">Person Event Statistics<a class="headerlink" href="#person-event-statistics" title="Permanent link">#</a></h2>
<p>At some point I got curious about how the length of an ongoing phase of life compares to the rest of it. How much of my life have I spent studying? What share of it has been with my current employer? It is the kind of question you can answer on the back of an envelope, but I had recently been enjoying Python and decided to write something for it.</p>
<p>As usual, I went a little overboard - and had fun doing it. The tool reads events from a JSON file and visualizes them. It started out printing plain statistics, until I discovered Python's built-in <a href="https://docs.python.org/3/library/tkinter.html">tkinter</a> and added a small GUI on top. With that in place, adding new events from the UI and rendering them as a bar chart against total lifetime came naturally.</p>
<p>It is in the same spirit as my early <a href="https://jerey.at/posts/weatherstation/">Weatherstation</a> and <a href="https://jerey.at/posts/image-2-text/">Image to Text</a> projects: pick a small itch, scratch it in Python and let the side quests teach you something new.</p>
<p>Looking back, this one was built during a demanding stretch in my personal life, where a lot of things were out of my hands. Having a small, self-contained coding task with clear inputs and a clean endpoint turned out to be unexpectedly grounding. A quiet reminder that side projects can serve more than just curiosity.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>These projects are a small sample of things I have built that never made it anywhere public. None of them solved a problem I could not have solved by hand. All of them were more interesting because I solved them with code.</p>
<p>The pattern is almost always the same. I get a working prototype, then I keep adding features for fun, and somewhere in that second phase I end up learning the actual lesson:</p>
<ul>
<li>How to design a CLI that is comfortable to use.</li>
<li>Staying focused on the original problem instead of disappearing into the tooling around it.</li>
</ul>
<p>In the end, learning new things while solving an existing problem is what keeps me coming back. There is always another small itch worth scratching.</p>]]></content:encoded></item><item><title>Desk, Coffee, Sleep - Simple Automations</title><link>https://jerey.at/posts/ha-automations/</link><guid isPermaLink="true">https://jerey.at/posts/ha-automations/</guid><pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate><description>Desk lights, coffee-ready pings, and lower standby power with a few Home Assistant automations.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/HA_OfficeAutomations.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/HA_OfficeAutomations.webp" alt="Desk, Coffee, Sleep - Simple Automations" /><p>I walk into my home office - <strong>click</strong> - and the desk lights up.
Some time later - <strong>ping</strong> - my phone tells me the coffee machine is ready.
And when the workday is done, the desk - <strong>click</strong> - shuts down on its own.</p>
<p>Magic? Not quite. Just Home Assistant and a few power outlets.</p>
<h2 id="home-assistant-automations">Home Assistant Automations<a class="headerlink" href="#home-assistant-automations" title="Permanent link">#</a></h2>
<p>I have <a href="https://jerey.at/posts/synology-ds720/">written</a> about Home Assistant a few times. The part I like most is that it is a gigantic toolbox.
Similar to <a href="https://jerey.at/posts/paperless-ngx/">paperless-ngx</a>, it can solve all kinds of problems - no matter how tiny they might seem.</p>
<p>As I mentioned in my <a href="https://jerey.at/posts/paperless-ngx-mail/">post about mail automation</a>, I really do enjoy automating things.
Most of the time the upfront work takes longer than the time an automation will ever save.
But building it is the fun part.</p>
<!-- ![Office Automations](/posts/assets/personal/HA_OfficeAutomations.webp) -->

<p>Here is a small selection of automations I built in Home Assistant and genuinely enjoy having.</p>
<div class="admonition note">
<p class="admonition-title">NOTE</p>
<p>These are based on WiFi power outlets flashed with <a href="https://www.tasmota.info/">Tasmota</a>. I specifically bought ones that also report power usage, because that enables much nicer automations.</p>
</div>
<h3 id="power-outlet-automation">Power Outlet Automation<a class="headerlink" href="#power-outlet-automation" title="Permanent link">#</a></h3>
<p>My desk has two WiFi power outlets:</p>
<ul>
<li>One mainly for my personal computer</li>
<li>One for "everything else" (display, speakers, chargers, ...)</li>
</ul>
<p>I also ended up with a Philips Hue dimmer switch from a bundle. In the Hue ecosystem it is meant to control lights (in my case: Hue Play Lightbars as indirect lighting).</p>
<p>But the nice part is that the dimmer switch can be remapped in Home Assistant. The Play Lightbars are Hue, the outlets are not - and Home Assistant happily ties those worlds together.</p>
<p>So one of the four buttons now controls the desk power via MQTT:</p>
<ul>
<li>Short press: turn the desk outlets on (safe default, avoids accidental shutdowns)</li>
<li>Long press: turn the desk outlets off</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Desk &amp; Computer Off</span>
<span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Switch off the Desk and Computer Switch</span>
<span class="nt">triggers</span><span class="p">:</span>
<span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">device_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">...</span>
<span class="w">    </span><span class="nt">domain</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">hue</span>
<span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">long_press</span>
<span class="w">    </span><span class="nt">subtype</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1</span>
<span class="w">    </span><span class="nt">unique_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">...</span>
<span class="w">    </span><span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">device</span>
<span class="nt">conditions</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[]</span>
<span class="nt">actions</span><span class="p">:</span>
<span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">turn_off</span>
<span class="w">    </span><span class="nt">device_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">...</span>
<span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">...</span>
<span class="w">    </span><span class="nt">domain</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">switch</span>
<span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">turn_off</span>
<span class="w">    </span><span class="nt">device_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">...</span>
<span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">...</span>
<span class="w">    </span><span class="nt">domain</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">switch</span>
<span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">single</span>
</code></pre></div>

<p>Once that worked, it was hard not to add a bit more.</p>
<p>With the Hue bundle I also got a motion sensor. Again: easy to integrate into Home Assistant. I use it for a simple "morning automation": on workdays, if there is motion between 6:00 and 9:00, the desk turns on.</p>
<p>The best part, though, is using power draw as a signal. My desk standby power is surprisingly high (around 40 W), so I prefer cutting power completely. If the desk outlet stays below a threshold (for example 45 W) for 15 minutes, Home Assistant switches it off automatically.</p>
<p><img alt="Desk Switch Auto Off" src="https://jerey.at/posts/assets/personal/HA_DeskSwitch.webp" loading="lazy" decoding="async" width="1280" height="445"></p>
<p>The end result is exactly what I wanted: I sit down and things wake up. When I am done, I just leave.</p>
<blockquote>
<p>Out of curiosity, I calculated a simple "what if" case: what would it cost if this outlet stayed on for a full year without the desk being actively used?
At an average standby draw of 40 W, that comes to 0.96 kWh per day, or roughly 350 kWh per year.
Assuming an electricity price of 0.30 € per kWh, that is about <strong>105 € per year</strong>.</p>
</blockquote>
<h3 id="coffee-machine-state">Coffee Machine State<a class="headerlink" href="#coffee-machine-state" title="Permanent link">#</a></h3>
<p>As mentioned in my post about <a href="https://jerey.at/posts/espresso-maker/">espresso makers</a>, I love good coffee, but I do not love waiting for an E61 machine to warm up.</p>
<p>The solution (again) was a WiFi power outlet. It publishes its state via MQTT and can also be controlled via MQTT. Whenever the outlet changes to "ON", Home Assistant starts a timer. When it expires:</p>
<ul>
<li>I get a push notification on my phone (via the Home Assistant app)</li>
<li>My pixel clock on the desk shows that the machine is ready</li>
</ul>
<p>I used to do this with iOS Shortcuts, but Home Assistant made it easier to send the same signal to multiple devices.</p>
<h3 id="nobody-home-or-awake-automation">Nobody Home (Or Awake) Automation<a class="headerlink" href="#nobody-home-or-awake-automation" title="Permanent link">#</a></h3>
<p>Another WiFi power outlet automation.</p>
<p>When I started with Home Assistant (in a rented apartment), I had no built-in smart home stuff. Power outlets were the easiest way to get results quickly - and they are still useful.</p>
<p>In this case I wanted to reduce standby consumption and also deal with one very specific problem: my TV sometimes turned itself on in the middle of the night. It also benefited from a regular reboot anyway. A smart outlet solved both.</p>
<p>The trigger is simple: when both my wife's phone and my phone leave the WiFi, I treat that as "nobody is home (or awake)". In that state, the TV definitely does not need power, so Home Assistant turns the outlet off. As soon as either phone appears in the network again, the outlet is automatically switched on again.</p>
<p>I did not apply this rule to the computer or coffee machine. The computer might be left on intentionally (updates, downloads, ...), and the coffee machine might be switched on while on the way home.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>Home automation is fun when it works.</p>
<p>I am still surprised by how much you can do with Home Assistant - mainly because you can make things fit your exact needs. You are not forced into whatever the vendor thought was a good idea.</p>
<p>Even tiny annoyances like "I have to keep holding a button to switch off power" are worth solving. Not because it is hard, but because it removes friction from daily life and even saves money.</p>
<p>One lesson that matters more than it should: keep a clean naming scheme and structure. Once you add a few devices, it gets confusing fast if names are inconsistent.</p>
<p>That is also why I set up Home Assistant from scratch after moving. My first setup started as a test and slowly turned into "production" without ever getting cleaned up.</p>
<p>For now, these automations work great. And judging by my track record, more will follow.</p>]]></content:encoded></item><item><title>From Inbox to Archive</title><link>https://jerey.at/posts/paperless-ngx-mail/</link><guid isPermaLink="true">https://jerey.at/posts/paperless-ngx-mail/</guid><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><description>Paperless-ngx incoming mail rules turned a repetitive PDF-filing task into a hands-off workflow.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/PaperlessMail.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/PaperlessMail.webp" alt="From Inbox to Archive" /><p>It's evening after a long day of work. A new email arrives with a PDF attachment — one of those documents you want to keep: save it somewhere safe and make sure it ends up in backups.</p>
<p>I used to do this manually: download the attachment, move it to the right folder on my NAS and then forget about it until I need it again.</p>
<p>Now that same email gets a blue tag in my mail client and the attachment is already filed away — on my NAS and backed up. Sorcery? Not quite. Just open-source.</p>
<h2 id="automate-regular-tasks">Automate regular tasks<a class="headerlink" href="#automate-regular-tasks" title="Permanent link">#</a></h2>
<p>I like automating repetitive tasks. For example: my desk power strip switches off automatically once the power draw stays below a threshold for a while. And the TV power is cut when two specific phones leave the Wi‑Fi — a simple "nobody's home (or awake)" signal.</p>
<div class="admonition note">
<p class="admonition-title">NOTE</p>
<p>The mentioned examples here are all done with the help of <a href="https://github.com/home-assistant">Home Assistant</a>.
See also my <a href="https://jerey.at/posts/self-hosted-services/">self hosted services</a>.</p>
</div>
<p>This kind of "laziness" is a good excuse to learn new tools — or, in this case, to use an existing tool more effectively. When I received the same type of email again, I finally looked for a way to store those attachments automatically on my NAS (and therefore inside my normal backup routine).</p>
<h2 id="paperless-ngx-once-again">Paperless-ngx once again<a class="headerlink" href="#paperless-ngx-once-again" title="Permanent link">#</a></h2>
<p>My first instinct was to solve this in the mail client.</p>
<p>But I don't use the same mail client across all devices and client-side automation tends to be fragile: one device ends up being "the automation machine", it needs NAS access, it has to be online at the right time and so on.</p>
<p>I also checked what my mail provider could do. That didn't get me to a clean "file attachments to the NAS" workflow either — especially not one that integrates with my storage and backups.</p>
<p>Then I remembered that <a href="https://docs.paperless-ngx.com/usage/#incoming-mail">Paperless-ngx</a> has an "incoming mail" feature. Since Paperless already lives next to my storage and backups (and is part of my self-hosted stack), this was exactly the missing piece.</p>
<p>After reading the docs and a few guides (e.g. <a href="https://digital-cleaning.de/index.php/paperless-ngx-teil-11-mail-abruf-mit-vielen-extras/">this one</a> in German), it was clear: I'm not the first person with this problem and Paperless-ngx has a solid solution.</p>
<h3 id="setup">Setup<a class="headerlink" href="#setup" title="Permanent link">#</a></h3>
<p>The setup is well documented and boils down to two concepts:</p>
<ol>
<li>Defining an e-mail account in paperless-ngx. This is necessary once per e-mail account.</li>
<li>Defining e-mail rules for the e-mail account. This is necessary once per process you want to define.</li>
</ol>
<p>Step 1 is basically "configure IMAP": server address, port, credentials. Similar to setting up a mail client on your device.</p>
<p>Step 2 is where the magic happens: you define rules that decide which emails should be processed. I filtered by:</p>
<ul>
<li>mailbox (inbox)</li>
<li>sender</li>
<li>subject</li>
<li>age</li>
</ul>
<p>Then I configured the rule to consume only the attachment (not the email body). When Paperless-ngx imports the attachment it can automatically set metadata like tags and correspondent.</p>
<p>Finally, I wanted a quick confirmation in my mail client that an email was handled correctly — especially while testing. Paperless-ngx can tag the processed email with a custom IMAP keyword and also ignore emails that already carry that keyword.</p>
<p>In my case, I used the keyword <code>apple:blue</code>, which shows up as a blue tag in Apple Mail.</p>
<p><img alt="Apple Mail Blue Flag" src="https://jerey.at/posts/assets/personal/PaperlessMailSetting.webp" loading="lazy" decoding="async" width="1108" height="354"></p>
<p>And that's it: emails arrive, attachments get consumed and the originals are marked as processed.</p>
<p><img alt="Apple Mail Blue Flag" src="https://jerey.at/posts/assets/personal/PaperlessMailFlag.webp" loading="lazy" decoding="async" width="102" height="80"></p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>This was a good reminder to look at the tools I already use before building something new: Paperless-ngx already solved the hard parts.</p>
<p>Manually moving a PDF to a network share isn't difficult — it's just the kind of task that's easy to postpone or forget. Automating it removes that mental overhead and makes the process consistent.</p>
<p>Paperless-ngx keeps on impressing me. And judging from my track record, this won't be the last time I'll find another feature that quietly solves an everyday annoyance.</p>]]></content:encoded></item><item><title>Keyboards &amp; Words Per Minute</title><link>https://jerey.at/posts/keyboard/</link><guid isPermaLink="true">https://jerey.at/posts/keyboard/</guid><pubDate>Fri, 19 Dec 2025 00:00:00 +0000</pubDate><description>How a colleague's mechanical keyboard sent me down a rabbit hole of switches, layers, and typing tests</description><media:thumbnail url="https://jerey.at/posts/assets/personal/keyboard/SoflePCB.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/keyboard/SoflePCB.webp" alt="Keyboards &amp; Words Per Minute" /><p>The office was silent. I had just poured myself a fresh coffee, ready to dive into the morning's work. My trusty Logitech MK710 sat in front of me – wireless, comfortable, leagues better than those terrible standard keyboards that come with most office computers. Perfect for a productive day.</p>
<p>Then I heard it: <em>clack-clack-clack-clack</em>.</p>
<p>It wasn't a typewriter, though it sure sounded like one. A colleague had started using a mechanical keyboard in the office. I found myself oddly captivated by the rhythmic clicking. After our coffee break conversation about his new keyboard, I figured I'd do what any reasonable person would do: take a quick look online to see what the fuss was about.</p>
<p>What followed was anything but quick. I had stumbled into a rabbit hole I didn't know existed.</p>
<h2 id="the-gateway-drug">The Gateway Drug<a class="headerlink" href="#the-gateway-drug" title="Permanent link">#</a></h2>
<p>My first purchase was a Corsair K70 RGB Mk.2 with Cherry MX Blue switches. Looking back, it was the perfect entry point – feature-rich without being overwhelming. The dedicated media controls and volume wheel quickly became indispensable. There's something satisfying about reaching up to skip a song without <code>Alt</code>+<code>Tab</code> out of your work. The metal top plate made it feel substantial, valuable even.</p>
<p><img alt="Corsair K70 RGB Mk.2" src="https://jerey.at/posts/assets/personal/keyboard/Corsair.webp" loading="lazy" decoding="async" width="1280" height="677"></p>
<p>The only thing I disliked? That distinctly "gamer" aesthetic. Not everything needs RGB lighting that cycles through the entire color spectrum. But it did its job: it got me interested. More than interested, actually.</p>
<p>I started browsing Reddit. Then forums. Then YouTube. I discovered that keyboards weren't just tools – they were a hobby. People discussed switch types the way others debate coffee beans. They talked about "actuation force" and "travel distance". Some folks even lubed their switches by hand, disassembling each one and carefully applying lubricant with a tiny paintbrush. </p>
<p>This was deeper than I'd imagined.</p>
<h2 id="building-my-own">Building My Own<a class="headerlink" href="#building-my-own" title="Permanent link">#</a></h2>
<p>Somewhere in my Reddit browsing, I found the <a href="https://github.com/josefadamcik/SofleKeyboard">SofleKeyboard</a>. A split keyboard. Column-staggered layout. OLED displays. Rotary encoders. And here's the kicker: you had to build it yourself. It wasn't available for purchase.</p>
<p>I was sold immediately.</p>
<p>A friend shared my newfound enthusiasm, so we embarked on this together. We ordered PCBs, switches, screws, and every little electrical component from the bill of materials. It was expensive – the PCBs alone cost more than I'd expected. But I was fascinated by something else: this entire keyboard design, including the build guide and parts list, was freely available online. Someone had designed this, built it, perfected it, and then just... shared it. For free. So others could recreate it.</p>
<p><img alt="Sofle" src="https://jerey.at/posts/assets/personal/keyboard/Sofle.webp" loading="lazy" decoding="async" width="1280" height="380"></p>
<p>The build was incredible. The finished product looked fantastic with its split design and those little OLED screens. But using it? That was a different story.</p>
<p>The column-staggered layout meant all the keys were arranged in neat columns rather than the traditional row-stagger we're used to. Your hands barely move once you learn it. The reduced key count (only 58 keys total) meant everything lived on layers – pressing a function key transformed the keyboard entirely. The K could be a K, or an arrow down, or an asterisk, depending on what else you pressed.</p>
<p><img alt="Sofle Done" src="https://jerey.at/posts/assets/personal/keyboard/SofleDone.webp" loading="lazy" decoding="async" width="1280" height="699"></p>
<p>It's brilliant in theory. With enough practice, your fingers never leave the home row. You can program custom macros. It's endlessly customizable.</p>
<p>But then came the problem: keyboard shortcuts. Try pressing <code>Ctrl+Shift+Alt+↓</code> in Visual Studio Code – a common shortcut for adding multiple cursors. On the Sofle, that becomes <code>Ctrl+Shift+Alt+↓</code> + the <code>Upper Layer</code> key. Five keys at once. Your hand contorts into positions that'd make a yoga instructor wince.</p>
<p>And there was another issue I hadn't considered: muscle memory. The Sofle requires you to use the same keyboard everywhere. Your brain adapts to those layers, to those key positions. Switch to a regular keyboard and suddenly you're fumbling like you're learning to type all over again. I discovered that I type the letter B with my right index finger – perfectly fine on a normal keyboard, but on the Sofle, B sits on the left half. My brain refused to cooperate.</p>
<p>The Sofle taught me something important: not every keyboard is meant for every use case. Sometimes the perfect keyboard on paper isn't perfect in practice.</p>
<h2 id="finding-the-sweet-spot">Finding the Sweet Spot<a class="headerlink" href="#finding-the-sweet-spot" title="Permanent link">#</a></h2>
<p>My next keyboard took longer to choose. I knew what I wanted now: hotswappable switches (no more desoldering), a more traditional layout, and crucially, ISO-DE support. I use German layout, and apparently, this hobby is heavily ANSI-focused. Finding ISO-DE keycaps became a recurring nightmare.</p>
<p>I nearly built an <a href="https://github.com/ebastler/isometria-75">Isometria 75</a> – another open-source design – but the creator mentioned ongoing issues with the board. I'd learned my lesson about jumping in too quickly.</p>
<p>Then the GMMK Pro was released. A 75% keyboard (meaning it has roughly 75% of a full keyboard's keys – goodbye numpad). ISO-DE layout. Hotswappable switches. A rotary encoder. It looked perfect.</p>
<p>It was also expensive. Very expensive. And that was before buying switches and keycaps.</p>
<p>I justified it the way I justify all my work equipment: compared to a carpenter or goldsmith, my initial investment is minimal. A desk, a chair, a monitor, peripherals – these are my tools. If I'm going to use something every single workday, I want to enjoy using it.</p>
<p>The GMMK Pro arrived, and it was everything I'd hoped for. That full metal housing felt premium. The layout was exactly what I needed – enough keys to stay productive, few enough to keep my hands from wandering. The journey seemed complete.</p>
<p>Then I started watching more YouTube channels about keyboards. Because of course I did.</p>
<h2 id="the-current-champion">The Current Champion<a class="headerlink" href="#the-current-champion" title="Permanent link">#</a></h2>
<p>The NEO65 changed the game again. A 65% layout (even more compact than the GMMK Pro, losing the function row entirely). But here's what made it special: it supports both ISO and ANSI layouts. It works wirelessly via USB dongle, has Bluetooth, and can be wired. And unlike most prebuilt boards, you can customize the firmware.</p>
<p>That last bit was crucial since I didn't want to have a macropad lying around on my desk.</p>
<p>It came with an array of foam and dampening materials – sound-tuning options I hadn't used before. After hours of tinkering, I had it exactly where I wanted it.</p>
<p>This is the keyboard I use daily now. It's genuinely the best typing experience I've had.</p>
<p><img alt="NeoAndGmmk" src="https://jerey.at/posts/assets/personal/keyboard/NeoAndGmmk.webp" loading="lazy" decoding="async" width="1280" height="725"></p>
<h2 id="the-collection-grows">The Collection Grows<a class="headerlink" href="#the-collection-grows" title="Permanent link">#</a></h2>
<p>I should mention the others that found their way into my life:</p>
<ul>
<li>The <strong>Das Keyboard 4</strong> with Cherry MX Brown switches was my office keyboard at my previous job. Solid, professional, utterly unremarkable. It did its job and nothing more.</li>
<li>The <strong>Logitech MX Keys Mini</strong> brought me full circle to that original MK710 feeling – great battery life, wireless, reliable. Sometimes it's nice to work on something that isn't mechanical. And the connectivity is brilliant: I can reach the TV-connected computer from across the room, or quickly respond to a longer email on my phone by connecting the keyboard to it. (Yes, I refuse to type long messages on a phone screen.)</li>
<li>The <strong>Logitech MX Mechanical Mini</strong> sits at my current workplace. It's a fun middle ground – low-profile mechanical switches that feel like something between the MX Keys and a traditional mechanical keyboard. Plus, one USB dongle can handle both the keyboard and mouse.</li>
</ul>
<h2 id="the-numbers-game">The Numbers Game<a class="headerlink" href="#the-numbers-game" title="Permanent link">#</a></h2>
<p>Dive deep enough into keyboards and you'll inevitably encounter typing tests. People showcase their keyboards in action, usually on sites that measure your words per minute. It's become part of the culture.</p>
<p>I'll admit: I enjoy these tests. Not because they matter much for programming – typing speed is rarely the bottleneck in software development. But they're useful for comparing setups. How do switches compare to each other? Is this new keycap profile better for me?</p>
<p><a href="https://monkeytype.com/">Monkeytype</a> is my favorite. It tracks three main metrics:</p>
<ul>
<li><strong>WPM</strong>: Words per minute (total correct characters divided by five, normalized to 60 seconds)</li>
<li><strong>Acc</strong>: Percentage of correctly pressed keys</li>
<li><strong>Consistency</strong>: How steady your typing speed stays</li>
</ul>
<p>I decided to put my keyboards to the test.</p>
<h3 id="the-experiment">The Experiment<a class="headerlink" href="#the-experiment" title="Permanent link">#</a></h3>
<p>I tested each keyboard three times: 30 seconds, lowercase English only. Here's what I found:</p>
<div class="table-wrap"><table>
<thead>
<tr>
<th>Keyboard</th>
<th>Avg WPM</th>
<th>Avg Acc (%)</th>
<th>Avg Consistency (%)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Neo 65</td>
<td>100.00</td>
<td>99.33</td>
<td>84.33</td>
</tr>
<tr>
<td>Apple Macbook Air M1</td>
<td>96.00</td>
<td>98.33</td>
<td>81.00</td>
</tr>
<tr>
<td>Logitech MX Keys Mini</td>
<td>94.33</td>
<td>97.67</td>
<td>79.33</td>
</tr>
<tr>
<td>GMMK Pro</td>
<td>94.33</td>
<td>98.33</td>
<td>76.00</td>
</tr>
<tr>
<td>Sofle</td>
<td>60.00</td>
<td>94.00</td>
<td>61.33</td>
</tr>
<tr>
<td>iPhone 13 mini</td>
<td>56.67</td>
<td>97.00</td>
<td>70.33</td>
</tr>
</tbody>
</table></div>
<p>Some observations:</p>
<p>Three runs is laughably insufficient data, but the patterns are interesting anyway. The Neo65 comes out on top – no surprise since I use it daily. That the MacBook Air takes second place is also no surprise: It is my primary personal computer.</p>
<p>The Sofle numbers are misleading. I haven't used it in months, and you can see the practice effect: 51 WPM on the first run, 69 WPM by the third. The consistency jumped from 49 to 66. Give me a week with it and those numbers would look very different.</p>
<p>Using the Sofle also revealed something about my typing: I hit B with my right index finger. On a traditional keyboard, this works fine. On the Sofle, where B sits on the left half, my brain staged a revolt.</p>
<p>The phone numbers explain why I hate typing on mobile devices: I'm literally half as fast. I even tried using the dictation function, but that went very poorly.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>What started with a colleague's clicking keyboard became a hobby for some time. It's fascinating how these rabbit holes appear everywhere – <a href="https://jerey.at/posts/espresso-maker/">coffee</a>, <a href="https://jerey.at/posts/mountain-biking/">mountain bikes</a>, mechanical keyboards. Each one seems simple on the surface, but dive in and you find entire communities, deep technical knowledge, and endless optimization possibilities.</p>
<p>Someone might reasonably ask: why spend hundreds on keyboards when your typing speed barely changes?</p>
<p>For me, it's simple. Compared to other professions, the investment is minimal. I use a keyboard every single workday. If a carpenter needs quality tools and a goldsmith needs precision instruments, why shouldn't I enjoy the tool I use most?</p>
<p>The real limitation for me was the ISO-DE layout. I considered switching to ANSI – it would open up endless keycap options. But I'd have two layouts competing in my muscle memory. I already experience this with keyboard shortcuts: my brain uses Command on thin keyboards (like the MX Keys) because it associates them with Mac layouts, and Ctrl on everything else. Adding another mental mapping seemed like asking for trouble.</p>
<p>Lately keyboards have become a quiet topic for me. I enjoy what I have and feel no urge to change anything right now.</p>
<p>For now, the Neo65 sits in front of me, ready for another day of work.</p>]]></content:encoded></item><item><title>Living With a 23-Year-Old Volvo V70</title><link>https://jerey.at/posts/volvo/</link><guid isPermaLink="true">https://jerey.at/posts/volvo/</guid><pubDate>Mon, 03 Nov 2025 00:00:00 +0000</pubDate><description>Crossing 300,000 km after 12 years of ownership of my 2002 Volvo V70</description><media:thumbnail url="https://jerey.at/posts/assets/personal/volvo/VolvoFront.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/volvo/VolvoFront.webp" alt="Living With a 23-Year-Old Volvo V70" /><p>It’s fall, the mornings are crisp, and temperatures are edging toward freezing. My garage is still unfinished, so I get into a cold car dreaming of the warm cup of coffee waiting at the office.<br />
Today is the first morning this year that I use the auxiliary heater. I leave home a bit late and immediately pay for it — heavy traffic. But I don’t really mind. A good podcast is running, and even if I did mind, the traffic jam would still be there.<br />
When I glance down at the dashboard to check the time, a small milestone quietly announces itself: <strong>300,016 km</strong>.</p>
<h2 id="my-car">My Car<a class="headerlink" href="#my-car" title="Permanent link">#</a></h2>
<p><img alt="Volvo Back" src="https://jerey.at/posts/assets/personal/volvo/VolvoBack.webp" loading="lazy" decoding="async" width="1280" height="757"></p>
<p>I bought the car at the end of 2013, and it has been good to me ever since. It’s a 2002 Volvo V70 II D5 with a 2.5‑liter diesel engine producing (or once producing) 163 hp. In everyday driving, it consumes about 6 l/100 km in summer and 7 l/100 km in winter — though this varies quite a bit.<br />
Back in my student days, when I was studying 500 km away from home, I once managed 1,400 km on a single tank, translating to an impressive 5 l/100 km. When I bought it, the odometer showed around 150,000 km. That means an average of about 12,500 km per year since.</p>
<h2 id="the-good">The Good<a class="headerlink" href="#the-good" title="Permanent link">#</a></h2>
<p>For many reasons, the car is a keeper. Despite its size and age, it’s still remarkably economical. It swallows everything I throw at it — including my mountain bike — without having to remove a single wheel.<br />
Other than the usual maintenance, it has required no major repairs. With roughly 160 hp, acceleration feels solid, and the 2.5‑liter engine makes highway driving relaxed. You can technically go from 30 km/h all the way to 210 km/h in fifth gear — given enough patience.<br />
Roof rails allow for all sorts of attachments, though I haven’t experimented beyond a few ski trips. The tow hitch has proven handy more than once. The cruise control works like a charm, the AC still blasts icy air, and the auxiliary heater has saved me from many cold mornings.<br />
Most of my apartments didn’t have a garage, so the Volvo often stood outside. The seats — after 23 years — remain as comfortable as ever.</p>
<h2 id="the-bad">The Bad<a class="headerlink" href="#the-bad" title="Permanent link">#</a></h2>
<p>No car this old is perfect. The auxiliary heater, for example, relies on a timer. If you forget to set it or leave at unpredictable times, you either drive off in a cold car or walk out early to start it manually.<br />
There’s also a safety feature that disables the heater permanently if it fails to warm up properly three times in a row. Only a Volvo workshop can reset it.</p>
<p>Then there’s the entertainment system. Mine came with a cassette player that can even skip tracks, but not the optional CD changer. I went through the usual experiments — starting with FM transmitters — before finally installing a Bluetooth module that emulates a CD changer and integrates with the stock radio. It works fine, but it lacks conveniences like autoplay and track selection. In short, it’s good but not elegant.</p>
<p>Rear-seat safety is another issue. Isofix wasn’t standard back then, so families had to make do without it. Retrofits are possible but hard to find and expensive.<br />
And finally, perhaps the car’s biggest weakness: the headlights. Even with high beam, visibility is mediocre. More than once, the car behind me has provided better road illumination than my own lights.</p>
<h2 id="the-memories">The Memories<a class="headerlink" href="#the-memories" title="Permanent link">#</a></h2>
<p>After twelve years together, the Volvo carries a lot of memories. Countless road trips through Croatia, Italy, the Czech Republic, Germany, Switzerland, Slovenia, and of course plenty of drives across Austria.<br />
The best part isn’t just the destinations, but the journeys themselves — the long talks, the quiet moments, the feeling of starting your holiday as soon as you leave the driveway.</p>
<p><img alt="Volvo Snow" src="https://jerey.at/posts/assets/personal/volvo/VolvoSnow.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>One small story stands out: the original battery lasted 18 years. When it finally gave up, I almost felt like it deserved a proper farewell.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>The Volvo V70 has been a loyal companion — practical, efficient, and surprisingly comfortable after all these years.<br />
Rust is beginning to appear here and there, but nothing critical yet. I hope it stays on the road a while longer. After 300,000 km together, it feels less like an old car and more like an old friend.</p>]]></content:encoded></item><item><title>From the Alps to the Sea by Bike</title><link>https://jerey.at/posts/ciclovia-alpe-adria/</link><guid isPermaLink="true">https://jerey.at/posts/ciclovia-alpe-adria/</guid><pubDate>Sat, 27 Sep 2025 00:00:00 +0000</pubDate><description>A 468 km cycling adventure from Germany to Italy along the Ciclovia Alpe Adria — through mountains, tunnels, and the seaside finish at Grado.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/ciclovia/05Railroad.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/ciclovia/05Railroad.webp" alt="From the Alps to the Sea by Bike" /><p>The cool morning air smelled faintly of pine and dew as I rolled past the quiet streets of my home town, the city slowly stirring awake beneath soft golden light. Ahead awaited over 450 kilometers of winding paths, mountain climbs, and countryside vistas — an adventure that stretched beyond a typical bike ride into a journey of endurance, friendship, and discovery. Crossing borders, tracing repurposed rail trails, and finally reaching the sun-drenched coast of Italy, this wasn’t just a cycling trip; it was a chance to reconnect with the world at a human pace after months confined by the pandemic.</p>
<p>In 2018, I got my first proper <a href="https://jerey.at/posts/mountain-biking/">mountain bike</a>, but it wasn’t until 2021 — amid global travel restrictions — that the idea of a longer, multi-day bikepacking adventure really took hold. What began as two friends fueled by wanderlust grew into an epic multi-day ride along the Ciclovia Alpe Adria, testing our preparation, stamina, and love for exploration.</p>
<h2 id="preparation">Preparation<a class="headerlink" href="#preparation" title="Permanent link">#</a></h2>
<p>The preparation consisted of multiple aspects: Training to build enough endurance to manage the distance and elevation gains. Preparing the bike and maintaining it properly before starting the journey. And perhaps most importantly: Packing as little as possible while bringing everything needed. This would prove to be crucial for enjoying the journey ahead.</p>
<h3 id="training">Training<a class="headerlink" href="#training" title="Permanent link">#</a></h3>
<p>I didn't follow any special training plan - I simply tried to go out for a bike ride whenever possible. Even my daily commute to the office results in roughly 30 km of cycling. Since I had just gotten my new bicycle, it was naturally fun to go out for a ride whenever possible. So the training aspect of preparation happened organically as part of my new bike enthusiasm.</p>
<h3 id="bike-preparation-and-maintenance">Bike preparation and maintenance<a class="headerlink" href="#bike-preparation-and-maintenance" title="Permanent link">#</a></h3>
<p>Owning a new bike made me anxious about potential theft during this trip. I had considered bike insurance, but a <a href="https://c.onl">friend</a> made a compelling point against it: "There are so many bikes, especially e-bikes, that are worth way more. Why would anyone steal this bike if it's secured with a proper lock?"
So I invested in an Abus CityChain 1060. While being very secure, it's also quite heavy. But given its size, we could use it for both bikes and share the weight burden during the trip.</p>
<p>For maintenance, I followed the standard procedure: clean the bike thoroughly, oil the chain, and check the tire pressure. I also refined the seating position beforehand to ensure optimal comfort, and installed a holder for a second water bottle - something that would prove invaluable on longer stretches.</p>
<h3 id="packing-strategy">Packing strategy<a class="headerlink" href="#packing-strategy" title="Permanent link">#</a></h3>
<p>The key to a successful bike tour is bringing everything you need while leaving behind everything you don't. Every additional gram means more weight to carry with you over hundreds of kilometers. </p>
<p>I met up with my cycling partner and we carefully discussed who would bring what. Despite having no previous bikepacking experience - spoiler alert - we managed to bring along just enough gear for the ride. The only items we arguably packed "too much" of were spare inner tubes, but as any experienced cyclist knows, those are always good to have extras of.</p>
<h3 id="route-planing">Route planing<a class="headerlink" href="#route-planing" title="Permanent link">#</a></h3>
<p>We then laid out a rough route with a flexible time plan. Since neither of us was fully prepared for such a demanding tour - despite our regular rides in good weather - we planned to cycle to Golling and take the train from there to Böckstein. This would allow us to avoid the most challenging mountain section of the trip. We also deliberately scheduled plenty of extra time. After all, this was a week off work - it should be enjoyable and not stressful.</p>
<h2 id="ciclovia-alpe-adria">Ciclovia Alpe Adria<a class="headerlink" href="#ciclovia-alpe-adria" title="Permanent link">#</a></h2>
<p>The <a href="https://www.alpe-adria-radweg.com">Ciclovia Alpe Adria</a> is a cycle path connecting Salzburg to Grado. It has ready made stages to follow and is very popular - thus there are many accommodations along the way to host bikes along the riders. Additionally, going back from Grado is also easy - especially compared to the public transportation - as there are several companies offering van rides back reasonably priced.</p>
<h3 id="day-1-home-to-golling-bischofshofen">Day 1 - Home to Golling / Bischofshofen<a class="headerlink" href="#day-1-home-to-golling-bischofshofen" title="Permanent link">#</a></h3>
<p>We met up near our homes - each cycling a few kilometers individually before starting the journey together.
Right away, we got stuck in a mud puddle while trying to avoid the main roads through a forest path. It felt like a warning of challenges to come, but fortunately, it wasn't representative of what followed. The weather turned out to be perfect, the bike routes were nearly empty (especially in the beginning), and we felt energized and motivated for the adventure ahead.</p>
<p>This first day also marked our first border crossing between Germany and Austria. To maintain high energy levels, I had packed plenty of energy bars to share. This quickly became our daily ritual: regular stops to reenergize.</p>
<p><img alt="Salzburg" src="https://jerey.at/posts/assets/personal/ciclovia/00Salzburg.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>We continued through Salzburg towards Hallein. Despite encountering many bike commuters, it was refreshing to experience a familiar city from the saddle of a bike. Until then, I had usually navigated Salzburg by car, bus, or on foot. But as I mentioned in a <a href="https://jerey.at/posts/mountain-biking/">previous blog post</a>, cycling offers its own perfect pace for experiencing places.</p>
<p>Heading south, we were grateful for the abundant signage guiding us in the right direction. Passing through Urstein brought back memories, as I had studied there for my bachelor's degree. It's surprising how much one can forget about familiar places over time.</p>
<p>Soon we arrived in Hallein, a beautiful historic city. We were pleasantly surprised by our progress and stamina - neither of us showing signs of fatigue yet.</p>
<p>We continued to Golling, which was meant to be our endpoint for the day. However, we discovered that the trains between Golling and Böckstein were fully booked with cyclists. So we instead took the train to Bischofshofen to stay there overnight, and hope for available space on the train to Böckstein the next day.</p>
<p>In Bischofshofen, we took some time for sightseeing, particularly impressed by the ski jump arena. After our long day of cycling, we enjoyed a hearty dinner and attempted to get an early night's sleep in preparation for the challenging day ahead. Not all goals were achieved that evening, however.</p>
<p><img alt="Bischofshofen" src="https://jerey.at/posts/assets/personal/ciclovia/01Bischofshofen.webp" loading="lazy" decoding="async" width="1280" height="1706"></p>
<p><strong>71 km</strong> covered.</p>
<h3 id="day-2-bischofshofen-to-bockstein-mallnitz-to-untergratschach">Day 2 - Bischofshofen to Böckstein / Mallnitz to Untergratschach<a class="headerlink" href="#day-2-bischofshofen-to-bockstein-mallnitz-to-untergratschach" title="Permanent link">#</a></h3>
<p>We started the day somewhat tired after our attempts at an early night. Fortunately, as the saying goes, the best cure for tiredness is exercise - and we had plenty ahead of us.</p>
<p>The stunning landscape definitely boosted our spirits. The scenery was consistently breathtaking, and before long we arrived at Bad Gastein. Before tackling the climb ahead, we decided to fuel up with a proper meal. This proved to be a wise decision, although the ascent through the beautiful town of Bad Gastein went more smoothly than anticipated. Had we known how manageable it would be, we might never have considered taking the train from Golling to Bischofshofen.</p>
<p><img alt="Bad Gastein" src="https://jerey.at/posts/assets/personal/ciclovia/02Gastein.webp" loading="lazy" decoding="async" width="1280" height="1706"></p>
<p>Upon reaching Böckstein, we were fortunate to secure spots on the train. Arriving in Mallnitz - now in Carinthia - we found we still had energy to spare and decided to continue. We passed through a town that seemed promising for overnight accommodation, but upon discovering a large festival in progress, we decided it would be wiser to press on a bit further.</p>
<p>We eventually stopped in Obervellach for the night. Since our goal for the day had been to reach Mallnitz, we had already gained some bonus distance. In total, we traveled 69 km this day, but the real challenge was definitely the elevation gain.</p>
<p><strong>69 km</strong> covered with a personal best of <strong>959 m</strong> elevation gain.</p>
<h3 id="day-3-untergratschach-via-spittal-an-der-drau-to-villach">Day 3 - Untergratschach via Spittal an der Drau to Villach<a class="headerlink" href="#day-3-untergratschach-via-spittal-an-der-drau-to-villach" title="Permanent link">#</a></h3>
<p>Having successfully managed the most challenging part of the tour, we began the third day with renewed enthusiasm.</p>
<p>The Ciclovia Alpe Adria route was excellently marked along the way, meaning we rarely needed to consult our phones for navigation. We stopped for coffee and pastries in Spittal an der Drau - which had actually been our planned destination for the day. But since we were making such good progress, we decided to continue on to Villach.</p>
<p><img alt="Spittal" src="https://jerey.at/posts/assets/personal/ciclovia/03Spittal.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>The landscape, while not quite as spectacular as the previous day, was still pleasant. The cycling infrastructure was excellent, allowing us to ride side by side for much of the way, providing ample opportunity for conversation.</p>
<p><strong>75 km</strong> covered with good company and conversation.</p>
<h3 id="day-4-villach-to-gemona-del-friuli">Day 4 - Villach to Gemona del Friuli<a class="headerlink" href="#day-4-villach-to-gemona-del-friuli" title="Permanent link">#</a></h3>
<p>Our target for the day was Tarvisio, but we arrived there before lunch and decided to continue our journey. This day marked our second border crossing: into Italy.</p>
<p><img alt="Italy" src="https://jerey.at/posts/assets/personal/ciclovia/04Italy.webp" loading="lazy" decoding="async" width="1280" height="1706"></p>
<p>The cycling route was the highlight of the day. Starting from Tarvisio, we enjoyed a gentle descent along a repurposed railway line, passing through numerous tunnels carved into the mountains.
This section was particularly enjoyable, and thanks to the slight downhill gradient, our average speed was impressively high despite covering the longest distance of our trip so far.</p>
<p><img alt="River" src="https://jerey.at/posts/assets/personal/ciclovia/06River.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p><img alt="River" src="https://jerey.at/posts/assets/personal/ciclovia/07RRBridge.webp" loading="lazy" decoding="async" width="1280" height="1706"></p>
<p>The turquoise waters of the Tagliamento River accompanied us for much of the way, adding to the scenic beauty of the route.</p>
<p>We eventually arrived in Gemona del Friuli and decided to stop there for the night. The day had grown increasingly warm, and the next significant town was too distant to reach comfortably.</p>
<p>A personal best of <strong>105 km</strong> - our longest day of cycling.</p>
<h3 id="day-5-gemona-del-friuli-to-udine">Day 5 - Gemona del Friuli to Udine<a class="headerlink" href="#day-5-gemona-del-friuli-to-udine" title="Permanent link">#</a></h3>
<p>The previous evening, we had begun planning the final days of our journey. Since we were already quite close to Grado and had been moving faster than anticipated, we decided to spend two nights in Udine.</p>
<p>This section of the route was perhaps the least enjoyable part of the entire trip. Route markers were often difficult to spot, causing us to miss turns frequently. We also had to share public roads with vehicles more often, requiring constant vigilance.</p>
<p>Fortunately, this was also our shortest day of cycling on the Ciclovia Alpe Adria.</p>
<p>In Udine, we explored the city and, similar to our night in Bischofshofen, attempted to get an early night's sleep. Not all aspects of our journey went according to plan, however!</p>
<p><img alt="Udine" src="https://jerey.at/posts/assets/personal/ciclovia/08Udine.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p><strong>55 km</strong> of somewhat challenging navigation.</p>
<h3 id="day-6-udine">Day 6 - Udine<a class="headerlink" href="#day-6-udine" title="Permanent link">#</a></h3>
<p>Having established a routine of hearty breakfasts followed by long days of cycling, this rest day in Udine felt almost strange. We had explored much of the city the previous day and found ourselves with time to spare. We simply walked around the charming city and, for once, actually managed an early night.</p>
<h3 id="day-7-udine-via-palmanova-to-grado">Day 7 - Udine via Palmanova to Grado<a class="headerlink" href="#day-7-udine-via-palmanova-to-grado" title="Permanent link">#</a></h3>
<p>After nearly missing our daily cycling routine, we eagerly embarked on the final leg of our journey. This was the grand finale: from Udine via Palmanova to our ultimate destination, Grado.</p>
<p>The day was warm and windy, and we were glad to leave the busy streets of Udine behind - not because we disliked the city, but because sharing roads with cars in urban areas can be stressful.</p>
<p>En route to Grado, we passed an impressive-looking town and decided to explore its center. We were delighted by our detour into Palmanova, a unique star-shaped city built in concentric circles. It offered numerous charming restaurants and bars. Had we known about it beforehand, we would have planned an overnight stay there too. Perhaps next time!</p>
<p><img alt="Palmanova" src="https://jerey.at/posts/assets/personal/ciclovia/09Palmanova.webp" loading="lazy" decoding="async" width="1280" height="290"></p>
<p>The final approach to Grado was both hot and magnificent. The Matteotti Bridge stretches for about five kilometers directly above the sea, offering breathtaking views.</p>
<p><img alt="Matteotti Bridge" src="https://jerey.at/posts/assets/personal/ciclovia/10GradoBridge.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>Entering Grado felt triumphant. We immediately headed for the beach, which felt like the perfect finish line for our journey.
After checking into our hotel, we took a moment to appreciate what we had accomplished.</p>
<p><img alt="Grado" src="https://jerey.at/posts/assets/personal/ciclovia/11Grado.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p><strong>58 km</strong> for the day, bringing our total to <strong>433 km</strong> in <strong>6 days</strong> of active cycling.</p>
<h3 id="day-8-grado">Day 8 - Grado<a class="headerlink" href="#day-8-grado" title="Permanent link">#</a></h3>
<p>Believe it or not, we decided to go for another bike ride! Just a short one this time, but if anyone had suggested this before our trip began, I would have laughed in disbelief.</p>
<p><strong>35 km</strong> of leisure cycling, bringing our final total to <strong>468 km</strong> in <strong>7 days</strong>.</p>
<h3 id="day-9-homeward-bound">Day 9 - Homeward Bound<a class="headerlink" href="#day-9-homeward-bound" title="Permanent link">#</a></h3>
<p>One advantage of popular cycling routes like the Ciclovia Alpe Adria is the availability of transportation options for the return journey. Several services offer to bring cyclists and their bikes all the way home with vans and trailers, which we gratefully utilized.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>To summarize our journey: We cycled from home to Golling, then took the train to Bischofshofen on day one. From Bischofshofen, we continued all the way to Untergratschach on day two. Day three took us to Villach. Our fourth day marked the crossing into Italy, arriving in Gemona del Friuli. From there we reached Udine on day five and spent all of day six exploring the city. Day seven brought us to our final destination, Grado. On day eight, we explored the Grado area by bike before heading home on day nine.</p>
<p><img alt="Route" src="https://jerey.at/posts/assets/personal/ciclovia/Route.webp" loading="lazy" decoding="async" width="699" height="892"></p>
<p>This ranks among the best holidays I've ever experienced. Cycling offers the perfect pace to appreciate your surroundings while still covering meaningful distances each day.</p>
<p>Completing this journey in 2021, after the strict Covid lockdowns, gave us valuable time to reconnect - not just for important life updates, but for those casual, sometimes silly conversations that make friendships special. With hours of cycling each day, we had ample time to chat about everything and nothing.</p>
<p>The structure of having a daily plan and routine was satisfying without ever feeling restrictive. And perhaps most importantly, the sense of accomplishment in completing such a journey remains one of my proudest moments and most treasured memories.</p>]]></content:encoded></item><item><title>Beyond Transportation - My Mountain Biking Progression</title><link>https://jerey.at/posts/mountain-biking/</link><guid isPermaLink="true">https://jerey.at/posts/mountain-biking/</guid><pubDate>Tue, 17 Jun 2025 00:00:00 +0000</pubDate><description>How a hand-me-down mountain bike sparked a passion for cycling, leading to bike upgrades, maintenance skills, and discovering the perfect pace for experiencing the world.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/mtb/BC31.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/mtb/BC31.webp" alt="Beyond Transportation - My Mountain Biking Progression" /><p>I've always liked riding bikes. For years, it was just a way to get around—nothing special, just a practical way to get from A to B. That changed in 2018, when a friend moved away and left me his mountain bike. Suddenly, I found myself looking up bike parts, learning about maintenance, and planning longer rides just for fun. What started as a simple hand-me-down turned into a hobby that stuck with me.</p>
<p>At first, the world of mountain bikes felt a bit overwhelming. There's a lot of information out there and everyone seems to have strong opinions about what makes a “proper” bike. I took it slow, talked to friends, and did my own research. Over time, I figured out what worked for me and what didn't.</p>
<h2 id="mountain-bikes">Mountain Bikes<a class="headerlink" href="#mountain-bikes" title="Permanent link">#</a></h2>
<p>So far, I have owned three bikes. The first one was a gift, while I bought the second and third myself.</p>
<h3 id="bergamont-revox-60">Bergamont Revox 6.0<a class="headerlink" href="#bergamont-revox-60" title="Permanent link">#</a></h3>
<p>The Bergamont Revox 6.0 was my first real mountain bike. It was a bit too small, but it had features I'd never had on a bike before—disc brakes and a lockout on the shocks. The 3x10 drivetrain was pretty standard for the bikes I was used to. The bike was a good first impression of the current state of mountain bikes. Since the frame was too small for me, I continued the legacy and gifted it to a friend—time to buy a bicycle with my own money for the first time.</p>
<p><img alt="Bulls Copperhead 3 &amp; Bergamont Revox 6.0" src="https://jerey.at/posts/assets/personal/mtb/BR6BC3.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<h3 id="bulls-copperhead-3">Bulls Copperhead 3<a class="headerlink" href="#bulls-copperhead-3" title="Permanent link">#</a></h3>
<p>After a year, I upgraded to a Bulls Copperhead 3. A friend helped me pick this one at a local shop and this one fit much better, since the frame size was finally right. I started tracking my rides and exploring more of the area I live in. I even talked a few friends into joining me for a two-day tour—90 km one day, 100 km the next. It was the first time I realized how much you can see from a bike and how it's the perfect pace for travel: fast enough to cover ground, slow enough to actually notice things.</p>
<p><img alt="Styria Bike Tour" src="https://jerey.at/posts/assets/personal/mtb/Steiermark.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>Owning this bike also got me more interested in the world of mountain bikes. I started following new developments, reading up on gear, and watching videos about the latest tech. That's when I first came across the 1x12 drivetrain and began to figure out what features I wanted in my next bike.</p>
<p><img alt="Bulls Copperhead 3" src="https://jerey.at/posts/assets/personal/mtb/BC32.webp" loading="lazy" decoding="async" width="1280" height="1707"></p>
<p>Looking back, it was a great choice to get started. The Magura MT4 brakes were amazing, and the Shimano Deore XT derailleur shifted very crisply.</p>
<h3 id="canyon-grand-canyon-8">Canyon Grand Canyon 8<a class="headerlink" href="#canyon-grand-canyon-8" title="Permanent link">#</a></h3>
<p>In 2021, I bought a Canyon Grand Canyon 8. This was the first bike I picked out myself, after a lot of research and knowing what I wanted. A <a href="https://c.onl">friend</a> of mine had already bought a Canyon bike and was satisfied with the entire "buying a bike online" process.
This bike offers a few things the Bulls Copperhead 3 didn't: It has the 1x12 Shimano Deore XT RD-M8100 drivetrain and a dropper seat post. It also has a lockable Fox 34 Rhythm shock. The brakes are Shimano RT10 discs (180 mm front, 160 mm rear) with MT400 calipers. If any part of the bike felt like a downgrade, it was definitely the brakes. The Magura MT4s simply felt much better.
The gear shifting is very smooth and crisp. Compared to the 3x10 drivetrain of the previous bike, it feels like a big step up.
Being able to adjust the seat height at the press of a button is something I really enjoy. This allows quick adjustments to the seating position, which is very helpful for longer rides. Especially if you ride in more technical terrain, it is good to get the seat out of the way.
This is also the first bike where I cared about the design—it looks very sleek and good-looking.
It has made riding even more fun and I've started looking for new trails and longer routes. This is also the bike I used for my longest bikepacking tour so far: the Ciclovia Alpe Adria. I'll write about that trip in a future post.</p>
<p><img alt="Canyon Grand Canyon 8" src="https://jerey.at/posts/assets/personal/mtb/Canyon.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>This bike has been a great buy so far. I still enjoy riding it, and there really isn't a feature missing. Of course, there are always new parts I'd like to upgrade, but all the components on this bike fit my needs perfectly.</p>
<h2 id="maintenance">Maintenance<a class="headerlink" href="#maintenance" title="Permanent link">#</a></h2>
<p>The more I rode, the more I realized it made sense to learn some basic maintenance. You don't need much space or fancy tools—just a simple toolkit and a repair stand are enough for most things. Adjusting gears, swapping brake pads, and cleaning the drivetrain turned out to be pretty straightforward. YouTube and a good book were all I needed to get started. Keeping the bike in good shape makes every ride better and it's also satisfying to fix things yourself.
Learning these basics also meant I could help family and friends with their bikes.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>Mountain biking is still just a hobby for me, but one that I keep coming back to. I like the mix of being outside, getting some exercise, and tinkering with the bike.
One big advantage is that you can ride alone or with friends. Riding by myself helps me to completely disconnect from the world, whereas riding with friends gives you a lot of time to talk.
If you're thinking about mountain biking, just give it a go and don't worry too much about the details. Most things become clearer once you start riding regularly—at least, that's what worked for me.</p>]]></content:encoded></item><item><title>My Synology NAS - From Storage to Service Hub</title><link>https://jerey.at/posts/self-hosted-services/</link><guid isPermaLink="true">https://jerey.at/posts/self-hosted-services/</guid><pubDate>Tue, 03 Jun 2025 00:00:00 +0000</pubDate><description>Transforming a Synology NAS into a privacy-respecting service hub with a sustainable self-hosting workflow.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/Dashy.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/Dashy.webp" alt="My Synology NAS - From Storage to Service Hub" /><p>Diving into the self-hosted world can be both exciting and overwhelming. With countless services to choose from and various ways to deploy them, it's easy to get lost in the possibilities. In this post, I'll walk you through my journey of integrating services on my <a href="https://jerey.at/posts/synology-ds720/">Synology DS 720+</a> and share insights I've gained along the way.</p>
<h2 id="finding-services">Finding Services<a class="headerlink" href="#finding-services" title="Permanent link">#</a></h2>
<p>The initial challenge with any new tech hobby is figuring out where to start. Which services are worth your time? What problems can they actually solve for you?</p>
<p>I was fortunate to have specific goals in mind: setting up an MQTT broker and deploying <a href="https://jerey.at/posts/paperless-ngx/">Paperless-ngx</a>. Having already tested Paperless locally, I was confident it would benefit from running 24/7 on my Synology NAS. The MQTT broker was familiar territory, making it an ideal first project.</p>
<p>For discovering additional services, I relied on <a href="https://www.reddit.com/r/selfhosted/">Reddit</a>, the excellent <a href="https://github.com/awesome-selfhosted/awesome-selfhosted">awesome-selfhosted</a> GitHub repository, and fellow tech bloggers. My criteria were simple: does it solve a real problem, replace a proprietary service with something I control, or offer an interesting learning experience?</p>
<p>Here are the core services that have earned permanent spots in my self-hosted ecosystem:</p>
<ul>
<li>
<p><a href="https://github.com/Freika/dawarich">Dawarich</a>: German for "I've been there" – this privacy-focused alternative to Google Maps Timeline tracks your location history on your own server. It even offers integration with Immich for displaying trip photos. <img alt="Dawarich" src="https://jerey.at/posts/assets/personal/Dawarich.webp" loading="lazy" decoding="async" width="1280" height="898"></p>
</li>
<li>
<p><a href="https://github.com/immich-app/immich">Immich</a>: While Synology Photos served me well as a Google Photos replacement, it has limitations. Immich offers compelling features that have me seriously considering a full migration (though I'm still in the evaluation phase). <img alt="Immich" src="https://jerey.at/posts/assets/personal/Immich.webp" loading="lazy" decoding="async" width="1280" height="1028"></p>
</li>
<li>
<p><a href="https://github.com/Lissy93/dashy">Dashy</a>: With a growing collection of services and smart home devices, managing all those IP addresses became unwieldy. Dashy creates a sleek dashboard that not only organizes everything visually but also monitors service status at a glance.</p>
</li>
<li>
<p><a href="https://github.com/paperless-ngx/paperless-ngx">Paperless-ngx</a>: I've detailed this brilliant document management system in a <a href="https://jerey.at/posts/paperless-ngx/">dedicated post</a>. In short, it helps you transforms physical document chaos into digital organization.</p>
</li>
<li>
<p><a href="https://github.com/home-assistant">Home Assistant</a>: Perhaps the crown jewel of self-hosting, Home Assistant unifies smart home devices while keeping data local. I've created custom automations like automatically powering down my desk equipment when power draw drops below a threshold for a specified period, or turning off the TV when nobody's home. <img alt="Home Assistant" src="https://jerey.at/posts/assets/personal/HomeAssistant.webp" loading="lazy" decoding="async" width="1106" height="646"></p>
</li>
<li>
<p><a href="https://github.com/FreshRSS/FreshRSS">FreshRSS</a>: I enjoy reading personal blogs. FreshRSS elegantly solves the aggregation problem, bringing my favorite blogs together in one place and making them available on my phone for convenient reading.</p>
</li>
</ul>
<h2 id="getting-started">Getting Started<a class="headerlink" href="#getting-started" title="Permanent link">#</a></h2>
<p>My initial approach to installing services was somewhat clunky – SSHing into the NAS and managing Docker containers via terminal commands. The breakthrough came when I set up VS Code's remote development over SSH, giving me a seamless development experience.</p>
<p>My current workflow is straightforward:</p>
<ol>
<li>First, I scout for documentation and ideally a ready-made <code>docker-compose.yml</code> file at the projects page I want to get up and running.</li>
<li>After enabling SSH access in the Synology interface, I connect via VS Code.</li>
<li>I create a dedicated folder for each service containing the compose file and often a README documenting my setup decisions and useful references. This folder is under git versioning so I can always track changes I make.</li>
<li>Carefully mapping volumes in the compose file ensures data persistence. I maintain a standard structure with each service having its own subfolder within my main <code>docker</code> directory.</li>
<li>After launching the service, I follow any first-time setup procedures from the project documentation.</li>
<li>For services I want accessible outside my home network, I configure Synology's <a href="https://kb.synology.com/en-us/DSM/help/DSM/AdminCenter/system_login_portal_advanced?version=7#b_5">reverse proxy</a> and assign a proper domain name.</li>
</ol>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>Self-hosting has been a rewarding journey that strikes a nice balance – deep enough to be interesting without becoming overwhelming. The services run reliably, maintenance overhead is manageable, and I've discovered fascinating open-source projects along the way.</p>
<p>The community around self-hosting is vibrant and supportive, offering endless possibilities for exploration without demanding constant attention.</p>
<p>My Synology has proven to be an excellent platform due to its energy efficiency and always-on availability. The main limitations are Synology's DiskStation Manager (DSM) ecosystem constraints and the relatively modest computing power. As AI applications become more accessible, I occasionally wish for more processing muscle to run things like <a href="https://jerey.at/posts/paperless-ngx/#plugins">Paperless plugins</a> locally, preserving privacy while gaining powerful features.</p>]]></content:encoded></item><item><title>From C++ Talks to Mountain Peaks - My Colorado Adventure</title><link>https://jerey.at/posts/cppcon-colorado/</link><guid isPermaLink="true">https://jerey.at/posts/cppcon-colorado/</guid><pubDate>Mon, 05 May 2025 00:00:00 +0000</pubDate><description>Join me as I recount my experience at CppCon 2022 and my adventures exploring the stunning landscapes of Colorado.</description><media:thumbnail url="https://jerey.at/posts/assets/travel/colorado/Denver1.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/travel/colorado/Denver1.webp" alt="From C++ Talks to Mountain Peaks - My Colorado Adventure" /><p>Sharpening the tools you use daily is essential. As a programmer, this means staying up-to-date with the latest recommendations and understanding how new features in programming languages solve real-world problems. For me, conferences and training sessions have always been a great way to achieve this. </p>
<p>In 2022, I had the opportunity to attend the <a href="https://cppcon.org/">CppCon</a> in Aurora, Colorado. Given the long travel distance, I decided to extend my trip by a week to explore the state. Here's a glimpse into my experience.</p>
<h2 id="cppcon-2022">CppCon 2022<a class="headerlink" href="#cppcon-2022" title="Permanent link">#</a></h2>
<p>CppCon, organized by the Standard C++ Foundation, is a premier conference for C++ enthusiasts. It features talks on upcoming features, best practices, beginner-friendly tutorials, and even general software engineering topics. Beyond the technical content, it’s a fantastic social gathering and a chance to meet like-minded people.</p>
<p><img alt="CppCon Bjarne Stroustrup" src="https://jerey.at/posts/assets/travel/colorado/CppCon.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>I attended the conference with two colleagues, and none of us had been to Colorado before. Since Aurora doesn’t offer much in terms of attractions compared to Denver, we opted to stay in downtown Denver and commute daily to the conference venue.</p>
<p>The conference spanned five days, where I attended five talks each day. While the content was enriching, it was also overwhelming — especially when paired with jet lag. By the end of each day, I was both inspired and exhausted.</p>
<h3 id="the-good">The Good<a class="headerlink" href="#the-good" title="Permanent link">#</a></h3>
<p>CppCon had many highlights. The talks were well-organized, and the spacing between sessions allowed for networking and relaxation. Coffee, snacks, and drinks were plentiful, keeping everyone energized. </p>
<p>It was inspiring to see the "big names" in the C++ community deliver talks in person. The atmosphere was welcoming, and attendees were eager to connect and share ideas. Some companies even had exhibits, offering insights into their workflows and tools.</p>
<h3 id="the-bad">The Bad<a class="headerlink" href="#the-bad" title="Permanent link">#</a></h3>
<p>Not all talks lived up to expectations. A few felt more like product advertisements than technical discussions, which was disappointing. </p>
<p>The location of the conference was another downside. While staying at the conference hotel might have been convenient, the area lacked activities and dining options. The hotel restaurants were expensive. Staying in downtown Denver turned out to be a great decision, as it offered more affordable accommodations and plenty of evening activities.</p>
<h2 id="exploring-colorado">Exploring Colorado<a class="headerlink" href="#exploring-colorado" title="Permanent link">#</a></h2>
<p>As someone from Austria, I was curious to see how Colorado compared. Both regions are known for their mountains and skiing, so I expected some similarities. After the conference, I had nine days to explore the state with one of my colleagues, and we planned an itinerary packed with adventure.</p>
<h3 id="denver">Denver<a class="headerlink" href="#denver" title="Permanent link">#</a></h3>
<p>We started exploring Denver right away. Despite the long conference days and jet lag, we managed to enjoy dinners and evening walks around the city. Highlights included the Colorado State Capitol, Union Station, and Coors Field. </p>
<p><img alt="Colorado State Capitol" src="https://jerey.at/posts/assets/travel/colorado/Denver2.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p><img alt="Union Station" src="https://jerey.at/posts/assets/travel/colorado/UnionStation.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p>As an NFL fan, attending a live game was an awesome experience. Watching the Denver Broncos play the Houston Texans was unforgettable. Overall, Denver offered a great mix of sightseeing and relaxation.</p>
<p><img alt="Denver Broncos vs Houston Texans" src="https://jerey.at/posts/assets/travel/colorado/DenverNFL.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<h3 id="manitou-springs">Manitou Springs<a class="headerlink" href="#manitou-springs" title="Permanent link">#</a></h3>
<p>With a rental car, we headed to Manitou Springs. On the way, we visited the <a href="https://www.worldwariiaviation.org/">National Museum of World War II Aviation</a>. The museum was a gem, with passionate staff and fascinating exhibits. We even saw a vintage flight simulator in action.</p>
<p><img alt="Aircraft" src="https://jerey.at/posts/assets/travel/colorado/AP.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p><img alt="Vintage Flight Simulator" src="https://jerey.at/posts/assets/travel/colorado/APSim.webp" loading="lazy" decoding="async" width="1280" height="720"></p>
<p>Driving up Pikes Peak was another highlight. The thin air at the summit was noticeable, but the breathtaking views made it worthwhile. We also visited the Garden of the Gods and explored a charming penny arcade.</p>
<p><img alt="Pikes Peak Summit" src="https://jerey.at/posts/assets/travel/colorado/PikesPeak.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<h3 id="rocky-mountain-national-park">Rocky Mountain National Park<a class="headerlink" href="#rocky-mountain-national-park" title="Permanent link">#</a></h3>
<p>Our journey continued to the Rocky Mountain National Park, with a stop at the Red Rocks Amphitheatre. Having seen concerts online from this iconic venue, it was amazing to experience it in person. I hope to attend a live performance there someday.</p>
<p>Driving through the Rockies from Grand Lake to Estes Park was a mix of awe and reflection. The aftermath of a forest fire was evident, but it was fascinating to see how the landscape was regenerating.</p>
<p>On our way back to Denver, we stopped in Georgetown and rode the historic <a href="https://www.georgetownlooprr.com/">Georgetown Loop Railroad</a>. We wrapped up the day with classic BBQ before flying home the next day.</p>
<p><img alt="Regenerating Forest in RMNP" src="https://jerey.at/posts/assets/travel/colorado/RMNP.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="Forest Fire Aftermath in RMNP" src="https://jerey.at/posts/assets/travel/colorado/RMNPFire.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>CppCon was a unique experience. While the talks and networking opportunities were fantastic, some aspects fell short of my high expectations — particularly the prevalence of advertising talks. Nevertheless, I’m glad we stayed in downtown Denver, as it added a lot to the overall experience.</p>
<p>Exploring Colorado was equally rewarding. From Denver’s vibrant city life to the natural beauty of Manitou Springs and the Rockies, the trip showcased the diversity of the U.S. It also reminded me of the challenges of balancing photography with group travel — a lesson I’ll carry forward.</p>
<p>All in all, this trip was a perfect blend of professional growth and personal adventure.</p>]]></content:encoded></item><item><title>How Paperless-ngx Simplified My Workflow</title><link>https://jerey.at/posts/paperless-ngx/</link><guid isPermaLink="true">https://jerey.at/posts/paperless-ngx/</guid><pubDate>Wed, 02 Apr 2025 00:00:00 +0000</pubDate><description>Explore my journey from a DIY Python tool to Paperless-ngx, an open-source document management system that keeps data private.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/PaperlessNGX.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/PaperlessNGX.webp" alt="How Paperless-ngx Simplified My Workflow" /><p>In 2019, I wrote a <a href="https://jerey.at/posts/image-2-text/">Python tool</a> to extract text from images. To enhance the extraction, the tool attempted to rotate the image to align as much text horizontally as possible.
The motivation behind this tool was the high price of decent duplex document scanners and my desire to avoid organizing hard copy documentation manually.
A major challenge with organization is handling documents that belong to multiple categories.</p>
<p>My solution was to stamp each document with a numbering stamp and store all documents in the same folder. Whenever I needed something, I would search for it digitally and quickly find the document's number.</p>
<p>Then, I began self-hosting and stumbled across <a href="https://paperless-ngx.com">Paperless-ngx</a>.</p>
<h2 id="paperless-ngx"><a href="https://paperless-ngx.com">Paperless-ngx</a><a class="headerlink" href="#paperless-ngx" title="Permanent link">#</a></h2>
<p>Paperless is an open-source document management system. It performs text recognition and enables you to search for documents based on their content.
Additionally, you can assign titles, tags, and even an archive serial number (ASN) to documents, making it perfect for my needs.</p>
<p>One important aspect of self-hosting Paperless-ngx is that the uploaded data remains on <a href="https://jerey.at/posts/synology-ds720/">my server</a>. The data is stored in both its original and enhanced versions, which is crucial in case another tool becomes available or this one becomes obsolete.</p>
<p>For a full list of features, check out the <a href="https://docs.paperless-ngx.com/#features">official website</a>.</p>
<h3 id="workflow">Workflow<a class="headerlink" href="#workflow" title="Permanent link">#</a></h3>
<p>A typical workflow involves scanning a document, uploading it to the <code>consume</code> folder of Paperless, waiting for it to process, and then using the web UI for document classification. 
The title, ASN, tags, correspondent, etc., can be set there. Depending on the configuration, new documents receive a default tag indicating that they are still in the inbox and need to be handled. Once everything for a document is set, this tag can be removed.</p>
<h3 id="plugins">Plugins<a class="headerlink" href="#plugins" title="Permanent link">#</a></h3>
<p>To improve the workflow of Paperless, several interesting plugins are available.
With the rise of AI, I came across two tools that utilize AI for Paperless. A key requirement for such plugins is the ability to run the LLM locally, as Paperless-ngx hosts sensitive data that I do not want to expose.</p>
<p>For a detailed comparison of these tools, check out this <a href="https://www.reddit.com/r/selfhosted/comments/1hxediz/paperlessgpt_yet_another_paperlessngx_ai/">Reddit thread</a>, where the creator of Paperless-GPT compares it to Paperless-AI. The creator of Paperless-AI also comments in the thread.</p>
<h4 id="paperless-gpt"><a href="https://github.com/icereed/paperless-gpt">Paperless GPT</a><a class="headerlink" href="#paperless-gpt" title="Permanent link">#</a></h4>
<p>Paperless GPT aims to improve OCR results using LLMs. Simple OCR mistakes like <code>New Yark</code> can be corrected to <code>New York</code> by the LLM.
With this improved text, the plugin can automatically set the title and tags for the document.
Another useful feature is the ability to trigger processing for new documents automatically with one tag or manually with another. In manual mode, you must approve the suggested changes.</p>
<h4 id="paperless-ai"><a href="https://github.com/clusterzx/paperless-ai">Paperless-AI</a><a class="headerlink" href="#paperless-ai" title="Permanent link">#</a></h4>
<p>This plugin also analyzes documents and uses LLMs to automatically assign tags.
Its standout feature is the ability to chat with your documents, which can be handy for quickly gathering information, such as details about your car.
Like Paperless GPT, it supports both manual and automatic modes.</p>
<h2 id="quickscan">QuickScan<a class="headerlink" href="#quickscan" title="Permanent link">#</a></h2>
<p>Since I do not own a scanner and document scanners are expensive, I explored alternatives and discovered <a href="https://www.quickscanapp.com/">QuickScan</a>. I decided to try it and was impressed.
It allows you to export documents via WebDAV directly to the server and offers many customization options.
One of its most convenient features is the export favorites, which let you define regular tasks, such as uploading documents to Paperless.
Since Paperless also stores documents for my kids, I have specific export favorites for them, which automatically store their documents in a different path within the Paperless system.
Another aspect I liked is that the app is not subscription-based. I do not mind paying for software, but I prefer a one-time purchase over increasing my monthly expenses.
QuickScan is free to use but accepts donations. Donors receive rewards like changing the app's color, but all core functionality is available to everyone.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>I am glad I found a successor to my self-written Python app. The web UI is excellent, and as a popular self-hosted service, there is plenty of help available online.
The workflow I have developed is efficient and makes the scanning process very convenient. Here are my typical steps:</p>
<ol>
<li>Gather a batch of documents to scan.</li>
<li>Stamp the first document with a numbering stamp.</li>
<li>Scan the first document and send it to the <code>consume</code> folder using QuickScan.</li>
<li>Add the document to a binder if it needs to be kept as a physical copy. Otherwise, discard it.</li>
<li>Repeat these steps until all documents are in Paperless-ngx.</li>
<li>Use the Paperless-ngx web UI to configure the documents by adding titles, ASNs, etc.</li>
</ol>
<p>When I discovered the plugins, I was excited to use them to further improve the digitization process. However, since I do not have a powerful computer and my initial local testing did not meet my expectations, I decided to wait.
There is a lot of ongoing development in AI tools, and better models are becoming increasingly available.</p>]]></content:encoded></item><item><title>Starting My Self-hosted Journey with a NAS</title><link>https://jerey.at/posts/synology-ds720/</link><guid isPermaLink="true">https://jerey.at/posts/synology-ds720/</guid><pubDate>Fri, 14 Mar 2025 00:00:00 +0000</pubDate><description>Find out how I transitioned from basic storage to a versatile home server with self-hosted services on a Synology NAS.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/SynologyDS720+.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/SynologyDS720+.webp" alt="Starting My Self-hosted Journey with a NAS" /><p>In 2014, I bought my first network attached storage (NAS): the Synology DS 213j. Back then, I used it for storage-related tasks only, such as photo backup and media server. In 2020, I then bought the Synology DS 720+. At that time, I started experimenting with Docker and thought it would be great to have my own server that I could use for more than just storage-related tasks. This was the start of my self-hosted journey.</p>
<h2 id="synology-ds-720">Synology DS 720+<a class="headerlink" href="#synology-ds-720" title="Permanent link">#</a></h2>
<p>The 720+ is a two-bay NAS with a quad-core processor and 2GB of RAM, which can be upgraded. It offers low power consumption, which was important for me to avoid fearing the electrical bill and to never feel the need to switch off the server. Having gathered experience with the DS 213j, I chose to stick with Synology. The applications they offer out of the box are easy to set up and keep running. Additionally, it is possible to run Docker containers on it, making it a versatile server.</p>
<h2 id="self-hosted-services">Self-hosted Services<a class="headerlink" href="#self-hosted-services" title="Permanent link">#</a></h2>
<p>The first self-hosted service was the weather station I created along with an MQTT broker. The current services I host vary a lot. Whenever I stumble across a new project that can be self-hosted and looks interesting to me, I tend to give it a try. Depending on my liking, they will remain around or be stopped and deleted. Self-hosted services that have been around for a long time are <a href="https://www.home-assistant.io/">Home Assistant</a>, <a href="https://docs.paperless-ngx.com/">Paperless-ngx</a>, and <a href="https://freshrss.org/index.html">FreshRSS</a>.</p>
<h3 id="why-self-hosted">Why Self-hosted?<a class="headerlink" href="#why-self-hosted" title="Permanent link">#</a></h3>
<p>When you host services yourself, you are in control of your data. This makes migrating to a different service (e.g., switching your note-taking app) easier. It's important to check how the data is stored; for note-taking, I prefer plain text, mostly Markdown.</p>
<p>A trigger for me to think more about my data was during the Covid pandemic when I was cycling a lot. A fitness watch tracked my data, and the provider visualized it well. However, retrieving the data from the provider's server after selling the watch was difficult. Fortunately, several people on GitHub shared scripts to retrieve the data, often with the caveat, ".. works until version X.Y.Z". This was a wake-up call to take better care of my data.</p>
<p>This doesn't mean I avoid all services, but I consider the accessibility of stored data more carefully. One question I often ponder: Why would a company offer free online storage, mail hosting, and a calendar? It's not truly free.</p>
<p>However, using online services can be convenient and perfectly fine. It's crucial to understand why something is provided to you and whether you can retrieve your data without issues later. You might be content with your current storage method, but that could change.</p>
<h2 id="the-future">The Future<a class="headerlink" href="#the-future" title="Permanent link">#</a></h2>
<p>My own server started greatly with the Synology ecosystem. But lately, I also felt that the server could use some more computing power — especially if you want to use some LLMs. I am far from any choice, but the topic "Home Lab" is discussed a lot online, and there are plenty of YouTube channels giving great introductions to this rabbit hole. One thing to consider when moving away from Synology: Any service updates have to be done by me, and if something breaks, I also have to fix it.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>Through my NAS, I had a great introduction to home servers and self-hosted services. It is a great starting point to consider your private data more thoroughly. With the rise of AI, it would also be great to have more computing power available. Use cases can be an automatic tag creation for documents added to <a href="https://docs.paperless-ngx.com/">Paperless-ngx</a>. Since these documents can contain sensitive data, I would not want to use an online provided service.</p>]]></content:encoded></item><item><title>Exploring the World of Espresso Makers</title><link>https://jerey.at/posts/espresso-maker/</link><guid isPermaLink="true">https://jerey.at/posts/espresso-maker/</guid><pubDate>Mon, 24 Feb 2025 00:00:00 +0000</pubDate><description>Exploring the world of espresso making, from my first budget machine to the Lelit Mara X. A look at the machines I’ve used, lessons learned, and what I’d do differently today.</description><media:thumbnail url="https://jerey.at/posts/assets/personal/Coffee.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/personal/Coffee.webp" alt="Exploring the World of Espresso Makers" /><p>It might be obvious from some of my projects, but I love coffee.
In 2018, I started with a cheap espresso maker. It broke rather quickly, but in doing so, it sparked my deep interest in coffee.</p>
<h2 id="getting-started-with-espresso">Getting Started with Espresso<a class="headerlink" href="#getting-started-with-espresso" title="Permanent link">#</a></h2>
<p>There are countless guides on what makes a good espresso. Factors like grind coarseness, coffee dosage, bean selection, and grinder quality all play a role (always grind fresh!). Then, there are standard guidelines:</p>
<ul>
<li>Espresso should take around <strong>25 seconds</strong> from the first drop to the last.</li>
<li>The typical ratio is around <strong>1:2.5</strong>—meaning if you use <strong>15g of coffee beans</strong>, you aim for <strong>37.5g of espresso</strong> in your cup.</li>
</ul>
<p>But the most important rule? These are just starting points. The real goal is always <strong>taste</strong> — the most important part is that the espresso tastes the way you want it and that you enjoy the process of making it.</p>
<h2 id="why-an-espresso-maker">Why an Espresso Maker?<a class="headerlink" href="#why-an-espresso-maker" title="Permanent link">#</a></h2>
<p>One question I hear a lot from guests is: <em>“Why an espresso maker? Doesn’t the whole process feel tedious?”</em></p>
<p>Well, yes and no.</p>
<p>A fully automated machine simplifies life, but that also means you end up drinking more coffee just because it’s so convenient. There’s also the cleaning aspect — manual espresso machines with portafilters allow for thorough cleaning, whereas most automatic machines provide little access to their internal brewing components.</p>
<p>And then, of course, there’s the <strong>taste</strong>. I’ve yet to find an automatic espresso machine that matches the flavor of a well-dialed-in manual espresso shot. Why shouldn’t your home-brewed espresso taste just as good as the one from your favorite coffee shop?</p>
<h2 id="quickmill-cassiopea">Quickmill Cassiopea<a class="headerlink" href="#quickmill-cassiopea" title="Permanent link">#</a></h2>
<p>After my first espresso maker broke, I bought the <strong>Quickmill Cassiopea</strong>. This machine features dedicated thermoblocks for both coffee extraction and hot water/steam, allowing simultaneous espresso pulling and milk frothing.</p>
<p><img alt="Quickmill Cassiopea" src="https://jerey.at/posts/assets/personal/QuickMill.webp" loading="lazy" decoding="async" width="1280" height="1707"></p>
<p>The good part about the coffee machine was the quick heat-up time thanks to the thermoblocks. The bad parts were the weak steam power and that the group head is Quickmill-specific, limiting the accessories available for it.</p>
<h2 id="e61-group-head">E61 Group Head<a class="headerlink" href="#e61-group-head" title="Permanent link">#</a></h2>
<p>After a few years, I wanted to try an <strong>E61 group head</strong>. I sold my Quickmill at a small loss and started looking for a new machine. After some research, it came down to two machines.</p>
<h3 id="lelit-mara-x-vs-bezzera-magica-s">Lelit Mara X vs. Bezzera Magica S<a class="headerlink" href="#lelit-mara-x-vs-bezzera-magica-s" title="Permanent link">#</a></h3>
<p>Both machines use the <strong>E61 group head</strong>, meaning a wide variety of accessories are available. Apart from aesthetics, the key difference was that the <strong>Mara X</strong> offers temperature control settings and different brewing modes:</p>
<ul>
<li><strong>Steam Priority Mode</strong> – Functions like a traditional E61 machine.</li>
<li><strong>Coffee Priority Mode</strong> – A Lelit-specific setting that optimizes for espresso extraction.</li>
</ul>
<div class="admonition note">
<p class="admonition-title">NOTE</p>
<p>The modes have evolved over time — if you buy a Mara X today, it may have different options.</p>
</div>
<p>In the end, the <strong>Mara X simply made better-tasting espresso</strong> in my testing, so I went with it. Since September 2020, I’ve been very happy with my choice.</p>
<p><img alt="Lelit Mara-X" src="https://jerey.at/posts/assets/personal/MaraX.webp" loading="lazy" decoding="async" width="389" height="623"></p>
<h3 id="the-big-drawback-heat-up-time">The Big Drawback: Heat-Up Time<a class="headerlink" href="#the-big-drawback-heat-up-time" title="Permanent link">#</a></h3>
<p>A common downside of <strong>E61 machines</strong> is their long <strong>heat-up time</strong>. Lelit states <strong>24 minutes</strong>, but real-world measurements show that stability is achieved <strong>after 30 minutes</strong>. And temperature stability is critical for consistent shots.</p>
<p>My solution? A <strong>WiFi smart plug</strong> that preheats the machine automatically. While this helps, it doesn’t allow for spontaneous espresso-making. Plus, <strong>E61 machines consume a lot of energy</strong> compared to thermoblock machines since they heat an entire boiler just to extract a few milliliters of espresso.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>Like many hobbies, espresso making can become a deep rabbit hole. There’s an overwhelming amount of information online, sometimes to the point, where you optimize settings, despite liking the taste already.</p>
<p>Would I buy my setup again? <strong>Probably not.</strong></p>
<p>Since purchasing the Mara X, new machines have emerged. While I appreciate the E61 experience, I dislike the long heat-up times and high energy consumption. Yes, the smart plug is a decent workaround, but spontaneous espresso isn’t really an option.</p>]]></content:encoded></item><item><title>Coffee Grinder Automation - A 2025 Update</title><link>https://jerey.at/posts/coffee-grinder-automation-2025/</link><guid isPermaLink="true">https://jerey.at/posts/coffee-grinder-automation-2025/</guid><pubDate>Fri, 14 Feb 2025 00:00:00 +0000</pubDate><description>An overview of how I improved my coffee grinder automation project by refactoring the code for better quality and maintainability.</description><media:thumbnail url="https://jerey.at/posts/assets/project/CoffeeGrinderAutomation.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/project/CoffeeGrinderAutomation.webp" alt="Coffee Grinder Automation - A 2025 Update" /><p>Back in 2020, I automated my coffee grinder. Before that, I had to weigh the ground coffee by hand.</p>
<p>More expensive coffee grinders offer time-based grinding and some even offer weight-based grinding.
Since mine didn't, I decided to take matters into my own hands and created a project to help me with it.</p>
<div class="gh-card-wrap"><a class="gh-card" href="https://github.com/Jerey/coffee-automation" target="_blank" rel="noopener"><span class="gh-card-icon"><svg viewBox="0 0 16 16" width="22" height="22" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0016 8c0-4.42-3.58-8-8-8z"/></svg></span><span class="gh-card-text"><span class="gh-card-repo">Jerey/coffee-automation</span><span class="gh-card-host">github.com</span></span></a></div>

<p>I revisited this project in 2025 and was not happy with the code.</p>
<h2 id="the-problem">The Problem<a class="headerlink" href="#the-problem" title="Permanent link">#</a></h2>
<p>The logic for starting, stopping, and managing the grinding process was scattered across multiple functions, making it difficult to understand and maintain.</p>
<p>Looking at the main <code>loop()</code> of the Arduino-based software showed this:</p>
<div class="highlight"><pre><span></span><code><span class="kt">void</span><span class="w"> </span><span class="nf">loop</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w">  </span><span class="c1">// ...</span>
<span class="w">  </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">automaticGrindingOngoing</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="k">auto</span><span class="w"> </span><span class="n">currentWeight</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">getCurrentWeightAndPublish</span><span class="p">();</span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">((</span><span class="n">currentWeight</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">thresholdTargetGrams</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">desiredGrams</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">      </span><span class="c1">// Start or continue the grinding</span>
<span class="w">    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w">      </span><span class="c1">// The current weight with a given threshold has reached the target weight ..</span>
<span class="w">      </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">currentWeight</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">desiredGrams</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="c1">// .. now approach the desired weight slowly.</span>
<span class="w">        </span><span class="c1">// (the grinder retention and the distance between the grinder and the scale forced this approach)</span>
<span class="w">        </span><span class="n">startGrinding</span><span class="p">(</span><span class="s">&quot;automaticGrinding&quot;</span><span class="p">,</span><span class="w"> </span><span class="mi">150</span><span class="p">);</span>
<span class="w">      </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w">        </span><span class="c1">// .. or the desiredGrams have been reached.</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><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">grindingOngoing</span><span class="w"> </span><span class="o">&amp;&amp;</span>
<span class="w">       </span><span class="p">(</span><span class="n">millis</span><span class="p">()</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">grindingStartedTime</span><span class="p">)</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">grindingTime</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="c1">// Time-based grinding was ongoing and we have reached the desired grinding time -&gt; Stop the grinder.</span>
<span class="w">  </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="c1">// We are not grinding, so handle anything else (such as updates over the air).</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

<blockquote>
<p>Code parts were replaced by comments to ease the reading.
<a href="https://github.com/Jerey/coffee-automation/blob/ea603f113caf657fe9d7a6bd0ccbf01cdb5467bc/src/program.cpp#L217-L250">Here</a> is a link to the full code section.</p>
</blockquote>
<p>When MQTT messages are received to start grinding, the <code>bool</code> flags <code>automaticGrindingOngoing</code> or <code>grindingOngoing</code> are set.</p>
<p>Looking at the <a href="https://github.com/Jerey/coffee-automation/blob/ea603f113caf657fe9d7a6bd0ccbf01cdb5467bc/src/program.cpp#L138-L169"><code>callback</code></a>, I was surprised that there wasn't a <code>startAutomaticGrinding</code> function called or that the <code>startGrinding</code> function starts the grinding but doesn't care about stopping it.
The stopping logic was handled separately in the <code>loop()</code>, leading to a fragmented and hard-to-follow codebase.</p>
<h2 id="improving-the-code">Improving the Code<a class="headerlink" href="#improving-the-code" title="Permanent link">#</a></h2>
<p>The first idea was to extract the entire grinder and MQTT handling logic to their own libraries.</p>
<p>This left the <code>program.cpp</code> with less code only handling the update over the air and the connection to the Wi-Fi.</p>
<h3 id="grindingcontroller"><code>GrindingController</code><a class="headerlink" href="#grindingcontroller" title="Permanent link">#</a></h3>
<p>A lot of the logic was now moved to the <code>GrindingController</code>.
It now takes care of the MQTT <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/lib/CoffeeAutomation/GrindingController.cpp#L46-L76"><code>callback</code></a> with dedicated functions for each MQTT command:</p>
<ul>
<li><code>automaticGrinding(..)</code> -&gt; Handles weight-based grinding</li>
<li><code>timeBasedGrinding(..)</code> -&gt; Handles time-based grinding</li>
</ul>
<p>The <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/lib/CoffeeAutomation/GrindingController.cpp#L78-L102"><code>automaticGrinding</code></a> function takes care of everything needed for weight-based grinding.
It tares the scale, grinds beans until the target weight minus a threshold is reached, and then slowly approaches the target weight. Additionally, it also handles all the MQTT updates.</p>
<p>The <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/lib/CoffeeAutomation/GrindingController.cpp#L20-L25"><code>timeBasedGrinding</code></a> function sends out updates via MQTT, starts, and stops the grinder based on the desired time.</p>
<p>The <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/lib/CoffeeAutomation/GrindingController.cpp#L116-L119"><code>GrindingController::loop()</code></a> is called by the <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/src/program.cpp#L81-L86"><code>program::loop()</code></a>.
Its main task is checking for MQTT updates and publishing the current weight on the scale regularly.</p>
<h3 id="mqttgrinder"><code>MqttGrinder</code><a class="headerlink" href="#mqttgrinder" title="Permanent link">#</a></h3>
<p>The <code>MqttGrinder</code> is more or less a wrapper of the <a href="https://github.com/knolleary/pubsubclient"><code>PubSubClient</code></a>, a library used for MQTT communication.
It takes care of any grinder-specific MQTT communication, such as <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/lib/CoffeeAutomation/MqttGrinder.cpp#L25-L41">publishing</a> new values, <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/lib/CoffeeAutomation/MqttGrinder.cpp#L6-L11">subscribing</a> to MQTT topics relevant to the grinder, and setting up the <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/lib/CoffeeAutomation/MqttGrinder.cpp#L58C26-L58C34">callback</a> which is called when one of the subscribed topics is received.</p>
<h3 id="blocking-vs-non-blocking">Blocking vs. Non-Blocking<a class="headerlink" href="#blocking-vs-non-blocking" title="Permanent link">#</a></h3>
<p>The old implementation within the <code>program::loop()</code> was (almost) non-blocking, which actually is a good practice. A few delays were part of it, but in theory, the <code>program::loop()</code> would be pretty much executed periodically.</p>
<p>The new implementation is blocking since the new grinding functions remain in a <code>while</code>-loop until the targets are reached - theoretically, they can also never be reached.</p>
<p>But since the grinder controller always has exactly one task to do (either time-based or automatic grinding) and a firmware update should only occur when the grinder is not grinding, this solution felt right.
Additionally, the two main functions of the <code>GrindingController</code> (<code>automaticGrinding</code> and <code>timeBasedGrinding</code>) now are responsible for their own actions all the time, making the code easier to understand.</p>
<p>And despite the two functions being blocking, the <code>MqttGrinder</code> will still handle the MQTT messages regularly since the current weight is published periodically.
This is probably the part that should be refactored next, as there is a lot of hidden logic there. That <code>GrindingController::getCurrentWeightAndPublish()</code> publishes the weight after a defined time has passed (<code>scaleUpdateTime</code>) and therefore triggers an MQTT update is pretty hidden.</p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>It was fun to refactor this old project. Often my ESP-based projects start off as a quick proof of concept through which the code quality often suffers.</p>
<p>Additionally, I no longer own that grinder and therefore no longer use this project.
My current grinder is a so-called "single dose coffee grinder" - you weigh the beans before pouring them into the grinder and the output of the grinder is exactly the same weight.</p>
<p>But still having the load cell available enabled me to "experience" my own documentation for the setup - where the schematic allowed me to quickly have something up and running.
For testing, I chose to skip the relay and instead use the built-in LED of the D1 mini to indicate the current state of the grinder.</p>
<p>There are still several code parts that would benefit from a refactoring:</p>
<ul>
<li>The new <a href="https://github.com/Jerey/coffee-automation/blob/f62142d689d115cbf7541daf6f7284798d2f38ee/lib/CoffeeAutomation/GrindingController.cpp#L46-L76"><code>callback</code></a> function is rather long for being a mapping of topic to function.</li>
<li>A lot of logic is still hidden. That removing <code>GrindingController::getCurrentWeightAndPublish()</code> results in no MQTT messages being updated is not something I would expect.</li>
</ul>
<p>Additionally, further functions could be added:</p>
<ul>
<li>Fallback to ensure that the relay is switched off if anything bad happens (firmware crashes, etc.).</li>
<li>Hardware buttons to control the grinder: MQTT enables many ways to control the grinder. But in real life, one would always control the grinder while standing there. It would be great to adjust settings and start the grinding without the need for any MQTT-compatible device.</li>
</ul>
<p>If any of those code updates will come is unclear since I am no longer a user of the project.
But I also did not expect to refactor this project and yet here we are.</p>
<p>For a detailed view of the changes, you can check out the <a href="https://github.com/Jerey/coffee-automation/pull/2">GitHub pull request</a>.</p>]]></content:encoded></item><item><title>Stockholm, Kiruna and Oslo</title><link>https://jerey.at/posts/kiruna-stockholm-oslo/</link><guid isPermaLink="true">https://jerey.at/posts/kiruna-stockholm-oslo/</guid><pubDate>Sun, 09 Feb 2025 00:00:00 +0000</pubDate><description>A journey through the breathtaking beauty of Scandinavia´s cities and landscapes. From the Northern Lights in Kiruna to the stunning architecture of Stockholm and Oslo.</description><media:thumbnail url="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/OsloPhoto.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/OsloPhoto.webp" alt="Stockholm, Kiruna and Oslo" /><h1 id="stockholm-kiruna-and-oslo">Stockholm, Kiruna and Oslo<a class="headerlink" href="#stockholm-kiruna-and-oslo" title="Permanent link">#</a></h1>
<p>As I embarked on this journey through Scandinavia, my passion for photography grew alongside my excitement to capture the breathtaking beauty of these cities and landscapes. From the Northern Lights in Kiruna to the stunning architecture of Stockholm and Oslo, each destination offered a unique experience that pushed my skills to new heights.</p>
<h2 id="stockholm">Stockholm<a class="headerlink" href="#stockholm" title="Permanent link">#</a></h2>
<p>Our journey began in Stockholm, a great city to get started with photography. The city's rich culture, delicious food, and shorter winter days made it an ideal spot to test my camera. We spent three nights exploring the city's many museums, including the Vasa Museum on the island of Djurgården.</p>
<h3 id="vasa-museum">Vasa Museum<a class="headerlink" href="#vasa-museum" title="Permanent link">#</a></h3>
<p>The Vasa Museum is home to a Swedish warship that sank on its maiden voyage in the 17th century. The ship was discovered in the 1950s and has undergone extensive preservation efforts. I had previously visited this museum several times, but it never failed to amaze me.
Having bought the Canon EOS M50 Mark II and the Sigma EF-M 16mm f/1.4 prior to this trip, I had to bring it to the museum. That allowed me to further figure out photography in dark conditions.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/StockholmVasaFromTop.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/StockholmVasaFromBack.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<h3 id="julbord">Julbord<a class="headerlink" href="#julbord" title="Permanent link">#</a></h3>
<p>Despite growing up in Austria, my family always celebrated Christmas in a Swedish fashion - so with a traditional Swedish Christmas feast, known as Julbord.
During December, several restaurants offer a Julbord.
So additionally to Christmas, I got to enjoy this buffet an additional time that year.</p>
<h3 id="walking">Walking<a class="headerlink" href="#walking" title="Permanent link">#</a></h3>
<p>The best way to explore a city is typically by walking. This allowed us to take photos whenever we stumbled across nice places. My friend taught me a few tricks on photography. For example in the following photo if you look to the left of the light pole, you can see a large tower far away in the background.
This tower drew attention to it, which was not the intention for this photo. So by strategically placing the light pole in front of it, the photo became visually more appealing.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/StockholmPark.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<p>In addition to exploring the city, we also tried some delicious Swedish street food, like Tunnbrödsrulle, a flatbread wrapped around a sausage, mashed potatoes, and other tasty fillings.</p>
<p>Here are a few impressions of Stockholm and some of my first photos with my camera.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/StockholmNationalmuseum.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/StockholmStora.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/StockholmAtNight.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<h2 id="kiruna">Kiruna<a class="headerlink" href="#kiruna" title="Permanent link">#</a></h2>
<p>After three days in Stockholm, we flew to Kiruna. I definitely recommend a seat at the window, as the landscape is spectacular.
We had a rental car in Kiruna, which introduced a first time: Driving a car with spikes.
Similar to Stockholm, we immediately started walking around the city, grabbed a Souvas Rulle (similar to the Tunnbrödsrulle, but with reindeer meat instead of the sausage) and spent some time in the apartment waiting for the Northern Lights.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/KirunaAtDay.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/KirunaChurchOther.webp" loading="lazy" decoding="async" width="1280" height="1920"></p>
<p>As I wrote in the last post, we checked out the local church, walked around the town in the dark and drove to a less light polluted area to raise the chances of seeing the Northern Lights - which we kind of did, but only through the lens of the camera.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/KirunaFirstAurora.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p>We also took part in an Aurora expedition with dinner - a snowmobile, Northern Lights and dinner tour. Sadly we did not see the Northern Lights that day, but the landscape and food were great.
Another day, we drove to the Esrange Space Center. It was a long car drive, but it was worth it. We stopped on several occasions for photography and even saw a Moose and a Reindeer in the wild. The space station visitor center was interesting, but the best part about going there was definitely the landscape and the almost untouched nature.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/KirunaSpaceStation1.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/KirunaSpaceStation2.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/KirunaSpaceStation3.webp" loading="lazy" decoding="async" width="1280" height="914"></p>
<p>After that, we went on to our next hotel, which was situated at the Torne river. There we were able to see the Northern Lights with our eyes.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/KirunaAuroraCamp.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<h2 id="oslo">Oslo<a class="headerlink" href="#oslo" title="Permanent link">#</a></h2>
<p>To finish our Scandinavian trip - sorry Finland - we went to Oslo in Norway. We walked through the city, took several photos at the Oslo Opera House and the pier. We saw the Royal Palace and took it rather slow and easy that day, after having spent much time sightseeing and taking photos.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/OsloOpera.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/kiruna-stockholm-oslo/OsloHarbor.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>The entire trip was a great experience. The Northern Lights were definitely the highlight of this journey. Kiruna is an awesome city and I definitely want to go there again. I have been to Stockholm often, so it is kind of "known" but as always a great experience. And Oslo has been awesom. It was nice to see the city for a second time, but due to the Corona pandemic rising again, many activities were not possible and we were also already exhausted from the past days walking and taking pictures.</p>]]></content:encoded></item><item><title>Kiruna and the Northern Lights</title><link>https://jerey.at/posts/photography/</link><guid isPermaLink="true">https://jerey.at/posts/photography/</guid><pubDate>Wed, 05 Feb 2025 00:00:00 +0000</pubDate><description>A trip to Sweden’s Kiruna offered me an unforgettable opportunity to capture the stunning Northern Lights and explore new depths in my photography through the use of professional gear.</description><media:thumbnail url="https://jerey.at/posts/assets/travel/photography/SecondNorthernLights3.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/travel/photography/SecondNorthernLights3.webp" alt="Kiruna and the Northern Lights" /><h1 id="introduction">Introduction<a class="headerlink" href="#introduction" title="Permanent link">#</a></h1>
<p>Having Swedish roots, a friend of mine asked me in 2021, if I wanted to join him on a trip to Sweden to capture the Northern Lights.</p>
<h2 id="camera-setup">Camera Setup<a class="headerlink" href="#camera-setup" title="Permanent link">#</a></h2>
<p>Up until then, I had primarily taken photos with my phone. In 2015, I started creating yearly photo albums after realizing that I rarely revisited my pictures. This practice motivated me to take more photos, knowing they would be part of my next album. As a result, I had already been considering purchasing an entry-level mirrorless camera. The chance to photograph the Northern Lights became the final push I needed. I bought an used Canon EOS M50 Mark II, which came with a Canon EF-M 15-45mm f/3.5-6.3 IS STM lens.</p>
<h2 id="lenses-and-a-tripod">Lenses and a Tripod<a class="headerlink" href="#lenses-and-a-tripod" title="Permanent link">#</a></h2>
<p>My friend, an experienced photographer, quickly introduced me to essential photography techniques and settings. He recommended purchasing an additional lens for capturing the Aurora Borealis: the SIGMA EF-M 16mm f/1.4. Its wide aperture would allow more light to enter, making it ideal for low-light photography.</p>
<p>Another lens he suggested was the budget friendly Canon EF 50mm f/1.8, perfect for portrait photography. Since my M50 has an EF-M mount, I needed an adapter to use this lens. The adapter applies a 1.6x crop factor, effectively converting the 50mm lens into an 80mm equivalent. This exposed one of the first drawbacks of my camera: the limited availability and high cost of compatible lenses.</p>
<p>Another crucial investment was a sturdy tripod. I chose the Rollei Compact Traveller No. 1, ensuring stability for long-exposure shots.</p>
<h2 id="kiruna">Kiruna<a class="headerlink" href="#kiruna" title="Permanent link">#</a></h2>
<p>With my camera gear ready, I began testing my lenses and exploring the camera’s settings. Initially, it was overwhelming, but much of my learning came from hands-on experimentation during the trip.</p>
<p>December in Kiruna is incredibly cold, so we often found ourselves indoors, waiting for the Northern Lights or preparing food. During these breaks, we experimented with different camera settings, even attempting to take photos through the windows. One amusing moment occurred when I excitedly captured a green light on my camera, only to realize upon closer inspection that it was the reflection of the microwave clock. The long exposure time had allowed the camera to pick up details that were invisible to the naked eye.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/photography/Microwave.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p>We ventured into the city and its surroundings. After capturing several shots of the local church, we drove to a remote area with minimal light pollution. At first, we saw what appeared to be an unusual cloud, but upon pointing the camera at it, we discovered that it was actually the Aurora Borealis. While it wasn’t yet visible to the naked eye, seeing it through the camera was thrilling.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/photography/Church.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/photography/FirstNorthernLights.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p>Our next hotel was situated near the river Torne surrounded by a forest — an ideal location for photography but also a gamble, as the northern lights are not guaranteed and there was not much else to do there. Fortunately, luck was on our side. This time, we could see the lights with our bare eyes — not as vividly as through the camera, but still an unforgettable sight.</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/photography/SecondNorthernLights1.webp" loading="lazy" decoding="async" width="1280" height="854"></p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/photography/SecondNorthernLights2.webp" loading="lazy" decoding="async" width="1280" height="853"></p>
<p>Here a photo for comparison taken with my phone:</p>
<p><img alt="" src="https://jerey.at/posts/assets/travel/photography/SecondNorthernLightsPhone.webp" loading="lazy" decoding="async" width="1280" height="960"></p>
<h2 id="final-thoughts">Final Thoughts<a class="headerlink" href="#final-thoughts" title="Permanent link">#</a></h2>
<p>All in all, the trip to Sweden was an incredible experience. I captured my first stunning photos with a proper camera and compared them to those taken with my phone. However, I also encountered another challenge: post-processing. Editing the images required time and effort — yet another skill to master.</p>
<p>Despite this, the journey reinforced my passion for photography, and I left Sweden with unforgettable memories and a deeper appreciation for the art of capturing light.</p>]]></content:encoded></item><item><title>Mara X Meter</title><link>https://jerey.at/posts/mara-x-meter/</link><guid isPermaLink="true">https://jerey.at/posts/mara-x-meter/</guid><pubDate>Thu, 01 Jul 2021 00:00:00 +0000</pubDate><description>An e-paper display which visualizes the heat-up process and key metrics of the Lelit Mara-X espresso machine.</description><media:thumbnail url="https://jerey.at/posts/assets/project/Heatup.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/project/Heatup.webp" alt="Mara X Meter" /><p>My espresso machine is a Lelit Mara-X. Among several adaptations to the classic E-61 brewing unit, the Mara-X features a serial input and output interface. This interface provides information such as the current software version, target boiler temperature, current boiler temperature, current heat exchanger temperature, and the heating status (on or off).</p>
<p>I have come across several projects that visualize this data in various ways.</p>
<p>To ensure that the aesthetics of the coffee machine remain largely unaffected by modern displays, I decided to use an e-paper display. This display visualizes the data relevant to me and also draws a heat-up graph.</p>
<div class="gh-card-wrap"><a class="gh-card" href="https://github.com/Jerey/mara-x-meter" target="_blank" rel="noopener"><span class="gh-card-icon"><svg viewBox="0 0 16 16" width="22" height="22" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0016 8c0-4.42-3.58-8-8-8z"/></svg></span><span class="gh-card-text"><span class="gh-card-repo">Jerey/mara-x-meter</span><span class="gh-card-host">github.com</span></span></a></div>]]></content:encoded></item><item><title>Coffee Grinder Automation</title><link>https://jerey.at/posts/coffee-grinder-automation/</link><guid isPermaLink="true">https://jerey.at/posts/coffee-grinder-automation/</guid><pubDate>Wed, 01 Apr 2020 00:00:00 +0000</pubDate><description>An automated coffee grinder using a D1-mini and load cell to achieve precise weight-based grinding for consistent coffee quality.</description><media:thumbnail url="https://jerey.at/posts/assets/project/CoffeeGrinderProtoType.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/project/CoffeeGrinderProtoType.webp" alt="Coffee Grinder Automation" /><p>I enjoy a good cup of coffee. To achieve that at home, I bought a coffee grinder along with a nice espresso machine. Several factors are important for a great-tasting coffee: coarseness, dosage, quality of the beans, extraction time, and more. To find the best-tasting coffee, it is crucial to minimize the changing factors, one of which is the amount of beans. Previously, I had to stand by the coffee grinder with a scale and manually measure the amount of ground beans. After working from home for a while, I got annoyed with this process, so I decided to automate the grinding of the beans.</p>
<p>Having gained some experience in the <code>ESP8266</code> world, I decided to use a <code>D1-mini</code> for the task. After some research, I chose to use a load cell along with the load cell amplifier HX711. The grinder itself is switched on and off by a relay controlled by the <code>D1-mini</code>.</p>
<p>A <code>node-red</code> flow along with the <code>node-red</code>-dashboard is used to control the <code>D1-mini</code>. The commands are sent through <code>mqtt</code> topics.</p>
<p>On the dashboard, there are several functions: weight-based and time-based grinding, a brew timer, and the taring of the scale. The main challenge with this project was that it takes some time for the ground coffee to reach the scale while grinding. Therefore, stopping the grinder when the weight is reached might already be too late. It was necessary to stop the grinding process a bit earlier and then grind some more until the target weight is reached.</p>
<p><img alt="" src="https://jerey.at/posts/assets/project/CoffeeGrinderDashboard.webp" loading="lazy" decoding="async" width="977" height="516"></p>
<div class="gh-card-wrap"><a class="gh-card" href="https://github.com/Jerey/coffee-automation" target="_blank" rel="noopener"><span class="gh-card-icon"><svg viewBox="0 0 16 16" width="22" height="22" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0016 8c0-4.42-3.58-8-8-8z"/></svg></span><span class="gh-card-text"><span class="gh-card-repo">Jerey/coffee-automation</span><span class="gh-card-host">github.com</span></span></a></div>]]></content:encoded></item><item><title>Image to Text</title><link>https://jerey.at/posts/image-2-text/</link><guid isPermaLink="true">https://jerey.at/posts/image-2-text/</guid><pubDate>Fri, 01 Feb 2019 00:00:00 +0000</pubDate><description>A Python tool that extracts text from scanned documents, stores it digitally, and organizes them using a numbering system for easy retrieval.</description><media:thumbnail url="https://jerey.at/posts/assets/project/img2txt.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/project/img2txt.webp" alt="Image to Text" /><p>I enjoy the transition to a paperless world, as I find it much easier to organize documents digitally. However, since many documents are still required as hard copies, one is forced to archive them. A tough question then is: How many and which categories of organizers will one need for all documents?</p>
<p>For me, I decided that I do not want any categories and each organizer should be used until it is full.</p>
<p>To be able to find documents, I wrote a Python program that extracts text from scanned documents and stores it in a computer-processable way.</p>
<p>Additionally, each document is stamped with a numbering stamp (which automatically increases its number with each stamping) and the computer-processable data is stored in folders named by the number of the document. So whenever I am looking for a certain document, I type in keywords from the document and the computer provides me with the number of the document and a preview of it. Then I can grab the organizer, which has the number range written on it, and take the document from it.</p>
<div class="gh-card-wrap"><a class="gh-card" href="https://github.com/Jerey/image-to-pdf-and-txt" target="_blank" rel="noopener"><span class="gh-card-icon"><svg viewBox="0 0 16 16" width="22" height="22" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0016 8c0-4.42-3.58-8-8-8z"/></svg></span><span class="gh-card-text"><span class="gh-card-repo">Jerey/image-to-pdf-and-txt</span><span class="gh-card-host">github.com</span></span></a></div>]]></content:encoded></item><item><title>Weatherstation</title><link>https://jerey.at/posts/weatherstation/</link><guid isPermaLink="true">https://jerey.at/posts/weatherstation/</guid><pubDate>Fri, 01 Sep 2017 00:00:00 +0000</pubDate><description>Hard- and software project to track, store and visualize the current temperature and humidity.</description><media:thumbnail url="https://jerey.at/posts/assets/project/WeatherstationNodeJSServer2.webp"/><content:encoded><![CDATA[<img src="https://jerey.at/posts/assets/project/WeatherstationNodeJSServer2.webp" alt="Weatherstation" /><p>Looking for a project to work on in my spare time that touches on several technologies, I came across the <code>ESP8266</code> and had the idea to create a "weather station." It should be able to track the room climate by storing the current temperature and humidity. Additionally, this collected data should be presented in a nice way.</p>
<p>The project consists of two major parts: <a href="https://github.com/jerey/weatherstation-client">The client</a>, which measures the current temperature and humidity and publishes the data via <code>MQTT</code>. It is based on an <code>ESP</code>, and a custom PCB was created for the <code>D1 mini</code> to facilitate the connections. The second part is the server, which stores the received values and presents them in a nice way. There are multiple ways to start a server to handle the data: either with a <a href="https://github.com/jerey/weatherstation-server">Node.js-based server</a>, which was specifically created for this project utilizing a SQL database to store the values and starting a web server to present the data in the form of graphs, or with the help of <code>node-red</code>, <code>influxdb</code>, and <code>grafana</code>. Here, a <code>node-red</code> flow is started, which automatically stores the published data to an <code>InfluxDB</code>, and <code>Grafana</code> is used to visualize it.</p>
<p><img alt="" src="https://jerey.at/posts/assets/project/WeatherstationNodeJSServer1.webp" loading="lazy" decoding="async" width="1280" height="831"></p>
<p><img alt="" src="https://jerey.at/posts/assets/project/WeatherstationGrafana.webp" loading="lazy" decoding="async" width="1280" height="418"></p>
<div class="gh-card-wrap"><a class="gh-card" href="https://github.com/Jerey/weatherstation-client" target="_blank" rel="noopener"><span class="gh-card-icon"><svg viewBox="0 0 16 16" width="22" height="22" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0016 8c0-4.42-3.58-8-8-8z"/></svg></span><span class="gh-card-text"><span class="gh-card-repo">Jerey/weatherstation-client</span><span class="gh-card-host">github.com</span></span></a></div>

<div class="gh-card-wrap"><a class="gh-card" href="https://github.com/Jerey/weatherstation-server" target="_blank" rel="noopener"><span class="gh-card-icon"><svg viewBox="0 0 16 16" width="22" height="22" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0016 8c0-4.42-3.58-8-8-8z"/></svg></span><span class="gh-card-text"><span class="gh-card-repo">Jerey/weatherstation-server</span><span class="gh-card-host">github.com</span></span></a></div>]]></content:encoded></item></channel></rss>