<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Schallberts Blog</title><link>https://blog.schallbert.de/</link><description>Recent content on Schallberts Blog</description><generator>Hugo</generator><language>de</language><lastBuildDate>Sun, 06 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.schallbert.de/index.xml" rel="self" type="application/rss+xml"/><item><title>Schallberts Blog mit Hugo</title><link>https://blog.schallbert.de/announcements/2026-09-06-blog-anniversary-hugo/</link><pubDate>Sun, 06 Sep 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/announcements/2026-09-06-blog-anniversary-hugo/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/hugo-website-landing-cover.avif"&#10; class="post-cover"&#10; alt="Image: Crop my landing page with Hugo: We see the logo of the blog, half a speaker and half cog-wheel as menu bar, simple three-word navigation, and the cover images representing the blog&amp;#39;s focus points on Hardware, Electronics, and Software."&#10; title="Schallberts Blog mit Hugo" /&gt;&#10;&lt;h2 id="was-ist-geschehen"&gt;Was ist geschehen?&lt;/h2&gt;&#10;&lt;p&gt;Zum fünften Geburtstag 🥳 stelle ich &lt;strong&gt;schallbert&amp;rsquo;s Blog&lt;/strong&gt; auf eine neue technische Basis!&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/hugo-jekyll-build-compare-banner.avif" alt="Image: side-by-side comparison of my website, once built with jekyll, and now built with hugo." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich habe diesen Blog von &lt;a href="https://blog.schallbert.de/projects/thissite/"&gt;jekyll&lt;/a&gt; nun auf den Webseitengenerator &lt;a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hugo&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; umgezogen. Das war viel Aufwand: Über etwa vier Monate hinweg habe ich einen Großteil meiner Freizeit hineingesteckt. Dabei ging der Löwenanteil in das Erstellen und Polieren der Formatierung, Anpassung meiner Inhalte an die neue Umgebung, Reparatur defekter Links und die Dokumentation meines Umbaus.&lt;/p&gt;&#10;&lt;h2 id="warum-habe-ich-das-gemacht"&gt;Warum habe ich das gemacht?&lt;/h2&gt;&#10;&lt;p&gt;Mit Jekyll stand ich immer mehr auf Kriegsfuß: Viele Abhängigkeiten mit immer häufigeren Defekten, die zunehmend lange Bauzeit und die komplexe Umgebung zum Ausrollen auf meinem Server waren die Hauptgründe.&lt;/p&gt;&#10;&lt;p&gt;Meine Seite baut nun etwa &lt;em&gt;fünf mal so schnell&lt;/em&gt; wie vorher.&lt;/p&gt;&#10;&lt;p&gt;Sie lädt &lt;em&gt;weniger Formatierung&lt;/em&gt; nach (CSS, JS) und die Bilder sind noch einmal &lt;em&gt;platzsparender&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Außerdem habe ich weder Ruby noch zig Gems auf Stand zu halten.&lt;/p&gt;&#10;&lt;p&gt;Bei den Themes habe ich in Hugo Wert darauf gelegt, es nach benötigter Funktion auszuwählen und nicht nur nach gefälligem Aussehen. Somit ist es jetzt viel &lt;em&gt;simpler und performanter&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h2 id="was-haben-die-leserinnen-davon"&gt;Was haben die LeserInnen davon?&lt;/h2&gt;&#10;&lt;p&gt;Der Blog bietet nun einen &lt;a href="https://blog.schallbert.de/index.xml/"&gt;RSS-Feed&lt;/a&gt;. Außerdem sind nun auch meine Projekte in die &lt;a href="https://blog.schallbert.de/tags/"&gt;Schlagwort-Ansicht&lt;/a&gt; mit eingebunden. Zu guter Letzt sind durch die Migration zahlreiche Artikel entstanden. Diese sollen einen solchen Umzug für andere leichter machen als ich es hatte, und die Strukturen in Hugo verdeutlichen.&lt;/p&gt;&#10;&lt;h2 id="artikel-zum-thema"&gt;Artikel zum Thema&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/projects/migrating-jekyll-to-hugo/"&gt;Projekt: Migration von Jekyll zu Hugo&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/hugo-create-language-specific-permalinks/"&gt;Permalinks sprachunabhängig korrekt darstellen&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/hugo-link-checker/"&gt;Website-Links in Hugo mit &lt;em&gt;linkcheck&lt;/em&gt; überprüfen&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/migrate-jekyll-to-hugo-image-alignment/"&gt;Bildformatierung und Platzierung in Hugo&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="was-fehlt-noch"&gt;Was fehlt noch?&lt;/h2&gt;&#10;&lt;p&gt;Ich bin mit dem Umbau noch nicht ganz fertig:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Es soll möglich sein, Bilder zentriert und in nativer Auflösung anzuzeigen&lt;/li&gt;&#10;&lt;li&gt;Der Feed soll Coverbilder meiner Artikel korrekt anzeigen&lt;/li&gt;&#10;&lt;li&gt;Ich möchte in CSS einen noch einheitlicheren Look haben&lt;/li&gt;&#10;&lt;li&gt;Übersichtsseiten könnten die ganze Bildschirmseite einnehmen und dann größere Bilder anzeigen&lt;/li&gt;&#10;&lt;li&gt;Es fehlen einige englische Übersetzungen&lt;/li&gt;&#10;&lt;li&gt;Meine aktuellsten Projekte könnten auf der Landing Page mit angezeigt werden&lt;/li&gt;&#10;&lt;li&gt;Schlagworte könnten spezifischer und trennschärfer implementiert werden&lt;/li&gt;&#10;&lt;li&gt;Schön wäre ein mitscrollendes Inhaltsverzeichnis auf der linken Seite&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Auf diesen Seiten wird es in den kommende Wochen in vielen Details noch einmal Verbesserungen geben.&#10;Ich freue mich darauf!&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;schallbert&lt;/strong&gt;&lt;/p&gt;&#10;</description></item><item><title>Umzug von Jekyll zu Hugo</title><link>https://blog.schallbert.de/projects/migrating-jekyll-to-hugo/</link><pubDate>Sun, 06 Sep 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/migrating-jekyll-to-hugo/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/hugo-jekyll-build-compare-cover.avif"&#10; class="post-cover"&#10; alt="Image: Hugo console build output. It shows a full build time below 8sec. (~70% reduction over build time with Jekyll)"&#10; title="Umzug von Jekyll zu Hugo" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: Mittel 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: 0€&lt;/li&gt;&#10;&lt;li&gt;Zeit ~20h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In diesem Projekt beschreibe ich meinen Umzug von Jekyll mit der Optik von Minimal Mistakes zu Hugo (Theme Terminal).&lt;/p&gt;&#10;&lt;h2 id="warum-überhaupt-wechseln"&gt;Warum überhaupt wechseln?&lt;/h2&gt;&#10;&lt;p&gt;Schon seit langem war ich nicht mehr vollständig zufrieden mit dem Bauen meiner Website per Jekyll. Der Auslöser für den Umstieg war, dass ich eines Tages plötzlich wegen eines Kompatibilitätsproblemes nicht mehr lokal bauen konnte. Ein &lt;code&gt;bundle update --conservative&lt;/code&gt; oder &lt;code&gt;bundle install&lt;/code&gt; wie üblich half nicht. Auch das manuelle Nachinstallieren der verursachenden Pakete per &lt;code&gt;gem install commonmarker-0.23.12&lt;/code&gt; oder &lt;code&gt;gem install posix-spawn -v 0.3.15 -- --with-cflags=\&amp;quot;-Wno-incompatible-function-pointer-types\&amp;quot;&lt;/code&gt; brachten keine Abhilfe.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/jekyll-update-bundle-installer-error.avif" alt="Image: Bundler dependency tree showing the error: Failed to build gem native extension. Multiple errors like this occurred lately when I tried building my site." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ein Update von &lt;em&gt;Ruby&lt;/em&gt; und &lt;em&gt;Jekyll&lt;/em&gt; selbst brachte ebenfalls keinen Erfolg. Und überhaupt: Die Bauzeit mit Jekyll ist inzwischen ziemlich lang geworden. Ich bin durch meinen &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;Umzug der Seite von Github Pages auf einen eigenen Server&lt;/a&gt; nicht mehr an Jekyll gebunden, was einen Wechsel attraktiver macht.&lt;/p&gt;&#10;&lt;h3 id="weitere-schwierigkeiten-mit-jekyll"&gt;Weitere Schwierigkeiten mit Jekyll&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;CI/CD: Meine Deploy-Pipeline war vor Monaten mit ähnlichen Fehlern ausgefallen, sodass ich auf eine ältere Jekyll-Version zurückgreifen musste. Damit vergrößert sich technische Schuld und Angriffsfläche meiner Actions. Ich müsste auf die Suche nach einer gut gewarteten Containerlösung eines anderen Anbieters wechseln. Suche und Einrichtung würden zusätzliche Zeit erfordern.&lt;/li&gt;&#10;&lt;li&gt;RSS-Feed: Ich möchte zu meiner Website gern einen RSS-Feed bereitstellen. Meine Experimente mit Jekyll zeigten jedoch, dass nur &amp;ldquo;Posts&amp;rdquo; und keine &amp;ldquo;Announcements&amp;rdquo; inkludiert werden. Das wollte ich beheben.&lt;/li&gt;&#10;&lt;li&gt;Bildformate: Cover-Bilder (Thumbnails) für Posts werden im RSS-Feed durch Jekyll eigentlich mit gebaut. Durch meinen Umstieg von &lt;code&gt;.jpg&lt;/code&gt; auf das platzsparendere &lt;code&gt;.avif&lt;/code&gt; funktionierte das nun leider nicht mehr. Eine Lösung habe ich trotz mehrerer Anläufe nicht durch den Pull Request Review bekommen.&lt;/li&gt;&#10;&lt;li&gt;Icons: Das von mir verwendete Theme nutzt &lt;em&gt;Font Awesome&lt;/em&gt; zur Darstellung von Icons und ist insgesamt für eine statische Website recht groß. Ich wollte aber nichts aus dem Netz nachladen und habe die Bibliothek daher lokal gespeichert. Daher war mein Plan, nur die wirklich benötigten Icons heraussuchen und den Rest zu löschen. Mit dem Umstieg auf Hugo ist dies nicht mehr notwendig.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="die-chancen"&gt;Die Chancen&lt;/h3&gt;&#10;&lt;p&gt;Von Hugo verspreche ich mir schnellere Bauzeiten, eine vereinfachte CI/CD ohne Nachinstallieren von Bundler, Ruby etc. und die Behebung der oben beschriebenen Schwierigkeiten. Das Abhängigkeits- und Paketmanagement von Hugo unterscheidet sich grundlegend von Jekyll: Zusätzliche Inhalte werden per &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Git Submodules&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; eingebunden und das Abhängigkeismanagement nutzt statt der &lt;a href="https://bundler.io/man/gemfile.5.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Gemfile&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; schlicht &lt;a href="https://instagit.com/gohugoio/hugo/hugo-dependency-management-go-modules/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Go modules&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="das-migrationswerkzeug"&gt;Das Migrationswerkzeug&lt;/h2&gt;&#10;&lt;p&gt;Die Ordnerstrukturen der Inhalte unterscheidet sich deutlich zwischen &lt;em&gt;Jekyll&lt;/em&gt; und &lt;em&gt;Hugo&lt;/em&gt;. Ich nutze daher ein in Hugo enthaltenes &lt;a href="https://gohugo.io/commands/hugo_import_jekyll/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Importwerkzeug&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, welches mir zumindest die Artikel unter &lt;code&gt;/posts&lt;/code&gt; korrekt importiert.&lt;/p&gt;&#10;&lt;p&gt;Alle spezifischen Inhalte wie Übersichtsseiten &lt;code&gt;/tags, /pages/error, /pages/legal&lt;/code&gt; und Seiten, wo Projekte und Ankündigungen abgelegt sind &lt;code&gt;/_announcements, /_projects&lt;/code&gt;, werden allerdings nicht mit umgezogen und müssen manuell kopiert und modifiziert werden.&lt;/p&gt;&#10;&lt;h3 id="anpassen-der-front-matter"&gt;Anpassen der Front Matter&lt;/h3&gt;&#10;&lt;p&gt;Viele von mir gemachten Anpassungen in Jekyll funktionieren in Hugo entweder nicht richtig oder führen zum Abbruch des Bauvorganges - die &lt;strong&gt;Front Matter&lt;/strong&gt; einer jeden Markdown-Datei muss also angepasst und teils bereinigt werden.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Alt: So kann die Front matter einer Übersichtsseite in Jekyll aussehen&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;lang&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Beitragsarchiv&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;subtitle&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Diese Sammlung enthält alle meine Beiträge&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;layout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;collection&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;collection&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;posts&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;permalink&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/posts-archive/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;entries_layout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grid&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;classes&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;wide&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;author_profile&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In meinem Falle liegen einige Theme-abhänigen Parameter im Frontmatter vor wie &lt;code&gt;layout, classes, author_profile&lt;/code&gt;. Der &lt;code&gt;lang&lt;/code&gt;-Parameter aus meiner Internationalisierungserweiterung in Jekyll ist nicht einmal korrekte Hugo-Syntax: es erwartet die angezeigte Sprache entweder als Feld unter &lt;code&gt;params: lang&lt;/code&gt; oder Ordnerweit. Auch andere, an den eigenen Geschmack angepasste Parameter können definiert und unter &lt;code&gt;params&lt;/code&gt; mitgegeben werden.&lt;/p&gt;&#10;&lt;p&gt;Permalinks finden gar keinen Eingang mehr in die Front matter, stattdessen findet die Konfiguration &lt;a href="https://gohugo.io/configuration/permalinks/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;zentral statt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Eine &lt;a href="https://gohugo.io/content-management/front-matter/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hugo-kompatible front matter&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; kann so aussehen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Beitragsarchiv&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Diese Sammlung enthält alle meine Beiträge&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;layout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;collection&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="konfiguration-des-theme"&gt;Konfiguration des Theme&lt;/h2&gt;&#10;&lt;p&gt;Eine Basis-Konfiguration meines Themes kann ich mit folgendem Kommando installieren:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;main&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git submodule add https://github.com/panr/hugo-theme-terminal ./themes/terminal &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Zum Aktivieren muss ich es in der Hauptkonfiguration bekannt machen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /hugo.toml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Add it only if you keep the theme in the `themes` directory.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Remove it if you use the theme as a remote Hugo Module.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;theme&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;terminal&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und schon baut Hugo für mich im Terminal-Theme und Originalfarben.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/hugo-initial-build-with-terminal.avif" alt="Image: One of my blog posts in original Terminal theme: Left-adjusted, filling half the screen and in dark grey, orange as contrast color, and white text." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dies ist nun nicht vertikal zentriert und für meinen Geschmack zu schmal ausgelegt, der Text recht klein und &amp;ldquo;meine&amp;rdquo; Blog-Farben sollen ebenfalls anders sein .&lt;/p&gt;&#10;&lt;p&gt;Daher erstelle ich eine &lt;code&gt;.css&lt;/code&gt;-Datei, die einige Eigenschaften des Basis-Themes überschreibt. Sie muss im Ordner &lt;code&gt;/static&lt;/code&gt; liegen und den Namen des Themes tragen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/* /static/terminal.css */&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;:&lt;span style="color:#a6e22e"&gt;root&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --background: &lt;span style="color:#ae81ff"&gt;#252a34&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --foreground: &lt;span style="color:#ae81ff"&gt;#d2eaef&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --foreground-dimmed: &lt;span style="color:#ae81ff"&gt;#8b9ea2&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --accent: &lt;span style="color:#ae81ff"&gt;#25a679&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --accent-highlight: &lt;span style="color:#ae81ff"&gt;#79ae9c&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --radius: &lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;px&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --font-size: &lt;span style="color:#ae81ff"&gt;1.35&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;rem&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --line-height: &lt;span style="color:#ae81ff"&gt;1.8&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;em&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/* Use system fonts, reduce transferred kB */&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;body&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;font-family&lt;/span&gt;: system-ui, &lt;span style="color:#f92672"&gt;-&lt;/span&gt;apple-system, &lt;span style="color:#e6db74"&gt;&amp;#34;Segoe UI&amp;#34;&lt;/span&gt;, Roboto, Arial, &lt;span style="color:#66d9ef"&gt;sans-serif&lt;/span&gt; &lt;span style="color:#75715e"&gt;!important&lt;/span&gt;; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/* make content wider */&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;body&lt;/span&gt; .&lt;span style="color:#a6e22e"&gt;container&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;max-width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;70&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;rem&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und schon sieht es fast wie früher aus.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/hugo-custom-build-with-terminal.avif" alt="Image: One of my blog posts in the modified Terminal theme: centered, filling three quarters of the screen and in dark grey, teal as contrast color, and blueish-white text with a sans-serif system font." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="organisation-der-ordnerstruktur"&gt;Organisation der Ordnerstruktur&lt;/h2&gt;&#10;&lt;p&gt;Anders als bei der von mir verwendeten &lt;em&gt;Jekyll&lt;/em&gt;-Version verfügt &lt;em&gt;hugo&lt;/em&gt; über eingebaute Internationalisierung (&lt;code&gt;i18n&lt;/code&gt;). Dafür muss ich meine Ordnerstruktur umbauen und explizit in &lt;code&gt;/de&lt;/code&gt; und &lt;code&gt;/en&lt;/code&gt;-Ordnerinhalte sortieren. Inhalte werden nun statt auf der höheren Ordner-Ebene direkt unter &lt;code&gt;/_pages, /_announcements, /_posts&lt;/code&gt; im Ordner &lt;code&gt;content&lt;/code&gt; gehalten, was die Sache übersichtlicher macht.&lt;/p&gt;&#10;&lt;h3 id="vorher"&gt;Vorher&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _announcements&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── assets&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── audio&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── css&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── fontawesome&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── katex&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── main.scss &lt;span style="color:#75715e"&gt;# &amp;lt;-- theme import, customization&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── webfonts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── docs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── images&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── js&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── lunr&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── _main.js&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── main.min.js&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── plugins&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── vendor&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── video&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── banner.js&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── CNAME&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _config.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── de&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── l10n.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── navigation.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── ui-text.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── en&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── l10n.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── navigation.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── ui-text.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── en&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── index.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── Gemfile&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── Gemfile.lock&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _includes &lt;span style="color:#75715e"&gt;# &amp;lt;-- Layouts (HTML), partials, 3rd-party&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── index.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- Landing page&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _layouts &lt;span style="color:#75715e"&gt;# &amp;lt;-- Page layouts&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── archive.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── archive-taxonomy.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── categories.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── category.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── collection.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── compress.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── default.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── home.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── posts.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── search.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── single.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── splash.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── tag.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── tags.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── LICENSE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── package.json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── package-lock.json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _pages&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── 404.md &lt;span style="color:#75715e"&gt;# &amp;lt;-- German version&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── &lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── en&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── 404.md &lt;span style="color:#75715e"&gt;# &amp;lt;-- English version&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── &lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _posts/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── en &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _projects/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── Rakefile&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── README.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _sass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── minimal-mistakes &lt;span style="color:#75715e"&gt;# &amp;lt;-- Styles (SCSS) for layouts and partials&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── minimal-mistakes.scss&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── _video.scss&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── _site/ &lt;span style="color:#75715e"&gt;# &amp;lt;-- Build artifacts&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── staticman.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── tags&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── en&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │   └── tags.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── tags.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="nachher"&gt;Nachher&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── archetypes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── assets&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── scss&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── landing.scss &lt;span style="color:#75715e"&gt;# &amp;lt;-- custom styling for my landing page&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── content&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── de&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── about.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── announcements/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── posts/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── projects/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── en&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── about.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── posts/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── projects/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── hugo.toml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── layouts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── _default&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── baseof.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- defines header, add-ons, content, footer structure&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── index.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- landing page&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── list.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- grid view&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── single.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- post view&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── _markup&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── render-link.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- link highlighting and function (referrer, tabs)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── _partials&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── cover.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- cover (thumbnail) image rendering&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── math.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- math rendering (katex)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── _shortcodes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── audio.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── image.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── video.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── resources&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── _gen&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── assets/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── static&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── assets&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── audio/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── css/ &lt;span style="color:#75715e"&gt;# &amp;lt;-- contains overrides for custom layouts &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── docs/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── images/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── js/ &lt;span style="color:#75715e"&gt;# &amp;lt;-- math (katex) JS lives here&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── video/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── terminal.css &lt;span style="color:#75715e"&gt;# &amp;lt;-- Central theme override (colors, formatting, styling)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── themes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── terminal &lt;span style="color:#75715e"&gt;# &amp;lt;-- GIT submodule: Vanilla &amp;#34;Terminal&amp;#34; theme&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bei beiden Lösungen werden Layouts in &lt;code&gt;html&lt;/code&gt; und Stil/Format ind &lt;code&gt;CSS&lt;/code&gt; definiert. Da ich bei Jekyll mehr Plugins verwenden musste, wird das Setup größer.&lt;/p&gt;&#10;&lt;h2 id="manuelle-anpassungen-shortcodes"&gt;Manuelle Anpassungen: Shortcodes&lt;/h2&gt;&#10;&lt;p&gt;Mit &lt;em&gt;Shortcodes&lt;/em&gt; kann man in Hugo nicht-Text-Inhalte (Medien, Links, Formatierung, Stil) definieren, die dann nach definierter Vorschrift gerendert werden. Dafür legt man eine &lt;code&gt;html&lt;/code&gt;-Datei an, welche den Dekorator definiert und entsprechende &lt;code&gt;css-Klassen&lt;/code&gt; einbindet. Außerdem fügt man die gewünschte Formatierung in der überschreibenden Theme-&lt;code&gt;css&lt;/code&gt;-Datei ein.&lt;/p&gt;&#10;&lt;p&gt;Da ich hier absolut kein Profi bin, probiere ich anschließend so lange herum, bis mir das Layouting gefällt und ich keine Artefakte/Überlagerungen mehr sehe.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── layouts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── _shortcodes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── my-shortcode.html &lt;span style="color:#75715e"&gt;# &amp;lt;-- Custom shortcode file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── static&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── terminal.css &lt;span style="color:#75715e"&gt;# &amp;lt;-- Central theme override (colors, formatting, styling)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="hugo"&gt;Hugo&lt;/h3&gt;&#10;&lt;p&gt;Shortcodes werden in der Markdown-Datei mit folgender Syntax angezeigt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{{&amp;lt; shortcode-html-filename options &amp;gt;}}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{{&amp;lt; image src=&amp;#34;/path/to/image.avif&amp;#34; alt=&amp;#34;Image: alt text&amp;#34; position=&amp;#34;right&amp;#34; &amp;gt;}}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="jekyll"&gt;Jekyll&lt;/h3&gt;&#10;&lt;p&gt;Im Vergleich dazu sehen Shortcode-ähnliche Dekoratoren in Jekyll für &lt;a href="https://jekyllrb.com/docs/liquid/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Liquid, die in Jekyll benutzte Beschreibungssprache&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; so aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;### Liquid in interpreter code&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{% Liquid bracket syntax %}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;### Liquid &amp;#34;shortcode&amp;#34; pendant&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{% include gallery id=&amp;#34;gallery&amp;#34; caption=&amp;#34;Eindrücke von &lt;span style="font-weight:bold"&gt;**MobFobAmp**&lt;/span&gt;. Zum Vergrößern anklicken.&amp;#34; %}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;### Liquid decorator for layouting&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{:.list-inline}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="image"&gt;image&lt;/h3&gt;&#10;&lt;p&gt;Anwendungsbeispiel zur Bildanzeige. In der nächsten Zeile folgt ein zentriertes Bild.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/favicon/logo_icon.avif" alt="Image: My blog logo, half a speaker chassis, half cog wheels, centered." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/favicon/logo_icon.avif" alt="Image: My blog logo, half a speaker chassis, half cog wheels, left-adjusted with text flow." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der klassische Bild-Dekorator für Markdown &lt;code&gt;[Alt Text](/path/to/image)&lt;/code&gt; wird in Hugo standardmäßig als mittig zentriertes Bild ohne Textfluss gerendert. Für ein schönes Layouting benötige ich jedoch ein paar Varianten, welche etwas zusätzlichen Code in cascading style sheets und html erfordern.&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/favicon/logo_icon.avif" alt="Image: My blog logo, half a speaker chassis, half cog wheels, right-adjusted with text flow." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Bild zentriert, ohne Textfluss&lt;/li&gt;&#10;&lt;li&gt;Bild links , Textfluss rechts&lt;/li&gt;&#10;&lt;li&gt;Bild rechts, Textfluss links&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Zuerst besorgt sich Hugo Dateipfad und Bildtext. Anschließend liest es die gewünschte Formatierung aus der Markdown-Datei, welche mit der &lt;code&gt;position&lt;/code&gt;-Variable angelegt wird. Anschließend wendet sie die &lt;code&gt;image-wrapper&lt;/code&gt; Klasse aus der &lt;code&gt;.css&lt;/code&gt;-Datei darauf an. Die Ausrichtung der Bilder gilt für bessere Lesbarkeit nur für die Desktop-Variante der Webseite (&lt;code&gt;&amp;gt;768px&lt;/code&gt;).&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_shortcodes/image.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$src&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Get&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;src&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$alt&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Get&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;alt&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$position&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Get&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;position&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;center&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;image-wrapper &lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$position&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;img&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$src&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;alt&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$alt&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/* /static/terminal.css */&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;image-wrapper&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;center&lt;/span&gt; &lt;span style="color:#f92672"&gt;img&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;image-wrapper&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;left&lt;/span&gt; &lt;span style="color:#f92672"&gt;img&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;image-wrapper&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;right&lt;/span&gt; &lt;span style="color:#f92672"&gt;img&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;max-width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;border&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;px&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;solid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;var&lt;/span&gt;(&lt;span style="color:#f92672"&gt;--&lt;/span&gt;accent);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;display&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;block&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;margin-left&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;auto&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;margin-right&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;auto&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@&lt;span style="color:#66d9ef"&gt;media&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#f92672"&gt;min-width&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;768px&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;image-wrapper&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;left&lt;/span&gt; &lt;span style="color:#f92672"&gt;img&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;float&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;left&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;margin-right&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;rem&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;max-width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;image-wrapper&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;right&lt;/span&gt; &lt;span style="color:#f92672"&gt;img&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;float&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;right&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;margin-left&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;rem&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;max-width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="audio"&gt;audio&lt;/h3&gt;&#10;&lt;p&gt;Zur Darstellung des Audio-Players verwende ich die im Browser implementierten Standards.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;audio controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/audio/aa_alpha_fingered_flageolet.mp3" type="audio/mp3"&gt;&#10; Your browser does not support the audio element.&#10; &lt;/audio&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Beispielplayer: Melodie auf dem E-Bass&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_shortcodes/audio.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$src&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Get&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;src&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$caption&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Get&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;caption&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;media-wrapper&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;media-container&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;audio&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;controls&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;source&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$src&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;audio/mp3&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Your browser does not support the audio element.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;audio&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$caption&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;media-title&amp;#34;&lt;/span&gt;&amp;gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In &lt;code&gt;css&lt;/code&gt; verwende ich einen Wrapper und Media-Container, um Titel im Rahmen des Players anzeigen zu können und die Formatierung zu erleichtern. Auch hier habe ich angepasstes Verhalten für ein &amp;ldquo;responsive layout&amp;rdquo; bei Mobilgeräten oder Änderungen der Größe des Browser-Fensters.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/* /static/terminal.css */&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;media-wrapper&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;max-width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;margin&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;var&lt;/span&gt;(&lt;span style="color:#f92672"&gt;--&lt;/span&gt;line&lt;span style="color:#f92672"&gt;-&lt;/span&gt;height) &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;display&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;flex&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;justify-content&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;center&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;media-container&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;max-width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;768&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;px&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;display&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;block&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;border&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;px&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;solid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;var&lt;/span&gt;(&lt;span style="color:#f92672"&gt;--&lt;/span&gt;accent);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;background-color&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;var&lt;/span&gt;(&lt;span style="color:#f92672"&gt;--&lt;/span&gt;accent);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;border-radius&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;var&lt;/span&gt;(&lt;span style="color:#f92672"&gt;--&lt;/span&gt;radius);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;overflow&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;hidden&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;text-align&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;center&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;media-container&lt;/span&gt; &lt;span style="color:#f92672"&gt;audio&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;media-container&lt;/span&gt; &lt;span style="color:#f92672"&gt;video&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;display&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;block&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;media-container&lt;/span&gt; &lt;span style="color:#f92672"&gt;iframe&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;width&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;%&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;min-height&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;400&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;px&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;aspect-ratio&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;16&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;display&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;block&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;media-title&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;word-break&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;break-word&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;hyphens&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;auto&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;color&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;var&lt;/span&gt;(&lt;span style="color:#f92672"&gt;--&lt;/span&gt;background);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;font-weight&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;bold&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="video"&gt;video&lt;/h3&gt;&#10;&lt;p&gt;Das CSS ist identisch mit &lt;code&gt;audio&lt;/code&gt;. Es gibt nur im &lt;code&gt;html&lt;/code&gt; geringfügige Unterschiede. Der von mir verwendete Container unterstützt unter &lt;code&gt;src=&lt;/code&gt; als Quellen sowohl &amp;ldquo;embed-Links&amp;rdquo; von Webseiten wie &lt;em&gt;peertube&lt;/em&gt; als auch lokale Quelldateien.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-01_trolley.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Video-Beispiel (lokal): Linearführungen mit Kugelumlauf&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_shortcodes/video.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$src&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Get&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;src&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$title&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Get&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;title&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$caption&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Get&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;caption&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;media-wrapper&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;media-container&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$src&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;iframe&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$src&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;frameborder&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;allowfullscreen&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style="color:#f92672"&gt;iframe&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;video&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;controls&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;source&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$src&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;video/mp4&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Your browser does not support the video tag.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;video&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;or&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$title&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$caption&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;media-title&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$title&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$title&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$caption&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="links"&gt;links&lt;/h3&gt;&#10;&lt;p&gt;Folgendes Verhalten wünsche ich mir für Links auf meinem Blog:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;interne Links sollen im selben Tab geöffnet werden und die Nutzer weiterleiten.&lt;/li&gt;&#10;&lt;li&gt;interne Links sollen zu Seiten in derselben Sprache verweisen und nicht auf die Standard-Sprache oder zurück zur Hauptseite führen.&lt;/li&gt;&#10;&lt;li&gt;externe Links sollen mit einem Symbol gekennzeichnet werden: &lt;code&gt;↗&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;externe Links sollen in einem frischen Tab geöffnet werden: &lt;code&gt;target=&amp;quot;_blank&amp;quot;&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;exterme Links sollen weder auf die ursprünglichen Inhalte zugreifen noch den Quellverweis sehen können &lt;code&gt;rel=&amp;quot;noopener noreferrer&amp;quot;&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Dafür erstelle ich unter &lt;code&gt;/layouts/_markup&lt;/code&gt; die Datei &lt;code&gt;render-link.html&lt;/code&gt;.&#10;Den Code hierfür und seine Hintergründe stelle ich in einem separaten Post vor: &lt;a href="https://blog.schallbert.de/hugo-create-language-specific-permalinks/"&gt;Hugo Multi-language: Interne Permalinks&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Der Override für CSS sieht wie folgt aus.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/* /static/terminal.css */&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.&lt;span style="color:#a6e22e"&gt;external-link&lt;/span&gt; .&lt;span style="color:#a6e22e"&gt;external-link-icon&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;display&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;inline-block&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;font-size&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;var&lt;/span&gt;(&lt;span style="color:#f92672"&gt;--&lt;/span&gt;font&lt;span style="color:#f92672"&gt;-&lt;/span&gt;size);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;vertical-align&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;middle&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;transform&lt;/span&gt;: translateY(&lt;span style="color:#ae81ff"&gt;-0.1&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;em&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="manuelle-anpassungen-partials"&gt;Manuelle Anpassungen: Partials&lt;/h2&gt;&#10;&lt;p&gt;Partials kann man sich vorstellen wie die Layout-Bausteine für eine Hugo-Website. Sie werden unter &lt;code&gt;/layouts/_partials&lt;/code&gt; abgelegt und überschreiben die Standards des verwendeten Themes. Dabei wird die Ursprungsdatei gar nicht mehr geladen, was ein Herüberkopieren der Originaldatei in den Ordner mit anschließender manueller Anpassung empfehlenswert macht.&lt;/p&gt;&#10;&lt;h3 id="index"&gt;index&lt;/h3&gt;&#10;&lt;p&gt;Die Startseite für den Blog ist komplett selbt geschrieben und liegt nicht in Markdown, sondern in &lt;code&gt;html&lt;/code&gt; vor. Sie zeigt ihre Inhalte anders an als alle anderen Blog-Seiten und daher lohnte es sich für mich nicht, Templates zu programmieren. In der entsprechenden &lt;code&gt;/layouts/_partials/index.html&lt;/code&gt; lade ich dafür notwendige &lt;code&gt;scss&lt;/code&gt; und &lt;code&gt;css&lt;/code&gt;-Dateien und gebe die Struktur der Seite vor. Die folgende Tabelle gibt einen Überblick über meine Änderungen.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;File&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Purpose&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Customization&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;cover&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;thumbnail / cover image for overview pages, lists, and grids&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Add custom cover image decorator, alt text&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;head&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Defines Favicon, search console, page parameters, feeds etc.&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Custom favicon path&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;header&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Page header containing menu, logo, navigation etc.&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Add logo and subtitle to menu, lang selector&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;logo&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Custom page logo, title, subtitle&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Full&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;math&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Displays math content with Latex/Katex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;None, implement math support&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="math"&gt;math&lt;/h3&gt;&#10;&lt;p&gt;Mein Blog soll mathematische Notation unterstützen. Dafür benutze ich &lt;a href="https://katex.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Katex&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_partials/math.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/assets/css/katex.min.css&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/assets/js/katex.min.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/assets/js/contrib/auto-render.min.js&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;onload&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;renderMathInElement(document.body);&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; document.&lt;span style="color:#a6e22e"&gt;addEventListener&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;DOMContentLoaded&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt;() {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;renderMathInElement&lt;/span&gt;(document.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;, {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;delimiters&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#a6e22e"&gt;left&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;\\[&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;right&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;\\]&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;}, &lt;span style="color:#75715e"&gt;// block&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#a6e22e"&gt;left&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;$$&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;right&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;$$&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;}, &lt;span style="color:#75715e"&gt;// block&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#a6e22e"&gt;left&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;\\(&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;right&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;\\)&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;}, &lt;span style="color:#75715e"&gt;// inline&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;throwOnError&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="cover-image"&gt;cover image&lt;/h3&gt;&#10;&lt;p&gt;Unter der Bezeichnung &lt;code&gt;cover&lt;/code&gt; verstehe ich ein Titelbild eines Blog-Posts. Es wird in Übersichtsseiten, Artikel- und Tag-Listen etc. angezeigt und soll die Inhalte des Blog-posts unterstützend beschreiben. Auch mein &lt;code&gt;RSS&lt;/code&gt;-Feed soll diese Bilder für die Übersicht verschiedener Artikel auf Endgeräten meiner Leserschaft verwenden.&lt;/p&gt;&#10;&lt;p&gt;Es ist besonders stark komprimiert und benötigt wenig Übertragungsbandbreite. Daher ist seine Auflösung gering (&lt;code&gt;440x220&lt;/code&gt; oder &lt;code&gt;640x352&lt;/code&gt;). Für einen einheitlichen Look sind sie zudem stets monochrom.&lt;/p&gt;&#10;&lt;p&gt;Die Markdown-Syntax zum Einbinden des Covers sehe ich wie folgt vor:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;cover_alt&lt;/span&gt;: &lt;span style="color:#f92672"&gt;&amp;#39;Image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Hugo console build output. It shows a full build time below 2sec. (~90% reduction over build time with Jekyll)&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;cover&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/assets/images/migrating-hugo-to-jekyll/hugo-jekyll-build-compare-cover.avif&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Anschließend erstelle ich unter &lt;code&gt;layouts/_partials&lt;/code&gt; die Datei &lt;code&gt;cover.html&lt;/code&gt; und schreibe folgenden Code hinein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_partials/cover.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* Handles cover-image generation */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$cover&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$autoCover&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$.Site.Params.autoCover&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;index&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Params&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;cover&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Resources.GetMatch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Params.Cover&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$cover&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Resources.GetMatch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Params.Cover&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.RelPermalink&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$cover&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;absURL&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Params.Cover&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$.Site.Params.AutoCover&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Params.Cover&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Resources.GetMatch&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;cover.*&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$cover&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Resources.GetMatch&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;cover.*&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.RelPermalink&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$cover&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;!-- Cover image found --&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;img&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$cover&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post-cover&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;alt&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Params.cover_alt&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;plainify&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Description&lt;/span&gt;&lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;plainify&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Params.CoverCredit&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;plainify&lt;/span&gt;&lt;span style="color:#f92672"&gt;|&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Title&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;plainify&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Der Code sucht und extrahiert den Pfad zum Coverbild. Sollte ein valider Pfad gefunden werden, wird es als &lt;code&gt;post-cover&lt;/code&gt; mit entsprechendem CSS angezeigt.&lt;/p&gt;&#10;&lt;h2 id="manuelle-anpassungen-layouts"&gt;Manuelle Anpassungen: Layouts&lt;/h2&gt;&#10;&lt;p&gt;Damit sich meine Seite in Hugo weiterhin ganz ähnlich anfüllt wie zuvor in Jekyll muss ich in den Layouts ein paar Änderungen vornehmen. Im Folgenden habe ich sie beschrieben.&lt;/p&gt;&#10;&lt;h3 id="baseofhtml"&gt;baseof.html&lt;/h3&gt;&#10;&lt;p&gt;Hier werden die Basis-Inhalte definiert. Definitionen für Kopf- und Fußzeile werden importiert und Stylesheets eingebunden. Die allermeisten oben beschriebenen Änderungen werden an dieser Stelle importiert.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/baseof.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;html&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lang&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$.Site.Language&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;head&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Param&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;math&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;partialCached&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;math.html&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;block&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;title&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;&amp;gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.IsHome&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$.Site.Title&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Title&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt; :: &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$.Site.Title&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;title&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;partial&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;head.html&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/assets/css/gallery.css&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;relURL&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&amp;gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/assets/css/media.css&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;relURL&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;head&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;body&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$container&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cond&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;$.Site.Params.FullWidthTheme&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;container full&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cond&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;$.Site.Params.CenterTheme&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;container center&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;container&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$container&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cond&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;$.Site.Params.oneHeadingSize&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34; headings--one-size&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;partial&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;header.html&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;block&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;block&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;footer&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;partial&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;footer.html&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;body&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;html&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="listhtml"&gt;list.html&lt;/h3&gt;&#10;&lt;p&gt;Für Übersichtsseiten wie &lt;code&gt;Artikel&lt;/code&gt;, &lt;code&gt;Tags&lt;/code&gt;, &lt;code&gt;Projekte&lt;/code&gt; benötige ich eine Listenansicht. Dabei möchte ich je vier Artikel in einer Zeile anzeigen. Jeder Artikel bekommt ein eigenes &amp;ldquo;Kärtchen&amp;rdquo; mit Titelbild (cover), Titel, Kurzbeschreibung und - falls Verfügbar - weiteren Details wie Lesezeit. Hugos &lt;code&gt;paginator&lt;/code&gt; crawlt den entsprechenden Ordner und stellt die Inhalte zusammen, wärend mein Stylesheet &lt;code&gt;list.css&lt;/code&gt; Formatierung und Anzeige übernimmt.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_default/list.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;define&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/assets/css/list.css&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;relURL&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Content&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;index-content&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;posts posts-grid&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Paginator.Pages&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;article&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post on-list&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Permalink&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post-card&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aria-label&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Title&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;partial&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;cover.html&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post-card-body&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;h4&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post-title&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Title&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;markdownify&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;h4&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post-excerpt&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Description&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;p&lt;/span&gt;&amp;gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Description&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;p&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;and&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Param&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;readingTime&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;eq&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Param&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;readingTime&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post-reading-time&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.ReadingTime&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$.Site.Params.minuteReadingTime&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;min read&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;a&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;article&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;partial&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;pagination.html&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="singlehtml"&gt;single.html&lt;/h3&gt;&#10;&lt;p&gt;Meine &lt;code&gt;single,html&lt;/code&gt; ist fast identisch mit der Standardimplementierung von &lt;a href="https://github.com/panr/hugo-theme-terminal/blob/master/layouts/_default/single.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;terminal&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Ich habe lediglich die Möglichkeit hinzugefügt, ein zum Titel gehöriges &amp;ldquo;banner-image&amp;rdquo; zu haben, und importiere die &lt;code&gt;_partial/cover.html&lt;/code&gt;-Vorschrift zum Laden eines Coverbildes.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;article&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Params.banner&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;img&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;post-banner&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;alt&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$.Params.banner_alt&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;plainify&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bei all den Änderungen könnte man überlegen, ein eigenes Theme zu forken&amp;hellip; 🤔&lt;/p&gt;&#10;&lt;h2 id="direkter-vergleich"&gt;Direkter Vergleich&lt;/h2&gt;&#10;&lt;p&gt;Dann schauen wir uns jetzt mal an, wie meine Seite mit Jekyll gegenüber Hugo lädt. Hierfür öffne ich die &lt;em&gt;Entwickleroptionen (F12)&lt;/em&gt; in &lt;a href="https://www.firefox.com/de/?redirect_source=mozilla-org" target="_blank" rel="noopener noreferrer" class="external-link"&gt;meinem Browser&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, wähle das &lt;em&gt;Network&lt;/em&gt;-Tab an und klicke dort auf &lt;em&gt;All&lt;/em&gt;. Anschließend lade ich die Seite neu.&lt;/p&gt;&#10;&lt;p&gt;Dabei entsteht die folgende Auflistung:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/blog_schallbert-transfersize-loadtime-hugo.avif" alt="Image: A snapshot of my browser window with the blog&amp;#39;s landing page opened. Developer options are activated, and the lower part of the image shows a table with the individual page content files, their size, and contribution to the load time. For hugo, it transfers a total of 189kB in 175ms." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/blog_schallbert-transfersize-loadtime-jekyll.avif" alt="Image: A snapshot of my browser window with the blog&amp;#39;s landing page opened. Developer options are activated, and the lower part of the image shows a table with the individual page content files, their size, and contribution to the load time. For jekyll, it transfers a total of 328kB in 306ms." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Zu erwähnen ist, dass ich hier eigentlich gar nicht Jekyll mit Hugo vergleiche, sondern lediglich deren Ausgabe und daher die gewählten Themes maßgeblich für Größe und Ladezeit verantwortlich sind. Also vergleiche ich praktisch das &lt;em&gt;minimalmistakes theme in Jekyll&lt;/em&gt; mit dem &lt;em&gt;terminal theme in Hugo&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h3 id="größe"&gt;Größe&lt;/h3&gt;&#10;&lt;p&gt;Ein anderes Diagramm erhalte ich, wenn ich ganz unten links auf die Stoppuhr klicke und damit eine &lt;a href="https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/performance_analysis/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;performance analysis&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ausführe.&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/migrating-hugo-to-jekyll/blog_schallbert-landing-performance-analysis-terminaltheme.avif" alt="Image: A cake diagram as the output of firefox&amp;#39;s performance analysis tool in developer options. Analyzing the hugo&amp;#39;s terminal theme on a typical blog post of mine, images make up for three quarters of total transferred size. The last quarter is largely taken by css, and just 12kB html at a total size of 165kB." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die folgende Tabelle zeigt ein paar meiner Seiten im direkten Vergleich. Allerdings ist die Übersicht mit Vorsicht zu genießen: Die Werte ändern sich schon mal, wenn ich neu lade (auch mit ausgeschaltetem Cache). Gerade bei eingebundenen Inhalten Dritter wie meinen Videos kommen hier teils stark unterschiedliche Werte heraus.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Item&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;minimal-mistakes&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;terminal-schallbert&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Jekyll&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Hugo&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;transferred&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[kB]&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Diff [%]&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;time&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[ms]&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Diff [%]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Landing&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;163.47&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;98.68&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-39&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Post&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2861&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1767&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-38&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;340&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;270&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-20&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;List page&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1507&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;140&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-90&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Insgesamt habe ich mit Hugo und Terminal deutliche Optimierungen vorliegen.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Manuell implementiert in Hugo hatte ich das bedingte Laden von Katex (&lt;code&gt;90kB min.css&lt;/code&gt;), Größenbeschränkungen für Medien-Vorschaudateien und insgesamt weniger CSS.&lt;/li&gt;&#10;&lt;li&gt;Automatisch in Hugo sind Medien-Vorschaudateien von den hochauflösenden Dateien abgeleitet und per Bildprozessor auf die Darstellungsverhältnisse im gebauten Zustand platzoptimiert. Diese Optimierung spart bei vielen, klein dargestellten Bildern eine Menge Bandbreite.&lt;/li&gt;&#10;&lt;li&gt;Das Terminal-Theme zeigt anders als minimalmistakes keine Kärtchen für &amp;ldquo;weitere Posts&amp;rdquo; an. Dies spart erneut die Übertragung von vier Vorschaubildern. Der Javascript-Inhalt geht außerdem gegen null - eines der Ziele, das ich mir gesteckt hatte.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="komplexität"&gt;Komplexität&lt;/h3&gt;&#10;&lt;p&gt;Ganz klar: Hugo ist hier im Vergleich zu Jekyll unschlagbar.&lt;/p&gt;&#10;&lt;p&gt;Da Themes in Hugo steckbare Module sind, benötigen sie keinen Abhängigkeitsbaum in Form von Gems wie bei Jekyll zum Nachladen und installieren. Entsprechend kann hier viel weniger kaputt gehen. Für mich verringert sich die Bauzeit für einen &amp;ldquo;full build&amp;rdquo; (zweisprachig) um &lt;strong&gt;75%&lt;/strong&gt; und auch das automatische Bauen bei Veränderung meiner Markdown-Dateien ist sowohl &lt;strong&gt;80%&lt;/strong&gt; schneller als auch aussagekräftiger: Wenn ich in Jekyll nämlich &amp;ldquo;incremental build&amp;rdquo; einschalte, dann gibt es keine aktualisieren Paginator-Seiten mehr - sprich Listenansichten und Übersichtsseiten werden nicht mit gebaut.&lt;/p&gt;&#10;&lt;p&gt;Auf der anderen Seite kann ich in Jekyll sehr flexibel und feingranular selbst eingreifen, eigene Gems entwerfen und recht einfach besondere Gimmicks für meine Seite herstellen wie ein mitscrollendes Inhaltsverzeichnis. Jenes vermisse ich in Hugo etwas.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Für die tagtägliche Benutzbarkeit gefällt mir Hugo ganz klar besser als Jekyll.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="auslieferungcontinuous-deployment"&gt;Auslieferung/Continuous Deployment&lt;/h3&gt;&#10;&lt;p&gt;Für Jekyll gestaltete sich Bauen und Ausliefern in Gitea etwas hakelig: erst mit einem &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/"&gt;Jekyll-Dockerimage&lt;/a&gt; war ich in der Lage, &lt;a href="https://blog.schallbert.de/gitea-action-runner-native-jekyll/"&gt;ruby richtig aufzusetzen&lt;/a&gt;. Damit und weil ich meinen Actions nicht erlaube zu cachen dauert das Bauen ziemlich lang: Erst den aktuellen Docker-Container ziehen, ruby und jekyll updaten, gems installieren, die Quelldateien auschecken, anschließend bauen und ausliefern - hier vergehen gerne einmal &lt;code&gt;90 Sekunden&lt;/code&gt;, bis ein neuer Artikel live ist.&lt;/p&gt;&#10;&lt;p&gt;Für Hugo muss ich mal schauen, ob ich am Ende nicht doch einen eigenen Post mache&amp;hellip;&lt;/p&gt;&#10;</description></item><item><title>htmltest-Konfiguration für Hugo</title><link>https://blog.schallbert.de/hugo-link-checker/</link><pubDate>Mon, 10 Aug 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/hugo-link-checker/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-08-10-htmltest-success-output-cover.avif"&#10; class="post-cover"&#10; alt="Image: An SSH public key randomart image as visual fingerprint for humans to quickly see if public keys match"&#10; title="htmltest-Konfiguration für Hugo" /&gt;&#10;&lt;p&gt;Nun bin ich mit dem &lt;a href="https://blog.schallbert.de/projects/migrating-jekyll-to-hugo/"&gt;Umzug meiner Website zu Hugo&lt;/a&gt; fast fertig. Ich musste mehrere Anpassungen in der Frontmatter jedes Artikel vornehmen, damit sie in Hugo ähnliche Funktionen aufweisen wie zuvor mit Jekyll. Es ergaben sich einige Änderungen in der Ordnerstruktur teils durch die Migration, teils auch durch meine Erfahrungen als Autor, sodass auf dem Weg einige Links in praktisch jedem meiner Artikel zu überarbeiten waren. Inzwischen habe ich fast 340 davon, wenn man die Übersetzungen mitzählt.&lt;/p&gt;&#10;&lt;p&gt;Der schieren Masse der Links komme ich ohne weitere Hilfsmittel nicht bei. Also benötige ich Werkzeug.&lt;/p&gt;&#10;&lt;h2 id="htmltest"&gt;htmltest&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/wjdp/htmltest" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Htmltest&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist ein Hilfsprogramm, das HTML-Dateien nach Problemen durchsuchen kann. Unter anderem spürt es defekte Links auf. Ich installiere es per&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Red Hat-based Linux package manager install command&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo dnf install htmltest&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;und wende es auf die von Hugo &amp;ldquo;gebauten&amp;rdquo; HTMl-Dateien im &lt;code&gt;/public&lt;/code&gt;-Verzeichnis an:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;htmltest public/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✘✘✘ failed in 2.890119823s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2612&lt;/span&gt; errors in &lt;span style="color:#ae81ff"&gt;337&lt;/span&gt; documents&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Das sind aber ganz schön viele Fehler! Die Darreichungsform erscheint mir außerdem ziemlich unübersichtlich, siehe Bild.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-08-10-htmltest-error-note.avif" alt="Image: Output of a htmltest report in the console. It is completely filled with text. There are different error messages like &amp;#39;name resolution error&amp;#39;, &amp;#39;missing trailing slash&amp;#39; and so on. It is nowhere easy to read but mentions the source path of the issue." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="ausgabe-in-datei-umleiten"&gt;Ausgabe in Datei umleiten&lt;/h3&gt;&#10;&lt;p&gt;Folglich tue ich mich erstmal ziemlich schwer mit diesem Werkzeug. Ich hatte angenommen, dass mir ein paar Links kaputtgegangen sind und nicht mit tausenden Fehlern gerechnet. Also fiel ich dem typischen &lt;a href="https://en.wikipedia.org/wiki/Not_invented_here" target="_blank" rel="noopener noreferrer" class="external-link"&gt;not-invented-here&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Syndrom anheim und programmierte selbst ein Werkzeug in &lt;em&gt;python&lt;/em&gt;, welches sich im Nachhinein als nicht gut genug entpuppte. Kurzgesagt:&lt;/p&gt;&#10;&lt;p&gt;Htmltest ist ein tolles und unglaublich schnelles Werkzeug. Wenn man dessen Ausgabe leserlich macht und richtig deutet, kann man damit ziemlich glücklich werden. Mit ein bisschen Herumprobieren gelange ich zu folgendem Befehl:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;htmltest public/ 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; | ansi2html &amp;gt; htmltest-output.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;&#10;&lt;p&gt;Bedeutung: &amp;ldquo;Führe &lt;em&gt;htmltest&lt;/em&gt; im &lt;code&gt;/public/&lt;/code&gt;-Ordner aus. Leite Fehler aus &lt;code&gt;stderr&lt;/code&gt; in die Ausgabe &lt;code&gt;stdout&lt;/code&gt; um. Reiche die Ausgabe an &lt;em&gt;ansi2html&lt;/em&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; weiter. Schreibe dessen Ausgabe in eine Datei namens &lt;code&gt;htmltest-output.html&lt;/code&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-08-10-htmltest-ansi2html-output.avif" alt="Image: Output of a htmltest report in html after piping through ansi2html, opened with a browser. The lines are formatted properly and have colour coding. Looks neat." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das sieht doch so schon viel besser aus!&lt;/p&gt;&#10;&lt;h3 id="fehlerkategorien"&gt;Fehlerkategorien&lt;/h3&gt;&#10;&lt;p&gt;Die mit &lt;em&gt;htmltest&lt;/em&gt; gefundene Fehler lassen sich für meine Zwecke in ein paar wenige Kategorien sortieren:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Hugo-intern bedingte Fehler wie durch &lt;code&gt;livereload.js&lt;/code&gt; verursachte &lt;code&gt;target does not exist&lt;/code&gt;-Meldungen.&lt;/li&gt;&#10;&lt;li&gt;Unvollständige Links, die zwar in der Praxis funktionieren, aber &lt;code&gt;href lacks trailing slash&lt;/code&gt;-Fehler erzeugen. Diese Art Fehler machen bei mir den Löwenanteil der berichteten Probleme aus.&lt;/li&gt;&#10;&lt;li&gt;Lookup-Fehler &lt;code&gt;GET &amp;lt;src&amp;gt; [...] failure in name resolution&lt;/code&gt;, bei fehlender Verbindung, unzureichenden Rechten, aktiver Bot-Abwehr oder wenn der Zielserver offline ist&lt;/li&gt;&#10;&lt;li&gt;Harte, durch defekte Links erzeugte &lt;code&gt;target does not exist&lt;/code&gt;-Fehler. Genau diese wollte ich eigentlich beheben.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="das-log-bereinigen"&gt;Das Log bereinigen&lt;/h3&gt;&#10;&lt;p&gt;Wie bekomme ich nun nur noch Fehler der vierten Kategorie angezeigt? Durch ein paar Änderungen in den Werkeugen und die Beseitigung der Trailing-Slash-Fehler.&lt;/p&gt;&#10;&lt;p&gt;Fehler der Kategorie 1, die durch Hugos nützliche &lt;code&gt;livereload&lt;/code&gt;-Funktion hervorgerufen werden, sehen so aus:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt; target does not exist --- en/tags/tools/index.html --&amp;gt; /livereload.js?mindelay=10&amp;amp;v=2&amp;amp;port=1313&amp;amp;path=livereload&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;LiveReload ist diejenige Funktion, die mir ständiges &lt;code&gt;F5&lt;/code&gt;-Tippen im Browserfenster erspart, wenn Hugo eine meiner Änderungen nach dem Speichern der Markdown-Datei baut. Für den Test kann ich es jedoch kurz deaktivieren. Die folgende Lösung reduziert die Anzahl erzeugter Fehler um eins pro html-Seite.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;hugo server --disableLiveReload&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Lookup-Fehler der Kategorie 3 vermeide ich durch einen Fokus auf interne Links worüber ich die vollständige Kontrolle habe. Hierbei hilft mir der Parameter &lt;code&gt;-s, --skip-external&lt;/code&gt; in &lt;em&gt;htmltest&lt;/em&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;htmltest -s public/ 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; | ansi2html &amp;gt; htmltest-output.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✘✘✘ failed in 494.7678ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;446&lt;/span&gt; errors in &lt;span style="color:#ae81ff"&gt;337&lt;/span&gt; documents&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tote externe Links kann ich zu einem späteren Zeitpunkt beheben; nun will ich erstmal die durch die Migration entstandenen Probleme lösen.&lt;/p&gt;&#10;&lt;h2 id="missing-trailing-slash-fehlerbehebung-per-markup"&gt;&amp;ldquo;Missing trailing slash&amp;rdquo;: Fehlerbehebung per Markup&lt;/h2&gt;&#10;&lt;p&gt;Mit den vielen &lt;code&gt;href lacks trailing slash&lt;/code&gt;-Fehlern tue ich mich lange zeit schwer, müsste ich doch an fast jeden Markdown-Link in jedem Artikel ein &lt;code&gt;/&lt;/code&gt; anfügen. Zum Glück finde ich eine Lösung in meinem Markup &lt;code&gt;render-link.html&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_markup/render-link.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Destination&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;safeURL&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_external&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;or&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_anchor&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;#&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_asset&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/assets/&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[...]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* Add / to internal paths that have no file extension */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;and&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_external&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_anchor&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_asset&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$parsed&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;urls&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Parse&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;and&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$parsed&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasSuffix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$parsed&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Path&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;))&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;eq&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Ext&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$parsed&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Path&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;printf&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;%s/&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[...]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;&#10;&lt;p&gt;Bedeutung: Interne Links, die weder auf Überschriften &lt;code&gt;is_anchor&lt;/code&gt; noch Mediendateien &lt;code&gt;is_asset&lt;/code&gt; verweisen, bekommen genau dann ein &lt;code&gt;/&lt;/code&gt; angehangen, wenn sie eine korrekte Syntax aufweisen &lt;code&gt;parsed&lt;/code&gt; und noch keinen Slash am Ende der URL vorweisen können.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Dies löst 95% der Fehlermeldungen durch weniger hässliches &lt;code&gt;html&lt;/code&gt;. Die restlichen 5% hole ich mir durch Anhängen von ein paar Slashes in der Hugo-Konfiguration. Beispiele:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# hugo.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/en/&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# /en -&amp;gt; /en/ &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;logoHomeLink&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/en/&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# /en -&amp;gt; /en/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Fertig! Übrig bleiben nur noch harte, interne Fehler.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;htmltest -s public/ 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; | ansi2html &amp;gt; htmltest-output.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✘✘✘ failed in 359.28ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;102&lt;/span&gt; errors in &lt;span style="color:#ae81ff"&gt;84&lt;/span&gt; documents&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="manuelles-suchen-und-beheben"&gt;Manuelles Suchen und Beheben&lt;/h2&gt;&#10;&lt;p&gt;Was nun folgt ist reine Fleißarbeit. Ich öffne das Zieldokument (also jenes, auf welches per Link verwiesen wird) und schaue mir an, ob zum Beispiel Überschriftenlinks zwischen verschiedenen Sprachen uneinheitlich sind. Falls ja, füge ich individuelle Anchor-Links an die Überschrift an.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;### Überschrift {#heading3}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Falls in der Zieldatei alles paletti ist, öffne ich zusätzlich alle Quelldateien, die den defekten Link verwenden. Dort korrigiere ich den Link manuell. In den meisten Fällen befindet sich die Zieldatei inzwischen in einem anderen Ordner, oder der Sprachpräfix ist falsch gesetzt.&lt;/p&gt;&#10;&lt;h2 id="das-ergebnis"&gt;Das Ergebnis&lt;/h2&gt;&#10;&lt;p&gt;Nach ein paar Stunden anstrengender und dröger Handrbeit werde ich belohnt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;htmltest public/ -s 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Skipping the checking of external links.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;htmltest started at 10:51:23 on public&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;========================================================================&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✔✔✔ passed in 238.970354ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tested &lt;span style="color:#ae81ff"&gt;338&lt;/span&gt; documents&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bald kann ich mit meiner neuen alten Seite online gehen!&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;&lt;a href="https://packages.debian.org/en/sid/colorized-logs" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ansi2html bzw. colorized-logs&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist ein Hilfsprogramm, welches Konsolenausgabe inkl. Formatierung und Textfarben in &lt;code&gt;html&lt;/code&gt; umwandelt.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Hugo Multi-Lang: Interne Permalinks</title><link>https://blog.schallbert.de/hugo-create-language-specific-permalinks/</link><pubDate>Mon, 27 Jul 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/hugo-create-language-specific-permalinks/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-07-07-defaultcontentlanguage-in-hugo-config.avif"&#10; class="post-cover"&#10; alt="Image: An SSH public key randomart image as visual fingerprint for humans to quickly see if public keys match"&#10; title="Hugo Multi-Lang: Interne Permalinks" /&gt;&#10;&lt;p&gt;Wer in Hugo wie ich einen mehrsprachigen Blog baut, könnte folgendes Problem kennen:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Interne Links in Artikeln verschiedener Sprachen verweisen nur auf Seiten in der Standard-Sprache.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="konfiguration"&gt;Konfiguration&lt;/h2&gt;&#10;&lt;p&gt;Grundlage für das Problem ist die folgende Einstellung:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# hugo.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;defaultContentLanguage&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;de&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;defaultContentLanguageInSubdir&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;disableDefaultLanguageRedirect&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Meine Standardsprache ist Deutsch und zugehörige Artikel werden in das Hauptverzeichnis des Blogs gebaut. Eine Weiterleitung in das Sprachverzeichnis &lt;code&gt;/de&lt;/code&gt; findet nicht statt.&lt;/p&gt;&#10;&lt;p&gt;Die Verzeichnisstruktur lautet:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── content&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── de&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── about.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── announcements&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── legal.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── posts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   ├── privacy.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   │   └── projects&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── en&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── about.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── legal.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── posts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   ├── privacy.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│   └── projects&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="das-problem-mit-der-standardsprache"&gt;Das Problem mit der Standardsprache&lt;/h2&gt;&#10;&lt;p&gt;Ich halte Artikel durch Nutzung von &lt;a href="https://gohugo.io/content-management/urls/#slug" target="_blank" rel="noopener noreferrer" class="external-link"&gt;slug&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; in meiner Standardsprache unter Adressen erreichbar wie &lt;code&gt;example.com/my-post&lt;/code&gt;, &lt;code&gt;example.com/my-2nd-post/&lt;/code&gt;.&#10;In der Zweitsprache hingegen erscheinen sie als &lt;code&gt;example.com/en/my-post&lt;/code&gt;, &lt;code&gt;example.com/en/my-2nd-post/&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Wenn ich zwei Artikel nun intern verlinken möchte, dann verweist der Link stets auf den Artikel meiner Standardsprache.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# English version of my post&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# example.com/en/my-post&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;slug&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-post&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Post&amp;#39;s English content&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;2nd](/my-2nd-post) &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ich erreiche damit statt des gewünschten &lt;code&gt;/en/my-2nd-post&lt;/code&gt; nur die deutsche Version &lt;code&gt;/my-2nd-post&lt;/code&gt;. Das finde ich blöd, denn so müsste ich jeden Link in allen Artikeln manuell anpassen. Leider scheint Hugo dafür keine eingebaute Lösung mitzubringen. Also erstelle ich mir eine Vorschrift, wie Links gerendert werden sollen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_markup/render-link.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="lösung-sprachabhängige-link-prefixes"&gt;Lösung: Sprachabhängige Link-Prefixes&lt;/h2&gt;&#10;&lt;p&gt;Dort soll folgendes passieren: Wenn beim Bauen ein relativer Link innerhalb der Seite gefunden wird, so ist genau dann ein Sprachpräfix wie &lt;code&gt;/en&lt;/code&gt; vor diesen Link zu setzen, wenn die Sprache der zu rendernden Seite nicht der Standardsprache entspricht (denn für letztere benötige ich wegen &lt;code&gt;defaultContentLanguageInSubdir: false&lt;/code&gt; keinen Sprachpräfix).&lt;/p&gt;&#10;&lt;h3 id="standardsprache-auslesen"&gt;Standardsprache auslesen&lt;/h3&gt;&#10;&lt;p&gt;Nur wie komme ich an die Standardsprache? Die Parameter &lt;code&gt;defaultContentLanguage&lt;/code&gt; sind in Templates nicht zugänglich. Nach längerer Recherche finde ich eine passende Lösung für das Problem in der &lt;a href="https://discourse.gohugo.io/t/is-there-a-way-to-retrieve-the-value-of-defaultcontentlanguage/9643" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hugo community&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;: Die Standardsprache ist meist die erste Sprache des gewichtsbasierten Index (welcher die &lt;a href="https://gohugo.io/configuration/languages/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Sprachen nach Reihenfolge&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; erscheinen lässt): &lt;code&gt;index .Page.Site.Home.AllTranslations 0 &lt;/code&gt; Wenn ich diesen nun auslese, ist die &amp;ldquo;erste&amp;rdquo; meine Standardsprache.&lt;/p&gt;&#10;&lt;h3 id="code-für-render-linkhtml"&gt;Code für render-link.html&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* /layouts/_markup/render-link.html */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Destination&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;safeURL&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$currentLang&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Page.Language.Locale&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_external&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;or&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_anchor&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;#&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_asset&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/assets/&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_lang_prefixed&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;printf&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/%s/&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$currentLang&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_anchor&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;printf&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;%s%s&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Page.RelPermalink&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_external&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;and&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_lang_prefixed&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_asset&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Page.Language.IsDefault&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;printf&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/%s%s&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$currentLang&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{- /* Add / to internal paths that have no file extension */ -}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;and&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_external&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_anchor&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_asset&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$parsed&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;urls&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Parse&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;and&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$parsed&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;not&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasSuffix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$parsed&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Path&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;))&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;eq&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Ext&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$parsed&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.Path&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;printf&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;%s/&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;relURL&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_external&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;target&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;_blank&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;noopener noreferrer&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;external-link&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;{{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;}}&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Text&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;safeHTML&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_external&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;span&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;external-link-icon&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aria-hidden&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&amp;gt;↗&amp;lt;/&lt;span style="color:#f92672"&gt;span&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;a&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Solange die Standardsprache und der Gewichtsindex übereinstimmen, d.h. die Standardsprache ganz oben auftaucht, funktioniert dies prima.&lt;/p&gt;&#10;&lt;h3 id="zusatzfunktionen"&gt;Zusatzfunktionen&lt;/h3&gt;&#10;&lt;p&gt;Was der Code sonst noch macht:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Prüfung, ob Links nach &amp;ldquo;extern&amp;rdquo;, also von meiner Site weg gehen. Jene bekommen dann ein eigenes Fenster und den Indikator &lt;code&gt;↗&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Prüfung, ob der Link auf eine Überschrift &amp;ldquo;anchor&amp;rdquo; führt. Details dazu siehe unten.&lt;/li&gt;&#10;&lt;li&gt;Prüfung, ob der Link zu einer (Medien-)Datei &amp;ldquo;asset&amp;rdquo; führt. Asset-Links werden nicht modifiziert.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Handelt es sich um einen Überschriftslink, so wird dieser gegebenenfalls auf die vollständige URL ergänzt. Das geschieht nach folgendem Schema: &lt;code&gt;#heading --&amp;gt; /&amp;lt;opt_lang&amp;gt;/&amp;lt;opt_category&amp;gt;/&amp;lt;title&amp;gt;/#heading&lt;/code&gt;&lt;/p&gt;&#10;&lt;h2 id="permalinks"&gt;Richtig auflösende Permalinks auf Überschriften&lt;/h2&gt;&#10;&lt;p&gt;Auch für hochspezielle Permalinks innerhalb einer Seite habe ich eine Lösung gefunden. Nehmen wir an, ich habe verschiedensprachige Überschriften :&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# German&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# content/de/posts/yyyy-mm-dd-hugo-create-language-specific-permalinks.md&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Mit _markup interne Links richtig routen&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Hugo Multi-Lang: Interne Permalinks&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;slug&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hugo-create-language-specific-permalinks&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## Richtig auflösende Permalinks auf Überschriften {#permalinks}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Lorem Ipsum...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# English&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /content/en/posts/yyyy-mm-dd-hugo-create-language-specific-permalinks.md&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Routing internal links with _markup&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Hugo Multi-Lang: Internal Permalinks&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;slug&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hugo-create-language-specific-permalinks&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## Correct Permalinks to headings {#permalinks}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Lorem Ipsum...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dann sollte ein Klick auf &lt;a href="https://blog.schallbert.de/hugo-create-language-specific-permalinks/#permalinks"&gt;diesen Link zurück zur Überschrift #permalinks&lt;/a&gt; sprachunabhängig wieder zurück zur Überschrift führen. Schlüssel für diese Lösung ist das Einführen einer Überschriftsdefinition (&lt;a href="https://www.markdownlang.com/cheatsheet/headings.html#heading-ids-extended-syntax" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Heading-ID&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) in Markdown, in diesem Fall &lt;code&gt;{#permalinks}&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="funktioniert-so-noch-nicht"&gt;Funktioniert so noch nicht.&lt;/h3&gt;&#10;&lt;p&gt;Weil &lt;code&gt;#permalinks&lt;/code&gt; auf &lt;code&gt;&amp;lt;blog's baseurl&amp;gt;/#permalinks&lt;/code&gt; verweist und somit der Link auf die aktuelle Seite fehlt. Doch auch das bekommen wir hin: Ich erweitere den Link-Renderer um ein paar Zeilen Code und gebe die aktuelle Seiten-URL als Präfix hinzu.&lt;/p&gt;&#10;&lt;h3 id="code-für-render-linkhtml-mit-unterstützung-für-überschriften"&gt;Code für render-link.html mit Unterstützung für Überschriften&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_anchor&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;strings&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;.HasPrefix&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;#&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$is_anchor&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;printf&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;%s%s&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;.Page.RelPermalink&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$url&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{{-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt; &lt;span style="color:#75715e"&gt;-}}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nun wird &lt;code&gt;(#permalinks)&lt;/code&gt;erweitert zu &lt;code&gt;/hugo-create-language-specific-permalinks/#permalinks&lt;/code&gt;, sodass der Link endlich zum Ziel führt.&lt;/p&gt;&#10;</description></item><item><title>Scheitern von SSH-Verbindungen</title><link>https://blog.schallbert.de/ssh-typical-issues/</link><pubDate>Fri, 12 Jun 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/ssh-typical-issues/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-06-12-ssh-errors-thumb.avif"&#10; class="post-cover"&#10; alt="Image: A list of different SSH error messages. Some very common, but a few have rare causes."&#10; title="Scheitern von SSH-Verbindungen" /&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-06-12-ssh-icon.svg" alt="Image: terminal logo image from Aegis simple image pack, framing the topic of this post." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;In diesem kurzen Artikel geht es um Fehler beim Aufbau einer SSH-Verbindung. Egal ob andere Rechner, Server oder Dienste wie Versionsverwaltung - die Fehlermeldungen und zugehörige Lösungen sind meist gleich. Weiterlesen lohnt sich allerdings nur, wenn gängige Hilfeseiten wie &lt;a href="https://docs.github.com/en/authentication/troubleshooting-ssh" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Githubs troubleshooting-ssh&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; nicht die erwünschte Lösung brachte.&lt;/p&gt;&#10;&lt;h2 id="problem-no-route-to-host"&gt;Problem: &amp;ldquo;No Route To Host&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;Die Konsolenausgabe zeigt (im lokalen Netz):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ ssh 192.168.0.27&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh: connect to host 192.168.0.27 port 22: No route to host&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oder (in einem entfernten Netz)&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ ssh 1.2.3.4&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh: connect to host 1.2.3.4 port 22: Network is unreachable&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ursache ist sehr wahrscheinlich eine falsch eingegebene IP oder eine fehlgeschlagene DNS-Namensauflösung. Im lokalen Netz ist ein realistischer Grund, dass der Teilnehmer abgeschaltet ist und der Router ihn daher nicht erreichen kann. Abhilfe schafft meist eine Korrektur der Zieladresse, Einschalten des Ziels oder Update der Routing-Tabelle.&lt;/p&gt;&#10;&lt;h2 id="problem-connection-refused"&gt;Problem: Connection Refused&lt;/h2&gt;&#10;&lt;p&gt;Konsolenausgabe:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git push gitea&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh: connect to host git.schallbert.de port 22: Connection refused&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fatal: Could not read from remote repository.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hier kann nicht mal eine Verbindung zum Ziel hergestellt werden. Man ist also &amp;ldquo;vor&amp;rdquo; der Authentisierung schon gescheitert. Meiner Erfahrung nach werden solche Fehler aus drei Gründen erzeugt:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Der Host ist noch nicht bereit. Er bootet beispielsweise gerade (Netzwerkkarte ist an) aber der SSH-Dienst ist noch nicht hochgefahren. Hier hilft meist: abwarten.&lt;/li&gt;&#10;&lt;li&gt;Eine Firewall, ein Rate Limiter, oder Dienste wie &lt;em&gt;fail2ban&lt;/em&gt; weisen die Verbindung ab (&lt;code&gt;REJECT&lt;/code&gt;). Die Gründe hierfür sind meist zu viele erfolglose Anmeldeversuche oder eine fehlgeleitete Bot-Erkennung. In seltenen Fällen kann es aber auch sein, dass die eigene IP-Adresse auf einer &amp;ldquo;deny-list&amp;rdquo; auftaucht, die von den Zielen eingelesen wurde. Auch hier empfiehlt sich: warten.&lt;/li&gt;&#10;&lt;li&gt;Der angesprochene Dienst erwartet einen anderen Port als den SSH-Standard &lt;code&gt;Port 22&lt;/code&gt;. Somit stimmen User/Ziel-Adresse/Port nicht überein.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Das Port-Problem lässt sich leicht lösen.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-06-12-gitea-server-configuration-ssh-port.avif" alt="Image: Gitea showing SSH port IDs under /admin/config/Server_Configuration/SSH_Configuration, Port:222, Listen Port: 22" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Herausfinden des &amp;ldquo;Listen Port&amp;rdquo; auf dem Host (Server Configuration / SSH). Auf dem Bild ist dargestellt, wie die Einstellung auf meiner Gitea-Instanz aussieht.&#10;Alternativ genügt meist ein Blick in die Konfigurationsdatei des entsprechenden Services.&lt;/li&gt;&#10;&lt;li&gt;Die SSH-Konfiguration unter &lt;code&gt;~./ssh/config&lt;/code&gt; mit einer konkreten Port-Nummer versehen. Beispiel:&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /.ssh/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Host gitserver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname git.schallbert.de&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Port &lt;span style="color:#ae81ff"&gt;222&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="problem-too-many-authentication-failures"&gt;Problem: Too Many Authentication Failures&lt;/h2&gt;&#10;&lt;p&gt;Konsolenausgabe:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git pull&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Received disconnect from &amp;lt;ipAddress&amp;gt; port &amp;lt;portId&amp;gt;: Too many authentication failures&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wir haben nun also eine Verbindung zum Server 😁, aber der mag uns nicht reinlassen 😨. Immerhin wird mitgeteilt, warum: Er meint, wir hätten zu viele Schlüssel an seiner Haustür ausprobiert.&lt;/p&gt;&#10;&lt;h3 id="server-anmeldelimit-prüfen"&gt;Server-Anmeldelimit prüfen&lt;/h3&gt;&#10;&lt;p&gt;Dieser Fehler tritt auf, wenn das Server-Limit der Anmeldeversuche gerissen wird. Der Wert beträgt beim Service &lt;code&gt;sshd&lt;/code&gt; beispielsweise standardmäßig &lt;a href="https://www.baeldung.com/linux/too-many-authentication-failures-error" target="_blank" rel="noopener noreferrer" class="external-link"&gt;MaxAuthTries = 6&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Hat man viele Schlüssel für mehrere Verbindungen abgespeichert oder verwendet wie ich neuerdings &lt;a href="https://blog.schallbert.de/hardware-security-keys-yubikey/"&gt;Hardware Security Keys&lt;/a&gt; und legt Zweitschlüssel an (also &lt;code&gt;ssh-keys.count*2&lt;/code&gt;), wird dieser Wert schnell überschritten.&lt;/p&gt;&#10;&lt;p&gt;Fies bei diesem Fehler ist außerdem, dass er nur bei bestimmten Services &amp;ldquo;unten&amp;rdquo; in des Agents Schlüsselbund auftritt: unter den ersten sechs dürfen ja fünf Schlüssel falsch sein. Zudem schützt einen nicht, wenn man die Schlüssel in der &lt;code&gt;~./ssh/config&lt;/code&gt; korrekt hinterlegt hat. Der Agent nimmt stets alle Schlüssel zum Probieren mit, wenn man es ihm nicht explizit verbietet.&lt;/p&gt;&#10;&lt;p&gt;Das Verbot erteilen wir per &lt;code&gt;IdentitiesOnly yes&lt;/code&gt;. Hiermit wird dem Agent klar gesagt, dass er nur die für diesen Host explizit angegebenen Identities (Also Schlüsseldateien oder Nutzername/Passwort) verwenden soll.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /.ssh/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host gitserver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname git.schallbert.de&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Port &lt;span style="color:#ae81ff"&gt;222&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User &amp;lt;user&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PreferredAuthentications publickey&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IdentityFile &amp;lt;path_to_private_key1&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Identityfile &amp;lt;path_to_private_key2&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IdentitiesOnly yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="git-config-überprüfen"&gt;Git-Config überprüfen&lt;/h3&gt;&#10;&lt;p&gt;Diesen Fehler bekommt man auch, wenn die Git-Konfiguration des Repository unter &lt;code&gt;&amp;lt;reponame&amp;gt;/.git/config&lt;/code&gt; nicht mit dem in der SSH-Konfiguration &lt;code&gt;~/.ssh/config&lt;/code&gt; hinterlegten Datensatz für Server und Repository übereinstimmt. Ändert man die SSH-Konfiguration, so sind alle auf diesem Server bereitgehaltenen Repo-Konfigurationen ebenfalls zu ändern.&lt;/p&gt;&#10;&lt;p&gt;Beispiel:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;lt;reponame&amp;gt;/.git/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;remote &lt;span style="color:#e6db74"&gt;&amp;#34;origin&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ssh://git@git.schallbert.de:222/schallbert/&amp;lt;reponame&amp;gt;.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;fetch &lt;span style="color:#f92672"&gt;=&lt;/span&gt; +refs/heads/*:refs/remotes/origin/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;branch &lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;remote &lt;span style="color:#f92672"&gt;=&lt;/span&gt; origin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;merge &lt;span style="color:#f92672"&gt;=&lt;/span&gt; refs/heads/main&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;vscode-merge-base &lt;span style="color:#f92672"&gt;=&lt;/span&gt; origin/main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In diesem Falle wird die &lt;code&gt;gitserver&lt;/code&gt;-Konfiguration gar nicht verwendet, sondern sich direkt einwählt. Dies führt dazu, dass der Agent kein &lt;code&gt;IdentitiesOnly yes&lt;/code&gt; Flag mitgeliefert bekommt und er Schlüssel durchprobiert, bis der Server abwinkt. Lösung:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;remote &lt;span style="color:#e6db74"&gt;&amp;#34;origin&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; git@gitserver:schallbert/&amp;lt;reponame&amp;gt;.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;fetch &lt;span style="color:#f92672"&gt;=&lt;/span&gt; +refs/heads/*:refs/remotes/origin/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="problem-permission-denied-publickeypassword"&gt;Problem: Permission Denied (publickey,password)&lt;/h2&gt;&#10;&lt;p&gt;Konsolenausgabe:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ ssh -T git@gitserver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git@git.schallbert.de: Permission denied &lt;span style="color:#f92672"&gt;(&lt;/span&gt;publickey,password&lt;span style="color:#f92672"&gt;)&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dieses Problem kann leider viele verschiedene Ursachen haben. Ein paar räumt Githubs &lt;a href="https://docs.github.com/en/authentication/troubleshooting-ssh/error-permission-denied-publickey" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bereits aus dem Weg, darunter:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;sudo&lt;/code&gt; verwendet&lt;/li&gt;&#10;&lt;li&gt;Falscher Server&lt;/li&gt;&#10;&lt;li&gt;nicht den &lt;code&gt;git&lt;/code&gt;-user verwendet&lt;/li&gt;&#10;&lt;li&gt;Falschen Schlüssel verwendet (client)&lt;/li&gt;&#10;&lt;li&gt;Schlüssel nicht hinterlegt (server)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Die Ausgabe bei falschem Username sieht so aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ ssh -T mit@gitserver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mit@git.schallbert.de: Permission denied &lt;span style="color:#f92672"&gt;(&lt;/span&gt;publickey&lt;span style="color:#f92672"&gt;)&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Eine weitere Ursache kann eine fehlende Abstimmung zwischen der Konfigurationsdatei &lt;code&gt;~./ssh/config&lt;/code&gt; und dem im &lt;em&gt;git client&lt;/em&gt; hinterlegten Remote-Adresse liegen. Sind diese nicht deckungsgleich, wird eine eventuell bestehende Konfiguration gar nicht erst benutzt.&lt;/p&gt;&#10;&lt;p&gt;Eine Überprüfung kann man mit &lt;code&gt;git remote -v&lt;/code&gt; durchführen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git remote -v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;origin&#9;ssh://git@git.schallbert.de:222/schallbert/blog.git &lt;span style="color:#f92672"&gt;(&lt;/span&gt;fetch&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;origin&#9;ssh://git@git.schallbert.de:222/schallbert/blog.git &lt;span style="color:#f92672"&gt;(&lt;/span&gt;push&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Korrekt wäre mit der verbesserten Konfigurationsdatei (Ports eindeutig gesetzt, Identity Files definiert und auf die Angegebenen beschränkt):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Update link to server&amp;#39;s git repository, use host alias from config file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git remote update origin git@gitserver:schallbert/blog.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Now check if the update was effective&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git remote -v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;origin&#9;git@gitserver:schallbert/blog.git &lt;span style="color:#f92672"&gt;(&lt;/span&gt;fetch&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;origin&#9;git@gitserver:schallbert/blog.git &lt;span style="color:#f92672"&gt;(&lt;/span&gt;push&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="problem-agent-refused-operation"&gt;Problem: Agent Refused Operation&lt;/h3&gt;&#10;&lt;p&gt;Konsolenausgabe:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;local-setup&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git push --set-upstream origin local-setup &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sign_and_send_pubkey: signing failed &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ED25519-SK &amp;lt;path-to-private-key&amp;gt; from agent: agent refused operation&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Diesen Fehler sehe ich erst, seitdem ich &lt;a href="https://blog.schallbert.de/hardware-security-key-add-ssh/"&gt;Hardware Security Keys für ssh verwende&lt;/a&gt;. Aus meiner Sicht kann er zwei Ursachen haben. Beide sind zum Glück leicht zu beheben:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Der Agent kennt den Schlüssel nicht. Der Befehl &lt;code&gt;ssh-add&lt;/code&gt; löst das Problem. Prüfung per &lt;code&gt;ssh-add -l&lt;/code&gt; | grep &amp;ldquo;&amp;lt;your-key&amp;rsquo;s-comment&amp;gt;&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Der angeforderte Schlüssel ist nicht da. Kann bei HSK und HSM passieren, falls sie nicht eingesteckt sind, nicht richtig auf dem NFC-Lesegerät platziert sind oder das HSM im Netzwerk nicht verfügbar ist. Oder wenn manuell im Verzeichnis &lt;code&gt;~./ssh/config&lt;/code&gt; gearbeitet wurde und der Schlüssel nun umbenannt oder gelöscht ist.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Hardware Security Key: SSH einrichten</title><link>https://blog.schallbert.de/hardware-security-key-add-ssh/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/hardware-security-key-add-ssh/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-ssh-randomart_thumb.avif"&#10; class="post-cover"&#10; alt="Image: An SSH public key randomart image as visual fingerprint for humans to quickly see if public keys match"&#10; title="Hardware Security Key: SSH einrichten" /&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;In meinem Post über &lt;a href="https://blog.schallbert.de/hardware-security-keys-yubikey/"&gt;Hardware Security Keys&lt;/a&gt; schrieb ich, dass ich möglichst viele meiner Verbindungen passwortlos mit meinem neuen HSK absichern möchte. Ich verwende &lt;em&gt;SSH&lt;/em&gt;-Verbindungen für verschiedene Dienste und Zwecke: Ich greife auf meinen &lt;a href="https://en.wikipedia.org/wiki/Virtual_private_server" target="_blank" rel="noopener noreferrer" class="external-link"&gt;VPS&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zu, benutze Dienste wie Github, Gitea sowie meine eigene &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;Gitea-Instanz&lt;/a&gt; und lasse die Erzeugung von Backups im lokalen Netz über &lt;a href="https://linuxize.com/post/how-to-use-scp-command-to-securely-transfer-files/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;scp&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; laufen, welches das SSH-Protokoll verwendet.&lt;/p&gt;&#10;&lt;h2 id="ssh-zugriff-für-hardware-security-keys-einrichten"&gt;SSH-Zugriff für Hardware Security Keys einrichten&lt;/h2&gt;&#10;&lt;p&gt;HSKs wie mein Yubikey können private Schlüssel für SecureShell-Zugriffe abspeichern. Somit muss man sie nicht mehr lokal auf jedem Endgerät ablegen (das bleibt aber konfigurierbar, siehe unten). Zudem sind sie gegenüber der Verwahrung auf einem Rechner besser gegen unbefugte Zugriffe geschützt.&lt;/p&gt;&#10;&lt;p&gt;Also probiere ich aus, meinen Github-SSH-Zugriff auf HSK umzustellen und folge &lt;a href="https://www.yubico.com/blog/github-now-supports-ssh-security-keys/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Yubicos Anleitung für passwordlose Authentisierung für Github&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Das Verfahren zur Erzeugung von SSH-Schlüsseln nach FIDO unterscheidet sich für verschiedene Schlüsselhersteller nicht. Ich verlinke ebenfalls die &lt;a href="https://docs.nitrokey.com/nitrokeys/features/fido2/ssh" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Nitrokey-Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; als Referenz.&lt;/p&gt;&#10;&lt;h2 id="was-so-nicht-funktioniert"&gt;Was so nicht funktioniert&lt;/h2&gt;&#10;&lt;p&gt;Doch mit den Empfehlungen von Yubico selbst komme ich auf meinem Betriebssystem und der Standardinstallation von &lt;code&gt;openssh &lt;/code&gt;nicht zum Ziel. Das aus der Anleitung zusammengebaute Kommando lautet:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh-keygen -t ed25519-sk -O resident -O verify-required -O application&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ssh:github -O user&lt;span style="color:#f92672"&gt;=&lt;/span&gt;schallbert -C &lt;span style="color:#e6db74"&gt;&amp;#34;schallbert@github.com&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nach Erstellung muss man den öffentliche Schlüssel (Dateiendung &lt;code&gt;.pub&lt;/code&gt;) in der Web-Anwendung des jeweiligen Dienstes im Menüpunkt SSH-Zugang hinterlegen.&lt;/p&gt;&#10;&lt;h3 id="bedeutung-des-kommandos-und-der-wichtigsten-optionen"&gt;Bedeutung des Kommandos und der wichtigsten Optionen&lt;/h3&gt;&#10;&lt;p&gt;Dieser Befehl&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; sagt der Anwendung &lt;a href="https://www.man7.org/linux/man-pages/man1/ssh-keygen.1.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ssh-keygen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, dass sie mir einen Schlüssel des Typs &lt;code&gt;-t ed25519-sk&lt;/code&gt; erzeugen soll. Die Endung &lt;code&gt;-sk&lt;/code&gt; sagt aus, dass der Schlüssel auf einem Security Key - in meinem Fall dem Yubikey - gespeichert werden soll.&lt;/p&gt;&#10;&lt;p&gt;Um das Flag &lt;code&gt;-O resident&lt;/code&gt; zu verstehen muss man wissen, dass FIDO Authenticator-Schlüssel wie mit &lt;code&gt;-sk&lt;/code&gt; erzeugt aus zwei Teilen bestehen: Ein &lt;code&gt;key handle&lt;/code&gt; Teil, welcher auf der Festplatte des Rechners verbleibt und der private Schlüssel selbst, welcher &lt;a href="https://www.man7.org/linux/man-pages/man1/ssh-keygen.1.html#FIDO_AUTHENTICATOR" target="_blank" rel="noopener noreferrer" class="external-link"&gt;nur auf dem HSK existiert&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Nur zusammen können sie Authentisierungsanfragen korrekt beantworten. Das Flag selbst weist jedoch an, beide Schlüsselteile auf den Yubikey zu schreiben. Das erleichtert die Verwendung mehrerer Geräte mit demselben Schlüssel: Der &lt;code&gt;key handle&lt;/code&gt;-Teil kann vom Yubikey ausgelesen und auf anderen Endgeräten abgelegt werden. Auf der anderen Seite jedoch genügt nun das Verlorengehen des Yubikey allein, um möglichen Angreifern SSH-Zugriff zu geben.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-f filename&lt;/code&gt; legt Namen für die Schlüsseldateien fest. Macht besonders dann Sinn, wenn man mehrere SSH-Verbindungen oder HSM verwalten muss. Ich habe mir eine Konvention erstellt, um nicht durcheinander zu kommen. Diese orientiert sich stark an der Schreibweise des Schlüssel-Auslesens von einem HSK (&lt;code&gt;ssh-keygen -K&lt;/code&gt;).&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-f &amp;lt;keyType&amp;gt;_&amp;lt;protocol&amp;gt;:&amp;lt;service&amp;gt;_&amp;lt;user&amp;gt;_&amp;lt;device&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Als konkretes Beispiel würde mein Dateiname hier wie folgt lauten:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-f ed25519-sk_ssh:github_schallbert_yubikey-main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;⚠️ Obacht bei Nutzung von Windows: Dort ist der Doppelpunkt &lt;code&gt;:&lt;/code&gt; nicht als Zeichen in Dateinamen erlaubt. Er wird durch den Unterstrich &lt;code&gt;_&lt;/code&gt; ersetzt, was in der &lt;code&gt;config&lt;/code&gt; entsprechend abgebildet werden muss (&lt;code&gt;strg+h&lt;/code&gt; -&amp;gt; &lt;code&gt;:&lt;/code&gt; ersetzen mit &lt;code&gt;_&lt;/code&gt;)&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-C schallbert@github.com&lt;/code&gt; fügt der Schlüsseldatei selbst einen Kommentar hinzu, zum Beispiel eine Kontaktadresse. Somit bekommt der Schlüssel ein Label, was die Handhabung unter mehreren vereinfacht.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-O application=ssh:github&lt;/code&gt; dient nur der sauberen Auflistung im Authenticator-Programm und sorgt dafür, dass ich Schlüssel nicht miteinander verwechseln kann. Ansonsten steht dort nämlich nur &lt;code&gt;ssh:keygen&lt;/code&gt; und ich kann nicht sehen, für welche Services ich Schlüssel hinterlegt habe. Außerdem hilfreich bei der Verwendung von &lt;code&gt;-resident&lt;/code&gt;, da bei der Extraktion der Schlüssel mit &lt;code&gt;ssh-keygen -K&lt;/code&gt; Unterscheidbarkeit gegeben bleibt.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-O user=schallbert&lt;/code&gt; hat für mich keinen praktischen Nutzen, da SSH dieses Flag nicht interessiert und der Yubikey Authenticator meinen Nutzernamen anders als erwartet nicht anzeigt.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-O no-touch-required&lt;/code&gt; vermeidet, dass der Hardware Security Key bei jeder Verwendung des Schlüssels berührt werden muss. Nützlich bei sehr häufiger Verwendung, allerdings Sicherheitsverlust durch Automatisierbarkeit.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-O verify-required&lt;/code&gt; sagt aus, dass die PIN des HSM abzufragen ist, bevor der Schlüssel genutzt werden kann. Eigentlich als zusätzliche Sicherheit gedacht, bekomme ich damit in meiner Shell jedoch Probleme. Und zwar wird die PIN einmal abgefragt, erzeugt aber vom &lt;code&gt;ssh-agent&lt;/code&gt; nicht jedes mal eine Abfrage in meiner Konsole. Sobald ich in einer anderen Session versuche mich per SSH zu verbinden, erhalte ich eine Fehlermeldung &lt;code&gt;agent refused operation&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh -T git@github.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sign_and_send_pubkey: signing failed &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ED25519-SK &lt;span style="color:#e6db74"&gt;&amp;#34;schallbert@github.com&amp;#34;&lt;/span&gt; from agent: agent refused operation&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@opnsense: Permission denied &lt;span style="color:#f92672"&gt;(&lt;/span&gt;publickey&lt;span style="color:#f92672"&gt;)&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="woran-hat-es-gelegen"&gt;Woran hat es gelegen?&lt;/h2&gt;&#10;&lt;p&gt;Nach langer, frustrierender Fehlersuche (Was mache ich falsch? Funktioniert es mit dem Zweitschlüssel? Warum geht es beim ersten mal, danach aber nicht mehr? Was ist, wenn ich die Schlüssel erneut erstelle? Was passiert an anderen Rechnern? Was meint &amp;ldquo;KI&amp;rdquo; dazu? - Letzteres kostete bestimmt eine Stunde und ließ mich komplett auf der Stelle treten) probiere ich es mit einem anderen Kommando. Das funktioniert auf Anhieb.&lt;/p&gt;&#10;&lt;h2 id="was-hingegen-funktioniert"&gt;Was hingegen funktioniert&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh-keygen -t ed25519-sk -O application&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ssh:github -C &lt;span style="color:#e6db74"&gt;&amp;#34;schallbert@github.com&amp;#34;&lt;/span&gt; -f ed25519-sk_ssh:github_schallbert_yubikey-main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Lasse ich &lt;code&gt;verify-required&lt;/code&gt; weg, kann ich mich erfolgreich anmelden und der SSH-agent läuft sauber durch.&lt;/p&gt;&#10;&lt;h3 id="was-verify-required-benötigt"&gt;Was &lt;code&gt;verify-required&lt;/code&gt; benötigt&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://superuser.com/questions/1804740/yubikey-ssh-authentication-fails-with-signing-failed-for-ecdsa-sk" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dieser Post im Forum superuser&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bringt für mein Fehlerbild Licht ins Dunkel. Die Terminal-Session mit der ich das SSH-Schlüsselpaar erstelle speichert die per &lt;code&gt;verify-required&lt;/code&gt; Flag erzwungene Pin-Eingabe zwischen. Daher funktioniert der Login erst einmal. Logge ich mich später oder in einem anderen Fenster ein, wie ich es oben getan habe um z.B. &lt;code&gt;git pull&lt;/code&gt; in einem meiner Repos zu prüfen, fragt der SSH-Agent meine PIN nicht ab.&lt;/p&gt;&#10;&lt;p&gt;Das liegt wohl daran, dass der SSH-Agent selbst keine Prompts an das anfragende Programm ausgeben kann. Abhilfe schafft ein Untermodul von Openssh: &lt;a href="https://packages.fedoraproject.org/pkgs/openssh/openssh-askpass/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;openssh-askpass&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Dieses kann man dem Agent übergeben, welcher dann die PIN-Abfrage über &lt;em&gt;askpass&lt;/em&gt; an den User übermittelt.&lt;/p&gt;&#10;&lt;h3 id="installation-von-ssh-askpass"&gt;Installation von ssh-askpass&lt;/h3&gt;&#10;&lt;p&gt;Mein Betriebssystem hatte &lt;em&gt;ssh-askpass&lt;/em&gt; nicht installiert, daher hole ich das hier nach. Es verwendet den Paketmanager &lt;code&gt;DNF&lt;/code&gt; &amp;ldquo;Dandified YUM&amp;rdquo; statt &lt;em&gt;apt&lt;/em&gt; oder anderer gängiger Manager. Dabei ist die Namensgebung meiner Meinung nach nicht 100% einheitlich, spreche ich den ssh-Programme doch im Terminal mit &lt;em&gt;ssh-agent&lt;/em&gt; an, muss jedoch &lt;em&gt;openssh-agent&lt;/em&gt; tippen für die Installation.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# verify-required needs openssh subpackage through Fedora&amp;#39;s package manager&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# sudo dnf install openssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Installing:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete!&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nun will ich dem Agent mitteilen, dass er &lt;em&gt;ssh-askpass&lt;/em&gt; zum Erfragen der PIN ansprechen kann. Dafür mus ich wissen, in welchem Pfad sich das Submodul befindet.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# which openssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/which: no openssh-askpass &lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Komisch, aber ich hatte es doch installiert! Frage ich mal den Paketmanager:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# dnf repoquery --list openssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/libexec/openssh/ssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aha, openssh versteckt sich im Ordner &lt;code&gt;libexec&lt;/code&gt;. Na dann kann ich das Modul jetzt dem Agent bekanntgeben.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# eval &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;ssh-agent -s; SSH_ASKPASS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/usr/libexec/openssh/ssh-askpass&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Agent pid &lt;span style="color:#ae81ff"&gt;16747&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-ssh-askpass-prompt.avif" alt="Image: Testing the ssh connection to Github with ssh -T and getting prompted for HSK device pin" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich prüfe die Verbindung zu Github und bekomme wie gewünscht einen Dialog angezeigt, der meine PIN abfragt. Anschließend muss ich den HSK noch berühren und bekomme dann die Erfolgsmeldung. Damit das alles auch in einem neuen Terminal und nach Neustart des Rechners funktioniert, füge ich eine Referenz auf den Schlüssel in meine Konfigurationsdatei ein.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ~/.ssh/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host github&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname github.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PreferredAuthentications publickey&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IdentityFile ~/.ssh/ed25519-sk_ssh:github_schallbert_yubikey-main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Um auf Nummer sicher zu gehen, den Agenten neustarten und neue Terminalsessions erstellen. Sollten Zugangsdaten im Cache gewesen sein, ist das hiermit zurückgesetzt. Anschließend erneut die SSH-Verbindung testen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; schallbert@machine:~# eval &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;ssh-agent -k&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; eval &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;ssh-agent -s&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="nervig-gnome-popup-allow-inhibiting-shortcuts"&gt;Nervig: Gnome-Popup &amp;ldquo;Allow inhibiting shortcuts&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-gnome-inhibit-shortcuts-prompt.avif" alt="Image: Gnome popup *Allow inhibiting shortcuts*" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Einziger Wehrmutstropfen ist, dass ich jetzt beim Verbinden mit SSH ein zusätzliches Popup bekomme. Das wird durch die grafische Nutzeroberfläche &lt;em&gt;gnome&lt;/em&gt; ausgelöst. Leider finde ich keine Option, die zugehörige Einstellung permanent speichern. Somit wird es bei jeder Verbindung mit &lt;code&gt;verify-required&lt;/code&gt; angezeigt. Bei Gnome wird ist das Problem bereits &lt;a href="https://gitlab.gnome.org/GNOME/gnome-connections/-/work_items/85" target="_blank" rel="noopener noreferrer" class="external-link"&gt;für andere Anwendungen bekannt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, anscheinend aber noch nicht abgestellt.&lt;/p&gt;&#10;&lt;h2 id="übertragung-auf-windows-systeme"&gt;Übertragung auf Windows-Systeme&lt;/h2&gt;&#10;&lt;p&gt;Ich habe meine Schlüssel ja mit der Option &lt;code&gt;-O resident&lt;/code&gt; erzeugt. Da liegt es nahe, die Extraktion des &lt;code&gt;key-handle&lt;/code&gt; mal auf einem komplett anderen System zu testen: &lt;em&gt;Windows&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Ich öffne die Kommandozeile und tippe&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@windows-machine:~# ssh-keygen -K&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Enter PIN &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; authenticator:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;You may need to touch your authenticator to authorize key download.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Provider &lt;span style="color:#e6db74"&gt;&amp;#34;internal&amp;#34;&lt;/span&gt; returned failure -1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Unable to load resident keys: invalid format&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Invalid format? Im Netz steht öfters geschrieben, dass die Schlüssel manchmal am Ende noch ein &lt;a href="https://stackoverflow.com/questions/47859437/windows-10-openssh-key-invalid-format#68593742" target="_blank" rel="noopener noreferrer" class="external-link"&gt;LineFeed-Zeichen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; benötigen um richtig erkannt zu werden. Ohne Download vom HSK kann ich sie aber nicht modifizieren. Also übertrage ich öffentlichen Schlüssel und key-handle manuell auf den Rechner und probiere testweise das &lt;code&gt;git pull&lt;/code&gt;-Kommando aus. Und siehe da: Es klappt!&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-windows-fido-key-ssh-pin-prompt.avif" alt="Image: FIDO ssh connection test on a Windows machine: OS prompts for PIN entry. It works!" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Möchte man also die Schlüssel mit verschiedenen Betriebssystemfamilien verwenden, nutzt die Option &lt;code&gt;-resident&lt;/code&gt; anscheinend nichts. Der Befehl &lt;code&gt;ssh-keygen -K&lt;/code&gt; ist nicht granular steuerbar: Er lädt alle auf dem HSK befindlichen resident keys herunter.&lt;/p&gt;&#10;&lt;h2 id="vps-vorsicht-bei-schlüssel-ersatz"&gt;VPS: Vorsicht bei Schlüssel-Ersatz&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-hetzner-key-add-irrelevant-for-existing-servers.avif" alt="Image: Hetzner telling me that new SSH keys will not propagate to existing resources (VPS). Important to know!" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Noch ein Hinweis aus eigener Erfahrung: Hat man bereits SSH-Schlüssel hinterlegt bei einem Dienstanbieter, der selber Ressourcen bereitstellt (In meinem Fall einen Server als VPS) so werden neue SSH-Schlüssel meist nicht über das Web-Frontend an diese Ressource weitergereicht.&lt;/p&gt;&#10;&lt;p&gt;Die &amp;ldquo;alten&amp;rdquo; Schlüssel werden folglich weiterhin benötigt und die Neuen nicht verwendet, obwohl sie in der Web-App abgelegt sind!&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Die neuen Schlüssel müssen dem Ziel manuell bekanntgemacht werden.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Das führe ich der &lt;a href="https://community.hetzner.com/tutorials/howto-ssh-key/de#schritt-4---ssh-key-auf-server-hinterlegen" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung von meinem Hoster Hetzner&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; folgend mit dem Befehl &lt;code&gt;ssh-copy-id&lt;/code&gt; aus.&lt;/p&gt;&#10;&lt;h3 id="neue-schlüssel-manuell-auf-vps-ablegen"&gt;Neue Schlüssel manuell auf VPS ablegen&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh-copy-id -i ./ed25519-sk_ssh:server_schallbert_yubikey-main.pub server &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dabei ist &lt;code&gt;server&lt;/code&gt; der SSH-Alias für meinen VPS. Ich verbinde mittels des bekannten, &amp;ldquo;alten&amp;rdquo; Schlüssels lasse den &amp;ldquo;Neuen&amp;rdquo; ablegen. Doch irgend etwas funktioniert nicht wie erwartet:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt;, to filter out any that are already installed&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/ssh-copy-id: WARNING: All keys were skipped because they already exist on the remote system.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;&#9;&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; you think this is a mistake, you may want to use -f option&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="schlüssel-erfolgreich-übertragen"&gt;Schlüssel erfolgreich übertragen?&lt;/h3&gt;&#10;&lt;p&gt;Ich logge mich also auf meinem Server ein und prüfe, ob der neue Schlüssel tatsächlich in der &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; Datei auftaucht. Tut er nicht. Also führe ich das obige Kommando erneut aus, diesmal mit aktivierter &lt;code&gt;-f (Force)&lt;/code&gt; Option. Und tatsächlich, die Übertragung klappt!&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh-copy-id -f -i ./ed25519-sk_ssh:server_schallbert_yubikey-main.pub server &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Number of key&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt; added: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Now try logging into the machine, with: &lt;span style="color:#e6db74"&gt;&amp;#34;ssh -i &amp;lt;identityFile&amp;gt; &amp;#39;server&amp;#39;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;and check to make sure that only the key&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt; you wanted were added.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Zum Testen wie empfohlen nun mit dem neuen Schlüssel einloggen und&amp;hellip; Läuft! Erst jetzt können die alten Schlüssel sicher gelöscht werden: &lt;code&gt;shred -u &amp;lt;path/to/private/key&amp;gt;&lt;/code&gt;&lt;/p&gt;&#10;&lt;h3 id="auf-direktem-wege-ohne-ssh-copy-id"&gt;Auf direktem Wege ohne ssh-copy-id&lt;/h3&gt;&#10;&lt;p&gt;Will man den neuen Schlüssel ohne zusätzliche Werkzeuge bekannt machen, so kann man den Inhalt der öffentlichen Schlüsseldatei &lt;code&gt;&amp;lt;key-filename&amp;gt;.pub&lt;/code&gt; auch direkt selbst in eine neue Zeile von &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; schreiben. Dafür benötigt man lediglich eine offene SSH-Verbindung zum Ziel.&lt;/p&gt;&#10;&lt;h2 id="überlegungen-zu-komfort-und-bedienbarkeit"&gt;Überlegungen zu Komfort und Bedienbarkeit&lt;/h2&gt;&#10;&lt;p&gt;Wenn ich mir meine SSH-Verbindungen so ansehe, dann teilen sie sich grob in zwei Kategorien:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Admin-Zugänge&lt;/strong&gt; (VPS, NAS, Raspberry Pi) und Signaturen&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Protokoll-Zugänge&lt;/strong&gt; (Git etc.)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Erstere erfordern aus meiner Sicht ein höheres Sicherheitsniveau. Zudem nutze ich sie nicht so häufig wie Versionsverwaltungen. Daher empfehle ich für die erste Kategorie &lt;code&gt;verify-required&lt;/code&gt; und würde dies bei der Zweiten weglassen. Mit der Option &lt;code&gt;no-touch-required&lt;/code&gt; würde ich ebenso verfahren: falls alle paar Minuten ein Commit hochgeladen werden soll, werden ständige Berührungen des Schlüssels schnell irritierend.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Alle Optionen bekommt man bei Aufruf des Handbuchs per &lt;code&gt;man ssh-keygen&lt;/code&gt; angezeigt.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Hardware Security Keys</title><link>https://blog.schallbert.de/hardware-security-keys-yubikey/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/hardware-security-keys-yubikey/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-yubikey_thumb.avif"&#10; class="post-cover"&#10; alt="Image: Browser (Firefox) login process for passkey usage: PIN, then touch"&#10; title="Hardware Security Keys" /&gt;&#10;&lt;h2 id="begriffsabgrenzung-tpm-und-hsk"&gt;Begriffsabgrenzung: TPM und HSK&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: center"&gt;Akronym&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: center"&gt;Bezeichnung&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: center"&gt;Kurzbeschreibung&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;TPM&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;Trusted Platform Module&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;Fest verbaut. Schützt das Gerät selbst&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;HSK&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;Hardware Security Key&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;Mobil. Schützt Konten, Zertifikate, private Schlüssel&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Das &lt;a href="https://de.wikipedia.org/wiki/Trusted_Platform_Module" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Trusted Platform Module&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist ein Sicherheitschip, der kryptografische Schlüssel sicher speichert und Funktionen wie Secure Boot, BitLocker-Verschlüsselung und Geräteidentität bereitstellt. Er ist fest auf der Hauptplatine eines Computers oder Smartphones verlötet.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-this-is-a-yubikey.avif" alt="Image: A Hardware Security Key with USB-C connector called YubiKey5 NFC." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ein &lt;a href="https://en.wikipedia.org/wiki/Hardware_security_module" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hardware Security Key &lt;em&gt;HSK&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, Hardware-Sicherheitsschlüssel hingegen ist meist mobil und kann mit verschiedenen Geräten verbunden werden. Es kann Schlüssel nicht nur speichern, sondern meist auch kryptografische Operationen (Schlüsselgenerierung, Signatur, Verschlüsselung) ausführen. HSKs sind hochsicher und manipulationsgeschützt konstruiert. Sie können extern zertifiziert bzw. auditiert sein, sodass auch hochsensible Vorgänge wie Zahlungstransaktionen ausgeführt werden können.&lt;/p&gt;&#10;&lt;h2 id="was-bisher-geschah"&gt;Was bisher geschah&lt;/h2&gt;&#10;&lt;p&gt;Ich hörte einen Podcast vom Bundesamt für Sicherheit in der Informationstechnik (BSI) zum Thema &lt;a href="https://www.bsi.bund.de/SharedDocs/Audio/DE/BSI/Update_verfuegbar_Folge40_2024_02_29.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&amp;ldquo;Passkey statt Passwort&amp;rdquo;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Den Empfehlungen folgend habe ich mir ein mobiles Hardware-Sicherheitsmodul (HSM) gekauft, um in Zukunft &lt;a href="https://en.wikipedia.org/wiki/WebAuthn" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Passkeys&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für meine Dienste zu verwenden. Schon lange bin ich nicht mehr glücklich mit meiner fragmentierten Geheimnisverwaltung aus Zetteln, Browser-Plugins, offline-Passwortmanagern wie &lt;a href="https://en.wikipedia.org/wiki/KeePass" target="_blank" rel="noopener noreferrer" class="external-link"&gt;KeePass&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, auf Festplatte abgelegten &lt;a href="https://en.wikipedia.org/wiki/Secure_Shell" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;SSH&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Schlüsseln, Backup-Keys und &lt;a href="https://askleo.com/how-should-i-keep-recovery-codes-secure/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Notfallcodes&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; diverser Anbieter.&lt;/p&gt;&#10;&lt;p&gt;Ich möchte endlich mal Ordnung in die Landschaft bringen, ohne mich an einen kommerziellen Anbieter so fest zu binden, dass ich von ihm abhängig werde.&lt;/p&gt;&#10;&lt;h2 id="zielvorstellung"&gt;Zielvorstellung&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Keine Passwörter oder Login-Daten mehr merken müssen&lt;/li&gt;&#10;&lt;li&gt;Möglichst einfache, dabei sichere Verwaltung von Passwörtern aufbauen&lt;/li&gt;&#10;&lt;li&gt;Ordnung in den Accountwust bringen&lt;/li&gt;&#10;&lt;li&gt;Digitalen Nachlass regeln&lt;/li&gt;&#10;&lt;li&gt;Anderen ermöglichen, es mir gleichzutun&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Welche Erwartung ich jetzt schon einmal nehmen kann: Ganz ohne Passwörter oder Pins geht es auch mit dem HSK nicht. Ein Yubikey ist kein Passwortmanager. Er kann zwar Zertifikate, Passkeys und Secrets für OTPs halten, gibt diese Geheimnisse dann aber nicht mehr preis. Mit seiner Hilfe kann man signieren (Authentizität beweisen), Verschlüsseln und abgeleitete Schlüssel erstellen.&lt;/p&gt;&#10;&lt;p&gt;Vielmehr lässt sich die Passwortverwaltung mit Hilfe eines Hardware-Sicherheitsschlüssels aber zentralisieren und vereinfachen, da gängige Passwortmanager z.B. Passkeys unterstützen und so kein &amp;ldquo;Masterpasswort&amp;rdquo; mehr nötig ist.&lt;/p&gt;&#10;&lt;h3 id="yubikey"&gt;Yubikey&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-yubico-authenticator.avif" alt="Image: Screenshot of Yubico Authenticator&amp;#39;s menu options, showing `Home`, `Accounts`, `Passkeys`, `Certificates`, and `Slots`" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Als HSK nutze ich zwei &lt;a href="https://www.yubico.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Yubikey&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Schlüsselanhänger. Einen zum immer dabeihaben und einen als Zweitschlüssel, welcher an einem sicheren Ort verwahrt wird. Es gibt noch andere Anbieter mobiler Hardware Security Keys wie &lt;a href="https://www.nitrokey.com/de" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Nitrokey&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Die kannte ich vor Verfassen dieses Artikels noch nicht. Das ist bedauerlich, ist ihr Produkt doch quelloffen und damit idealer Testkandidat für mein Blog 😥.&lt;/p&gt;&#10;&lt;h3 id="yubico-authenticator"&gt;Yubico Authenticator&lt;/h3&gt;&#10;&lt;p&gt;Das GUI-Programm &amp;ldquo;Yubikey Authenticator&amp;rdquo; ist nützlich zum Einrichten, Belegen der Funktionstaste (z.B. Bestätigung oder Passwortausgabe als Tastatursimulation) und falls man &lt;a href="https://www.bsi.bund.de/DE/Themen/Verbraucherinnen-und-Verbraucher/Informationen-und-Empfehlungen/Cyber-Sicherheitsempfehlungen/Accountschutz/Zwei-Faktor-Authentisierung/zwei-faktor-authentisierung_node.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;2FA&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Einmalkennwörter z.B. als &lt;a href="https://en.wikipedia.org/wiki/Time-based_one-time_password" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;TOTP&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; erzeugen will. Außerdem unterstützt mein Yubikey &lt;a href="https://en.wikipedia.org/wiki/Public_key_infrastructure" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;PKI&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, sodass man dort unter anderem auch &lt;a href="https://letsencrypt.org/certificates/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;SSL&lt;/em&gt;-Zertifikate&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und private Schlüssel geschützt ablegen kann.&lt;/p&gt;&#10;&lt;h2 id="konzept-des-hsk"&gt;Konzept des HSK&lt;/h2&gt;&#10;&lt;p&gt;Dabei gehört es zum Konzept, dass private Schlüssel entweder direkt auf dem Hardware-Schlüssel erzeugt werden oder - ohne sie lokal dauerhaft zu speichern - zwar auf einem Endgerät erstellt, aber auf dem Yubikey abgelegt werden. Diese verbleiben auf dem Schlüssel und sind nicht exportierbar. Er unterstützt gängige asymmetrische Verfahren wie &lt;em&gt;RSA&lt;/em&gt; und &lt;em&gt;ECDH&lt;/em&gt;. Für Details siehe z.B. &lt;a href="https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Publikationen/TechnischeRichtlinien/TR02102/BSI-TR-02102.pdf?__blob=publicationFile&amp;amp;v=15" target="_blank" rel="noopener noreferrer" class="external-link"&gt;diese BSI-Publikation über kryptografische Verfahren&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Außerdem wird der Schlüssel die meiste Zeit offline, also ohne Energieversorgung herumliegen und dadurch kaum angreifbar sein. Eine zusätzliche Pin als Schutzmaßnahme bei Verlust des Schlüssels ist ein weiterer Sicherheitsfaktor.&lt;/p&gt;&#10;&lt;h3 id="zweitschlüssel"&gt;Zweitschlüssel&lt;/h3&gt;&#10;&lt;p&gt;Dennoch sollte man sich notieren, welche Zugänge man mit welchem Verfahren schützt, damit die richtigen Accounts im Falle des Verlustes gesperrt werden können. Ein &amp;ldquo;&lt;a href="https://support.yubico.com/s/article/How-to-register-your-spare-key" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Zweitschlüssel&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;rdquo; sollte vorhanden und belegt sein, was fast genauso zeitaufwändig ist wie den Hauptschlüssel zu konfigurieren.&lt;/p&gt;&#10;&lt;h3 id="nfc"&gt;NFC&lt;/h3&gt;&#10;&lt;p&gt;Von der &lt;a href="https://en.wikipedia.org/wiki/Near-field_communication" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;NFC&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Unterstützung meines Schlüssels hatte ich mir erhofft, Logins noch schneller und leichter hinter mich bringen zu können. Zwei Dinge hier stören mich jedoch: Meinen Laptop kriege ich partout nicht dazu, den Yubikey per NFC zu erkennen. Und auf dem Handy funktioniert die NFC-Erkennung höchstens mittelmäßig. Halte ich den Schlüssel dran, startet die App und bittet mich um Eingabe der Pin. Tue ich dies, muss ich den Schlüssel wieder entfernen und erneut an den Sensor halten &amp;ldquo;Schlüssel nicht bewegen!&amp;rdquo;. Das funktioniert längst nicht jedes mal und wirkt insgesamt ziemlich störanfällig. Schade.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-nfc-device-manager.avif" alt="Image: NFC shown in device manager of my Windows laptop: Drivers installed but not detecting my Yubikey" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="was-mir-beim-umgang-mit-yubikey-fehlt"&gt;Was mir beim Umgang mit Yubikey fehlt&lt;/h2&gt;&#10;&lt;p&gt;Insgesamt vermisse ich ein paar Komfort-Funktionen beim alltäglichen Umgang mit dem Yubikey.&#10;Bei Klick auf die gewünschten Schlüssel oder Accounts passiert nämlich nichts. Und konfigurieren kann ich da anscheinend auch nichts.&lt;/p&gt;&#10;&lt;p&gt;Bei der Verwendung von &lt;strong&gt;Passkeys&lt;/strong&gt; würde ich mir eine Verknüpfung wünschen. Ein Klick auf den entsprechenden Schlüssel im Authenticator sollte die entsprechende App bzw. das Browserfenster mit der Zielwebsite aufrufen. Da ich das HSK zu diesem Zeitpunkt bereits mit der PIN entsperrt habe, kann sogar der Anmeldevorgang nach Berührung des Schlüssels vollautomatisch stattfinden.&lt;/p&gt;&#10;&lt;p&gt;Ähnliches wünsche ich mir beim Umgang mit &lt;strong&gt;Accounts&lt;/strong&gt;. Warum heißt das Modul &amp;ldquo;Accounts&amp;rdquo;, wenn ich meine Konten nicht direkt darüber aufrufen kann? Auch hier muss ich die entsprechenden WebApps etc. selbst öffnen, den Passwortmanager am Start haben und dann noch die 2FA-Codes vom Authenticator herauskopieren. Das geht bestimmt einfacher.&lt;/p&gt;&#10;&lt;h3 id="touch-password-pin-puk-management-key-passphrase"&gt;Touch, password, PIN, PUK, management key, passphrase&lt;/h3&gt;&#10;&lt;p&gt;Je nach Modul gibt es auch bei der Verwendung des Yubikey zumeist eine Mehr-Faktor-Authentisierung. Das unkomplizierteste Verfahren ist eine Anwesenheitserkennung über eine Berührung des Sticks. Damit kann sichergestellt werden, dass eine Person das HSK aktiv bedient und nicht etwa automatisch eingeloggt werden kann. &lt;em&gt;Touch&lt;/em&gt; kann modulübergreifend angewendet werden; zur Detailkonfiguration komme ich &lt;a href="https://blog.schallbert.de/hardware-security-key-add-ssh/"&gt;in meinem Artikel über SSH&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Das &lt;em&gt;Accounts&lt;/em&gt;-Modul kann optional mittels Passwort, das &lt;em&gt;Passkeys&lt;/em&gt;-Modul per &lt;em&gt;PIN&lt;/em&gt; gesichert werden. Manche Anbieter verlangen die Einrichtung einer PIN als zweiten Faktor sogar.&lt;/p&gt;&#10;&lt;p&gt;Das &lt;em&gt;Certificates&lt;/em&gt;-Modul muss mit &lt;a href="https://docs.yubico.com/yesdk/users-manual/application-piv/pin-puk-mgmt-key.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PIN, und PUK und Management Key&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; vor unbefugtem Zugriff gesperrt werden. Private Schlüssel oder Zertifikate können optional zusätzlich mit einer &lt;a href="https://www.ssh.com/academy/ssh/passphrase" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Passphrase&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ausgestattet werden, was die Schlüssel selbst schützt (und aus meiner Sicht für den Yubikey keinen Sinn macht, da die privaten Schlüssel ohnehin nicht auslesbar sind).&lt;/p&gt;&#10;&lt;h2 id="funktionsumfang-und-kryptographische-landschaft"&gt;Funktionsumfang und kryptographische Landschaft&lt;/h2&gt;&#10;&lt;p&gt;Da das HSK einen so großen Sicherheitsgewinn verspricht, möchte ich natürlich möglichst viele seiner Fähigkeiten nutzen. Ich habe jetzt ein paar Wochen mit den verschiedenen Modulen herumgespielt und muss feststellen, dass man sich ganz schön hineindenken und viel lernen muss, um alle gut nutzen zu können. Immerhin versteht man danach ungefähr was die Module tun und bekommt einen guten Überblick zu den Themen Internet-Sicherheit, Verschlüsselungsverfahren und -Protokolle.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Accounts&amp;rdquo; und &amp;ldquo;Passkeys&amp;rdquo; sind für Eingabemasken (Plattformen, Webseiten, Shops) und Apps (Mailer, Passwortmanager, Messenger) einfach einzurichten: Absolut empfehlenswert! - schallbert&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Yubikey-Modul&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Beschreibung&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Accounts&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Verwaltung von Konten/Anmeldungen, unterstützt TOTP&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Passkeys&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Passwortloses Anmeldeverfahren durch &lt;a href="https://bitmi.de/it-sicherheitsblog-was-ist-eigentlich-fido2/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;FIDO/WebAuthn&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (Public-Key-Credential)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Certificates&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Speicherung und Nutzung von Zertifikaten; unterstützt &lt;a href="https://www.openpgp.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;OpenPGP&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (PGP) und &lt;a href="https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Publikationen/TechnischeRichtlinien/TR02103/BSI-TR-02103.pdf?__blob=publicationFile&amp;amp;v=2" target="_blank" rel="noopener noreferrer" class="external-link"&gt;X.509&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (PIV)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Slots&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Zwei programmierbare Slots zur Ausgabe statischer Passworte oder Einmalkennwörter&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Kompliziert und aufwändig wird es meiner Meinung nach mit Public/Private Keys und Zertifikaten. Erzeugungs- und Validierungsverfahren hängen vom Betriebssystem oder darauf verfügbaren Programmen ab und Anleitungen greifen teils zu kurz oder widersprechen sich: Nur für Nerds oder Profis. - schallbert&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Gehen wir die Module mal durch.&lt;/p&gt;&#10;&lt;h3 id="2-faktor-authentisierung-yubikey-accounts"&gt;2-Faktor-Authentisierung &amp;ldquo;Yubikey: Accounts&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Der Yubikey kann als Generator für zeitbasierte Einmalkennwörter dienen. Dafür legt man wie in jeder anderen Authenticator-App auch pro Dienst ein Konto an und übergibt einen Sicherheitsschlüssel (Geheimnis, Secret) an den Yubikey. Auf Basis dieses Geheimnisses können bei angestecktem HSK nun Einmalcodes erzeugt werden.&lt;/p&gt;&#10;&lt;p&gt;Da es keine Möglichkeit gibt, diese &amp;ldquo;Secrets&amp;rdquo; vom Yubikey zu exportieren, muss der Vorgang für Zweitschlüssel wiederholt werden. Doch nicht jeder Anbieter sieht die Nutzung mehrerer Authentikatoren vor. Also kann es sinnvoll sein, das Geheimnis in der Zwischenablage&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; zu halten und ihn bei der Konfiguration des/der Zweitschlüssel wiederzuverwenden.&lt;/p&gt;&#10;&lt;h3 id="passkeys-yubikey-passkeys"&gt;Passkeys &amp;ldquo;Yubikey: Passkeys&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Das Passkey-Modul meines Yubikey funktioniert recht gut. Viele Services&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; unterstützen die passwortlose Anmeldung leider noch nicht oder akzeptieren Passkeys nur als zweiten Faktor. Bei anderen Services&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; kann ich das Passwort nicht deaktivieren, sodass ich kaum einen Sicherheitsgewinn sehe oder mir werden Backup-Codes ausgegeben, die im Notfall als zweiter Faktor dienen sollen&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;. Immerhin, ich kann durch den Passkey jetzt bei einigen Diensten die Passwort-TOTP-Anmeldung umgehen und bin schneller eingeloggt.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Die wohl größten Vorteile der Technologie für Nutzerinnen und Nutzer liegen darin, dass Sie in Zukunft zum einen keine Passwörter mehr erstellen und verwalten müssen. Zum anderen sind Passkeys immun gegen die breite Masse bekannter Phishing-Angriffe.&amp;rdquo; - &lt;a href="https://www.bsi.bund.de/DE/Themen/Verbraucherinnen-und-Verbraucher/Informationen-und-Empfehlungen/Cyber-Sicherheitsempfehlungen/Accountschutz/Passkeys/passkeys-anmelden-ohne-passwort_node.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;BSI-Website, Oktober 2025&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Die Authenticator-App braucht man nicht zur Konfiguration von Passkeys. Dies läuft komplett im Browser oder der Kommandokonsole ab. Beide müssen das Handling von Passkeys unterstützen.&lt;/p&gt;&#10;&lt;h3 id="keys--certificates-yubikey-certificates"&gt;Keys &amp;amp; Certificates &amp;ldquo;Yubikey: Certificates&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Um hier etwas Licht hineinzubringen, müssen wir uns erst einmal den Unterschied zwischen Schlüsselpaar und Zertifikat im Kontext der Authentisierung vor Augen führen. In beiden Fallen geht es darum, die Identität des Client zu verifizieren. Vor einem Server (z.B. per SSH), einer Institution (z.B. einer Certificate Authority) oder einem anderen Client (z.B. per &lt;em&gt;PGP&lt;/em&gt;). Somit wird für das Ziel feststellbar, dass es sich tatsächlich um den zur Verbindung berechtigten Client handelt.&lt;/p&gt;&#10;&lt;h3 id="publicprivate-key-pair"&gt;Public/Private Key Pair&lt;/h3&gt;&#10;&lt;p&gt;Das reine Schlüsselpaar (also kein Zertifikat) wird üblicherweise dann verwendet, wenn das Ziel der Kommunikation im Besitz desjenigen ist, der auch über den Client verfügt. Dann nämlich kann der Client zum Aufsetzen der Verbindung den öffentlichen Schlüssel auf dem Host platzieren. Dieses Verfahren wird z.B. bei Einrichten von SSH-Verbindungen und für einige APIs wie bei der Search Console verschiedener Suchmaschinenanbieter angewendet.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-gitea-ssh-keys.avif" alt="Image: Gitea account security settings: SSH public key stored in Gitea Webapp" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der Host kann dem Client mit dem öffentlichen Schlüssel verschlüsselte Testnachricht schicken, welche nur mit dem zugehörigen privaten Schlüssel wieder in Klartext umgewandelt werden kann. Schickt der Client den korrekten Klartext zum Host zurück, ist für den Host bewiesen, dass der Verbindungspartner authentisch ist.&lt;/p&gt;&#10;&lt;h3 id="certificates--piv"&gt;Certificates / PIV&lt;/h3&gt;&#10;&lt;p&gt;Doch was, wenn der Host nicht unter der Kontrolle des Clients steht? Wie soll dann sichergestellt werden, dass sich weder Client noch Server für jemand anders ausgibt und einen gefälschten öffentlichen Schlüssel hinterlegt? Genau da kommen Zertifikate zum Einsatz.&lt;/p&gt;&#10;&lt;p&gt;Vereinfacht gesagt enthält das Zertifikat nicht nur den öffentlichen Schlüsselteil, sondern zusätzliche Informationen über den Netzteilnehmer und die gewünschte Verbindung. Nun kommt eine vertrauenswürdige, an der Kommunikation zwischen Client und Server unbeteiligte dritte Partei ins Spiel. In der Private Key Infrastructure &lt;em&gt;PKI&lt;/em&gt; nennt man sie Certificate Authority &lt;em&gt;CA&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Der Netzteilnehmer sendet nun den öffentlichen Schlüssel samt Metadaten über sich selbst an die CA und stellt einen sogenannten &lt;em&gt;CSR&lt;/em&gt;, einen Certificate Signing Request. Die CA prüft nun, ob der Kanal des Teilnehmers auch wirklich diesem gehört.&lt;/p&gt;&#10;&lt;p&gt;Bei einer E-Mail (Stichwort &lt;a href="https://www.gpg4win.org/doc/en/gpg4win-compendium_8.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PGP/GPG&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) kann die CA beispielsweise einen aus dem Public Key errechneten Hash-Link an die angegebene Mailadresse versenden. Wird der Link geklickt, beweist dies der CA gegenüber, dass die Mailadresse tatsächlich dem Client gehört und sie kann das Zertifikat ausstellen, den CSR also mit ihrem privaten Schlüssel signieren.&lt;/p&gt;&#10;&lt;p&gt;Bei Domains (Stichwort SSL/TLS) kann die CA beim Domain Name Service &lt;em&gt;DNS&lt;/em&gt;-Anbieter prüfen, ob die Angaben zur Anfrage des Servers passen.&lt;/p&gt;&#10;&lt;p&gt;Clients haben die Signaturen gängiger Certificate Authorities eingespeichert (z.B. im Browser). Wird das Zertifikat auf dem Rückweg zwischen Server und Client manipuliert, verändert sich der Hashwert und der Client spuckt eine Zertifikatswarnung aus: &amp;ldquo;invalid certificate&amp;rdquo;.&#10;Jetzt muss man jedoch den Certificate Authorities vertrauen&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; 🙃&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Der Rechner sollte dafür offline sein. Wenn man supersicher sein will, macht man das Anlernen auf einem Live-Betriebssystem. Das bootet üblicherweise von einem USB-Stick und hat nach dem Herunterfahren alles vergessen.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;Z.B. Gar nicht: Mailbox, 2FA: Discord und Kraken, zum Zeitpunkt Apr-26. Vielleicht habe ich aber auch nicht die richtigen Einstellungen gefunden?&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:3"&gt;&#10;&lt;p&gt;Z.B. Google, Github und Hetzner, zum Zeitpunkt Apr-26.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:4"&gt;&#10;&lt;p&gt;Auch diese Backup-Codes müssen sicher abgelegt werden. Idealerweise wieder offline, unbedingt aber unabhängig vom HSK.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:5"&gt;&#10;&lt;p&gt;Es gibt Alternativen zu Institutionen wie CAs, siehe &lt;a href="https://de.wikipedia.org/wiki/Web_of_Trust" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Web of Trust&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. So kann die Verifikation der Identität beispielsweise auch peer-to-peer von unbeteiligten Dritten stattfinden.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Giteas act_runner rootless: Startprobleme</title><link>https://blog.schallbert.de/act-runner-dind-failed-to-start-the-child/</link><pubDate>Sat, 28 Mar 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/act-runner-dind-failed-to-start-the-child/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-28-act_runner-dind-failed-to-start-child-solved-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Top page crop of OWASP&amp;#39;&amp;#39;s Docker Security Cheat Sheet [Source, downloaded May-26](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html) [License](https://creativecommons.org/licenses/by-sa/4.0/)"&#10; title="Giteas act_runner rootless: Startprobleme" /&gt;&#10;&lt;h2 id="das-fehlerbild"&gt;Das Fehlerbild&lt;/h2&gt;&#10;&lt;p&gt;Mein &lt;a href="https://blog.schallbert.de/gitea-act-runner-dind/"&gt;docker-in-docker (DinD) act_runner&lt;/a&gt; stürzt kurz nach dem Start mit folgender Fehlermeldung ab:&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-28-act_runner-dind-failed-to-start-child-problem.avif" alt="Image: console window with docker logs text output: `\[rootlesskit:parent\] error: failed to start the child: fork/exec /proc/self/exe: operation not permitted`" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="issue-ticket-bei-gitea"&gt;Issue-Ticket bei Gitea&lt;/h2&gt;&#10;&lt;p&gt;In der &lt;a href="https://gitea.com/gitea/act_runner/issues/721" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Gitea-Community (issue #721)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; habe ich ein Ticket für das Problem angelegt. Im Folgenden gehe ich auf eigene Erkenntnisse ein und fasse die Diskussion etwas zusammen.&lt;/p&gt;&#10;&lt;h2 id="problem-mit-kernel-berechtigungen"&gt;Problem mit Kernel-Berechtigungen?&lt;/h2&gt;&#10;&lt;p&gt;Der &lt;code&gt;act_runner&lt;/code&gt; muss bei der Docker-in-Docker Konfiguration selbst einen eigenen &lt;a href="https://docs.docker.com/engine/security/#docker-daemon-attack-surface" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Docker Daemon&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; betreiben. Nur so kann der Runner für die &lt;em&gt;Actions&lt;/em&gt; eigene Container erstellen. Dies ist aus Sicherheitsgründen erst einmal nicht erlaubt.&lt;/p&gt;&#10;&lt;h3 id="warum-container-keine-container-starten-dürfen"&gt;Warum Container keine Container starten dürfen&lt;/h3&gt;&#10;&lt;p&gt;Im Kontext des &lt;em&gt;act_runner&lt;/em&gt; führt eine Action Code aus, der selbst Teil des Repository ist. Wird unbemerkt Schadcode in die Action eingeschleust, z.B. von einem &amp;ldquo;Collaborator&amp;rdquo;, so kann dieser bei einer Docker-Installation ohne DinD über den &lt;a href="https://docs.docker.com/engine/security/rootless/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;unter Umständen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit &lt;strong&gt;root-Rechten&lt;/strong&gt; ausgestatteten Docker-Daemon &lt;code&gt;dockerd&lt;/code&gt; direkt auf das Hostsystem durchgreifen.&lt;/p&gt;&#10;&lt;h3 id="auswirkungen-falls-trotz-dind-etwas-schief-geht"&gt;Auswirkungen, falls trotz DinD etwas schief geht&lt;/h3&gt;&#10;&lt;p&gt;Bei Anwendung des DinD-Konzeptes kann dies bei einem Angriff lediglich zum Zugriff auf den &lt;code&gt;dockerd&lt;/code&gt; innerhalb des &lt;em&gt;act_runner&lt;/em&gt; Containers führen - und das nur, wenn die Action nicht sauber gekapselt ist. Immer noch nicht gut, aber vertretbar: Bei Neustart des Containers ist der Status quo ante wiederhergestellt. Durchgriff auf das Hostsystem besteht allenfalls indirekt dadurch, dass der Container selbst mit sämtlichen Kernel-Features ausgestattet ist.&lt;/p&gt;&#10;&lt;h3 id="einfache-lösung-dind-container-privileged-starten"&gt;Einfache Lösung: DinD-Container &lt;code&gt;privileged&lt;/code&gt; starten&lt;/h3&gt;&#10;&lt;p&gt;Um dem DinD-Container jetzt die Möglichkeit zu geben eigene &lt;em&gt;Actions&lt;/em&gt; aufzusetzen, kann in der Konfiguration &lt;code&gt;privileged: true&lt;/code&gt; gesetzt werden. Damit werden dem Container &lt;strong&gt;alle Kernel-Fähigkeiten&lt;/strong&gt; eingeräumt. Dies ist laut (dem hervorragend gemachten) &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-3-limit-capabilities-grant-only-specific-capabilities-needed-by-a-container" target="_blank" rel="noopener noreferrer" class="external-link"&gt;OWASP Security Cheat Sheet&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; möglichst zu vermeiden. Sollte &lt;em&gt;act_runner&lt;/em&gt; nun selbst angegriffen werden oder kritische Sicherheitslücken offenbaren, hätte der Eindringling auf diese Weise bereits alle Möglichkeiten, der Kapselung vor dem Host-System zu entgehen.&lt;/p&gt;&#10;&lt;p&gt;Will man den einfachen Weg gehen, sieht die entsprechende &lt;code&gt;docker-compose.yml&lt;/code&gt; wie folgt aus.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# section ACT_RUNNER&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;runner&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea/act_runner:latest-dind-rootless&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea-runner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;privileged&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;And remember: Do not run containers with the &amp;ndash;privileged flag!!!&amp;rdquo; - OWASP&amp;rsquo;s Docker Security Cheat Sheet, 2026 &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;License&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="sackgasse-nur-unbedingt-benötigte-rechte-vergeben"&gt;Sackgasse: Nur unbedingt benötigte Rechte vergeben&lt;/h3&gt;&#10;&lt;p&gt;Nach kurzer Recherche finde ich einen &lt;a href="https://www.codestudy.net/blog/can-i-run-docker-in-docker-without-using-the-privileged-flag/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Artikel auf CodeStudy.net&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, welcher sich mit dem Thema DinD beschäftigt. Hieraus bastele ich Änderungen in meiner &lt;code&gt;docker-compose.yml&lt;/code&gt; zusammen.&lt;/p&gt;&#10;&lt;p&gt;Meine Vorgehensweise:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Änderungen in &lt;code&gt;docker-compose.yml&lt;/code&gt; vornehmen&lt;/li&gt;&#10;&lt;li&gt;Service neu starten &lt;code&gt;docker compose restart &amp;lt;service&amp;gt;&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Logs anschauen &lt;code&gt;docker logs --tail 100 &amp;lt;container-name&amp;gt;&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Falls gestartet: Programm laufen lassen &lt;code&gt;web-app-&amp;gt;repo-&amp;gt;jobs-&amp;gt;rerun_all-jobs&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Bei Fehlschlag: Wiederholen&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Ich beginne mit wenigen, aber sehr mächtigen Rechten. Nach ein paar Iterationen erhalte ich:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# section act_runner DinD-rootless&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;runner&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea/act_runner:latest-dind-rootless&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea-runner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;privileged&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cap_add&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;SYS_ADMIN&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;security_opt&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#66d9ef"&gt;no&lt;/span&gt;-&lt;span style="color:#ae81ff"&gt;new-privileges:true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;apparmor:unconfined &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;systempaths=unconfined&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;writable-cgroups=true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aber noch immer fehlen mir Rechte, die außerhalb von Kernel-Privilegien und Sicherheitszonen liegen: Der Docker-Daemon möchte auf &lt;code&gt;sysfs&lt;/code&gt; und &lt;code&gt;proc&lt;/code&gt;, also Systemdateiverzeichnisse und -Prozesse zugreifen können.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;warning msg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;[rootlesskit:child ] failed to mount sysfs, falling back to read-only mount: operation not permitted&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Das kann ich selbst mit &lt;code&gt;cap_add: ALL&lt;/code&gt; nicht ausgleichen. Zudem wird die Quellenlage im Internet auf dieser technischen Ebene echt dünn (und ich bin kein Docker-Spezialist). Ein &lt;a href="https://zhsj.me/blog/view/dind-without-privileged" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Re-Mount&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; der Dateisysteme könnte hier noch helfen; das erscheint mir aber zu experimentell und fehleranfällig.&lt;/p&gt;&#10;&lt;p&gt;Nach weiteren Stunden Recherche und Trial-and-Error auf meinem Server muss ich &lt;a href="https://github.com/docker-library/docker/issues/546" target="_blank" rel="noopener noreferrer" class="external-link"&gt;@tianon in einer Diskussion auf Github&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zustimmen: Der Docker Daemon benötigt so viele Rechte und Fähigkeiten, dass man gleich bei &lt;code&gt;privileged: true&lt;/code&gt; bleiben kann und sich nicht mit einer Armada an &lt;code&gt;CAP_ADD&lt;/code&gt; und System-bind-mounts herumschlagen muss.&lt;/p&gt;&#10;&lt;h3 id="zukünftige-lösung-virtualisierung-oder-daemonless"&gt;Zukünftige Lösung: Virtualisierung oder Daemonless&lt;/h3&gt;&#10;&lt;p&gt;Doch es gibt möglicherweise bessere Lösungen: Eigene Container-Laufzeitumgebungen wie &lt;a href="https://github.com/nestybox/sysbox" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Sysbox&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Die Box hat selbst auf dem Hostsystem keine besonderen Rechte, ähnlich wie in einer virtuellen Maschine scheint sie in ihr laufenden Applikationen aber alle Rechte und Fähigkeiten zur Verfügung stellen zu können.&lt;/p&gt;&#10;&lt;p&gt;Installation und Inbetriebnahme bis hin zu funktionierenden Job-Containern führt für diesen Post zu weit. Daher muss ich vorerst auf andere &lt;a href="https://www.jaburjak.cz/posts/docker-in-docker-unprivileged/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Quellen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verweisen.&lt;/p&gt;&#10;&lt;p&gt;Auch das Umschwenken &lt;a href="https://tiendu.github.io/2025/04/18/dind.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;von Docker auf Podman&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist eine mögliche Lösung: Podman ist Daemonless, rootless und bietet ähnlichen Funktionsumfang wie Docker. Ein Umzug nach Podman wäre für mich ein eigenes Projekt und passt nicht in den Kontext dieses Posts.&lt;/p&gt;&#10;&lt;h2 id="linux-security-modules-seccomp-apparmor-selinux"&gt;Linux Security Modules (seccomp, AppArmor, SELinux)&lt;/h2&gt;&#10;&lt;p&gt;Begriffsklärung: &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;LSM&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ermöglichen verschiedene Sicherheitsprüfungen und -Sperren auf Kernelebene. Durch &amp;ldquo;Mandatory Access Control&amp;rdquo; können Sicherheitserweiterungen wie AppArmor Kernel-Fähigkeiten für einzelne Anwendungen steuern und Zugriffe falls nötig sperren.&lt;/p&gt;&#10;&lt;h3 id="apparmor"&gt;Apparmor&lt;/h3&gt;&#10;&lt;p&gt;Ich betreibe meinen Server (VPS) mit Ubuntu. Dieses Betriebssystem hat in den aktuelleren Versionen &amp;ldquo;user namespace creation restrictions&amp;rdquo; in AppArmor integriert, was das Betreiben von &lt;em&gt;appimages&lt;/em&gt;, &lt;em&gt;WebApps&lt;/em&gt; und eben auch &lt;em&gt;Containers&lt;/em&gt; mit höheren Rechten verhindert.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Unprivileged user namespaces are a feature in the Linux kernel [&amp;hellip;]; it enables unprivileged users to gain administrator (root) permissions within a confined environment [&amp;hellip;]&amp;rdquo; - mbelair, Ubuntu Discourse, as of May-2026, &lt;a href="https://discourse.ubuntu.com/t/understanding-apparmor-user-namespace-restriction/58007" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Ubuntu Discourse Website&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Kurzgesagt wurde dieses Werkzeug als &lt;a href="https://de.wikipedia.org/wiki/H%C3%A4rten_%28Computer%29" target="_blank" rel="noopener noreferrer" class="external-link"&gt;security-hardening-Maßnahme&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; entwickelt, um Angriffsfläche auf dem Host-System bei Ausführung von Programmen zu reduzieren, die höhere Rechte erfordern.&lt;/p&gt;&#10;&lt;h3 id="einfache-lösung-komplett-abstellen"&gt;Einfache Lösung: Komplett abstellen&lt;/h3&gt;&#10;&lt;p&gt;Die Dampfhammermethode stellt das Feature für Benutzernamensräume komplett kalt. Wir teilen AppAmor mit, dass Drittprogramme wie in alten Betriebssystemversionen auch ohne Einschränkungen Kernel-Features oder höhere Rechte nutzen dürfen. Den zugehörigen Befehl habe ich aus dem &lt;a href="https://askubuntu.com/questions/1511854/how-to-permanently-disable-ubuntus-new-apparmor-user-namespace-creation-restric" target="_blank" rel="noopener noreferrer" class="external-link"&gt;askubuntu-Forum&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; entliehen.&lt;/p&gt;&#10;&lt;p&gt;Er schaltet die Einschränkung von Nicht-Admin-Benutzernamensräumen (die Übertragung ins Deutsche tut schon fast weh) für diese Sitzung ab, indem er die Kernelparameter zur Laufzeit umschreibt (&lt;code&gt;-w&lt;/code&gt;).&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbertTestsThis@machine:~# sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ich halte den Befehl zur Fehlersuche für sehr hilfreich. Möchte man herausfinden, ob das gewünschte Programm wegen AppArmor nicht startet:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Obiges Kommando eingeben&lt;/li&gt;&#10;&lt;li&gt;das Drittprogramm, den Container etc. testen&lt;/li&gt;&#10;&lt;li&gt;durchbooten. Oder den Befehl mit &lt;code&gt;=1&lt;/code&gt; eingeben. Danach ist der Ursprungszustand wiederhergestellt.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Möchte man diese Verwundbarkeit permanent erhalten, so gibt man ein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;makeVulnerability@machine:~# echo &lt;span style="color:#e6db74"&gt;&amp;#39;kernel.apparmor_restrict_unprivileged_userns = 0&amp;#39;&lt;/span&gt; | sudo tee /etc/sysctl.d/20-apparmor-donotrestrict.conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;makeVulnerability@machine:~# sudo shutdown -r now&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dies alleine stellt noch keine Sicherheitslücke dar. Es ist jetzt nur prinzipiell einfacher geworden, eventuelle Schwächen im Kernel auszunutzen und der &amp;ldquo;Sandbox&amp;rdquo; des Containers zu entrinnen.&lt;/p&gt;&#10;&lt;h3 id="richtige-lösung-für-jede-anwendung-justieren"&gt;Richtige Lösung: Für jede Anwendung justieren&lt;/h3&gt;&#10;&lt;p&gt;Es gibt Anwendungen (wie meine DinD-Version von &lt;code&gt;act_runner&lt;/code&gt;), die benötigen zwingend erweiterte Rechte um ordnungsgemäß zu funktionieren. Und nur diese sollten die Möglichkeit erhalten, Nicht-Admin-Benutzernamensräume zu erhalten.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://docs.docker.com/engine/security/apparmor/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Docker selbst&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; hat &lt;em&gt;AppAmor&lt;/em&gt; einen Abschnitt in der Dokumentation gewidmet. Um zur Lösung für den DinD-Runner zu gelangen, benötigt es eine &lt;a href="https://www.spad.uk/posts/rootless-dind-noble/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Transferleistung (danke, @thespad)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Zur Wiederholung nochmal die Fehlermeldung von oben:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;rootlesskit:parent&lt;span style="color:#f92672"&gt;]&lt;/span&gt; error: failed to start the child: fork/exec /proc/self/exe: operation not permitted&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner | s6-svwait: fatal: some services reported permanent failure or their supervisor died&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Das Logfile sagt aus, dass der &lt;code&gt;parent&lt;/code&gt; (Docker-Daemon im Container) sein &lt;code&gt;child&lt;/code&gt; (Runner-Container) nicht starten kann, weil er für andere Teilnehmer (&lt;code&gt;fork&lt;/code&gt;) selbst keine Prozesse (&lt;code&gt;/proc/self&lt;/code&gt;) erstellen kann. Wer ist der User dieses Daemons? &lt;code&gt;rootlesskit&lt;/code&gt;.&#10;Genau hier hakt unsere Lösung ein: Wir müssen in der &lt;code&gt;docker-compose.yml&lt;/code&gt; das AppArmor-Profil für &lt;em&gt;rootlesskit&lt;/em&gt; im DinD-Container des act_runner freigeben.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# section act_runner DinD-rootless&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;runner&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea/act_runner:latest-dind-rootless&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea-runner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;privileged&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;security_opt&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;apparmor=rootlesskit&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Entsprechend gut sieht jetzt auch das Logfile aus. Fertig!&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-28-act_runner-dind-failed-to-start-child-solved.avif" alt="Image: act_runner rootless-DinD logfile with --privileged:true and --security_opt:apparmor=rootlesskit, showing a clean startup" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Ich habe das Gefühl, dass jener Post mit &amp;ldquo;KI&amp;rdquo; geschrieben wurde. Wenn es konkret wird und es um &amp;ldquo;critical volume mounts&amp;rdquo; geht, stehen dort nämlich keine echten System-Pfade mehr und der Artikel wird insgesamt so vage, dass ich den Ratschlägen folgend gar nicht bis zum Ziel komme.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Edelstahl V2a bzw. AISI304 gravieren</title><link>https://blog.schallbert.de/engrave-aisi304-stainless-steel/</link><pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/engrave-aisi304-stainless-steel/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-08-Engrave-Stainless-thumb.avif"&#10; class="post-cover"&#10; alt="Image: A tapered engraving tool for stainless stell application"&#10; title="Edelstahl V2a bzw. AISI304 gravieren" /&gt;&#10;&lt;h2 id="warum-dieser-versuch"&gt;Warum dieser Versuch?&lt;/h2&gt;&#10;&lt;p&gt;Ich wurde gefragt, ob ich einen Hilfsanker für eine Segelyacht gravieren könne. Dieser &lt;a href="https://ankerplex.eu/wp-content/uploads/2025/06/DATA-SHEET-ANKERPLEX.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anker&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist aus &lt;code&gt;10mm&lt;/code&gt; starkem Edelstahlblech gefertigt und passt mit Maßen von &lt;code&gt;389x630mm&lt;/code&gt; auf meine Maschine.&lt;/p&gt;&#10;&lt;p&gt;Ich habe noch nie mit dem Material gearbeitet und bin so neugierig, dass ich den Auftrag annehme.&lt;/p&gt;&#10;&lt;h2 id="das-material"&gt;Das Material&lt;/h2&gt;&#10;&lt;p&gt;Der Anker besteht aus Edelstahl der Sorte &lt;a href="https://en.wikipedia.org/wiki/SAE_304_stainless_steel" target="_blank" rel="noopener noreferrer" class="external-link"&gt;AISI304&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Andere Namen für diese Legierung sind &lt;code&gt;18/10&lt;/code&gt;, &lt;code&gt;18/8&lt;/code&gt; bezogen auf ihren Chrom/Nickel-Gehalt oder &lt;code&gt;V2A/A2&lt;/code&gt; nach ihrer Entwicklung aus der Versuchsschmelze bzw. ihre Herstellung &amp;ldquo;&lt;strong&gt;A&lt;/strong&gt;ir hardened&amp;rdquo; und der Legierungsgruppe. Sie ist verhältnismäßig gut formbar und wird daher häufig in industriellen Anwendungen genutzt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-08-Anker-AISI304-total.avif" alt="Image: Arrow-shaped anchor with non-polished steel surface made of AISI304 stainless steel" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dieser Edelstahl hat für die spanende Bearbeitung jedoch ein paar Nachteile: Seine Wärmeleitfähigkeit ist recht gering. Daher geht wenig Wärme ins Material, was den Fräser umso heißer werden lässt. Dazu ist das Material zäh. Der Span löst sich nicht gut vom Werkzeug, es entsteht leicht eine &lt;a href="https://de.wikipedia.org/wiki/Aufbauschneide" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Aufbauschneide&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Das erzeugt Unsauberkeiten in der Fräsbahn. Schiebt der Fräser Späne vor sich her, wird das Material nicht vollständig geschnitten und es neigt zur Gratbildung. Außerdem ist der Edelstahl kaltverfestigend: Reibt oder Steht der Fräser am Material, verändert der von ihm ausgehende Druck die Materialoberfläche. Sie wird härter als das Werkzeug selbst.&lt;/p&gt;&#10;&lt;p&gt;All diese Eigenschaften zusammengenommen sorgen für hohen Werkzeugverschleiß und großen Kühlbedarf. Die Schnittparameter müssen außerdem in einem engen Korridor gehalten werden.&lt;/p&gt;&#10;&lt;h2 id="das-werkzeug"&gt;Das Werkzeug&lt;/h2&gt;&#10;&lt;p&gt;Ich wähle einen Gravierfräser von &lt;a href="https://shop.vhf.de/articleGroups/Gravierfraeser-fuer-Edelstahl-W_G_GFV.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;vhf&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; speziell für die Bearbeitung von Edelstahl. Mit &lt;code&gt;6mm&lt;/code&gt; Schaftdurchmesser, jedoch nur &lt;code&gt;4mm&lt;/code&gt; Schneidendurchmesser, einer kurzen geraden Schneide, &lt;code&gt;60°&lt;/code&gt; Gravurwinkel und &lt;code&gt;0.4mm&lt;/code&gt; Spitzendurchmesser wirkt das Werkzeug gedrungen und stabil. Der Fräser ist mit einer &lt;a href="https://www.harveytool.com/resources/tool-coatings" target="_blank" rel="noopener noreferrer" class="external-link"&gt;AlTiN&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Beschichtung versehen.&lt;/p&gt;&#10;&lt;p&gt;Ob die dünne Spitze den Fräskräften und der Wärmeentwicklung standhält?&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-08-Gravierfr%c3%a4ser.avif" alt="Image: Coated carbide tapered engrave mill cutter for stainless steel" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="diskussion-um-fräsparameter"&gt;Diskussion um Fräsparameter&lt;/h2&gt;&#10;&lt;p&gt;Im Forum &lt;a href="https://cnczone.nl/viewtopic.php?f=22&amp;amp;t=24339" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cnczone.nl&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; frage ich nach Erfahrungswerten für Tiefenzustellung, Vorschub und Drehzahl für meinen Vollhartmetallfräser.&lt;/p&gt;&#10;&lt;p&gt;Die Internetrecherche&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; liefert mir eine Ziel-Schnittgeschwindigkeit für AISI304 von etwa &lt;code&gt;60m/min&lt;/code&gt; und einen Zahnvorschub zwischen &lt;code&gt;0.02mm und 0.05mm&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Außerdem rufe ich den Fräserhersteller an und frage nach Empfehlungen für optimalen Einsatz auf einer weichen Maschine wie meiner. Der Hersteller sagt, ich müsse unbedingt kühlen. Idealerweise mit reichlich Emulsion auf dem Material. Seine Empfehlung lautet für den ersten Versuch und mit dem Fräser:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;S15000 F600 Z-0.1 (vhf-Beratung)&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Bei Gravuren seien häufige Richtungswechsel zu erwarten, und Ziel solle es sein, die Maschine möglichst gleichmäßig und mit kurzen Beschleunigungswegen zu führen. Das würde Rattermarken weitestgehend vermeiden.&lt;/p&gt;&#10;&lt;p&gt;Aus dem Forum erhalte ich Werte zwischen &lt;code&gt;F240&lt;/code&gt; und &lt;code&gt;F800&lt;/code&gt; bei maximaler Spindeldrehzahl.&lt;/p&gt;&#10;&lt;p&gt;Meine Erfahrungen bei der Verarbeitung von Stahl aus vorherigen Artikeln &lt;a href="https://blog.schallbert.de/milling-secc/"&gt;SECC fräsen&lt;/a&gt; und &lt;a href="https://blog.schallbert.de/milling-steel/"&gt;Stahl bearbeiten&lt;/a&gt; lasse ich einfließen und stelle einen Zahnvorschub von &lt;code&gt;0.04mm&lt;/code&gt; ein. Ich passe die Drehzahl der Schnittgeschwindigkeit von &lt;code&gt;60m/min&lt;/code&gt; an. Ich fahre gern zügig durch das Material, stelle in der Tiefe aber nur sehr vorsichtig zu. Dies ergibt für die vorliegende Schneidengeometrie folgende Werte:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;S30000 F1200 Z-0.1 (Schallbert)&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Damit liege ich mit Drehzahl und Vorschub genau beim Doppelten der Empfehlung von &lt;em&gt;vhf&lt;/em&gt;. Ich wähle den Vorschub so zügig, damit ich weniger Reibungshitze am Fräser erzeuge und mehr Wärme in den Span geht. Die Tiefenzustellung halte ich gering, weil die auf die Konstruktion wirkenden Kräfte so niedriger sind. Davon verspreche ich mir eine höhere Gravurgenauigkeit und bessere Details.&lt;/p&gt;&#10;&lt;p&gt;Die Gesamttiefe der Gravur lege ich auf &lt;code&gt;0.2mm&lt;/code&gt; fest.&lt;/p&gt;&#10;&lt;h3 id="einfluss-der-maschinenkinematik"&gt;Einfluss der Maschinenkinematik&lt;/h3&gt;&#10;&lt;p&gt;Wie oben beschrieben werden Drehzahl und Vorschub aufeinander eingestellt. Dabei ist es wichtig zu prüfen, ob der eingestellte Vorschub überhaupt erreicht werden kann. In Abhängigkeit von der Beschleunigungsfähigkeit der Maschine sowie Größe und Form der Gravur kann es nämlich sein, dass die Maschine durch die vielen Richtungswechsel im Schnitt mit viel geringerem Vorschub unterwegs ist.&lt;/p&gt;&#10;&lt;p&gt;Daher sind Drehzahl und Vorschub an die real erzielbaren Werte anzupassen. Diese können mit der Maschine im Simulationsmodus beobachtet werden. In meinem Falle ist die Maschinenbeschleunigung im Verhältnis zur Größe der Gravur so hoch, dass ich fast immer in Zielgeschwindigkeit operieren kann. Beispielrechnung gefällig?&lt;/p&gt;&#10;&lt;p&gt;Die Maschine benötigt folgende Zeitspanne, um vom Stillstand auf den eingestellten Vorschub von &lt;code&gt;F1200&lt;/code&gt; zu kommen:&lt;/p&gt;&#10;$$a_{machine} = 400\frac{mm}{s^2}, v_{feedrate} = 1200\frac{mm}{min} = 20\frac{mm}{s}$$$$t = \frac{20}{400} = 50ms$$&lt;p&gt;Ich habe also nur sehr kurze Zeiten, in denen die Maschine mehr am Material reibt als schneidet.&lt;/p&gt;&#10;&lt;h2 id="vorbereitung-des-gravurvorganges"&gt;Vorbereitung des Gravurvorganges&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-08-AISI304-Cooling.avif" alt="Image: Material on the milling bed. Vacuum on, cooling fluid applied. Ready to mill." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich verarbeite nur ganz selten Metall. Ich habe also keine fertige Schneid- oder Kühlemulsion vorliegen. Als Ersatz nehme ich Ballistol-Spray und etwas Wasser zur Hand. Das Öl reibe ich in die Oberfläche ein. Hinzu füge ich für die Fläche von &lt;code&gt;10x15cm&lt;/code&gt; etwa ein Esslöffel Wasser.&#10;Beides bildet zu meinem Erstaunen von selbst eine Emulsion.&lt;/p&gt;&#10;&lt;p&gt;Ich habe vor, den Anker per Vakuum zu spannen und bringe ihn mittels Passstiften auf dem Frästisch mittig in Position. Die Aussparungen für Griff und Stieleinsatz schließe ich mit beschichtetem Butterbrotpapier (&lt;em&gt;Pro-Tipp! Günstig, super reißfest, umweltschonend, fast luftdicht, gut zu verarbeiten&lt;/em&gt;).&lt;/p&gt;&#10;&lt;p&gt;Nach Einschalten der Vakuumpumpe muss ich leider feststelen, dass der Anker auf einer Seite nicht ganz plan aufliegt. Ich bekomme statt &lt;code&gt;-0.7bar&lt;/code&gt; nur knapp &lt;code&gt;-0.4bar&lt;/code&gt; hin. Das ist für die bei der Gravur wirkenden, geringen Kräfte nicht dramatisch. Die Linienbreite allerdings wird so kaum konstant zu halten sein.&lt;/p&gt;&#10;&lt;p&gt;Meine Lösung ist nun, das Fräsmotiv herunter zu skalieren. Durch die geringere Größe wirken sich Unterschiede in der Linienbreite kaum noch aus.&lt;/p&gt;&#10;&lt;h2 id="durchführung"&gt;Durchführung&lt;/h2&gt;&#10;&lt;p&gt;Bevor ich die Gravur durchführe lasse ich die Maschine im Simulationsmodus laufen. Dabei entdecke ich noch zwei sehr langsame Eintauchvorgänge. Das CAM erstellt sie vermutlich wegen der von mir eingestellten Eintauchrampe, welche auf sehr kurzen Linienzügen erfolgt.&#10;Dies korrigiere ich mit manuell vorgegebenen Eintauchpunkten.&lt;/p&gt;&#10;&lt;p&gt;Nun lasse ich den Fräsjob durchlaufen. Die Emulsion bleibt trotz Spindelgebläse an Ort und Stelle. Die Geräuschentwicklung ist bei der ersten Tiefenzustellung weit geringer als bei der Zweiten - wird jedoch nie unangenehm. Vibrationen treten kaum auf. Der Fräsvorgang dauert auch bei dem aufwändigeren Logo kaum fünf Minuten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-08-Gravur-Petrel.avif" alt="Image: Petrel logo engraved onto stainless steel anchor" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="ergebnis"&gt;Ergebnis&lt;/h2&gt;&#10;&lt;p&gt;Ich bin hochzufrieden. So ein gutes Ergebnis hatte ich auf meiner Hobbymaschine auf Basis meiner Beobachtungen in den Foren nicht erwartet. Praktisch keine Rattermarken, saubere Fräskanäle und nur an den Stopp- und Austauchpunkten leichte Gratbildung. Das Material bleibt jedoch im Fräskanal und erzeugt keine fühlbaren Kanten. Auch im Detail ist das Fräsbild noch ziemlich ansehnlich.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-03-08-Gravur-Ford.avif" alt="Image: Ford logo engraved onto stainless steel anchor" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das folgende Video zeigt ein paar Details des Fräsvorganges. Meine letzte offene Frage: Ist das leicht raue Geräusch des Fräsers bei der zweiten Tiefenzustellung ein Hinweis auf größeren Werkzeugverschleiß durch Materialverfestigung?&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/7KNuywPo47HWq85FXDmyJE" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Engraving Stainless Steel&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;&lt;a href="https://www.machining-custom.com/blog/304-stainless-steel-cnc-turning-characteristics-and-technology.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Quelle1: VNT Hardware&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &lt;a href="https://www.machiningdoctor.com/mds/?matId=1750" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Quelle2: Machiningdoctor&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &lt;a href="https://www.cncoptimization.com/resources/tables/stainless-speeds-feeds/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Quelle3: CNC Optimization&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>HudL - Datenbank veröffentlicht</title><link>https://blog.schallbert.de/announcements/2026-02-28-hudl-datenbank-release/</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/announcements/2026-02-28-hudl-datenbank-release/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2024-10-01-datenbank-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Crop of HudL - Datenbank coverimage"&#10; title="HudL - Datenbank veröffentlicht" /&gt;&#10;&lt;h2 id="der-song"&gt;Der Song&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2024-10-01-datenbank-coverimage.avif" alt="Image: Cover image for &amp;#39;HudL- Datenbank&amp;#39; showing a children&amp;#39;s painting of a ghost with band instruments on their head" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Unser Song &amp;ldquo;Datenbank&amp;rdquo; entstand im Jahr 2024 aus einer Jam-Session in Bremen. Von unseren Hörern nach mehreren Live-Auftritten als &amp;ldquo;Smash Hit&amp;rdquo; bezeichnet, haben wir uns 2025 ins Studio begeben und &amp;ldquo;Datenbank&amp;rdquo; eingespielt.&lt;/p&gt;&#10;&lt;p&gt;Nun, Anfang 2026, wagen wir das Abenteuer und veröffentlichen &amp;ldquo;Datenbank&amp;rdquo; über den Berliner Distributor &lt;a href="https://recordjet.com/en/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;RecordJet&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Wir hoffen, es gefällt!&lt;/p&gt;&#10;&lt;h2 id="wo-kann-ich-datenbank-anhören"&gt;Wo kann ich &amp;ldquo;Datenbank&amp;rdquo; anhören?&lt;/h2&gt;&#10;&lt;p&gt;Praktisch überall, wo man streamen kann! Beispiele:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper inline"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2026-02-28-spotifylogo.avif" alt="Image: Spotify logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;a href="https://open.spotify.com/album/6k1s8Nlvu9kOZZUKm4NAlf?si=Ivv7u-JaSHa5V0Vq-CkYHQ" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Spotify&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper inline"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2026-02-28-tidallogo.avif" alt="Image: Tidal logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;a href="https://tidal.com/album/504158589/u" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Tidal&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper inline"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2026-02-28-youtubelogo.avif" alt="Image: Youtube logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;a href="https://youtu.be/RdU2wvEWe-A?si=R3dA4ZsF3gDqHGcR" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Youtube&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper inline"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2026-02-28-applemusiclogo.avif" alt="Image: Apple Music logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;a href="https://music.apple.com/us/album/datenbank-single/1882269188" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Apple Music&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper inline"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2026-02-28-amazonmusiclogo.avif" alt="Image: Amazon Music logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;a href="https://music.amazon.com/tracks/B0GR54SK2N?marketplaceId=ATVPDKIKX0DER&amp;amp;musicTerritory=US&amp;amp;ref=dm_sh_k6dDvkwdobXw05VcKh2aH2Rrz" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Amazon Music&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="wer-ist-hudl-eigentlich"&gt;Wer ist HudL eigentlich?&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2026-02-28-hudllogo.avif" alt="Image: HudL logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hannes und die Luken - kurz HudL - ist ein Bandprojekt aus Spaß, gegründet im Jahr 2024.&lt;br&gt;&#10;Die drei Musiker aus Bremerhaven und Bremen in der klassischen Rockbesetzung Gitarre, Schlagzeug und Bass verweben Texte aus dem Alltag mit eingängigen Melodien.&lt;/p&gt;&#10;&lt;p&gt;Nicht wenige HörerInnen erwischen sich dabei, dass ihre Finger unwillkürlich die Dauerschleife anwählen. Das bereuen sie keineswegs!&lt;/p&gt;&#10;&lt;h3 id="besetzung"&gt;Besetzung&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Lucas Freund - Gitarre und Lead-Vocals&lt;/li&gt;&#10;&lt;li&gt;Hannes Christen - Schlagzeug und Backing-Vocals&lt;/li&gt;&#10;&lt;li&gt;Lukas Preußer - Bass und Backing-Vocals&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="danke-für-die-hilfe"&gt;Danke für die Hilfe&lt;/h3&gt;&#10;&lt;p&gt;Im Hintergrund hatten wir helfende Hände, ohne die wir niemals so schnell und in dieser Qualität hätten veröffentlichen können. Vielen Dank daher an Lennert Röthemeyer für die Studioaufnahme des Schlagzeuges sowie an Feliks Weber und seine &lt;a href="https://www.nocturm-studios.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Nocturn Studios&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für Bass-Recording, Mixing und Mastering!&lt;/p&gt;&#10;</description></item><item><title>Werkzeugvermessung automatisieren</title><link>https://blog.schallbert.de/edingcnc-macro-explained/</link><pubDate>Tue, 10 Feb 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/edingcnc-macro-explained/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-02-10-tls-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Drawing of Tool Length Sensor on stock material with distance spindle collet, visualizing Z0 measurement"&#10; title="Werkzeugvermessung automatisieren" /&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-02-10-tls.avif" alt="Image: Image: Drawing of Tool Length Sensor on stock material with distance to the spindle collet, visualizing Z0 measurement" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Mich erreichen immer wieder Fragen zu meinem &lt;a href="https://blog.schallbert.de/macros-for-cnc/"&gt;Post über CNC-Makros&lt;/a&gt;. In diesem Beitrag möchte ich ein paar Antworten geben und Details zur Vermessung und Kompensation verschiedener Werkzeuglängen teilen.&lt;/p&gt;&#10;&lt;h2 id="frage-wie-berechnet-das-makro-die-werkzeuglänge"&gt;Frage: Wie berechnet das Makro die Werkzeuglänge?&lt;/h2&gt;&#10;&lt;p&gt;Im Abschnitt &lt;a href="https://blog.schallbert.de/macros-for-cnc/#function-measure-tool-length"&gt;Werkzeuglänge Vermessen&lt;/a&gt; steht folgender Code:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSafety&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ; &lt;span style="color:#75715e"&gt;# Go to safety height (machine coordinates)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; X&lt;span style="color:#f92672"&gt;[&lt;/span&gt;xPosTls&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Y&lt;span style="color:#f92672"&gt;[&lt;/span&gt;xPosTls&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ; &lt;span style="color:#75715e"&gt;# Go to tool length sensor&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSpindleTip &lt;span style="color:#f92672"&gt;+&lt;/span&gt; toolLengthEst &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Move Z down to 10mm above estimated tool tip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Was passiert in den Zeilen &lt;code&gt;1-3&lt;/code&gt;?&#10;Wenn &lt;code&gt;Z=0&lt;/code&gt; im Maschinenkoordinatensystem (&lt;code&gt;G53&lt;/code&gt;) die höchste Position ist, sind &lt;code&gt;zSpindleTip&lt;/code&gt; und &lt;code&gt;toolLengthEst&lt;/code&gt; dann negative Werte?&lt;/p&gt;&#10;&lt;h3 id="antwort"&gt;Antwort:&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Diese Zeile fährt die Maschine zum in Variable &lt;a href="https://github.com/Schallbert/macro_edingCNC_sorotec/blob/a547e36c0f47490464e1fdb56d1a4b0e706b44fe/macro.cnc#L341" target="_blank" rel="noopener noreferrer" class="external-link"&gt;#4506&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; hinterlegten Wert. Bei mir ist er &lt;code&gt;0&lt;/code&gt;, also maximale Z-Höhe. Dieser Wert bleibt permanent gespeichert (siehe &lt;a href="https://github.com/Schallbert/macro_edingCNC_sorotec/tree/main?tab=readme-ov-file#permanent" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) und man muss ihn vorher im Makro-Setup-Dialog festlegen.&lt;/li&gt;&#10;&lt;li&gt;Führt eine Bewegung zum Werkzeuglängensensor (WZLS) im Maschinenkoordinatensystem aus. Dafür habe ich meinen Sensor an einem festen Punkt auf der Maschine platziert.&lt;/li&gt;&#10;&lt;li&gt;Mit &lt;code&gt;zSpindleTip&lt;/code&gt; (Variable &lt;code&gt;#4509&lt;/code&gt;) ist die Höhe auf der &lt;code&gt;Z-Achse&lt;/code&gt; gemeint, in der die Spindelnase den Werkzeuglängensensor freitastet. Dieser Wert ist negativ. Weshalb? Der größte Wert der Z-Achse ist &lt;code&gt;Null&lt;/code&gt;, wenn sie sich ganz am oberen Anschlag der Maschine befindet. Senkt man dia Achse von hier aus zum WZLS ab, bewegt man sie in negative Z-Richtung. Addiert man die geschätzte Werkzeuglänge &lt;code&gt;toolLengthEst&lt;/code&gt; sowie eine Sicherheitshöhe von &lt;code&gt;10mm&lt;/code&gt; dazu (falls man sich bei der Werkzeuglängenschätzung vertan hat), bleibt aber unter dem mechanischen Anschlag der Z-Achse, so ist der Wert noch immer negativ. Die Spindelnase hat nun aber genügend Luft zum WZLS, weil jetzt virtuell Werkzeug und Sicherheitshöhe hinzuaddiert wurden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Pro Tipp: Im &amp;ldquo;Variables&amp;rdquo; Tab von EdingCNC kann man sich Werte von Variablengruppen anzeigen lassen. Man gibt eine Gruppe ein, z.B. &lt;code&gt;#4500&lt;/code&gt; und erhält die Live-Werte aller zehn Variablen.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Um das gut nachvollziehen zu können bietet sich an, die CNC-Software zum Ausprobieren nebenbei im Simulationsmodus laufen zu lassen.&#10;Konkretes Beispiel:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;zSafety &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# machine coordinates, Z-axis at top notch&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;zSpindleTip &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;86&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;43&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# machine coordinates, where empty spindle collet touches TLS&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;toolLengthEst &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;35&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# the length the tool is protruding from spindle collet&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;86&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;43&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;35&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#75715e"&gt;# = -41.32mm, so below max Z height&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die Benennung der Variablen habe ich in meinem Makro-Repository &lt;a href="https://github.com/Schallbert/macro_edingCNC_sorotec/tree/main?tab=readme-ov-file#permanent" target="_blank" rel="noopener noreferrer" class="external-link"&gt;auf Github&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; abgelegt.&lt;/p&gt;&#10;&lt;h3 id="frage-wie-berechne-ich-die-werkstückhöhe-nach-einer-werkzeugvermessung"&gt;Frage: Wie berechne ich die Werkstückhöhe nach einer Werkzeugvermessung?&lt;/h3&gt;&#10;&lt;p&gt;Folgendes Szenario: Man vermisst das Werkzeug und bestimmt anschließend per Makro die Werkstückhöhe. Wenn ich nun die Werkzeuglänge verändere ohne das Werkzeugwechselmakro aufzurufen, stimmt der Werkstücknullpunkt ja nicht mehr. Mit einem kürzeren Werkzeug liegt der Nullpunkt &amp;ldquo;zu tief&amp;rdquo;, bei einem Längeren &amp;ldquo;zu hoch&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Wie kann man dieses Problem lösen?&lt;/p&gt;&#10;&lt;h3 id="antwort-mehrere-lösungswege"&gt;Antwort: Mehrere Lösungswege&lt;/h3&gt;&#10;&lt;p&gt;Aus meiner Sicht gibt es hier drei Lösungswege.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Werkzeuglängenänderung immer über das Werkzeugwechselmakro laufen lassen. Man kann dort ja dieselbe Werkzeugnummer erneut eingeben.&lt;/li&gt;&#10;&lt;li&gt;Den Z-Nullpunkt über die Werkstückvermessung neu setzen (nicht empfohlen&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;)&lt;/li&gt;&#10;&lt;li&gt;Ohne Makro: Per Kommando &lt;code&gt;G92&lt;/code&gt; das Z-Koordinatensystem verschieben. Beispiel folgt.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-02-10-Enter-G92.avif" alt="Image: Coordinate shift G92 executed in the CNC software&amp;#39;s control terminal" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="lösung-3-koordinatensystem-verschieben"&gt;Lösung #3: Koordinatensystem verschieben&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-02-10-coordshift-before.avif" alt="Image: CNC&amp;#39;s coodinate window showing machine and workpiece coordinates before shift to reflect a change of tools" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Man kann das Werkstückkoordinatensystem mit den Befehl &lt;a href="https://www.linuxcnc.org/docs/html/gcode/g-code.html#gcode:g92" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;G92 Coordinate System Offset&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verschieben. Führt man ihn aus, so wird die ihm übergebene Zahl der angewählten Achse als neuer Wert im Koordinatensystem übernommen.&lt;/p&gt;&#10;&lt;p&gt;In diesem Fall wollen wir ja nur die relative Position verschieben und damit den Unterschied der Werkzeuglängen zueinander ausgleichen. Machen wir es mal konkret:&lt;/p&gt;&#10;&lt;p&gt;Nehmen wir an, das alte Werkzeug &lt;code&gt;T17&lt;/code&gt; hat eine freie Länge von &lt;code&gt;32mm&lt;/code&gt; und das neue Werkzeug (wieder &lt;code&gt;T17&lt;/code&gt;, denn wir spannen in diesem Beispiel nur um oder setzen ein Schlichtwerkzeug mit gleichen Schnittdaten ein) eine freie Länge von &lt;code&gt;24mm&lt;/code&gt;. Der neue Wert ist kürzer, und daher muss der Nullpunkt um &lt;code&gt;8mm&lt;/code&gt; nach unten wandern. &amp;ldquo;Nach unten&amp;rdquo; ist im Koordinatensystem der Z-Achse ein negativer Wert.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;;&lt;span style="color:#75715e"&gt;# Pseudocode&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;;&lt;span style="color:#75715e"&gt;# (CamelCaseWord steht für eine Variablennummer wie #5003):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;G92&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;AktuelleZPosition&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;AlteWerkzeuglänge&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NeueWerkzeuglänge&lt;/span&gt;)&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;;&lt;span style="color:#75715e"&gt;# 20mm + (32mm - 24mm ) = 20mm + 8mm = 28mm &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="verschiebung-werkstücknullpunkt-vs-werkstückkoordinatensystem"&gt;Verschiebung Werkstücknullpunkt vs. Werkstückkoordinatensystem&lt;/h3&gt;&#10;&lt;p&gt;Diese Rechnung mag erst einmal verwirren, versetzt sie das Koordinatensystem doch nach oben. Aber genau das ist erforderlich, damit der Werkstücknullpunkt relativ zur Werkzeugspitze erhalten bleibt: Das Werkzeug ist &lt;code&gt;8mm&lt;/code&gt; kürzer. Da wir den Werkstücknullpunkt nicht nach unten schieben, muss das Koordinatensystem halt nach oben versetzt werden. So wird die nun längere Strecke zwischen Werkzeugspitze und Werkstück kompensiert.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Coordinate System&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Tool tip position Z [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Workpiece position Z [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Tool top position Z [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Workpiece position Z [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Before shift&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Before shift&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;After shift&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;After shift&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Workpiece&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;28&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Machine&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-41.42&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-61,42&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-41.42&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-69,42&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-02-10-coordshift-after.avif" alt="Image: CNC&amp;#39;s coodinate window showing machine and workpiece coordinates after shift to reflect a change of tools. The shift only affects workpiece coordinates." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Man sieht hier, dass sich durch das Versetzen des Koordinatensystems die Position der Werkzeugspitze im &lt;em&gt;Werkstückkoordinatensystem&lt;/em&gt; verändert. Die Z-Achse wird dabei nicht bewegt. Das &lt;em&gt;Maschinenkoordinatensystem&lt;/em&gt; bleibt von dieser Änderung unberührt. Da die Z-Achse nun aber einen weiteren Weg zum Werkstück zurücklegen muss, liegt seine Position im Maschinenkoordinatensystem jetzt um &lt;code&gt;-8mm&lt;/code&gt; unter dem vorigen Wert.&lt;/p&gt;&#10;&lt;h2 id="rahmenbedingungen"&gt;Rahmenbedingungen&lt;/h2&gt;&#10;&lt;p&gt;Die Makros funktionieren nur unter bestimmten Voraussetzungen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Die Werkzeuglänge wurde zuvor gemessen bzw. Werkzeugwechsel werden stets per Makro gesteuert und nicht ohne &amp;ldquo;Wissen&amp;rdquo; der Maschine&lt;/li&gt;&#10;&lt;li&gt;Der Werkstücknullpunkt wird bei bereits bekannter Werkzeuglänge ermittelt.&lt;/li&gt;&#10;&lt;li&gt;Es gab keinen Not-Aus oder einen anderen kritischen Fehler. Dies würde den flüchtigen Speicher für die relevanten Variablen löschen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Nach einem Werkzeugwechsel die Werkstückhöhe in Z erneut einzumessen verschiebt meiner Ansicht nach den falschen Parameter. In Wirklichkeit hat sich doch nicht die Höhe des Werkstückes geändert, sondern die Position der Werkzeugspitze. Daher würde ich stets vorziehen, den sich ändernden Parameter anzupassen.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Vortrag: Software-defined vehicle</title><link>https://blog.schallbert.de/announcements/2026-01-25-lecture-sdv-available/</link><pubDate>Sun, 25 Jan 2026 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/announcements/2026-01-25-lecture-sdv-available/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2026-01-25-SDV-thumb.avif"&#10; class="post-cover"&#10; alt="Image: text reading SDV - software defined vehicle"&#10; title="Vortrag: Software-defined vehicle" /&gt;&#10;&lt;h2 id="-dezember-2025--neuer-vortrag-veröffentlicht"&gt;&amp;mdash; Dezember 2025 &amp;mdash; Neuer Vortrag veröffentlicht&lt;/h2&gt;&#10;&lt;p&gt;Ich habe vor kurzem einen Vortrag zum Thema software-defined vehicle gehalten. Die Folien sind jetzt zur Ansicht auf meiner Hauptseite unter &lt;a href="https://lectures.schallbert.de" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Vorträge&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verfügbar.&lt;/p&gt;&#10;&lt;p&gt;Die Rückmeldungen waren überwältigend positiv. Der Vortrag gewährt tiefe Einblicke in die aktuelle Lage der Automobilindustrie und hilft dabei zu verstehen, warum sich die &amp;ldquo;klassischen&amp;rdquo; Autobauer so schwer tun, mit den modernen Fahrzeugen aus Fernost mitzuhalten.&lt;/p&gt;&#10;</description></item><item><title>Stahlblech (SECC) bearbeiten</title><link>https://blog.schallbert.de/milling-secc/</link><pubDate>Mon, 22 Dec 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/milling-secc/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-12-22-HTPC-slotted-thumb.avif"&#10; class="post-cover"&#10; alt="Image: additional slots in HTPC case (SECC) for better cooling"&#10; title="Stahlblech (SECC) bearbeiten" /&gt;&#10;&lt;h2 id="das-werkstück"&gt;Das Werkstück&lt;/h2&gt;&#10;&lt;p&gt;Es handelt sich um ein PC-Gehäuse aus &lt;code&gt;0.8mm&lt;/code&gt; starkem, verzinktem Stahlblech &amp;ldquo;SECC&amp;rdquo;. Es stammt aus den frühen 2000ern und wurde kürzlich mit neuem Innenleben ausgestattet.&lt;/p&gt;&#10;&lt;h3 id="die-aufgabe"&gt;Die Aufgabe&lt;/h3&gt;&#10;&lt;p&gt;Durch die nun viel leistungsfähigere Grafikkarte (&lt;a href="https://de.wikipedia.org/wiki/Thermal_Design_Power" target="_blank" rel="noopener noreferrer" class="external-link"&gt;TDP&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &lt;code&gt;200W&lt;/code&gt;) und das beengte Design als &lt;a href="https://de.wikipedia.org/wiki/Home_Theater_Personal_Computer" target="_blank" rel="noopener noreferrer" class="external-link"&gt;HTPC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; war die entstehende Verlustwärme nicht im gewünschten Maße abzutransportieren, sodass im Gehäuse unter Last Temperaturen um &lt;code&gt;50°C&lt;/code&gt; vorherrschten. Das ist mir viel zu hoch.&lt;/p&gt;&#10;&lt;h3 id="die-gegebenheiten"&gt;Die Gegebenheiten&lt;/h3&gt;&#10;&lt;p&gt;Die Grafikkarte folgt gängigen aktuellen Designs: Ein metallener Radiator bedeckt die komplette Oberfläche der Platine. Seine Rippen sind in Querrichtung zum Sockel ausgerichtet und die darauf montierten, flachen Lüfter (&lt;code&gt;3x80mm&lt;/code&gt;) drücken die Luft durch die Leitbleche in Richtung Moainboard und Gehäusedeckel.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-12-22-graka-target-1080.avif" alt="Image: The graphics card for which cooling shall be optimized." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Zwischen Grafikkarte und Gehäusedeckel sind leider nur wenige Millimeter Platz. Nun möchte ich also genau an dieser Stelle zusätzliche Löcher in die Abdeckung einbringen. Das Gehäuse besitzt bereits Belüftungsbohrungen mit &lt;code&gt;3mm&lt;/code&gt; Durchmesser in einem &lt;code&gt;10x19mm 45°&lt;/code&gt;-Raster an den Seiten. Ich versuche nun, dieses Muster auch auf die Bohrungen für die Grafikkartenentlüftung anzuwenden. So komme ich auf eine Gesamtzahl von 133 Stück, welche ich der Bequemlichkeit wegen ungern anzeichnen, körnen und von Hand fertigen möchte.&lt;/p&gt;&#10;&lt;p&gt;Also erstelle ich mir eine Zeichnung im CAD und schließlich ein Bohrzyklus-Programm für meine Fräse.&lt;/p&gt;&#10;&lt;h2 id="werkzeug1-3mm-bohrer"&gt;Werkzeug1: 3mm-Bohrer&lt;/h2&gt;&#10;&lt;p&gt;Ich habe zwar noch ein paar Baumarkt-Stahlbohrer aus üblichem Hochgeschwindigkeits-Schnellarbeitsstahl (HSS) herumliegen, diese befinden sich jedoch in teils schlechtem Zustand. Also nehme ich einen noch unbenutzten Stahlbohrer aus meinem Dremel-Sortiment.&lt;/p&gt;&#10;&lt;h3 id="bohrparameter"&gt;Bohrparameter&lt;/h3&gt;&#10;&lt;p&gt;Er hat eine hellgraue, metallene Färbung und sieht unbeschichteten Werkzeugen aus Vollhartmetall so ähnlich, dass ich (fälschlicherweise) annehme, er sei aus Wolframcarbid. Daher wählte ich folgende Parameter:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;3mm VHM-Bohrer für Stahl, 2 Schneiden: S17000 F470 Z-0.2 Rückzug 0.5mm&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Ich arbeite wie üblich komplett trocken, setze also keine Kühlschmierstoffe ein.&lt;/p&gt;&#10;&lt;h3 id="bohrvorbereitung"&gt;Bohrvorbereitung&lt;/h3&gt;&#10;&lt;p&gt;Zur Vorbereitung des Werkstücks bringe ich auf den Abschnitt des Gehäuses Klebeband an, wo später die Löcher sein sollen. Mit der maximalen Bohrtiefe durchstoße ich das Material nur minimal. Da das Klebeband auf diese Weise intakt bleibt, verliere ich keinen Unterdruck durch offenliegende Löcher im Vakuumtisch.&lt;/p&gt;&#10;&lt;p&gt;Ich lege den Gehäusedeckel mit der Außenseite auf das Maschinenbett und decke freie Löcher meines Vakuumtisches mit einer Gummimatte ab. Anschließend bringe ich das Portal in eine Position nahe der Löcher, damit die Spindel im Pfadverlauf nicht mit der Gehäusewand kollidiert. Außerdem entferne ich das Anfahren der Parkposition &lt;code&gt;G28&lt;/code&gt; aus der Fräsdatei, denn nun liegt der Gehäusedeckel im Weg. Nach Hochziehen der Z-Achse von der letzten Bohrung endet das Programm mit &lt;code&gt;M30&lt;/code&gt; (Stop und zurückspulen).&lt;/p&gt;&#10;&lt;h3 id="das-ergebnis"&gt;Das Ergebnis&lt;/h3&gt;&#10;&lt;p&gt;Um es vorwegzunehmen: Der Bohrer hält nicht durch. Dass etwas nicht in Ordnung ist, merke ich nach wenigen Sekunden am Geräusch der Maschine: Sie ist plötzlich viel leiser als vorher.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-12-22-hss-meltdown.avif" alt="Image: The HSS drill only lives to the fourth hole of 133, then its tip is gone." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich schaffe auf diese Weise schlappe 3 von 133 Löchern. Danach befindet sich die Spitze des Bohrers in einem der halb fertiggestellten Löcher und scheint mit diesem verschweißt.&lt;/p&gt;&#10;&lt;h3 id="problemanalyse"&gt;Problemanalyse&lt;/h3&gt;&#10;&lt;p&gt;Anscheinend war der Bohrer gar nicht aus Vollhartmetall, und damit bin ich mit völlig ungeeigneten Parametern unterwegs gewesen. Ich habe die Symptome in zwei Foren gegeben (&lt;a href="https://hobbyline.info/forum/index.php?thread/687-2-schneider-stahlblech-wie-anwenden/&amp;amp;postID=12837#post12837" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hobbyline&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sowie &lt;a href="https://www.cnczone.nl/viewtopic.php?t=23051" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cnczone&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) und ich erhalte die Information, dass ich besser mit &lt;code&gt;S1000 F30&lt;/code&gt; (!) unterwegs sein sollte, wenn ich ein paar mehr Löcher mit dem Bohrer hätte schaffen wollen.&lt;/p&gt;&#10;&lt;h3 id="die-lösung"&gt;Die Lösung&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-12-22-slots.avif" alt="Image: Slot milling solves the problem as I can use a carbide endmill now" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich wechsle also auf Hartmetallfräser. Ich habe nur einen in meiner Sammlung, der für Stahl gemacht wurde. Dieser hat allerdings einen größeren Durchmesser, sodass ich mein Design anpassen muss.&lt;/p&gt;&#10;&lt;p&gt;Nun erzeuge ich mir im CAD Langlöcher, welche mit &lt;code&gt;6mm&lt;/code&gt; Breite und &lt;code&gt;22mm&lt;/code&gt; Länge im &lt;code&gt;45°&lt;/code&gt;-Winkel über dem Radiator der Grafikkarte platziert sind.&lt;/p&gt;&#10;&lt;h2 id="werkzeug2-6mm-vhm-fräser"&gt;Werkzeug2: 6mm VHM-Fräser&lt;/h2&gt;&#10;&lt;p&gt;Ich wende meine bereits &lt;a href="https://blog.schallbert.de/milling-steel/"&gt;bewährte Frästechnik&lt;/a&gt; für SECC-Stahl an und verfeinere sie weiter: Hohe Spindeldrehzahl, großer Vorschub, geringe Tiefenzustellung.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;6mm VHM-Fräser, AlTiN-beschichtet, 2 Schneiden, 0.2mm 45°-Fase: S10600 F880 Z-0.18&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="bearbeitungsvorgang"&gt;Bearbeitungsvorgang&lt;/h3&gt;&#10;&lt;p&gt;Die Maschine arbeitet ruhig und das Werkzeug ist nach Fertigstellung, die knapp zehn Minuten benötigt, nur handwarm. Beim Eintauchen wird sie kurz lauter. Das nächste mal werde ich die Eintauchrampe noch flacher einstellen - etwa auf &lt;code&gt;5°&lt;/code&gt;. Damit verspreche ich mir, die untere Schneidfläche zu schonen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/hoqrsaBrW2nrjszyJTPEpC" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Cutting SECC steel of my PC case&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="schnittqualität"&gt;Schnittqualität&lt;/h3&gt;&#10;&lt;p&gt;Die Schnittkanten sind gratfrei, hochglänzend und messerscharf. Daher ist eine Fase anzubringen. Dies mache ich von Hand mit Ziehklinge und Cuttermesser, was ich schnell bereue: Ich bekomme das nicht so sauber und gleichmäßig hin wie gewünscht.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-12-22-slots-complete.avif" alt="Image: SECC surface quality after milling &amp;amp; de-burr" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Nicht so schlimm: Ich erhalte ein ordentlich gearbeitetes Gehäuse mit deutlich verbesserter Kühlung praktisch zum Nulltarif (Wenn man vom Lehrgeld absieht). In der Abbildung oben ist der Radiator der Grafikkarte klar durch die neuen Schlitze im Gehäuse zu erkennen.&lt;/p&gt;&#10;</description></item><item><title>Jekyll-dockerimage: Bundler und Gemfile</title><link>https://blog.schallbert.de/bundler-ci-gemfile-issue/</link><pubDate>Fri, 14 Nov 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/bundler-ci-gemfile-issue/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-11-15-bundler-errors.avif"&#10; class="post-cover"&#10; alt="Image: bundler logo with error overlay"&#10; title="Jekyll-dockerimage: Bundler und Gemfile" /&gt;&#10;&lt;h2 id="was-ist-bundler"&gt;Was ist &lt;em&gt;bundler&lt;/em&gt;?&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://bundler.io/guides/faq.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;Bundler&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist ein Werkzeug, mit dem Abhängigkeiten zwischen Modulen und Bibliotheken in der Programmiersprache &lt;a href="https://www.ruby-lang.org/en/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;Ruby&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verwaltet und versioniert werden können. Ich benutze &lt;em&gt;Bundler&lt;/em&gt;-Kommandos regelmäßig, z.B. um meinen Blog zu bauen und auf dem Server live zu stellen.&lt;/p&gt;&#10;&lt;p&gt;Will ich lokal bauen, so tippe ich beispielsweise&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bundle exec jekyll serve --incremental --future&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;in die Konsole. Dieser Befehl sagt &lt;em&gt;Bundler&lt;/em&gt;, es soll die Applikation &lt;em&gt;jekyll&lt;/em&gt; im Server-Modus ausführen und ihr Parameter mitgeben, welche &lt;em&gt;jekyll&lt;/em&gt; nicht bei jeder Dateiänderung komplett neu bauen lässt und noch nicht veröffentlichte Seiten dennoch mit erstellt.&lt;/p&gt;&#10;&lt;h2 id="bundler-in-der-ci-pipeline"&gt;&lt;em&gt;bundler&lt;/em&gt; in der CI-Pipeline&lt;/h2&gt;&#10;&lt;p&gt;Auch für die Veröffentlichung meiner Seite nutze ich &lt;em&gt;Bundler&lt;/em&gt; als Teil eines Jekyll-Dockerimages, wie bereits öfters auf meiner Seite verlinkt, z.B. bei der Umstellung auf &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;self-hosted&lt;/a&gt;. Seit ich auf eine neue Version der Blog-Software umgestiegen bin, sehe ich in meiner CI-Pipeline rätselhafte Fehler. Lokal baut das System jedoch einwandfrei. Wie man solche Fehler angeht und behebt, habe ich hier festgehalten.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;There was an error while trying to write to /path/to/Gemfile.lock&amp;rdquo; - CI console output&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="keine-schreibrechte"&gt;Keine Schreibrechte&lt;/h3&gt;&#10;&lt;p&gt;&lt;em&gt;Bundler&lt;/em&gt; selbst kann auf meiner CI wie der &lt;em&gt;Jekyll&lt;/em&gt;-user auch nur Dateien lesen und nicht schreiben. Was ich für den &lt;em&gt;Jekyll&lt;/em&gt; output mit &lt;code&gt;chown&lt;/code&gt; beheben kann, möchte ich &lt;em&gt;bundler&lt;/em&gt; aber gar nicht erlauben: Ich will ja gerade, dass &lt;code&gt;Gemfile.lock&lt;/code&gt; zwischen meiner lokalen Bauumgebung und der CI identisch bleibt, damit ich mögliche Fehler schon vorab beheben kann.&lt;/p&gt;&#10;&lt;h3 id="spezifikationsdatei-für-bundler"&gt;Spezifikationsdatei für &lt;em&gt;bundler&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;In der Datei &lt;code&gt;Gemfile.lock&lt;/code&gt; stehen alle Abhängigkeiten der verwendeten Applikation drin, inkl. Versionsnummer und Bezugsquelle. Wird nur der Quellcode der Anwendung plus diese Datei geliefert, so kann &lt;em&gt;bundler&lt;/em&gt; eine konkrete Version aller Abhängigkeiten beim Bauen ziehen, sodass unabhängig von Maschine und Version mit ähnlichen Voraussetzungen gearbeitet werden kann.&lt;/p&gt;&#10;&lt;h3 id="fehlerbehebung"&gt;Fehlerbehebung&lt;/h3&gt;&#10;&lt;p&gt;Ein erster Versuch der Behebung durch Angleichung sämtlicher Versionen zwischen lokal und CI schlug fehl.&#10;Der zweite Versuch, dem Jekyll-user Schreibrechte auf die &lt;code&gt;Gemfile.lock&lt;/code&gt; zu geben, schlug ebenfalls fehl.&#10;Der dritte Versuch führte mich zur Bundler-Website, wo ich die Parameter genauer unter die Lupe nahm, Stichwort &amp;ldquo;frozen&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Es ist nämlich so, dass man &lt;em&gt;bundler&lt;/em&gt; verbieten kann, die &lt;code&gt;Gemfile.lock&lt;/code&gt; umzuschreiben. Dafür verwendet man das Kommando&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bundle config set frozen true&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Laut &lt;a href="https://bundler.io/v2.7/man/bundle-config.1.html#LIST-OF-AVAILABLE-KEYS" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dokumentation&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; wird die Verarbeitung abgebrochen, sobald die Datei umgeschrieben werden müsste.&lt;/p&gt;&#10;&lt;p&gt;Obwohl ich diesen Befehl meiner &lt;code&gt;yaml&lt;/code&gt;-Datei in der CI hinzugefügt hatte, schlug das Bauen erneut fehl. Auslöser war dieselbe Bibliothek &lt;a href="https://nokogiri.org/#" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;nokogiri&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; wie vorher, nur war die Fehlermeldung nun viel hilfreicher:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-11-14-gemfile-lock-incompatible.avif" alt="Image: jekyll build error due to build platform incompatibility between local and remote." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="gemfilelock-für-verschiedene-umgebungen-optimieren"&gt;&lt;code&gt;Gemfile.lock&lt;/code&gt; für verschiedene Umgebungen optimieren&lt;/h2&gt;&#10;&lt;p&gt;Also befolge ich den Vorschlag und führe das gewünschte Kommando auf meinem lokalen Rechner aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bundle lock --add-platform x86_64_musl &lt;span style="color:#75715e"&gt;# my CI runner&amp;#39;s environment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Randnotiz: &lt;a href="https://musl.libc.org/about.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;musl&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist eine &lt;em&gt;libc&lt;/em&gt;-Implementierung für Linux, sprich mit ihrer Hilfe laufen viele in &lt;code&gt;C&lt;/code&gt; geschriebene Programme.&lt;/p&gt;&#10;&lt;p&gt;In meiner Lockfile taucht daraufhin folgender Eintrag auf:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;nokogiri (1.18.10-x86_64-linux-musl)&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Nun läuft meine CI wieder sauber durch und nebenbei habe ich die Portabilität meines Jekll-Setups verbessert.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-11-14-jekyll-yml-freeze-bundler.avif" alt="Image: successful CI run with updated dependencies" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>fail2ban: Update zerstört Konfiguration</title><link>https://blog.schallbert.de/fail2ban-error-configuration-bad-value/</link><pubDate>Thu, 23 Oct 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/fail2ban-error-configuration-bad-value/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-23-docker-chain-thumb.avif"&#10; class="post-cover"&#10; alt="Image: iptables view for docker-user chain"&#10; title="fail2ban: Update zerstört Konfiguration" /&gt;&#10;&lt;p&gt;Alle paar Wochen sehe ich auf meinem Server üblicherweise nach dem Rechten:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Ist bei Docker alles ok? &lt;code&gt;docker ps&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Werden die Backups korrekt erzeugt? &lt;code&gt;borgmatic repo-info&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Funktionieren meine Abwehrmaßnahmen? &lt;code&gt;iptables -n -L&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Laufen meine Daemons zur Update-Automation? &lt;code&gt;htop -F /server-config&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Dieses mal allerdings zeigte &lt;code&gt;iptables&lt;/code&gt; mir gähnende Leere. Das ist ungewöhnlich und deutet darauf hin, dass mit &lt;em&gt;fail2ban&lt;/em&gt; etwas nicht stimmt.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# iptables -n -L&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Chain FORWARD &lt;span style="color:#f92672"&gt;(&lt;/span&gt;policy DROP&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;target prot opt source destination &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DOCKER-FORWARD all -- 0.0.0.0/0 0.0.0.0/0 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Chain DOCKER-USER &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; references&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;target port opt source destination &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Übersetzt lautet die Ausgabe etwa in etwa&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Die &lt;code&gt;FORWARD&lt;/code&gt;-Chain leitet wie von &lt;em&gt;Docker&lt;/em&gt; benötigt an die für Container relevante &lt;code&gt;DOCKER-USER&lt;/code&gt;-Chain weiter. Unter dieser sind allerdings keine Anwendungen gelistet, die Pakete zurückweisen oder deren Weiterleitung verbieten (&lt;code&gt;REJECT/DROP&lt;/code&gt;) können.&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;An dieser Stelle müssten zwei Einträge für &lt;code&gt;caddy-server&lt;/code&gt; und &lt;code&gt;caddy-ratelimit&lt;/code&gt; auftauchen.&lt;/p&gt;&#10;&lt;h2 id="fail2ban-error-failed-during-configuration"&gt;&amp;ldquo;fail2ban ERROR Failed during configuration&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;Also schaue ich zuerst mal bei Docker nach:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker logs -t fail2ban &lt;span style="color:#75715e"&gt;# Logs für den Container mit Namen fail2ban mit Zeitempel ausgeben&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Statt wie üblich nach dem Durchscrollen mit &lt;code&gt;&amp;lt;timestamp&amp;gt; Server ready&lt;/code&gt; belohnt zu werden, bekomme eine nicht enden wollende Flut an Logmeldungen angezeigt. Sie wiederholen sich und zeigen, dass &lt;em&gt;fail2ban&lt;/em&gt; mit &lt;code&gt;Exit with code 255&lt;/code&gt; aussteigt. Einen besseren Überblick liefert mir das Log, wenn ich nur die letzten 100 Zeilen ausgebe:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker logs -t --tail &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt; fail2ban &lt;span style="color:#75715e"&gt;# Nur die letzten 100 Zeilen des Logs ausgeben, mit Zeitstempel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="die-fehlermeldung"&gt;Die Fehlermeldung&lt;/h3&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;&amp;lt;timestamp, ID&amp;gt; ERROR Failed during configuration: Bad value substitution: &#10;option &amp;#39;action&amp;#39; in section &amp;#39;caddy-ratelimit&amp;#39; contains an interpolation key &amp;#39;banaction&amp;#39; which is not a valid option name. &#10;Raw value: &amp;#39;%(action_)s&amp;#39;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die Meldung ist interessant, weil ich genau an dieser Stelle auch schon beim Erstellen im Zuge des Artikels &lt;a href="https://blog.schallbert.de/fail2ban-with-caddy/#check-jail"&gt;Fail2ban mit Caddy&lt;/a&gt; Probleme mit der &lt;code&gt;jail.local&lt;/code&gt; hatte. Damals musste ich &lt;code&gt;action = iptables-multiport&lt;/code&gt; aus der Konfiguration nehmen, weil die auslösenden IP-Adressen auf der falschen &lt;code&gt;CHAIN&lt;/code&gt; (INPUT) gelandet waren.&lt;/p&gt;&#10;&lt;p&gt;Nun scheint also die default-action auf der &lt;code&gt;DOCKER-USER&lt;/code&gt; chain nicht mehr zu funktionieren. Also gehe ich ins Netz und suche nach ein paar Quellen für eine mögliche Lösung. Ich finde aber weder in der Update-Historie von &lt;em&gt;fail2ban&lt;/em&gt; noch in der von mir benutzten Distribution von &lt;a href="https://docs.linuxserver.io/images/docker-fail2ban/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;linuxserver.io&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Hinweise auf Updates, die hier große Änderungen im &lt;code&gt;action.d&lt;/code&gt; Ordner veranlassen würden.&lt;/p&gt;&#10;&lt;h3 id="lösung-per-banaction--iptablestypemultiport"&gt;Lösung per &lt;code&gt;banaction = iptables[type=multiport]&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Also bemühe ich die Websuche und finde einen Kommentar eines &lt;a href="https://github.com/fail2ban/fail2ban/issues/3894" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Maintainers von fail2ban&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;[&amp;hellip;] With action = iptables[type=multiport, protocol=tcp, chain=DOCKER-USER] you overwrote the default action [&amp;hellip;] [Solution:] simply set the chain variable only in the jail [&amp;hellip;]: &lt;code&gt;port = http, https banaction = iptables[type=multiport] chain = DOCKER-USER&lt;/code&gt;&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Die Grundlage dieser Aussage kann man in den &lt;a href="https://docs.docker.com/engine/network/packet-filtering-firewalls/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Docker Docs&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; nachlesen. Nebenbemerkung: An allen Stellen wird empfohlen, für zukünftige Anwendungen das modernere &lt;em&gt;nftables&lt;/em&gt; statt &lt;em&gt;iptables&lt;/em&gt; zu verwenden. &lt;a href="https://linux-audit.com/networking/nftables/differences-between-iptables-and-nftables-explained/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gibt es einen Artikel zum Thema.&lt;/p&gt;&#10;&lt;p&gt;Somit kann ich das bisher in meiner &lt;code&gt;jail.local&lt;/code&gt; vorhandene &lt;code&gt;action&lt;/code&gt;-Feld komplett löschen und stattdessen &lt;code&gt;banaction&lt;/code&gt; verwenden. Sie sieht nun wie folgt aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# jail.local update Oct-2025&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Server/Client errors: 401 403 404 500&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;caddy-status]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;enabled = true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;chain = DOCKER-USER&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;port = http,https&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;filter = caddy-status&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;logpath = &amp;lt;path/to/logfiles&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;findtime = 10min&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;maxretry = 5&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;bantime = 1d&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;banaction = iptables[type=multiport]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mit dieser Konfiguration fährt &lt;em&gt;fail2ban&lt;/em&gt; normal hoch und gibt &lt;code&gt;Server ready&lt;/code&gt; aus.&lt;/p&gt;&#10;&lt;h3 id="überprüfen-der-lösung-mittels-iptables"&gt;Überprüfen der Lösung mittels &lt;em&gt;iptables&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Ich erzeuge mir wie oben beschrieben Einträge in den Test-Logfiles, welche die Aktionen von &lt;code&gt;caddy-status&lt;/code&gt; und &lt;code&gt;caddy-ratelimit&lt;/code&gt; auslösen sollen. Wie erwartet erhalte ich in der &amp;ldquo;blocklist&amp;rdquo;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# iptables -n -L&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Chain DOCKER-USER &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; references&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;target prot opt source destination &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;f2b-caddy-ratelimit tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 80,443&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;f2b-caddy-status tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 80,443&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Albasia-Holz fräsen</title><link>https://blog.schallbert.de/albasia-on-cnc/</link><pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/albasia-on-cnc/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-on-cnc-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Macro shot of Albasia plywood, layer view"&#10; title="Albasia-Holz fräsen" /&gt;&#10;&lt;h2 id="was-ist-albasia"&gt;Was ist Albasia?&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-samplecolors.avif" alt="Image: Albasia sandwich material samples with coloured melamine surface" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Albasia (&lt;a href="https://en.wikipedia.org/wiki/Falcataria_falcata" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Falcataria falcata&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) ist ein sehr leichtes Weichholz. Es kommt hauptsächlich in &lt;a href="https://www.plywoodworking.com/exploring-albasia-wood-a-sustainable-solution-for-the-timber-industry.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Südostasien&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; vor und wächst schnell, was sein &lt;a href="https://www.juergensen.de/news/albasia-sperrholz-unsere-gruene-loesung-fuer-die-industrie/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Nachhaltigkeitsrating&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verbessert. Seine Farbe variiert zwischen weißlich-gelb und pastelligem braun-rosa, ist im Vergleich zu Buche oder Eiche aber deutlich heller.&lt;/p&gt;&#10;&lt;p&gt;Hierzulande ist es meist als Multiplexplatte im Handel. Dadurch werden Nachteile wie die starke Neigung zum &lt;a href="https://www.holzplatten-shop.de/media/pdf/2e/97/f3/Das-Schwinden-und-Quellen-von-Holz.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Schwinden und Quellen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; abgemildert.&lt;/p&gt;&#10;&lt;p&gt;Die Dichte von Albasia ist nochmal etwa 15% geringer als die des ohnehin schon sehr leichten Pappelholzes. Es bildet im Wuchs lange Fasern, was die Zerspanbarkeit beeinträchtigt. Da es durch seine vielen Lufteinschlüsse zudem gut dämmt, wird es häufig im Fahrzeugausbau (Camper, Boote etc.) verwendet. Um die geringere Festigkeit auszugleichen, wird Albasia-Multiplex oft nicht roh, sondern als Sandwich-Platte angeboten.&lt;/p&gt;&#10;&lt;h3 id="mit-sandwichtechnik-zu-guten-materialeigenschaften"&gt;Mit Sandwichtechnik zu guten Materialeigenschaften&lt;/h3&gt;&#10;&lt;p&gt;Mein Testmaterial verfügt über eine beidseitig aufgebrachte, &lt;code&gt;0.8mm&lt;/code&gt; starke Schicht aus Hochdrucklaminat (HPL). Die Oberfläche ist in farbigen Melamindekoren erhältlich und kann als Extra gehärtet bestellt werden, was sie mattiert sowie unempfindlich gegenüber Kratzern macht.&lt;/p&gt;&#10;&lt;h3 id="sind-albasia-verbundplatten-für-den-außenbereich-geeignet"&gt;Sind Albasia-Verbundplatten für den Außenbereich geeignet?&lt;/h3&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Unsere Platten sind übrigens nicht für den Außenbereich vorgesehen.&amp;rdquo; - EpicPLY, Händler&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Auch wenn das Material für den Innenausbau entwickelt wurde: Ich teste die Wetterfestigkeit der Platten momentan mit einem Experiment. Dafür verschraube ich die Verbundplatte im rechten Winkel auf Stoß miteinander, sodass einmal die Beschichtung und einmal die Holzkante der Witterung direkt ausgesetzt sind. Ich habe eine offene Kante mit Hartöl versiegelt und eine unbehandelt belassen. Nun hänge ich die Konstruktion ein Jahr draußen auf den Balkon und sehe dann, wie sich das Material verhält - Update dann in einem gesonderten Post!&lt;/p&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Image: test material and tools: plywood sample, srews, drills"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-outdoor-test-setup.avif" alt="Image: test material and tools: plywood sample, srews, drills" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Image: Albasia weather test mounted outside"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-outdoor-test-mount.avif" alt="Image: Albasia weather test mounted outside" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Image: Comparison of oiled and raw plywood surface on day0 of test. Both look fresh and new."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-outdoor-test-surface.avif" alt="Image: Comparison of oiled and raw plywood surface on day0 of test. Both look fresh and new." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Versuchsaufbau für Bewetterungs- und Schraubentest.&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-outdoor-test-setup.avif" alt="Image: test material and tools: plywood sample, srews, drills" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Vorbereitung für den Schraubentest&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-outdoor-test-mount.avif" alt="Image: Albasia weather test mounted outside" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Der zusammengesetzte Testaufbau ist zur Bewitterung angebracht.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-outdoor-test-surface.avif" alt="Image: Comparison of oiled and raw plywood surface on day0 of test. Both look fresh and new." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Am Tag 0: Beide Oberflächen sehen noch frisch aus.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;p&gt;Meine Erwartung: anders als bei Birkensperrholz und ähnlich wie Pappelholz wird Albasia nicht für den Außenbereich taugen. Ich nehme an, dass sich die Fasern im Kantenbereich mit Wasser vollsaugen und das Material zum Quellen bringen. Dies würde es schnell verrotten lassen. Außerdem schätze ich die Gefahr für Insektenbefall als hoch ein, weil die Fasern echt weich sind und sich leicht abschälen lassen. Daher würde ich es nur in Innenräumen einsetzen.&lt;/p&gt;&#10;&lt;h3 id="verschraubungen"&gt;Verschraubungen&lt;/h3&gt;&#10;&lt;p&gt;Trotz seines leichten Kerns sei die Albasia-Verbundplatte gut zu verschrauben und würde mit einer &amp;ldquo;Schraubenauszugsfestigkeit von &lt;code&gt;120kg&lt;/code&gt;&amp;rdquo; bei einer &lt;code&gt;4mm-Holzschraube&lt;/code&gt; in einem &amp;ldquo;sehr stabilen Bereich&amp;rdquo; liegen, meint mein Händler.&lt;/p&gt;&#10;&lt;p&gt;Als Laie kann ich mit solchen Werten nicht viel anfangen und führe ein kleines Experiment durch. Ich nehme eine &lt;code&gt;4x35&lt;/code&gt; Holzschraube her und schaue mir die vorgesehenen Anzugsmomente an. Hier gilt für meinen Hersteller etwa:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;strong&gt;Parameter&lt;/strong&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;strong&gt;Wert&lt;/strong&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Auszugsparameter \( f_{ax,k} \)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;14 N/mm²&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Torsionsfestigkeit \( f_{tor,k} \)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3 Nm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Einschraubdrehmoment \( R_{tor,mean} \)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;\( \frac{f_{tor,k}}{R_{tor,mean}} \) ≥ 1,5&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Vorbohren in Nadelholz&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.5 mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Vorbohren in Laubholz&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3 mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;a href="https://www.spax.com/de-de/p/universalschraube-teilgewinde-senkkopf-t-star-plus-4cut-wirox.html?variant=0191010400353" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Quelle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Nun schaue ich, ob ich zwei Bretter auf Stoß mit dem vorgesehenen Anzugsmoment verbinden kann. Dafür nehme ich einen Drehmomentschraubendreher zur Hand. Doch ich komme gar nicht bis zum Auslösen der Mechanik: Bereits vorher beginnt die Schraube, sich in die Deckschicht zu ziehen. Das maxinale Drehmoment, bevor die Schraube im Material &amp;ldquo;verschwindet&amp;rdquo;, schätze ich auf lediglich &lt;code&gt;0.8Nm&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2025-10-06-albasia-fastener-test.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Video: Ich versuche, eine Schraube auf Drehmoment im Holz festzuziehen.&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Somit lassen sich durchaus stabile Schraubverbindungen herstellen, jedoch würde ich beim Anziehen stets vorsichtig vorgehen, großzügig mit der Menge an Schrauben sein und zusätzlich Dübel- oder formschlüssige Verbindungsarten wählen.&lt;/p&gt;&#10;&lt;h2 id="albasia-auf-der-cnc-fräse"&gt;Albasia auf der CNC-Fräse&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-6mm-schlicht.avif" alt="Image: fine-cutting Albasia with a 6mm carbide endmill" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Albasia kann wie jedes andere Weichholz auch mit der CNC-Fräse verarbeitet werden. Für Weichhölzer typische Probleme wie Ausrisse oder Fasernester sind auch hier zu erwarten. Hinzu kommt die harte Beschichtung, welche die Bearbeitung wegen anderer Anforderungen an die Parameterwahl noch einmal schwieriger macht.&lt;/p&gt;&#10;&lt;h3 id="mit-welchen-fräsern-gelingt-die-bearbeitung"&gt;Mit welchen Fräsern gelingt die Bearbeitung?&lt;/h3&gt;&#10;&lt;p&gt;Für den Fräser ist der Materialmix herausfordernd. Beim Fräsen in HPL hatte ich im Artikel &lt;a href="https://blog.schallbert.de/cnc-router-overload/"&gt;zur Überlastung meines Fräsmotors&lt;/a&gt; ja bereits schlechte Erfahrungen gesammelt. Das Material damals besaß im Vergleich zu Albasia-Verbund jedoch einen viel höheren Hartpapierfaseranteil (Späne sind hellbraun und mit vielen &amp;ldquo;Punkten&amp;rdquo; durchzogen), was den Fräser in Rekordzeit stumpf werden ließ.&lt;/p&gt;&#10;&lt;p&gt;Ich setze Vollhartmetallfräser &lt;code&gt;VHM&lt;/code&gt; mit Spanbrecher und kaum Drall (&lt;code&gt;15°&lt;/code&gt;) ein, um die langen Holzfasern zu brechen. Sie verstopfen sonst sehr schnell die Absaugung und sammeln sich um den Fräser an, was zu Brandgefahr führen kann.&lt;/p&gt;&#10;&lt;p&gt;Für die Kantenbearbeitung habe ich mit komprimierenden Fräsern wie dem Viertelstab gute Erfahrungen gemacht. Taucht man nur leicht in das Material ein, so eignet sich aber auch ein V-Cut. Bei größerer Eintauchtiefe erreichen Halbrundfräser &amp;ldquo;Ball-nose&amp;rdquo; ein schönes Fräsbild.&lt;/p&gt;&#10;&lt;p&gt;Auf dem Bild ist ein Vergleich dargestellt. Rechts mit grün-grauer Beschichtung das Rohmaterial, geschnitten an einer Formatkreissäge. Links in violett ist das von mir CNC-gefräste Werkstück zu sehen. Das Schnittbild unterscheidet sich deutlich voneinander und die mit dem Fräser bearbeitete Fläche ist viel sauberer und ohne Ausrisse.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-mill-vs-saw.avif" alt="Image: Albasia / Albizia plywood sandwiched between HPL coatings. Saw cut reveals to be much more coarse with pull-outs versus the milled surface." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Geht man in der Vollnut mit spanbrechenden Fräsern vor, lohnt sich ein Schlichtgang mit gerade gezahntem Fräser und großen Vorschüben bei nur geringer Abnahme von &lt;code&gt;0.2mm&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Allen Fräsern gemein sein muss&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;eine möglichst scharfe Schneide (keine Beschichtung!)&lt;/li&gt;&#10;&lt;li&gt;eine große Schnittgeschwindigkeit (keine zu kleinen Durchmesser verwenden)&lt;/li&gt;&#10;&lt;li&gt;ein verhältnismäßig großer Vorschub.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Mit Einschneidern habe ich eine hohe Oberflächengüte erzielen können. Der große Spankanal sorgt für gute Abfuhr, allerdings sind Ausrisse bei der Arbeit im Vollen mitunter problematisch. Die Schneidlast ist recht hoch, und die Schneiden sind im harten Obermaterial nicht dauerhaltbar. Für Schlichtgänge allerdings ideal.&lt;/p&gt;&#10;&lt;h3 id="welche-parameter"&gt;Welche Parameter?&lt;/h3&gt;&#10;&lt;p&gt;Albasia ist so weich, dass es den Achsantrieben der Fräse kaum Widerstand bietet. Die Schnittgeschwindigkeit ist hoch anzusetzen, Quellen im Internet empfehlen etwa &lt;code&gt;500m/min&lt;/code&gt; als Richtwert. Als Vorschub habe ich Werte bis &lt;code&gt;6000mmm/min&lt;/code&gt; ausprobiert. Ich habe aber auch nur kleine Werkstücke bearbeitet, weswegen noch höhere Vorschübe wegen der ständigen Richtungswechsel kaum einen Zeitvorteil böten. Ein zusätzlicher Schlichtgang ist wegen der Marken des Spanbrechers Pflicht.&lt;/p&gt;&#10;&lt;p&gt;Einen guten Kompromiss zwischen Fräszeit und Oberflächenqualität habe ich für verschiedene Fräser unten festgehalten. Die Gravierfräser lasse ich allerdings ausschließlich in der HPL-Schicht arbeiten, weswegen ich Vorschub und Drehzahl etwas reduziere.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Werkzeug (s= Anzahl Schneiden)&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Vorschub &lt;code&gt;F&lt;/code&gt; [mm/min]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Drehzahl &lt;code&gt;S&lt;/code&gt; [U/min]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Zustellung Z [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Zustellung XY [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;VHM-2s spanbrechend 15° 6mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4200&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;24000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.7&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;VHM-2s gerade 6mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3700&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;26500&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;21&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;VHM V-cut 3s 90° 10mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;21000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.27&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;VHM Radienfräser 2s 6mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;21000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.27&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;VHM Viertelstab 4s r2 6mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3600&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;15000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="schlichtgang"&gt;Schlichtgang&lt;/h3&gt;&#10;&lt;p&gt;Das folgende Video zeigt die den Schlichtvorgang nach dem Schruppen eines &lt;code&gt;145x145mm&lt;/code&gt; großen Werkstückes. Durch die geringe seitliche Zustellung bilden sich kaum Fasern aus und der geringe Vorschub sorgt trotz leichtem Drall des Fräsers für eine sehr hohe Oberflächengüte.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2025-10-06-albasia-schlicht.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;CNC-Schlichten von Albasia Verbundmaterial&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="gravuren"&gt;Gravuren&lt;/h2&gt;&#10;&lt;p&gt;Bei beschichteten Albasiaplatten finden Gravuren idealerweise in der Deckschicht statt. Wird nicht in das Trägermaterial eingetaucht, so können keine Probleme mit Ausrissen und losen Fasern entstehen. Da die Deckschicht nicht sehr stark ist, fällt die Wahl schnell auf V-Fräser mit stumpfem Winkel oder mit abgeflachter Spitze.&lt;/p&gt;&#10;&lt;p&gt;Alternativ hierzu können Bereiche mit Schaftfräsern vorgeräumt werden, falls das CAM-Programm eine solche Option bereitstellt. So entstehen komplett glatte &amp;ldquo;Böden&amp;rdquo; innerhalb der Taschen. Oder man verwendet Halbrundfräser wegen ihrer nur sanft ansteigenden Geometrie im unteren Bereich. Unterstützt das CAM-Programm die 3D-Gravur hiermit nicht, so kann die Fräsergeometrie wie im Artikel &lt;a href="https://blog.schallbert.de/halftone-image-on-dibond/#simulation-radienfr%c3%a4ser"&gt;Halbtonbilder auf Dibond&lt;/a&gt; beschrieben angenähert werden.&lt;/p&gt;&#10;&lt;p&gt;Zwei Beispiele habe ich im untenstehenden Bild vorbereitet: Der CR-Code in der oberen Hälfte entstand mit einem 6mm-Radienfräser bei &lt;code&gt;0.2mm&lt;/code&gt; Eintauchtiefe. Die entstehenden Punkte und Linien fühlen sich durch die sanften Übergänge weich und angenehm an. Der Schriftzug in der unteren Bildhälfte ist mit einem Gravierfräser und bis zu &lt;code&gt;7mm&lt;/code&gt; Tiefenzustellung gefräst. Hierdurch kommt der natürliche Holzton zwar schön zur Geltung, bei kleinen Schrithöhen wie dieser (max. &lt;code&gt;30mm&lt;/code&gt;) entstehen aber leicht Ausbrüche bei Buchstaben mit &amp;ldquo;Inseln&amp;rdquo; (a, e, b, o, etc.).&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-06-albasia-engrave-compare.avif" alt="Image: Albasia sandwich material engraved. Upper part using a radius endmill not penetrating the HPL cover, yields dark and very clean finish but lacks contrast. Lower part with a classic 60° V-cut with a brighter wooden finish but with artifacts" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="mit-farbe-arbeiten"&gt;Mit Farbe arbeiten&lt;/h2&gt;&#10;&lt;p&gt;Die Kontraste der Gravuren sind zwar in Ordnung, die Lesbarkeit ist aber längst nicht perfekt. Da liegt es nahe, Gravuren mit Farbe auszulegen. Ich habe mit ein paar Farbtönen experimentiert; ein paar Impressionen finden sich im Bild unten.&lt;/p&gt;&#10;&lt;h3 id="welcher-lack"&gt;Welcher Lack?&lt;/h3&gt;&#10;&lt;p&gt;Die Platte wird mit einer Schutzfolie ausgeliefert, um die farbige Beschichtung zu schützen. Das macht das Auslegen mit Farbspray total einfach, wenn ich die Schutzfolie für die Gravurbearbeitung auf dem Werkstück belasse. Ich habe mit handelsüblichem Acryllack auf Wasserbasis (oben rechts), Farbspray mit Metallpartikeln (unten) und mit lösemittelbasiertem Permanent Marker (oben links) gearbeitet.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-10-25-engrave-compare.avif" alt="Image: Impressions of coloured engravings of the name *Schallbert* and the QR-code to this website." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dabei ging die Lackierung stets leicht von der Hand. Die Abriebfestigkeit ist bei den in HPL gravierten Flächen hoch. Graviert man bis hindurch zum Holz, so stellen sich die Fasern durch das Lackieren etwas auf, was später nur sehr zeitaufwendig korrigiert werden kann und dem Gesamteindruck nicht zuträglich ist.&lt;/p&gt;&#10;&lt;h3 id="zu-beachten-schutzfolie"&gt;Zu beachten: Schutzfolie&lt;/h3&gt;&#10;&lt;p&gt;Der Lackiervorgang sollte möglichst direkt nach dem Gravieren gestartet werden und nicht erst Tage oder gar Wochen später. Ansonsten nämlich neigt die Schutzfolie zum Ablösen bzw. zur Blasenbildung an den Rändern der Gravur und die Farbe wird durch Kapillarkräfte in Bereiche gezogen, wo sie nicht hingehört. Dies hat mir die gold-blaue Version meiner Testreihe leider deutlich sichtbar ruiniert.&lt;/p&gt;&#10;&lt;h2 id="fazit-albasia-für-was"&gt;Fazit: Albasia für was?&lt;/h2&gt;&#10;&lt;p&gt;Alles in Allem würde ich Albasia zu den eher gutmütigen Materialien zur CNC-Bearbeitung zählen, die einen weiten Parameterbereich von Vorschüben bei hoher Schnittqualität akzeptiert. Herausfordernd sind die Neigung zum Bilden langer Fasern und die sehr unterschiedliche Härte zwischen Deckschicht und Kernmaterial; Geht man mit zu hohen Vorschüben vor, so nutzt der Fräser in den Bereichen mir HPL-Deckschicht sehr schnell ab.&lt;/p&gt;&#10;&lt;p&gt;Als Gravurmaterial eignet es sich ebenfalls, wofür aber die Beschichtung allein bereits genügen würde und durch die kleinen Gravurtiefen enge Grenzen für die Strichbreite gesetzt sind. Die Gravur kann also allenfalls nettes Beiwerk zum eigentlichen Einsatz der Platten sein.&lt;/p&gt;&#10;&lt;p&gt;Die Beschichtung macht das Material absolut möbelbaufähig. Arbeitet man mit verdeckten Kanten, so kann ich mir sogar den Einsatz in der Küche vorstellen. Da es eine hohe Biegestabilität besitzt und die Beschichtung mit hoher Härte besticht, ist es sogar als Oberfläche für den täglichen Gebrauch verwendbar. Das Plattenmaterial ist federleicht und eignet sich daher auch prima für mobile Anwendungen im Sichtbereich.&lt;/p&gt;&#10;&lt;p&gt;An die Robustheit von Birke Multiplex reicht es jedoch nicht heran. Belastete Verbindungen traue ich dem Material ohne Weiteres nicht zu; bei Verschraubungen würde ich Oberflächen maximieren und stets Möbelbauverbindungen einsetzen (also keine Holzschrauben nehmen).&lt;/p&gt;&#10;</description></item><item><title>passtdas auf Github</title><link>https://blog.schallbert.de/announcements/2025-09-14-passtdas/</link><pubDate>Sun, 14 Sep 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/announcements/2025-09-14-passtdas/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-passtdas-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Visualizazion of how passtdas analyzes and determines workpiece edges"&#10; title="passtdas auf Github" /&gt;&#10;&lt;h2 id="-september-2025--passtdas-zum-download-verfügbar"&gt;&amp;mdash; September 2025 &amp;mdash; passtdas zum Download verfügbar&lt;/h2&gt;&#10;&lt;p&gt;Das Werkzeug &lt;a href="https://blog.schallbert.de/passtdas/"&gt;passtdas&lt;/a&gt; liest eine G-Code Datei für die Bearbeitung mit einer CNC-Fräsmaschine und ermittelt die Werkstückgrenzen (Extremwerte) in X und Y.&#10;Anschließend schreibt es ein weiteres CNC-Programm mit diesen Punkten und ermöglicht so, dass die Werkstückgrenzen direkt an der Maschine angefahren werden können.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-ext.avif" alt="Image: Visual example of an extreme value dataset for a simple geometry" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;So kann stets sichergestellt werden, dass der Rohling korrekt ausgerichtet ist und das Werkstück drauf passt.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/Schallbert/passtdas" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Link zum Projekt auf Github&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Ich habe vor, das Projekt demnächst in eine Browser-Lösung zu portieren und will es auf diese Weise einfacher zugänglich machen.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Schallbert&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>passtdas - ein Hilfsprogramm für die CNC</title><link>https://blog.schallbert.de/passtdas/</link><pubDate>Mon, 08 Sep 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/passtdas/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-passtdas-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Visualizazion of how passtdas analyzes and determines workpiece edges"&#10; title="passtdas - ein Hilfsprogramm für die CNC" /&gt;&#10;&lt;h2 id="das-problem"&gt;Das Problem&lt;/h2&gt;&#10;&lt;p&gt;Ein Bekannter von mir schrieb mich an. Er betreibt eine CNC-Portalfräsmaschine als Hobby so wie ich und fertigt oft Einzelstücke. Die dabei entstehenden Reste sind aber teils zu schade zum Wegwerfen. Für eine bestmögliche Materialausnutzung probiert er, diese Reste als Rohlinge für weitere Projekte zu verwenden.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Weißt du, wie ich an der Maschine herausfinden kann, ob mein Frästeil auf den Rohling passt?&amp;rdquo; - Anonym&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Schnell ist es passiert: Den Maßen zufolge passt das Werkstück so gerade auf den Rohling. Setzt man den Nullpunkt nur leicht falsch, so fährt der Fräser über die Werkstückgrenzen hinaus und man kann das Werkstück wegwerfen.&lt;/p&gt;&#10;&lt;h2 id="die-lösung"&gt;Die Lösung&lt;/h2&gt;&#10;&lt;p&gt;Es wäre doch toll, wenn die Maschine vor Beginn des Fräsvorganges die Kontur des Werkstückes abfahren würde, oder? So könnte man unkompliziert herausfinden, ob sich der Rohling wirklich komplett innerhalb der Werkstückgrenze befindet.&lt;/p&gt;&#10;&lt;p&gt;Da ich auf Anhieb keine schnelle Lösung online finden kann, denke ich an selbstgeschriebene Software für die Umsetzung. Daher mache ich mich an die Arbeit.&lt;/p&gt;&#10;&lt;p&gt;TODO: ADD VIDEO LINK&lt;/p&gt;&#10;&lt;h2 id="die-software"&gt;Die Software&lt;/h2&gt;&#10;&lt;p&gt;Für einen schnellen Prototypen wähle ich die Hochsprache &lt;a href="https://www.python.org/downloads/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;python&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Warum? Ich beherrsche &lt;em&gt;python&lt;/em&gt; recht gut, Bibliotheken für die erforderlichen geometrischen Berechnungen sind verfügbar, und das geschriebene Programm lässt sich leicht in eine all-in-one-Datei kompilieren.&lt;/p&gt;&#10;&lt;p&gt;Das Programm soll Maschineninstruktionen (G-Code) lesen und die Fräspfade analysieren. Die Stellen, an denen im Fräsprogramm die maximalen und minimalen &lt;code&gt;X- und Y-Werte&lt;/code&gt; angefahren werden, sollen abgespeichert und selbst als Bewegungskommando in einer Pfaddatei ausgegeben werden.&lt;/p&gt;&#10;&lt;p&gt;Lädt man dieses Programm auf die Maschine, so wird ein so ermittelter &amp;ldquo;Extremwert&amp;rdquo; angefahren und die Z-Achse von Sicherheits- auf Messhöhe abgesenkt. Anschließend wird das Programm pausiert, sodass der Bediener Anpassungen der Werkstückposition vornehmen kann. Erst durch Einabe eines weiteren Startbefehls wird der nächste Extremwert angefahren usw.&lt;/p&gt;&#10;&lt;p&gt;Dieses Programm wird &lt;em&gt;passtdas&lt;/em&gt; heißen.&lt;/p&gt;&#10;&lt;h3 id="programmart"&gt;Programmart&lt;/h3&gt;&#10;&lt;p&gt;Ich entscheide mich für ein Konsolenprogramm mit wenigen Parametern:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Shortcut&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Description&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;-f&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;--file&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;The source file to be analyzed. Required.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;-s&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;--zsafety&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Safety height the machine will do flyovers with. Optional.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;-p&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;--zprobe&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Probe height the machine will pinpoint to. Optional.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Die beiden Höhenparameter bekommen Default-Werte, sodass sie nicht immer mit angegeben werden müssen.&lt;/p&gt;&#10;&lt;h3 id="eingabe"&gt;Eingabe&lt;/h3&gt;&#10;&lt;p&gt;&lt;em&gt;passtdas&lt;/em&gt; liest Dateien ein und durchsucht sie zeilenweise nach Bewegungsbefehlen &lt;code&gt;G00, G01, G02, G03&lt;/code&gt;. Während die Auswertung für lineare Bewegungen wie &lt;code&gt;G00, G01&lt;/code&gt; einfach ist - Lineare Bewegungen lassen sich durch Punkt-zu-Punkt-Verbindungen ausdrücken, deren Extremwerte nicht zwischen den Punkten liegen können - kann das für Kreissegmente durchaus herausfordernd sein.&lt;/p&gt;&#10;&lt;p&gt;Die Höheninformation auf der Z-Achse wird ebenfalls mitgelesen und gespeichert. So kann die Zerspanungstechnikerin schnell prüfen, ob die maximale Frästiefe mit der Gewünschten übereinstimmt.&lt;/p&gt;&#10;&lt;p&gt;Befinden sich in der G-Code-Datei keine oder falsch formulierte Anweisungen, so gibt das &lt;em&gt;passtdas&lt;/em&gt; entsprechende Fehlermeldungen aus und bricht die Analyse ab.&lt;/p&gt;&#10;&lt;h3 id="definition-kreissegmente"&gt;Definition Kreissegmente&lt;/h3&gt;&#10;&lt;p&gt;Zum Hintergrund: Im G-Code können Kreissegmente auf zwei Arten definiert werden.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X5.660 Y-0.000 Z10.000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G02 X-5.660 Y0.000 R5.660 F1600&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kommando:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Fahre einen Kreisbogen im Uhrzeigersinn ab dem Punkt &lt;code&gt;X5.66 Y0&lt;/code&gt; mit Radius &lt;code&gt;5.66mm&lt;/code&gt; bis zum Punkt &lt;code&gt;X-5.66 Y0&lt;/code&gt; mit Vorschub &lt;code&gt;1600mm/min&lt;/code&gt; auf Höhe &lt;code&gt;10mm&lt;/code&gt;.&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Zeichnet man diese Kommandos, entsteht ein hängender Halbkreis. Kreise können mittels dreier Punkte definiert werden. Doch nur zwei Punkte sind gegeben. Der dritte Punkt muss mit Hilfe von Anfangs- und Endpunkt aus dem ebenfalls angegebenen Kreisradius berechnet werden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-arcr.avif" alt="Image: drawing an arc using given radius, start- and endpoint" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;em&gt;Aufgabe: Finde heraus, wo dieser Kreis seine Extremwerte in XY hat.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;Aber auch so kann ein Kreissegment definiert sein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X-2.5696 Y5.0431&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G03 X-4.6258 Y3.2616 Z-0.5000 I2.5696 J-5.0431&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kommando:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Fahre einen Kreisbogen gegen den Uhrzeigersinn ab dem Punkt &lt;code&gt;X-2.56 Y5.04&lt;/code&gt; mit Mittelpunktkoordinaten &lt;code&gt;I2.56 J-5.04&lt;/code&gt; bis zum Punkt &lt;code&gt;X-4.62 Y3.26&lt;/code&gt;.&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Zeichnet man diese Kommandos, entsteht ein kurzes Kreissegment im oberen linken Quadranten. Hier sind zwar drei Punkte (Start- und Endpunkt sowie Kreismittelpunkt) gegeben. Doch auch hier kann es mitunter schwierig werden, herauszufinden, ob irgendwo auf dem Kreisbogen ein Extremwert angefahren wird.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-arcij.avif" alt="Image: drawing an arc using center point, start- and endpoint" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;em&gt;Aufgabe: Finde heraus, wo dieser Kreis seine Extremwerte in XY hat.&lt;/em&gt;&lt;/p&gt;&#10;&lt;h3 id="extremwerte-von-kreissegmenten-berechnen"&gt;Extremwerte von Kreissegmenten berechnen&lt;/h3&gt;&#10;&lt;p&gt;Ein Kreissegment kann je nach Spanne bis zu vier zusätzliche Extremwerte haben: &lt;code&gt;X+, Y+, X-, Y-&lt;/code&gt;. Bei einem Vollkreis sind dies stets die Koordinatenschnittpunkte vom Radius aus gesehen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_extremes_from_arc&lt;/span&gt;(arc, coordinates):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; x_plus_radius &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [center_x &lt;span style="color:#f92672"&gt;+&lt;/span&gt; radius, center_y, xyz[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; y_plus_radius &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [center_x, center_y &lt;span style="color:#f92672"&gt;+&lt;/span&gt; radius, xyz[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; x_minus_radius &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [center_x &lt;span style="color:#f92672"&gt;-&lt;/span&gt; radius, center_y, xyz[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; y_minus_radius &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [center_x, center_y &lt;span style="color:#f92672"&gt;-&lt;/span&gt; radius, xyz[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extremevalue_order &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [x_plus_radius, y_plus_radius, x_minus_radius, y_minus_radius]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (arc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;-&lt;/span&gt; arc[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# crossing the 0° line (x-axis), handle overflow with nested if below&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; arc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;360&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; []&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; crossing_angle &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;721&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; crossing_angle &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(int(arc[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]), int(arc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; i &lt;span style="color:#f92672"&gt;=&lt;/span&gt; int(crossing_angle &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; i &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; i &lt;span style="color:#f92672"&gt;-=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(extremevalue_order[i])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sind Kreismittelpunkt sowie Kreisradius bekannt, kann aus den Anfangs- und Endpunkten der überstrichene Winkel bestimmt werden. Hierdurch wird nun klar, ob Kreisanfangspunkt und/oder -Endpunkt selbst Extremwerte darstellen, oder ob durch Überstreichen eines Koordinatennullpunktes zusätzliche Extremwerte entstehen. Genau dies tut das obige Codebeispiel.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-arcext.avif" alt="Image: Possible coordinate extreme values of an arc segment" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="datentabelle"&gt;Datentabelle&lt;/h3&gt;&#10;&lt;p&gt;Alle möglichen Extremwerte (zwei bei einer Linie, bis zu vier bei einem Kreissegment) werden nun in einer Datentabelle gesammelt. Sobald jede G-Codezeile ausgewertet und die Liste damit komplett ist, werden die Maximal- und Minimalwerte für jede Achse aus dieser Liste bestimmt.&lt;/p&gt;&#10;&lt;p&gt;So entstehen schließlich vier &lt;code&gt;XYZ&lt;/code&gt;-Koordinaten, welche für jede Raumrichtung die maximale Ausdehnung des Werkstückes beschreibt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-ext.avif" alt="Image: Visual example of an extreme value dataset for a simple geometry" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Parallel durchsucht das Programm alle Befehle an die Z-Achse und merkt sich die maximale Eintauchtiefe.&lt;/p&gt;&#10;&lt;h3 id="ausgabe"&gt;Ausgabe&lt;/h3&gt;&#10;&lt;p&gt;Das Kommandozeilenprogramm verfügt über keine visuelle Ausgabe. Daher beschränke ich mich auf die Erstellung eines Simulationsprogramms für die Fräsmaschine, in dem die Werkstückausdehnung angefahren wird und bei jedem Punkt pausiert:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G90&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Go to coordinate zero&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;Zmin of this job: -1.0&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X0 Y0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;PathPreview: Hit START to go to Ymin: [&amp;#39;0.0&amp;#39;, &amp;#39;-5.66&amp;#39;]&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X0.0 Y-5.66&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;PathPreview: Hit START to go to Xmin: [&amp;#39;-5.66&amp;#39;, &amp;#39;0.0&amp;#39;]&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X-5.66 Y0.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;PathPreview: Hit START to go to Ymax: [&amp;#39;0.0&amp;#39;, &amp;#39;5.66&amp;#39;]&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X0.0 Y5.66&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;PathPreview: Hit START to go to Xmax: [&amp;#39;5.66&amp;#39;, &amp;#39;0.0&amp;#39;]&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X5.66 Y0.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Obiges Beispiel fährt die Extremwerte eines Kreises mit &lt;code&gt;r=5.66mm&lt;/code&gt; ab. Dabei fährt es stets eine Sicherheitshöhe von &lt;code&gt;Z40&lt;/code&gt; zum Überstreichen der Punkte an und bewegt die Z-Achse bei Ankunft langsam mit &lt;code&gt;F1200&lt;/code&gt; auf die Zielhöhe von &lt;code&gt;Z15&lt;/code&gt;. Wichtig dabei ist, dass der Nullpunkt in &lt;code&gt;Z&lt;/code&gt; vorher korrekt vermessen wurde - sonst kommt es möglicherweise zur Kollision mit dem Werkstück.&lt;/p&gt;&#10;&lt;h2 id="test-und-verifikation"&gt;Test und Verifikation&lt;/h2&gt;&#10;&lt;p&gt;Um zu sehen ob das Programm auch wie erwartet funktioniert habe ich drei Testdateien erstellt:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Unit tests: Prüft einzelne Funktionen im Programm, z.B. ob Linien- und Kreissegmente korrekt erkannt werden&lt;/li&gt;&#10;&lt;li&gt;integration tests: Testen, ob Funktionen im Verbund korrekt funktionieren, z.B. die richtige Berechnung von Extremwerten anhand von Eingabewerten&lt;/li&gt;&#10;&lt;li&gt;End to end tests: Diese Tests prüfen über die gesamte Funktionskette, z.B. ob falsch formatierte Eingabedateien erkannt werden&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-unittest.avif" alt="Image: Test result summary for passtdas" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Zum Schluss erstelle ich mehrere Testdateien mit dem Programm und fahre die daraus erzeugten Extremwerte mit meiner CNC an.&lt;/p&gt;&#10;&lt;h2 id="open-source-software"&gt;Open-source software&lt;/h2&gt;&#10;&lt;p&gt;Der Quellcode für &lt;em&gt;passtdas&lt;/em&gt; steht kostenlos auf &lt;a href="https://github.com/Schallbert/passtdas" target="_blank" rel="noopener noreferrer" class="external-link"&gt;GitHub&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Verfügung, muss allerdings durch Python interpretiert werden.&lt;/p&gt;&#10;&lt;p&gt;Ich baue ein Endanwenderfreundliches und leicht zu bedienendes Programm daraus, sobald genügend Leute den Wunsch dafür geäußert haben.&lt;/p&gt;&#10;</description></item><item><title>Giteas *act_runner* rootless ausführen</title><link>https://blog.schallbert.de/gitea-act-runner-dind/</link><pubDate>Sun, 10 Aug 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/gitea-act-runner-dind/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-08-10-gitea-act-runner-dind-rootless-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Symbolized Docker containers next to text &amp;#39;&amp;#39;rootless act_runner DinD&amp;#39;&amp;#39;"&#10; title="Giteas *act_runner* rootless ausführen" /&gt;&#10;&lt;p&gt;In diesem Artikel zeige ich, wie ich den &lt;em&gt;act_runner&lt;/em&gt; meiner &lt;em&gt;Gitea&lt;/em&gt;-Instanz von &lt;code&gt;gitea/act_runner&lt;/code&gt; auf &lt;code&gt;gitea/act_runner:latest-dind-rootless&lt;/code&gt; umstelle.&lt;/p&gt;&#10;&lt;p&gt;Warum dieser Aufwand? Weil &lt;code&gt;act_runner&lt;/code&gt; in &lt;em&gt;Docker&lt;/em&gt; läuft und Zugriff auf den Daemon per &lt;code&gt;/var/run/docker.sock&lt;/code&gt; als sogenanntes Volume (gemeint im Sinne von Datenträger) bzw. Bind-Mount benötigt, um zu funktionieren. Der Besitzer dieses Sockets ist &lt;code&gt;root&lt;/code&gt;, wodurch der Container praktisch Vollzugriff auf das Hostsystem erhält.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;RULE #1 - Do not expose the Docker daemon socket (even to the containers)&amp;rdquo; - OWASP / Docker Security Cheat Sheet&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Dies ist hoch riskant, denn es liegt im Prinzip des Runners, von Dritten erzeugten Job-Code auszuführen. Ein offenes Einfallstor. In Verbindung mit direktem Durchgriff auf das Hostsystem kann das im Falle eines erfolgreichen Angriffes einen Totalausfall bzw. die &amp;ldquo;feindliche Übernahme&amp;rdquo; meiner Infrastruktur bedeuten.&lt;/p&gt;&#10;&lt;h2 id="gefahr-durch-dockersock"&gt;Gefahr durch &lt;code&gt;docker.sock&lt;/code&gt;&lt;/h2&gt;&#10;&lt;p&gt;Ich richte mich in dieser Anleitung nach der Empfehlung von &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#docker-security-cheat-sheet" target="_blank" rel="noopener noreferrer" class="external-link"&gt;OWASP zum Thema Docker-Sicherheit&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und wende deren Regel(n) in der Praxis an. OWASP steht für &amp;ldquo;Open Web Application Security Project&amp;rdquo;. Es ist eine Organisation, die sich für höhere Sicherheit von Webanwendungen einsetzt und dabei Anwender wie mich mit kostenfreien Artikeln, Dokumentation und Technologie unterstützt.&lt;/p&gt;&#10;&lt;p&gt;Die unter &lt;strong&gt;Regel 1&lt;/strong&gt; beschriebenen Maßnahmen lauten:&lt;/p&gt;&#10;&lt;h3 id="dockers-tcp-socket-abgeschaltet-lassen"&gt;Dockers &lt;em&gt;tcp&lt;/em&gt; Socket abgeschaltet lassen&lt;/h3&gt;&#10;&lt;p&gt;Sollte der Zugang zum &lt;em&gt;Docker Daemon&lt;/em&gt; per &lt;em&gt;tcp&lt;/em&gt; eingeschaltet sein, kann sich mit diesem - sofern keine weiteren Vorkehrungen getroffen wurden - über eine ungesicherte Verbindung und ohne Authentisierung verbunden werden. Der Daemon ist dann durch praktisch jeden Internetnutzer zugänglich und damit angreifbar.&lt;/p&gt;&#10;&lt;p&gt;Wie stelle ich also sicher, dass der &lt;em&gt;tcp&lt;/em&gt; Socket abgeschaltet ist?&#10;Die Anleitung hierzu findet sich in den &lt;a href="https://docs.docker.com/engine/daemon/remote-access/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Docker Docs&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und muss quasi verkehrt herum angewandt werden. Auch dort wird ausdrücklich vor ungeschützter Verfügbarmachung des &lt;em&gt;tcp&lt;/em&gt;-Sockets gewarnt.&lt;/p&gt;&#10;&lt;p&gt;Nach Ausführen aller erforderlichen Schritte lasse ich &lt;a href="https://en.wikipedia.org/wiki/Netstat" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;netstat&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; einmal durchlaufen. Es listet mir offene Sockets, Netzwerkschnittstellen und Routing-Tabellen auf:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# check if Docker Daemon&amp;#39;s &amp;#34;dockerd&amp;#34; tcp socket is exposed&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# netstat -lntp | grep dockerd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# test is a pass if this command does not return anything.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bei mir findet sich kein Eintrag mit &lt;code&gt;dockerd&lt;/code&gt;. Mein Server ist an dieser Stelle schon einmal nicht verwundbar.&lt;/p&gt;&#10;&lt;h3 id="den-docker-socket-varrundockersock-nicht-in-andere-container-einbinden"&gt;Den Docker Socket &lt;code&gt;/var/run/docker.sock&lt;/code&gt; nicht in andere Container einbinden&lt;/h3&gt;&#10;&lt;p&gt;Hier wird es schon etwas schwieriger: &lt;em&gt;act_runner&lt;/em&gt; benötigt den Socket, um Job-Container zu erzeugen, zu verwalten und schließlich wieder zu entsorgen. Ohne Zugriff auf den &lt;em&gt;Docker Daemon&lt;/em&gt; über den Socket funktioniert die Build-Pipeline schlicht und einfach nicht - es sei denn, man möchte komplett auf &lt;em&gt;Docker&lt;/em&gt; verzichten und sowohl den Runner als auch die Build-Jobs direkt auf der Host-Maschine laufen lassen. Damit gingen aber viele Nachteile einher: Verlust der Kapselung, keine Portabilität, schlechte Skalierbarkeit, geringere Sicherheit&amp;hellip;&lt;/p&gt;&#10;&lt;p&gt;Aber auch für die Option mit &lt;em&gt;Docker&lt;/em&gt; gibt es eine Lösung: &lt;a href="https://gitea.com/gitea/act_runner/src/branch/main/examples/docker-compose#running-act_runner-using-docker-in-docker-dind" target="_blank" rel="noopener noreferrer" class="external-link"&gt;act_runner Docker-in-Docker aufsetzen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Mit diesem Setup erhält &lt;em&gt;act_runner&lt;/em&gt; einen eigenen &lt;em&gt;Docker Daemon&lt;/em&gt;, jedoch mit eingeschränkten Rechten und ohne Zugriff auf den des Host-Systems. Dieser übernimmt dann den Lebenszyklus von Job-Containern, sodass sie komplett unabhängig vom Host-System laufen.&lt;/p&gt;&#10;&lt;h2 id="docker-in-docker"&gt;Docker in Docker&lt;/h2&gt;&#10;&lt;p&gt;Folgendes Schaubild verdeutlicht den Unterschied:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-08-10-runner-dind-setup.avif" alt="Image: act_runner with standard configuration versus DinD-rootless. The latter has an isolated Docker Daemon running within the act_runner container." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="act_runner-rootless-starten"&gt;Act_runner &amp;ldquo;rootless&amp;rdquo; starten&lt;/h3&gt;&#10;&lt;p&gt;Also folge ich der Anleitung und kopiere mir eine passende &lt;code&gt;docker-compose.yml&lt;/code&gt; zusammen. Wichtig hier:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Es muss &lt;code&gt;privileged: true&lt;/code&gt; gesetzt werden. Ansonsten kann der Docker Daemon im &lt;em&gt;act_runner&lt;/em&gt;-Container nicht richtig starten, da ihm Kernel-Funktionen fehlen. Somit schmiert der ganze Container wieder und wieder ab, ohne dabei hilfreiche Fehlermeldungen zu erzeugen.&lt;/li&gt;&#10;&lt;li&gt;Die Umgebungsvariable &lt;code&gt;DOCKER_HOST=unix:///var/run/user/1000/docker.sock&lt;/code&gt; muss gesetzt sein. Hier wird der Docker Socket über einen nicht-privilegierten User gesteuert und steht dem Runner für die Verwaltung von Job Containern zur Verfügung.&#10;Der Daemon läuft gekapselt im Container und steht nicht in Verbindung mit der Host-Maschine.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-08-10-gitea-act-runner-dind-rootless.avif" alt="Image: DinD-rootless runner is now working fine" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="volume-verwirrung"&gt;Volume-Verwirrung&lt;/h3&gt;&#10;&lt;p&gt;Prima! Da der Runner nun läuft, lasse ich ihn direkt mal auf einen Job los. Leider schlägt das Bauen bereits nach Sekundenbruchteilen mit dieser Meldung fehl:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;# Runner step: Set up job&#10;failed to start container: Error response from daemon: error while creating mount source path &amp;#39;&amp;lt;volumeSourceFullPath&amp;gt;&amp;#39;: mkdir &amp;lt;volumeSourcePath&amp;gt;: permission denied&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hier habe ich stundenlang recherchieren müssen und war lange der falschen Überzeugung, dass es an fehlenden Berechtigungen bei Ordnern auf der Host-Maschine liegt. Später erst verstand ich wirklich, dass Docker-in-Docker genau so gemeint ist, wie es heißt: Nicht nur werden Container durch Container erzeugt, sondern innerhalb des Containers läuft ein separater Docker Daemon!&lt;/p&gt;&#10;&lt;p&gt;Damit funktioniert nämlich der klassische Weg nicht mehr, Volumes in Job Containern direkt vom Host-System per &lt;code&gt;-v /a/b:/x/y&lt;/code&gt; verfügbar zu machen.&#10;Stattdessen müssen Volumes nun durchgereicht werden. Beispiel:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;em&gt;host&lt;/em&gt; directory &lt;code&gt;/opt/server/www/blog-artifacts&lt;/code&gt; -&amp;gt; &lt;em&gt;act_runner&lt;/em&gt; volume &lt;code&gt;/tmp/blog-artifacts&lt;/code&gt; &amp;ndash;&amp;gt; &lt;em&gt;job&lt;/em&gt; container volume &lt;code&gt;/tmp/blog-artifacts&lt;/code&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Das &lt;code&gt;:z&lt;/code&gt; im Volume vom &lt;em&gt;act_runner&lt;/em&gt; wird jetzt wichtig. Es zeigt &lt;em&gt;Docker&lt;/em&gt; gegenüber an, dass dieses Volume zwischen Containern geteilt wird. Diese Volumes müssen nicht nur in der &lt;code&gt;docker-compose.yml&lt;/code&gt; von &lt;em&gt;act_runner&lt;/em&gt; angegeben werden, sondern auch die Job-Skripte im Ordner &lt;code&gt;.gitea/workflows/&lt;/code&gt; sind jetzt entsprechend anzupassen. Hier wird das &lt;code&gt;:z&lt;/code&gt; auf der &amp;ldquo;rechten Seite&amp;rdquo; hingegen nicht benötigt.&lt;/p&gt;&#10;&lt;h3 id="is-not-a-valid-volume"&gt;&amp;ldquo;is not a valid volume&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Allem Aufwand zum Trotz laufen meine Jobs noch immer nicht durch. Dieses Mal wegen einer Fehlermeldung, die &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/#action-volumes"&gt;mir bereits bekannt vorkam&lt;/a&gt;:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log*" data-lang="log*"&gt;# Runner step: Set up job&#10;[/tmp/blog-artifacts] is not a valid volume, will be ignored&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also rein in die &lt;code&gt;config.yml&lt;/code&gt; von &lt;em&gt;act_runner&lt;/em&gt; und die Volume-Namen hinzufügen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /gitea/runner/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;valid_volumes&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/blog-artifacts&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/lectures-artifacts&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In dieser Datei kann ich &lt;code&gt;privileged: false&lt;/code&gt; stehen lassen, denn der Job Container selbst benötigt anders als &lt;em&gt;act_runner&lt;/em&gt; keine Kernel-Features.&lt;/p&gt;&#10;&lt;h2 id="berechtigungen-korrekt-setzen"&gt;Berechtigungen korrekt setzen&lt;/h2&gt;&#10;&lt;p&gt;Nun erhalte ich erneut &lt;code&gt;Permission Denied&lt;/code&gt; Fehlermeldungen beim Ausführen meiner Jobs, wenn auch nicht direkt im ersten Schritt der Aktionen. Da ich die Volume-Pfade jetzt bis ins Kleinste geprüft habe, kann es eigentlich nur noch an den Ordnerberechtigungen auf der Host-Maschine liegen.&lt;/p&gt;&#10;&lt;p&gt;Damit die vom Job-Container erzeugten Artefakte durch die Volumes auf meinem Host abgelegt werden können, muss ich das zu schreibende Verzeichnis und alle Unterordner &lt;code&gt;-R&lt;/code&gt; an den vorher definierten, nicht-privilegierten User &lt;code&gt;ID=1000&lt;/code&gt; übergeben:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# chown -R 1000:1000 /target/path/to/artifact/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Endlich funkioniert alles reibungslos und ich habe der (unwahrscheinlichen, aber möglichen) Übernahme meines Host-Systems durch bösartige Job-Container einen Riegel vorgeschoben.&lt;/p&gt;&#10;</description></item><item><title>Rate limiter für Gitea</title><link>https://blog.schallbert.de/gitea-rate-limiter/</link><pubDate>Fri, 25 Jul 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/gitea-rate-limiter/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-07-25-gitea-rate-limiter-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Gitea&amp;#39;&amp;#39;s logo, a cup of tea, protected by many arrows symbolizing GET requests"&#10; title="Rate limiter für Gitea" /&gt;&#10;&lt;p&gt;Hier halte ich eine Schritt-für-Schritt Anleitung fest, die das Problem meiner &lt;a href="https://blog.schallbert.de/gitea-out-of-memory/"&gt;&lt;em&gt;Gitea&lt;/em&gt;-Abstürze&lt;/a&gt; lösen soll. Die Erfahrung aus der Erstanwendung eines Rate Limiters &lt;a href="https://blog.schallbert.de/fail2ban-with-caddy/"&gt;für meinen Blog&lt;/a&gt; lasse ich hier einfließen mit dem Ziel, dass die Anwendung robuster läuft und gegen ungerichtete Denial of Service Attacken geschützt wird.&lt;/p&gt;&#10;&lt;h2 id="schritt-1-log-quelle-für-rate-limiter-festlegen"&gt;Schritt 1: Log-Quelle für rate limiter festlegen&lt;/h2&gt;&#10;&lt;p&gt;&lt;em&gt;Gitea&lt;/em&gt; erzeugt natürlich selbst Logs, theoretisch bis herunter auf Zugriffsebene durch externe Clients. Doch schaffte ich es in der Vergangenheit nicht, &lt;a href="https://blog.schallbert.de/server-protection/#was-noch-nicht-funktioniert-gitea--fail2ban"&gt;&lt;em&gt;Giteas&lt;/em&gt; Access-Logs aus Docker auszuleiten&lt;/a&gt;. Daher geben wir jetzt dem als Reverse Proxy angeflanschten &lt;em&gt;Caddyserver&lt;/em&gt; die Anweisung, Logs stellvertretend zu erstellen. In ihnen tauchen dann Zugriffe auf das Webinterface von &lt;em&gt;Gitea&lt;/em&gt; auf.&lt;/p&gt;&#10;&lt;p&gt;In der &lt;code&gt;Caddyfile&lt;/code&gt; sieht das Log-Modul total unspektakulär aus.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /caddy/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;log {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output file &lt;span style="color:#e6db74"&gt;/log/&lt;/span&gt;gitea&lt;span style="color:#f92672"&gt;/&lt;/span&gt;access&lt;span style="color:#f92672"&gt;.&lt;/span&gt;log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="internen-traffic-vom-log-ausschließen"&gt;Internen Traffic vom Log ausschließen&lt;/h3&gt;&#10;&lt;p&gt;Nun schaue ich die Logs mal an und sehe Einträge, die ich gar nicht haben will. So erzeugt &lt;em&gt;act_runner&lt;/em&gt; zum Beispiel alle zwei Sekunden einen &lt;code&gt;Fetch Task POST&lt;/code&gt; nach &lt;em&gt;Gitea&lt;/em&gt; und alle zehn Sekunden einen &lt;code&gt;GET&lt;/code&gt; Request mit demselben Ziel als &lt;a href="https://blog.schallbert.de/fix-gitea-runner/"&gt;Health check&lt;/a&gt;. Diese brauchen für mich im Log gar nicht erst aufzutauchen. Mein erster Gedanke hier war, einfach Anfragen von internen IP-Adressen nicht zu loggen. Da &lt;em&gt;Caddy&lt;/em&gt; allerdings als Reverse Proxy fungiert, laufen ausnahmslos alle IP-Adressen &amp;ldquo;intern&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Also muss ich &lt;em&gt;act_runner&lt;/em&gt; spezifische Logs anders erkennen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /caddy/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git&lt;span style="color:#f92672"&gt;.&lt;/span&gt;schallbert&lt;span style="color:#f92672"&gt;.&lt;/span&gt;de {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reverse_proxy &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;//&lt;/span&gt;&lt;span style="color:#e6db74"&gt;gitea&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Enable logging for fail2ban, don&amp;#39;t log for runner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log_skip &lt;span style="color:#e6db74"&gt;/api/&lt;/span&gt;actions&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output file &lt;span style="color:#e6db74"&gt;/log/&lt;/span&gt;gitea&lt;span style="color:#f92672"&gt;/&lt;/span&gt;access&lt;span style="color:#f92672"&gt;.&lt;/span&gt;log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die &lt;a href="https://caddyserver.com/docs/caddyfile/directives/log_skip" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;log_skip&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Direktive weist &lt;em&gt;caddy&lt;/em&gt; nun an, keine Logs für Zugriffe auf &lt;code&gt;/api/actions*&lt;/code&gt; mehr anzulegen.&lt;/p&gt;&#10;&lt;h3 id="reverse-proxy-remote-ips-anzeigen"&gt;Reverse-Proxy: Remote IPs anzeigen&lt;/h3&gt;&#10;&lt;p&gt;Dennoch habe ich noch immer ein Problem: Wenn im Log alle externen Anfragen auf einer internen IP-Adresse hereinkommen, wie soll ich &amp;ldquo;böse&amp;rdquo; Anfragen dann wegblocken? Eine Websuche &lt;a href="https://caddy.community/t/how-to-get-a-true-remote-ip-behind-caddy-reverse-proxy/22348/2" target="_blank" rel="noopener noreferrer" class="external-link"&gt;zeigt, dass dies für Reverse Proxy in Docker&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ein üblicher Stolperstein ist. Leider helfen mir viele Lösungen nicht, da sie andere Serversoftware wie &lt;em&gt;nginx&lt;/em&gt; verwenden oder andere Services hinter ihren Proxies laufen haben als &lt;em&gt;Gitea.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;Doch tatsächlich ist es ganz einfach: Nur eine Zeile muss an der richtigen Stelle in die &lt;code&gt;Caddyfile&lt;/code&gt; eingefügt werden und schon kommen die remote IP-Adressen unverfälscht rein.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /caddy/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git&lt;span style="color:#f92672"&gt;.&lt;/span&gt;schallbert&lt;span style="color:#f92672"&gt;.&lt;/span&gt;de {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reverse_proxy &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;//&lt;/span&gt;&lt;span style="color:#e6db74"&gt;gitea&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trusted_proxies &lt;span style="color:#ae81ff"&gt;172&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;16&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Docker-internal netwock traffic runs with these IPs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[...] &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die Direktive &lt;a href="https://caddyserver.com/docs/caddyfile/options#trusted-proxies" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;trusted_proxies&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; teilt &lt;em&gt;Caddy&lt;/em&gt; mit, dass dem von &lt;em&gt;Docker&lt;/em&gt; bereitgestelltem Netzwerk vertraut werden kann. Somit wird die eigentliche Quell-IP angezeigt statt der internen Schnittstelladresse des Containers. Genau das brauche ich, um die Adressen später mit &lt;em&gt;fail2ban&lt;/em&gt; analysieren zu können.&lt;/p&gt;&#10;&lt;h2 id="schritt-2-anwendungsfall-bestimmen"&gt;Schritt 2: Anwendungsfall bestimmen&lt;/h2&gt;&#10;&lt;p&gt;Schauen wir uns an, wie viele HTTP &lt;code&gt;200 ok&lt;/code&gt; Anfragen im Grenzfall zwischen Normalnutzung und &amp;ldquo;abuse&amp;rdquo; kommen. Dazu surfe ich auf &lt;em&gt;Gitea&lt;/em&gt; herum und klicke einen Haufen Dinge an, wie ich das als Mensch sonst nie in der Geschwindigkeit tun würde. Danach mache ich eine Auswertung der Logs.&lt;/p&gt;&#10;&lt;p&gt;Damit haben wir erste Richtwerte für den Rate Limiter.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# rate limiter tests&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;findtime = 10s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;maxretry = 10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;bantime = 6h&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="schritt-3-fail2ban-konfigurieren"&gt;Schritt 3: Fail2ban konfigurieren&lt;/h2&gt;&#10;&lt;p&gt;Anschließend konfigurieren wir Filter und Jail-Datei von &lt;code&gt;fail2ban&lt;/code&gt; so, dass sie die oben definierten Logs analysieren.&lt;/p&gt;&#10;&lt;h3 id="filter"&gt;Filter&lt;/h3&gt;&#10;&lt;p&gt;Ich verwende hier schlicht denselben Filter für meinen Rate-Limiter aus dem &lt;a href="https://blog.schallbert.de/fail2ban-with-caddy/#ordner-filterd"&gt;vorherigen Artikel&lt;/a&gt; wieder. Uns interessieren nur erfolgreiche Anfragen, die wir innerhalb eines Zeitfensters zählen.&lt;/p&gt;&#10;&lt;h3 id="jail"&gt;Jail&lt;/h3&gt;&#10;&lt;p&gt;Ich verwende die Werte aus den rate limiter Tests 1:1 hier in der Jail-Datei weiter. Als Filter verweise ich auf &lt;code&gt;caddy-ratelimit&lt;/code&gt; (Link oben).&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /fail2ban/config/fail2ban/jail.local&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;gitea-ratelimit]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;enabled = true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;chain = DOCKER-USER&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;port = http,https&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;filter = caddy-ratelimit&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;logpath = /var/log/caddy2/gitea/access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;findtime = 10s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;maxretry = 10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;bantime = 6h&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mit einem Neustart von &lt;em&gt;fail2ban&lt;/em&gt; schalte ich den Schutz scharf.&lt;/p&gt;&#10;&lt;h2 id="schritt-4-rate-limiter-testen"&gt;Schritt 4: Rate limiter testen&lt;/h2&gt;&#10;&lt;p&gt;Hier gehe ich genauso vor wie in meinem Artikel &lt;a href="https://blog.schallbert.de/fail2ban-with-caddy/#test-rate-limiter"&gt;fail2ban with caddy&lt;/a&gt; und komme zum selben Ergebnis. Es funktionert!&lt;/p&gt;&#10;&lt;h2 id="-update-oktober-2025"&gt;⚠️ Update Oktober 2025&lt;/h2&gt;&#10;&lt;p&gt;Wird &lt;em&gt;fail2ban&lt;/em&gt; auf Services in Docker-Containern angewendet, kann mit der oben genannten Konfiguration des &lt;em&gt;jails&lt;/em&gt; Gein Fehler auftreten. Die Behebung habe ich im folgenden Artikel dokumentiert: &lt;a href="https://blog.schallbert.de/fail2ban-error-configuration-bad-value/"&gt;&lt;em&gt;fail2ban&lt;/em&gt;-Fehler &amp;ldquo;Bad value substitution&amp;rdquo; for &amp;lsquo;action&amp;rsquo;&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Rate limiter mit Caddy und fail2ban</title><link>https://blog.schallbert.de/fail2ban-with-caddy/</link><pubDate>Thu, 10 Jul 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/fail2ban-with-caddy/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-07-10-fail2ban-ratelimit-thumb.avif"&#10; class="post-cover"&#10; alt="Image: block drawing of how caddy interacts with fail2ban to rate limit accesses to my blog"&#10; title="Rate limiter mit Caddy und fail2ban" /&gt;&#10;&lt;p&gt;Hier beschreibe ich, wie man das meist zur &lt;a href="https://blog.schallbert.de/server-protection/"&gt;Abwehr unerlaubter Zugriffsversuche&lt;/a&gt; eingesetze Werkzeug &lt;em&gt;fail2ban&lt;/em&gt; auch zur Begrenzung der Anzahl von erfolgreichen Zugriffen innerhalb eines definierten Zeitfensters verwenden kann. Dies nennt man &amp;ldquo;rate limiting&amp;rdquo; und soll denial of service Attacken vereiteln, bei denen &lt;a href="https://blog.schallbert.de/gitea-out-of-memory/"&gt;mein Server überlastet&lt;/a&gt; wird.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-07-10-fail2ban-ratelimit.avif" alt="Image: block drawing of how caddy interacts with fail2ban to rate limit accesses to my blog" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="caddy-logs-einschalten"&gt;Caddy-Logs einschalten&lt;/h2&gt;&#10;&lt;p&gt;Zuerst müssen wir den Logger des Webservers einschalten. Bei Caddy geht das mit wenigen Zeilen Code, die ich meiner &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/#caddy"&gt;bestehenden Konfiguration&lt;/a&gt; hinzufüge:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# caddy2/config/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;blog.schallbert.de {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Define webserver&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;root * /www/blog&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;encode gzip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;file_server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Enable logging for fail2ban&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;log {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;output file /log/blog/access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ab jetzt werden im angegebenen Pfad Logs angelegt, die ich mit einer &lt;a href="https://blog.schallbert.de/logrotate-mistake/"&gt;&lt;em&gt;logrotate&lt;/em&gt;-Konfiguration&lt;/a&gt; verwalte.&lt;/p&gt;&#10;&lt;h2 id="fail2ban-als-rate-limiter-konfigurieren"&gt;Fail2ban als Rate Limiter konfigurieren&lt;/h2&gt;&#10;&lt;p&gt;Nochmal kurz zur Wiederholung: &lt;em&gt;fail2ban&lt;/em&gt; greift auf die Paketfilter-Regeln der Maschine zu und modifiziert sozusagen ihre Firewall, um dynamische Angriffe abzuwehren. Damit dies funktioniert, müssen &lt;em&gt;fail2ban&lt;/em&gt; Logdateien zur Verfügung gestellt werden, die eine Host-Applikation wie beispielsweise ein Webserver erzeugt und IP-Adressen der Client-Rechner enthalten.&lt;/p&gt;&#10;&lt;h3 id="vorüberlegung-http-200-ok-als-filter"&gt;Vorüberlegung: &amp;ldquo;http 200 OK&amp;rdquo; als Filter?&lt;/h3&gt;&#10;&lt;p&gt;Auch normale, zulässige Zugriffe &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/200" target="_blank" rel="noopener noreferrer" class="external-link"&gt;200 OK&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; müssen für meinen Rate Limiter zählen.&lt;/p&gt;&#10;&lt;p&gt;Um ein Gefühl zu bekommen wie der Logger Zugriffe aufzeichnet, werfe ich einen Blick in die Logs. Ich lasse mir die Anzahl Einträge zurückgeben, die einen Status &lt;code&gt;200 OK&lt;/code&gt; enthalten. Dazu führe ich mit &lt;em&gt;grep&lt;/em&gt; eine Suche durch und lasse mir die Anzahl Treffer zeilenweise per &lt;code&gt;wc -l&lt;/code&gt; ausrechnen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:/var/log/caddy2/blog# grep -o &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#34;status&amp;#34;:200&amp;#39;&lt;/span&gt; access.log | wc -l&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;847&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Gut, es hat heute also bereits 847 Zugriffe gegeben, die &lt;code&gt;200 OK&lt;/code&gt; zurückgemeldet bekommen haben. Nun rufe ich selbst mal die Landing page meiner Blogseite auf und lasse mir dann die Artikelübersicht anzeigen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:/var/log/caddy2/blog# grep -o &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#34;status&amp;#34;:200&amp;#39;&lt;/span&gt; access.log | wc -l&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;858&lt;/span&gt; // after calling blog.schallbert.de&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rschallbert@machine:/var/log/caddy2/blog# grep -o &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#34;status&amp;#34;:200&amp;#39;&lt;/span&gt; access.log | wc -l&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;882&lt;/span&gt; // page loaded after clicking &lt;span style="color:#e6db74"&gt;&amp;#34;Posts&amp;#34;&lt;/span&gt; button&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whow, das waren 35 Einträge für zwei Klicks! Wenn ich mir das Log so ansehe, dann erklären sich die vielen Zeilen mit Assets - sprich Bildern und Logos, die geladen werden.&lt;/p&gt;&#10;&lt;p&gt;Damit ist leider klar, dass das Filterkriterium &lt;code&gt;status:200&lt;/code&gt; für meinen Rate Limiter nicht ohne Weiteres funktionieren kann. Denn die Anzahl Meldungen hängen maßgeblich von dem jeweiligen Artikel ab. Ich müsste also eine Grenze definieren, über der kein normaler Mensch Zugriffe auf mein Blog erzeugt.&lt;/p&gt;&#10;&lt;p&gt;Um nur &amp;ldquo;echte&amp;rdquo; Zugriffe auf meine Seiten zu zählen, muss ich die Assets in den Logs irgendwie ausschließen. Glücklicherweise gibt es dafür eine einfache &lt;a href="https://caddyserver.com/docs/caddyfile/directives/log_skip" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Direktive in Caddy&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# caddy2/config/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Enable logging for fail2ban&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log_skip &lt;span style="color:#e6db74"&gt;/assets*&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; log {&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; output file /&lt;/span&gt;log&lt;span style="color:#f92672"&gt;/&lt;/span&gt;blog&lt;span style="color:#f92672"&gt;/&lt;/span&gt;access&lt;span style="color:#f92672"&gt;.&lt;/span&gt;log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;log_skip&lt;/code&gt; sorgt nun dafür, dass alle Zugriffe auf Dateien im &lt;code&gt;assets/&lt;/code&gt;-Ordner und darunter nicht mitgeloggt werden.&lt;br&gt;&#10;Das Verhalten von Fail2ban wird mittels zweier Konfigurationsdateien festgelegt:&lt;/p&gt;&#10;&lt;h3 id="ordner-filterd"&gt;Ordner &lt;code&gt;filter.d&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;filter.d/&amp;lt;filter-name&amp;gt;.conf&lt;/code&gt; beinhaltet die Definition eines zu betrachtenden Ereignisses. So kann die &lt;code&gt;failregex&lt;/code&gt; ein &amp;ldquo;Seite nicht gefunden&amp;rdquo; &lt;code&gt;http 404&lt;/code&gt; Statuscode ebenso zum Auslösen des Paketfilters verwendet werden wie der erfolgreiche Zugriff für den Aufbau meines Rate Limiter, welcher auf &lt;code&gt;200 OK&lt;/code&gt; reagiert. Abgeschrieben habe ich für die Regex bei &lt;a href="https://www.kassner.com.br/en/2023/09/10/fail2ban-caddy-json-logs/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Rafael Kassner&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /opt/fail2ban/config/filter.d/caddy-ratelimit.conf&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;Definition]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;failregex = &amp;#34;client_ip&amp;#34;:&amp;#34;&amp;lt;HOST&amp;gt;&amp;#34;(.*)&amp;#34;status&amp;#34;:200&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;datepattern = \d+&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;ignoreregex =&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Außerdem werden in der Datei weitere Randbedingungen bestimmt, z.B. das Datumsformat &lt;code&gt;datepattern&lt;/code&gt; an den Log-Output des zu schützenden Systems angepasst.&lt;/p&gt;&#10;&lt;h3 id="datei-jaillocal"&gt;Datei &lt;code&gt;jail.local&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;jail.local&lt;/code&gt; bestimmt die Bedingungen, bei deren Eintritt der Paketfilter für die Client-IP aktiv wird und weitere Zugriffsversuche blockt. Für den Einsatz als Rate Limiter benötige ich folgende Felder:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;findtime&lt;/code&gt; - das Zeitfenster, in welchem Angriffe gezählt werden, &lt;code&gt;maxretry&lt;/code&gt; ist die Anzahl zulässiger Versuche im Zeitfenster und &lt;code&gt;bantime&lt;/code&gt; ist die Zeit, für die geblockt wird. &lt;code&gt;ignoreip&lt;/code&gt; ist meist per Werkseinstellung auf die relevanten internen IP-Adressen vorkonfiguriert. In meinem Falle funkt &lt;em&gt;act_runner&lt;/em&gt; zum Beispiel an intern &lt;em&gt;gitea&lt;/em&gt; zur Abfrage, ob neue Automatisierungsaufgaben anliegen. Diese Zugriffe will ich auf keinen Fall behindern.&lt;/p&gt;&#10;&lt;p&gt;Das im Folgenden verwendete Beispiel für &lt;em&gt;fail2ban&lt;/em&gt; zeigt den Rate Limiter auf meinem Blog. Am Ende sind Dateien und Log-Einträge für &lt;em&gt;gitea&lt;/em&gt; im Wesentlichen gleich. Nur Filternamen und Parameter unterscheiden sich voneinander durch die unterschiedlichen Anforderungen an Webseite und DevOps-Platform.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /opt/fail2ban/jail.local&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;caddy-ratelimit]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;enabled = true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;chain = DOCKER-USER&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;action = iptables-multiport&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;port = http,https&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;filter = caddy-ratelimit&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;logpath = /var/log/caddy2/blog/access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;findtime = 10s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;maxretry = 10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;bantime = 6h&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sind diese beiden Dateien konfiguriert, kann man &lt;em&gt;fail2ban&lt;/em&gt; neu starten und sich die Logs anschauen. Wenn man noch Fehler in den Dateien hat, gibt &lt;em&gt;fail2ban&lt;/em&gt; folgendes aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ERROR Errors in jail &lt;span style="color:#e6db74"&gt;&amp;#39;caddy-ratelimit&amp;#39;&lt;/span&gt;. Skipping...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tritt dies auf, kann z.B. das Jail nicht dem Filter (muss gleichnamig sein!) nicht zugeordnet werden, eine Feld-Definition wie &lt;code&gt;maxretry&lt;/code&gt; ist falsch getippt oder ein Zahlenformat kann nicht gelesen werden. Wenn alles gut läuft, lautet der Eintrag:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Reading config files: /etc/fail2ban/filter.d/caddy-ratelimit.conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="test-rate-limiter"&gt;Rate Limiter testen&lt;/h2&gt;&#10;&lt;p&gt;Nun möchte ich prüfen, ob alles wie erwartet funktioniert.&lt;/p&gt;&#10;&lt;h3 id="filter-regex-überprüfen"&gt;Filter-Regex überprüfen&lt;/h3&gt;&#10;&lt;p&gt;Als ersten Schritt schaue ich auf den Filterbegriff und prüfe, ob er in den Logs zuverlässig gefunden wird. Praktischerweise hat &lt;em&gt;fail2ban&lt;/em&gt; hierfür ein passendes Werkzeug eingebaut: &lt;code&gt;fail2ban-regex &amp;lt;logfile&amp;gt; &amp;lt;filter&amp;gt;&lt;/code&gt;. Ich kann also schlicht die Filterdatei und ein Test-Logfile eingeben und sehe dann, ob ich Treffer bekomme.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;fail2ban-regex ./access.log /etc/fail2ban/filter.d/caddy-ratelimit.conf &#10;&#10;Running tests&#10;=============&#10;&#10;Use filter file : caddy-ratelimit, basedir: /etc/fail2ban&#10;Use datepattern : \d+ : \d+&#10;Use log file : ./access.log.2&#10;Use encoding : UTF-8&#10;&#10;Results&#10;=======&#10;&#10;Failregex: 2009 total&#10;|- #) [# of hits] regular expression&#10;| 1) [2009] &amp;#34;client_ip&amp;#34;:&amp;#34;&amp;lt;HOST&amp;gt;&amp;#34;(.*)&amp;#34;status&amp;#34;:200&#10;`-&#10;&#10;Ignoreregex: 0 total&#10;&#10;Date template hits:&#10;|- [# of hits] date format&#10;| [2685] \d+&#10;`-&#10;&#10;Lines: 2685 lines, 0 ignored, 2009 matched, 676 missed&#10;[processed in 0.12 sec]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quantitativ bekomme ich, was ich erwarte: das oben beschriebene Suchverfahren per &lt;em&gt;grep&lt;/em&gt; liefert mir exakt dieselben Ergebnisse.&lt;/p&gt;&#10;&lt;h3 id="check-jail"&gt;Jail überprüfen&lt;/h3&gt;&#10;&lt;p&gt;Als nächsten Schritt muss ich testen, ob der Rate Limiter auch greift. Dafür simuliere ich mir Logeinträge. Die Jail-Regeln nehme ich für diesen Test stark zurück, sonst muss ich zu viele Eingaben in kurzer Zeit erzeugen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /opt/fail2ban/jail.local TEST&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;findtime = 10s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;maxretry = 2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;bantime = 100s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nach einem Neustart von &lt;em&gt;fail2ban&lt;/em&gt; erzeuge ich mir eine zweite Konsole, wo ich Log-Einträge simulieren kann. Dabei müssen mindestens Zeitpunkt, IP-Adresse des Aufrufenden und Status enthalten sein; ich gehe aber auf Nummer sicher und nehme vollständige Einträge.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;ts&amp;#34;:1751246139,&amp;#34;remote_ip&amp;#34;:&amp;#34;1.1.1.1&amp;#34;,&amp;#34;status&amp;#34;:200,[superLongIrrelevantOtherStuffForFiltering]}&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt; /var/log/caddy/blog/access.log&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dies gebe ich mehrfach ein, um über mein Limit von 2 retries zu kommen.&lt;/p&gt;&#10;&lt;p&gt;In der ersten Konsoleninstanz prüfe ich anschließend den &lt;em&gt;fail2ban&lt;/em&gt;-Status für den entsprechenden Filter.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fail2ban-client status caddy-ratelimit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Status &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; the jail: caddy-ratelimit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;|- Filter&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| |- Currently failed:&#9;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| |- Total failed:&#9;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;- File list:&#9;/var/log/caddy2/blog/access.log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;- Actions&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |- Currently banned:&#9;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |- Total banned:&#9;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;- Banned IP list:&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Das war nichts. Warum offenbart die fail2ban-Logdatei:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /fail2ban/config/log/fail2ban/fail2ban.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WARN &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-ratelimit&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Ignoring all log entries older than 20s; &lt;span style="color:#75715e"&gt;# probably messages generated within a fail2ban restart period&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also nochmal mit passenden Zeitstempeln. Diese muss ich aus der aktuellen Systemzeit (&lt;code&gt;$(date +%s.%N)&lt;/code&gt;) ziehen, wenn ich sie ständig anpassen muss. Daher nehme ich eine vollständige Logzeile her, modifiziere sie auf die IP-Adresse &lt;code&gt;1.1.1.1&lt;/code&gt; und füge passende Zeitstempel ein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;echo&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;{\&amp;#34;level\&amp;#34;:\&amp;#34;info\&amp;#34;,\&amp;#34;ts\&amp;#34;:$(date +%s.%N),\&amp;#34;logger\&amp;#34;:\&amp;#34;http.log.access.log0\&amp;#34;,\&amp;#34;msg\&amp;#34;:\&amp;#34;handled request\&amp;#34;,\&amp;#34;request\&amp;#34;:{\&amp;#34;remote_ip\&amp;#34;:\&amp;#34;1.1.1.1\&amp;#34;,\&amp;#34;remote_port\&amp;#34;:\&amp;#34;38229\&amp;#34;,\&amp;#34;client_ip\&amp;#34;:\&amp;#34;1.1.1.1\&amp;#34;,\&amp;#34;proto\&amp;#34;:\&amp;#34;HTTP/1.1\&amp;#34;,\&amp;#34;method\&amp;#34;:\&amp;#34;GET\&amp;#34;,\&amp;#34;host\&amp;#34;:\&amp;#34;blog.schallbert.de\&amp;#34;,\&amp;#34;uri\&amp;#34;:\&amp;#34;/vacuum-clamping/\&amp;#34;,\&amp;#34;headers\&amp;#34;:{\&amp;#34;Accept-Encoding\&amp;#34;:[\&amp;#34;gzip, deflate, br\&amp;#34;],\&amp;#34;Connection\&amp;#34;:[\&amp;#34;keep-alive\&amp;#34;],\&amp;#34;User-Agent\&amp;#34;:[\&amp;#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0\&amp;#34;],\&amp;#34;Accept\&amp;#34;:[\&amp;#34;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\&amp;#34;],\&amp;#34;Accept-Language\&amp;#34;:[\&amp;#34;en-US,en;q=0.5\&amp;#34;]},\&amp;#34;tls\&amp;#34;:{\&amp;#34;resumed\&amp;#34;:false,\&amp;#34;version\&amp;#34;:772,\&amp;#34;cipher_suite\&amp;#34;:4865,\&amp;#34;proto\&amp;#34;:\&amp;#34;http/1.1\&amp;#34;,\&amp;#34;server_name\&amp;#34;:\&amp;#34;blog.schallbert.de\&amp;#34;}},\&amp;#34;bytes_read\&amp;#34;:0,\&amp;#34;user_id\&amp;#34;:\&amp;#34;\&amp;#34;,\&amp;#34;duration\&amp;#34;:0.001207354,\&amp;#34;size\&amp;#34;:12290,\&amp;#34;status\&amp;#34;:200,\&amp;#34;resp_headers\&amp;#34;:{\&amp;#34;Last-Modified\&amp;#34;:[\&amp;#34;Tue, 10 Jun 2025 19:33:01 GMT\&amp;#34;],\&amp;#34;Content-Encoding\&amp;#34;:[\&amp;#34;gzip\&amp;#34;],\&amp;#34;Server\&amp;#34;:[\&amp;#34;Caddy\&amp;#34;],\&amp;#34;Alt-Svc\&amp;#34;:[\&amp;#34;h3=\\\&amp;#34;:443\\\&amp;#34;; ma=2592000\&amp;#34;],\&amp;#34;Vary\&amp;#34;:[\&amp;#34;Accept-Encoding\&amp;#34;],\&amp;#34;Etag\&amp;#34;:[\&amp;#34;\\\&amp;#34;gzip\\\&amp;#34;\&amp;#34;],\&amp;#34;Content-Type\&amp;#34;:[\&amp;#34;text/html; charset=utf-8\&amp;#34;]}}&amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nach mehrmaligem Aufruf dieses gebastelten Kommandos erhalte ich nun&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fail2ban-client status caddy-ratelimit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Status &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; the jail: caddy-ratelimit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;|- Filter&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| |- Currently failed:&#9;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| |- Total failed:&#9;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;| &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;- File list:&#9;/var/log/caddy2/blog/access.log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;- Actions&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |- Currently banned:&#9;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |- Total banned:&#9;&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;- Banned IP list:&#9;1.1.1.1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Das sieht super aus. Auf zum letzten Schritt.&lt;/p&gt;&#10;&lt;h3 id="paketfilter-überprüfen"&gt;Paketfilter überprüfen&lt;/h3&gt;&#10;&lt;p&gt;Wenn wir nun einmal in die Logs schauen, müsste unser &lt;code&gt;[caddy-ratelimit]&lt;/code&gt; Jail nun bei jeder &amp;ldquo;normalen&amp;rdquo; Anfrage triggern.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine: nano fail2ban/config/log/fail2ban/fail2ban.log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-ratelimit&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP a&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-status&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP b&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-ratelimit&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Ignore 172.18.0.1 by ip&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-status&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Ignore 172.18.0.1 by ip&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-ratelimit&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP c&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-ratelimit&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP c&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-status&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP c&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-status&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP c&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-ratelimit&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP d&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-status&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP d&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-status&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP e&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-ratelimit&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP e&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;timestamp&amp;gt; &amp;lt;id&amp;gt; INFO &lt;span style="color:#f92672"&gt;[&lt;/span&gt;caddy-ratelimit&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Found &amp;lt;IP f&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Um die ganze Kette getestet zu haben müssen wir jetzt noch prüfen, ob die entsprechende IP-Adresse auch wirklich in der Hardware geblockt ist. Dafür gebe ich ein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# iptables -n -L | grep &lt;span style="color:#e6db74"&gt;&amp;#34;1.1.1.1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;REJECT all -- 1.1.1.1 0.0.0.0/0 reject-with icmp-port-unreachable&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Scheint ja alles in Ordnung zu sein. Für einen letzten Test von einer externen IP-Adresse rufe ich im Browser einen &lt;a href="https://www.deadlinkchecker.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Link Checker&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; auf, der meine Webseite crawlen und damit den Rate Limiter auslösen soll. Obwohl dieser 44 Links in kürzester Zeit durchläuft, wird der Test als &amp;ldquo;bestanden&amp;rdquo; angezeigt. Sehr merkwürdig, &lt;code&gt;[caddy-ratelimit]&lt;/code&gt; hätte anschlagen müssen!&lt;/p&gt;&#10;&lt;h3 id="iptable-chain-input-statt-forward"&gt;iptable-chain INPUT statt FORWARD&lt;/h3&gt;&#10;&lt;p&gt;Verwirrt schaue ich mir mal die ganze &lt;code&gt;iptable&lt;/code&gt; an. Parallel suche ich mir die IP-Adresse des Link Checker heraus. Sie taucht in den &lt;code&gt;iptables&lt;/code&gt; auf. Und trotzdem wird offensichtlich nicht geblockt. Woran das liegt? Ein genauer Blick zeigt:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;Chain INPUT (policy ACCEPT)&#10;target prot opt source destination &#10;f2b-caddy-ratelimit tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 22&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Problem hier ist, dass mein Jail auf der &lt;code&gt;INPUT&lt;/code&gt;-Chain liegt. Die Anfragen gehen aber doch nicht direkt an meine Server-Hardware, sondern werden über &lt;em&gt;Docker&lt;/em&gt; an &lt;em&gt;Caddyserver&lt;/em&gt; weitergeleitet. Um zu funktionieren, muss ich auf der &lt;code&gt;FORWARD&lt;/code&gt;-Chain landen, wo &lt;code&gt;DOCKER-USER&lt;/code&gt; bereits ist. Sehr merkwürdig, hatte ich doch in der &lt;code&gt;jail.local&lt;/code&gt; extra &lt;code&gt;chain = DOCKER-USER&lt;/code&gt; angegeben. Irgend etwas muss diese Definition überschreiben.&lt;/p&gt;&#10;&lt;p&gt;Ein unscheinbarer &lt;a href="https://gist.github.com/Rankarusu/23a04ed587b05c6f2b701f2457a127b0?permalink_comment_id=5347631#gistcomment-5347631" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Forenpost&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zusammen mit dem Kommentar &lt;code&gt;multiport&lt;/code&gt; im iptables-Print bringt mich auf die Lösung: Die &lt;code&gt;action = iptables-multiport&lt;/code&gt; Direktive überschreibt mein &lt;code&gt;chain = DOCKER-USER&lt;/code&gt; Statement, denn in der zugehörigen Konfigurationsdatei &lt;code&gt;iptables.conf&lt;/code&gt; wird folgendes gesetzt: &lt;code&gt;chain = INPUT&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Also lösche ich den &lt;code&gt;action&lt;/code&gt; Eintrag einfach, sodass &lt;em&gt;fail2ban&lt;/em&gt; auf den Default für &lt;code&gt;DOCKER-USER&lt;/code&gt; zurückfällt: &lt;code&gt;multiport dports 80,443&lt;/code&gt;.&#10;Nun taucht der Jail-Name auch korrekt in &lt;code&gt;iptables&lt;/code&gt; auf.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# iptables -n -L&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Chain DOCKER-USER &lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; references&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;target prot opt source destination &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;f2b-caddy-ratelimit tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 80,443&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="erfolg-genießen"&gt;Erfolg genießen&lt;/h2&gt;&#10;&lt;p&gt;Endlich zeigt ein weiterer Link Check, dass mein Rate Limiter funktioniert.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-07-10-deadlinkcheck-ratelimit-working.avif" alt="Image: deadlinkchecker view for blog.schallbert.de hits configured rate limit and gets blocked subsequentially. Thus it returns a Timeout" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="-update-oktober-2025"&gt;⚠️ Update Oktober 2025&lt;/h2&gt;&#10;&lt;p&gt;In Verbindung mit diesem Artikel gibt es ein wichtiges Update, welches den &lt;a href="https://blog.schallbert.de/fail2ban-error-configuration-bad-value/"&gt;&lt;em&gt;fail2ban&lt;/em&gt; Fehler &amp;ldquo;Bad value substitution&amp;rdquo; for &amp;lsquo;action&amp;rsquo;&lt;/a&gt; behebt. Dieses Problem kann auftauchen, wenn &lt;em&gt;fail2ban&lt;/em&gt; Services prüfen soll, die in Docker-Containern laufen.&lt;/p&gt;&#10;</description></item><item><title>Gitea stürzt ab: Zu viele Anfragen</title><link>https://blog.schallbert.de/gitea-out-of-memory/</link><pubDate>Mon, 30 Jun 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/gitea-out-of-memory/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-06-30-server-load-thumb.avif"&#10; class="post-cover"&#10; alt="Image: server at max load when gitea is flooded with GET requests"&#10; title="Gitea stürzt ab: Zu viele Anfragen" /&gt;&#10;&lt;p&gt;Hier möchte ich kurz darstellen, wie ein Absturz eines Dienstes meinen Server über Stunden wiederholt lahmgelegt hat. Und dies so gründlich, dass ich nur aus der Konsole des Anbieters überhaupt einen Neustart durchführen konnte. Ich erkläre, wie es dazu kam und wie ich dieses und ähnliche Probleme in Zukunft vermeiden möchte.&lt;/p&gt;&#10;&lt;h2 id="ich-wurde-angegriffen-oder"&gt;Ich wurde angegriffen. Oder?&lt;/h2&gt;&#10;&lt;p&gt;Ich saß gerade an einem Artikel, den ich später posten wollte. Zur Sicherheit erstellte ich wie üblich einen Commit und wollte ihn auf meine &lt;em&gt;Gitea&lt;/em&gt;-Instanz schieben. Doch mein &lt;code&gt;git push&lt;/code&gt; Befehl lief einfach nicht durch.&lt;/p&gt;&#10;&lt;p&gt;Verwundert versuchte ich, meine Webseite aufzurufen. Zurück kam:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-06-30-404-message.avif" alt="Image: A browser&amp;#39;s timeout error message `Could not complete your request`" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Merkwürdig. Anschließend wollte ich mich auf meinem Server einloggen, um nach dem Rechten zu sehen: &lt;code&gt;ssh &amp;lt;servername&amp;gt;&lt;/code&gt;. Wieder blieb das Terminal ohne Reaktion. Mist!&lt;/p&gt;&#10;&lt;p&gt;Timeout. Als letzte Rettung meldete ich mich bei meinem Hoster an und schaute auf die Graphen des Servers:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-06-30-server-load.avif" alt="Image: 200% CPU load on my server for nearly two hours" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Oh, was ist denn da los? Nun versuche ich über die Weboberfläche, den Server herunterzufahren. Klappt auch nicht. Erst ein harter Neustart ist erfolgreich.&#10;Ich kann mich wieder per SSH einloggen und sehe, dass auch alle &lt;em&gt;Docker&lt;/em&gt;-Container wieder ganz normal hochfahren.&lt;/p&gt;&#10;&lt;h2 id="was-war-passiert"&gt;Was war passiert?&lt;/h2&gt;&#10;&lt;p&gt;Gut, dass ich eine Woche lang alle Logs aufbewahre. So und über die Auslastung der Maschine über Zeit kann ich einigermaßen rekonstruieren, was passiert ist.&lt;/p&gt;&#10;&lt;h3 id="welche-logs-helfen"&gt;Welche Logs helfen?&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Crash lokalisieren! Auslösende Anwendung finden im &lt;code&gt;kern.log&lt;/code&gt;, Zeitstempel notieren.&lt;/li&gt;&#10;&lt;li&gt;Gibt es systemweite Auswirkungen oder andere Dienste, die beeinträchtigt werden? Im &lt;code&gt;syslog&lt;/code&gt; nachsehen.&lt;/li&gt;&#10;&lt;li&gt;Falls der Verdacht aufkommt, das System sei möglicherweise gehackt worden: &lt;code&gt;auth.log&lt;/code&gt; hat die Details.&lt;/li&gt;&#10;&lt;li&gt;Sollte die betroffene Anwendung im Container laufen, helfen entsprechende Logs dort möglicherweise weiter.&lt;/li&gt;&#10;&lt;li&gt;Logs der Anwendung selbst sichten. Besonders die Zeit kurz vor dem Crash ist interessant.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Und hier nochmal im Detail die Logs, die ich mir für meine Verhalten angesehen habe und wo sie zu finden sind.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;log name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;purpose&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;relevant content for this issue&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;/var/log/syslog&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;System-wide (bare metal) messages&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(docker) warning: &amp;ldquo;health check for container &lt;code&gt;&amp;lt;ID&amp;gt;&lt;/code&gt; timeout&amp;rdquo; (containerd) error: &amp;ldquo;ttrpc: received message on inactive stream&amp;rdquo;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;/var/log/auth.log&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Contains authentication messages from external hosts, in my case mostly SSH&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;None&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;/var/log/kern.log&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Logs all app, service, daemon and system crashes&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Out of memory: Killed process &lt;code&gt;&amp;lt;ID&amp;gt;&lt;/code&gt; (&lt;code&gt;&amp;lt;serviceName&amp;gt;&lt;/code&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[.]/gitea/log/gitea.log&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Protocol for requests, actions on Gitea&amp;rsquo;s web interface, repo changes etc.&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;GET&lt;/code&gt; requests, crash/restart indications&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;docker container logs &lt;code&gt;&amp;lt;containerID&amp;gt;&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Docker&amp;rsquo;s logs for the container in question&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Received signal 15; terminating. (SIGTERM)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="kernlogsyslog"&gt;kern.log/syslog&lt;/h3&gt;&#10;&lt;p&gt;Hier sehe ich neben dem Stack trace, was genau passiert ist. Die am besten verständlichste Meldung ist folgende:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;Out of memory: Killed process `&amp;lt;ID&amp;gt;` (gitea)&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Gitea ist also der Stecker gezogen worden, weil es praktisch alle Systemressourcen gezogen hatte.&#10;Wenn ich im Log nach oben scrolle, sehe ich ein paar Minuten vorher:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;timestamp&amp;gt; &amp;lt;machineName&amp;gt; dockerd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;716&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;warning msg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Health check for container &amp;lt;ID&amp;gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;timestamp&amp;gt; &amp;lt;machineName&amp;gt; dockerd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;716&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;error msg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;stream copy error: reading from a closed fifo&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bereits hier sehe ich Warnungen, dass der gitea-Container nicht sauber arbeitet. Ein weiterer Vorteil, den das Einbauen von &amp;ldquo;Healthchecks&amp;rdquo; in der &lt;code&gt;docker-compose.yml&lt;/code&gt; Datei hat. Über Zweck und Einbau von Healthchecks in &lt;em&gt;docker&lt;/em&gt; hatte ich aus anderen Gründen bereits &lt;a href="https://blog.schallbert.de/fix-gitea-runner/#healthcheck"&gt;einen Artikel verfasst&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="gitealog"&gt;gitea.log&lt;/h3&gt;&#10;&lt;p&gt;Folgender Log-Eintrag zeigt wegen eines &lt;a href="https://github.com/go-gitea/gitea/issues/22275" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Bugs in einem Submodul&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; indirekt an, dass &lt;em&gt;Gitea&lt;/em&gt; gerade neu gestartet ist:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cmd/web.go:205:serveInstalled&lt;span style="color:#f92672"&gt;()&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;W&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Table system_setting Column version db default is , struct default is &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und weiter oben im Log nun:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-06-30-attack-log.avif" alt="Image: Gitea log showing numerous GET request entries, many of which for a specific large file (MiB range), following a typical Gitea startup message" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Sehr interessant. Die &lt;code&gt;highlight.css&lt;/code&gt; liegt in meinem öffentlichen Repo. Auf ihrer Grundlage erhält die Seite &lt;a href="https://lectures.schallbert.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;lectures.schallbert.de&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ihr Aussehen. Diese Datei ist ziemlich groß, fast &lt;code&gt;1MiB&lt;/code&gt;. Und sie wird hier gleich zig male geladen, und das praktisch für jeden Commit.&lt;/p&gt;&#10;&lt;p&gt;Nun schaue ich mir weitere Crashes in der Vergangenheit an. Immer sind es bursts von &lt;code&gt;GET&lt;/code&gt;-Befehlen auf große Dateien oder die Anforderung von &lt;code&gt;compares&lt;/code&gt; zwischen zwei Branches des Repository, die einem Aufhängen meines Servers vorausgehen.&lt;/p&gt;&#10;&lt;h2 id="wer-steckt-dahinter"&gt;Wer steckt dahinter?&lt;/h2&gt;&#10;&lt;p&gt;Alle crash-auslösenden Requests kommen aus dem selben IP-Adressraum. Die Abstürze starteten bereits vor ein paar Wochen. Zumeist aber zu Tageszeiten, die ich (und anscheinend auch viele meiner LeserInnen) nicht mitbekamen. Und nach ein paar Minuten war der Server stets wieder im Normalbetrieb.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-06-30-gcp-dos.avif" alt="Image: Whois-request for the IP that had my server crashed, owner: Google LLC (GCP)" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Oha, die Spur führt zu Google&amp;rsquo;s Cloud Platform (GCP).&lt;/p&gt;&#10;&lt;h3 id="aber-ich-hatte-doch-robots-ausgesperrt"&gt;Aber ich hatte doch Robots ausgesperrt?&lt;/h3&gt;&#10;&lt;p&gt;In der Tat hatte ich die &lt;a href="https://blog.schallbert.de/gitea-search-indexation/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Suchmaschinenindexierung für Gitea gestoppt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Merkwürdig. Oder habe ich es hier gar nicht mit einer der Google-spiders oder &amp;ldquo;KI&amp;rdquo;-Scraper, sondern mit einem fiesen Hacker zu tun, der sich bei &amp;ldquo;Google Cloud&amp;rdquo; eine virtuelle Maschine gemietet hat?&lt;/p&gt;&#10;&lt;h3 id="log-recherche-wie-häufig-wird-die-datei-angefragt"&gt;Log-Recherche: Wie häufig wird die Datei angefragt?&lt;/h3&gt;&#10;&lt;p&gt;Ein Spider würde ja höchstens einmal alle paar Wochen alle meine Seiten durchsuchen, richtig? Und sich hoffentlich nicht über meine &lt;code&gt;robots.txt&lt;/code&gt; hinwegsetzen. Erst recht nicht würde ein Crawler mehrfach und in kurzen Abständen denselben Request stellen, oder?&lt;/p&gt;&#10;&lt;p&gt;Zum dies zu prüfen, suche ich in den *Gitea Logs nach Einträgen von GET-Anfragen auf einer der großen und damit ressourcenintensiv zu übertragenen Dateien:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gunzip gitea.log.&amp;lt;date.rotateID&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grep &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;lt;filename&amp;gt;&amp;#34;&lt;/span&gt; gitea.log.&amp;lt;date.rotateID&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In der Liste sehe ich, dass dieselbe Anfrage auf dieselbe Zieldatei von derselben IP-Adresse mehrfach und binnen Sekunden gestellt wird. Kurz vor dem Crash benötigt mein Server schon fast &lt;code&gt;4sec&lt;/code&gt;, um die Anfrage zu bedienen.&lt;/p&gt;&#10;&lt;p&gt;Auch sehe ich, dass sich die IP-Adresse alle paar Stunden ändert.&lt;/p&gt;&#10;&lt;h2 id="einen-dos-angriff-parieren"&gt;Einen DoS-Angriff parieren&lt;/h2&gt;&#10;&lt;p&gt;Zusammenfassend muss ich zum Schluss kommen, dass ich aus dem Adressraum des GCP per &lt;a href="https://en.wikipedia.org/wiki/Denial-of-service_attack" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Denial-of-service&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; angegriffen werde.&lt;/p&gt;&#10;&lt;p&gt;Um ein bisschen mehr Hintergrundwissen zu sammeln, besuche ich ein paar Websites zum Thema. Dort lerne ich, dass &lt;em&gt;gitea&lt;/em&gt; auf meinem Server unter einer &lt;a href="https://www.geeksforgeeks.org/computer-networks/types-of-dos-attacks/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Application Layer Attack&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zusammenbricht. Jetzt da ich weiß, was passiert und wie das Problem heißt, fällt das Finden von Lösungen gleich viel leichter 😅&lt;/p&gt;&#10;&lt;p&gt;Ich will mich natürlich nicht kampflos ergeben, indem ich meine Gitea-Instanz dauerhaft vom Netz nehme. Also, welche Optionen habe ich?&lt;/p&gt;&#10;&lt;h3 id="mehr-ressourcen-bereitstellen"&gt;Mehr Ressourcen bereitstellen&lt;/h3&gt;&#10;&lt;p&gt;Zugegeben, meine Maschine hat nur &lt;code&gt;40GB&lt;/code&gt; Speicher und &lt;code&gt;2GB VRAM&lt;/code&gt; sowie eine mickrige 2-Kern CPU aus 2009. Ich könnte einen leistungsfähigeren Server buchen, um Lastspitzen besser abreiten zu können. Doch dies würde den Angriff nicht verhindern, sondern nur seine Auswirkungen abmildern.&lt;/p&gt;&#10;&lt;h3 id="rate-limiting-direkt-im-webserver"&gt;Rate limiting direkt im Webserver&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Rate_limiting" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Rate limiter&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; begrenzen die Anzahl Anfragen eines Clients innerhalb eines Zeitfensters. Dadurch werden die Ressourcen des Servers geschont. Dies ist eine &amp;ldquo;weiche&amp;rdquo; Abwehr von Dos-Attacken, denn auslösende IP-Adressen werden mit einer Fehlermeldung kurzzeitig und sanft abgewehrt. Üblicherweise wird &lt;code&gt;HTTP status code 429&lt;/code&gt; &amp;ldquo;Too Many Requests&amp;rdquo; zurückgegeben, wenn der Limiter eingreift.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Gitea&lt;/em&gt; verfügt über keinen Rate Limiter. In &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/#web-access"&gt;meinem Setup&lt;/a&gt; läuft &lt;em&gt;Gitea&lt;/em&gt; hinter einem reverse proxy, der von meinem &lt;em&gt;Caddyserver&lt;/em&gt; bereitgestellt wird. Hier also müsste ich ansetzen. Für &lt;em&gt;Caddy&lt;/em&gt; gibt es Rate limiter nur als &lt;a href="https://caddyserver.com/docs/modules/http.handlers.rate_limit" target="_blank" rel="noopener noreferrer" class="external-link"&gt;externe Module&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, welche manuell in &lt;em&gt;xcaddy&lt;/em&gt; nachinstalliert und eingerichtet werden müssen.&lt;/p&gt;&#10;&lt;h3 id="blocken-mit-ip-table-regeln"&gt;Blocken mit IP-table Regeln&lt;/h3&gt;&#10;&lt;p&gt;Hier könnte man sich erneut &lt;em&gt;fail2ban&lt;/em&gt; hernehmen und mehrfache Anfragen derselben Ressource von einer IP-Adresse schlicht blocken. &lt;em&gt;Gitea&lt;/em&gt; hat in der &lt;a href="https://docs.gitea.com/next/administration/fail2ban-setup" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dokumentation eine Beschreibung der Einrichtung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. In meinem Falle müsste ich für die Umsetzung dort weitermachen, &lt;a href="https://blog.schallbert.de/server-protection/#was-noch-nicht-funktioniert-gitea--fail2ban"&gt;wo ich bereits einmal aufgegeben hatte&lt;/a&gt; und damit nicht nur SSH, sondern auch normale Seitenaufrufe überwachen.&lt;/p&gt;&#10;&lt;p&gt;Für mich klingt diese Lösung am Sinnvollsten, denn sie trennt klar Funktionen in verschiedene Anwendungen auf. Ich nutze ausschließlich bei mir bereits verfügbare Applikationen: &lt;em&gt;Caddy&lt;/em&gt; würde die Zugriffslogs bereitstellen und &lt;em&gt;fail2ban&lt;/em&gt; müsste sie lesen und Filter in der Konfoguration &lt;code&gt;jail.local&lt;/code&gt; so setzen, dass es sich wie ein Rate Limiter verhält.&lt;/p&gt;&#10;&lt;p&gt;Jetzt schaue ich mal, wie ich &lt;em&gt;Caddy&lt;/em&gt; entsprechend mit &lt;em&gt;fail2ban&lt;/em&gt; verbinden kann. Den weiterführenden Artikel &lt;a href="https://blog.schallbert.de/fail2ban-with-caddy/"&gt;Fail2ban mit Caddy einrichten&lt;/a&gt; verlinke ich direkt mal 🙂&lt;/p&gt;&#10;</description></item><item><title>Webshop selber aufsetzen - Teil3</title><link>https://blog.schallbert.de/ecommerce-selection/</link><pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/ecommerce-selection/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-06-15-opencart-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Snip of OpenCart&amp;#39;s article about data safety"&#10; title="Webshop selber aufsetzen - Teil3" /&gt;&#10;&lt;h2 id="die-auswahl-anhand-von-leitfragen"&gt;Die Auswahl anhand von Leitfragen&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Welche Lösung ist einfach gestrickt und mit wenig Zeitaufwand zu installieren?&lt;/li&gt;&#10;&lt;li&gt;Welche Lösung ist leichter zu pflegen?&lt;/li&gt;&#10;&lt;li&gt;Bei welchem System kann ich als Betreiber am einfachsten nachvollziehen, wie was funktioniert (Transparenz, Abhängigkeiten)?&lt;/li&gt;&#10;&lt;li&gt;Wo ist die Angriffsoberfläche geringer?&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="hier-fehlt-noch-einiges"&gt;Hier fehlt noch einiges!&lt;/h2&gt;&#10;&lt;p&gt;In den letzten Wochen ist mir die Lust nach einem eigenen Webshop etwas vergangen, da ich mich erst um die Sicherheit meines Servers und die Einarbeitung alter, fast fertiger Projekte kümmern wollte. Ich werde diese Serie vervollständigen, sobald mich das ich-will-was-Neues-machen-Fieber erneut packt.&lt;/p&gt;&#10;&lt;p&gt;Haltet durch!&#10;&lt;em&gt;Schallbert&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Webshop selber aufsetzen - Teil2</title><link>https://blog.schallbert.de/ecommerce-alternatives/</link><pubDate>Thu, 15 May 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/ecommerce-alternatives/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-05-15-ecommerce-frameworks-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: logos from different e-commerce solutions"&#10; title="Webshop selber aufsetzen - Teil2" /&gt;&#10;&lt;h2 id="ein-paar-optionen-für-die-webshop-software"&gt;Ein paar Optionen für die Webshop-Software&lt;/h2&gt;&#10;&lt;p&gt;Gehen wir mal davon aus, dass in der Vorauswahl nur Software landet, die meine &lt;a href="https://blog.schallbert.de/ecommerce-requirements/"&gt;Anforderungen&lt;/a&gt; zumindest teilweise erfüllt. Im Folgenden möchte ich ein paar mögliche Ecommerce-Lösungen vorstellen.&lt;/p&gt;&#10;&lt;h3 id="shopify"&gt;Shopify&lt;/h3&gt;&#10;&lt;p&gt;Shopify ist eine alles-inklusive-Paketlösung für den Onlinehandel. Shopify Inc. hat ihren Hauptsitz in Kanada und wickelt das europäische Geschäft über Irland ab. Die auf der Website zu findenden Bereiche &lt;a href="https://www.shopify.com/de/legal" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Rechtliches&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sowie die Komplexität der Informationsdarstellung zum Thema &lt;a href="https://www.shopify.com/de/legal/impressum?country=de&amp;amp;lang=en" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Datenschutz&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; lassen mich vermuten, dass Shopify ein multinationaler Konzern ist - und nicht nur die Onlineshops selbigen zur Verfügung stellt.&lt;/p&gt;&#10;&lt;p&gt;Aber zurück zum Thema.&lt;/p&gt;&#10;&lt;p&gt;Die Domain sowie das Hosting sind bei Shopify im Preis inbegriffen und der eigene Shop kann ohne Programmierkenntnisse per Weboberfläche, Auswählen eines Themes und Einstellen von ein paar Parametern in kurzer Zeit erstellt werden. Shopify besitzt eine ausführliche Dokumentation und bietet Fortbildungen und allerlei weitere Dienstleistungen für ihre Abonnenten an.&lt;/p&gt;&#10;&lt;p&gt;Doch wo bleibt da der Spaß? Schließlich betreibe ich ein Techblog und bin kein Geschäftsmann im eigentlichen Sinne. Ein bisschen Programmieren muss da schon drin sein. Außerdem gefällt mir als Open-Source-Fan das Konzerngehabe (man schaue sich ihre moderne, mit automatisch abspielenden Videos ausgestattete, auf Marketing-Zwecke durchoptimierte Website an) nicht so besonders gut. Die Konfigurierbarkeit ist bestenfalls mittelmäßig und die Optimierung auf Suchmaschinenergebnisse muss ich wie bei den meisten anderen Lösungen auch selbst erledigen. Was Datenschutz und Sicherheit angeht, betreiben sie die übliche Compliance- und Stand-der-Technik-Kommunikation; ich kann also nicht dahinter schauen und muss davon ausgehen, dass sie ihre Kunden auch im eigenen Interesse gut schützen.&lt;/p&gt;&#10;&lt;p&gt;Shopify bezeichnet sich selbst als &lt;a href="https://www.shopify.com/blog/open-source-ecommerce#5" target="_blank" rel="noopener noreferrer" class="external-link"&gt;closed-source SaaS platform&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Daher streiche ich diese Plattform von meiner Liste präferierter Webshoplösungen, weil ich mich nicht ohne Not von Wohl und Wehe Dritter abhängig machen möchte. Bei Shopify hätte nämlich nicht ich die Kontrolle über Abokosten oder &amp;ldquo;meinen&amp;rdquo; Anteil an der Cloud, sondern Shopify. Nicht ich kann bestimmen, wie viele Transaktionen im &amp;ldquo;Plan&amp;rdquo; inklusive sind, sondern Shopify. Nicht meine Kunden blieben Herren ihrer Daten, sondern Shopify oder ein von ihnen mit der Abwicklung beauftragtes Drittunternehmen.&lt;/p&gt;&#10;&lt;h3 id="wordpress--woocommerce"&gt;Wordpress / WooCommerce&lt;/h3&gt;&#10;&lt;p&gt;Ich kenne Wordpress nur als Moloch. Die Eierlegende Wollmilchsau, die durch eine unübersichtlich gewordene Anzahl an Plugins, Erweiterungen und Funktionen alles kann, was man sich nur wünschen kann. Und vieles mehr, was man nicht braucht.&lt;/p&gt;&#10;&lt;p&gt;Aber es ist quelloffen, kostenfrei und wird laut eigener Darstellung von sehr, sehr vielen Shopbetreibern genutzt. Schauen wir uns also mal die bekannteste Ecommerce-Erweiterung zu Wordpress an: &lt;a href="https://woocommerce.com/document/build-online-store/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;WooCommerce&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Ich fand auf Anhieb zig Anleitungen zum Aufsetzen eines WooCommerce-Shops (Beispielsweise von &lt;a href="https://www.greengeeks.com/blog/set-up-woocommerce-wordpress-ultimate-guide/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;GeeksForGeeks&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder &lt;a href="https://themeisle.com/blog/how-to-set-up-woocommerce/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Themeisle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;), also verzichte ich im Folgenden auf eine eigene Darstellung.&lt;/p&gt;&#10;&lt;p&gt;Nach dem Querlesen einiger Anleitungen fasse ich zusammen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Ein WooCommerce-Shop lässt sich einfach per Assistent aufsetzen und konfigurieren&lt;/li&gt;&#10;&lt;li&gt;Es stehen sehr viele Themes zur Verfügung und einfach einzubindende Shopdesigns bieten für jeden Geschmack eine passende Optik&lt;/li&gt;&#10;&lt;li&gt;Es hilft, bereits eine Wordpress-Seite zu haben und etwas Basiswissen bezüglich Domains, DNS und Hostern zu besitzen&lt;/li&gt;&#10;&lt;li&gt;Viele Zahlungsdienstleister und einige Versandabwickler sind vorkonfiguriert auswählbar&lt;/li&gt;&#10;&lt;li&gt;Es gibt eine Menge YouTube-Lehrvideos zum Thema und eine ausführliche Dokumentation liegt ebenfalls vor&lt;/li&gt;&#10;&lt;li&gt;Zahllose Plugins erlauben die Optimierung des Shops auch auf sehr spezialisierte Gebiete und Einsatzzwecke&lt;/li&gt;&#10;&lt;li&gt;Allerdings steht dieser Optionsvielfalt gegenüber, dass die Auswahl der &amp;ldquo;besten Lösung&amp;rdquo; schwer fallen kann und dass Optimierungen der Seite hinsichtlich Geschwindigkeit und Auffindbarkeit komplex werden können&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Alles in allem wirkt WooCommerce auf mich wie eine ausgewogene Mischung aus &amp;ldquo;Ich will schnell etwas Brauchbares zusammen klicken&amp;rdquo; und &amp;ldquo;Ich möchte alles konfigurieren und umbauen können&amp;rdquo;.&lt;/p&gt;&#10;&lt;h3 id="snipcart"&gt;Snipcart&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://snipcart.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Snipcart&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; würde ich als &amp;ldquo;Shopping-Plugin&amp;rdquo; für eine beliebige Website verstehen. Auch statische Websites, die eigentlich nur einen Warenkatalog darstellen können, werden mit Snipcart zu einer vollwertigen eCommerce-Lösung. Kernelement ist hier der Warenkorb &amp;ldquo;cart&amp;rdquo;, über den der Kaufvorgang abgewickelt wird.&lt;/p&gt;&#10;&lt;p&gt;Die Schnittstelle ist das &lt;code&gt;HTML Markup&lt;/code&gt; der Webseite. Snipcart wird als &lt;code&gt;JavaScript&lt;/code&gt;-Blob eingebunden und per &lt;code&gt;HTML&lt;/code&gt; referenziert. Gestaltung und Aussehen läuft über cascading style sheets. In meinem Fall also könnte ich das vorhandene &amp;ldquo;Tech Stack&amp;rdquo; bestehend aus einer statischen Jekyll-site mit CD-Pipeline in Gitea auf einer Caddyserver-Instanz auch für den Shop nutzen und mit minimalen Anpassungen eine Optik aus einem Guss für Blog und Shop zaubern. Klingt doch schon einmal prima.&lt;/p&gt;&#10;&lt;p&gt;Snipcart wird von einer kanadischen Firma in Québec entwickelt. Es bezeichnet sich als &amp;ldquo;&lt;a href="https://snipcart.com/ecommerce-inventory-management" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Shopping Cart Platform&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;rdquo;. Für die Installation wird ein Account benötigt und man muss die eigene Shopping-Domain ähnlich wie bei der Google Search Console oder Google Analytics im Web-Frontend von Snipcart angeben, um einen sogenannten &lt;code&gt;API-Key&lt;/code&gt; zu erhalten. Nur mit diesem wird der JavaScript-Blob später auch korrekt funktionieren.&lt;/p&gt;&#10;&lt;p&gt;Und damit kommt das erste größere Problem: Snipcart schränkt meine Freiheit ein.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;preconnect&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;lt;https://app.snipcart.com&amp;gt;&amp;#34;&lt;/span&gt; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;preconnect&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;lt;https://cdn.snipcart.com&amp;gt;&amp;#34;&lt;/span&gt; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mir zeigt dies, dass Snipcart die Daten gern &lt;a href="https://snipcart.com/blog/add-shopping-cart-to-website" target="_blank" rel="noopener noreferrer" class="external-link"&gt;bei sich haben will&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Ich muss deren content delivery network für die Abwicklung des Bestellprozesses verwenden und bei mir einbinden. Es ist also mitnichten so, dass alle Verbindungen bei mir bleiben und ich den JS-Code von Snipcart &amp;ldquo;kaufe&amp;rdquo; und im Browser des Kunden bzw. eine dahinter liegende Datenbanklösung auf meinem Server betreibe.&lt;/p&gt;&#10;&lt;p&gt;Wenn einem also volle Autonomie und Kontrolle über die eigene Site nicht super wichtig ist, kann Snipcart eine sehr elegante Shopsystem-Lösung sein. Dies lohnt sich vor allem für bereits bestehende Seiten. Für mich jedoch ist sie damit raus.&lt;/p&gt;&#10;&lt;h3 id="opencart"&gt;OpenCart&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://www.opencart.com/index.php?route=common/home" target="_blank" rel="noopener noreferrer" class="external-link"&gt;OpenCart&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bietet eine &lt;code&gt;PHP&lt;/code&gt;-basiertes Shopmodul kostenlos und quelloffen an. Es kann sowohl als &amp;ldquo;Software as a Service&amp;rdquo; über Drittanbieter gebucht wie auch per &lt;a href="https://github.com/opencart/opencart/blob/master/INSTALL.md" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; heruntergeladen und auf eigenen Servern bereitgestellt werden.&lt;/p&gt;&#10;&lt;p&gt;Installation und Administration geschehen über eine Weboberfläche und ist meines Ermessens nach unkompliziert möglich. Auch im DockerHub findet man ein paar Images, mit welchen man direkt loslegen kann.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-05-15-opencart.avif" alt="Image: OpenCart demo, category overview" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;OpenCart ist vollständig an die eigenen Bedürfnisse anpassbar. Auch vorgefertigte Themes können eingebunden werden. Aus Neugier habe ich mal ein paar Webseiten angeschaut, die OpenCart einsetzen: Das Shopsystem ist aus meiner Sicht gut strukturiert und einfach zu bedienen. Jedoch ist die Ladegeschwindigkeit der von mir besuchten Beispielseiten nicht immer super schnell.&lt;/p&gt;&#10;&lt;p&gt;Im Gesamtpaket halte ich OpenCart mit seiner anpassbaren, leichtgewichtigen Shoplösung in &lt;code&gt;PHP&lt;/code&gt; auch durch die immer noch aktive Community (13+ Jahre!) für eine gute Lösung.&lt;/p&gt;&#10;&lt;h3 id="django"&gt;Django&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://www.djangoproject.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Django&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist ein Webframework für &lt;code&gt;Python&lt;/code&gt;. Es bietet viele Funktionen, ermöglicht Erweiterungen durch schlichtes Einbinden von Python-Modulen und man kann auch als Programmier-Einsteiger schnell funktionierende Lösungen erzeugen. Obwohl es eine Menge Bibliotheken beispielsweise für Websites, Shops &lt;a href="https://django-shop.readthedocs.io/en/latest/tutorial/intro.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;wie z.B. django-SHOP&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, Datenbankeinbindungen, Einbindung von Zahlsystemen usw. gibt, muss alles noch selbst zusammen gesteckt und konfiguriert werden.&lt;/p&gt;&#10;&lt;p&gt;Dadurch erhält man zwar volle Kontrolle und eine perfekt an die eigenen Bedürfnisse anpassbare Lösung - doch es gibt auch einen gewichtigen Nachteil: Lernzeit. Die Sicherheit der Datenbank muss selbst hergestellt werden, Validierung von User inputs muss selbst programmiert oder zumindest korrekt konfiguriert werden. Das Abspeichern von Kundenstammdaten passiert standardmäßig erst einmal im Klartext. Zugriffs- und Löschkonzepte müssen selbst erstellt und implementiert werden bzw. man sollte Zeit einplanen, passende Module ausfindig zu machen und zu integrieren.&lt;/p&gt;&#10;&lt;p&gt;Sollen Frameworks verwendet werden, empfiehlt es sich vorab eine aktive Wartung durch die Community zu prüfen. Für mich sieht es beispielsweise so aus, als wäre bei &lt;a href="https://github.com/awesto/django-shop" target="_blank" rel="noopener noreferrer" class="external-link"&gt;django-SHOP&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bereits seit dem Jahre 2021 keine große Bewegung mehr auf dem Github-Repo. Eine kurze Suche hingegen offenbart &lt;a href="https://github.com/topics/django-ecommerce" target="_blank" rel="noopener noreferrer" class="external-link"&gt;eine Menge Alternativen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; - hier gilt es nun, etwas Passendes ins Auge zu fassen oder gleich selbst das Rad neu zu erfinden 😃&lt;/p&gt;&#10;&lt;h2 id="die-alternative-für-anfänger-wie-mich-ohne-shopsystem"&gt;Die Alternative für Anfänger wie mich: ohne Shopsystem&lt;/h2&gt;&#10;&lt;p&gt;Was ist mit &amp;ldquo;Ohne Shopsystem&amp;rdquo; gemeint? Ein formularbasierter Ansatz. Hier schaut sich der User eine statische Website an, die Katalog- und Produktseiten enthält. Zusätzlich sind ein paar Elemente eingeblendet wie ein Dropdown-Menü für Variante und ein Feld für die Stückzahl. Daneben ein schlichter Button. Wird dieser geklickt, so öffnet sich ein Formular, welches automatisch die geklickten Inhalte überträgt und nach Kontaktdaten des Users fragt.&lt;/p&gt;&#10;&lt;p&gt;Wird alles ordnungsgemäß ausgefüllt, so erzeugt das Formular beim Absenden eine PGP-verschlüsselte Mail an mich. Ich kann dann die Order analog zu manuell erstellten Bestell-Mails sichten, eine Bestellbestätigung senden und die Zahlung anfordern.&lt;/p&gt;&#10;&lt;p&gt;So lässt sich mit wenigen Produkten bei wenigen Kunden ohne großen Aufwand ein Shop-Äquivalent datensparsam umsetzen. Natürlich nicht so bequem wie mit einer Ecommerce-Lösung. Aber für den Anfang reicht das vielleicht trotzdem.&lt;/p&gt;&#10;&lt;h2 id="im-nächsten-artikel"&gt;Im nächsten Artikel&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/ecommerce-selection/"&gt;Auswahl und Implementierung&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Webshop selber aufsetzen - Teil1</title><link>https://blog.schallbert.de/ecommerce-requirements/</link><pubDate>Tue, 15 Apr 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/ecommerce-requirements/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-04-15-ecommerce-pt1-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: text saying &amp;#39;&amp;#39;E-commerce Pt1: Requirements of a technophiliac&amp;#39;&amp;#39;"&#10; title="Webshop selber aufsetzen - Teil1" /&gt;&#10;&lt;p&gt;Dies ist Teil eins der Artikelserie über den Aufbau meines eigenen Webshops. Ich liste Anforderungen und Wünsche für meine Ecommerce-Lösung auf und grenze mich damit auf ein paar passende Software-Lösungen ein, die ich dann für meinen Webshop verwenden könnte.&lt;/p&gt;&#10;&lt;h2 id="was-mein-webshop-können-muss"&gt;Was mein Webshop können muss&lt;/h2&gt;&#10;&lt;p&gt;Für mich als Betreiber soll der Webshop möglichst schlank, transparent und automatisiert operieren. Ich möchte nur wenig Zeit mit Betrieb, Wartung und Pflege verbringen. Zahlungsabwicklung, Verwaltung des Bestellstatus, Kundenregistrierung, Versenden von Kundeninformation sowie eine große Anzahl Schritte im Versandprozess wie Frankieren und Einpflegen von Versandbestätigungen soll automatisiert stattfinden oder zumindest für die Automatisierung vorbereitet werden.&lt;/p&gt;&#10;&lt;p&gt;Aber auch für Kunden muss der Webshop verständlich aufgebaut sein: Simpel und mit wenigen Klicks von der Startseite bis zum Checkout, leicht zu bedienen, ähnlich zu anderen gängigen Webshops, mit unkomplizierter Zahlungsabwicklung. Dabei sollen so wenige Daten vom Kunden wie möglich erhoben werden.&lt;/p&gt;&#10;&lt;p&gt;Der Webshop muss nur wenige Produkte darstellen. Daher kann die Navigation sehr einfach gehalten werden. Durch die geringe Anzahl Produktkategorien kan möglicherweise komplett auf ein Baumsystem verzichtet werden.&lt;/p&gt;&#10;&lt;p&gt;Schauen wir uns mal schnell im Detail an, welche Funktionen der Webshop am Ende bereitstellen muss.&lt;/p&gt;&#10;&lt;h3 id="funktionen-für-admins"&gt;Funktionen für Admins&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Produkt anlegen&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Titel&lt;/li&gt;&#10;&lt;li&gt;Tags&lt;/li&gt;&#10;&lt;li&gt;Kurzbeschreibung&lt;/li&gt;&#10;&lt;li&gt;Beschreibung&lt;/li&gt;&#10;&lt;li&gt;Bilder&lt;/li&gt;&#10;&lt;li&gt;Videos&lt;/li&gt;&#10;&lt;li&gt;Preis&lt;/li&gt;&#10;&lt;li&gt;Steckbrief / Datenblatt / Anleitung&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ol start="2"&gt;&#10;&lt;li&gt;Produkt editieren&lt;/li&gt;&#10;&lt;li&gt;Produkt entfernen&lt;/li&gt;&#10;&lt;li&gt;Bestellungen verwalten&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schritt quittieren&lt;/li&gt;&#10;&lt;li&gt;Schritt erneut starten&lt;/li&gt;&#10;&lt;li&gt;Prozess stornieren&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ol start="5"&gt;&#10;&lt;li&gt;Kunden verwalten&#10;&lt;ul&gt;&#10;&lt;li&gt;anlegen (sandbox)&lt;/li&gt;&#10;&lt;li&gt;löschen (Datenschutz)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="funktionen-für-käufer"&gt;Funktionen für Käufer&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Webshop durchstöbern&lt;/li&gt;&#10;&lt;li&gt;Produkt(e) konfigurieren oder Produktvariante auswählen (z.B. Farbe, Qualität)&lt;/li&gt;&#10;&lt;li&gt;Produkt(e) in einen Warenkorb legen&lt;/li&gt;&#10;&lt;li&gt;Warenkorb verwalten&lt;/li&gt;&#10;&lt;li&gt;Registrierung vornehmen (optional, nur zur Kundendatenablage und Bestellverwaltung)&lt;/li&gt;&#10;&lt;li&gt;Versand wählen&lt;/li&gt;&#10;&lt;li&gt;Zahlung vornehmen&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="automatische-funktionen"&gt;Automatische Funktionen&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Zahlungsregistrierung&lt;/li&gt;&#10;&lt;li&gt;Rechnungserstellung und -Versand&lt;/li&gt;&#10;&lt;li&gt;Inventarverwaltung&lt;/li&gt;&#10;&lt;li&gt;Versandbenachrichtigung&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="nichtfunktionale-anforderungen"&gt;Nichtfunktionale Anforderungen&lt;/h2&gt;&#10;&lt;p&gt;Hier liste ich auf, was der Webshop &amp;ldquo;nebenbei&amp;rdquo; noch so alles können soll. Alles, was nicht unmittelbar zur Auftragsabwicklung benötigt oder für Kunden sichtbar wird.&lt;/p&gt;&#10;&lt;h3 id="usability-betreiberseite"&gt;Usability Betreiberseite&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Der Shop soll mit möglichst geringen Abhängigkeiten zu Software Dritter auskommen und nur diejenigen Module einbinden, die unbedingt benötigt werden. Beispiele:&#10;&lt;ul&gt;&#10;&lt;li&gt;Zahlungsabwicklung&lt;/li&gt;&#10;&lt;li&gt;Warenkorb&lt;/li&gt;&#10;&lt;li&gt;Kundenkonto / Adressdaten&lt;/li&gt;&#10;&lt;li&gt;Lagerbestände&lt;/li&gt;&#10;&lt;li&gt;Bestellabwicklung (Mailer) für Käufer&lt;/li&gt;&#10;&lt;li&gt;Rechnungsstellung.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Der Shop soll nur so viele dynamisch erzeugte Elemente enthalten wie unbedingt nötig.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="usability-käuferseite"&gt;Usability Käuferseite&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Der Webshop soll sich wie aktuelle übliche ECommerce-Lösungen anfühlen: Flink, übersichtlich, einfach.&lt;/li&gt;&#10;&lt;li&gt;Kein Zwang zum Kundenkonto.&lt;/li&gt;&#10;&lt;li&gt;Der Shop soll gängige Zahlungsmöglichkeiten unterstützen (Debit, Kredit, PayPal, Apple/GooglePay etc.). Dabei sind nur lokale (in Deutschland ansässige) Zahlungsdienstleister akzeptabel.&lt;/li&gt;&#10;&lt;li&gt;Für den User sollen Lagerbestände bzw. Fertigungszeiten transparent angezeigt werden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="datenschutz"&gt;Datenschutz&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Der Shop soll nur für die Geschäftsabwicklung notwendige Daten erfassen.&lt;/li&gt;&#10;&lt;li&gt;Falls technisch und rechtlich möglich soll der Shop ohne Cookie-Banner auskommen können.&lt;/li&gt;&#10;&lt;li&gt;Kundenkonten sollen automatisch nach 2 Jahren der Nicht-Nutzung (mit Ankündigung 2 Wochen vorher) gelöscht werden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="look--feel"&gt;Look &amp;amp; Feel&lt;/h3&gt;&#10;&lt;p&gt;Hier einige Beispiele für Shopdesigns:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://store.caddyserver.com/en-eur/collections/all" target="_blank" rel="noopener noreferrer" class="external-link"&gt;caddyshop&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://dev.to/contentful_blog/how-to-build-an-ecommerce-static-site-with-jekyll-contentful-and-commerce-layer-3c9" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Beispiel-ecommerce mit Jekyll&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.geeksforgeeks.org/e-commerce-website-using-django/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Beispiel-ecommerce mit Django&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Davon abgeleitete Anforderungen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Der Webshop soll von Aufbau und Farbgebung her an meine Blogseite erinnern und eine einheitliche Optik bieten.&lt;/li&gt;&#10;&lt;li&gt;Es sollen nur statische Fonts ohne Nachladen verwendet werden.&lt;/li&gt;&#10;&lt;li&gt;Produktbeschreibungen sollen als Markdown-Dokumente (oder Ähnlich) hochgeladen werden können.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="datenbank--oder-dateilösung"&gt;Datenbank- oder Dateilösung?&lt;/h2&gt;&#10;&lt;p&gt;Es muss nicht immer gleich eine Datenbank sein. Für viele Anwendungen werden nur Bruchteile der Funktionalität gängiger Datenbanklösungen benötigt, doch erhöht sich die Komplexität des Gesamtsystems erheblich. Sollte z.B. gleichzeitiges Schreiben selten oder nie vorkommen und keine starke Verkettung von Daten vorliegen, so kann eine Dateilösung einfacher, schneller und effizienter sein &lt;a href="https://engineeringkiosk.dev/podcast/episode/129-simplify-your-stack-files-statt-datenbanken/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Link zu einer passenden Engineering Kiosk Podcast-Episode&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Andererseits kommen viele Shopsysteme &amp;ldquo;with batteries included&amp;rdquo;, verfügen also schon von sich aus über eingebaute und für die Programmierer nahezu transparente Datenverwaltung.&lt;/p&gt;&#10;&lt;h3 id="dateilösung-statische-website"&gt;Dateilösung (statische Website)&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Auf Betreiberseite soll sich der Webshop so anfühlen wie ein Git-Projekt (everything is a file).&lt;/li&gt;&#10;&lt;li&gt;Änderungen im Angebot, Artikelbeschreibung, Preise etc. sollen per &lt;code&gt;git push&lt;/code&gt; auf den Server gespielt werden können - wie bei einer statischen Webseite.&lt;/li&gt;&#10;&lt;li&gt;Wird ein Pull Request nach &lt;code&gt;main&lt;/code&gt; gemerged, so soll der Webshop neu bauen (CD-Pipeline) und die Änderungen auf dem Server veröffentlichen.&lt;/li&gt;&#10;&lt;li&gt;Für Inventarverwaltung, Warenkorb, Kundeninteraktion und automatisierte Funktionen sind dynamische Elemente erforderlich. Hier gilt zu entscheiden, was direkt in Javascript und welche Elemente besser per Automatisierungspipeline gelöst werden sollen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="datenbanklösung-dynamische-website"&gt;Datenbanklösung (dynamische Website)&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Produkte werden in der Datenbank gehalten.&lt;/li&gt;&#10;&lt;li&gt;Die Verwaltung des Webshops geschieht per Web-Oberfläche.&lt;/li&gt;&#10;&lt;li&gt;Die Datenbankverwaltung soll teilautomatisiert erfolgen, dabei sind Backups und Verschlüsselung für Kundendaten vorzusehen.&lt;/li&gt;&#10;&lt;li&gt;Die Datenbank ist transparent für den Admin: Alle Einträge hier erfolgen über eine Web-Schnittstelle.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="im-nächsten-artikel"&gt;Im nächsten Artikel&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/ecommerce-alternatives/"&gt;Vergleich verschiedener Ecommerce-Lösungen&lt;/a&gt; für meinen Webshop&lt;/p&gt;&#10;</description></item><item><title>PeerTube im Fediverse</title><link>https://blog.schallbert.de/fediverse-ups-and-downs/</link><pubDate>Tue, 01 Apr 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/fediverse-ups-and-downs/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2025-03-17-makertube.jpg"&#10; class="post-cover"&#10; alt="Image: My MakerTube channel &amp;#39;&amp;#39;CNC corner&amp;#39;&amp;#39;"&#10; title="PeerTube im Fediverse" /&gt;&#10;&lt;h2 id="kurze-begriffsklärung"&gt;Kurze Begriffsklärung&lt;/h2&gt;&#10;&lt;h3 id="fediverse"&gt;Fediverse?&lt;/h3&gt;&#10;&lt;p&gt;Das &lt;a href="https://fediverse.info/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Fediverse nennt etwas Fediverse&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, wenn mehrere durchaus unterschiedliche Anbieter dasselbe Protokoll nutzen, um Nutzer dieser Anbieter frei miteinander interagieren zu lassen - und das in Bezug auf digitale Medien. Somit entsteht stark vernetzte aber dabei stets dezentrale Dienste, bei dem freie Anbieterwahl zum Konzept gehört. &lt;a href="https://www.theverge.com/24063290/fediverse-explained-activitypub-social-media-open-protocol" target="_blank" rel="noopener noreferrer" class="external-link"&gt;The Verge&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; geht hier einmal ins Detail und gibt einen Ausblick.&lt;/p&gt;&#10;&lt;h3 id="peertube"&gt;PeerTube?&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/PeerTube" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PeerTube&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist eine dezentrale Videoplattform, bei der sich mehrere Server die Last teilen und auch Zuschauer beim Verbreiten von Videos helfen können. Grob gesagt steht &lt;em&gt;PeerTube&lt;/em&gt; zu &lt;em&gt;YouTube&lt;/em&gt; wie &lt;em&gt;Mastodon&lt;/em&gt; zum Kurznachrichtendienst &lt;em&gt;X&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h2 id="der-anlass"&gt;Der Anlass&lt;/h2&gt;&#10;&lt;p&gt;Dieser Artikel beginnt mit einer E-Mail.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;You&amp;rsquo;re receiving this non-automated email because you&amp;rsquo;re one of the handful of people who has a video hosted on this PeerTube instance. I have decided to shut down this service [&amp;hellip;]&amp;rdquo; - Tom, &lt;em&gt;scitech.video&lt;/em&gt; administrator&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Es tue ihm wirklich leid, aber der Weiterbetrieb der Instanz koste zu viel Zeit, Wartungsaufwand und Geld. Zudem sei die Plattform im Moment nur wenig genutzt, sodass der Aufwand unverhältnismäßig erscheine.&lt;/p&gt;&#10;&lt;p&gt;So weit, so verständlich.&lt;/p&gt;&#10;&lt;h2 id="toms-geschichte-scitechvideo"&gt;Toms Geschichte: &lt;em&gt;scitech.video&lt;/em&gt;&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-04-01-scitechvideo-stats.avif" alt="Image: scitech.video statistics as of Mid-March 2025: ~16k views total" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Da ich dort allerdings alle meine Videos hochgeladen und verlinkt hatte, war ich sehr an den Hintergründen interessiert. Also hakte ich nach. Meine Nachfrage wurde ausführlich und prompt beantwortet.&lt;/p&gt;&#10;&lt;h3 id="die-anfänge"&gt;Die Anfänge&lt;/h3&gt;&#10;&lt;p&gt;Tom setzte seine PeerTube-Instanz im Jahr 2019 auf. Ursprünglich, um eigene Inhalte zu teilen. Bereits wenig später öffnete er das Hosting-Angebot, wenn auch mit leichtem Unwohlsein, für andere.&lt;/p&gt;&#10;&lt;p&gt;Aus der Admin-Perspektive wirke so eine Videoplattform wie der &amp;ldquo;wilde Westen&amp;rdquo;. Seinem Angebot ein gesundes Wachstum angedeihen zu lassen, stellte sich als recht schwierig heraus - vor allem, weil viele Bots und Werbetreibende unterwegs sind.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Growing the service turned out to be challenging. Every time I opened registrations spambots started creating accounts easily and uploading videos with advertisements in the video descriptions [&amp;hellip;]&amp;rdquo; - Tom, &lt;em&gt;scitech.video&lt;/em&gt; administrator, via E-Mail&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="viele-bots-und-unerwünschte-user-unterwegs"&gt;Viele Bots und unerwünschte User unterwegs&lt;/h3&gt;&#10;&lt;p&gt;Diese unerwünschten Spammer, Scammer, Advertiser konnte er zwar stets davon abhalten, &lt;em&gt;scitech.video&lt;/em&gt; zu verseuchen. Dennoch bedeutete dies ständige Arbeit und manuelle Überwachung der automatischen Tools zum Ablehnen und Moderieren.&lt;/p&gt;&#10;&lt;p&gt;An dieser Stelle möchte ich Tom beipflichten. Ich habe im Schnitt etwa &lt;em&gt;340&lt;/em&gt; fehlgeschlagene und dann von mir weggeblockte Verbindungen externer IP-Adressen auf meinen Server pro Tag. Allein auf dem &lt;code&gt;ssh-Port&lt;/code&gt;. Und mein Server ist weder besonders groß, bekannt oder kann als lohnenswertes Ziel durchgehen. Ganz zu schweigen von zahllosen Versuchen, auf meine Gitea-Nutzeroberfläche zu kommen, die Website nach verborgenen Links zu crawlen und vieles mehr.&lt;/p&gt;&#10;&lt;p&gt;Für Betreiber eines Portals, welches auch externen Usern Schreibzugriff gibt (Videos, Beschreibungen, Tags, Kommentare), potenziert sich das Problem natürlich nochmal. Hier gehört anders als bei meiner Seite ja zum Konzept, sich zu beteiligen.&lt;/p&gt;&#10;&lt;h3 id="copyright"&gt;Copyright?&lt;/h3&gt;&#10;&lt;p&gt;Genau, grundsätzlich fällt aus Sicht des Plattformbetreibers erst einmal jedes Video unter das Urheberrecht oder verwandte Schutzrechte. Aber ist der Nutzer, der gerade dieses Video veröffentlichen will auch der Urheber? Oder hat dieser Jemand Nutzungsrechte erworben?&lt;/p&gt;&#10;&lt;p&gt;Je nach Land und zugehöriger Rechtslage müssen sich Admins mit diesem Thema sehr genau auseinandersetzen. Es können hohe Strafen sowohl für die Betreiber als auch für die User fällig werden, sollte eine Urheberrechtsverletzung vorliegen.&lt;/p&gt;&#10;&lt;p&gt;Ersteres ist zumindest in Deutschland einem &lt;a href="https://datenbank.nwb.de/Dokument/1055605/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Urteil des Bundesgerichtshofes vom 23.10.2024&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, wie folgt aufzulösen: &amp;ldquo;[&amp;hellip; sind] nach einem klaren Hinweis auf eine Rechtsverletzung die dort eingestellten Angebote im Rahmen des technisch und wirtschaftlich Zumutbaren auf gleichartige Verletzungen zu überprüfen und rechtsverletzende Inhalte zu sperren oder zu löschen&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Durch die starke Fokussierung der Instanzen auf bestimmte Themenbereiche fallen Verletzungen jedoch meist schnell auf - denn auch User können Beiträge &amp;ldquo;reporten&amp;rdquo;.&lt;/p&gt;&#10;&lt;h3 id="die-rechtslage-für-soziale-netzwerke"&gt;Die Rechtslage für soziale Netzwerke&lt;/h3&gt;&#10;&lt;p&gt;Einige Länder bereiten im Moment Gesetze vor, die Kinder und Jugendliche vor der Nutzung sozialer Netzwerke und damit verbundenen Gefahren schützen soll. Als Referenz hier ein &lt;a href="https://www.bzkj.de/bzkj/service/alle-meldungen/bzkj-schutzmassnahmen-und-staerkere-medienbildung-fuer-kinder-und-jugendliche-257566" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Link zur Bundeszentrale für Jugend- und Medienschutz&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Auch hier sind die Admins gefragt, diese Regeln umzusetzen - doch wie?&lt;/p&gt;&#10;&lt;p&gt;Dies jedenfalls fragt sich Tom angesichts der zunehmend vielfältiger werdenden Rechtsauffassungen unterschiedlicher Länder.&lt;/p&gt;&#10;&lt;h3 id="updates-und-wartung"&gt;Updates und Wartung&lt;/h3&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;PeerTube upgrades were a bit of a drag because everything was tightly coupled with their specific nginx configuration, with breaking changes that had to be adjusted every couple of minor versions.&amp;rdquo; - Tom, &lt;em&gt;scitech.video&lt;/em&gt; administrator, via E-Mail&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Die Arbeiten seien nicht besonders schwierig auszuführen, dennoch kosten sie eine Menge Zeit. Wie bei anderer Software auch könne es passieren, dass ein PeerTube-Server zum Beispiel nach Betriebssystemupdates sich nicht mehr hochfahren lässt oder es zu Inkompatibilitäten verschiedener Softwarestände oder Modulen untereinander kommt.&lt;/p&gt;&#10;&lt;p&gt;Aus Leistungsgründen stellt sich das Testen als schwierig heraus, sodass oft direkt in &amp;ldquo;Prod&amp;rdquo; gearbeitet wird. Im Fehlerfalle leidet der Livebetrieb.&lt;/p&gt;&#10;&lt;p&gt;Die Konfiguration sei relativ aufwändig und müsse auf die Hardware angepasst werden. Größere PeerTube-Updates erfordern auch hier Änderungen.&lt;/p&gt;&#10;&lt;h3 id="fazit"&gt;Fazit&lt;/h3&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;I&amp;rsquo;m also disillusioned about the fediverse generally as a communication medium. A constant battle against spam/LLMs, so much anger, so much focus on follows, likes and boosts.&amp;rdquo; - Tom, &lt;em&gt;scitech.video&lt;/em&gt; administrator, via E-Mail&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Tom ist frustriert, dass auch hier die Aufmerksamkeitsökonomie durchschlägt. Obwohl werbefrei gedacht und als kleine Graswurzelbewegung ausgerollt, werden aus Idealismus heraus erstellte Instanzen schnell für Werbung und Selbstdarstellung missbraucht. Die Kosten beliefen sich zudem auf mehrere hundert Dollar im Jahr, was die Kosten-Nutzen-Bilanz weiter verschlechtere.&lt;/p&gt;&#10;&lt;p&gt;Eine Lösung könne hier &amp;ldquo;die Rückkehr zu Web1.0 oder geschlossene Gemeinschaften&amp;rdquo; darstellen, wie es sie zum Beispiel bei &lt;em&gt;Meatspace&lt;/em&gt;, &lt;em&gt;Matrix&lt;/em&gt;, &lt;em&gt;Discord&lt;/em&gt; etc. bereits gebe.&lt;/p&gt;&#10;&lt;p&gt;Auch alle anderen Faktoren machen Tom den Weiterbetrieb seiner Instanz schwer: Die Nutzerbasis ist nie besonders groß geworden und es gibt nur wenige Menschen, die ihre Inhalte hier noch hochladen. Als Vater hat er nun viel weniger Zeit zur Verfügung und muss Projekte priorisieren. Somit beendet er nun seine immerhin sechs Jahre betriebene Reise als Anbieter im Fediverse.&lt;/p&gt;&#10;&lt;p&gt;Ich wünsche Tom für die Zukunft weiterhin viel Kraft, Idealismus und Enthusiasmus.&lt;/p&gt;&#10;&lt;h2 id="jans-geschichte-makertubenet"&gt;Jans Geschichte: &lt;em&gt;makertube.net&lt;/em&gt;&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-04-01-makertube-stats.avif" alt="Image: MakerTube statistics as of Mid March-2025: ~120k views total" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Da ich nun nach einer neuen Hosting-Plattform für meine Videos Ausschau halten musste, bemühte ich die Suche für &amp;ldquo;video-maker&amp;rdquo; auf der &lt;a href="https://joinpeertube.org/instances?profile=video-maker" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PeerTube-Website&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Da meine Inhalte einen klaren Tech-Fokus haben und von Forschung, Produktentwicklung bis in den Vorserienbau reichen, war es nicht schwer, schnell passende Kandidaten ausfindig zu machen.&lt;/p&gt;&#10;&lt;p&gt;So kam ich dann zu meinem &lt;a href="https://makertube.net/c/cnc_corner/videos" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Kanal auf MakerTube&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Nach erfolgreicher Anmeldung schrieb ich eine Mail an die Admins mit ähnlichen Fragen, die ich Tom bereits gestellt hatte.&lt;/p&gt;&#10;&lt;h3 id="motivation---warum-makertube"&gt;Motivation - warum MakerTube?&lt;/h3&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Ich habe einen kleinen Youtube-Kanal und die permanente Werbung und der Fokus auf die großen Kanäle, Klickbait etc. und wie die Leute gezwungen werden sich der Content-Maschine anzupassen ging mir auf die Nerven. [&amp;hellip;]&amp;rdquo; - Jan, &lt;em&gt;MakerTube&lt;/em&gt; administrator, via E-Mail&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Jan war bereits im Fediverse unterwegs und beschäftigt sich auch beruflich mit Webdiensten, die stark skalieren müssen. Daher erschien es für ihn logisch, selbst eine Instanz aufzusetzen - dann aber natürlich mit den wirklich interessanten Themen dieser Welt. Es sei ja nicht als Konkurrenz zu YouTube gedacht und fokussiere sich daher auf Maker, Musikerinnen, Künstler etc.&lt;/p&gt;&#10;&lt;h3 id="ist-der-einstieg-leicht"&gt;Ist der Einstieg leicht?&lt;/h3&gt;&#10;&lt;p&gt;Es bedeute schon einiges an Aufwand, so eine Instanz hochzuziehen und performant zu halten. &lt;a href="https://framasoft.org/en/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Framasoft&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, welche die PeerTube-Software entwickeln, machten einen Superjob und unterstützen die Hoster-Community wo sie können.&lt;/p&gt;&#10;&lt;p&gt;Ich persönlich kann bestätigen, dass sich PeerTube in den neueren Versionen ganz anders anfühlt als noch vor ein paar Jahren und sich mit praktischen Features wie automatisch generierten Untertiteln immer näher an den Funktionsumfang von &lt;em&gt;YouTube&lt;/em&gt; heranrobbt.&lt;/p&gt;&#10;&lt;p&gt;Das Fediverse, so Jan, sei nach wie vor eine Nische. Daher falle das Wachstum seiner Instanz eher moderat und sehr organisch aus. &lt;em&gt;PeerTube&lt;/em&gt; sei im Moment noch etwas für enthusiastische Videomacher; am normalen Publikum laufen die Inhalte größtenteils ungesehen vorbei.&lt;/p&gt;&#10;&lt;h3 id="wie-groß-ist-der-zeitaufwand"&gt;Wie groß ist der Zeitaufwand?&lt;/h3&gt;&#10;&lt;p&gt;Jan legte den Fokus auf niedrige laufende Kosten bei gleichzeitiger Skalierbarkeit, falls die Sache plötzlich doch groß werden sollte. Jetzt, da das System reibungsfrei läuft, liege der Aufwand bei ein paar Stunden pro Woche für Moderation von Usern, Inhalten und Support.&lt;/p&gt;&#10;&lt;p&gt;Ausnahmen: Größere Updates, Upgrades und technische Probleme.&lt;/p&gt;&#10;&lt;h3 id="was-sagen-die-leute"&gt;Was sagen die Leute?&lt;/h3&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Es gibt positive Rückmeldungen und die Leute sind dankbar, dass ich die Seite betreibe und ich mir die Arbeit mache, aber ich würde sagen nicht sehr viele.&amp;rdquo; - Jan, &lt;em&gt;MakerTube&lt;/em&gt; administrator, via E-Mail&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Aber natürlich gebe es auch immer mal wieder Entrüstung darüber, dass das halt nicht YouTube wäre und nicht jeder sofort einen Account aufmachen könne.&lt;/p&gt;&#10;&lt;h3 id="wie-erkennt-ihr-urheberrechtsverletzungen"&gt;Wie erkennt ihr Urheberrechtsverletzungen?&lt;/h3&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Bezüglich Copyright gibt es da keine direkten Hilfsmittel. Es gibt die Report-Funktion an den Videos, um auf Verstöße gegen Copyright oder Instanzregeln hinzuweisen. Das wird auch von Benutzern genutzt.&amp;rdquo; - Jan, &lt;em&gt;MakerTube&lt;/em&gt; administrator, via E-Mail&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Verantwortung und Haftung lägen bei so kleinen Anbietern wie &lt;em&gt;MakerTube&lt;/em&gt; in erster Linie beim User. Wenn allerdings auf Verstöße hingewiesen wird, müssen die Admins tätig werden und Inhalte löschen. Auf &lt;em&gt;MakerTube&lt;/em&gt; sei dies allerdings im Alltag kein Problem: Die Registrierungen neuer User wird genau geprüft und im Zweifel werde nachgefragt, um welche Inhalte es gehe. Bei den meisten Verletzungen gehe es um Hintergrundmusik der Videos, da gebe es scheinbar weniger Bewusstsein für Urheberrecht wie bei Uploads von Videokopien.&lt;/p&gt;&#10;&lt;h3 id="und-wie-finanziert-sich-das-ganze"&gt;Und wie finanziert sich das Ganze?&lt;/h3&gt;&#10;&lt;p&gt;MakerTube finanziert sich über &lt;a href="https://makertube.net/about/instance/support" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Spenden&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Gerade zu Anfang ging laut Jan viel Zeit dafür drauf, dies den Leuten klar zu machen und genügend Budget für einen flüssigen Betrieb zu erhalten. Eine Videoplattform sei ressourcenintensiv: Viel CPU-Power für Transkodierung und Streaming, einen Haufen Plattenplatz für die ganzen Assets bei gleichzeitig hohen Anforderungen an die Bandbreite, gerade in &amp;ldquo;Stoßzeiten&amp;rdquo;.&lt;/p&gt;&#10;&lt;h3 id="fazit-1"&gt;Fazit&lt;/h3&gt;&#10;&lt;p&gt;Ich bin mit &lt;em&gt;MakerTube&lt;/em&gt; bis jetzt zufrieden: Transkodierungen gehen flott, Videos laufen flüssig. Sogar der &lt;em&gt;Heise-Verlag&lt;/em&gt; hat nun ein erstes Video auf die Instanz hochgeladen. Es ist allerdings deutlich schwieriger, ohne &amp;ldquo;Fanbase&amp;rdquo; auf die Landing Page zu kommen, was bei &lt;em&gt;scitech.video&lt;/em&gt; für mich nie ein Problem darstellte 😅.&lt;/p&gt;&#10;&lt;p&gt;Lasst mich mit einem weiteren Zitat von der Site selbst schließen:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;MakerTube is here to stay.&amp;rdquo; - &lt;em&gt;MakerTube&lt;/em&gt;, March 2025, about &lt;em&gt;MakerTube&lt;/em&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Danke, Jan. Auf die Zukunft!&lt;/p&gt;&#10;</description></item><item><title>MakerTube</title><link>https://blog.schallbert.de/announcements/2025-03-17-moving-to-makertube/</link><pubDate>Mon, 17 Mar 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/announcements/2025-03-17-moving-to-makertube/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2025-03-17-makertube.jpg"&#10; class="post-cover"&#10; alt="Image: my channel on MakerTube called &amp;#39;CNC corner&amp;#39;."&#10; title="MakerTube" /&gt;&#10;&lt;p&gt;Ich bekam eine Mail mit dem Hinweis, dass die von mir genutzte PeerTube-Instanz &lt;code&gt;scitech.video&lt;/code&gt; Mitte April 2025 ihren Betrieb einstellen würde. Als Begründung nannte der Host unter anderem den hohen Zeit- und Kostenaufwand für den Betrieb sowie die geringe Anzahl Nutzer. Dazu gibt es inzwischen sogar einen eigenen Artikel, der sich mit den &lt;a href="https://blog.schallbert.de/fediverse-ups-and-downs/"&gt;Vor- und Nachteilen als Betreiber im Fediverse&lt;/a&gt; beschäftigt.&lt;/p&gt;&#10;&lt;p&gt;Als Nachfolgeportal für meine Videos habe ich &lt;a href="https://makertube.net/c/cnc_corner/videos" target="_blank" rel="noopener noreferrer" class="external-link"&gt;MakerTube&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ausgewählt. Dorthin migriere ich nun sämtliches Material. Stück für Stück in den kommenden Wochen.&lt;/p&gt;&#10;&lt;p&gt;MakerTube wird in Deutschland über Hetzner gehostet und hat viel, viel mehr Rechenpower als scitech. Daher laufen die Videos deutlich reibungsloser als vorher.&lt;/p&gt;&#10;&lt;p&gt;Abschließend werde ich alle Videolinks auf dieser Site umbiegen müssen, damit sie auch weiterhin funktionieren.&lt;/p&gt;&#10;&lt;p&gt;Für Nutzende von &lt;code&gt;schallbert.de&lt;/code&gt; ändert sich nichts 😀&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Schallbert&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Gitea: Suchmaschinenindexierung stoppen</title><link>https://blog.schallbert.de/gitea-search-indexation/</link><pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/gitea-search-indexation/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-03-12-gitea-indexation-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: How gitea recommends to remove search indexation"&#10; title="Gitea: Suchmaschinenindexierung stoppen" /&gt;&#10;&lt;p&gt;Nach ewigen Zeiten habe ich nur zum Spaß mal wieder einen Ausflug auf die Google Search Console gemacht. Normalerweise interessiert die mich überhaupt nicht, weil ich selbst Google gar nicht nutze und davon ausging, dass die Suchmaschinen ihre Arbeit aus Eigeninteresse schon gut machen würden.&lt;/p&gt;&#10;&lt;p&gt;Ich war etwas irritiert, als ich feststellte, dass es das Gros meiner Seiten nicht mal durch den Indexer geschafft hat.&lt;/p&gt;&#10;&lt;h2 id="das-problem"&gt;Das Problem&lt;/h2&gt;&#10;&lt;p&gt;Wie sollen denn Leute meine Webpräsenz finden? Selbst wenn sie die korrekten Suchbegriffe wüssten, würden sie über Google kaum etwas angezeigt bekommen. Immerhin stand klar drin, dass keiner meiner Blogartikel im Indexer vorkam. Auf meiner Gitea-Instanz hingegen waren mehrere tausend Seiten dem Indexer bekannt. Ein klares Missverhältnis. Wieso überhaupt erzeugt Gitea ein so hohes Dateiaufkommen?&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-03-12-searchconsole.avif" alt="Image: Google Search console view for git.schallbert.de. Thousands of pages are crawled that a real user will never be interested in." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Googles &lt;a href="https://en.wikipedia.org/wiki/Web_crawler" target="_blank" rel="noopener noreferrer" class="external-link"&gt;crawler&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (ein Programm, welches das Netz nach Inhalten durchsucht und für die Suche indexierbar macht) findet anscheinend jeden noch so kleinen Commit in öffentlichen Repositories sowie die dahinter liegenden Dateien. Auch Automationsläufe und andere Metadaten werden indexiert.&lt;/p&gt;&#10;&lt;p&gt;Das ist natürlich vollkommen unnötig und verbraucht mir an einer Stelle Energie, die ich viel lieber in der Präsentation meines Blogs hätte. Also muss eine Lösung her.&lt;/p&gt;&#10;&lt;h2 id="lösungsansätze"&gt;Lösungsansätze&lt;/h2&gt;&#10;&lt;p&gt;Mir fielen direkt ein paar Möglichkeiten ein:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Den Crawler für jedes Repository so konfigurieren, dass nur die oberste Ordnerebene durchsucht wird.&lt;/li&gt;&#10;&lt;li&gt;Suchindexierung für &lt;code&gt;git.schallbert.de&lt;/code&gt; komplett abschalten.&lt;/li&gt;&#10;&lt;li&gt;Per Search Console so lange Korrekturen fahren, bis der Indexer auf meinen Blogbeiträgen korrekte Zuordnungen anzeigt.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Die naheliegendste Lösung für mich war, den Indexer für meine Gitea-Instanz komplett abzuschalten. Korrekturschleifen greifen bei Googles Crawler erst Tage oder Wochen später und waren mir zu langwierig. Eine eigene &lt;code&gt;robots.txt&lt;/code&gt; für jedes Repository hielt ich für zu aufwändig.&lt;/p&gt;&#10;&lt;h2 id="meine-umsetzung"&gt;Meine Umsetzung&lt;/h2&gt;&#10;&lt;p&gt;Hierfür nahm ich die &lt;a href="https://docs.gitea.com/administration/search-engines-indexation" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Gitea-Dokumentation&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Hand. Der Vorschlag mit einer &lt;code&gt;robots.txt&lt;/code&gt; leuchtete mir sofort ein, ebenso ihr Inhalt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;User-agent&lt;/span&gt;: *&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Disallow&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hiermit wird Crawlern von jedem Anbieter &lt;code&gt;*&lt;/code&gt; gesagt, dass ab dem Stammverzeichnis &lt;code&gt;/&lt;/code&gt; überhaupt nicht indexiert werden soll.&lt;/p&gt;&#10;&lt;p&gt;Leider nur war mir überhaupt nicht klar, wie Gitea diese Datei in seiner Instanz bereitstellt:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;To make Gitea serve a custom robots.txt (default: empty 404) for top level installations, create a file with path &lt;code&gt;public/robots.txt&lt;/code&gt; in the &lt;a href="https://docs.gitea.com/administration/customizing-gitea" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;custom&lt;/code&gt; folder or &lt;code&gt;CustomPath&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="giteas-konfigurationsverzeichnis"&gt;Giteas Konfigurationsverzeichnis&lt;/h3&gt;&#10;&lt;p&gt;Also probierte ich ein Wenig herum. Erstellte mal einen &lt;code&gt;public&lt;/code&gt;-Ordner mit entsprechender Datei, dann einen &lt;code&gt;custom&lt;/code&gt;-Ordner auf verschiedenen Ebenen innerhalb der Gitea-Ordnerstruktur. Jedes mal startete ich den Container neu und schaute nach, ob die robots-Datei auch auf dem Server erschien.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-03-12-whererobots.avif" alt="Image: Where I put robots.txt in Gitea&amp;#39;s folder tree" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Nichts.&lt;/p&gt;&#10;&lt;p&gt;Doch dann löste ich mich von den Ordnernamen und packte die Datei schlicht in das Konfigurationsverzeichnis von Gitea - die niedrigste Ebene, auf der &lt;em&gt;gitea&lt;/em&gt; als Ordnername schon gar nicht mehr vorkommt. Und dies war die Lösung.&lt;/p&gt;&#10;&lt;h3 id="test"&gt;Test&lt;/h3&gt;&#10;&lt;p&gt;Nach Neustart konnte ich die Datei im Browser erfolgreich anzeigen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-03-12-robotsolution.avif" alt="Image: Browser successfully loaded my robots.txt for the Gitea instance" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Inzwischen beginnt auch Google, die unerwünschten Seiten aus dem Indexer zu entfernen. Meine Blogartikel werden über die nächsten Wochen hoffentlich auch in der Google-Suche verfügbar.&lt;/p&gt;&#10;</description></item><item><title>Graustufenbilder auf Dibond</title><link>https://blog.schallbert.de/halftone-image-on-dibond/</link><pubDate>Tue, 25 Feb 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/halftone-image-on-dibond/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-02-25-bloglogo-stipple-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: halftone image engraved with my CNC"&#10; title="Graustufenbilder auf Dibond" /&gt;&#10;&lt;p&gt;Im letzten Artikel hatte ich Dibond (Aluminium-Sandwichplatte) als Material zum Zerspanen wiederentdeckt. Ich setze es aus Umweltgründen - Verbundmaterialien sind nichts, was ich für besonders nachhaltig oder recyclebar halte - immer seltener ein. Dennoch üben die wunderschönen Oberflächen immer wieder eine gewisse Anziehungskraft auf mich aus. Nun denn - zur Sache!&lt;/p&gt;&#10;&lt;h2 id="bloglogo-als-punktwolke"&gt;Bloglogo als Punktwolke&lt;/h2&gt;&#10;&lt;p&gt;Inzwischen war ich in der Lage, ein von Stipplegen erzeugtes Bild mal auf die Fräse zu werfen. Dafür musste ich ein im Vorgängerartikel &lt;a href="https://blog.schallbert.de/halftone-cnc/"&gt;Graustufenbilder mit der CNC fräsen&lt;/a&gt; noch vorhandenes Problem lösen: In meinem meistgenutzten CAM-Werkzeug nämlich können Fräspfade für punktebasierte Halbtonbilder kaum erstellt werden, da jeder der über 2200 Punkte einzeln mit einer Fräsoperation versehen werden müsste.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;CADasCAM&lt;/em&gt; hingegen ermöglicht es, ganze Gruppen von Elementen ein- und derselben Fräsaktion hinzuzufügen. Nun fragte ich mich, mit welchem Fräser ich in Dibond am besten arbeiten solle, denn ich habe ja nur eine sehr dünne Deckschicht zur Verfügung.&lt;/p&gt;&#10;&lt;h3 id="simulation-radienfräser"&gt;Simulation Radienfräser&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-02-25-r2-ballnose-approximation.jpg" alt="Image: my drawing for V-cut to radius cutter approximation" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich entschied mich für einen Radienfräser mit Halbrundkopf. Für diesen hat das CAM allerdings keine Berechnungsvorschrift, sodass er sich nicht ohne Weiteres auswählen lässt. Nach Rücksprache mit dem Hersteller kam die Idee auf, den Radienfräser durch einen V-cut mit Abflachung an der Spitze für das CAM zu simulieren, tatsächlich auf der Fräse aber den Kugelkopf einzusetzen.&lt;/p&gt;&#10;&lt;p&gt;Um hier passende Werte zu erhalten, fertigte ich ein paar Zeichnungen an. In der Tabelle unten sind Näherungswerte festgehalten für den Falle, dass die Eintauchtiefe &lt;code&gt;0.3mm&lt;/code&gt; nicht überschreitet. Nun konnte ich die Pfadberechnung im CAM durchführen und sogar eine Tiefenbegrenzung von &lt;code&gt;0.22mm&lt;/code&gt; einhalten. Die flachsten Bereiche haben eine Gravurtiefe von lediglich &lt;code&gt;0.10mm&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Bei der Pfadberechnung ist zu beachten, dass die Rückzugshöhen möglichst gering ausfallen sollten. Dann muss die Z-Achse der Maschinen nicht für jeden Punkt so weit hochfahren. Ich halte einen Wert von &lt;code&gt;0.5mm&lt;/code&gt; für angemessen.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Radienfräser&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Simulation durch V-Cut&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;r=3mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;140°, 1.0mm Abflachung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;r=2mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;140°, 0.5mm Abflachung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="fräsvorgang"&gt;Fräsvorgang&lt;/h3&gt;&#10;&lt;p&gt;Die Motivgröße für die Gravur beträgt &lt;code&gt;180x180mm&lt;/code&gt; und ich fräse mit folgenden Werten: &lt;code&gt;Radienfräser 2s r3 6mm, S20000, F1800&lt;/code&gt;. Ich durchsteche die Schutzfolie der Dibondplatte, deren Dicke ich nicht kompensiere. Ich verwende eine geringe Menge Schmiermittel zur Verbesserung der Schnittqualität. Nach knapp &lt;code&gt;11&lt;/code&gt; Minuten ist der Fräsvorgang beendet.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-02-25-r2-bloglogo-stipple.avif" alt="Image: Bloglogo as halftone dot image in Dibond, black" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="beobachtungen"&gt;Beobachtungen&lt;/h3&gt;&#10;&lt;p&gt;Das Motiv ist in diesem Versuch schon einmal viel deutlicher zu erkennen. Leider habe ich dennoch klar ein Problem mit der Oberflächenebenheit. So ist im mittleren Bereich rechts beispielsweise zu tief graviert (Aufbauschneide - siehe Video - dort ist die Fräse zum Schluss unterwegs), während die Graviertiefe ganz oben rechts und oben mittig nicht ausreichend erscheint.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Fräse und Fräser haben überhaupt kein Problem mit dem Material. Der Gravurvorgang klingt ein wenig wie die Arbeit mit der Ständerbohrmaschine, unterbrochen von Positioniergeräuschen der Schrittmotoren.&lt;/li&gt;&#10;&lt;li&gt;Die für Dibond verwendete Aluminiumsorte &lt;em&gt;AlMg1&lt;/em&gt; ist weich und zäh. Sie eignet sich nicht gut zum Zerspanen.&lt;/li&gt;&#10;&lt;li&gt;Fräser tendieren hier zum Erzeugen von &lt;a href="https://en.wikipedia.org/wiki/Built_up_edge" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Aufbauschneiden&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, welche den Fräserradius vergrößern und die Schnittqualität verschlechtern.&lt;/li&gt;&#10;&lt;li&gt;Die Schutzfolie verhindert Ausrisse und Spanränder.&lt;/li&gt;&#10;&lt;li&gt;Aufbringen von etwas Schmiermittel ist eine gute Idee.&lt;/li&gt;&#10;&lt;li&gt;Selbst auf einem Vakuumtisch ruiniert jedes Staubkorn unter dem Werkstück die Fräsarbeit. Wenige hundertstel Millimeter Höhendifferenz haben große Auswirkung auf die Punktgröße und führen zu Verfälschungen.&lt;/li&gt;&#10;&lt;li&gt;Das Maschinenbett muss absolut eben sein oder die Maschine muss über eine hochwertige, aktive Höhenkompensation verfügen; z.B. mittels vorheriger Oberflächenvermessung durch einen &lt;a href="https://blog.schallbert.de/touching-off/"&gt;Kantenfinder&lt;/a&gt;. Meine Steuersoftware unterstützt das Verfahren sogar unter dem Namen &lt;em&gt;ZheightComp&lt;/em&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="fazit"&gt;Fazit&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Ich mag Dibond als Material sehr, aber es scheint meine Zuneigung nicht zu erwidern.&lt;/li&gt;&#10;&lt;li&gt;Mit meinen technischen Mitteln bekomme ich Gravuren nicht mit wiederholbar in guter Qualität hin.&lt;/li&gt;&#10;&lt;li&gt;Vielleicht sollte ich stattdessen Plastik oder Holz verarbeiten. Oder einen Laser verwenden&amp;hellip;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Zum Abschluss noch ein kurzes Zeitraffer-Video des Fräsvorganges.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/qTd6Ad2QStCp5AiWmrHZem" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Graustufenbilder mit der CNC herstellen - Teil2&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="saturn-als-ascii-art"&gt;Saturn als ASCII-Art&lt;/h2&gt;&#10;&lt;p&gt;Halbtonbilder kann man statt mit Linien, Wellen, Punkten natürlich auch mit Mustern herstellen. Als Beispiel habe ich mal das Bild des Planeten Saturn von &lt;a href="https://asciiart.website/index.php?art=nature/astronomy" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Christopher Johnsons ASCII Art&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für die Fräse aufbereitet.&lt;/p&gt;&#10;&lt;h3 id="cadcam"&gt;CAD/CAM&lt;/h3&gt;&#10;&lt;p&gt;Für das richtige Größenverhältnis wird eine Schriftart benötigt, bei der jedes Zeichen dieselbe Setzbreite besitzt. Ich wähle die TrueType-Schriftart &lt;em&gt;Courier New&lt;/em&gt; und passe Schriftgröße und Zeilenabstand so an, dass das Motiv auf mein Material passt und die Gravur nicht die Deckschicht perforiert.&lt;/p&gt;&#10;&lt;p&gt;Dies zwingt mich zur Verwendung eines &lt;code&gt;1/8&amp;quot; Gravurstichel mit 36° Spitzenwinkel und 0.1mm Abflachung&lt;/code&gt;. Ich hätte hier einen Radienfräser bevorzugt: Im weichen Dibond kommt es bei spitzwinkligen Sticheln zu Materialaufwurf und -Verschmierung. Dies führt zu unsauberen Linien und einer rauen Oberfläche. Da das Werkstück sehr klein ist, würde die Schrift jedoch viel zu breit ausfallen.&lt;/p&gt;&#10;&lt;p&gt;Die Pfadberechnung zeigt, dass ich auf dem nur &lt;code&gt;80x80mm&lt;/code&gt; großen Werkstück Fettdruck wählen muss, um meine Mindesttiefe von &lt;code&gt;0.08mm&lt;/code&gt; überhaupt zu erreichen.&lt;/p&gt;&#10;&lt;h3 id="fertigung"&gt;Fertigung&lt;/h3&gt;&#10;&lt;p&gt;Ich habe das Bild vom Saturn zweimal gefertigt: Auf &lt;code&gt;80x80mm&lt;/code&gt;, um die maximale Auflösung im Material zu testen und auf &lt;code&gt;180x180mm&lt;/code&gt; (Abbildung unten) zum Prüfen der Kontrastwirkung.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-02-25-halftone-saturn.avif" alt="Image: Halftone image of Saturn from my CNC machine. Image rights: asciiart.website" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich bin zufrieden mit dem Ergebnis. Der Kontrast ist hoch und die silberweiße Oberfläche des Aluminium(oxyds) sieht auf der mattschwarzen Lackierung richtig edel aus.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/wxcEMorVuY3hVZkseFjXyU" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Graustufenbilder mit der CNC fertigen: Teil3&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="qr-codes"&gt;QR-Codes&lt;/h2&gt;&#10;&lt;p&gt;Wenn ich schon einmal mit Dibond arbeite: Ich probiere gleich noch aus, ein paar QR-Codes mit einem Radienfräser einzubringen.&#10;Dieses Mal verwende ich verschiedene Maschinen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-02-25-qr-dibond.avif" alt="Image: QR-codengrave tests in dibond with a radius cutter" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="auf-der-hobbyline"&gt;Auf der Hobbyline&lt;/h3&gt;&#10;&lt;p&gt;In schwarz graviere ich auf der mir zur Verfügung gestellten Hobbyline im übertakteten Gravurmodus. Ich fixiere das Werkstück mit einigen Punkten Doppelklebeband. Ich fräse trocken ohne Schmiermittel. Beobachtungen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Gravurtiefe scheint mit zunehmender Linienlänge zuzunehmen (Aufbauschneide, Fräser schmiert ins Material)&lt;/li&gt;&#10;&lt;li&gt;Teilweise wölbt sich die Lackoberfläche am Fräsrand nach oben&lt;/li&gt;&#10;&lt;li&gt;Sehr unsaubere Fräskanäle, starke Richtungsabhängigkeit&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="auf-der-basicline"&gt;Auf der Basicline&lt;/h3&gt;&#10;&lt;p&gt;Hier nutze ich natürlich meinen Vakuumtisch und profitiere vom viel stabileren Aufbau. Ich verwende eine geringe Menge Schmiermittel. War gespannt, ob die das weiße Material trotzdem ausreichende Kontraste zulässt. Beobachtungen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Auch meine &lt;a href="https://blog.schallbert.de/projects/qr-codengrave/"&gt;QR-codeengrave&lt;/a&gt; Software muss richtig bedient werden. Ansonsten verschieben sich wie hier Nullpunkte und Größenverhältnisse. Folge: Mein Handy erkennt den QR-Code nicht 😐&lt;/li&gt;&#10;&lt;li&gt;Die viel bessere Niederhaltung verhindert starke Unterschiede in der Fräskanalbreite.&lt;/li&gt;&#10;&lt;li&gt;Dennoch sieht man die Austauchpunkte der Maschine und manchmal auch mitgeschleifte Späne.&lt;/li&gt;&#10;&lt;li&gt;Die Fräsqualität ist noch immer nicht befriedigend.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="fazit-1"&gt;Fazit&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-01_smallparts-thumb.jpg" alt="Image: single-flute cutter for PMMA/Acrylics." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;In Zukunft muss ich Dibond ähnlich behandeln wie Acrylglas: Sehr scharf geschliffene Fräser verwenden, die das Material ordentlich schneiden. Idealerweise Einschneider mit Habichtschnabel und Eckenfase (weiche Kanten), zentrumsschneidend, dazu ordentlich Drall. Z.B. &lt;a href="https://shop.vhf.de/articleGroups/Einzahnfraeser-mit-Stirnradius-W_F_ES_SCR.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;diese hier (Link zu vhf)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. So wird - glaube ich - eine Materialverdrängung in das noch weichere Kernmaterial vermieden und Späne außerdem im Spankanal nach oben gefördert, sodass sie nicht anhaften können oder ein zweites Mal unter den Fräser geraten.&lt;/p&gt;&#10;&lt;p&gt;Zudem sollte ich mit zügigen Vorschubwerten arbeiten. Dann heizt sich das Material nicht zu stark auf. Die Verwendung von ein wenig Schmiermittel sowie das Aufbringen von Schutzfolie trägt zu besserem Fräsergebnis bei. Weitere Verarbeitungshinweise gibt es beispielsweise &lt;a href="https://www.cnc-aus-holz.at/index.php?thread/2397-aluverbundplatte-dibond-gravieren/&amp;amp;postID=27536#post27536" target="_blank" rel="noopener noreferrer" class="external-link"&gt;im CNC-aus-Holz-Forum&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Stöbern kann sich lohnen.&lt;/p&gt;&#10;</description></item><item><title>Graustufenbilder mit der CNC fräsen</title><link>https://blog.schallbert.de/halftone-cnc/</link><pubDate>Mon, 27 Jan 2025 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/halftone-cnc/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-01-27_bloglogo-thumb.avif"&#10; class="post-cover"&#10; alt="Image: TSP-paths of my blog&amp;#39;&amp;#39;s logo"&#10; title="Graustufenbilder mit der CNC fräsen" /&gt;&#10;&lt;p&gt;Bis jetzt habe ich Bilder stets als Gravuren in Volltonfarben erstellt. Damit lassen sich Texte wie &lt;a href="https://blog.schallbert.de/negative-carving-with-estlcam/"&gt;diesen hier&lt;/a&gt;, aber auch &lt;a href="https://blog.schallbert.de/engrave-multicolor/"&gt;Zeichnungen&lt;/a&gt; und &lt;a href="https://blog.schallbert.de/makerfaire-ruhr/#vorbereitung"&gt;Bilder&lt;/a&gt; kontrastreich und hübsch fertigen.&lt;/p&gt;&#10;&lt;h2 id="das-ziel"&gt;Das Ziel&lt;/h2&gt;&#10;&lt;p&gt;Bei der Volltondarstellung können viele Details nicht abgebildet werden. Nun möchte ich aber ein echtes Foto ins Material bringen. Idealerweise soll dies zügig, ohne manuelle Nacharbeit und mit hoher Bildqualität passieren.&lt;/p&gt;&#10;&lt;p&gt;Mein CAM-Werkzeug stellt hierfür eine &amp;ldquo;Halbtonbild&amp;rdquo;-Funktion zur Verfügung, mit der ich im Folgenden herumexperimentiere. Auch hier ist es wieder so, dass selten direkt gute Ergebnisse aus der Maschine fallen. Außerdem sind die Erzeugung solcher Abbildungen sehr zeitintensiv, und Fehlversuche folglich &amp;ldquo;teuer&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Daher gebe ich am Ende des Artikels ein paar Tipps und Tricks, mit deren Hilfe recht schnell gute Ergebnisse erzielt werden können.&lt;/p&gt;&#10;&lt;h2 id="ein-wenig-farbwissen-rasterverfahren"&gt;Ein wenig Farbwissen: Rasterverfahren&lt;/h2&gt;&#10;&lt;p&gt;Das Problem, einen ganzen Farbraum mit nur wenigen verfügbaren Farben abbilden zu wollen, ist schon hunderte Jahre alt. Während Farbverläufe durch Mischen von Farben in der analogen Welt und auch am Bildschirm mittels unterschiedlicher Luminanzwerte der drei Farbkanäle in Pixeln hervorragend dargestellt werden können, stehen bei längst nicht jeder Anwendung eine große Anzahl Farben zur Verfügung.&lt;/p&gt;&#10;&lt;p&gt;Drucker zum Beispiel&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Sie haben meist nur die drei Grundfarben &lt;code&gt;cyan (C)&lt;/code&gt; - ein Türkiston, &lt;code&gt;magenta (M)&lt;/code&gt; - ein Pinkton, &lt;code&gt;yellow (Y)&lt;/code&gt; - gelb sowie &lt;code&gt;key (K)&lt;/code&gt; - meist Schwarz - zur Verfügung. Hieraus müssen sie dann die Illusion aller möglicher Farben erzeugen, denn sie können die Pigmente wegen ihrer hohen Deckkraft nicht mischen. Ansonsten würde der zuletzt aufgebrachte Farbton dominieren oder die Farbe würde schlicht verlaufen. Hieraus ergibt sich die Notwendigkeit, Rasterverfahren einzusetzen.&lt;/p&gt;&#10;&lt;h3 id="halftones-halbtonbild"&gt;Halftones (Halbtonbild)&lt;/h3&gt;&#10;&lt;p&gt;Eines dieser Rasterverfahren heißt &lt;em&gt;halftone&lt;/em&gt;. Hierbei wird durch Farbpunkte oder -Muster unterschiedlicher Strichstärke ein &lt;a href="https://en.wikipedia.org/wiki/Halftone" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Farbton simuliert&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, welchen das Auge ab einem gewissen Abstand nicht mehr auflösen kann und daher gemeinsam mit dem umgebenen Freiraum und gegebenenfalls anderen Farbpunkten zu einem Tonwert &amp;ldquo;mittelt&amp;rdquo;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-01-27_bloglogo_9600dots.avif" alt="Image: 9600 dots stipple image of my blog&amp;#39;s logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dieses Verfahren eignet sich für die &lt;a href="https://hackaday.com/2011/07/28/creating-halftone-pictures-with-a-cnc-machine/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Fertigung mittels CNC-Fräse&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, sofern die Oberfläche des zu bearbeitenden Werkstücks eine andere Farbe besitzt als das Kernmaterial.&lt;/p&gt;&#10;&lt;p&gt;Für Halftones existieren viele verschiedene Erzeugungsverfahren, bei denen sich Anordnung der Punkte, Striche, Musterform usw. teils stark unterscheiden. Auf der CNC bestimmen das verwendete Gravurmaterial, die benötigte Auflösung und das gewünschte Werkzeug das Verfahren, da sich nicht mit jeder Kombination eine gute Qualität erreichen lässt.&lt;/p&gt;&#10;&lt;h3 id="dithering"&gt;Dithering&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Dither#Digital_photography_and_image_processing" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dithering&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; wird eher bei der Darstellung auf Bildschirmen verwendet. Hierbei variiert nicht wie beim Halbtonverfahren Stärke bzw. Größe von Farbpunkten oder Mustern, sondern die räumliche Verteilung andersfarbiger Punkte, welche in die Grundfarbe gesprenkelt werden. Dadurch entsteht der Eindruck einer Mischfarbe.&lt;/p&gt;&#10;&lt;p&gt;Auch hier gibt es wieder verschiedene Erzeugungsverfahren: Diffusion, Rauschen, Muster, &lt;a href="https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Floyd-Steinberg-Algorithmus&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;hellip;&lt;/p&gt;&#10;&lt;h2 id="materialauswahl"&gt;Materialauswahl&lt;/h2&gt;&#10;&lt;p&gt;Hier habe ich eine Menge ausprobiert. Am Besten eignen sich Materialien mit homogener Zusammensetzung und hohem Kontrast zwischen Oberflächen- und Kernfarbe. Zudem sollte die Schichtdicke des Oberflächenmaterials nicht zu groß ausfallen, da sie komplett durchstochen werden muss und dann schnell sehr große Einzelpunkte erforderlich werden. Die Oberfläche ist idealerweise mattiert, denn Seiden- Metall- oder Hochglanz erzeugen eine Blickwinkelabhängigkeit, bei der die Erkennbarkeit des Motivs leidet.&lt;/p&gt;&#10;&lt;p&gt;Außerdem sollte das Material komplett plan zur Fräsfläche sein, da die Punktgröße sonst unerwünscht stark variiert.&lt;/p&gt;&#10;&lt;h3 id="geeignete-materialien-"&gt;Geeignete Materialien 👍&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Kernmaterial&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Beschichtung&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Besonderheit&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Mitteldichte Faserplatte&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Grundierung + Lack&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Fräsbahn nicht überlappen&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Aluverbundmaterial&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lack oder Folie&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Max. 0.18mm tief fräsen&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Acrylglas (PMMA)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lack oder Folie&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;XT verwenden (Dickentoleranz)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Am wenigsten Aufwand macht mir die Verarbeitung von fertig lackiertem Aluverbundmaterial. Hier ist praktisch keine Vor- und Nacharbeit erforderlich. Es ist aber darauf zu achten, die Frästiefe auf &lt;code&gt;0.18mm&lt;/code&gt; zu begrenzen. Sonst wird der PU-Kern deformiert oder es entstehen Ausrisse.&lt;/p&gt;&#10;&lt;p&gt;Das Aufbringen einer Folie kann eine gute Lösung sein, wenn man den Zeitaufwand eines Farbauftrags scheut. Ich selbst bin leider nicht besonders geschickt im Umgang mit Folie. Ständig erzeuge ich Luftblasen oder Einschlüsse kleinster Staubkörnchen. Nach dem Fräsvorgang sind die dadurch entstandenen Defekte im Höhenprofil deutlich anhand zu großer Löcher im Material zu erkennen.&lt;/p&gt;&#10;&lt;h3 id="ungeeignete-materialien-"&gt;Ungeeignete Materialien 👎&lt;/h3&gt;&#10;&lt;p&gt;Die hier aufgelisteten Materialien eignen sich meiner Meinung nach so nicht gut zum Erzeugen von Halbtonbildern. Ich habe ein paar Bilder misslungener Versuche hier abgelegt. Oft liegt ein Grund für Fehlschläge in einer für das Material zu hoch gewählten Bildauflösung.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Kernmaterial&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Beschichtung&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Grund&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Mitteldichte Faserplatte&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Furnier&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Zu hohe Schichtdicke&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Mitteldichte Faserplatte&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Metallfolie&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Papierkern der Metallfolie ist weiß&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Aluverbundmaterial&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Metall, gebürstet&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Material &amp;ldquo;matscht&amp;rdquo;, Auflösung schlecht&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Aluverbundmaterial&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Keine, PU-Kern&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Alu in Kern gedrückt, Auflösung mies&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Weiche Alu-Legierungen&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lack oder Folie&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Aluminium verschmiert, Auflösung mies&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-01-27-3guys_perfect.avif" alt="Image: 3 guys als halftone image on metalized MDF" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hier experimentiere ich mit der Bildpunktdichte. Im unteren Bereich habe ich sie zu eng angeordnet, sodass sich die Beschichtung beim Fräsen löst. Auf meiner Gravur-CNC benötigt ein solches Bild auf einer Platte mit den Maßen&lt;code&gt;15x10cm&lt;/code&gt; trotz Übertaktung etwa &lt;code&gt;15 Minuten&lt;/code&gt;. Das Material ist wegen des Metallglanzes, der Ausrissgefahr, einer kaum erreichbaren Flächenverleimung und der hohen Schichtdicke nicht gut für solch hohe Auflösungen geeignet.&lt;/p&gt;&#10;&lt;h2 id="halbtonbilder-anforderungen-an-die-cnc"&gt;Halbtonbilder: Anforderungen an die CNC&lt;/h2&gt;&#10;&lt;p&gt;Das Problem hängt hier ausnahmsweise mal nicht mit Haltekräften, Fräskräften oder Schwierigkeiten beim Niederhalten zusammen. Im Gegenteil, beim Fräsen von Bildern werden außerordentlich geringe Kräfte aufgebracht, und die zumeist in Z-Richtung, wo die Maschinen eine Stabilitätsreserve besitzen.&lt;/p&gt;&#10;&lt;p&gt;Die Komplexität der Jobs stellt das Problem dar. Das Portal muss große Strecken zurücklegen und auf dem Weg ständig bremsen und beschleunigen. Endstufen, Schrittmotoren und Lager der Linearspindeln sind hierdurch stark belastet.&lt;/p&gt;&#10;&lt;h3 id="wellenstrichelinien"&gt;Wellen/Striche/Linien&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-01-27-anywhereamps_halftonegrid.avif" alt="Image: Halftone wave-grid image of AnywhereAmps1.2 compared to original image" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Im Linienverfahren fährt die Maschine ein Bild zeilenweise ab. Helligkeitsinformationen werden dabei üblicherweise per Frästiefe übertragen. Damit ergeben z.B. besonders dunkle Bereiche breitere Schnitte, welche dann im Schatten liegen. Dieses Verfahren eignet sich prima bei Fräsungen in Schaumstoffen, MDF und Acrylglas. Sie lassen sich außerdem recht zügig durchführen, da die Maschine im Materialeingriff bleibt und lediglich für Zeilensprünge Positioniervorgänge erforderlich sind.&lt;/p&gt;&#10;&lt;h3 id="punktverfahren"&gt;Punktverfahren&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-01-27-bloglogo_stipple.avif" alt="Image: bloglogo made of 2000 Vonoroi stipples" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hier wird die Größe von Punkten verändert, sodass mal größere und mal kleinere Vertiefungen im Material entstehen.&lt;/p&gt;&#10;&lt;p&gt;Halbtonbilder im Punktverfahren benötigen verhältnismäßig viel Zeit auf der Maschine. Was mit einem Hobbylaser in einer halben Stunde vonstatten gehen kann, dauert mit einer CNC-Maschine bei gleicher Größe und Auflösung locker mal zwei Stunden (auch hier gibt es &lt;a href="https://blog.schallbert.de/cnc-tuning-kinematics/"&gt;Optimierungsmöglichkeiten&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;Zur Fertigung solcher Bilder muss die Maschine extrem oft die Z-Achse bewegen. Beim Fräsen von Punkten muss sie für jeden &amp;ldquo;Pixel&amp;rdquo; einmal zwischen Sicherheitshöhe und Materialeingriff wechseln.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Beispiel: Werden &lt;code&gt;400x400&lt;/code&gt; Punkte gefräst (Das wäre im Pixelmaßstab ein ziemlich mies aufgelöstes Bild), so muss die Maschine &lt;code&gt;32000x&lt;/code&gt; ihre Z-Achse in Gang setzen!&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Daher sollte die Sicherheitshöhe nur ein paar Zehntel über der Werkstückoberfläche liegen, z.B. &lt;code&gt;0.5mm&lt;/code&gt;. Um die schwere Y-Achse zu schonen, macht es Sinn, das Bild per &lt;em&gt;CAM&lt;/em&gt; zeilenweise auf der X-Achse abzuarbeiten. So wird die Y-Achse nur für Zeilensprünge bewegt.&lt;/p&gt;&#10;&lt;p&gt;Vorteilhaft ist hier eine leichte Portalfräsmaschine mit wenig Masse auf der Z-Achse. Sie sollte gut geschmiert und die Achsführungen gereinigt sein. Gleichzeitig sollte sie nicht zu steif sein, damit sie sich nicht durch die vielen abrupten Bewegungen aufschwingt.&lt;/p&gt;&#10;&lt;p&gt;Für Schrittmotoren und -Treiber sind diese Jobs Marathonrennen. Ihre Temperaturen sollten stets gut im Auge behalten werden. Ich empfehle, die Maschine nicht mit Halbtonbildern im Dauerbetrieb zu fahren. Die Achs- und Portalbelastungen hingegen sind vernachlässigbar: Die Maschine ist kaum Last in X- und Y-Richtung ausgesetzt, da sie zumeist nach unten im Materialeingriff steht und dies auch nur minimal zum Erzeugen kegelförmiger Vertiefungen.&lt;/p&gt;&#10;&lt;h3 id="labyrinthliniensegmente"&gt;Labyrinth/Liniensegmente&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-01-27-bloglogo_tsp.avif" alt="Image: TSP-paths my bloglogo connecting 9600 Vonoroi stipples" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Soll das gesamte Bild in nur einem Zug abgefahren werden, so muss erst ein &amp;ldquo;Problem des Handlungsreisenden&amp;rdquo; (&lt;a href="https://en.wikipedia.org/wiki/Travelling_salesman_problem" target="_blank" rel="noopener noreferrer" class="external-link"&gt;TSP&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) gelöst werden, wie es zum Beispiel die von &lt;a href="https://www.evilmadscientist.com/2012/stipplegen-weighted-voronoi-stippling-and-tsp-paths-in-processing/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;EvilMadScientist&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; vorgestellte Software &lt;em&gt;Stipplegen2&lt;/em&gt; vermag. Bei diesem Verfahren wird die Dichte der Liniensegmente pro Flächeneinheit erhöht, wenn ein Bereich besonders dunkel erscheinen soll. Auf der CNC-Maschine sind solche Bilder recht zügig fertiggestellt, da sie die Z-Achse nur einmal eintauchen lassen.&lt;/p&gt;&#10;&lt;p&gt;Nachteilig bei diesem Verfahren sind die geringe erreichbare Auflösung (Es werden sehr viele Punkte benötigt) und der geringe Kontrast, denn alle Linien sind gleich dick.&lt;/p&gt;&#10;&lt;h2 id="cnc-werkzeuge"&gt;CNC-Werkzeuge&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-01-27-graver.avif" alt="Image: This CNC graver is a good tool for halftone image fabrication" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Die Erzeugung von Halbtonbildern sind Gravurarbeiten. Daher empfiehlt sich die Verwendung von Gravierfräsern mit &lt;code&gt;60°&lt;/code&gt; oder &lt;code&gt;90°&lt;/code&gt; Spitzenwinkel sowie einer Abflachung von &lt;code&gt;0-0.5mm&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Ist das Motiv klein, so sollte gar keine Abflachung verwendet werden. Der Spitzenwinkel sollte groß sein, wenn nicht tief gefräst werden soll oder kann oder falls die &amp;ldquo;helle Farbe&amp;rdquo; die Kernfarbe des Materials ist. So werden tiefe Schatten vermieden, die das Bild zusätzlich abdunkeln.&lt;/p&gt;&#10;&lt;p&gt;Wird lackierte Aluminium-Verbundplatte bearbeitet, so kann ich Halbrundfräser mit &lt;code&gt;r&amp;lt;2mm&lt;/code&gt; bei sehr geringer Eintauchtiefe von maximal &lt;code&gt;0.18mm&lt;/code&gt; empfehlen. Hierbei wird allerdings vorausgesetzt, dass das Maschinenbett überplant und damit sehr eben ausgerichtet ist.&lt;/p&gt;&#10;&lt;h2 id="herstellung"&gt;Herstellung&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-01-27-bloglogo_tsp_machined.avif" alt="Image: TSP-paths on aluminium sandwich" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Bild: Versuch, mein Bloglogo als TSP-Pfad mit 9600 Kanten auf schwarz lackiertes Aluminiumverbundmaterial zu gravieren. Verwendet habe ich einen &lt;code&gt;1/8&amp;quot; 36°-Stichel mit 0.1mm Abflachung&lt;/code&gt;. Am unteren Bildrand ist zu erkennen, dass das Material etwas tiefer liegt und daher nicht mehr vom Stichel erreicht werden kann.&lt;/p&gt;&#10;&lt;h3 id="bildvorbereitung"&gt;Bildvorbereitung&lt;/h3&gt;&#10;&lt;p&gt;Das Bild sollte eine hohe Auflösung besitzen und in Graustufen vorliegen. Idealerweise besitzt es hohe Kontraste und nutzt das gesamte Grautonspektrum.&lt;/p&gt;&#10;&lt;p&gt;Nun wird das Bild entweder in ein CAM-Tool geladen, welche die Anfertigung von Halbtonbildern unterstützt - z.B. von &lt;a href="https://gadgets.vectric.com/v8/halftoner" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Vectric&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, Estlcam - oder aber in ein Bearbeitungswerkzeug, welches Punktewolken oder Pfade erzeugen kann, z.B. &lt;a href="https://www.evilmadscientist.com/2012/stipplegen2/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Stipplegen2&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder Inkscape.&lt;/p&gt;&#10;&lt;p&gt;Dort probiere ich stets ein paar verschiedene Einstellungen durch in Abhängigkeit vom verwendeten Material, dem gewünschten Fräser und der Bildwirkung. Oft sieht man bereits dort, ob das Foto wirken kann und erlaubt eine Einschätzung, ob die Auflösung für das verwendete Material geeignet ist.&lt;/p&gt;&#10;&lt;h3 id="wie-man-zu-guten-ergebnissen-kommt"&gt;Wie man zu guten Ergebnissen kommt&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Große Abmessungen bevorzugen: Je größer das Bild gewählt wird, desto weniger fallen Materialinhomogenitäten und ausgebrochene Bereiche auf.&lt;/li&gt;&#10;&lt;li&gt;Große Beobachtungsdistanz ermöglichen: Je weiter das Bild vom Betrachter entfernt positioniert wird, desto schärfer wird die Auflösung wahrgenommen. Bevor es dann irgendwann zu klein erscheint 😆&lt;/li&gt;&#10;&lt;li&gt;Lichteinfallwinkel berücksichtigen: Hat das Material Seidenglanz, metallischen Glanz oder ist sogar hochglänzend, so hängt die Abbildungsqualität sehr vom Blickwinkel und der Distanz zur Lichtquelle ab. Je glänzender das Material, desto kleiner wird der Bereich, in dem die Abbildung gut zu erkennen ist.&lt;/li&gt;&#10;&lt;li&gt;Homogene, nicht schmierende Materialien verwenden. Ich habe mit Acrylglas die besten Ergebnisse erzielen können.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="cnc-durchlauf"&gt;CNC-Durchlauf&lt;/h3&gt;&#10;&lt;p&gt;Hier ist vor allem eines mitzubringen: Zeit. Im Folgenden ein paar Videoausschnitte meiner Versuche (Zeitraffer).&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/n7CnjF5CUn1sidg6mxYhvL" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Graustufenbilder mit der CNC fertigen: Teil1&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Ausnahmen bestätigen die Regel. Natürlich gibt es sowohl bei Bildschirmen als auch bei Druckern Ausführungen, welche dieses Problem umgehen: Sie setzen etwa zusätzliche Subpixel mit zusätzlichen Farben ein und können so größere Farbräume abdecken oder - wie im Tiefdruck (Magazine, Verpackungen etc.) üblich - verwenden eine höhere Anzahl Farben beziehungsweise Farbmischungen, deren Tonwerte näher an den benötigten Halbtönen liegen.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Mehrfarbige Gravuren</title><link>https://blog.schallbert.de/engrave-multicolor/</link><pubDate>Mon, 30 Dec 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/engrave-multicolor/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-30-multicolorengrave-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: multicolored sign &amp;#39;&amp;#39;Helft Waldbraende verhueten&amp;#39;&amp;#39;"&#10; title="Mehrfarbige Gravuren" /&gt;&#10;&lt;h2 id="die-projektidee"&gt;Die Projektidee&lt;/h2&gt;&#10;&lt;p&gt;Vor ein paar Wochen war ich im Wald unterwegs. Dort sah ich ein wunderschönes, altes Schild mit dem Aufdruck &amp;ldquo;Helft Waldbrände verhüten&amp;rdquo;. Ein Zeichenstil wie in Tinte aus vergangenen Zeiten. Kurzerhand fotografierte ich es.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-30_waldbrand-original.jpg" alt="Image: An old *Waldbrände verhüten* sign" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Leider ist das Schild ziemlich verblichen und wirkt als Foto wenig plastisch. Daher beschloss ich, mit etwas Zeitaufwand aber ohne große Kosten mit Hilfe meiner CNC-Gravurmaschine eine dreidimensionale Kopie des Schildes anzufertigen.&lt;/p&gt;&#10;&lt;h2 id="vom-jpg-nach-svg"&gt;Vom &lt;code&gt;.jpg&lt;/code&gt; nach &lt;code&gt;.svg&lt;/code&gt;&lt;/h2&gt;&#10;&lt;p&gt;Bevor ich einen Job für die CNC erstellen kann, muss ich das Bild aufbereiten und in Vektorform bringen. Mein CAM-Programm kann mit Pixelgrafiken nämlich wenig anfangen. Ich möchte das Bild gern als &amp;ldquo;Carve&amp;rdquo; erstellen, also mit einem spitzen Gravierfräser diejenigen Bildbereiche schnitzen, die später farbig erscheinen sollen. Hierfür benötige ich zudem eine Farbtontrennung, da das Bild auch in der gefrästen Fassung später weiße, rote und schwarze Anteile haben soll.&lt;/p&gt;&#10;&lt;h3 id="bildaufbereitung"&gt;Bildaufbereitung&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-30_waldbrand-cleaned.jpg" alt="Image: Preparing the image for trace operation" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hierfür nutze ich gängige Bildbearbeitungsprogramme wie &lt;a href="https://www.gimp.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;gimp&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und &lt;a href="https://www.getpaint.net/index.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;paint.net&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Ich verwende das &lt;em&gt;curves&lt;/em&gt;-Werkzeug, um den Kontrast zu erhöhen, richte die Perspektive mittels 3D-Drehung gerade und hole mir anschließend mit dem &lt;em&gt;Zuschneiden&lt;/em&gt;-Werkzeug den gewünschten Bildausschnitt. Anschließend radiere ich Bilddefekte, Schmutz oder durch die Kontrasterhöhung entstandene Artefakte weg. Zum Schluss noch ein wenig Schärfe hineindrehen und eine Tontrennung vornehmen, sodass ich keine Rottöne oder Graustufen, sondern reine Farben erhalte.&lt;/p&gt;&#10;&lt;p&gt;Nun exportiere ich das Bild als &lt;code&gt;.jpg&lt;/code&gt; und importiere es in &lt;a href="https://inkscape.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Inkscape&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="pfadkonversion"&gt;Pfadkonversion&lt;/h3&gt;&#10;&lt;p&gt;Hier skaliere ich das Bild zuerst, sodass es auf eine A4-Seite passt. Außerdem erstelle ich ganz unten links im Bild ein &lt;code&gt;1mm x 1mm&lt;/code&gt; kleines Quadrat, was mir für die beiden Farbschichtbilder später im CAM als Nullpunkt dienen soll, selbst aber nicht gefräst wird.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-30_waldbrand-trace.jpg" alt="Image: tracer in Inkscape" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Wie bereits in einem früheren &lt;a href="https://blog.schallbert.de/path-from-image/#inkscape"&gt;Artikel zur Pfadkonversion&lt;/a&gt; beschrieben, nutze ich die Trace-Funktion und kann so die roten und schwarzen Bereiche mittels unterschiedlicher Helligkeitsgrenzwerte in je eigene Layers freistellen. Die &amp;ldquo;rote&amp;rdquo; Layer enthält erst einmal auch die schwarzen Bildbereiche. Ich kann jedoch Layers voneinander subtrahieren, sodass nach dieser Aktion nur noch die roten Bildanteile übrig bleiben.&lt;/p&gt;&#10;&lt;p&gt;Anschließend lasse ich die Pfade noch vereinfachen und entferne fälschlicherweise erstellte Pfadartefakte. Ich exportiere die &amp;ldquo;rote&amp;rdquo; und die &amp;ldquo;schwarze&amp;rdquo; Layer jeweils als &lt;code&gt;.svg&lt;/code&gt;-Datei.&lt;/p&gt;&#10;&lt;h2 id="pfadplanung-im-cam"&gt;Pfadplanung im CAM&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-30-feuertrace.jpg" alt="Image: *red* areas to be cut" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Im CAM muss ich zwei Jobs planen - je einen für jede Farbe. Die unbearbeiteten Bildbereiche sollen weiß erscheinen. Die Gravur des ersten Jobs wird mit roter Farbe ausgelegt werden, und die zweite Gravur soll später tiefschwarz erscheinen.&lt;/p&gt;&#10;&lt;p&gt;Ich importiere also zuerst die &amp;ldquo;rote&amp;rdquo; SVG-Datei, wähle die für DIN A4 passende Skalierung, setze den Nullpunkt unten links und mache mich anschließend an die Erstellung der Carve-Pfade.&lt;/p&gt;&#10;&lt;h3 id="job1-rote-bereiche"&gt;Job1: &amp;ldquo;rote&amp;rdquo; Bereiche&lt;/h3&gt;&#10;&lt;p&gt;Ich setze eine Tiefenbegrenzung von &lt;code&gt;2mm&lt;/code&gt; und wähle für die Gravur einen &lt;code&gt;6mm 90°&lt;/code&gt; Stichel mit einer Schneide und &lt;code&gt;0.5mm&lt;/code&gt; Abflachung an der Spitze. Für die Räumarbeiten größerer gravierter Bereiche suche ich einen &lt;code&gt;3mm&lt;/code&gt; Zweischneider für Holz heraus und fräse mit &lt;code&gt;60%&lt;/code&gt; seitlicher Zustellung. Die Drehzahl stelle ich für beide Fräser bei &lt;code&gt;24000U/min&lt;/code&gt;, den Vorschub auf &lt;code&gt;3000mm/min&lt;/code&gt; ein.&lt;/p&gt;&#10;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-30-waldtrace.jpg" alt="Image: *black* areas to be cut" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="job2-schwarze-bereiche"&gt;Job2: &amp;ldquo;schwarze&amp;rdquo; Bereiche&lt;/h3&gt;&#10;&lt;p&gt;Bei den schwarzen Bereichen erlaube ich für Job2 eine höhere Tiefenzustellung von &lt;code&gt;2.8mm&lt;/code&gt; und spare mir den Räumfräser, da hier weniger zusammenhängende Flächen abgenommen werden müssen. Durch die größere Tiefe soll das Schwarz noch dunkler und das Motiv plastischer wirken.&lt;/p&gt;&#10;&lt;h2 id="durchführung"&gt;Durchführung&lt;/h2&gt;&#10;&lt;p&gt;Für das Projekt nehme ich eine schwarz durchgefärbte Faserplatte (MDF) zur Hand. Mit weißem Acryllack streiche ich sie mehrfach an, bis ich eine homogene und gut deckende Oberfläche habe. Nachdem alles getrocknet ist, bringe ich eine selbstklebende Folie auf. Profis nehmen Schablonenfolien (Maskierfolien) z.B. von Orafol, ich behelfe mir jedoch mit einer viel einfacheren Einschlagfolie für Bücher. Nicht perfekt, da ihre Haftung für kleine Flächen nicht ausreicht - aber gut genug für mich.&lt;/p&gt;&#10;&lt;h3 id="rote-flammen"&gt;Rote Flammen&lt;/h3&gt;&#10;&lt;p&gt;Nun lasse ich den ersten Job auf der Fräse durchlaufen. Dafür klebe ich das Werkstück mit Doppelklebeband im Arbeitsbereich der Fräse fest und richte sie ordentlich aus, sodass der Nullpunkt einfach gesetzt werden kann.&lt;/p&gt;&#10;&lt;p&gt;Sobald die Fräse durchgelaufen ist, setze ich Schrauben in die T-Nuten des Grundplatte an den Rändern des Werkstücks. So kann ich es nachher wiederholgenau positionieren. Anschließend löse ich es vom Maschinenbett, reinige es mit Bürste und Pinsel und bringe rote Sprühfarbe auf. Dummerweise zieht diese sofort in das MDF ein (siehe Video unten). Die Flammen bleiben somit schwarz. Ich probiere es erneut, wieder ohne Erfolg. Also muss ich nochmal ins Geschäft und einen viel viskoseren Lack zum Aufbringen per Pinsel kaufen.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Soll MDF lackiert werden, eignet sich Sprühfarbe wegen ihrer geringen Viskosität nicht: Sie zieht ein und hinterlässt kaum Pigmente auf der Oberfläche.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Nachdem ich auch hier wieder gewartet habe bis die ausgelegten Flammen getrocknet sind, montiere ich das Werkstück erneut auf dem Maschinenbett.&lt;/p&gt;&#10;&lt;h3 id="schwarze-tannen"&gt;Schwarze Tannen&lt;/h3&gt;&#10;&lt;p&gt;Nun lasse ich den zweiten Job durchlaufen. Dies geht trotz Verzicht auf den Räumfräser bedeutend schneller, da der Werkzeugwechsel entfällt.&lt;/p&gt;&#10;&lt;p&gt;Anschließend ziehe ich die Schutzfolie vom Werkstück ab und reinige es gründlich.&#10;Wie schön das Schild geworden ist!&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-30-multicolorengrave.jpg" alt="Image: carving complete: My copy of *Waldbrände verhüten*." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="video-der-3d-gravur"&gt;Video der 3D-Gravur&lt;/h2&gt;&#10;&lt;p&gt;Zu diesem Projekt gibt es mal wieder ein Zeitraffer-Video, bereitgestellt auf meinem PeerTube-Kanal.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/briT2e8qbbY6uWFFmv688n" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;multicolorEngrave&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="was-noch-besser-werden-könnte"&gt;Was noch besser werden könnte&lt;/h2&gt;&#10;&lt;p&gt;Im Nachhinein betrachtet gibt es bei meinen Projekten oft Dinge, die noch besser gemacht werden können. Wenn ich so auf das Video gucke, fallen mir zwei Sachen auf:&lt;/p&gt;&#10;&lt;h3 id="1-pfadqualität-bestimmt-fräsqualität"&gt;1. Pfadqualität bestimmt Fräsqualität&lt;/h3&gt;&#10;&lt;p&gt;Ich wollte möglichst schnell zum Ergebnis kommen und habe daher bei der Umwandlung vom Pixelbild zur Vektorgrafik nicht alle Pfadartefakte ausgeräumt. Diese äußern sich am fertigen Produkt als kleine Inseln und Punkte, die weniger tief gefräst wurden als der Rest. Am besten ist dies in den roten Bildbereichen zu erkennen. In Natura fällt das kaum auf. Im Video erkennt man aber, dass Fräse zum Ausarbeiten dieser Stellen zusätzliche Zeit benötigt.&lt;/p&gt;&#10;&lt;h3 id="2-cam-strategie-kostet-zeit"&gt;2. CAM-Strategie kostet Zeit&lt;/h3&gt;&#10;&lt;p&gt;Mein Programm für die Fräsplanung läuft Fräspfade für Räumung, Umrisse und Ausspitzungen von oben nach unten jeweils zeilenweise ab. Dies hat zur Konsequenz, dass unglaublich viele Positioniervorgänge für nicht zusammenhängende Pfade erforderlich werden. Zudem wird jeder Punkt im ungünstigsten Falle drei mal angefahren.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;G00&lt;/code&gt;-Kommandos sind hier schlecht, weil sie eine Menge Zeit kosten und die Maschine unnötige Wege verfährt. Dieses Problem ließe sich nur mit einem professionellerem CAM-Werkzeug beheben.&lt;/p&gt;&#10;</description></item><item><title>Repo-Lookout: Sicherheitslücke beheben</title><link>https://blog.schallbert.de/repo-lookout-fix-deploy/</link><pubDate>Fri, 20 Dec 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/repo-lookout-fix-deploy/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-20-repolookout-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Repolookout reporting a possible vulnerability on my site"&#10; title="Repo-Lookout: Sicherheitslücke beheben" /&gt;&#10;&lt;h2 id="repo-informationen-öffentlich-zugänglich"&gt;Repo-Informationen öffentlich zugänglich&lt;/h2&gt;&#10;&lt;p&gt;Eines schönen Tages bekam ich eine E-Mail von &lt;a href="https://www.repo-lookout.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Repo Lookout&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Darin stand, dass eines meiner Repositories für Zugriffe aus dem Internet freigegeben sei. Dies stelle ein potentielles Sicherheitsrisiko dar, da möglicherweise geheime Quelldateien, versteckte Funktionen oder sogar Passwörter enthalten sein könnten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-20_repolookout_warning.jpg" alt="Image: RepoLookout mail notification" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Zuerst hielt ich diese &amp;ldquo;Ihr-Repo-ist-nicht-sicher&amp;rdquo;-Warnung für einen Phishing-Versuch.&#10;Doch durch einfaches Eingeben der darin enthaltenen Links wurde klar, dass &lt;em&gt;Repo Lookout&lt;/em&gt; Recht hatte und mein &lt;a href="https://lectures.schallbert.de" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Lectures&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Repo nicht nur wie von mir gewünscht öffentlich war, sondern dass auch die Metadaten zur Versionskontrolle offen auf dem Webserver lagen.&lt;/p&gt;&#10;&lt;h3 id="ist-das-schlimm"&gt;Ist das schlimm?&lt;/h3&gt;&#10;&lt;p&gt;Normalerweise sollen innere Struktur und Konfiguration (Actions, Diskussionen, Wiki etc.) hinter einem Repository nicht öffentlich bleiben. Erst recht nicht, wenn das Repository als &lt;code&gt;private&lt;/code&gt; angelegt ist. Doch auch bei öffentlichen Repos sollte niemand auf die Struktur dahinter zugreifen können.&lt;/p&gt;&#10;&lt;p&gt;Daher die Mission von Repo-Lookout:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Find source code repositories that have been inadvertently exposed to the public and report them to the domain&amp;rsquo;s technical contact.&amp;rdquo; - Repo Lookout /about (&lt;a href="https://www.crissyfield.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Crissy Field GmbH&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;In diesem Falle unkritisch, aber unerwartet und unschön.&lt;/p&gt;&#10;&lt;p&gt;Schadet mir nicht, weil mein Webserver die Dateien lediglich zum Abruf dort liegen hat und sie selbst bei Manipulation keine Rückwirkung auf mein Repository gehabt hätten. Außerdem hatte ich alle Secrets wie &lt;a href="https://blog.schallbert.de/server-config-version-control/#secrets-in-docker-composeyml"&gt;in diesem Artikel beschrieben&lt;/a&gt; in eigens angelegte Dateien ausgelagert, sodass sie nicht mehr in den Konfigurationsdateien auftauchen. Das Repository liegt zudem getrennt auf der &lt;a href="https://git.schallbert.de/schallbert/lectures" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Gitea-Server-Instanz&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Dennoch sollte nur am Internet hängen, was ich bewusst freigeben möchte.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-12-20_repo_commithistory.jpg" alt="Image: The repository&amp;#39;s commit history is public anyways" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der Bildausschnitt von &lt;em&gt;Gitea&lt;/em&gt; zeigt denselben Commit wie die Warnung von &lt;em&gt;Repo Lookout&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h3 id="wie-es-dazu-kam"&gt;Wie es dazu kam&lt;/h3&gt;&#10;&lt;p&gt;In der Deploy-Pipeline für meine Subdomain &lt;code&gt;lectures.schallbert.de&lt;/code&gt; und Landing Page &lt;code&gt;schallbert.de&lt;/code&gt; habe ich einen direkt-Checkout von der &lt;em&gt;Gitea&lt;/em&gt;-Instanz zum Webserver &lt;em&gt;Caddy&lt;/em&gt;. Dieser läuft automatisch an, sobald die &lt;code&gt;main&lt;/code&gt;-Branches ein Update erhalten. Der Runner startet eine &lt;a href="https://github.com/marketplace/actions/checkout" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Checkout-Action&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, welche das Repository ins entsprechende Verzeichnis des Webservers kopiert.&lt;/p&gt;&#10;&lt;h3 id="checkout-action-kopiert-auch-den-git-ordner"&gt;Checkout-Action kopiert auch den &lt;code&gt;.git&lt;/code&gt;-Ordner&lt;/h3&gt;&#10;&lt;p&gt;Dabei wird der &lt;code&gt;.git&lt;/code&gt;-Ordner einfach mit aufgesetzt. Dort drin liegen alle von der Versionskontrollsoftware &lt;a href="https://git-scm.com/docs/gitrepository-layout" target="_blank" rel="noopener noreferrer" class="external-link"&gt;benötigten Daten zur Zustandsverwaltung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; des Repos. Den Kopiervorgang selbst kann ich auf &lt;em&gt;Gitea&lt;/em&gt; nicht sehen, denn dort taucht der versteckte &lt;code&gt;.git&lt;/code&gt;-Ordner gar nicht erst im Verzeichnis auf. Verständlich, denn auf Basis dieses Ordners findet die gesamte Darstellung auf &lt;em&gt;Gitea&lt;/em&gt; statt.&lt;/p&gt;&#10;&lt;p&gt;So blieb das unerwünschte Verhalten unter meinem Radar - und laut &lt;em&gt;Repo Lookout&lt;/em&gt; bin ich bei Weitem nicht der Einzige, dem es so geht.&lt;/p&gt;&#10;&lt;h2 id="option-1-fix-auf-dem-webserver"&gt;Option 1: Fix auf dem Webserver&lt;/h2&gt;&#10;&lt;p&gt;Die naheliegendste Lösung ist, den Zugriff auf die Datei serverseitig zu blockieren. Dies kostet nur wenige Ressourcen und ist leicht einzurichten.&lt;/p&gt;&#10;&lt;p&gt;Dieser Foreneintrag zeigt, wie das geht: &lt;a href="https://caddy.community/t/v2-hide-entire-folder-caddyfile/7234" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hide-entire-folder-caddyfile&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Für meine zu schützenden Dateien füge ich also folgende Einträge der &lt;code&gt;Caddyfile&lt;/code&gt; hinzu:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /caddy2/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;respond /.git/* &amp;#34;Access denied&amp;#34; 403&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;respond /.gitea/* &amp;#34;Access denied&amp;#34; 403&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Damit sage ich Caddy, dass es bei Aufruf einer beliebigen Datei &lt;code&gt;/*&lt;/code&gt; im Ordner &lt;code&gt;/.git&lt;/code&gt; mit dem Fehlercode &lt;code&gt;403&lt;/code&gt; &amp;ldquo;Forbidden&amp;rdquo; antworten soll. Dabei ist die Wildcard (&lt;code&gt;*&lt;/code&gt;) unbedingt erforderlich, denn sonst wird &lt;em&gt;lediglich der Ordner selbst&lt;/em&gt; und nicht die enthaltenen Dateien gesperrt.&lt;/p&gt;&#10;&lt;p&gt;Zur Kontrolle prüfe ich was passiert, wenn ich die Git Logs anfrage:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;# Terminal&#10;curl &amp;lt;lectures.schallbert.de&amp;gt;/.git/logs/HEAD&#10;Access denied &#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Funktioniert prima!&lt;/p&gt;&#10;&lt;h2 id="option-2-fix-in-der-checkout-action"&gt;Option 2: Fix in der Checkout-Action&lt;/h2&gt;&#10;&lt;p&gt;Es gibt allerdings eine noch viel elegantere Lösung: bereits vorher in der Pipeline dafür sorgen, dass der Ordner gar nicht erst auf dem Server auftaucht.&lt;/p&gt;&#10;&lt;h3 id="variante-1-mit-hilfe-von-sparse-checkout"&gt;Variante 1: Mit Hilfe von &lt;code&gt;sparse-checkout&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/33933702/git-checkout-except-one-folder" target="_blank" rel="noopener noreferrer" class="external-link"&gt;sparse-checkout&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ermöglicht die Auswahl von Ordnern und Dateien, die zum Checkout gehören sollen. Alle anderen Dateien im Repository bleiben unberührt und tauchen im Branch nicht auf. Dies spart vor allem bei großen Repositories Zeit und Speicherplatz. Macht aber natürlich nur Sinn wenn bereits vorher bekannt ist, dass nicht alle Dateien angefasst werden müssen.&lt;/p&gt;&#10;&lt;h3 id="negativliste-für-sparse-checkout"&gt;Negativliste für &lt;code&gt;sparse-checkout&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;In meinem Falle will ich die oben erwähnten Ordner gerade &lt;em&gt;nicht&lt;/em&gt; per Checkout auf den Server kopieren, den ganzen Rest aber schon. Wie ich das hinbekomme? Mit Hilfe der Negation im &lt;code&gt;no-cone&lt;/code&gt; mode.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;The user has explicitly said &amp;lsquo;I want these directories and not those directories.&amp;rsquo;&amp;rdquo; - Derrick Stolee, Microsoft, auf &lt;a href="https://github.com/git/git/commit/55dfcf9591b088ce60ec80eb5425dda18223cac0" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;In der &lt;a href="https://github.com/marketplace/actions/checkout" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung zu Checkout-Action&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; steht, dass &lt;code&gt;sparse-checkout&lt;/code&gt; auch für die vom Runner automatisierte Aktion unterstützt wird.&lt;/p&gt;&#10;&lt;p&gt;Nun programmiere ich mit Hilfe des &lt;a href="https://github.github.com/actions-cheat-sheet/actions-cheat-sheet.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github Actions Cheet Sheet&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /.gitea/workflows/deploy-lectures.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;CHECKOUT ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v4&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;./tmp&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;sparse-checkout&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; /*&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; !.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; !.gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;sparse-checkout-cone-mode&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Zur Erläuterung: Das Skript zum Veröffentlichen auf meinem Webserver verwendet die Aktion &lt;code&gt;checkout&lt;/code&gt;, Unterfunktion &lt;code&gt;sparse-checkout&lt;/code&gt; und bezieht sämtliche Dateien im Ordner im Stammverzeichnis &lt;code&gt;tmp&lt;/code&gt; und darunter ein bis auf &lt;code&gt;.git&lt;/code&gt; und &lt;code&gt;.gitea&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="was-ist-der-no-cone-mode"&gt;Was ist der &lt;code&gt;No-Cone Mode&lt;/code&gt;?&lt;/h3&gt;&#10;&lt;p&gt;Standardmäßig erwartet &lt;code&gt;sparse-checkout&lt;/code&gt; eine Liste von Ordnern, die für den Checkout erfasst werden sollen. Im &lt;code&gt;no-cone&lt;/code&gt; Modus wird stattdessen eine Liste von Patterns erwartet. Hier sind sämtliche Operatoren möglich, die auch in der &lt;code&gt;.gitignore&lt;/code&gt; zum Spezifizieren von Dateien, Ordnern, Auslassungen etc. verwendet werden können. Dies ermöglicht mir den Ausschluss bestimmter Ordner, hat aber einige &lt;a href="https://git-scm.com/docs/git-sparse-checkout#_internalsnon_cone_problems" target="_blank" rel="noopener noreferrer" class="external-link"&gt;gewichige Nachteile&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Durch viel höhere Komplexität der Pattern-Befehle, die damit einhergehende Fehleranfälligkeit sowie die deutlich rechenintensivere Auswertung bei größeren Repositories wird die Verwendung des &lt;code&gt;no-cone&lt;/code&gt; Mode nicht empfohlen und ist in der Dokumentation als &amp;ldquo;deprecated&amp;rdquo; (überholt) angegeben. Dennoch, probieren geht über Studieren!&lt;/p&gt;&#10;&lt;h3 id="test-mit-sparse-checkout"&gt;Test mit &lt;code&gt;sparse-checkout&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Nun lade ich die Action auf meine Gitea-Instanz hoch und lass meinen &lt;em&gt;runner&lt;/em&gt; einmal drüber laufen. Anschließend logge ich mich auf dem Webserver ein und schaue, ob der &lt;code&gt;.git&lt;/code&gt;-Ordner erstellt wurde oder nicht:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;lectures# ls -la&#10;[...]&#10;drwxr-xr-x 8 root root 4096 Dec 20 10:41 .git&#10;[...]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Mist, der Ordner ist ja immer noch da. Ich schaue in den Logs der Action auf meiner Gitea-Instanz nach:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;[...]&#10;hint: &#9;git branch -m &amp;lt;name&amp;gt;&#10;Initialized empty Git repository in /workspace/schallbert/lectures/tmp/.git/&#10;[...]&#10;::group::Setting up sparse checkout&#10;[command]/usr/bin/git config core.sparseCheckout true&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;An &lt;code&gt;sparse-checkout&lt;/code&gt; liegt es also nicht. Dafür aber an der Art und Weise, wie &lt;em&gt;checkout&lt;/em&gt; funktioniert: Offensichtlich wird für ein ordentliches Aufsetzen des Repository auf meinem Webserver der &lt;code&gt;.git&lt;/code&gt;-Ordner zwingend benötigt. Also bleibt mir nur übrig, ihn nach dem Auschecken automatisch zu löschen.&lt;/p&gt;&#10;&lt;h3 id="variante-2-rm--rf"&gt;Variante 2: &lt;code&gt;rm -rf&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Und so probiere ich es mit Gewalt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /.gitea/workflows/deploy-lectures.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;CHECKOUT ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v4&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;./tmp&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;REMOVE TEMPORARY FILES ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; rm -rfv ./tmp/.git ./tmp/.gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nun endlich taucht der &lt;code&gt;.git&lt;/code&gt;-Ordner auf meinem Webserver nicht mehr auf und mein &amp;ldquo;Repo-Leak&amp;rdquo; ist geflickt. Nochmal danke an &lt;em&gt;Repo Lookout&lt;/em&gt;!&lt;/p&gt;&#10;&lt;h2 id="fazit"&gt;Fazit&lt;/h2&gt;&#10;&lt;p&gt;Ich hatte hier das Problem, dass der versteckte &lt;code&gt;.git&lt;/code&gt;-Ordner, wo Konfiguration und Struktur von Repositories gespeichert werden, unbeabsichtigt und ohne mein Wissen auf meinem Webserver veröffentlicht waren.&lt;/p&gt;&#10;&lt;p&gt;Ich habe hier zwei funktionierende Optionen zur Lösung des Problems vorgestellt:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Ein Zugriffsverbot auf dem Webserver einrichten&lt;/li&gt;&#10;&lt;li&gt;Die Pipeline so umbauen, dass sie den &lt;code&gt;.git&lt;/code&gt;-Ordner nach erfolgtem Ausrollen selbstständig löscht.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Die zweite Option ist zwar etwas aufwendiger einzubauen, doch packt sie das Problem bei der Wurzel, anstatt lediglich die Symptome zu beheben. Außerdem entspricht sie dem ersten Grundsatz aus dem Datenschutz: Datenminimierung geht vor Schutzmaßnahmen.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Was nicht existiert, kann nicht verloren gehen&amp;rdquo; - Schallbert&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;</description></item><item><title>Logrotate-Rekursion</title><link>https://blog.schallbert.de/logrotate-mistake/</link><pubDate>Mon, 18 Nov 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/logrotate-mistake/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-11-18_logrotate-recursion.jpg"&#10; class="post-cover"&#10; alt="Image: Logrotate creating .1 recursively"&#10; title="Logrotate-Rekursion" /&gt;&#10;&lt;h2 id="was-funktioniert-nicht"&gt;Was funktioniert nicht?&lt;/h2&gt;&#10;&lt;p&gt;Eigentlich wollte ich &lt;a href="https://github.com/logrotate/logrotate" target="_blank" rel="noopener noreferrer" class="external-link"&gt;logrotate&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; nutzen, um meine Logs täglich zu bereinigen und so Datensparsamkeit umzusetzen: So können IP-Adressen, die ich für&amp;rsquo;s &lt;a href="https://blog.schallbert.de/server-protection/"&gt;Banning&lt;/a&gt; brauche, einheitlich und automatisiert nach kurzer Zeit gelöscht werden. Die Server-Logs interessieren mich ansonsten überhaupt nicht und stehlen mir nur Speicherplatz.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-11-18_logrotate-recursion.jpg" alt="Image: The way logrotate does the rotation looks all too recursive." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Mit meiner aktuellen Konfiguration erstellt &lt;code&gt;logrotate&lt;/code&gt; zwar eine Logdatei am Tag, nennt sie aber &lt;code&gt;xyz.log.1, xyz.log.1.1, xyz.log.1.1.1&lt;/code&gt; anstatt der erwarteten &lt;code&gt;xyz.log.1, xyz.log.2, xyz.log.3&lt;/code&gt;. Somit funktioniert die Löschroutine nicht mehr und die Anzahl der Logs wächst stetig.&lt;/p&gt;&#10;&lt;h2 id="und-warum"&gt;Und warum?&lt;/h2&gt;&#10;&lt;p&gt;Ich vermute, dass meine &lt;em&gt;logrotate&lt;/em&gt;-Konfiguration fehlerhaft ist. Im Moment sieht sie wie folgt aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## LOGROTATE file named &amp;#39;blog&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;blog/* {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# it&amp;#39;s ok if the file doesn&amp;#39;t already exist&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;missingok&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Sets the logs to rotate in intervals&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;daily&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Tells the system to remove old logs and only keep the most recent rotated logs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;rotate 7&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Rotated logs will be compressed and kept on disk if they are 10 MB or less.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;size 10M&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# compress and delaycompress: These two options are used together and indicate that &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# rotated logs should be compressed (gzip) except for the most recent one.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;compress&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;delaycompress&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wie üblich nehme ich als Erstes die Bedienungsanleitung zur Hilfe, die ich mittels &lt;code&gt;man logrotate&lt;/code&gt; in der Konsole aufrufe.&#10;Dort steht, dass die zu rotierende Datei direkt angegeben (und nicht nur per &amp;ldquo;Wildcard&amp;rdquo; gewählt &lt;code&gt;*&lt;/code&gt;) werden kann. Außerdem kann ein Logverhalten auch auf mehrere Dateipfade angewendet werden, indem sie durch Leerzeichen getrennt vor die geschweifte Klammer gesetzt werden.&lt;/p&gt;&#10;&lt;p&gt;Das Sternchen scheint genau mein Problem mit den kaskadierenden Logs zu erzeugen: Dadurch wird nicht nur die eigentliche Zieldatei &lt;code&gt;access.log&lt;/code&gt; rotiert sondern auch alle bereits rotierten Dateien nochmal angefasst.&lt;/p&gt;&#10;&lt;h2 id="die-lösung"&gt;Die Lösung&lt;/h2&gt;&#10;&lt;p&gt;Entsprechend der Bedienungsanleitung schreibe ich die Bearbeitungsvorschrift für Logrotate so um, dass ich statt fünf eigenen Vorschriften für die jeweiligen Logs nur zwei Dateien erzeuge. Diese sind zudem einfacher und kürzer als die obige Ausgangsversion.&lt;/p&gt;&#10;&lt;p&gt;Beispiel:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# rotate webserver log files.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;gitea/access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;blog/access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;landing/access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;lectures/access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Sets the logs to rotate in intervals&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;daily&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Tells the system to remove old logs and only keep the most recent rotated logs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;rotate 7&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# compress and delaycompress: These two options are used together and indicate that &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# rotated logs should be compressed (gzip) except for the most recent one.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;compress&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;delaycompress&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="konfigurations-update"&gt;Konfigurations-Update&lt;/h3&gt;&#10;&lt;p&gt;Dies kann ich durch meine vorhandene Server-Konfigurations-Pipeline durch einfachen Push ins Repository auf dem Server ausrollen.&#10;Nun muss ich die Dateien dort nur noch händisch nach &lt;code&gt;/etc/logrotate.d&lt;/code&gt; kopieren.&lt;/p&gt;&#10;&lt;h3 id="logrotate-testen"&gt;Logrotate testen&lt;/h3&gt;&#10;&lt;p&gt;Jetzt wäre es noch schön, wenn ich ausprobieren könnte, ob die von mir vorgenommenen Änderungen auch tatsächlich funktionieren. Dafür nehme ich erneut die Bedienungsanleitung von &lt;em&gt;logrotate&lt;/em&gt; zur Hilfe und tippe:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;logrotate -d -v &amp;lt;LOGROTATE-DESCRIPTOR-FILE&amp;gt;&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Mit aktivierten &lt;em&gt;debug&lt;/em&gt; und &lt;em&gt;verbose&lt;/em&gt; Flags wird logrotate die in der Konfigurationsdatei angegebene Rotationsvorschrift anwenden und Rückmeldung geben, tatsächlich auf dem Dateisystem aber keine Änderungen oder Rotationen durchführen. Bei mir sieht das Ergebnis des Tests dann etwa so aus:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;lectures/access.log &#10; after 1 days (7 rotations)&#10;empty log files are rotated, old logs are removed&#10;considering log gitea/access.log&#10; Now: 2024-11-28 17:19&#10; Last rotated at 2024-11-27 00:00&#10; log needs rotating&#10;considering log blog/access.log&#10; Now: 2024-11-28 17:19&#10; Last rotated at 2024-11-16 00:00&#10; log needs rotating&#10;considering log landing/access.log&#10; Now: 2024-11-28 17:19&#10; Last rotated at 2024-11-07 00:00&#10; log needs rotating&#10;considering log lectures/access.log&#10; Now: 2024-11-28 17:19&#10; Last rotated at 2024-09-21 00:00&#10; log needs rotating&#10;rotating log gitea/access.log, log-&amp;gt;rotateCount is 7&#10;[...]&#10;renaming lectures/access.log.7.gz to lectures/access.log.8.gz (rotatecount 7, logstart 1, i 7), &#10;renaming lectures/access.log.6.gz to lectures/access.log.7.gz (rotatecount 7, logstart 1, i 6), &#10;renaming lectures/access.log.5.gz to lectures/access.log.6.gz (rotatecount 7, logstart 1, i 5), &#10;renaming lectures/access.log.4.gz to lectures/access.log.5.gz (rotatecount 7, logstart 1, i 4), &#10;renaming lectures/access.log.3.gz to lectures/access.log.4.gz (rotatecount 7, logstart 1, i 3), &#10;renaming lectures/access.log.2.gz to lectures/access.log.3.gz (rotatecount 7, logstart 1, i 2), &#10;renaming lectures/access.log.1.gz to lectures/access.log.2.gz (rotatecount 7, logstart 1, i 1), &#10;renaming lectures/access.log.0.gz to lectures/access.log.1.gz (rotatecount 7, logstart 1, i 0)&#10;[...]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die Ausgabe zeigt mir, dass sowohl alle gewünschten Logdateien von &lt;em&gt;logrotate&lt;/em&gt; berücksichtigt werden und auch, dass die Rotation korrekt und täglich durchgeführt wird.&lt;/p&gt;&#10;&lt;p&gt;Wieder etwas gelernt 😃&lt;/p&gt;&#10;</description></item><item><title>Klingenbutler</title><link>https://blog.schallbert.de/klingenbutler/</link><pubDate>Mon, 14 Oct 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/klingenbutler/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_klingenbutler-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: First prototype of Klingenbutler"&#10; title="Klingenbutler" /&gt;&#10;&lt;h2 id="warum-klingenbutler"&gt;Warum Klingenbutler?&lt;/h2&gt;&#10;&lt;p&gt;Nachdem das Problem der fehlenden Ablage für feste Shampoos in meiner Dusche gelöst hatte - daraus entstand der &lt;a href="https://blog.schallbert.de/seifenbutler-pilot-run/"&gt;Seifenbutler&lt;/a&gt; - fielen mir dort herumfliegende Rasierer mehr und mehr störend auf.&lt;/p&gt;&#10;&lt;p&gt;Liegen sie unten auf dem Rand der Dusche, bildet sich um sie herum eine Lache. Dort entstehen dann besonders viele Kalkflecken und die Reinigung geht schlechter von der Hand. Legt man die auf die Armaturen, fallen sie früher oder später herunter und werden beschädigt. Selbiges gilt für die wackelige Ablage auf dem Rand der Duschkabine.&lt;/p&gt;&#10;&lt;p&gt;Es liegt nahe, die Klemmmechanik des Seifenbutler wiederzuverwenden. Somit muss nur der &amp;ldquo;vordere Teil&amp;rdquo; neu designt werden und schon erhalte ich eine formschöne Ablage für Rasierer - den &lt;em&gt;Klingenbutler&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h2 id="das-design"&gt;Das Design&lt;/h2&gt;&#10;&lt;p&gt;Ich mag die runde Formgebung des Seifenbutler und möchte den Klingenbutler ähnlich gestalten. Die Rasierer sollen allerdings nicht allzu leicht versehentlich berührt werden können. Daher entschließe ich mich, sie möglichst &amp;ldquo;innen&amp;rdquo; anzubringen. Hierfür erstelle ich einen Ring mit Öffnung, der wiederum drei Einkerbungen für Rasierer besitzt. Die Radien der Einkerbungen gestalte ich so, dass alle mir zur Verfügung stehenden Nassrasierer dort hineinpassen und sogar Zahnbürsten sich ohne Kontakt der Borsten zum Klingenbutler einhängen lassen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_klingenbutlercad.jpg" alt="Image: CAD drawing of Klingenbutler V1.0" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Öffnung (in der Zeichnung links) gestalte ich für Ober- und Unterschale so, dass die Oberschale in allen möglichen geschlossenen Positionen stets die Unterschale überdeckt. Damit vermeide ich überstehende Kanten bei besonders großen oder kleinen Duschstangendurchmessern.&lt;/p&gt;&#10;&lt;h3 id="einrasten-der-rasierer"&gt;Einrasten der Rasierer&lt;/h3&gt;&#10;&lt;p&gt;Schwieriger war schon die Planung von Rasthilfen, sodass die Rasierer sich nicht von allein lösen können. Hier entschied ich mich beim ersten Entwurf für eine Kerbe in Form eines &lt;a href="https://en.wikipedia.org/wiki/Tetrahedron" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Doppeltetraeders&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Im Bild oben ist die Kerbe mit &amp;ldquo;drain&amp;rdquo; beschriftet. Sie besitzt auf ca. &lt;code&gt;15mm&lt;/code&gt; Länge ein Gefälle von &lt;code&gt;3mm&lt;/code&gt;, lässt Spritzwasser ablaufen und ermöglicht eine schnelle Trocknung der Rasierer.&lt;/p&gt;&#10;&lt;h3 id="g2-kurven"&gt;G2-Kurven&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_twodifferentradii.jpg" alt="Image: two different radii joined. There is a visible kink due to differences in slope at that point." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Als ich einen meiner Entwürfe kurz vor Einstieg in die Fertigung einem Bekannten von mir zeigte - er ist 3D-Designer von Beruf - fiel ihm sofort auf, dass ich keinen fließenden Übergang zweier Kurvenradien vorgesehen hatte. Er meinte, dass in solchen Fällen &amp;ldquo;G2-Kurven&amp;rdquo; im CAD eingesetzt werden und das Modell so viel harmonischere Übergänge bekäme.&lt;/p&gt;&#10;&lt;p&gt;Im Fachjargon scheint man dabei von &lt;a href="https://en.wikipedia.org/wiki/Smoothness#Geometric_continuity" target="_blank" rel="noopener noreferrer" class="external-link"&gt;geometrischer Stetigkeit&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zu sprechen. Sogleich fragte ich die Softwarefirma hinter meinem CAD-Werkzeug, ob sie &lt;a href="https://hobbyline.info/forum/index.php?thread/822-wie-mache-ich/&amp;amp;postID=11565#post11565" target="_blank" rel="noopener noreferrer" class="external-link"&gt;so etwas ebenfalls unterstütze&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und siehe da - es gibt das Werkzeug &lt;em&gt;tangentenstetige Verbindung erzeugen&lt;/em&gt;. Von nun an werde ich dieses Werkzeug verwenden, wenn ich Radien miteinander verbinde.&lt;/p&gt;&#10;&lt;p&gt;Das Werkzeug erzeugt eine &lt;a href="https://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Bezier-Kurve&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, deren Endpunkte tangential an die beiden Kreissegmente anschließen und die Knickstelle auf diese Weise beseitigen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_twodifferentradiig2.jpg" alt="Image: radii joined with tangential continuity using a Bezier Curve" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Diese Herangehensweise hat außer der gefälligeren Optik sogar noch einen Vorteil in der Fertigung: Beim Abfahren von Knickstellen muss die CNC-Maschine plötzliche Richtungsänderungen durchführen, ähnlich wie bei verbundenen Liniensegmenten, die in einem Winkel aufeinandertreffen. Somit muss abgebremst und im neuen Kurvenradius beschleunigt werden. Durch Verwendung tangentenstetiger Verbindungen hingegen werden Beschleunigungsspitzen deutlich abgemildert und die Maschine läuft mit gleichmäßigerer Geschwindigkeit durch.&lt;/p&gt;&#10;&lt;p&gt;Hat man wie mit dem &lt;em&gt;Klingenbutler&lt;/em&gt; Projekte mit vielen &amp;ldquo;Knickstellen&amp;rdquo; in der Zeichnung, so kommen hier schnell ein paar Sekunden Fertigungszeit pro Teil zusammen, die leicht einzusparen sind - dazu vielleicht später mal mehr.&lt;/p&gt;&#10;&lt;h3 id="erster-prototyp---version-10"&gt;Erster Prototyp - Version 1.0&lt;/h3&gt;&#10;&lt;p&gt;Die Fertigung trat ich mit meinem &amp;ldquo;klassischen&amp;rdquo; Prototypenmaterial an: gegossenes PMMA (Acrylglas) mit &lt;code&gt;5mm&lt;/code&gt; Stärke in opak weiß. Verwendet man einigermaßen passende Parameter, so verhält sich das Material äußerst gutmütig. Gleichzeitig legt das Material schonungslos alle Fehler offen: Hier eine Fase etwas zu weit, dort leichte Rattermarken durch Herausbeschleunigen der Maschine aus einer Ecke, im Boden des Gewindes gefangene Späne - All das tritt die etwas lichtdurchlässige Materialkomposition und starke Reflexionen an Materialkanten stark hervor.&#10;Daher ist dieses Material hervorragend geeignet, Design- und Fertigung von Produkten zu verbessern.&lt;/p&gt;&#10;&lt;p&gt;Durch meine Erfahrungen mit dem Seifenbutler gingen Niederhalten während der Fertigung und Zusammenbau des Klingenbutler problemlos vonstatten. Zahnbürsten und Rasierer sind von nun an an einem festen Platz in der Dusche zu finden 🙂.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_firstproto.jpg" alt="Image: First prototype of Klingenbutler V1.0" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="version-11---fertigung-in-bambus"&gt;Version 1.1 - Fertigung in Bambus&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_bambus.jpg" alt="Image: First time Bamboo on my CNC" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Eigentlich will ich ja weg von Kunststoff. Die Produktion meiner Seifenbutler habe ich im Laufe des Jahres zwar bereits auf &amp;ldquo;greencast&amp;rdquo; Acrylglas umgestellt - also PMMA, das selbst aus 100% recycelter Ware besteht - aber besser wäre ja noch, gar keinen Kunststoff einzusetzen.&lt;/p&gt;&#10;&lt;p&gt;Dafür ließ ich mir ein paar Muster &amp;ldquo;Moso-Bambus&amp;rdquo; kommen: Stäbchen-Leimholz in drei Schichten kreuzverleimt, ähnlich wie Sperrholz, knapp &lt;code&gt;7.5mm&lt;/code&gt; stark. Für die Fertigung in Bambus legte ich die Passtoleranzen etwas großzügiger aus (&lt;code&gt;0.1mm&lt;/code&gt; Untermaß )und wandelte metrische Gewinde im Plan schlicht in einfache Bohrungen für Holzschrauben um.&lt;/p&gt;&#10;&lt;h3 id="unnötiges-weglassen"&gt;Unnötiges weglassen&lt;/h3&gt;&#10;&lt;p&gt;Beim Zusammenbau des ersten Prototypen wurde mir klar, dass das dritte Bajonett überflüssig geworden war. Zusammen mit ein paar kleineren, weiteren Änderungen entwickelte ich Version 1.1:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Dritte Verschraubung im Rastkreis weglassen&lt;/li&gt;&#10;&lt;li&gt;Rastnasen/Ablaufrinne: Durchgängige Gravur ohne Stoppen bei &amp;ldquo;Schlüsselloch&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Bajonett: Innenseiten auf Rückseite anfasen und so die Oberfläche durchbrechen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="drehzahl-und-vorschub"&gt;Drehzahl und Vorschub&lt;/h3&gt;&#10;&lt;p&gt;Für das Vollnutfräsen nutze ich einen DLC-beschichteten (Diamond-like-carbon) &lt;code&gt;4mm&lt;/code&gt; Zweizahnfräser ohne Drall mit &lt;code&gt;12mm&lt;/code&gt; Schneidenlänge. Ich betreibe ihn bei &lt;code&gt;S30000U/min&lt;/code&gt; mit einem Vorschub von&lt;code&gt;F3000mm/min&lt;/code&gt;. Ich gehe direkt in die volle Materialtiefe mit &lt;code&gt;Z+ 7.5mm&lt;/code&gt; und fahre Bohrungen und Materialrampen mit &lt;code&gt;20°&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Fasen bringe ich mittels eines &lt;code&gt;90°&lt;/code&gt; Gravurfräser, dreischneidig, Durchmesser &lt;code&gt;10mm&lt;/code&gt; an. Ihn betreibe ich mit &lt;code&gt;F5000mm/min&lt;/code&gt; bei &lt;code&gt;S15000U/min&lt;/code&gt; und einem Mittenabstand von &lt;code&gt;2mm&lt;/code&gt;, was bei einer glatten Kante mit &lt;code&gt;0.5mm&lt;/code&gt; Breite schon bedeutet, dass er &lt;code&gt;2.5mm&lt;/code&gt; Luft &amp;ldquo;nach unten&amp;rdquo; in Z-Richtung benötigt.&lt;/p&gt;&#10;&lt;p&gt;Für Bohrungen, Taschen und kleine Aussparungen verwende ich einen hinterschliffenen &lt;code&gt;2mm&lt;/code&gt; Zweischneider Fischschwanz mit &lt;code&gt;15°&lt;/code&gt; Drall und &lt;code&gt;20mm&lt;/code&gt; Schneidenlänge. Ihn betreibe ich mit vorsichtigen &lt;code&gt;1700mm/min&lt;/code&gt; bei &lt;code&gt;S30000U/min&lt;/code&gt; und einer Tiefenzustellung von nur &lt;code&gt;Z+1.5mm&lt;/code&gt;. Auch hier tauche ich mit &lt;code&gt;20°&lt;/code&gt; ins Material ein.&lt;/p&gt;&#10;&lt;h3 id="fertigung"&gt;Fertigung&lt;/h3&gt;&#10;&lt;p&gt;Das Arbeiten im mir bis Dato noch völlig unbekannten Material &amp;ldquo;Bambus&amp;rdquo; ging überraschend leicht von der Hand. Dank meiner noch neuen und sehr scharfen Fräser waren keinerlei Ausrisse zu beklagen und das Material selbst hatte ebenfalls kaum Defekte oder Schadstellen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/ayYr653wLVLzVpmiMkhyz8" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Klingenbutler CNC-fräsen&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Bei der Bearbeitung entstanden recht feine Späne, die ein wenig an Staub von der Verarbeitung von MDF-Platten erinnern. Ich bin mir noch nicht sicher, ob ich mit der Drehzahl weiter heruntergehen sollte oder ob dies an den geraden Schneiden des Fräsers liegt. Dieser muss Späne beim Fräsen in der Vollnut unter Umständen mehrere Umdrehungen lang mit sich führen, bevor er sie in der Nut hinter sich lassen kann.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_klingenbutler.jpg" alt="Image: Klingenbutler V1.1 Bamboo edition" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Bohrungen jedenfalls sind mit diesem Fräser und unveränderten Parametern nicht zu empfehlen. Sämtliche Bohrlöcher haben verkohlte Ränder, was auf viel zu hohe Drehzahlen schließen lässt. Zudem habe in einem Stück gebohrt, anstatt den Fräser mehrfach zurückzuziehen. Nehmen wir den Dreisatz zur Hilfe: Beim Vorschub von &lt;code&gt;FZ+2000mm/min&lt;/code&gt;, &lt;code&gt;S30000/U/min&lt;/code&gt; und &lt;code&gt;Z7.5mm&lt;/code&gt; führt der Fräser &lt;code&gt;112.5&lt;/code&gt; Umdrehungen im Bohrloch aus, reiben die Schneiden also 225 mal sinnlos an der Wand des Bohrloches entlang - abtragen tut ja nur die Zentrumsschneide unten. Zudem werden die Späne ja nicht ausgeworfen sondern die ganze Zeit über mitgeführt.&lt;/p&gt;&#10;&lt;p&gt;Für den nächsten Prototypen werde ich Bohrlöcher also entweder mit Rückzügen und drastisch reduzierter Drehzahl fertigen oder schlicht einen kleineren Fräser verwenden.&lt;/p&gt;&#10;&lt;h3 id="ergebnis"&gt;Ergebnis&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_bambusdetail.jpg" alt="Image: Klingenbutler detail view (underside)" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Das kann sich sehen lassen. Ich habe die Fase zum Kantenbrechen aus der Fertigung in Acrylglas übernommen. Sie fällt mit &lt;code&gt;0.5mm&lt;/code&gt; sehr schmal aus. Trotzdem zeigt sich auch im Detail ein sehr sauberes Bild. Ober- und Unterschale des Klingenbutler lassen sich weich gegeneinander verschieben und auch der Klemmer, mein Sorgenkind noch &lt;a href="https://makertube.net/w/64TS5d5cp2eNqf8SSPvY77" target="_blank" rel="noopener noreferrer" class="external-link"&gt;aus diesem Video&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ließ sich ohne Ausrisse fräsen.&lt;/p&gt;&#10;&lt;p&gt;Der Zusammenbau geht problemlos vonstatten und auch die Holzschrauben lassen sich leicht in die vorgebohrten Löcher eindrehen. Der Klingenbutler aus Bambus hält an der Duschstange sogar noch besser als sein Pendant aus Acrylglas - der raueren Oberfläche und höheren Materialstärke sei dank.&lt;/p&gt;&#10;&lt;p&gt;Bleibt abzuwarten, ob das Material auch im Feuchtraum langlebig genug ist. Für Schneidbretter in der Küche wird es ja noch immer gern verwendet.&lt;/p&gt;&#10;&lt;h2 id="ausblick"&gt;Ausblick&lt;/h2&gt;&#10;&lt;p&gt;Die Verwendung von Bambus hat mich sehr motiviert hinterlassen: Ein toller Werkstoff. Hart und fest aber dabei leicht, gut maschinell zu bearbeiten, formstabil und kaum Materialdefekte: So wünscht man sich das Arbeiten mit Naturmaterialien!&lt;/p&gt;&#10;&lt;p&gt;Dieser Prototyp landet nun zum Dauertest bei uns in der Dusche. In der Zwischenzeit verfeinere ich das Design weiter.&lt;/p&gt;&#10;&lt;h3 id="version-12"&gt;Version 1.2&lt;/h3&gt;&#10;&lt;p&gt;Für V1.2 habe ich folgende kleine Verbesserungen in Planung:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Bohrungen: Kleineren Fräser wählen (zu Lasten der Zykluszeit)&lt;/li&gt;&#10;&lt;li&gt;Befestigung: Ausschnitt fräsen, sodass das Rändelrad nicht mehr übersteht&lt;/li&gt;&#10;&lt;li&gt;Aussparung innen an der Oberschale ist überflüssig&lt;/li&gt;&#10;&lt;li&gt;Unterschiedliche Radien mit G2-Kurven übergehen lassen&lt;/li&gt;&#10;&lt;li&gt;Rastnasen durch harmonischere, runde Ablaufrinne ersetzen&lt;/li&gt;&#10;&lt;li&gt;Maserung ausrichten: Alle Teile &amp;ldquo;senkrecht&amp;rdquo; fertigen&lt;/li&gt;&#10;&lt;li&gt;Die Klemmer nun in der ersten Aufspannung fräsen und um &lt;code&gt;0.04mm&lt;/code&gt; aufdicken&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Auch hier ist das Ziel, mittels stetiger Verbesserung hin zu einem guten Produkt zu kommen.&lt;/p&gt;&#10;</description></item><item><title>Neue Startseite</title><link>https://blog.schallbert.de/announcements/2024-10-01-new-landing-page/</link><pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/announcements/2024-10-01-new-landing-page/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2024-10-01-schallbert_landing-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Thumb for schallbert.de domain update"&#10; title="Neue Startseite" /&gt;&#10;&lt;h2 id="oktober-2024--neues-website-design-"&gt;Oktober-2024 &amp;mdash; Neues Website-Design &amp;mdash;&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2024-10-01-schallbert_landing.jpg" alt="Image: Newly designed schallbert.de landing page" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;a href="https://schallbert.de" target="_blank" rel="noopener noreferrer" class="external-link"&gt;schallbert.de&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; hat jetzt endlich die &amp;ldquo;Baustellenphase&amp;rdquo; überwunden. Ab jetzt steht nicht nur dieser Blog zur Verfügung, sondern auch die zusätzlichen Kategorien &lt;a href="https://blog.schallbert.de/announcements/2024-03-01-seifenbutler-out-now/"&gt;Shop&lt;/a&gt; und &lt;a href="https://lectures.schallbert.de" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Vorträge&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="shop"&gt;Shop&lt;/h3&gt;&#10;&lt;p&gt;Dieser Bereich befindet sich noch in einer frühen Planungsphase. Ich überlege hier, meinen &lt;a href="https://blog.schallbert.de/projects/seifenbutler/"&gt;Seifenbutler&lt;/a&gt;, Rohlinge für CNC-Gravuren und ein paar meiner Software-Projekte wie zum Beispiel &lt;a href="https://blog.schallbert.de/projects/qr-codengrave/"&gt;QR-Codengrave&lt;/a&gt; als App zum Kauf anzubieten.&lt;/p&gt;&#10;&lt;p&gt;Ich habe mich bis jetzt weder auf ein Webshop-Framework festgelegt, noch bin ich momentan in der Lage, viel Zeit in den Aufbau der Seite oder die Fertigung von Produkten zu stecken. Von daher stellt der Shop ein Langfristiges Ziel dar. Als Bezugsquelle für Seifenbutler steht weiterhin &lt;a href="https://mopemaster.com/produkt/seifenbutler-seifenhalter-dusche/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Mopemasters Onlineshop&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Verfügung.&lt;/p&gt;&#10;&lt;h3 id="vorträge"&gt;Vorträge&lt;/h3&gt;&#10;&lt;p&gt;Diese Subdomain habe ich erstellt, um meine Vorträge zu sammeln. Sie nutzen ein anderes Framework als mein Blog und sind daher besser separat untergebracht.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Schallbert&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>NVME-Festplatte kaputt?</title><link>https://blog.schallbert.de/broken-nvme/</link><pubDate>Tue, 10 Sep 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/broken-nvme/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-09-10_nvme-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: My NVME-SSD that suddenly broke down"&#10; title="NVME-Festplatte kaputt?" /&gt;&#10;&lt;h2 id="das-ereignis"&gt;Das Ereignis&lt;/h2&gt;&#10;&lt;p&gt;Mitte April 2024:&lt;/p&gt;&#10;&lt;p&gt;Nach einem Systemupdate fährt mein Laptop plötzlich nicht mehr hoch. Der Bootloader &lt;em&gt;grub-core&lt;/em&gt; meldet, er habe Probleme beim Schreiben von Sektoren.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;error: ../grub-core/efi/efidisk.c:638: failure writing sector 0xXXXXXXXX&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Beim Reboot bleibt mir der Bildschirm nach Anzeige des Firmenlogos mit blinkendem Cursor schwarz. Wenn ich hinter die Maske schaue, dann schlägt das Laden diverser Services fehl:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2024-09-10_nvme-boot-errors.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Boot errors due to read-only nvme&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="erste-analyse"&gt;Erste Analyse&lt;/h2&gt;&#10;&lt;p&gt;Ich vermutete ein Problem beim Update des Rechners und führte aus dem &lt;a href="https://en.wikipedia.org/wiki/UEFI" target="_blank" rel="noopener noreferrer" class="external-link"&gt;UEFI&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; von Lenovo heraus einen Festplattentest durch. Ergebnis:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;The media check is complete, the result is: FAIL&#10;&lt;/code&gt;&lt;/pre&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-09-10_diskcheck-fail.jpg" alt="Image: disk check tool diagnoses a defective device" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Eher kein Problem mit dem Betriebssystem sondern eines mit der Platte. Mist. Muss ich um meine Daten bangen? Ich habe zwar Backups herumliegen, das letzte ist aber drei Wochen alt und somit sind einige Stunden Arbeit auf der Kippe. Zudem kostet mich ein Wiederaufsetzen einiges an Zeit. Daher probiere ich erst einmal, ob sich noch etwas retten lässt.&lt;/p&gt;&#10;&lt;h3 id="selbsttest"&gt;Selbsttest&lt;/h3&gt;&#10;&lt;p&gt;Ich lasse also die &lt;a href="https://en.wikipedia.org/wiki/Self-Monitoring,_Analysis_and_Reporting_Technology" target="_blank" rel="noopener noreferrer" class="external-link"&gt;SMART&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Tests für die Platte mit &lt;code&gt;smartctl -x /dev/nvm0&lt;/code&gt; durchlaufen und weiß nun, dass sie etwa &lt;code&gt;7600&lt;/code&gt; Betriebsstunden hinter sich und &lt;code&gt;240GB&lt;/code&gt; nutzbare Kapazität hat. Die unter dem Namen &lt;code&gt;nvme0n1&lt;/code&gt; eingesetzte Disk meint, sie sei nun &amp;ldquo;read-only&amp;rdquo;. Und zwar sämtliche Partitionen inklusive Kernel. Na klasse. Das Ergebnis meiner Online-Suche war in etwa:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Die Platte ist irreparabel beschädigt. Es muss eine Neue her.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="reparaturversuch"&gt;Reparaturversuch&lt;/h3&gt;&#10;&lt;p&gt;Man muss ja nicht alles glauben, was im Netz steht. Also nehme ich das Lenovo-eigene Reparaturwerkzeug für Festplatten zur Hand. Es lässt sich aus dem UEFI heraus aufrufen und firmiert unter dem Namen &lt;a href="https://download.lenovo.com/pccbbs/thinkvantage_en/userguide_uefi_v04.08.000.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Bad Block Recovery Tool&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Das Programm scannt die Platte auf defekte Sektoren und versucht, betroffene Blöcke wiederherzustellen. Der Durchlauf benötigt für die letzten 10% fast eine halbe Stunde.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-09-10_badblockrecovery_summary.jpg" alt="Image: Bad Block Recovery tool&amp;#39;s summary for my disk" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-09-10_badblockrecovery_detail.jpg" alt="Image: Bad Block Recovery tool&amp;#39;s output messages" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ergebnis: Wiederherstellung fehlgeschlagen. Ich führe das Werkzeug erneut aus. Und noch ein drittes Mal im langsamen &lt;code&gt;pedantic&lt;/code&gt;-Modus. Immer wieder ohne Erfolg. Ständig bekomme ich &lt;code&gt;critical medium error&lt;/code&gt; angezeigt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-09-10_badblockrecovery_log.jpg" alt="Image: Bad Block Recovery tool&amp;#39;s log" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Ob dies alles nicht klappt, weil sich die Platte in den Read-only mode gesetzt hat?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Da ich nicht mehr booten kann und keine Konsole zur Verfügung habe, sind meine Möglichkeiten mit Bordmitteln nun erschöpft.&lt;/p&gt;&#10;&lt;h3 id="einwurf-der-read-only-mode-bei-ssds"&gt;Einwurf: Der Read-only mode bei SSDs&lt;/h3&gt;&#10;&lt;p&gt;Laut Internet &lt;a href="https://www.disktuna.com/a-write-protected-ssd-nvme-read-only/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;(DiskTuna)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist der &lt;em&gt;read only mode&lt;/em&gt; zur Schadensbegrenzung vorhanden. Stellt der Controller des Speichergerätes einen schwerwiegenden Fehler fest, versetzt er die Platte in Nur-Lesezugriff. Somit wird das Risiko minimiert, dass weitere Blöcke korrumpiert oder mit in den Tod gerissen werden.&lt;/p&gt;&#10;&lt;p&gt;Im Forum &lt;a href="https://superuser.com/questions/1802470/ssd-in-read-only-mode-after-power-failure" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Superuser&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; beschreibt jemand einen identischen Fehler zu meinem, und tatsächlich ist da wohl nichts mehr zu machen.&lt;/p&gt;&#10;&lt;h2 id="datenrettung-per-boot-stick"&gt;Datenrettung per Boot-Stick&lt;/h2&gt;&#10;&lt;p&gt;Weiter geht&amp;rsquo;s mit einem USB-Stick, den ich von einem anderen Rechner aus zum Linux-Boot-Stick mache. Ich stopfe ihn an meinen Laptop und kann ihn endlich wieder hochfahren. Als Medium zur Datenrettung schließe ich eine externe Festplatte an.&lt;/p&gt;&#10;&lt;h3 id="übersicht-verschaffen-mit-fdisk"&gt;Übersicht verschaffen mit &lt;em&gt;fdisk&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Zuerst schaue ich, ob alle Platten und Partitionen auftauchen. Dafür tippe ich das Kommando &lt;code&gt;sudo fdisk -l&lt;/code&gt; in die Konsole.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-09-10_fdisk.jpg" alt="Image: fdisk&amp;#39;s output on my live system" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Glück gehabt, alles ist noch da! Vielleicht kann ich die Dateien ja einfach von der defekten Festplatte herunter kopieren?&lt;/p&gt;&#10;&lt;h3 id="kopieren-mit-cp"&gt;Kopieren mit &lt;em&gt;cp&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Probieren wir&amp;rsquo;s aus.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo cp -r -v /mnt/nvme0n1p3/Documents/my/important/files /mnt/externalHdd/save&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ein paar Sekunden denke ich, dass alles glatt geht. Doch dann bricht &lt;code&gt;cp&lt;/code&gt; mit Fehlermeldung ab, das Volume sei nicht verfügbar. Tatsächlich ist die Platte nicht mehr gemountet. Ich kann sie allerdings direkt wieder finden und mounten. Ein paar Versuche später habe ich meine wirklich wichtigen Daten gesichert, der Kopiervorgang bricht aber stets nach ein paar Sekunden ab. Für die ganze Platte würde mir dieses Vorgehen viel zu lange dauern.&lt;/p&gt;&#10;&lt;p&gt;Genau dieses Verhalten - mounten, ein paar Sekunden kopieren, Platte verschwindet - habe ich online in ein paar Foreneinträgen bestätigt bekommen.&lt;/p&gt;&#10;&lt;p&gt;Also muss eine andere Lösung her.&lt;/p&gt;&#10;&lt;h3 id="daten-retten-mit-ddrescue"&gt;Daten retten mit &lt;em&gt;ddrescue&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-09-10_ddrescue.jpg" alt="Image: ddrescue output when saving my broken SSD&amp;#39;s files to an external HDD." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Nach kurzer Online-Suche installiere ich das Programm &lt;a href="https://www.gnu.org/software/ddrescue/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ddrescue&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;In der zugehörigen Dokumentation stehen einige Dinge sehr klar drin. Dass man z.B. verstanden haben sollte was es genau tut, bevor man es verwendet. Oder das Folgende:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Never try to repair a file system on a drive with I/O errors; you will probably lose even more data. &amp;quot; - ddrescue Manual&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Jetzt stellt sich für mich die Frage, ob ich oben mit dem Block-Reparaturversuch genau so einen Fehler begangen habe. Oder bin ich mit der Bad Block Recovery auf der sicheren Seite, da hier von Datenblöcken auf der Platte und nicht vom &amp;ldquo;darüber liegenden&amp;rdquo; Dateisystem die Rede ist?&#10;Hier steht auch, dass die sogenannte &lt;em&gt;Mapfile&lt;/em&gt; eine Kernfunktion des Programmes ist. Nach Abbruch, Neustart, für die Zusammenführung von Backups und zum Verkürzen der Datenrettung bietet sie einen großen Zeitvorteil. Daher sei es keine schlechte Idee, sich die Mapfile schon bei Erstellung einer Offlinhe-Sicherheitskopie zu erzeugen und gut wegzulegen. Insgesamt fand ich das Tool extrem hilfreich, effektiv und leicht zu bedienen und kann es an dieser Stelle nur weiterempfehlen.&lt;/p&gt;&#10;&lt;p&gt;Zurück zur Sache: Ich finde einen ganz einfachen Befehl, die komplette Datenpartition meines beschädigten NVME-Speichers auf eine externe Festplatte zu kopieren:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ddrescue --sparse /dev/nvme0n1p3 /run/media/liveuser/myExternalHDD/datapartition&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die &lt;code&gt;--sparse&lt;/code&gt;-Option überspringt mit Nullen belegte Bereiche der Quellplatte und kann so eine Menge Platz auf dem Zieldatenträger einsparen.&#10;Das Programm benötigt eine Viertelstunde für den kompletten Durchlauf. Es kann wie oben im Bild zu erkennen tatsächlich &lt;code&gt;99.6%&lt;/code&gt; der Daten retten und auch mehrere Monate später habe ich die restlichen &lt;code&gt;0.4%&lt;/code&gt; noch nicht vermisst. Glück gehabt.&lt;/p&gt;&#10;&lt;h3 id="überprüfung-output-von-ddrescue-ansehen"&gt;Überprüfung: Output von &lt;em&gt;ddrescue&lt;/em&gt; ansehen&lt;/h3&gt;&#10;&lt;p&gt;Jedenfalls legt das Programm am Ende eine einzige Datei auf meiner externen Platte ab: &lt;code&gt;datapartition.img&lt;/code&gt;&#10;Dieses Image kann ich mit &lt;code&gt;fdisk -l&lt;/code&gt; anschauen:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;datapartition.img: 236.89 GiB, 254356226048 bytes, 496789504 sectors&#10;Units: sectors of 1 * 512 = 512 bytes&#10;Sector size (logical/physical): 512 bytes / 512 bytes&#10;I/O size (minimum/optimal): 512 bytes / 512 bytes&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alles da, das ist ja schon einmal gut. Ob denn nun auch alles drin ist?&lt;/p&gt;&#10;&lt;p&gt;Dafür mounte ich die Partition mal und schaue mich per &lt;code&gt;cd&lt;/code&gt; und &lt;code&gt;ls -l&lt;/code&gt; darin um:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo mkdir /mnt/olddisk &lt;span style="color:#75715e"&gt;# Mount point of the rescued data image&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo mount /run/media/liveuser/myExternalHDD/datapartition.img /mnt/olddisk&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Zum Glück war alles drin, was drin sein sollte. Nun konnte ich das Image unmounten, den Rechner herunterfahren und eine neue Festplatte einbauen lassen.&lt;/p&gt;&#10;&lt;h2 id="die-neue-festplatte"&gt;Die neue Festplatte&lt;/h2&gt;&#10;&lt;p&gt;Auch hier habe ich noch einmal Glück gehabt: Auf meinem Gebrauchtlaptop war noch Garantie und so bekam ich ihn binnen einer Woche mit einer neuen (und doppelt so großen) Festplatte zurück gesendet. Da ich so große Angst vor Datenklau bekam, hatte ich meine alte Platte vor dem Versenden ausgebaut. Dabei kam ich mir ganz schön paranoid vor.&lt;/p&gt;&#10;&lt;p&gt;Diese Arbeit hätte ich mir aber vielleicht sparen können, denn &lt;em&gt;ddrescue&lt;/em&gt; hat auch für paranoide Leute wie mich eine Software-Lösung parat: Die Option &lt;code&gt;--fill-mode&lt;/code&gt;.&#10;Hiermit können nach erfolgter Rettung die &amp;ldquo;noch guten&amp;rdquo; Blöcke und Sektoren gelöscht werden. Nur die ohnehin defekten Sektoren bleiben erhalten. Aus der &lt;a href="https://www.gnu.org/software/ddrescue/manual/ddrescue_manual.html#Fill-mode" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Online-Dokumentation&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ddrescue --fill-mode&lt;span style="color:#f92672"&gt;=&lt;/span&gt;+ --force /dev/zero bad_drive mapfile&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ob dies allerdings auch einer Platte wie meiner funktioniert hätte, die der On-Board-Controller auf &amp;ldquo;read-only&amp;rdquo; gesetzt hat, ist mir nicht klar. Egal.&lt;/p&gt;&#10;&lt;p&gt;Ich kann jetzt jedenfalls mit einem voll funktionsfähigen Laptop weiter Blog-Einträge verfassen ☺️.&lt;/p&gt;&#10;</description></item><item><title>Giteas act_runner: Startprobleme</title><link>https://blog.schallbert.de/fix-gitea-runner/</link><pubDate>Fri, 30 Aug 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/fix-gitea-runner/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-08-30_badgateway_runner-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Bad Gateway error message from Gitea&amp;#39;&amp;#39;s act_runner at startup"&#10; title="Giteas act_runner: Startprobleme" /&gt;&#10;&lt;h2 id="das-problem"&gt;Das Problem&lt;/h2&gt;&#10;&lt;p&gt;Seitdem ich diese Seite in Eigenregie betreibe, habe ich nach Neustart des &lt;em&gt;Gitea&lt;/em&gt;-Containers das Problem, dass der aktivierte &lt;em&gt;act_runner&lt;/em&gt; manchmal nicht mit hochfährt. Er bricht dann unter Angabe von Status Code &lt;code&gt;-1&lt;/code&gt; ab und meine Workflows werden nicht abgearbeitet, sodass ich z.B. keine Artikel bauen oder veröffentlichen kann.&lt;/p&gt;&#10;&lt;p&gt;Die Fehlermeldung ist immer dieselbe:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;[...]&#10;runner-1 | time=&amp;#34;2024-08-26T10:09:46Z&amp;#34; level=info msg=&amp;#34;Starting runner daemon&amp;#34;&#10;runner-1 | time=&amp;#34;2024-08-26T10:09:46Z&amp;#34; level=error msg=&amp;#34;fail to invoke Declare&amp;#34; error=&amp;#34;unavailable: 502 Bad Gateway&amp;#34;&#10;runner-1 | Error: unavailable: 502 Bad Gateway&#10;runner-1 exited with code 1&#10;gitea | 2024/08/26 10:09:46 cmd/web.go:242:runWeb() [I] Starting Gitea on PID: 16&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Der Runner kann sich also nicht bei &lt;em&gt;Gitea&lt;/em&gt; einklinken. Wenn ich alle Services über Docker per &lt;code&gt;docker restart&lt;/code&gt; erneut starte, meint der Container für &lt;code&gt;act_runner&lt;/code&gt;, ihm fehle dafür ein Netzwerk mit ID &lt;code&gt;&amp;lt;ellenlanger Hexcode hier&amp;gt;&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="die-kurzfristige-lösung"&gt;Die kurzfristige Lösung&lt;/h2&gt;&#10;&lt;p&gt;Ich hatte die letzten Monate keine Zeit dem nachzugehen. Daher half ich mir stets damit, folgendes doppelt auszuführen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# schallbert server-console&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose -f /path/to/giteas/composefile up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Beim zweiten Anlauf dann lief der Runner wiederholbar erfolgreich an (Gitea war ja schon betriebsbereit):&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;gitea | 2024/08/26 10:38:17 routers/init.go:116:InitWebInstalled() [I] Git version: 2.45.2 (home: /data/gitea/home)&#10;runner-1 | time=&amp;#34;2024-08-26T10:38:22Z&amp;#34; level=info msg=&amp;#34;Starting runner daemon&amp;#34;&#10;runner-1 | time=&amp;#34;2024-08-26T10:38:22Z&amp;#34; level=info msg=&amp;#34;runner: action-runner, with version: v0.2.10, with labels: [ubuntu-latest], declare successfully&amp;#34;&#10;runner-1 exited with code 0&#10;runner-1 | time=&amp;#34;2024-08-26T10:40:32Z&amp;#34; level=info msg=&amp;#34;Started runner daemon&amp;#34;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dies ist natürlich nicht so schön und muss händisch von mir gemacht werden. Doch was ist die Ursache?&lt;/p&gt;&#10;&lt;h2 id="ursachenforschung"&gt;Ursachenforschung&lt;/h2&gt;&#10;&lt;p&gt;Zuerst hatte ich den Reverse Proxy von &lt;em&gt;Caddy&lt;/em&gt; im Verdacht, da er &lt;em&gt;Gitea&lt;/em&gt; an das Internet anbindet. Doch tatsächlich ist &lt;em&gt;Gitea&lt;/em&gt; selbst das Problem: Es ist zum Zeitpunkt, zu dem &lt;em&gt;Docker&lt;/em&gt; den &lt;em&gt;runner&lt;/em&gt; Container startet, noch gar nicht fertig hochgefahren. Also lese ich nach, wie ich Abhängigkeiten in Docker besser darstellen kann.&lt;/p&gt;&#10;&lt;h2 id="lösung-abhängigkeiten-abbilden"&gt;Lösung: Abhängigkeiten abbilden&lt;/h2&gt;&#10;&lt;p&gt;Zuerst versuche ich es mit dem Einbinden der Änderung in docker compose.&lt;/p&gt;&#10;&lt;h3 id="depends_on"&gt;depends_on&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;## service: runner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;## [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gitea&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;condition&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service_started&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Doch noch immer startet der &lt;em&gt;act_runner&lt;/em&gt; wohl zu früh. Docker stellt hier nämlich lediglich sicher, dass &lt;em&gt;Gitea&lt;/em&gt; gestartet wurde, nicht aber, dass es hochgefahren ist und stabil läuft.&lt;/p&gt;&#10;&lt;h3 id="healthcheck"&gt;Healthcheck&lt;/h3&gt;&#10;&lt;p&gt;Ich muss folglich sicherstellen, dass &lt;em&gt;Gitea&lt;/em&gt; betriebsbereit ist. Dies geht prima mit der Bedingung &lt;code&gt;service_healthy&lt;/code&gt;. Ich passe also die Instruktionen für den Runner wie folgt an:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;## service: runner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;## [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gitea&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;condition&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service_healthy&lt;/span&gt; &lt;span style="color:#75715e"&gt;# required so runner can attach to gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Der &lt;code&gt;service_healthy&lt;/code&gt; Qualifier ist ein Rückgabewert der &lt;code&gt;healthcheck&lt;/code&gt; Funktion. Diese besteht aus einem sogenannten &lt;code&gt;test&lt;/code&gt; und ein paar Begleitparametern, in welchem Intervall wie oft und nach welcher Zeit geprüft werden soll, ob der Dienst nun betriebsbereit ist.&lt;/p&gt;&#10;&lt;p&gt;Ich habe als Test ein einfaches Kommando gewählt mit der Annahme, dass Gitea (und damit implizit auch Caddy) fertig hochgefahren ist, sobald &lt;code&gt;curl&lt;/code&gt; seine Webseite aufrufen kann.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## service: gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;## [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;healthcheck&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;test&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;curl&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-f&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;https://git.schallbert.de/&amp;#34;&lt;/span&gt;] &lt;span style="color:#75715e"&gt;# checks if gitea is available&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;interval&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;retries&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;start_period&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;30s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mit diesen Zeilen prüft &lt;em&gt;Docker&lt;/em&gt;, ob Gitea &amp;ldquo;healthy&amp;rdquo; ist und startet den &lt;em&gt;act_runner&lt;/em&gt; entsprechend später. Das oben dargestellte Problem ist auf meinem Server seitdem komplett behoben und &lt;em&gt;Gitea&lt;/em&gt; sowie &lt;em&gt;runner&lt;/em&gt; verhalten sich zuverlässig und stabil.&lt;/p&gt;&#10;</description></item><item><title>Fehler-Logdateien versenden</title><link>https://blog.schallbert.de/server-deploy-logging/</link><pubDate>Tue, 20 Aug 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/server-deploy-logging/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-08-20_deploy_logging-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: A notification message sent on error"&#10; title="Fehler-Logdateien versenden" /&gt;&#10;&lt;p&gt;In diesem Artikel schaue ich mir an, wie ich &amp;ldquo;Monitoring&amp;rdquo; für meinen Server einrichte. Anwendungen und Dienste sollen mir im Fehlerfalle Benachrichtigungen senden können.&lt;/p&gt;&#10;&lt;h2 id="worum-geht-es-hier"&gt;Worum geht es hier?&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Sendemechaniken kennenlernen und auswählen&lt;/li&gt;&#10;&lt;li&gt;Testnachrichten verfassen und die Automation verifizieren&lt;/li&gt;&#10;&lt;li&gt;Fehlerbericht von &lt;em&gt;Borgmatic&lt;/em&gt; automatisch senden&lt;/li&gt;&#10;&lt;li&gt;Senden von Runner-logs durch &lt;em&gt;Gitea&lt;/em&gt;&lt;/li&gt;&#10;&lt;li&gt;Benachrichtigung bei Login auf meinem Server per &lt;em&gt;ssh&lt;/em&gt;&lt;/li&gt;&#10;&lt;li&gt;Erstellen und Senden eigener Logs zu Server-Update / Server-Fehlern&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="verifikation-und-überwachung"&gt;Verifikation und Überwachung&lt;/h3&gt;&#10;&lt;p&gt;Ich möchte nach dem Ausführen einer Automation wissen, ob sie erfolgreich durchgeführt wurde und alle Programme und Dienste ihre Arbeit erwartungsgemäß aufgenommen haben. Dies soll für jegliche Automation gelten - ob es sich konkret um das Senden eines Updates, die Anfertigung automatischer Backups oder eine Action von &lt;em&gt;Gitea&lt;/em&gt; handelt, muss egal sein.&lt;/p&gt;&#10;&lt;p&gt;Normalerweise würde ich für so etwas Berichtsmechanismen von &lt;em&gt;act_runner&lt;/em&gt; verwenden. Im Falle eines Server-Updates steht der Runner durch den bereits im Artikel &lt;a href="https://blog.schallbert.de/server-config-deploy/"&gt;Serverkonfiguration ausrollen&lt;/a&gt; erwähnten &lt;a href="https://blog.schallbert.de/server-config-version-control/#vor%c3%bcberlegungen"&gt;Zirkelbezug&lt;/a&gt; nicht zur Verfügung, da zeitweise alle Anwendungen heruntergefahren werden müssen.&lt;/p&gt;&#10;&lt;p&gt;Zudem kann es sein, dass Anwendungen eigene Vorgehensweisen haben, Monitoring zu betreiben. Also muss ich mir Mechanismen anschauen, mit denen ich leicht an die Informationen herankomme.&lt;/p&gt;&#10;&lt;h3 id="was-zeichnet-ein-gutes-monitoring-für-mich-aus"&gt;Was zeichnet ein gutes Monitoring für mich aus?&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Es ist unaufdringlich, meldet sich also nur im Fehlerfall oder bei ungewöhnlichen Vorgängen.&lt;/li&gt;&#10;&lt;li&gt;Es liefert spezifische Informationen und gut zu verstehende Fehlermeldungen.&lt;/li&gt;&#10;&lt;li&gt;Es nutzt einen Nachrichtenkanal, der auch bei Absturz des zu überwachenden Systems funktioniert.&lt;/li&gt;&#10;&lt;li&gt;Es stellt Berichte und Fehlermeldungen isoliert von anderen Themen dar und vermischt nichts.&lt;/li&gt;&#10;&lt;li&gt;Es fasst sich kurz.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="methoden-automatischer-berichterstattung"&gt;Methoden automatischer Berichterstattung&lt;/h2&gt;&#10;&lt;p&gt;Diesen Abschnitt gliedere ich ich auf. Im Allgemeinen Teil gehe ich auf das mir auf dem Ubuntu-Server zur Verfügung stehende Bordwerkzeug zum asynchronen Monitoring ein. Danach schaue ich mir die bei meinen Diensten teils eingebauten Lösungen bzw. mit ihnen kompatible Anwendungen an. Dabei möchte ich mich nicht auf das klassische Instrument der E-Mail beschränken, sondern auch kurz &amp;ldquo;modernere&amp;rdquo; Kommunikationskanäle wie Messengerprogramme oder RSS-Feed beleuchten.&lt;/p&gt;&#10;&lt;h3 id="mail-per-konsole---curl"&gt;Mail per Konsole - &lt;em&gt;curl&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Versand von Mails als Benachrichtigungen sind in vielen Firmen üblich. Dies geht mit Linux meist sogar ganz ohne zusätzliche Programme: Das standardmäßig vorhandene &lt;a href="https://curl.se/docs/manpage.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;curl&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; kann hier bereits weiterhelfen.&#10;&lt;em&gt;curl&lt;/em&gt; it ein Programm zum Datentransfer von oder zu einem Server. Gebe ich meinen Blog als Ziel ein, bekomme ich die HTML-Seite als Textdatei auf die Konsole ausgegeben:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl https://blog.schallbert.de&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bei der Gelegenheit fällt mir auf, wie viel unnötige Daten meine Blogsoftware erzeugt. Da werde ich später mal aufräumen müssen. Zurück zum Thema: Man kann &lt;em&gt;curl&lt;/em&gt; allerdings auch nutzen, um ein beliebiges Webbackend anzusprechen - Zum Beispiel einen Mailserver:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# copied from https://stackoverflow.com/questions/8260858/how-to-send-email-from-terminal&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl --url &lt;span style="color:#e6db74"&gt;&amp;#39;smtps://smtp.gmail.com:465&amp;#39;&lt;/span&gt; --ssl-reqd &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --mail-from &lt;span style="color:#e6db74"&gt;&amp;#39;from-email@gmail.com&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --mail-rcpt &lt;span style="color:#e6db74"&gt;&amp;#39;to-email@gmail.com&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --user &lt;span style="color:#e6db74"&gt;&amp;#39;from-email@gmail.com:YourPassword&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -T &amp;lt;&lt;span style="color:#f92672"&gt;(&lt;/span&gt;echo -e &lt;span style="color:#e6db74"&gt;&amp;#39;From: from-email@gmail.com\nTo: to-email@gmail.com\nSubject: Curl Test\n\nHello&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dies funktioniert nur dann auch für meinen Mail-Provider, wenn ich Logins durch externe Clients zulasse. Google nennt diese z.B. &amp;ldquo;weniger sichere Apps&amp;rdquo;. Ich bin wie in meinem Beitrag &lt;a href="https://blog.schallbert.de/server-config-version-control/#frisch-ans-werk"&gt;Server-Konfiguration mit Git&lt;/a&gt; beschrieben kein Fan davon, Secrets irgendwo hart hineinzuschreiben - daher würde ich den direkten Weg über &lt;em&gt;curl&lt;/em&gt; eher nicht nutzen.&lt;/p&gt;&#10;&lt;h3 id="mail-per-konsole---mail-mailx-mailutils-swaks"&gt;Mail per Konsole - &lt;em&gt;mail&lt;/em&gt;, &lt;em&gt;mailx&lt;/em&gt;, &lt;em&gt;mailutils&lt;/em&gt;, &lt;em&gt;swaks&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Wer all die Konfigurationsinformationen für den Server und Secrets nicht immer mitgeben möchte, dem stehen diverse handliche Werkzeuge für die Konsole wie &lt;a href="https://mailutils.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;mailutils&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder &lt;a href="https://github.com/jetmore/swaks" target="_blank" rel="noopener noreferrer" class="external-link"&gt;swaks&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Verfügung. Hier wird die Verbindung zum Mailserver über das Werkzeug einmalig konfiguriert und kann in z.B. in Umgebungsvariablen abgelegt werden. Die Syntax variiert von Programm zu Programm, hinten kommt aber stets eine Mail heraus.&lt;/p&gt;&#10;&lt;p&gt;An sich eine simple Lösung. Der einzige große Nachteil dabei für mich ist, dass sich hier Informationsdomänen vermischen. Ich hätte nur ungern noch einen &amp;ldquo;Berichtsthread&amp;rdquo; in meinen Mails, nach dem ich im Wust der Nachrichten irgendwann würde suchen müssen. Bei Benachrichtigungen auf anderen Kanälen kann ich zudem ein automatisches Verfallsdatum einstellen, sodass sie nach eingestellter Zeit aus meiner Liste verschwinden.&lt;/p&gt;&#10;&lt;h3 id="rss-feed-erzeugen"&gt;RSS Feed erzeugen&lt;/h3&gt;&#10;&lt;p&gt;Ungewöhnlich aber möglich: Ich könnte das Monitoring als &lt;a href="https://en.wikipedia.org/wiki/RSS" target="_blank" rel="noopener noreferrer" class="external-link"&gt;RSS-Feed&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; wie meinen Blog auch (&lt;a href="https://blog.schallbert.de/index.xml"&gt;schallberts-blog-feed&lt;/a&gt;) z.B. per Jekyll-Instanz erzeugen und als Website online stellen. Dieser wäre leicht zu abonnieren, mit praktisch jedem Reader zu lesen und ich könnte ihn sogar für jede Applikation ganz einfach getrennt vorsehen. Doch es gibt offensichtliche Nachteile:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Hoher Aufwand: Gitea runner mit Jekyll-Instanz, Webserver und Subdomain erforderlich.&lt;/li&gt;&#10;&lt;li&gt;Öffentlich verfügbar: Plötzlich werden Bauvorgänge, Updates, Upgrades und Fehlermeldungen für jedermann zugänglich.&lt;/li&gt;&#10;&lt;li&gt;Fehleranfällig: Schmiert Gitea, der Runner, mein Proxy oder der Webserver ab, bekomme ich keine Berichte.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Spätestens mit dem letzten Punkt ist diese Option für mich raus. Ich möchte ja gerade dann einen Bericht bekommen, wenn meine Applikationen nicht das tun, was sie sollen.&lt;/p&gt;&#10;&lt;p&gt;Werfen wir nun mal einen Blick auf die von mir bereits betriebenen Anwendungen und schauen, wie sie dieses Problem angehen.&lt;/p&gt;&#10;&lt;h3 id="borgmatic"&gt;&lt;em&gt;borgmatic&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Borgmatic bringt von Haus aus Kompabitilität zu einer Menge &lt;a href="https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Monitoring-Optionen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit. Darunter befinden sich &lt;a href="https://github.com/caronc/apprise" target="_blank" rel="noopener noreferrer" class="external-link"&gt;apprise&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://ntfy.sh/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ntfy&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://healthchecks.io/docs/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;healthchecks&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://cronitor.io/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cronitor&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://www.pagerduty.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;pagerduty&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://cronhub.io/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cronhub&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und &lt;a href="https://grafana.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;grafana&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;em&gt;Apprise&lt;/em&gt; ist eine Bibliothek. Sie ist quelloffen und kann von einer bestehenden Anwendung als Abhängigkeit eingebunden werden. Wie ein Adapter ermöglicht sie die asynchrone Kommunikation der Anwendung mit diversen verschiedenen Kommunikationsdiensten wie SMS, Mail, Messenger (z.B. &lt;a href="https://signal.org/de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Signal&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;), diverse Heimautomation oder den Benachrichtigungssystemen diverser Betriebssysteme. Letzteres funktioniert allerdings nur auf der lokalen Maschine. Der Trigger für die Kommunikation muss dabei stets von der Anwendung ausgehen.&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;ntfy&lt;/em&gt; ist ein Push-Benachrichtigungsdienst. Er ist quelloffen und kann sowohl selbst gehostet als auch über eine Webanwendung als Dienstleistung in Anspruch genommen werden. &lt;em&gt;Apprise&lt;/em&gt; z.B. unterstützt &lt;em&gt;ntfy&lt;/em&gt; als Kommunikationsdienst. Der Aufbau ist recht einfach und funktioniert wie bei &lt;a href="https://de.wikipedia.org/wiki/MQTT" target="_blank" rel="noopener noreferrer" class="external-link"&gt;MQTT&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; auch über einen Publication-Subscription bzw. Broker-Client Mechanismus, allerdings HTTP-basiert.&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;Healthchecks&lt;/em&gt; ist ein Dienst. Er ist quelloffen und kann selbst gehostet werden. Der Dienst ist dazu da, regelmäßige Aktivitäten zu überwachen und kann als Totmannschalter fungieren: Erfolgt anders als erwartet keine Rückmeldung vom überwachten Programm, so kann er selbst eine Fehlermeldung absetzen. Diese kann wiederum an diverse Kommunikationsdienste gesendet werden.&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;Cronitor&lt;/em&gt; ist eine Monitoringlösung und Webanwendung, die neben den von mir benötigten Benachrichtigungen eine Menge Analysetools, Leistungsmessungen und Metriken - zumeist gegen Geld - bereitstellt. Es gibt zwar einen kostenfreien &amp;ldquo;Hacker&amp;rdquo;-Account mit begrenztem Funktionsumfang, jedoch ist auch dieses Werkzeug mir entschieden zu groß und zu komplex.&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;PagerDuty&lt;/em&gt; sieht sich als kommerzielle &amp;ldquo;Operations&amp;rdquo; Plattform, die sowohl &amp;ldquo;Incident Management&amp;rdquo;, Automation, &amp;ldquo;Business Operations&amp;rdquo;, &amp;ldquo;AIOps&amp;rdquo; etc. bereitstellt. Ist für mich direkt raus. Spätestens beim Wort &amp;ldquo;AIOps&amp;rdquo; 😅&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;cronhub&lt;/em&gt; sieht mir wie eine kommerzielle Webanwendung aus, die ähnlich wie &lt;em&gt;Healthchecks&lt;/em&gt; Cron-Jobs erstellen, überwachen und bei Fehlern melden kann. Sie ist für mich uninteressant, denn sie scheint weder open source zu sein noch könnte ich sie selbst hosten.&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;Grafana&lt;/em&gt; ist eine Open-Source-Webanwendung, die entweder selbst gehostet oder als Cloud-Dienst verwendet werden kann. Obwohl viele größere Unternehmen und Projekte die Anwendung nutzen, ist sie für meine Zwecke um Größenordnungen zu umfang- und funktionsreich.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="fail2ban"&gt;&lt;em&gt;fail2ban&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Fail2ban hat keine Automationslösung wie &lt;em&gt;borgmatic&lt;/em&gt;. Es erzeugt schlicht Logdateien die ausgewertet werden müssten, um Inhalte für Benachrichtigungen zu gewinnen. Mir fällt im Moment nichts ein, was ich unbedingt von Fail2ban wissen müsste. Daher erstelle ich mir hierfür erst einmal keine Berichte.&lt;/p&gt;&#10;&lt;h3 id="gitea"&gt;&lt;em&gt;Gitea&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Bei Gitea lassen sich die Logdateien sehr feingliedrig konfigurieren. So können Access Logs getrennt von Service Logs, Repository Logs oder Action Logs herausgeschrieben und dann weiterverarbeitet werden. Als Benachrichtigungssystem bietet Gitea nach meiner Recherche ausschließlich einen &lt;a href="https://docs.gitea.com/next/administration/config-cheat-sheet#mailer-mailer" target="_blank" rel="noopener noreferrer" class="external-link"&gt;mailer&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; an. Dieser ist meiner Ansicht nach vor Allem für Repository- und Action Logs gedacht.&lt;/p&gt;&#10;&lt;p&gt;Auch hier würde es mir am meisten bringen, die Logs zu analysieren und bei Bedarf einen Bericht selbst zu erstellen.&lt;/p&gt;&#10;&lt;h3 id="server"&gt;Server&lt;/h3&gt;&#10;&lt;p&gt;Das Ausrollen meiner Server-Konfigurationsdateien übernehme ich per Skript ja sowieso schon selber. Also könnte ich auch hier im Fehlerfalle die Logs in eine Datei umleiten und sie dann einem Bericht in einen beliebigen Kanal anhängen.&lt;/p&gt;&#10;&lt;p&gt;Außerdem wären erfolgreiche Logins auf dem Server selbst eine Nachricht an mich wert. Dann kann ich sofort feststellen, ob ich das selbst war oder nicht.&lt;/p&gt;&#10;&lt;h2 id="die-auswahl-des-berichtsprogramms"&gt;Die Auswahl des Berichtsprogramms&lt;/h2&gt;&#10;&lt;p&gt;Ich muss nicht nur ein Monitoring-Programm heraussuchen, sondern auch einen Kommunikationsdienst wählen über den die Berichte versendet werden.&lt;/p&gt;&#10;&lt;h3 id="berichtsprogramm"&gt;Berichtsprogramm&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Programm&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Vorteil&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Nachteil&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;em&gt;curl&lt;/em&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;simpel&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Konfiguration muss mitgegeben werden&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;funktioniert super einfach mit &lt;em&gt;ntfy&lt;/em&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Keine Überwachung bei Absturz des Servers&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Bordwerkzeug&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;kein Service erforderlich&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;em&gt;Apprise&lt;/em&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ermöglicht Einbindung der Signal-Api&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Komplex im Zusammenspiel mit externen Diensten&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;kein Service erforderlich&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;em&gt;Healthchecks&lt;/em&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kann als Watchdog/Totmannschalter agieren&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Registrierung erforderlich&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kann lokal gehostet&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; werden&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lokales Hosting widerspricht Watchdog-Konzept&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Ich habe ja nur einen kleinen Server mit ein paar wenigen Anwendungen. Daher halte ich es hier so einfach wie möglich und wähle &lt;em&gt;Apprise&lt;/em&gt; für Dienste wie &lt;em&gt;borgmatic&lt;/em&gt;, die diese Bibliothek sowieso mitbringen und &lt;em&gt;curl&lt;/em&gt; für jene, deren Berichtsmechanik ich selbst programmieren muss.&lt;/p&gt;&#10;&lt;h3 id="kommunikationsweg"&gt;Kommunikationsweg&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Programm&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Vorteil&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Nachteil&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;SMS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hohe Zuverlässigkeit&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Registrierung erforderlich&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Beschränkung auf wenige Zeichen&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Keine Anhänge möglich&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Keine Topics, Domänenvermischung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Mail&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;einfach einzurichten&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schlechte Durchsuchbarkeit&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RSS-Feed&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gute Sortierbarkeit&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kompliziert einzurichten, fehleranfällig&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Client sehr leichtgewichtig&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lokales Hosting widerspricht Watchdog-Konzept&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Inhalte öffentlich verfügbar&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;em&gt;ntfy&lt;/em&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Simpel und leichtgewichtig&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Registrierung optional bei Web-Nutzung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Client zweckorientiert&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Unsicher: Keine Verschlüsselung ohne Registrierung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kostenlos für Kleinanwender&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Unabhängig von der eigenen Maschine&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;em&gt;Signal&lt;/em&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&amp;ldquo;Note to self&amp;rdquo; einfach einzurichten&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Einrichtung und Konfiguration der Signal-API komplex&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Unabhängig von der eigenen Maschine&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Topics etwas schwierig umzusetzen&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Nutzlos bei Absturz des Servers&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;SMS, Mail und RSS-Feed sind durch teils oben bereits erläuterte Nachteile für mich bereits raus. Bleiben also &lt;em&gt;ntfy&lt;/em&gt; und &lt;em&gt;Signal&lt;/em&gt;. &lt;em&gt;ntfy&lt;/em&gt; besticht durch seine Einfachheit: HTTP PUSH-request auf ein selbst definiertes &amp;ldquo;Topic&amp;rdquo; senden und selbiges auf dem Handy abonnieren - fertig. Es ist auch gut erweiterbar, denn durch self-hosting kann ich später bei Bedarf die Sicherheit erhöhen (Verschlüsselung) und das Sendeverhalten steuern. &lt;em&gt;Signal&lt;/em&gt; hingegen macht einen eigenen Client mit verhältnismäßig aufwändiger Konfiguration erforderlich. Außerdem müssten die Geräte Server-Handy verbunden werden, sodass nicht einfach neue Abonnenten hinzuzufügen sind. Dafür ist die Übertragung bestens Ende-zu-Ende gesichert, ich muss mich nicht registrieren und kostenlos ist es auch noch.&lt;/p&gt;&#10;&lt;p&gt;Vorerst entscheide ich mich auch hier für die weniger komplexe Lösung mit &lt;em&gt;ntfy&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h2 id="logging"&gt;Logging&lt;/h2&gt;&#10;&lt;p&gt;Gut. Nun ist geklärt, dass ich per &lt;em&gt;curl&lt;/em&gt; und &lt;em&gt;apprise&lt;/em&gt; über den &lt;em&gt;ntfy&lt;/em&gt;-Dienst nach Hause telefoniere. Nun schaue ich mal, welcher Inhalt zu übermitteln ist und wie ich möglichst unaufdringlich, dabei aber kurz und prägnant bleibe.&lt;/p&gt;&#10;&lt;h3 id="wann-sollen-logs-gesendet-werden"&gt;Wann sollen Logs gesendet werden?&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;em&gt;borgmatic&lt;/em&gt;: Wenn ein Backup fehlschlägt&lt;/li&gt;&#10;&lt;li&gt;Bei erfolgreichem Login auf meinen Server oder Gitea&lt;/li&gt;&#10;&lt;li&gt;Nach Ausrollen eines Updates der Server-Konfiguration&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;docker&lt;/em&gt;: Falls eine Applikation ausfällt oder nicht hochfährt&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;gitea&lt;/em&gt;: Fehlgeschlagener Durchlauf von &lt;em&gt;act_runner&lt;/em&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="was-gehört-für-mich-in-einen-bericht"&gt;Was gehört für mich in einen Bericht?&lt;/h3&gt;&#10;&lt;p&gt;Ich möchte die klassischen &amp;ldquo;W-Fragen&amp;rdquo; beantwortet bekommen.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Wann ist es passiert (Zeitstempel)?&lt;/li&gt;&#10;&lt;li&gt;Welche Anwendung berichtet?&lt;/li&gt;&#10;&lt;li&gt;Was ist passiert?&lt;/li&gt;&#10;&lt;li&gt;Wo (Modul, Codezeile) etc. ist das passiert?&lt;/li&gt;&#10;&lt;li&gt;Wie viele Verletzte (Schweregrad, Recovery)?&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Eine Benachrichtigung sieht dann etwa so aus:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;TIMESTAMP APPLICATION PRIORITY MESSAGE EFFECT DETAIL &#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="rohfassung-eines-berichts"&gt;Rohfassung eines Berichts&lt;/h3&gt;&#10;&lt;p&gt;Eine &lt;em&gt;ntfy&lt;/em&gt;-Nachricht könnte ungefähr so aussehen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Title: Fehler Borgmatic&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Priority: urgent&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Tags: warning&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#34;YYYY-MM-DD HH:MM:SS Backup-Erstellung abgebrochen. Zugriff auf Repository blockiert&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ntfy.sh/schallberts-topic&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-08-20_ntfytest.jpg" alt="Image: First ntfy test on my phone app sent with curl" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich installiere die entsprechende App auf meinem Handy, registriere mich auf das &amp;ldquo;Topic&amp;rdquo; und sende die Nachricht ab. Es ist schön, wenn Dinge einfach funktionieren!&#10;Aha, die App zeigt den Zeitpunkt des Einganges an. Das ist mir genau genug.&lt;/p&gt;&#10;&lt;h2 id="durchführung"&gt;Durchführung&lt;/h2&gt;&#10;&lt;p&gt;Ich nehme mir hier nacheinander alle Dienste vor, für die ich gern Benachrichtigungen einrichten möchte.&lt;/p&gt;&#10;&lt;h3 id="borgmatic-1"&gt;&lt;em&gt;borgmatic&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Starten wir mit einem Pilotversuch wieder in kleinen Schritten. Als Erstes konfiguriere ich Borgmatic zum Senden einer Nachricht über &lt;em&gt;Apprise&lt;/em&gt; an &lt;em&gt;ntfy&lt;/em&gt; bei Fehlschlag der Backup-Erstellung:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /borgmatic.d/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;on_error&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;echo &amp;#34;Error while creating a backup.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;apprise -vv --title &amp;#34;Borgmatic Error&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --&lt;span style="color:#ae81ff"&gt;body &amp;#34;Could not run {output}. Aborted {error}.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;ntfy://schallberts-topic&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dann teste ich das Kommando, indem ich innerhalb des Borgmatic-Containers &lt;em&gt;apprise&lt;/em&gt; anspreche. Und tatsächlich, es funktioniert. Aber der Weg hierhin hat mich locker eine Stunde gekostet, da die &lt;code&gt;yml&lt;/code&gt; mit ihren Syntaxregeln sogar innerhalb von Strings interpretiert und &lt;em&gt;borgmatic&lt;/em&gt; aufgrund von &lt;code&gt;:&lt;/code&gt; sowie &lt;code&gt;-&lt;/code&gt; Zeichen ständig die Konfigurationsdatei nicht lesen wollte. Wenn ihr einen Fehler sieht ähnlich diesem hier:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;At &amp;#39;on_error[1]&amp;#39;: {&amp;#39;apprise -vv --title &amp;#34;Borgmatic Error&amp;#34; --body &amp;#34;Could not run {output}&amp;#39;: &amp;#39;Aborted {error}.&amp;#34; ntfy://schallberts-topic&amp;#39;} is not of type &amp;#39;string&amp;#39;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Daran ist zu erkennen, dass die Interpretation von Zeichen oder Einrückungen schiefgelaufen ist und die Zeichensetzung zu prüfen ist. Alternativ kann auch der Pipe-Operator &lt;code&gt;|&lt;/code&gt; verwendet werden, um ein Kommando zu kombinieren. Referenz: &lt;a href="https://yaml.org/spec/1.2-old/spec.html#id2795688" target="_blank" rel="noopener noreferrer" class="external-link"&gt;yml-Spezifikation&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3 id="server-1"&gt;Server&lt;/h3&gt;&#10;&lt;p&gt;Eine Anwendung von &lt;em&gt;ntfy&lt;/em&gt; zur Überwachung von Logins auf einem Server findet sich bereits in der &lt;a href="https://docs.ntfy.sh/examples/#ssh-login-alerts" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dokumentation von &lt;em&gt;ntfy&lt;/em&gt; selbst&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Die Beschreibung zeigt, dass so etwas leicht selbst umzusetzen ist und gibt unter Verwendung von Pluggable Authentication Modules (&lt;a href="https://en.wikipedia.org/wiki/Linux_PAM" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PAM-Bibliothek&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) ein tolles Beispiel.&lt;/p&gt;&#10;&lt;p&gt;Am Ende der Datei &lt;code&gt;sshd&lt;/code&gt; im Verzeichnis &lt;code&gt;etc/pam.d&lt;/code&gt; folgenden Code einfügen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;session optional pam_exec.so /usr/bin/ntfy-ssh-login.sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Diese Zeile sagt &lt;em&gt;PAM&lt;/em&gt;, dass sie bei Öffnen einer &lt;code&gt;session&lt;/code&gt; per &lt;code&gt;ssh&lt;/code&gt; das ausführende Modul &lt;code&gt;pam_exec&lt;/code&gt; aufrufen soll, welches dann das hernach angegebene Skript abspult. Der Wert &lt;code&gt;optional&lt;/code&gt; bedeutet, dass die Konfigurationsdatei auch bei Fehlschlag der Aktion weiter durchlaufen werden soll. Mehr Details zum Umgang mit &lt;em&gt;PAM&lt;/em&gt; gibt es z.B. auf &lt;a href="https://www.baeldung.com/linux/pam-ssh-login-notifications" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Baeldung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Anschließend ist im Skript &lt;code&gt;ntfy-ssh-login.sh&lt;/code&gt; schlicht der &lt;em&gt;ntfy&lt;/em&gt;-Aufruf zu hinterlegen, wenn das Skript feststellt, dass PAM ein &lt;code&gt;open_session&lt;/code&gt; Event festgestellt hat.&lt;/p&gt;&#10;&lt;p&gt;Genau so habe ich es bei mir umgesetzt und es funktioniert auf Anhieb. Klasse! Der einzige Nachteil: Diese Modifikation habe ich direkt auf dem Server gemacht. Ohne Container und außerhalb meiner Konfigurations-Sicherung. Wenn ich den Server jetzt aus irgendeinem Grund neu aufsetzen muss, ist die Änderung im &lt;em&gt;PAM&lt;/em&gt; futsch und ich bekomme keine Benachrichtigungen mehr, bis ich die Änderung manuell erneut einpflege.&lt;/p&gt;&#10;&lt;h3 id="server-konfiguration"&gt;Server-Konfiguration&lt;/h3&gt;&#10;&lt;p&gt;Für das Senden eines Berichts nach Ausführen der Konfigurationsautomation muss ich eigentlich nur das im Rahmen des letzten Artikel verfasste &lt;a href="https://blog.schallbert.de/server-config-deploy/#die-fertige-automation"&gt;server-config-action&lt;/a&gt; Skript anpassen.&lt;/p&gt;&#10;&lt;p&gt;Zunächst möchte ich informiert werden, wenn das Skript fehlerfrei durchlief. Dafür füge ich am Ende der Datei einen &lt;em&gt;curl&lt;/em&gt;-Befehl ein.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# server-config-action.sh&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# action commands...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# send success notification&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Title: server-config-action&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Priority: low&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Tags: white_check_mark&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#34;Rollout successful&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ntfy.sh/schallbert-server-config-push-topic&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sollte das Skript allerdings nicht fehlerfrei durchlaufen, so möchte ich folgendes tun:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Aktionen nach Auftreten des Fehlers abbrechen&lt;/li&gt;&#10;&lt;li&gt;Ein Fehler-Log erstellen&lt;/li&gt;&#10;&lt;li&gt;Dieses Log versenden&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Den ersten Punkt kann ich durch Hinzufügen einer Falle (Englisch &amp;ldquo;Trap&amp;rdquo;) für Fehler erreichen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# server-config-action.sh&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;trap &lt;span style="color:#e6db74"&gt;&amp;#39;handle_error $LINENO&amp;#39;&lt;/span&gt; ERR&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# action commands...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hiermit wird die Funktion &lt;code&gt;handle_error&lt;/code&gt; aufgerufen. Ihr wird die Zeilennummer mitgegeben, an der der Fehler &lt;code&gt;ERR&lt;/code&gt; auftrat. Damit decke ich auch Fehler ab, die beim Neustart der Container entstehen können. Das Fehler-Log erstelle ich mir durch Wegschreiben der Ausgabe der einzelnen Skriptbefehle. Dies erreiche ich mit der folgenden Zeile:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# server-config-action.sh&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;exec 3&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 1&amp;gt;server-config-action.log 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# action commands ...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Jeglicher Standard-Output &lt;code&gt;stdout&lt;/code&gt; soll mit diesem Befehl an den Dateideskriptor &lt;code&gt;3&lt;/code&gt;, hier angegeben mit der Logdatai &lt;code&gt;server-config-action-log&lt;/code&gt;, überschreibend weitergereicht werden. Für &amp;ldquo;Anhängen&amp;rdquo; müssten es zwei Umleitungsoperatoren &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; sein. Somit ersetzt die Logdatei die Konsolenausgabe, welche ich im vorigen Artikel noch an dieser Stelle hatte.&lt;/p&gt;&#10;&lt;p&gt;Um das Log zugesandt zu bekommen, definiere ich nun folgende Funktion am Anfang des Bash-Skripts:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# server-config-action.sh&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# on error, send a notification&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;handle_error&lt;span style="color:#f92672"&gt;()&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# stop redirecting to file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exec 1&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; 1&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; curl &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Title: server-config-action FAILED&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Priority: high&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Tags: x&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -T server-config-action.log &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Filename: server-config-action.log&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ntfy.sh/schallbert-server-config-push-topic&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# set error trap&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# redirect stdout and stderr to file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# action commands...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-08-20_ntfytest_allchannels.jpg" alt="Image: Phone screenshot of my ntfy messages" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="gitea-1"&gt;&lt;em&gt;Gitea&lt;/em&gt;&lt;/h3&gt;&#10;&lt;p&gt;Für Berichte von Github Actions gibt es bereits ein existierendes Beispiel auf &lt;a href="https://docs.ntfy.sh/examples/#github-actions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ntfy_examples/#github-actions&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Und das Beste: Da der &lt;em&gt;act_runner&lt;/em&gt; von &lt;em&gt;Gitea&lt;/em&gt; an den wesentlichen Stellen &lt;a href="https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables" target="_blank" rel="noopener noreferrer" class="external-link"&gt;in den Umgebungsparametern&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; kompatibel ist, funktioniert das Senden an &lt;em&gt;ntfy&lt;/em&gt; bei mir auf Anhieb. In der Workflow-Datei des runners muss nur der auf der Website angegebene &lt;em&gt;curl&lt;/em&gt;-Befehl angegeben werden und fertig.&lt;/p&gt;&#10;&lt;h2 id="ergebnis"&gt;Ergebnis&lt;/h2&gt;&#10;&lt;p&gt;Hiermit sind jetzt fünf Benachrichtigungen über die wichtigsten Vorgänge auf meinem Server eingerichtet. Ich habe die zugrundeliegenden Mechaniken verstanden und kann jederzeit weitere Benachrichtigungen erstellen, wenn ich sie benötige. Löse ich alle Benachrichtigungen testweise aus, zeigt mein Handy das dargestellte Bild.&lt;/p&gt;&#10;&lt;p&gt;Eine Unabhängigkeit vom zu überwachenden System habe ich noch nicht erreicht. Die Benachrichtigungen gehen sämtlich von der betroffenen Maschine aus und es bestehen zum Teil &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/#reverse-proxy-connection-refused"&gt;sogar Abhängigkeiten&lt;/a&gt; zwischen Docker-Containern. Der &lt;em&gt;act_runner&lt;/em&gt; muss sich zum Beispiel über Websockets mit &lt;em&gt;Gitea&lt;/em&gt; verbinden. Und das funktioniert nur, wenn &lt;em&gt;Caddy&lt;/em&gt; den Reverse Proxy bereitstellt.&lt;/p&gt;&#10;&lt;p&gt;Stelle ich in den nächsten Monaten fest, dass ein Dienst &amp;ldquo;unter meinem Radar&amp;rdquo; nicht mehr arbeitsfähig ist, werde ich diese Unabhängigkeit herstellen müssen und entsprechend darüber berichten.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Kontext zum Setup mit &lt;a href="https://docs.ntfy.sh/#getting-started" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ntfy&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;Kontext zur &lt;a href="https://github.com/caronc/apprise/wiki/Notify_signal" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Signal-API&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:3"&gt;&#10;&lt;p&gt;Bericht von &lt;code&gt;asad-awadia&lt;/code&gt; zur &lt;a href="https://blog.aawadia.dev/2023/04/24/signal-api/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Einrichtung der Signal-API&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:4"&gt;&#10;&lt;p&gt;Healthchecks ist direkt verfügbar als &lt;a href="https://hub.docker.com/r/healthchecks/healthchecks" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Docker-Image&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:5"&gt;&#10;&lt;p&gt;Übrigens sind die Operatoren &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; und &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; für mich nicht schwer zu merken, denn früher habe ich viel mit der Programmiersprache &lt;code&gt;C&lt;/code&gt; zu tun gehabt. Dort - und in vielen anderen Programmiersprachen auch - sind dies Shift-Operatoren, die einen Wert bitweise &amp;ldquo;verschieben&amp;rdquo; bzw. ein Feld auf ein anderes schieben können. Bei den &lt;a href="https://en.wikipedia.org/wiki/Reduced_instruction_set_computer" target="_blank" rel="noopener noreferrer" class="external-link"&gt;RISC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Architekturen, die ich damals verwendet habe, war die Schiebe-Operation &amp;ldquo;billig&amp;rdquo;, also sehr schnell und speicherplatzschonend umgesetzt.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Server-Konfiguration ausrollen</title><link>https://blog.schallbert.de/server-config-deploy/</link><pubDate>Wed, 31 Jul 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/server-config-deploy/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-07-31-server-configdeploy-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Diagram showing a config file rollout on Schallbert&amp;#39;&amp;#39;s server"&#10; title="Server-Konfiguration ausrollen" /&gt;&#10;&lt;p&gt;Im vorigen Artikel habe ich meine &lt;a href="https://blog.schallbert.de/server-config-version-control/"&gt;Konfigurationsdateien unter Versionskontrolle gebracht&lt;/a&gt;. Nun will ich die auf dem Server bereitgestellten Updates automatisiert einspielen.&lt;/p&gt;&#10;&lt;h2 id="worum-geht-es-hier"&gt;Worum geht es hier?&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schreiben eines Skripts zur automatischen Erkennung des Update-Triggers&lt;/li&gt;&#10;&lt;li&gt;Server-Anwendungen sollen heruntergefahren und eine Sicherungskopie erstellt werden&lt;/li&gt;&#10;&lt;li&gt;Das Skript soll die Konfiguration auf dem System verteilen&lt;/li&gt;&#10;&lt;li&gt;Anschließend sollen alle Anwendungen neu gestartet werden&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="wege-und-möglichkeiten"&gt;Wege und Möglichkeiten&lt;/h2&gt;&#10;&lt;p&gt;Auch hier habe ich wieder mehrere Stunden mit Recherche verbracht. Bei größeren Projekten verwenden Infrastrukturexperten anscheinend spezialisierte Automatisierungswerkzeuge. Dazu zählen &lt;a href="https://docs.ansible.com/ansible/latest/getting_started/index.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Ansible&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, einfachere Tools wie &lt;a href="https://www.cdi.st/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cdist&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder gar &lt;a href="https://kubernetes.io/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Kubernetes&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für stark skalierende Dienste.&lt;/p&gt;&#10;&lt;h3 id="automatisierungsdienste-ansible-cdist-kubernetes"&gt;Automatisierungsdienste Ansible, cdist, Kubernetes&lt;/h3&gt;&#10;&lt;p&gt;Ansible und cdist scheinen dabei ein ähnliches Konzept zu verfolgen: Auf der Quellmaschine (in diesem Beispiel meinem Laptop) erstelle ich die Konfiguration für meinen Server und lege diese sowie die Vorschrift zur Konfiguration meiner Dienste in einem &amp;ldquo;Playbook&amp;rdquo; (Ansible) bzw. in &amp;ldquo;types&amp;rdquo; (cdist) ab.&lt;/p&gt;&#10;&lt;p&gt;Vereinfacht gesagt - so wie ich das verstanden habe - kümmert sich das Werkzeug anschließend auf Knopfdruck darum, die Konfiguration zu bauen, sich per &lt;code&gt;ssh&lt;/code&gt; auf dem Zielhost einzuwählen, sie dort hinüberzuschieben und anschließend zu starten. Für Ansible existieren sogar für mein Szenario mit &lt;code&gt;docker-compose&lt;/code&gt; &lt;a href="https://medium.com/swlh/deploying-docker-compose-applications-with-ansible-and-github-actions-7f1740392507" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Tutorials wie dieses&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, was den Einstieg nochmal erleichtert.&lt;/p&gt;&#10;&lt;p&gt;Einen anderen Weg geht Kubernetes, was sich als eher als Containermanager, Loadbalancer und Skalierungsagent versteht, aber für meine Zwecke Ähnliches leisten kann.&lt;/p&gt;&#10;&lt;h3 id="mein-weg"&gt;Mein Weg&lt;/h3&gt;&#10;&lt;p&gt;Ich hingegen brauche von den Fähigkeiten dieser Programme nur einen Bruchteil. Zudem schrecken mich der &amp;ldquo;zusätzliche&amp;rdquo; &lt;code&gt;ssh&lt;/code&gt;-Kanal, der Konfigurationsaufwand, die teils auf dem Zielsystem erforderlichen, zusätzlichen Programme sowie das erforderliche Einlesen und Auswählen des für mich besten Werkzeuges ab. Denn dank meiner sehr einfachen Pipeline aus dem letzten Artikel befindet sich die Konfiguration bereits auf meinem Server. Sie muss &amp;ldquo;nur noch&amp;rdquo; an die richtigen Stellen kopiert und betroffene Services einem Neustart unterzogen werden.&lt;/p&gt;&#10;&lt;p&gt;Daher versuche ich, dieses Problem mit Bordwerkzeug, meinem Gehirn in Betriebsbereitschaft und ein paar Suchanfragen bei Einschlägigen Foren zu den Themen &lt;a href="https://superuser.com/questions/181517/how-to-execute-a-command-whenever-a-file-changes" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&amp;ldquo;Ausführen eines Skriptes bei Dateiänderung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://www.freecodecamp.org/news/copy-a-directory-in-linux-how-to-cp-a-folder-in-the-command-line-in-linux-and-unix-macos/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&amp;ldquo;Kopieren von Ordnerstrukturen unter Linux&amp;rdquo;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zu lösen.&lt;/p&gt;&#10;&lt;h3 id="risiko-zirkelbezug"&gt;Risiko Zirkelbezug&lt;/h3&gt;&#10;&lt;p&gt;Ein Risiko gehe ich dabei jedoch ein: Da das Deployment über Gitea läuft, die Konfiguration aber Gitea selbst betrifft, kann ich bei einem Fehler in diesem Modul nichts mehr um- oder zurückstellen: Der Gitea-Service ist dann ja kaputt. In diesem Szenario müsste ich also händisch mit auf dem Server die Konfiguration wieder ans Laufen bekommen.&lt;/p&gt;&#10;&lt;p&gt;Ich probiere es jetzt trotzdem aus und schaue, ob ich tatsächlich mit so einem Problem konfrontiert werde. Falls ja, steige ich halt auf &lt;code&gt;cdist&lt;/code&gt; um und dokumentiere dies in einem eigenen Artikel! 🤗&lt;/p&gt;&#10;&lt;h2 id="vorbereitung-ordnersystem-und-skripte-erstellen"&gt;Vorbereitung: Ordnersystem und Skripte erstellen&lt;/h2&gt;&#10;&lt;p&gt;Ich überlege kurz und erstelle ein paar Ordner im Server-Dateisystem:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /opt/server-config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir automation-hooks-trigger&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir automation-hooks-handler&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Diese Ordner sollen die zwei Seiten der Automation abbilden. &lt;code&gt;trigger&lt;/code&gt; enthält Textdateien, die von der (Web)-Serviceseite aus manipuliert werden können. So soll &lt;em&gt;act_runner&lt;/em&gt; beispielsweise die Datei &lt;code&gt;server-config-update&lt;/code&gt; schreiben, sobald ein Update vorliegt.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;service &amp;ndash;&amp;gt; writes to trigger file ||| server_handler() &amp;ndash;&amp;gt; trigger_file.changed ? run_action() : loop()&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Im Handler-Ordner wiederum befinden sich Skriptdateien, die die erforderlichen Änderungen auf dem Server-Dateisystem vornehmen.&lt;/p&gt;&#10;&lt;h2 id="durchführung-shell-script-zum-abhandeln-des-updates"&gt;Durchführung: Shell-Script zum Abhandeln des Updates&lt;/h2&gt;&#10;&lt;p&gt;Im vorigen Artikel habe ich im Container &lt;em&gt;act_runner&lt;/em&gt; zum Signalisieren des Vorhandenseins einer neuen Serverkonfiguration das Kommando &lt;code&gt;touch server-config-update.txt&lt;/code&gt; ausgeführt. Auf dem Server prüfe ich nun mit folgendem Code periodisch ab, ob sich diese Datei geändert hat.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;### set directories&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;actionfile&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/opt/server-config/automation-hooks-handler/server-config-action.sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;triggerfile&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/opt/server-config/automation-hooks-trigger/server-config-update.txt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;### Set initial time of file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;LTIME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;stat -c %Z &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;triggerfile&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ATIME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;stat -c %Z &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;triggerfile&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[[&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$ATIME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; !&lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$LTIME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;actionfile&lt;span style="color:#e6db74"&gt;}&lt;/span&gt; 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LTIME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$ATIME&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sleep &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Per &lt;a href="https://en.wikipedia.org/wiki/Shebang_%28Unix%29" target="_blank" rel="noopener noreferrer" class="external-link"&gt;#!/bin/bash&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; wird dem Betriebssystem der Interpreter genannt, mit dem das Skript auszuführen ist. Der Befehl &lt;a href="https://wiki.ubuntuusers.de/stat/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;stat -c %Z&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; fragt ab, wann die Datei zuletzt geändert wurde und gibt den Zeitpunkt im &lt;a href="https://www.epochconverter.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Epoch-Format&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zurück. Schließlich wird die zu Anfang des Skriptes mit der in der Schleife genommenen Zeit verglichen und bei Veränderung kann dann die noch zu schreibende Deploy-Routine ablaufen. Anschließend wird der Referenzzeitpunkt aktualisiert.&#10;Zum Schluss pausiert das Skript (&lt;code&gt;sleep 10&lt;/code&gt;) für zehn Sekunden, bevor die Abfrage erneut losläuft. Ich verwende absolute Pfade, da ich das Skript sowohl für Tests von der Konsole aus als auch automatisch per &lt;code&gt;cron&lt;/code&gt; oder &lt;code&gt;systemd&lt;/code&gt; starten können will.&lt;/p&gt;&#10;&lt;h3 id="erster-test-und-einhängen-in-den-autostart"&gt;Erster Test und Einhängen in den &amp;ldquo;Autostart&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Nun müssen wir die Datei für einen ersten Test ausführbar machen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# change file mode bits: add &amp;#34;executable&amp;#34; flag to server-config-handler script&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod +x /opt/server-config/automation-hooks-handler/server-config-handler.sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dann erstelle ich die Datei &lt;code&gt;server-config-action.sh&lt;/code&gt; und gebe ihr als einziges Kommando ein &lt;code&gt;echo&lt;/code&gt;. Schauen wir mal, ob bis hierhin alles funktioniert. Dafür erstelle ich lokal meine Update-Triggerdatei und starte das Skript. Anschließend modifiziere ich sie in einer anderen Shell-Instanz per &lt;code&gt;touch server-config-update.txt&lt;/code&gt; und prompt erscheint in der Konsole &lt;code&gt;--- CONFIG UPDATE TRIGGER detected ---&lt;/code&gt;. Super!&lt;/p&gt;&#10;&lt;p&gt;Auf dem Server später muss ich das Skript automatisch nach einem Neustart ausführen lassen. Dafür nehme ich das Werkzeug &lt;code&gt;cron&lt;/code&gt; zur &lt;a href="https://wiki.ubuntuusers.de/Cron/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hilfe&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und erstelle einen neuen Eintrag per &lt;code&gt;crontab -e&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# cron can automatically execute recurring tasks&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# in this case, we&amp;#39;re running server-config-handler script on reboot&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@reboot sh /opt/server-config/automation-hooks-handler/server-config-handler.sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mit dem Befehl &lt;code&gt;ps aux&lt;/code&gt; kann ich nun prüfen, ob das Skript auch wirklich ausgeführt wird:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;schallbert@server: ps aux&#10;[...]&#10;8:15 0:00 /bin/bash ./server-config-handler.sh&#10;8:15 0:00 sleep 10&#10;[...]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="sicherungskopie"&gt;Sicherungskopie&lt;/h2&gt;&#10;&lt;p&gt;Eine Sicherungskopie meiner Anwendungen und Dateien anzufertigen bevor ich das Update ausrolle macht total Sinn. Also teile ich &lt;a href="https://blog.schallbert.de/server-protection/#regelm%c3%a4%c3%9fige-backups"&gt;Borg&lt;/a&gt; mit, dass ich jetzt ein Backup anlegen möchte. Vorher muss ich natürlich alle Services stoppen. Somit sind alle Daten zugriffsfrei, kohärent und statisch.&lt;/p&gt;&#10;&lt;h3 id="zustand-und-daten-einfrieren"&gt;Zustand und Daten einfrieren&lt;/h3&gt;&#10;&lt;p&gt;Hierfür lege ich in &lt;code&gt;automation-hooks-handler&lt;/code&gt; ein Skript an, welches alle Container bis auf &lt;em&gt;borg&lt;/em&gt; selbst beendet.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#! /bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /opt/automation-hooks-handler/backup-pre-action.sh&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# this shell script shuts down all docker containers prior to backup&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Shutting down containers for backup:&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;watchtower...&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /opt/watchtower&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose down 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Der Ausdruck &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; bedeutet, dass eventuelle Ausgabe von Fehlern in die Konsole umgeleitet wird. Die Zahl &lt;code&gt;1&lt;/code&gt; stellt nämlich den Filedescriptor für &lt;code&gt;stdout&lt;/code&gt; dar, während &lt;code&gt;2&lt;/code&gt; &lt;code&gt;stderr&lt;/code&gt; meint. Der Operator &lt;code&gt;&amp;gt;&amp;amp;&lt;/code&gt; funktioniert als &lt;code&gt;redirect merger&lt;/code&gt;, also als Umleitung und Zusammenführung. Später können wir an diesem Punkt hingehen und die Ausgabe in ein Logfile schreiben - das lasse ich aber der Einfachheit halber erst einmal aus.&lt;/p&gt;&#10;&lt;h3 id="borgmatic-trigger-handler-mechanismus-2-und-3"&gt;Borgmatic: Trigger-Handler Mechanismus #2 und #3&lt;/h3&gt;&#10;&lt;p&gt;Das Skript kann ich nun auf zwei Arten ausführen lassen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Als Aufruf durch das weiter oben beschriebene Skript &lt;code&gt;server-config-handler&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Durch die vor &lt;em&gt;borg&lt;/em&gt; geschaltete Automatisierungslösung &lt;em&gt;borgmatic&lt;/em&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Ich entscheide mich für die zweite Option und schreibe daher in die &lt;code&gt;borgmatic.d/config.yml&lt;/code&gt; die folgenden Befehle:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# borgmatic.d/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# List of one or more shell commands or scripts to execute before&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# creating a backup, run once per repository.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;before_backup&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;echo &amp;#34;Triggering container shutdown for backup.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;touch /etc/automation-hooks-trigger/backup-pre.txt&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;sleep 20&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;echo &amp;#34;Assuming container shutdown complete. Creating the backup now.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;after_backup&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;echo &amp;#34;Triggering container restart after backup.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;touch /etc/automation-hooks-trigger/backup-post.txt&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;sleep 10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;echo &amp;#34;Assuming container restart complete. Exiting.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Damit dies richtig funktioniert, muss ich in der zugehörigen &lt;code&gt;docker-compose.yml&lt;/code&gt; natürlich ein Volume anlegen und auf den Pfad im Dateisystem des Servers zeigen lassen: &lt;code&gt;${VOLUME_UPDATE_TRIGGER}:/etc/automation-hooks-trigger&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Nun muss ich die Gegenseite dieser Trigger einrichten: Handler überwachen die Triggerdateien auf Änderungen und rufen entsprechend Action-Skripte auf. Sie sind bis auf die Pfade von &lt;code&gt;triggerfile&lt;/code&gt; und &lt;code&gt;actionfile&lt;/code&gt; identisch mit dem oben erläuterten Handler.&lt;/p&gt;&#10;&lt;h3 id="das-backup-anlegen"&gt;Das Backup anlegen&lt;/h3&gt;&#10;&lt;p&gt;Würde ich &lt;em&gt;borg&lt;/em&gt; direkt ansprechen, so ließe sich das Backup über &lt;code&gt;create&lt;/code&gt; erstellen. Dazu müsste ich angeben, in welchem Repository die Sicherungskopie abgelegt werden soll und unter welchem Namen. Im Beispiel unten wird das mit dem Scope-Operator vorgegeben: &lt;code&gt;::config-update&lt;/code&gt;. Anschließend werden die zu sichernden Ordner angegeben.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;borg create /path/to/repo::config-update ~/opt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ich verwende ja &lt;em&gt;borgmatic&lt;/em&gt;, was mir über die Konfigurationsdatei einiges abnimmt. Dafür muss ich den Befehl allerdings im Container ausführen. Um besser prüfen zu können, ob auch alles funktioniert, lasse ich mir Statistiken &amp;ldquo;verbose&amp;rdquo; auf die Konsole ausgeben (&lt;code&gt;--stats -v 1&lt;/code&gt;) sowie die kopierten Dateien anzeigen &lt;code&gt;--files&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec borgmatic sh -c &lt;span style="color:#e6db74"&gt;&amp;#34;cd &amp;amp;&amp;amp; borgmatic --stats -v 1 --files 2&amp;gt;&amp;amp;1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Diese Zeile füge ich dem Automationsskript hinzu.&lt;/p&gt;&#10;&lt;h2 id="zweiter-test-zum-anlegen-des-backups"&gt;Zweiter Test zum Anlegen des Backups&lt;/h2&gt;&#10;&lt;p&gt;Wenn jetzt alles funktioniert, sieht der komplette Ablauf wie folgt aus:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Nach Eingang der geänderten Dateien durch Giteas Automation legt &lt;em&gt;act_runner&lt;/em&gt; (im Docker-Container) die Dateien auf dem Server ab und setzt anschließend den &lt;code&gt;server-config-update&lt;/code&gt; Trigger.&lt;/li&gt;&#10;&lt;li&gt;Binnen &lt;code&gt;10sec&lt;/code&gt; wird der Trigger von &lt;code&gt;server-config-handler.sh&lt;/code&gt; erkannt, welches anschließend &lt;code&gt;server-config-action.sh&lt;/code&gt; aufruft.&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;borgmatic&lt;/em&gt; (im Docker-Container) wird darin beauftragt, ein Backup anzufertigen. Dieses wiederum schreibt den &lt;code&gt;pre-backup&lt;/code&gt; Trigger.&lt;/li&gt;&#10;&lt;li&gt;Erneut binnen &lt;code&gt;10sec&lt;/code&gt; wird durch diesen &lt;code&gt;pre-backup-handler.sh&lt;/code&gt; das &lt;code&gt;backup-pre-action.sh&lt;/code&gt; Skript aufgerufen und alle Container bis auf &lt;em&gt;borgmatic&lt;/em&gt; gestoppt.&lt;/li&gt;&#10;&lt;li&gt;Durch die eingebaute Verzögerung wartet &lt;em&gt;borgmatic&lt;/em&gt; dies ab und erstellt nun das Backup.&lt;/li&gt;&#10;&lt;li&gt;Nach dem Backup schreibt &lt;em&gt;borgmatic&lt;/em&gt; den &lt;code&gt;backup-post-action.sh&lt;/code&gt; Trigger.&lt;/li&gt;&#10;&lt;li&gt;Binnen weiterer &lt;code&gt;10sec&lt;/code&gt; erkennt &lt;code&gt;post-backup-handler.sh&lt;/code&gt; die geänderte Datei und fährt alle Container über &lt;code&gt;post-backup-actions.sh&lt;/code&gt; wieder hoch.&lt;/li&gt;&#10;&lt;li&gt;&lt;em&gt;borgmatic&lt;/em&gt; teilt &lt;code&gt;server-config-update.sh&lt;/code&gt; mit, ob irgendwo im Prozess bis jetzt Fehler auftraten. Falls nicht, wird fortgefahren.&lt;/li&gt;&#10;&lt;li&gt;Alle Docker-Container werden gestoppt.&lt;/li&gt;&#10;&lt;li&gt;Die Server-Konfiguration wird auf die entsprechenden Stellen ausgerollt.&lt;/li&gt;&#10;&lt;li&gt;Es findet ein Neustart aller Docker-Container mit neuer Konfiguration statt.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Ein erster Erfolg: Bis Punkt 6 laufen diese Skripte sogar auf meinem Laptop bereits durch:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;schallbert@laptop: touch server-config-update.txt&#10;server-config-handler@laptop: --- RUN backup ---&#10;borgmatic@docker: /etc/borgmatic.d/config.yml: Running 4 commands for pre-backup hook&#10; Triggering container shutdown for backup. &#10;backup-pre-handler.sh@laptop: --- RUN backup-pre-action.sh ---&#10; Shutting down containers for backup:&#10; watchtower...&#10; [...]&#10; complete.&#10;borgmatic@docker: Assuming container shutdown complete. Creating the backup now.&#10; local: Creating archive&#10; Failed to create/acquire the lock /mnt/repository/lock.exclusive&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="fehlerbehebung-für-failed-to-acquire-the-lock"&gt;Fehlerbehebung für &amp;ldquo;failed to acquire the lock&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Dieses Problem taucht bei mir dann auf, wenn &lt;em&gt;borg&lt;/em&gt; beim Erstellen eines Backups einen Fehler meldet, der das Programm zum Abbruch bringt. In diesem Falle wird das Repository anscheinend nicht korrekt freigegeben, sodass es nach Neustart des Containers für den alten, nun nicht mehr existierenden Container reserviert bleibt. Das folgende Kommando löst dieses Problem:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec borgmatic sh -c &lt;span style="color:#e6db74"&gt;&amp;#34;cd &amp;amp;&amp;amp; borg break-lock /mnt/repository&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="übertragen-der-konfiguration"&gt;Übertragen der Konfiguration&lt;/h3&gt;&#10;&lt;p&gt;Nun müssen die Konfigurationsdateien noch an die jeweils richtige Stelle auf dem Server kopiert werden. Glücklicherweise hatte ich die Ziel-Ordnerstruktur bereits beim Erstellen des Repository geklont, sodass ich das mit einem einzigen Kopierbefehl ohne &amp;ldquo;hardcoding&amp;rdquo; hinbekommen sollte. Nach ein paar Netzrecherchen und einem Blick in die Bedienungsanleitung für den Kopierbefehl &lt;code&gt;man cp&lt;/code&gt; habe ich mein Kommando:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# copy recursively contents of folder &amp;#34;server-config&amp;#34; to &amp;#34;/opt&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo cp -r -v /opt/server-config/. /opt 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Somit sage ich dem Betriebssystem, es solle den Inhalt (&lt;code&gt;/.&lt;/code&gt;) des Ordners &lt;code&gt;server-config&lt;/code&gt; rekursiv (&lt;code&gt;-r&lt;/code&gt;), also inklusive aller Unterordner, in den Ordner &lt;code&gt;opt&lt;/code&gt; befördern, welcher sich im Stammverzeichnis &lt;code&gt;/&lt;/code&gt; befindet. &lt;code&gt;cp&lt;/code&gt; arbeitet hierbei überschreibend-ergänzend, wird also noch nicht vorhandene Dateien erstellen und vorhandene überschreiben, sie also nicht unter gleichem Namen &amp;ldquo;daneben kopieren&amp;rdquo;. Mit der Option &lt;code&gt;-v&lt;/code&gt; lasse ich zusätzliche Details ausgeben, und mit &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; leite ich die Fehlerausgabe in die Konsole um.&lt;/p&gt;&#10;&lt;p&gt;Alle Ordnerindikatoren müssen sich genau dort befinden, wo sie im Befehl stehen: Ein Slash hinter &lt;code&gt;/opt/&lt;/code&gt; würde Ordner nicht-überschreibend redundant kopieren, Dateien aber überschreiben. Ohne &lt;code&gt;.&lt;/code&gt; würde der Ordner &lt;code&gt;server-config&lt;/code&gt; am Zielpfad erstellt.&lt;/p&gt;&#10;&lt;h3 id="umstieg-auf-rsync"&gt;Umstieg auf rsync&lt;/h3&gt;&#10;&lt;p&gt;Der &lt;code&gt;cp&lt;/code&gt;-Befehl kopiert leider auch ein paar Dateien, die ich gar nicht kopiert haben möchte: Repository-spezifische Ordner wie &lt;code&gt;.gitea&lt;/code&gt;, oder auch die Ordner für Trigger und Handler. Diese benötige ich nur unter &lt;code&gt;server-config&lt;/code&gt;, nicht aber auch direkt in &lt;code&gt;opt&lt;/code&gt;. Um dies zu beheben nutze ich stattdessen das Kommando &lt;code&gt;rsync&lt;/code&gt;. Dort kann ich mit &lt;code&gt;-u&lt;/code&gt; einstellen, dass nur neuere Dateien überschrieben werden sollen und über eine &lt;code&gt;--exclude&lt;/code&gt;-Option nicht zu kopierende Dateien und Ordner ausschließen. Dies sieht dann so aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rsync -r -u -v --exclude &lt;span style="color:#e6db74"&gt;&amp;#39;.*&amp;#39;&lt;/span&gt; --exclude &lt;span style="color:#e6db74"&gt;&amp;#39;README.md&amp;#39;&lt;/span&gt; --exclude &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;lt;otherFolders&amp;gt;&amp;#39;&lt;/span&gt; /opt/server-config/. /opt 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Doch plötzlich startet &lt;em&gt;gitea&lt;/em&gt; nicht mehr. Fehlermeldung:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;docker@server: [...] failed to load config file &amp;#34;app.ini&amp;#34;: open: permission denied&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nach längerem Grübeln und diversen Neustarts des Docker-Clients sehe ich, dass &lt;em&gt;rsync&lt;/em&gt; die Berechtigungen der Quelldatei auf die Zieldatei geschrieben hat, was bei &lt;em&gt;cp&lt;/em&gt; vorher nicht der Fall war: &lt;code&gt;-rw-------&lt;/code&gt;. Nun ändere ich dies, indem ich &lt;code&gt;chmod +r app.ini&lt;/code&gt; ausführe. Schon fährt alles wieder wie gewohnt hoch! 🎉&lt;/p&gt;&#10;&lt;h3 id="alle-anwendungen-neu-starten"&gt;Alle Anwendungen neu starten&lt;/h3&gt;&#10;&lt;p&gt;Da ich alles auf meinem Server in Docker betreibe, genügen hierfür zwei einfache &lt;a href="https://docs.docker.com/reference/cli/docker/container/restart/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Befehle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker stop &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;docker ps -a -q&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...roll out config changes...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker restart &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;docker ps -a -q&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="die-fertige-automation"&gt;Die fertige Automation&lt;/h2&gt;&#10;&lt;p&gt;Mein Skript ist nun fertig und ruft lediglich das Action-Skript auf.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#! /bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#/opt/automation-hooks-handler/server-config-handler.sh&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[[&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$ATIME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; !&lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$LTIME&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;--- CONFIG UPDATE TRIGGER detected ---&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ./server-config-action.sh 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LTIME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$ATIME&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sleep &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Das Skript &lt;code&gt;server-config-action&lt;/code&gt; führt dann die oben beschriebenen Aktionen aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#! /bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#/opt/automation-hooks-handler/server-config-action.sh&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;--- RUN backup ---&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec borgmatic sh -c &lt;span style="color:#e6db74"&gt;&amp;#34;cd &amp;amp;&amp;amp; borgmatic --stats -v 1 --files 2&amp;gt;&amp;amp;1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;--- STOP all containers ---&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker stop &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;docker ps -a -q&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;--- DEPLOY config ---&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp -r -v /opt/server-config/. /opt 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;--- RESTART all containers ---&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker restart &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;docker ps -a -q&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Damit das Ganze nun zuverlässig funktioniert, müssen die drei Handler-Skripte im Hintergrund laufen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;server-config-handler.sh&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;backup-pre-handler.sh&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;backup-post-handler.sh&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Sie reagieren auf die jeweiligen Trigger, ausgelöst durch &lt;em&gt;act_runner&lt;/em&gt; von Gitea bzw. durch &lt;em&gt;borgmatic&lt;/em&gt;. Ich erweitere das &lt;em&gt;crontab&lt;/em&gt; jetzt entsprechend und bin dann mit der Aufgabe vorerst fertig. Die Konsolenausgabe des gesamten Vorgangs sieht so aus:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;--- RUN backup ---&#10;/etc/borgmatic.d/config.yml: Running 4 commands for pre-backup hook&#10;Triggering container shutdown for backup.&#10;--- RUN backup-pre-action.sh ---&#10;Shutting down containers for backup:&#10;gitea... &#10;fail2ban...&#10;complete.&#10;Assuming container shutdown complete. Creating the backup now.&#10;local: Creating archive&#10;&amp;lt;borg archive stats&amp;gt;&#10;/etc/borgmatic.d/config.yml: Running 4 commands for post-backup hook&#10;Triggering container restart after backup.&#10;--- RUN backup-post-action.sh ---&#10;Restarting containers after backup:&#10;fail2ban...&#10;gitea... &#10;watchtower...&#10;complete.&#10;Assuming container restart complete. Exiting.&#10;local: Pruning archives&#10;local: Compacting segments&#10;compaction freed about 1.82 MB repository space.&#10;local: Running consistency checks&#10;summary:&#10;/etc/borgmatic.d/config.yml: Successfully ran configuration file&#10;--- STOP all containers ---&#10;&amp;lt;container ids&amp;gt;&#10;--- DEPLOY config ---&#10;sending incremental file list&#10;&amp;lt;files that are copied&amp;gt;&#10;sent 206,558 bytes received 3,463 bytes 420,042.00 bytes/sec&#10;total size is 193,167 speedup is 0.92&#10;--- RESTART all containers ---&#10;&amp;lt;container ids&amp;gt;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Prima! Nun fehlt mir nur noch, dass diese Ausgabe mir zugestellt wird, falls etwas bei diesem Vorgang schief geht.&lt;/p&gt;&#10;</description></item><item><title>Server-Konfiguration mit Git</title><link>https://blog.schallbert.de/server-config-version-control/</link><pubDate>Mon, 15 Jul 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/server-config-version-control/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-07-15-server-versioncontrol-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Diagram thumb for putting server config under version control"&#10; title="Server-Konfiguration mit Git" /&gt;&#10;&lt;h2 id="worum-geht-es"&gt;Worum geht es?&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Ich wünsche eine Versionskontrolle für die Konfiguration von Serverapplikationen&lt;/li&gt;&#10;&lt;li&gt;Diskussion technischer Lösungen zur Umsetzung&lt;/li&gt;&#10;&lt;li&gt;Tutorial: Docker/Gitea/andere &amp;ldquo;Secrets&amp;rdquo; von der Versionskontrolle ausnehmen&lt;/li&gt;&#10;&lt;li&gt;Tutorial: Deploy-Pipeline erstellen&lt;/li&gt;&#10;&lt;li&gt;Automationstrigger für nachgelagertes Ausrollen der Dateien auf dem Server programmieren&lt;/li&gt;&#10;&lt;li&gt;Nächster Artikel: Automation auf dem Server integrieren und Upgrades einspielen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="hintergrund"&gt;Hintergrund&lt;/h2&gt;&#10;&lt;p&gt;Ich habe auf &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;meinem Server&lt;/a&gt; ja &lt;a href="https://blog.schallbert.de/server-auto-upgrade/"&gt;Watchtower laufen&lt;/a&gt;, um die Installierten Distributionen automatisch auf Stand und frisch gepatcht zu halten. Vor Kurzem hatte ich nun den Fall, dass sich mein &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/"&gt;Gitea Action Runner&lt;/a&gt; nicht mehr automatisch starten lassen wollte und auch bei manuellem Start eine Fehlermeldung ausgab.&lt;/p&gt;&#10;&lt;p&gt;Anscheinend hatte sich Docker ohne meine Aufsicht &lt;a href="https://blog.schallbert.de/server-auto-upgrade/"&gt;auf Stand gebracht&lt;/a&gt; und warf nun beim Hochfahren des Runner-Containers einen Volumefehler, die ich so vorher noch nicht gesehen hatte. Die Fehlermeldung war eindeutig und ließ sich mittels kleiner Änderungen in einer Konfigurationsdatei für Gitea leicht beheben. Dennoch hatte ich nun das Gefühl, dass eine Versionskontrolle für meine Konfiguration sinnvoll wäre, um Änderungen, Updates und ihre Beweggründe später besser nachvollziehen zu können.&lt;/p&gt;&#10;&lt;h2 id="vorüberlegungen"&gt;Vorüberlegungen&lt;/h2&gt;&#10;&lt;p&gt;Klingt nun für mich nach einem Zirkelbezug: Ich nehme die Konfigurationsdateien für meinen Server in Gitea auf, welches selbst auf meinem Server läuft. Das könnte beim Auto-Deploy noch spannend werden. Aber mehr dazu später.&lt;/p&gt;&#10;&lt;p&gt;Spontan fallen mir zwei Möglichkeiten ein, eine Versionskontrolle mit automatischer Synchronisation zu bekommen:&lt;/p&gt;&#10;&lt;h3 id="1-hardlink"&gt;1. Hardlink&lt;/h3&gt;&#10;&lt;p&gt;Diese Lösung würde die auf meinem Server in vielen Ordnern verstreuten Konfigurationdateien per &lt;a href="https://en.wikipedia.org/wiki/Hard_link" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hardlink&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; in einen zum Repository erklärten Ordner ablegen. Warum per Hardlink? Weil jeder Service auf meinem Server in sich gekapselt läuft und dafür jeweils eigene Konfigurationsdateien und Umgebungsvariablen gemeinsam mit dem Service abgelegt bekommt. Ohne Hardlinks müsste ich den gesamten Service-Ordner versionieren und meine &lt;code&gt;.gitignore&lt;/code&gt; entsprechend komplex aufblasen.&lt;/p&gt;&#10;&lt;p&gt;Mit Git würde ich die per Hardlink abgebildeten Dateien versionieren und ihre Inhalte auf diese Weise auf Gitea verfügbar machen.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;code&gt;schallbert@server:/server-config-files&lt;/code&gt; &amp;ndash;&amp;gt; hardlinks &amp;ndash;&amp;gt; repository &amp;ndash;&amp;gt; Gitea&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Ich bekäme also eine nachgelagerte &amp;ldquo;Versionsbeobachtung&amp;rdquo; mit Sicherungskopie in dem Sinne, dass die Dateien nun mehrfach herumliegen.&lt;/p&gt;&#10;&lt;h3 id="2-repo-und-auto-deploy-auf-den-server"&gt;2. Repo und auto-deploy auf den Server&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-07-15-server-versioncontrol.jpg" alt="Image: Diagram how version control with auto-deploy might work on my server" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;In diesem Szenario halte ich die Konfigurationsdateien lokal auf meinem Laptop und kann sie wie üblich mit Git versionieren. Auf Gitea würde ich das Repository abbilden, und am Ende der Kette müsste mein Runner bei jeder Änderung der Konfiguration ein Auto-Deploy auf dem Server ausführen und die betroffenen Container anschließend neu starten.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;code&gt;schallbert@laptop:/server-config-files&lt;/code&gt; &amp;ndash;&amp;gt; repository &amp;ndash;&amp;gt; Gitea &amp;ndash;&amp;gt; act-runner &amp;ndash;&amp;gt; &lt;code&gt;server:/&amp;lt;service1...ServiceN&amp;gt;/config-files&lt;/code&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Ich bekäme in diesem Falle den Verwaltungs-&amp;ldquo;Main&amp;rdquo; auf meinen Laptop, und der Server würde der Abbildung auf Gitea folgen.&lt;/p&gt;&#10;&lt;h3 id="probleme"&gt;Probleme&lt;/h3&gt;&#10;&lt;p&gt;In beiden Fällen habe ich noch keine Möglichkeit, geänderte Konfigurationen vorab zu testen. Alles, was ich tue geht direkt nach &amp;ldquo;Prod&amp;rdquo; und wäre live. Schlimmstenfalls kann ich mir so recht leicht mein Setup zerschießen.&lt;/p&gt;&#10;&lt;p&gt;Ändert nun den &lt;em&gt;Status Quo ante&lt;/em&gt; nicht, bei dem ich die Dateien direkt auf dem Server verändere. Also ein neues Problem nur in der Hinsicht, dass ich beim Aufsetzen damals noch keinen Prod-Betrieb hatte und so kein Ausfallrisiko bestand.&lt;/p&gt;&#10;&lt;p&gt;Lösung Zwei scheint mir komplexer umzusetzen zu sein, denn ich benötige hierfür eine Deployment-Pipeline, welche die Dateien im Repository auf meinem Server ausrollt. Zumal sich der Runner in einem Docker-Container aufhält, während die Konfigurationsdateien direkt im Dateisystem des Servers befinden.&lt;/p&gt;&#10;&lt;p&gt;Ein direkter Durchgriff auf das Serverdateisystem &lt;a href="https://maze88.dev/docker-socket-from-within-containers.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ist zwar technisch möglich&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, würde aber meinem beschränkten Verständnis nach &lt;a href="https://dev.to/pbnj/docker-security-best-practices-45ih" target="_blank" rel="noopener noreferrer" class="external-link"&gt;eine große Angriffsfläche für alle meine Dienste&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bedeuten, deren Konfigurationsdateien nun über das Repository in Gitea direkt manipulierbar würden.&lt;/p&gt;&#10;&lt;h3 id="auswahl-meiner-lösung"&gt;Auswahl meiner Lösung&lt;/h3&gt;&#10;&lt;p&gt;Lösung Zwei lagert mir das Sofort-Live-Problem zumindest auf meinen Laptop vor, sodass ich in einem ersten Schritt nicht direkt am Prod-System herumfuhrwerken muss. Hier ließe sich, denke ich, leichter eine Integrationsumgebung aufsetzen, mit der ich meine Konfigurationsänderungen vorab prüfen kann. Da ich alle meine Dienste im Docker laufen habe, könnte sich das vielleicht ganz bequem lösen lassen.&lt;/p&gt;&#10;&lt;h2 id="frisch-ans-werk"&gt;Frisch ans Werk&lt;/h2&gt;&#10;&lt;h3 id="konfigurations-repo-anlegen"&gt;Konfigurations-Repo anlegen&lt;/h3&gt;&#10;&lt;p&gt;Gut, dann ziehe ich mir in einem ersten Schritt die Konfigurationsdateien vom Server. Dafür nutze ich das Dateitransferkommando &lt;a href="https://manpages.debian.org/bookworm/openssh-client/scp.1.en.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;scp&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;: &lt;code&gt;scp server:/path/to/source path/to/target&lt;/code&gt; etwa ein Dutzend mal, bis ich alle Konfigurationsdateien auf meinem Server erwischt habe.&lt;/p&gt;&#10;&lt;p&gt;Ich setze die Ordnerstruktur in diesem Repo direkt so auf, wie die Dateien auf dem Server liegen. So, hoffe ich, mache ich mir das Leben später etwas leichter.&lt;/p&gt;&#10;&lt;h3 id="secrets-in-docker-composeyml"&gt;Secrets in &lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Nur - was tue ich mit &amp;ldquo;secrets&amp;rdquo; in den Konfigurationsdateien? Also privaten Schlüsseln, registration tokens, hashes? Die möchte ich lieber nicht mehr oder minder offen im Repository herumliegen haben. Bei Verwendung von &lt;a href="https://docs.docker.com/compose/compose-file/05-services/#env_file" target="_blank" rel="noopener noreferrer" class="external-link"&gt;docker-compose&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist das das recht einfach: Ich kann geheime Werte in versteckte Dateien für Umgebungsvariablen auslagern und vom Git-Tracking ausnehmen. Im einfachsten Falle heißen solche Dateien schlicht &lt;code&gt;.env&lt;/code&gt; und beinhalten eine Aufzählung an Umgebungsvariablen im Stil von&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;BORG_PASSPHRASE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;lt;redacted&amp;gt;&amp;#34;&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In der entsprechenden &lt;code&gt;docker-compose.yml&lt;/code&gt; ziehe ich die Variable aus der Datei &lt;code&gt;.env&lt;/code&gt; wie folgt an:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- BORG_PASSPHRASE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;BORG_PASSPHRASE&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Diese Änderungen nehme ich direkt mal lokal auf meinem Laptop vor. Über die &lt;code&gt;.gitignore&lt;/code&gt; teile ich mittels &lt;code&gt;.*&lt;/code&gt; mit, dass versteckte Dateien und damit &lt;code&gt;.env&lt;/code&gt; nicht ins Repository aufgenommen werden sollen. Doch woher weiß ich nun, ob die Container noch korrekt hochfahren?&lt;/p&gt;&#10;&lt;h3 id="secrets-in-giteas-appini"&gt;Secrets in Giteas &lt;code&gt;app.ini&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Bei Gitea habe ich mich schon deutlich schwerer damit getan, Secrets in Dateien auszulagern. Die &lt;code&gt;app.ini&lt;/code&gt; wird auch von Gitea dynamisch geschrieben, sodass die Datei bei jedem Neustart des Services ein wenig anders aussieht. Nach langer Recherche fand ich&#10;&lt;a href="https://github.com/go-gitea/gitea/issues/25034" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieses Issue&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, in welchem eine Lösung für das separate Speichern von Secrets gesucht und gefunden wurde.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Sinnvoll erscheint mir nur das separate Speichern der Werte &lt;code&gt;INTERNAL_TOKEN&lt;/code&gt; sowie &lt;code&gt;SECRET_KEY&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Die anderen beiden &lt;a href="https://docs.gitea.com/next/administration/config-cheat-sheet/#server-server" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Properties&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &lt;code&gt;LFS_JWT_SECRET&lt;/code&gt; und &lt;code&gt;JWT_SECRET&lt;/code&gt; werden ohnehin automatisch generiert und regelmäßig überschrieben.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In meiner Konfiguration verwende ich lediglich &lt;code&gt;INTERNAL_TOKEN&lt;/code&gt;. Also werde jenes im Klartext und ohne Anführungszeichen in eine versteckte Datei kopieren (&lt;code&gt;.INTERNAL_TOKEN&lt;/code&gt;) und sie per Docker-Volume dem Container zur Verfügung stellen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Gitea&amp;#39;s docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./.INTERNAL_TOKEN:/run/secrets/INTERNAL_TOKEN:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In der &lt;code&gt;app.ini&lt;/code&gt; ist es nun wichtig, den in der Composedatei angegebenen Pfad einzusetzen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Gitea&amp;#39;s app.ini&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;INTERNAL_TOKEN = /run/secrets/INTERNAL_TOKEN&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nicht etwa wie im oben verlinkten Issue angegeben &lt;code&gt;INTERNAL_TOKEN_URI=/run/secrets/INTERNAL_TOKEN&lt;/code&gt;, denn dies erzeugt in der von mir momentan verwendeten &lt;code&gt;V1.22.1&lt;/code&gt; einen Fehler: &lt;code&gt;Unsupported URI-Scheme&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="mal-ganz-grob-testen"&gt;Mal ganz grob testen&lt;/h3&gt;&#10;&lt;p&gt;Um zu prüfen, ob die geänderten Konfigurationsdateien noch funktionieren, installiere ich mir &lt;code&gt;docker&lt;/code&gt; und &lt;code&gt;docker-compose&lt;/code&gt; auf meinem Laptop. Anschließend probiere ich, die Container mal hochzufahren.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;# docker console log&#10;Error: Network &amp;#39;caddy-proxy&amp;#39; declared as external, but could not be found.&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Stimmt ja, Docker ist hier noch gar nicht konfiguriert. Also das Netzwerk erstellen: &lt;code&gt;sudo docker network create caddy-proxy&lt;/code&gt; und erneut versuchen. Schon beginnt der Download von Gitea samt Abhängigkeiten und der Container startet - wenn auch nicht so wie ich mir das vorgestellt habe: Innerhalb von Docker stimmen die Ordnerberechtigungen nicht, sodass weder Gitea, noch Act-runner auf alle benötigten Dateien zugreifen kann.&lt;/p&gt;&#10;&lt;p&gt;Dennoch, einen ersten Fehler finde ich:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;# gitea container log&#10;WARNING: The GITEA_RUNNER_REGISTRATION_TOKEN variable is not set. Defaulting to a blank string.&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ich hatte nämlich vergessen, den Token-String in Anführungszeichen zu setzen.&lt;/p&gt;&#10;&lt;p&gt;Für eine voll funktionsfähige Integrationsumgebung musste ich also noch mindestens zwei weitere Probleme lösen:&#10;Ordnerberechtigungen für den &lt;code&gt;Main&lt;/code&gt; auf meinem Laptop müssen so angelegt sein, dass auch der User &lt;code&gt;Docker&lt;/code&gt; Schreibrechte bekommt. Als einfache Lösung genügt vorerst, den betreffenden Volumes ein &lt;code&gt;:Z&lt;/code&gt; anzuhängen und sie so als &lt;a href="https://docs.docker.com/reference/cli/docker/container/run/#volumes-from" target="_blank" rel="noopener noreferrer" class="external-link"&gt;private unshared&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; kennzuzeichnen. Eine Volumendefinition sieht in meiner &lt;code&gt;docker-compose.yml&lt;/code&gt; nun so aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./gitea:/data:Z&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ich benötige eine zweite Datei für Umgebungsvariablen, um meine Dienste auf &lt;code&gt;localhost&lt;/code&gt; umzubiegen. Immerhin startet der Service auch so und ich sehe die Logausgabe. Die meisten Konfigurationsfehler kann ich bereits jetzt entdecken.&lt;/p&gt;&#10;&lt;p&gt;Ich bin mir nicht sicher, aber möglicherweise bekomme ich zusätzliche Probleme mit dem &lt;code&gt;caddyserver&lt;/code&gt; wie Zertifikatsverwaltung, Proxy-Einstellungen und so weiter.&lt;/p&gt;&#10;&lt;h2 id="eine-deploy-pipeline-erstellen"&gt;Eine Deploy-Pipeline erstellen&lt;/h2&gt;&#10;&lt;p&gt;Jetzt wäre es noch super, wenn die in das Gitea-Repo hochgeladenen (und vorab lokal auf Funktion getesteten) Dateien automatisch ihren Weg auf meinen Server finden würden. Hierfür könnte ich ein weiteres Docker-Volume anlegen, wo &lt;code&gt;act-runner&lt;/code&gt; dann die per &lt;code&gt;on:push&lt;/code&gt; Trigger abgelegten Daten hinpackt. Sie würden dann auf meinem Server verfügbar werden.&lt;/p&gt;&#10;&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;&#10;&lt;p&gt;Erinnern wir uns an &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/#action-volumes"&gt;meine letzten Versuche&lt;/a&gt; mittels &lt;code&gt;act_runner&lt;/code&gt; Artefakte auf dem Server bereit zu stellen, so können wir einen Großteil dessen auch für diese Aufgabe übernehmen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# deploy-to-server.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Workflow for saving the server&amp;#39;s config repo to the local disk system&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Upload-server-config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;run-name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ gitea.actor }} uploads server configuration files&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;on&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;push&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;branches&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;main&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobs&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Deploy job&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu-latest&lt;/span&gt; &lt;span style="color:#75715e"&gt;# this is the &amp;#34;label&amp;#34; the runner will use and map to docker target OS&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# left: where the output will end up on disk, right: volume name inside container&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/opt/server-config:/workspace/schallbert/server-config/tmp&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;CHECKOUT ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;./tmp&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;RUN FILE CHANGE TRIGGER ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; cd ./tmp/automation-hooks-trigger&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; touch server-config-update.txt&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="merkwürdige-volumefehler"&gt;Merkwürdige Volumefehler&lt;/h3&gt;&#10;&lt;p&gt;Doch der Weg bis hierhin war steinig. Lange hatte ich unter &lt;code&gt;volumes:&lt;/code&gt; auf der Containerseite lediglich &lt;code&gt;/server-config&lt;/code&gt; angegeben und nicht das Arbeitsverzeichnis des Runners. Dann läuft die Action zwar durch und alle Befehle der &lt;code&gt;#TEST&lt;/code&gt;-Sektion funktionieren auch. Wenn ich aber auf meinem Server nachschaue, bleibt der von Docker angelegte &lt;code&gt;server-config&lt;/code&gt; Ordner leer.&lt;/p&gt;&#10;&lt;p&gt;Mit folgenem Debug-Code unter &lt;code&gt;RUN FILE CHANGE TRIGGER&lt;/code&gt; helfe ich mir, die Pfadfehler zu finden:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# deploy-to-server.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; echo &amp;#34;hello world&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; touch updated.txt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; pwd&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ls -al&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mit &lt;code&gt;echo&lt;/code&gt; prüfe ich, ob mein Code im Runner überhaupt ausgeführt wird. Das &lt;code&gt;touch&lt;/code&gt;-Kommando setzt mir den aktuellen Zeitstempel in die &lt;code&gt;updated.txt&lt;/code&gt;-Datei, sodass ich dies später als &amp;ldquo;Hook&amp;rdquo; für weitere Automation verwenden kann. &lt;code&gt;pwd&lt;/code&gt; zeigt mir den aktiven Pfad innerhalb des Runners an, sodass ich das Docker-Volume korrekt auf die Server-Festplatte abbilden kann. &lt;code&gt;ls -al&lt;/code&gt; zeigt mir, ob die im Schritt &lt;code&gt;CHECKOUT&lt;/code&gt; zusammengestellten Konfigurationsdateien korrekt geschrieben wurden.&lt;/p&gt;&#10;&lt;p&gt;Ich erkenne dadurch, dass der Volume-Pfad auf der &amp;ldquo;rechten Seite&amp;rdquo; falsch war. Ich biege ihn entsprechend auf das aktive Verzeichnis des Runners um:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# deploy-to-server.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# left: where the output will end up on disk, right: volume name inside container&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/opt/server-config:/workspace/schallbert/server-config/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Anschließend bekam ich Folgendes zu lesen:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;# gitea / act-runner console log&#10;failed to create container: &amp;#39;Error response from daemon: Duplicate mount point: /workspace/schallbert/server-config&amp;#39;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Anscheinend legt sich der Runner die &amp;ldquo;rechte Seite&amp;rdquo; des Mountpoints automatisch selbst an und kann daher nicht erneut belegt werden. Erst durch das Hinzufügen eines weiteren Pfadteiles, in meinem Falle &lt;code&gt;/tmp&lt;/code&gt; - siehe &lt;a href="https://blog.schallbert.de/server-config-version-control/#setup"&gt;oben&lt;/a&gt; - behebe ich den Fehler und auf meinem Server erscheint endlich 😌 der lang ersehnte Ordner &lt;code&gt;server-config&lt;/code&gt; mit Inhalt:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;schallbert@schallbert-ubuntu-:/opt/server-config# ls -al&#10;total 52&#10;drwxr-xr-x 9 root root 4096 Jul 12 15:38 .&#10;drwxr-xr-x 9 root root 4096 Jul 11 19:56 ..&#10;-rwxr-xr-x 1 root root 395 Jul 11 20:05 boot-after-backup.sh&#10;drwxr-xr-x 3 root root 4096 Jul 11 20:05 borgmatic&#10;drwxr-xr-x 2 root root 4096 Jul 11 20:05 caddy2&#10;drwxr-xr-x 3 root root 4096 Jul 11 20:05 fail2ban&#10;drwxr-xr-x 8 root root 4096 Jul 12 15:38 .git&#10;drwxr-xr-x 3 root root 4096 Jul 11 20:05 .gitea&#10;drwxr-xr-x 4 root root 4096 Jul 11 20:05 gitea&#10;-rw-r--r-- 1 root root 312 Jul 11 20:05 .gitignore&#10;-rw-r--r-- 1 root root 557 Jul 11 20:05 README.md&#10;-rwxr-xr-x 1 root root 371 Jul 11 20:05 shutdown-for-backup.sh&#10;-rw-r--r-- 1 root root 0 Jul 12 15:38 updated.txt&#10;drwxr-xr-x 2 root root 4096 Jul 11 20:05 watchtower&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="verteilen-der-konfiguration-auf-dem-server"&gt;Verteilen der Konfiguration auf dem Server&lt;/h2&gt;&#10;&lt;p&gt;Gut, damit sind die ersten Schritte getan. Ich habe nun ein vernünftig konfiguriertes Git-Repository, was meine Server-Konfiguration abbildet und vom Laptop aus gepflegt und zumindest rudimentär getestet werden kann. Außerdem kann ich über eine automatische &lt;code&gt;Action&lt;/code&gt; Updates der Konfiguration auf dem Server ablegen und eine Update-Datei mit Zeitstempel beschreiben.&lt;/p&gt;&#10;&lt;p&gt;Nun muss das Update auf dem Server noch entgegengenommen, verteilt und die betroffenen Programme und Dienste sollen neu gestartet werden. Doch dies schauen wir uns im Artikel &lt;a href="https://blog.schallbert.de/server-config-deploy/"&gt;Serverkonfiguration ausrollen&lt;/a&gt; an.&lt;/p&gt;&#10;</description></item><item><title>Stahl bearbeiten</title><link>https://blog.schallbert.de/milling-steel/</link><pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/milling-steel/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-06-10-siemens-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: PC towers with CNC-made window"&#10; title="Stahl bearbeiten" /&gt;&#10;&lt;p&gt;Für unseren Retro-Gaming Stand auf der &lt;a href="https://blog.schallbert.de/makerfaire-ruhr/"&gt;Maker Faire Ruhr 2024&lt;/a&gt; habe ich Ausschnitte in PC-Gehäuse gefräst. Die zugehörige Geschichte möchte ich hier nun erzählen und passende Parameter für die Bearbeitung von Stahl auf &amp;ldquo;weichen&amp;rdquo; Hobbymaschinen teilen.&lt;/p&gt;&#10;&lt;h3 id="doch-vorsicht"&gt;Doch Vorsicht!&lt;/h3&gt;&#10;&lt;p&gt;Vorab möchte ich erwähnen, dass ich einige sicherheitsrelevante Dummheiten begangen habe, die in Zukunft unbedingt zu vermeiden sind:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Niemals mit Handschuhen in der Nähe sich drehender Maschinenteile hantieren!&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#10;&lt;li&gt;Kühl- und Schmiermittel müssen für den Verwendungszweck geeignet sein; Rauch- und Brandgefahr!&lt;/li&gt;&#10;&lt;li&gt;Schutz- und Abschrankeinrichtungen sind fest zu montieren und dürfen nicht umgangen werden!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Wer sich also mit Arbeits- und Betriebssicherheit auskennt, sollte das weiter unten zur Verfügung gestellte Video besser nicht anschauen.&lt;/p&gt;&#10;&lt;h2 id="das-werkstück"&gt;Das Werkstück&lt;/h2&gt;&#10;&lt;p&gt;Zu bearbeiten habe ich Stahlbleche von PC-Gehäusen (MIDI-Tower) mit einer Stärke von &lt;code&gt;0.7mm-0.9mm&lt;/code&gt;. Beim Material handelt es sich um galvanisch verzinkten, kalt gewalzten Stahl (&lt;a href="https://de.cosasteel.com/secc-steel/#SECC_Material_Specification" target="_blank" rel="noopener noreferrer" class="external-link"&gt;SECC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;), also eine günstige und verhältnismäßig leicht zu verarbeitende Stahlsorte mit niedrigem Kohlenstoffgehalt und geringer Beimischung anderer Legierungselemente.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-06-10-cad-pcwindow-opensea.jpg" alt="Image: PC tower case service flap drawing" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Diese Stahlsorte wird für Bleche im Automobilbau und vielen Haushaltsgeräten, vor allem in &amp;ldquo;weißer Ware&amp;rdquo; eingesetzt und findet zudem oft im Automaten- und Schaltschrankbau Verwendung. Somit ist diese Fräsarbeit übertragbar auf andere Einsatzgebiete.&lt;/p&gt;&#10;&lt;p&gt;Die Maße der Gehäuseseiten liegen zwischen &lt;code&gt;35x45xm&lt;/code&gt; und &lt;code&gt;52x50cm&lt;/code&gt;, passen also alle problemlos auf meine Maschine. Mit einer Ausnahme liegen die Seitenteile mit ihrer sichtbaren Seite plan und ohne Ausschnitte auf, sodass mein Vakuumtisch für das Niederhalten ohne Weiteres in Frage kommt.&lt;/p&gt;&#10;&lt;p&gt;Die Seitenwand eines der Gehäuse besitzt kiemenartige Perforationen zur Belüftung sowie eine Vertiefung für einen Griff. Bei den Lüftungsschlitzen behelfe ich mir mit Frischhaltefolie zum Halten des Vakuumdrucks, während der Verlust des Vakuums beim Durchfräsen der Griffmulde in Kauf genommen wird.&lt;/p&gt;&#10;&lt;h3 id="cadcam"&gt;CAD/CAM&lt;/h3&gt;&#10;&lt;p&gt;Die zu schneidenden Ausschnitte sind sämtlich geometrisch einfach und berücksichtigen für Rundungen &lt;code&gt;r=1.5*Fräserradius&lt;/code&gt;. Das Fenster für den &amp;ldquo;Meeres-PC&amp;rdquo; ist dabei zweiteilig angelegt und soll eine Sturmszene auf See darstellen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-06-10-cad-pcwindow-opensea-detail.jpg" alt="Image: CAD detail drawing for open-sea-PC" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Das Fenster ist rundherum &lt;code&gt;0.25mm&lt;/code&gt; kleiner als die Aussparung, was in einer fast spielfreien Passung führt und sehr professionell aussieht.&lt;/p&gt;&#10;&lt;p&gt;Im CAM stelle ich Gegenlauffräsen ein, was bei weniger steifen Maschinen wie meiner bei Bearbeitung harter und zäher Materialien anzuraten ist. Die Maschine läuft so etwas weicher und leiser - trotzdem schüttelt und vibriert sie noch beträchtlich.&lt;/p&gt;&#10;&lt;p&gt;Bei der Tiefenzustellung wähle ich einen Wert, der vier hundertstel Millimeter geringer ausfällt als die Materialstärke beträgt. So lasse ich praktisch nur noch eine hauchdünne Folie aus Lack und Korrosionsschutz übrig, verschleiße mir aber nicht die Haftvermittlungsmatte aus Gummi.&lt;/p&gt;&#10;&lt;h3 id="fräsparameter"&gt;Fräsparameter&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-06-10-cutter.jpg" alt="Image: endmill for cutting steel" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Im Vergleich zur Bearbeitung von Holz liegen die Schnittgeschwindigkeiten bei Stahl deutlich niedriger. Während ich in Birke Multiplex mit &lt;code&gt;450m/min&lt;/code&gt; unterwegs bin, empfehlen mir verschiedene Schnittdatenrechner wie &lt;a href="https://www.machiningdoctor.com/de/calculators/speeds-and-feeds-calculator/#f1p5" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieser hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Werte zwischen &lt;code&gt;80m/min&lt;/code&gt; und &lt;code&gt;200m/min&lt;/code&gt;. So ergibt sich beispielsweise ein Parametersatz wie &lt;code&gt;S4250u/min, F255mm/min&lt;/code&gt; für meinen Zweischneider aus Feinstkorn-Hartmetall.&lt;/p&gt;&#10;&lt;p&gt;Dieser Fräser besitzt im &lt;code&gt;45°&lt;/code&gt;-Winkel abgeschrägte Schnittkanten von &lt;code&gt;0.2mm&lt;/code&gt; Länge und mit &lt;code&gt;30°&lt;/code&gt; einen starken Drall. Zur Erhöhung der Standzeit ist er im Vakuumverfahren &lt;a href="https://de.wikipedia.org/wiki/Physikalische_Gasphasenabscheidung" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PVD&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit einer etwa &lt;code&gt;3µm&lt;/code&gt; starken &lt;a href="https://en.wikipedia.org/wiki/Titanium_aluminium_nitride" target="_blank" rel="noopener noreferrer" class="external-link"&gt;TiAlN&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Legierung beschichtet.&lt;/p&gt;&#10;&lt;p&gt;Ich habe auf meiner Maschine nach Rücksprache mit dem Hersteller des Fräsers Parameter vorgewählt, die ich in Testläufen nochmal anpassen und damit höhere Tiefenzustellung bei geringerer Spantemperatur erreichen konnte (keine blauen Späne mehr). Hier die Werte, die ich für die Bearbeitung schließlich verwendet habe:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Fräser&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Schneidengeometrie&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Drehzahl&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Vorschub XY&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Vorschub Z&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Zustellung Z&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6mm, Vollhartmetall&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2Flutes 30°, l=12, 45°0.2Fase&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6000u/min&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;550mm/min&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;250mm/min&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.8mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="update-fräsparameter"&gt;Update Fräsparameter&lt;/h3&gt;&#10;&lt;p&gt;Nach ein paar Rückfragen in verschiedenen Foren zum Thema habe ich neue Erkenntnisse gewonnen, die meine Fräsparameter für zukünftige Projekte nochmal deutlich verändern werden. Diese lauten:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;In der Stahlbearbeitung für die Fräser auf abgeschrägte Schnittkanten achten&lt;/li&gt;&#10;&lt;li&gt;Vorschub hoch, Tiefenzustellung runter (gilt besonders für &amp;ldquo;weiche&amp;rdquo; Maschinen)&lt;/li&gt;&#10;&lt;li&gt;Trocken fräsen ist weniger problematisch als ich dachte&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Dieses &lt;a href="https://www.youtube.com/watch?v=WzT4HJalwAs" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Youtube-Video von Stefan Gotteswinter&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; erläutert den Vorteil durch geringere Spandicken bei Verwendung abgeschrägter Schnittkanten sehr anschaulich. Dies funktioniert dann am besten, wenn die Tiefenzustellung nicht größer ist als die Länge der Fase am Schneidenende.&#10;Auch auf &lt;a href="https://www.cnczone.nl/viewtopic.php?t=23051" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cnczone&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; wird mir empfohlen, einen &amp;ldquo;High-speed milling&amp;rdquo;-Ansatz zu wählen.&lt;/p&gt;&#10;&lt;p&gt;Für meinen Zweischneider passe ich die Schnittdaten also so an, dass ich die Tiefenzustellung drastisch zurücknehme, dafür aber Vorschub und Drehzahl erhöhe:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Fräser&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Schneidengeometrie&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Drehzahl&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Vorschub XY&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Vorschub Z&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Zustellung Z&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6mm Zweischneider&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30°Drall, l=12, 45°0.2Fase&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10600u/min&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;970mm/min&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;450mm/min&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.2mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Ich bin schon sehr gespannt darauf, diese Werte in der Praxis zu erproben.&lt;/p&gt;&#10;&lt;h2 id="vorbereitung-des-fräsvorganges"&gt;Vorbereitung des Fräsvorganges&lt;/h2&gt;&#10;&lt;p&gt;Da ich das erste Mal in Stahl unterwegs bin, musste ich noch ein paar Besorgungen machen und Dinge vorab ausprobieren.&lt;/p&gt;&#10;&lt;h3 id="kühlschmierstoff"&gt;Kühlschmierstoff&lt;/h3&gt;&#10;&lt;p&gt;Bei Arbeiten in Stahl sei Kühlung zum Schutz des Fräsers unerlässlich, meinte der Fräsenhersteller. Also habe ich mir eine Spritze besorgt, mit der ich Schmiermittel punktgenau aufbringen kann. Nur: Welches Mittel wählen?&lt;/p&gt;&#10;&lt;p&gt;Für nur drei Teile besorge ich mir nicht extra eine Minimalmengenschmierung oder Kühlschmierstoffe aus dem Industriebedarf. Also probiere ich im Haushalt verfügbare Mittel aus:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Sprühschmierstoff WD40: Für mich nicht geeignet. Verdampft schnell, riecht.&lt;/li&gt;&#10;&lt;li&gt;Nähmaschinenöl / Feinöl: Nicht geeignet. Sehr flüssig (fließt in Vakuumlöcher), raucht und stinkt.&lt;/li&gt;&#10;&lt;li&gt;Rapsöl zum Braten: Wunderbar! Schmiert gut, relativ viskos, geruchsarm, kaum Rauchentwicklung.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="spanschutz"&gt;Spanschutz&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-06-10-chipprotector.jpg" alt="Image: This tube made of acrylic glass protects from flying chips" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Die bei der Bearbeitung entstehenden Metallspäne sind kurz, heiß und scharfkantig. Sie bleiben gern in der Kleidung hängen und fliegen so weit, dass selbst bei über einem Meter Entfernung zur Maschine noch reichlich Späne ankommen.&lt;/p&gt;&#10;&lt;p&gt;Also habe ich mir aus einem Acrylglasrohr mit &lt;code&gt;20cm&lt;/code&gt; Durchmesser ein Segment abgesägt, geschlitzt und es als Spanschutz an der Z-Achse eingehängt.&lt;/p&gt;&#10;&lt;p&gt;Mein Fehler an dieser Stelle: Ich habe auf die Klemmkraft alleine vertraut und den Spanschutz nicht zusätzlich mit Schrauben befestigt. Zum Glück verklemmte er sich erst beim Fertigen des letzten Teiles.&lt;/p&gt;&#10;&lt;h2 id="fräsen"&gt;Fräsen&lt;/h2&gt;&#10;&lt;p&gt;Man merkt sofort, dass die Maschine nicht die nötige Steifigkeit zur Verarbeitung von Stahl hat. Sobald der Fräser in das Material eintaucht, beginnt die Maschine zu rattern und zu schütteln. Dennoch kämpft sie sich tapfer durch das Material, während ich stets mit einem Tropfen Öl am Fräspfad bereit stehe.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/jbtp1jvVi2GLvJq1GdhmgF" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Stahl fräsen!&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Spindel hat mehr als genügend Reserven zur Verfügung selbst bei der geringen Drehzahl. Jedoch achte ich für die Werkstückpositionierung sehr darauf, möglichst mittig an der Kugelumlaufspindel der Y-Achse zu arbeiten. Hier erfährt die Maschine die geringsten Verwindungskräfte. Die Späne sehen für meine Augen gut aus.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-06-10-chips.jpg" alt="Image: steel chip quality" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das Geräusch verändert sich beim Fräsen: Ich habe mehr Vibrationen in der Maschine, wenn ich in Y-Richtung fräse, als wenn sich nur die X-Achse bewegt. Diesem Phänomen werde ich später genauer auf den Grund gehen.&lt;/p&gt;&#10;&lt;h2 id="das-ergebnis"&gt;Das Ergebnis&lt;/h2&gt;&#10;&lt;p&gt;Die Fensterausschnitte kann ich nach der Bearbeitung auf der CNC mit einem Cuttermesser einfach ausschneiden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2024-06-10-cuttingsteel.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Video: Auslösen des Ausschnittes mit einem Cuttermesser&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die (minimalen) Grate entferne ich fix mit einer Feile.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-06-10-pcwindow-opensea.jpg" alt="Image: Cutout completed and cleaned" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Danach fertige ich Fenster aus durchsichtigem Acrylglas in &lt;code&gt;3mm&lt;/code&gt; Stärke, welche ich rundherum mit einer &lt;code&gt;10mm&lt;/code&gt; breiten Falz von &lt;code&gt;1mm&lt;/code&gt; Tiefe versehe. Somit wird die Stärke des doppelseitigen Klebebands zum Einpassen berücksichtigt und die Scheibe schließt plan mit dem PC-Gehäuse ab.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-06-10-siemens.jpg" alt="Image: The ready-built PC" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das obige Bild zeigt ein &lt;code&gt;Celsius 1000 Workstation&lt;/code&gt; Gehäuse von Siemens, welches mit einem schlichten Fenster versehen, nun richtig edel aussieht.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Handschuhe können bei versehentlichem Kontakt mit dem Fräser mitgerissen werden. Dies kann schwere Handverletzungen nach sich ziehen.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>CNC Tuning #3</title><link>https://blog.schallbert.de/cnc-tuning-pathinterpreter/</link><pubDate>Sat, 04 May 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-tuning-pathinterpreter/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-20-G64RD-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Thumbnail of endmill cutting forces"&#10; title="CNC Tuning #3" /&gt;&#10;&lt;p&gt;Dies ist der letzte Teil meiner Reihe über die Optimierung der Fertigungszeit auf CNC-Fräsmaschinen. Er konzentriert sich auf den Pfadinterpreter der CNC-Steuersoftware.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-tuning-gcode/"&gt;&lt;em&gt;Teil 1&lt;/em&gt;: Besserer G-Code&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-tuning-kinematics/"&gt;&lt;em&gt;Teil 2&lt;/em&gt;: Kinematische Parameter hochdrehen&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Der Pfadinterpreter der Steuersoftware ist dafür zuständig, die Anweisungen aus dem &lt;code&gt;G-Code&lt;/code&gt; zu lesen und in Maschinenbewegungen zu übersetzen. Soll beispielsweise &lt;code&gt;G01 X100 Y200 Z-2.0 F2000 S18000&lt;/code&gt; ausgeführt werden, so muss der Pfadinterpreter die akuelle Position der Maschine kennen und einen Fräsvektor erzeugen, der zur gewünschten Position mit den vorgegebenen Parametern zeigt.&lt;/p&gt;&#10;&lt;p&gt;Hierbei muss der Interpreter die kinematischen Parameter der Maschine berücksichtigen: Mit welcher Rampe auf Zielgeschwindigkeit &lt;code&gt;F1000&lt;/code&gt; beschleunigt wird ob der gewünschte Vorschub überhaupt zulässig ist, und ab welchem Punkt wieder gebremst werden muss um den Zielpunkt exakt zu erreichen. Die so gewonnenen Daten werden schließlich in eine Schrittfolge für die drei Schrittmotoren übersetzt, in einen Pufferspeicher geladen und schließlich in Echtzeit an die Motorendstufen ausgegeben.&lt;/p&gt;&#10;&lt;h2 id="schritt-3-optimierung-des-pfadinterpreters"&gt;Schritt 3: Optimierung des Pfadinterpreters&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-05-04-laf-settings.jpg" alt="Image: Look Ahead Feed in the CNC control software" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Viele Steuersoftware-Anbieter haben integrierte Fräspfadfilter programmiert, welche mittels Kommando &lt;code&gt;G64&lt;/code&gt; oder per Menü zu konfigurieren sind. Ich verlinke hier mal ein paar Beispiele verschiedener Hersteller, wo für spezifische Details nachgelesen werden kann:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://www.datron.com/resources/blog/toolpath-filter-achieves-a-perfect-surface-finish/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&amp;ldquo;Toolpath filter&amp;rdquo; von Datron&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.edingcnc.com/settings/trajectory" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&amp;ldquo;Look Ahead Feed&amp;rdquo; von EdingCNC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="http://wiki.linuxcnc.org/cgi-bin/wiki.pl?TrajectoryControl" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&amp;ldquo;Path Blending&amp;rdquo; von LinuxCNC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Die Kunst ist nun, eine Konfiguration passend zum Fräsjob und der Maschinenkinematik zu finden. Es geht hier immer um einen Kompromiss zwischen Fertigungsgeschwindigkeit und Bahntoleranz. Soll also mit hohem Vorschub gefahren werden, werden eigentlich spitze Kanten verrundet, verbundene Liniensegmente in einem Toleranzband angefahren oder Pfade so vereinfacht, dass außerhalb der von der Maschine erreichbaren Beschleunigung befindliche Pfadpunkte nicht angefahren werden.&lt;/p&gt;&#10;&lt;h3 id="grundlagen-was-ist-look-ahead-feed"&gt;Grundlagen: Was ist &amp;ldquo;Look Ahead Feed&amp;rdquo;?&lt;/h3&gt;&#10;&lt;p&gt;Hiermit ist gemeint, dass der Pfadinterpreter der Steuersoftware sich während des Ausführens einer Fräsoperation - z.B. &lt;code&gt;G01 X* Y* (lineare Bewegung Punkt zu Punkt)&lt;/code&gt; - bereits die nächste(n) Operation(en) anschaut und herausfindet, ob und wie stark zwischen den Operationen abgebremst und wieder beschleunigt werden muss bzw. direkt durchgefahren werden kann. Die Physik dahinter erkläre ich &lt;a href="https://blog.schallbert.de/portal-milling-setup/#acceleration"&gt;im Artikel zum CNC-Setup&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Hierdurch kann teils massiv Zeit gespart werden, der Fräsvorgang wird flüssiger und die Maschine erfährt geringere Belastungen. Zusätzlich zur Vermeidung von Beschleunigungs- und Bremsvorgängen kann mittels Parameter &lt;code&gt;G64&lt;/code&gt; eingestellt werden, ob und wie Fräspfadfilter die geplante Trajektorie verändern dürfen, um weiter auf höhere Bahngeschwindigkeiten zu optimieren.&lt;/p&gt;&#10;&lt;h3 id="ideal-im-gravurbetrieb"&gt;Ideal im Gravurbetrieb&lt;/h3&gt;&#10;&lt;p&gt;Im Gravurbetrieb kann man sich solche Optionen hervorragend zu Nutze machen: Höhere Toleranzen sind hier meist OK, und leicht verrundete Kanten oft akzeptabel. Bei 3D-Gravuren dauert das Fräsen durch die vielen Pfade lang, und hier ist besonders viel Zeit herauszuholen.&lt;/p&gt;&#10;&lt;p&gt;Da ich Eding CNC benutze, kann ich nur für Parameter dieser Steuerung Tipps geben. Nun aber ab ins Detail zur Erklärung der Einstellungen!&lt;/p&gt;&#10;&lt;h3 id="fräspfadtoleranz-g64-p"&gt;Fräspfadtoleranz &lt;code&gt;G64 P*&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-05-04-lafblending.jpg" alt="Image: Look Ahead Feed G64 CNC corner blending" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Dieser Parameter gibt der Steuersoftware die allgemeine Toleranz der Pfadgenauigkeit vor.&lt;/p&gt;&#10;&lt;p&gt;Ich habe meine Maschine so eingestellt, dass sie im Gravurbetrieb Abweichungen vom vorgegebenen Fräspfad von bis zu &lt;code&gt;LAF blending tolerance = 0.3mm&lt;/code&gt; zulässt. Alternativ kann die Konfiguration per Kommando &lt;code&gt;G64 P0.3&lt;/code&gt; erfolgen. Im Bild habe ich skizziert, wie sich eine solche Einstellung in der Praxis auswirken kann: Der Fräspfad (weiß) besteht aus &lt;code&gt;G01&lt;/code&gt;-Liniensegmenten. Die Toleranz ist mit Hilfe von Kreisen eingezeichnet. Der Pfadinterpreter der Steuersoftware kann nun eigenständig Kreissegmente so definieren, dass innerhalb der Toleranz eine möglichst hohe Bahngeschwindigkeit erreicht wird.&lt;/p&gt;&#10;&lt;p&gt;Die so ermittelte Bahn (türkis) ähnelt einer Spline-Kurve. Zu beachten ist hier, dass sämtliche Linien von dieser Einstellung betroffen sein können - auch solche, die strenge Toleranzanforderungen haben. Ist dies nicht gewünscht, so sollte hier ein kleinerer Wert verwendet werden.&lt;/p&gt;&#10;&lt;h3 id="pfadverrundung-g64-r"&gt;Pfadverrundung &lt;code&gt;G64 R*&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Treffen zwei Liniensegmente im Winkel von bis zu &lt;code&gt;LAF full speed blending angle threshold = 12°&lt;/code&gt; aufeinander, wird die Maschine ohne abzubremsen versuchen, dem Winkel genau zu folgen. Bei größeren Winkeln wird mit reduzierter Geschwindigkeit mit per &lt;code&gt;G64 P*&lt;/code&gt; definierter Toleranz gefahren. Diese Konfiguration wird mit &lt;code&gt;G64 R12&lt;/code&gt; programmiert.&lt;/p&gt;&#10;&lt;p&gt;Die Werte hier sind mit großer Vorsicht einzustellen, weil sie zu Beschleunigungsspitzen auf der Maschine führen. Große Werte können Schrittverluste und höheren Verschleiß der CNC-Fräse verursachen. Dafür werden Bahngeschwindigkeit und Genauigkeit optimal gehalten.&lt;/p&gt;&#10;&lt;h3 id="pfadverrundung-bei-reduziertem-vorschub-g64-s-d"&gt;Pfadverrundung bei reduziertem Vorschub &lt;code&gt;G64 S* D*&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-05-04-G64RD.jpg" alt="Image: Look Ahead Feed G64 CNC corner radius" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Diese Konfiguration gibt der Steuersoftware die Möglichkeit, Kanten bis zum eingestellten Toleranzwert zu verrunden: &lt;code&gt;LAF reduced speed path smoothing delta = 0.001mm&lt;/code&gt;. In Kombination mit der Verrundung muss der Winkel eingestellt werden, bis zu dem diese Verrundung durchgeführt werden soll: &lt;code&gt;LAF reduced speed path smoothing angle threshold = 0°&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Programmiert wird diese Funktion am Besten in Kombination mit der Pfadverrundung bei vollem Vorschub als &lt;code&gt;G64 R* S* D*&lt;/code&gt;. Der Vorteil hierbei ist, dass Liniensegmenten wie im Bild gezeigt sehr genau gefolgt wird, und die Kanten gleichzeitig aber fein justierbar abgerundet werden. Bis zum über &lt;code&gt;R&lt;/code&gt; definierten Winkel wird sogar mit vollem Vorschub dem Pfad exakt gefolgt.&lt;/p&gt;&#10;&lt;p&gt;Werden bei &lt;code&gt;S&lt;/code&gt; sehr große Winkel eingestellt, so findet entsprechend &lt;code&gt;D&lt;/code&gt; auch eine stärkere Verrundung statt. Überschreitet der im Fräsprogramm vorgegebene Winkel jedoch den konfigurierten Wert, so fällt der Algorithmus auf die für &lt;code&gt;P&lt;/code&gt; hinterlegten Werte bzw. in den exakten Pfadfolgemodus zurück.&lt;/p&gt;&#10;&lt;h3 id="pfadvereinfachung-g64-q"&gt;Pfadvereinfachung &lt;code&gt;G64 Q*&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-05-04-lafcombine.jpg" alt="Image: Look Ahead Feed G64, path combine setting" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Enthält die G-Code Datei zahlreiche, sehr kurze Liniensegmente (wie sie oft bei der Erstellung von Pfaden aus Bilddateien entstehen), kann diese Funktion durch Vereinfachen der Fräspfade Abhilfe schaffen. Dabei gibt die Zahl hinter dem Parameter die Toleranz an, bis zu der Liniensegmente zu einer Linie verschmolzen werden.&lt;/p&gt;&#10;&lt;p&gt;Diese Einstellung entlastet den Pufferspeicher der Maschine und erhöht gleichzeitig die mittlere Bahngeschwindigkeit. Für den Gravurbetrieb halte ich diese Einstellung insbesondere für interessant, wenn mit &amp;ldquo;verrauschten&amp;rdquo; Daten gearbeitet wird und im CAD nicht die Möglichkeit bestand, die Anzahl der Pfade zu reduzieren.&lt;/p&gt;&#10;&lt;h3 id="beschleunigungsfilter-g64-f"&gt;Beschleunigungsfilter &lt;code&gt;G64 F*&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Mittels diesen Parameters können die durch Look Ahead Feed möglicherweise erzeugten Beschleunigungsspitzen abgemildert werden. Der minimal einzustellende Wert ergibt sich durch den Quotienten aus gewähltem Vorschub für die Operation und maximaler Geschwindigkeit der Maschine.&lt;/p&gt;&#10;&lt;p&gt;Beispiel: &lt;code&gt;Fmin = Foperate / Fg00&lt;/code&gt;, bei &lt;code&gt;Fg00 = 12000mm/min&lt;/code&gt; und &lt;code&gt;Foperate = 5000mmm/min&lt;/code&gt; ist &lt;code&gt;Fmin = 0.42&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Also wird bei Vorschub von &lt;code&gt;5000mm/min&lt;/code&gt; die maximal mögliche Beschleunigung der Maschine abgerufen und Kurvenradien so eingestellt, dass sie bei konstanter Bahngeschwindigkeit gefahren werden. Alle Werte größer gleich &lt;code&gt;Fmin = 1&lt;/code&gt; sind inherent sicher, unabhängig vom gewählten Vorschub.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-05-04-accelfilter.jpg" alt="Image: acceleration filter for G64 operation" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Dieser Parameter wurde entwickelt für die Bearbeitung von elastischen Materialien wie Gummi oder Kork, wo eine konstante Vorschubgeschwindigkeit extrem wichtig und die Bahntreue eher zweitrangig ist. Der Parameter wird in Kombination mit &lt;code&gt;G64 R*&lt;/code&gt; verwendet. Die folgende Programmierung für das obige Beispiel würde die volle Kapazität der Maschine abrufen: &lt;code&gt;G64 R120 F0.42&lt;/code&gt;, wobei mit &lt;code&gt;F5000&lt;/code&gt; gefahren wird, &lt;code&gt;F12000&lt;/code&gt; der Maximalvorschub der Maschine ist und spitze Winkel bis &lt;code&gt;120°&lt;/code&gt; ohne abzubremsen gefahren werden sollen.&lt;/p&gt;&#10;&lt;p&gt;Wende ich diese Werte auf meine Maschine an, entsteht das nebenstehende Bild. Hier soll ein Rechteck mit &lt;code&gt;25 x 100mm&lt;/code&gt; Kantenlänge gefertigt werden. Weiß ist wieder die Zieltrajektorie dargestellt. Die türkise Kurve zeigt die durch die Programmierung des Beschleunigungsfilters gefahrene Kurve mit konstanter Bahngeschwindigkeit. Je geringer der Vorschub im Verhältnis zum Maximalvorschub und je höher die Beschleunigung der Maschine eingestellt ist, desto kleiner wird der notwendige Radius.&lt;/p&gt;&#10;&lt;p&gt;Für den Gravurbetrieb und das Fräsen fester Materialien wird dieser Parameter meiner Erfahrung nach jedoch nicht verwendet.&lt;/p&gt;&#10;&lt;h2 id="meine-konfiguration-für-die-hobbyline-4530"&gt;Meine Konfiguration für die Hobbyline 4530&lt;/h2&gt;&#10;&lt;p&gt;Ich habe im Gravurbetrieb viel mit den verschiedenen LAF-Parametern herumgespielt und eine Menge Rohlinge als Lehrgeld &amp;ldquo;verbraucht&amp;rdquo;. Die kürzesten Fertigunszeiten erhielt ich bei vorheriger Optimierung der kinematischen Parameter mit folgender Konfiguration:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;code&gt;G64 Q0.3 R12 D0.3 S120&lt;/code&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Damit sage ich der Maschine folgendes:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Verwende den Pfadvorschau-Algorithmus und versuche, kurze Liniensegmente unter &lt;code&gt;0.3mm&lt;/code&gt; Länge zu verschmelzen. Versuche, den Vorschub bei einem Winkel zweier Streckenabschnitte von bis zu &lt;code&gt;12°&lt;/code&gt; nicht herunterzuregeln. Bei größeren Winkeln bis &lt;code&gt;120°&lt;/code&gt; kann statt einem exakten Nachfahren mit bis zu &lt;code&gt;r=0.3mm&lt;/code&gt; verrundet werden. Bei noch größeren Winkeln ist dem vorgegebenen Pfad zu folgen und, falls erforderlich, bis zum Stillstand abzubremsen.&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="fazit"&gt;Fazit&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-05-04-duplicated-software.jpg" alt="Image: dual install of CNC control software for different purposes" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Da mich die Parameteroptimierung für den Gravurbetrieb ziemlich weit vom Parametersatz für den normalen Fräsbetrieb weggebracht haben, habe ich mein Steuerprogramm einfach ein zweites Mal installiert.&lt;/p&gt;&#10;&lt;p&gt;Dort konnte ich dann Geschwindigkeit, Achsbeschleunigung und Pfadparametrierung getrennt festlegen. Nun habe ich das Beste beider Welten auf einer Maschine zur Verfügung:&lt;/p&gt;&#10;&lt;p&gt;Einen sehr genauen, weich operierenden Fräsbetrieb mit folgenden Parametern:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;Fmax XY 12000mm/min&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Fmax Z 4800mm/min&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Amax X 1000mm/s²&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Amax Y 1000mm/s²&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Amax Z 1000mm/s²&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;G64 P0.1 R6&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Und über die andere Verknüpfung einen überaus flinken Gravurbetrieb:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;Fmax XY 12000mm/min&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Fmax Z 4800mm/min&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Amax X 4000mm/s²&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Amax Y 3200mm/s²&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Amax Z 1600mm/s²&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;G64 Q0.3 R12 D0.3 S120&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="und-wo-ist-der-beweis"&gt;Und wo ist der Beweis?&lt;/h2&gt;&#10;&lt;p&gt;Das eingebettete Video zeigt dasselbe Gravurprojekt mit vier verschiedenen Optimierungsstufen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;oben links: Herstellerkonfiguration der Maschine, Standard-Fräspfade aus CAD/CAM&lt;/li&gt;&#10;&lt;li&gt;oben rechts: Geschwindigkeit und Beschleunigung auf Fräsbetrieb erhöht&lt;/li&gt;&#10;&lt;li&gt;unten links: Look Ahead feed eingeschaltet &lt;code&gt;G64 P0.3&lt;/code&gt;, Fräspfade im CAM optimiert&lt;/li&gt;&#10;&lt;li&gt;unten rechts: Optimiert für Gravurbetrieb wie oben beschrieben, CAM Flyover auf &lt;code&gt;2mm reduziert&lt;/code&gt; und Leerfahrten minimiert&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/2EWe1ZvVW9TKY6k3XtpmWF" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;CNC-Tuning für den Gravurbetrieb&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>CNC Tuning #2</title><link>https://blog.schallbert.de/cnc-tuning-kinematics/</link><pubDate>Sat, 27 Apr 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-tuning-kinematics/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-27-kinematics-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Thumbnail of a CNC machine&amp;#39;&amp;#39;s portal X-Axis"&#10; title="CNC Tuning #2" /&gt;&#10;&lt;p&gt;Dies ist der zweite Teil meiner Reihe über die Optimierung der Fertigungszeit auf CNC-Fräsmaschinen. Er konzentriert sich auf die kinematischen Maschinenparameter für den Gravurbetrieb.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-tuning-gcode/"&gt;&lt;em&gt;Teil 1&lt;/em&gt;: Besserer G-Code&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-tuning-pathinterpreter/"&gt;&lt;em&gt;Teil 3:&lt;/em&gt; Pfadinterpreter optimieren&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="schritt-2-tuning-der-kinematischen-parameter-der-cnc"&gt;Schritt 2: Tuning der kinematischen Parameter der CNC&lt;/h2&gt;&#10;&lt;p&gt;Die theoretischen Grundlagen habe ich im &lt;a href="https://blog.schallbert.de/portal-milling-setup/"&gt;Artikel zur CNC-Steuerungskonfiguration&lt;/a&gt; ausführlich erläutert. Meine Probleme damit und zugehörige Lösungen habe ich nach vielen Tests und Rücksprache mit dem Hersteller im &lt;a href="https://blog.schallbert.de/projects/one-year-zerspanobert/#velocityerror"&gt;Fazit zu einem Jahr CNC-Betrieb&lt;/a&gt; festgehalten. Die folgenden Abschnitte wenden die obigen Erkenntnisse auf eine Demonstrationsmaschine an, die ich mir habe leihen können.&lt;/p&gt;&#10;&lt;h3 id="verfahrgeschwindigkeit-im-eilgang"&gt;Verfahrgeschwindigkeit im Eilgang&lt;/h3&gt;&#10;&lt;p&gt;Für die Demomaschine konnte ich die Geschwindigkeit im Eilgang von den vorgeschlagenen &lt;code&gt;Fxy = 4m/min&lt;/code&gt; und &lt;code&gt;Fz = 2.4m/min&lt;/code&gt; auf &lt;code&gt;Fxy = 12m/min&lt;/code&gt; und &lt;code&gt;Fz = 4.8m/min&lt;/code&gt; erhöhen. Setze ich alle drei Achsen gleichzeitig in Bewegung, erhalte ich durch Steigung der Kugelgewindespindeln und Mikroschrittkonfiguration (8 pro Vollschritt) der Motoren eine kombinierte Schrittfrequenz von &lt;code&gt;96kHz&lt;/code&gt;, was unter der maximalen Schrittfrequenz von &lt;code&gt;125kHz&lt;/code&gt; der Steuerung liegt.&lt;/p&gt;&#10;&lt;h3 id="beschleunigung-im-eilgang"&gt;Beschleunigung im Eilgang&lt;/h3&gt;&#10;&lt;p&gt;Die Herstellerangaben empfehlen hier &lt;code&gt;aXY = 300mm/s²&lt;/code&gt; und &lt;code&gt;aZ = 200mm/s²&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Ich habe die Beschleunigung in mehreren Schritten hochgesetzt und bin in einer ersten Runde bei &lt;code&gt;aXY = 1000mm/s²&lt;/code&gt; und &lt;code&gt;aZ = 400mm/s²&lt;/code&gt; gelandet. Diese Werte sind für diese kleine und leichte Maschine mit nur &lt;code&gt;30x45x14cm&lt;/code&gt; Verfahrweg nach meiner Erfahrung für den Normalbetrieb ohne Weiteres möglich. Sie erhöhen die Fertigungsgeschwindigkeit stark, denn jeder Richtungswechsel, auch die im Materialeingriff bei &lt;code&gt;G01, G02, G03&lt;/code&gt;, werden nun um Faktor &lt;code&gt;3&lt;/code&gt; bzw. &lt;code&gt;2.5&lt;/code&gt; schneller ausgeführt.&lt;/p&gt;&#10;&lt;p&gt;So habe ich die Maschine oft betrieben und auch auf der Messe bin ich mit diesen Werten stabil und sicher gefahren. Das folgende Video zeigt die Fräse im Gravurbetrieb mit den angegebenen Werten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/vvmwT8e6vWnj5zbuqMdUfN" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;CNC-Tuning: Vorschau&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Danach jedoch wollte ich wissen, wie weit ich es treiben kann. Getreu dem vorher beschriebenen Motto&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Wer die Grenze nicht überschreitet, weiß nicht, wo sie liegt&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;habe ich erst einmal geprüft, wie sich massive Schrittverluste bei dieser Maschine anhören. Also habe ich eine Achse mit einem Holzbalken blockiert und bin vorsichtig dagegen gefahren:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2024-04-27-soundoflosingsteps.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Massive step loss soundcheck&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das deutlich vernehmbare Rattern entsteht wenn der Motorhochlauf zwar klappt, in Richtung Zielgeschwindigkeit aber diverse Vollschritte verloren gehen. Wird der Rotor bereits bei Anlauf des Motors &amp;ldquo;verloren&amp;rdquo;, so fiept der Motor nur noch, ohne die Achse überhaupt in Bewegung zu setzen. Verliert der Motor nur wenige Schritte, befindet sich aber auf Zielgeschwindigkeit, so gibt es einen rauen und unter Umständen kratzenden Ton.&lt;/p&gt;&#10;&lt;p&gt;Ich habe für das Experiment die X-Achse gewählt, weil sie sich am einfachsten blockieren lässt. Des Weiteren habe ich sie in &lt;code&gt;X+&lt;/code&gt;-Richtung blockiert. Begründung: diese Maschine besitzt kein professionelles Festlager zur Aufnahme von Druck- und Zugkräften der Kugelumlaufspindel. Stattdessen werden Druckkräfte in Richtung Schrittmotor sicher von einem auf der Spindel montierten Flansch abgefangen, der gegen in der Portalwange befindliche Kugellager drückt.&lt;/p&gt;&#10;&lt;p&gt;Zugkräfte hingegen werden hauptsächlich von den Motorlagern aufgenommen - da wollte ich kein Risiko eingehen.&#10;Nun ist also das Geräusch bei plötzlichem und ruckartigen Schrittverlust bekannt - das Übertakten kann beginnen!&lt;/p&gt;&#10;&lt;p&gt;Meine Vorgehensweise:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Die Beschleunigung einer Achse der Maschine verdoppeln&lt;/li&gt;&#10;&lt;li&gt;Die betreffende Achse umher joggen, dabei auf merkwürdige Geräusche achten und die Achsbewegung beobachten&lt;/li&gt;&#10;&lt;li&gt;Vorige Schritte wiederholen, bis massive Schrittverluste auftreten&lt;/li&gt;&#10;&lt;li&gt;Beschleunigung auf den Mittelwert zwischen &amp;ldquo;klappt&amp;rdquo; und &amp;ldquo;klappt nicht&amp;rdquo; zurückfahren&lt;/li&gt;&#10;&lt;li&gt;Den vorigen Schritt solange wiederholen, bis die Maschine sauber fährt&lt;/li&gt;&#10;&lt;li&gt;Alle Schritte auch für die anderen Achsen durchführen&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;So bin ich schließlich bei für eine solche Hobbymaschine unglaublichen &lt;code&gt;aXY = 4000mm/s²&lt;/code&gt;, &lt;code&gt;aZ = 1600mm/s²&lt;/code&gt; gelandet. Dies liegt nun eine Größenordnung über den empfohlenen Werten.&lt;/p&gt;&#10;&lt;p&gt;Die kleine Fräse scheint auch hier stabil zu laufen und die gefertigten Gravuren sehen gut aus. Allerdings konnte ich mir nicht vorstellen, dass ich Schrittmotoren, Treiberstufen und Netzteil noch innerhalb ihrer Spezifikation betreibe. Daher habe ich gemessen.&lt;/p&gt;&#10;&lt;h3 id="elektrische-messwerte"&gt;Elektrische Messwerte&lt;/h3&gt;&#10;&lt;p&gt;Das Netzteil ist mit &lt;code&gt;3.7A&lt;/code&gt; bei &lt;code&gt;36V&lt;/code&gt; angegeben. Ich darf diesen Wert also in der Summe weder im Eilgang &lt;code&gt;G00&lt;/code&gt; noch im Materialeingriff dauerhaft überschreiten. Wie oben erklärt, ist der Eilgang im Gravurbetrieb durch die geringeren Fräskräfte maßgeblich, sodass ich besonderes Augenmerk auf das Verfahren mehrerer Achsen gleichzeitig legen muss.&lt;/p&gt;&#10;&lt;p&gt;Die Motoren können je einen Dauerstrom von &lt;code&gt;3.0A&lt;/code&gt; verkraften, limitieren mir hier also nichts. In den elektrischen Tests schaue ich lediglich, ob ich mit der Energieaufnahme des Systems im Rahmen bleibe.&lt;/p&gt;&#10;&lt;h4 id="messaufbau"&gt;Messaufbau&lt;/h4&gt;&#10;&lt;p&gt;Ich schließe das aus &lt;a href="https://blog.schallbert.de/measure-inrush-current/"&gt;diesem Artikel bekannte&lt;/a&gt; Multimeter direkt am Ausgang des Netzteils an und nehme Spannung und Strom auf. Für das Experiment simuliere ich die Fertigung der oben im Video verlinkten Gravur und führe in zwei Durchläufen zuerst eine zeitlich grob aufgelöste Leistungsfaktoranalyse (&lt;code&gt;2Hz&lt;/code&gt;) durch, gefolgt von einer fein aufgelösten Stromermittlung mit &lt;code&gt;20kHz&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h4 id="ergebnisse"&gt;Ergebnisse&lt;/h4&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-27-powerdiagram-engrave.jpg" alt="Image: Power diagram for tuned engraving" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Im obigen Bild sind Spannung, Strom, Wirk- und Blindleistung aufgetragen. Zu erkennen ist, dass die Blindleistung (&lt;code&gt;grün&lt;/code&gt;) immer dann kurze Spitzen zeigt, wenn mehrere Achsen gleichzeitig in den Eilgang beschleunigen. Die Leistungsaufnahme einer Achse im Eingriff &lt;code&gt;G01/G02/G03, blau&lt;/code&gt; beträgt im Schnitt etwa &lt;code&gt;45W&lt;/code&gt; und ist ähnlich hoch wie bei der Konstantfahrt im Eilgang.&lt;/p&gt;&#10;&lt;p&gt;Das Netzteil ist in der Lage, die Spannung (&lt;code&gt;orange&lt;/code&gt;) ziemlich konstant zu halten und knickt selbst unter Volllast nur um knapp &lt;code&gt;0.2V&lt;/code&gt; ein. Einzig der Stromwert sah mir etwas zu glatt aus. Ich vermutete an dieser Stelle, dass die zeitliche Auflösung zu gering ist und beschloss, das Experiment mit Konzentration auf den Strom zu wiederholen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-27-currentdiagram-engrave.jpg" alt="Image: Current diagram for tuned engraving" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Und tatsächlich. Die höhere zeitliche Auflösung zeigt, dass die Stromaufnahme bei Beschleunigung mehrerer Achsen Spitzen zeigt, die über den bereits bekannten Werten liegt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-27-currentpeak-engrave.jpg" alt="Image: Zoomed-in current draw for axis acceleration" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Hier stechen Bewegungen auf der Y-Achse, welche das gesamte Portal in Bewegung setzen muss, besonders heraus. Und zwar genau dann, wenn die anderen Achsen noch in Bewegung sind oder gerade gehalten haben. Dies hängt mit den Schrittmotortreibern zusammen. Sie betreiben die Motoren nämlich noch etwa &lt;code&gt;0.5s&lt;/code&gt; nach Anhalten bei vollem Strom weiter. Die dadurch entstehende Überlagerung lässt die Spitzen noch dramatischer aussehen.&lt;/p&gt;&#10;&lt;h4 id="benchmark-spitzenlast"&gt;Benchmark Spitzenlast&lt;/h4&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-27-loadtest.jpg" alt="Image: Zoomed-in current draw for axis acceleration" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Na dann treiben wir es mal auf die Spitze mit einem Beschleunigungs-Benchmark. Wird das Netzteil auch bei voller Last unter Beschleunigung aller Motoren gleichzeitig die Ausgangsspannung halten können?&lt;/p&gt;&#10;&lt;p&gt;Um dies zu testen, habe ich einen G-Code geschrieben, der alle Achsen gleichzeitig auf Höchstgeschwindigkeit beschleunigen und dann wieder abbremsen lässt. Anschließend wird unter gleichen Bedingungen zum Ausgangspunkt zurückgefahren und das Ganze wiederholt.&lt;/p&gt;&#10;&lt;p&gt;Auf diese Weise kann ich herausfinden, ob das Netzteil genügend Reserven hat, um Lastspitzen abzufangen und gleichzeitig schauen, ob die Motoren bereits im größeren Maße Schritte verlieren. Im Mittel liegt hier die aufgebrachte Leistung bei &lt;code&gt;95W&lt;/code&gt; und die Versorgungsspannung sinkt um &lt;code&gt;0.28V&lt;/code&gt; auf &lt;code&gt;35.72V&lt;/code&gt; ab. Dabei muss das Netzteil in der Spitze &lt;code&gt;5.14A&lt;/code&gt; abgeben, was knapp &lt;code&gt;40%&lt;/code&gt; über Nennstrom liegt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2024-04-20-benchmark.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Benchmark&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Hört man ganz genau hin, so klingt die Maschine direkt zu Beginn des Benchmarks etwas rau, fängt sich dann aber. So können auch Schrittverluste klingen. Es ist daher sehr wichtig, die Maschine mit allen Sinnen zu überwachen.&lt;/p&gt;&#10;&lt;h3 id="mechanische-prüfung"&gt;Mechanische Prüfung&lt;/h3&gt;&#10;&lt;p&gt;Nun zur letzten Prüfung: Ich gebe beim Eilgang-Benchmark zusätzlich Last auf die Achsen, indem ich während des Programmablaufes mit meinem Arm an der jeweiligen Achse ziehe. Die Achsen Z und X laufen stabil weiter. Bei der Portalachse &lt;code&gt;Y&lt;/code&gt; merke ich jedoch, dass die Maschine nun anders klingt. Irgendwie rau, als würde man mit mit einer Raspel leicht über eine Holzplatte ziehen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-27-steplossy.jpg" alt="Image: Y-axis reveals massive step loss" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Lege ich eine große Last an, so fährt der Schrittmotor auf dieser Achse nicht mal mehr an, sondern fiept nur noch. Er kann den Pulsen der Endstufe nicht mehr folgen. Ich reduziere nun also die Beschleunigung schrittweise, bis der Motor die Portalachse auch unter mittlerer Last wiederholbar und ohne hörbare Schrittverluste beschleunigen kann. Hier lande ich am Ende bei immer noch flinken &lt;code&gt;[aX = 4000mm/s² aY = 3200mm/s² aZ = 1600mm/s²]&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;In einem letzten Schritt führe ich nach fünfmaliger Wiederholung des Benchmarks eine weitere Referenzfahrt durch und schaue mir die Logs der Steuersoftware an. Alle Achsen sind nun unauffällig hinsichtlich Schrittverlusten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-27-steplossxz.jpg" alt="Image: XZ-axis without step loss" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="appelle-zum-cnc-tuning"&gt;Appelle zum CNC-Tuning&lt;/h3&gt;&#10;&lt;p&gt;Bitte geht beim Tuning eurer Maschine behutsam vor. Lernt sie richtig kennen, bevor ihr damit beginnt. Hört genau hin und optimiert Achse für Achse nacheinander. Nehmt euch Zeit für Tests, Prüfungen und Experimente.&lt;/p&gt;&#10;&lt;p&gt;Selbst wenn ihr eine sehr ähnliche Maschine wie meine besitzt, machen andere Abmessungen bereits einen riesigen Unterschied hinsichtlich Steifigkeit und damit auch möglicher Verfahrgeschwindigkeit bzw. Beschleunigung. Sogar Wartungszustand, leichtgängigere Wellen, andere Führungen o.Ä. können dazu führen, dass ihr am Ende mit anderen Werten fahrt als ich.&lt;/p&gt;&#10;&lt;p&gt;Es wird kein Profi-Equipment benötigt, um die Grenzen der Maschine auszuloten. Nicht einmal eine Messuhr, da Schrittverluste auch mit Hilfe der Referenzschalter hinreichend genau bestimmt werden können.&lt;/p&gt;&#10;&lt;p&gt;Alles was ich durch die elektrischen Messungen oben festgestellt habe, ist, dass der Hersteller bei der Auslegung der Maschine sauber gearbeitet hat und keine Komponente unterdimensioniert ist. Im Gegenteil, diese Maschine ist für die Übertaktung bestens geeignet.&lt;/p&gt;&#10;&lt;p&gt;Und nun weiter zum letzten &lt;a href="https://blog.schallbert.de/cnc-tuning-pathinterpreter/"&gt;&lt;em&gt;Teil 3:&lt;/em&gt; Pfadinterpreter optimieren&lt;/a&gt;.&lt;/p&gt;&#10;</description></item><item><title>CNC Tuning #1</title><link>https://blog.schallbert.de/cnc-tuning-gcode/</link><pubDate>Sat, 20 Apr 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-tuning-gcode/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-20-camtuned-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Thumbnail of CAM simulation for G-code optimizing"&#10; title="CNC Tuning #1" /&gt;&#10;&lt;p&gt;Dies ist der erste Teil meiner Reihe über die Optimierung der Fertigungszeit auf CNC-Fräsmaschinen. Er konzentriert sich auf den Einsatz von CAD- und CAM-Werkzeugen zur Erzeugung effizienteren G-Codes.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-tuning-kinematics/"&gt;&lt;em&gt;Teil 2&lt;/em&gt;: Kinematische Parameter hochdrehen&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-tuning-pathinterpreter/"&gt;&lt;em&gt;Teil 3:&lt;/em&gt; Pfadinterpreter optimieren&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Es gibt diverse Maßnahmen zur Reduktion der Fertigungszeit mit einer CNC-Maschine. Erstmal jedoch sollte beleuchtet werden, warum so etwas gewünscht ist und welche möglichen Nachteile sich hierdurch ergeben.&lt;/p&gt;&#10;&lt;h2 id="warum-überhaupt"&gt;Warum überhaupt?&lt;/h2&gt;&#10;&lt;p&gt;Aus kaufmännischer Sicht sorgen geringere Fertigungszeiten für einen höheren Teiledurchsatz und ermöglichen damit einen höheren Umsatz in einer gegebenen Zeitspanne. Zudem ist die Investition der Maschine schneller amortisiert.&lt;/p&gt;&#10;&lt;p&gt;Als Hobbyist sind kürzere Fertigungszeiten vor allem dann interessant, wenn das Ziel die Herstellung sehr komplexer Geometrien oder die 3D-Fertigung ist. Hier können Fräszeiten durchaus mehrere Stunden betragen und die möglichen Einsparungen sind folglich besonders groß.&lt;/p&gt;&#10;&lt;p&gt;Bei mir steht wie auch in der Lehre der wissenschaftliche Gedanke im Vordergrund: Ich will herauszufinden, wo die Grenze der Maschine liegt. Ich möchte wissen, warum sie dort liegt. Ich werde versuchen, sie stabil innerhalb ihrer Grenzen zu betreiben. Dabei ist der Verschleiß unter Kontrolle zu halten.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Um diese Grenze jedoch genau zu kennen, muss sie zuerst übertreten werden. Woher sonst soll ich wissen, wo sie sich befindet?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h3 id="was-sind-mögliche-nachteile"&gt;Was sind mögliche Nachteile?&lt;/h3&gt;&#10;&lt;p&gt;Bestimmte Komponenten einer &amp;ldquo;übertakteten&amp;rdquo; Maschine könnten schneller als üblich verschleißen. Dazu zählen etwa Netzteile und Motor-Endstufen, die höhere transiente Ströme verkraften müssen. Lager und Führungen werden durch höhere Beschleunigungen stärker beansprucht. Umlaufspindeln und Maschinenrahmen müssen die dadurch resultierenden, stärkeren und schneller wechselnden Kräfte und Vibrationen aufnehmen und können früher Ermüdungserscheinungen oder sich lösende Verbindungen aufweisen.&lt;/p&gt;&#10;&lt;p&gt;Bei der Ermittlung der Grenzen kann die Maschine im Extremfall sogar beschädigt werden: Mit hoher Wahrscheinlichkeit treten jenseits der Grenze Schrittverluste&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; auf. Das ist zwar im Testbetrieb nicht weiter problematisch. Doch stimmt die angenommene Position dann nicht mehr mit der tatsächlichen Position überein und es besteht das Risiko des &amp;ldquo;Crashes&amp;rdquo; - wenn nicht mit Bedacht vorgegangen wird. Hier noch ein ziemlich gutes &lt;a href="https://www.youtube.com/watch?v=C_9dM1wx0Sg" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Youtube-Video von &lt;em&gt;gammaflow&lt;/em&gt; zum Thema Schrittverluste vermeiden&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Werden die Fräsbahnen im CAD oder CAM zugunsten kürzerer Fertigungszeiten verändert, so kann dies außerdem negative Auswirkungen auf Schnitt-, Kanten- und Oberflächenqualität haben.&lt;/p&gt;&#10;&lt;p&gt;Bei der Optimierung des Pfadinterpreters der Steuersoftware ist ebenfalls ein Kompromiss einzugehen: Höhere Fertigungsgeschwindigkeit wird hier mit geringerer Genauigkeit und/oder höherem Maschinenverschleiß eingekauft.&lt;/p&gt;&#10;&lt;h3 id="welche-maßnahmen-werden-nicht-diskutiert"&gt;Welche Maßnahmen werden nicht diskutiert?&lt;/h3&gt;&#10;&lt;p&gt;Nicht betrachten werde ich hier die Optimierung von Vorschub &lt;code&gt;F&lt;/code&gt; und Drehzahl &lt;code&gt;S&lt;/code&gt; des gewählten Fräsers im Eingriff. Diese Parameter hängen vom zu bearbeitendem Material, der Fräsergeometrie, den Fähigkeiten der Maschine und dem Mut des Bedienpersonals ab, sodass ich keine allgemein gültigen Tunings vorschlagen kann.&lt;/p&gt;&#10;&lt;p&gt;Einen anderen, großen Einflussfaktor muss ich hier ebenfalls ausklammern: Die Bearbeitungsstrategie des CAM-Programms bezogen auf Eilgänge. Mein CAM-Programm lässt mich da nicht besonders tief eingreifen, sodass ich für die Optimierung der letzten paar Prozent Fertigungszeit stets von Hand im G-Code arbeiten muss, was entsprechend fehleranfällig ist.&lt;/p&gt;&#10;&lt;h2 id="tuning-in-drei-schritten"&gt;Tuning in drei Schritten&lt;/h2&gt;&#10;&lt;p&gt;Bei meiner Optimierung bin ich in drei Schritten vorgegangen, die allerdings unabhängig voneinander umgesetzt werden können. Jeder Schritt hat mir hierbei deutlich spürbare Verbesserungen gebracht. Dieser Artikel konzentriert sich aus mehreren Gründen auf den Gravurbetrieb:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Gravuren erlauben meist größere Toleranzen als andere Betriebsarten. Dadurch kann stärker verrundet werden, was zu geringeren Beschleunigungen führt. Dies ermöglicht im Mittel größere Vorschübe&lt;/li&gt;&#10;&lt;li&gt;Die zu erwartenden Fräskräfte sind geringer: Keine Durchfräsungen, kaum Energieeinsatz bei der Spanverdrängung, kleineres Spanvolumen als bei Schaftfräsern&lt;/li&gt;&#10;&lt;li&gt;Dadurch kann die Maschine näher an ihren mechanischen und elektrischen Grenzen betrieben werden&lt;/li&gt;&#10;&lt;li&gt;Durch im Schnitt größere G-Code Dateien mehr Optimierungspotential&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="schritt-1-besserer-g-code"&gt;Schritt 1: Besserer G-Code&lt;/h2&gt;&#10;&lt;p&gt;Dieser Abschnitt folgt dem Credo &lt;code&gt;Garbage in, Garbage out&lt;/code&gt;. Will heißen: Wenn mein G-Code schon schlecht ist, kann die Maschine niemals optimale Fertigungszeiten erreichen. Was zeichnet also &amp;ldquo;schlechten&amp;rdquo; G-Code in Bezug auf Fertigungszeit aus?&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;übermäßig komplex und lang, möglicherweise Dateigröße im Megabyte-Bereich&lt;/li&gt;&#10;&lt;li&gt;extrem viele Stützpunkte und eine Menge kurzer Liniensegmente&lt;/li&gt;&#10;&lt;li&gt;Einen Haufen &amp;ldquo;Leerfahrten&amp;rdquo; im Eilgang auf XY mit hochgezogener Z-Achse&lt;/li&gt;&#10;&lt;li&gt;Viele Rückzüge der Z-Achse auf Sicherheitshöhe&lt;/li&gt;&#10;&lt;li&gt;Häufige Wartezeiten der Maschine (Spindelhochlauf, Kühlmittelpumpe, Controller-Puffer voll)&lt;/li&gt;&#10;&lt;li&gt;Übermäßiges Feinstschlichten auch bei B-Oberflächen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="aber-wie-soll-ich-meinen-g-code-vereinfachen"&gt;Aber wie soll ich meinen G-Code vereinfachen?&lt;/h3&gt;&#10;&lt;p&gt;Kurze Frage, lange Antwort: Es fängt im CAD an und hört möglicherweise im CAM nicht auf.&lt;/p&gt;&#10;&lt;h3 id="beispiel-cad"&gt;Beispiel CAD&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-20-bline.jpg" alt="Image: Letter &amp;#39;B&amp;#39; in CAM, drawn as a set of line segments" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Nehmen wir an, wir möchten eine Schrift fräsen. Unser CAD-Programm hat auch einen Haufen toller Schriftarten dazu. Wir erzeugen nun eine DXF-Datei für&amp;rsquo;s CAM. Im CAM dann sehen wir plötzlich beim Hineinzoomen, dass alle Buchstaben aus sehr kurzen Liniensegmenten bestehen statt aus Kreissegmenten oder &lt;a href="https://en.wikipedia.org/wiki/Spline_%28mathematics%29" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Splines&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Die Folge: Das CAM wird &lt;code&gt;G01&lt;/code&gt;-Bewegungen, also &amp;ldquo;Linear Moves&amp;rdquo; bzw. eine Aneinanderreihung von Strecken erzeugen.&lt;/p&gt;&#10;&lt;p&gt;Die verbundenen Strecken machen sehr viele Richtungswechsel der Maschine erforderlich. Folglich muss sie vor dem Verbindungspunkt ständig abbremsen und danach wieder beschleunigen. Die Maschine kann also nicht mit konstanter Geschwindigkeit fahren, denn das würde eine unendliche Beschleunigung am Knickpunkt erfordern.&lt;/p&gt;&#10;&lt;p&gt;Für dieses Problem gibt es Lösungen, die im Idealfall gemeinsam angewendet werden:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Im CAD-Programm einstellen, dass wann immer möglich Splines oder Kurven statt Liniensegmenten erstellt werden sollen&lt;/li&gt;&#10;&lt;li&gt;Vereinfachung der Zeichnung durch Reduktion der Stützstellen&lt;/li&gt;&#10;&lt;li&gt;Vermehrte Nutzung von Radien und Splines an Kanten, Vermeidung von Winkeln&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In meinem Falle war die erste Option nicht verfügbar, sodass ich nun ein anderes Programm für die Gravur von Schriften verwende als für meine üblichen Zeichnungen. Dadurch kann ich Schrift jetzt in Linien- und Bogensegmenten ausdrücken. Die dafür erforderliche Fräszeit halbiert sich und gleichzeitig kommt der G-Code mit weniger Zeilen aus. Außerdem wird die Schrift runder und hält sich sogar noch genauer an die anfangs gewählte Schriftart.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-20-bsplinevsline.jpg" alt="Image: Letter &amp;#39;B&amp;#39; in CAM, twice. Once per spline, once as line segments" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Gute Beispiele für die Vereinfachung von Pfaden durch Reduktion von Stützpunkten liefern Gimp und Inkscape - für Inkscape habe ich das sogar bereits meinem &lt;a href="https://blog.schallbert.de/path-from-image/#inkscape"&gt;Artikel zum Thema Bild-zu-Pfad-Konversion&lt;/a&gt; gezeigt. Mit ein paar wenigen Klicks erhält man auf Kosten der Genauigkeit Zeichnungsdaten, die teils deutlich schneller zu fertigen sind.&lt;/p&gt;&#10;&lt;p&gt;Da ein Schaftfräser sowieso keine Tasche mit &lt;code&gt;r=0&lt;/code&gt; in den Ecken fertigen kann, habe ich mir angewöhnt, sämtliche Ecken und Kanten im CAD abzurunden. Nicht nur liegen so hergestellte Teile besser in der Hand, sie sind außerdem schneller zu fertigen - die Zeitersparnis summiert sich, besonders auf langsamen und trägen Maschinen.&lt;/p&gt;&#10;&lt;h3 id="beispiel-cam"&gt;Beispiel CAM&lt;/h3&gt;&#10;&lt;p&gt;Im CAM kann ich durch vermehrte Verwendung von Anfahrpunkten und Rampen ein wenig Zeit sparen. Ich lege sie zumeist an das kürzeste Liniensegment und spare so einen Brems/Beschleunigungsvorgang. Bei der Eintauchart ist der Vorteil von &amp;ldquo;Rampe&amp;rdquo; gegenüber &amp;ldquo;Helix&amp;rdquo;, dass sämtliche Wege des Fräsers Nutzwege sind und ich ohne zusätzliche Achsbeschleunigungen durchfahren kann. Je nach Rampensteigung allerdings verlängert sich der Fräspfad, da Überlappungen entstehen.&lt;/p&gt;&#10;&lt;p&gt;Einen viel größeren Effekt auf die Fertigungszeit hat die kluge Auswahl von Räumzustellungen und -strategien für Taschen passend zu Material, Fräser und gewünschter Oberflächengüte. So erzeugt Wirbelfräsen (Trochoidalfräsen&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;) in vielen Fällen längere Dateien und kostet auf der Maschine mehr Zeit als der klassische &amp;ldquo;Rasenmäher&amp;rdquo;, welcher die Tasche bloß abzeilt. Für die Räumzustellung wähle ich bei Materialien wie Holz und Acrylglas gern große Werte über &lt;code&gt;60%&lt;/code&gt; - es sei denn, der Boden der Tasche muss besonders hohen Qualitätsansprüchen genügen.&lt;/p&gt;&#10;&lt;p&gt;Für Gravuren ist es meiner Meinung nach oft am Schnellsten, auf einen Räumgang mit einem zweiten Fräser zu verzichten und wenn möglich einen Gravurfräser mit abgeflachter Spitze - ähnlich derer für meine &lt;a href="https://blog.schallbert.de/cut-dibond-tests/"&gt;Tests mit Dibond&lt;/a&gt; - zu verwenden. Dieser benötigt zum Räumen zwar länger; das kann ich durch gesteigerte Gravurtiefe und eine recht breite Abflachung von &lt;code&gt;1.5mm&lt;/code&gt; aber kompensieren. Ich verwende bei einem solchen Fräser eine Räumzustellung in XY von &lt;code&gt;1mm&lt;/code&gt;. Übrig bleibt schließlich der Zeitvorteil durch weniger Werkzeugwechsel.&lt;/p&gt;&#10;&lt;h3 id="aber-wie-soll-ich-die-leerfahrten-reduzieren"&gt;Aber wie soll ich die Leerfahrten reduzieren?&lt;/h3&gt;&#10;&lt;p&gt;Endlich mal ein paar knackig-kurze Antworten:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Geringere Rückzugshöhe in &lt;code&gt;Z&lt;/code&gt; (ich verwende &lt;code&gt;5mm&lt;/code&gt; aber &lt;code&gt;2mm&lt;/code&gt; würde bei mir wahrscheinlich auch funktionieren)&lt;/li&gt;&#10;&lt;li&gt;Optimierung der Frässtrategie im CAM auf &amp;ldquo;minimale Wege&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Bei Nesting: Darauf achten, dass die Geometrie möglichst nicht Zeile für Zeile, sondern Teil für Teil gefertigt wird.&lt;/li&gt;&#10;&lt;li&gt;Im Zweifel Optimierung der Strategie im G-Code von Hand. Lohnt sich dann, wenn das CAM-Programm nicht gut genug arbeiten kann und viele Gleichteile zu fertigen sind&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="und-wo-ist-das-problem-mit-rückzügen-der-z-achse"&gt;Und wo ist das Problem mit Rückzügen der Z-Achse?&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Rückzüge finden vor allem im Gravurbetrieb sehr häufig statt&lt;/li&gt;&#10;&lt;li&gt;der Zeitverlust summiert sich&lt;/li&gt;&#10;&lt;li&gt;Beim Wiedereingriff wird meist nicht im Eilgang sondern mit für den Fräser vorgesehener Geschwindigkeit mit &lt;code&gt;G01&lt;/code&gt; auf das Material gefahren&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Eine Verbesserung kann hier eine gute Planung der Fräsreihenfolge im CAM bringen. Möglichst viele Sektionen sollten verbunden und ohne Absetzen des Fräsers gefertigt werden. Im CAM sollte für Rückzug und Wiedereinfahrt in &lt;code&gt;Z&lt;/code&gt; der Eilgang verwendet werden.&lt;/p&gt;&#10;&lt;h3 id="wie-reduziere-ich-wartezeiten"&gt;Wie reduziere ich Wartezeiten?&lt;/h3&gt;&#10;&lt;p&gt;Mit Kühlschmierstoffschaltvorgängen, Wechsel der Spindeldrehzahl oder des Werkzeugs sind Wartezeiten verbunden, die meist leicht zu reduzieren sind:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Kühlschmierstoffförderung durchlaufen lassen während des Programmablaufes&lt;/li&gt;&#10;&lt;li&gt;die Verwendung möglichst weniger Werkzeuge bei gleichbleibender Drehzahl für ein Produkt&lt;/li&gt;&#10;&lt;li&gt;Anschläge verwenden, um die Maschinenbeschickung zu vereinfachen und Nullpunkte nicht immer wieder neu setzen zu müssen&lt;/li&gt;&#10;&lt;li&gt;Wird die Maschine mit komplett verschiedenen parametern für Gravur- und Fräsbetrieb gefahren, empfiehlt sich die doppelte Installation der Steuersoftware mit jeweils passenden Parametersätzen. Dadurch entfällt das ständige Ändern der Konfiguration.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Weiter geht es in &lt;a href="https://blog.schallbert.de/cnc-tuning-kinematics/"&gt;&lt;em&gt;Teil 2&lt;/em&gt;: Kinematische Parameter hochdrehen&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Schrittverluste treten dann auf, wenn die Achse eines Schrittmotors nicht mehr synchron mit der durch die Steuerung vorgegebenen Schrittfolge dreht, sondern weniger bzw. mehr Schritte zurücklegt als vorgegeben. Fast immer ist die Ursache eines Schrittverlustes in einer unzureichenden Auslegung des Systems (z.B. zu geringe oder instabile Versorgungsspannung, zu schwache oder nicht an den Motor angepasste Endstufen) oder einer Überlastung des Schrittmotors zu finden. Selten jedoch können Schrittverluste Symptome von Defekten sein wie Lagerfraß, Windungsschluss oder durchschlagene Pufferkondensatoren.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;Das Trochoidalfräsen ist keine schlechte Praxis. Es hat andere Stärken, ist im Gravurrbetrieb aber nur selten von Nutzen.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Vakuumspannen</title><link>https://blog.schallbert.de/vacuum-clamping/</link><pubDate>Tue, 02 Apr 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/vacuum-clamping/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-cuttingforces-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Thumbnail of endmill cutting forces"&#10; title="Vakuumspannen" /&gt;&#10;&lt;p&gt;Wie ich im &lt;a href="https://blog.schallbert.de/makerfaire-ruhr/#verz%c3%b6gerungen"&gt;Artikel zur Maker Faire Ruhr 2024 erwähnte&lt;/a&gt;, hatte ich große Probleme beim Spannen meiner Werkstücke für die Fertigung von Spirographen.&lt;/p&gt;&#10;&lt;p&gt;In diesem Beitrag möchte ich genauer darauf eingehen, eine Analyse der Ursachen vornehmen und schließlich ein paar Lösungsmöglichkeiten vorstellen.&lt;/p&gt;&#10;&lt;p&gt;Was ich vorwegnehmen kann: Sämtliche Probleme lassen sich mit Wissen über die Ursachen sowie etwas Zeit-, Planungs- und Materialaufwand beheben.&lt;/p&gt;&#10;&lt;h2 id="das-problem"&gt;Das Problem&lt;/h2&gt;&#10;&lt;p&gt;Ich hatte wie üblich die Bahnplanung für die Spirographen so festgelegt, dass ich erst die inneren Bereiche (Taschen, Teile, Bohrungen) und danach die äußeren Bereiche fräsen lasse. Ganz zum Schluss sollten Ausschnitte und Fasen gefertigt werden. Des Weiteren sah ich vor, die Teile doppelseitig zu bearbeiten, damit auch die Rückseiten der Zahnräder gebrochene Kanten und weiche Übergänge aufweisen würden.&lt;/p&gt;&#10;&lt;h3 id="vakuumverlust"&gt;Vakuumverlust&lt;/h3&gt;&#10;&lt;p&gt;Bei meinem ersten Versuch bin ich so vorgegangen wie ich das im &lt;a href="https://blog.schallbert.de/projects/spirograph/"&gt;Spirographen-Projekt&lt;/a&gt; mit dem Material Acrylglas bereits erfolgreich durchführen konnte: Die Rückseite wird zuerst bearbeitet, aber nicht ganz durchgefräst. Nach dem Wenden des Werkstückes dann soll nur noch mit dem Fasenfräser ausgeschnitten und gleichzeitig die Kanten gebrochen werden.&lt;/p&gt;&#10;&lt;p&gt;Dies funktionierte für die erste Aufspannung auch hervorragend.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2024-04-02-spiro-back.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Rückseitige Bearbeitung der Spirographen.&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Bei der zweiten jedoch ging mit dem Durchstoßen der Oberfläche so viel Unterdruck verloren, dass sich das Material vom Vakuumtisch löste und seine ursprüngliche, leichte Wölbung annahm. Somit musste ich den Fräsvorgang abbrechen und mir eine andere Strategie zum Niederhalten überlegen.&lt;/p&gt;&#10;&lt;p&gt;Ich konnte mir zu diesem Zeitpunkt noch nicht richtig erklären, warum jetzt so viel mehr Druck verloren ging als bei der Fertigung in Acrylglas.&lt;/p&gt;&#10;&lt;h3 id="frischhaltefolie"&gt;Frischhaltefolie&lt;/h3&gt;&#10;&lt;p&gt;Ich probierte mit Frischhaltefolie dem Druckverlust entgegenzuwirken. Dabei stellten sich gleich mehrere Folgeschwierigkeiten: Fährt der Fräser durch die Folie, so wird sie beim Fräsen geschlossener Konturen vom Fräser erfasst und in Windeseile um selbigen gewickelt. Der Fräser schneidet nun nicht mehr, sondern reibt nur noch am Material, was sehr schnell zu Hitzespuren am Holz führt.&lt;/p&gt;&#10;&lt;p&gt;Doch selbst wenn ich die Frischhaltefolie vorab befestige: Da ich Teile ausschneide, ist beim ersten Eindringen in die auf der Unterseite bereits vorhandene Nut der &lt;em&gt;lokale Unterdruck&lt;/em&gt; am Teil bereits wieder so klein, dass es sich kaum an Ort und Stelle halten lässt.&lt;/p&gt;&#10;&lt;h3 id="verdrehung-des-werkstücks"&gt;Verdrehung des Werkstücks&lt;/h3&gt;&#10;&lt;p&gt;Für den zweiten Versuch passte ich die Strategie an: Der Schaftfräser würde das Werkstück durchfräsen, so dass ich keine zweite Aufspannung benötigte. Ich wollte die Kantenbrechung auf der Rückseite später optional über eine auf dem Tisch festgespannte Halterung realisieren, in die ich die Teile würde einlegen können.&lt;/p&gt;&#10;&lt;p&gt;Während das Ausschneiden der Teile noch problemlos funktionierte (erneut die &amp;ldquo;inneren&amp;rdquo; Teile zuerst fräsen), so bekam ich beim Anbringen der Fase bereits auf der Vorderseite große Probleme. Die Zahnräder begannen, sich zu verdrehen und die Schablone mit Hohlrad war überhaupt nicht niederzuhalten.&lt;/p&gt;&#10;&lt;h3 id="zu-wenig-zeit"&gt;Zu wenig Zeit&lt;/h3&gt;&#10;&lt;p&gt;Ich musste also schon bei der Bearbeitung der Vorderseite auf speziell für die Werkstücke angefertigte Halterungen zurückgreifen. Deren Konstruktion und Fertigung nahm Zeit in Anspruch die ich an dieser Stelle so nicht eingeplant hatte.&lt;/p&gt;&#10;&lt;p&gt;Vor der Messe konnte ich mich nicht in der Tiefe mit der Analyse beschäftigen - eine schnelle Lösung musste her.&lt;/p&gt;&#10;&lt;h2 id="die-ursachen"&gt;Die Ursachen&lt;/h2&gt;&#10;&lt;p&gt;Mit ein paar Wochen Abstand kann nun ich sagen, dass mehrere Faktoren zu den Problemen führten. So kommen eine schwierige Geometrie, ungünstige Materialeigenschaften und bestimmte Eigenheiten des Vakuumtischdesigns zusammen. Aber gehen wir dem Ganzen hier mal auf den Grund.&lt;/p&gt;&#10;&lt;h3 id="werkstück"&gt;Werkstück&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-rawmaterial.jpg" alt="Image: Veneered MDF raw material" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Das Ausgangsmaterial ist mitteldichte Faserplatte von &lt;code&gt;10mm&lt;/code&gt; Stärke. Sie ist einseitig mit &lt;code&gt;0.75mm&lt;/code&gt; starkem Olivenholzfurnier versehen. Dieses Material ist für die Bearbeitung auf einem Vakuumtisch bereits von Grund auf problematisch:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Durch Furnierung und Lagerung sind die Platten leicht gewölbt&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, wollen sich also von meinem Vakuumtisch abheben.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;MDF ist luftdurchlässig, sodass die Vakuumpumpe mit größeren Leckageströmen zu tun hat als üblicherweise (so bekomme ich bei Aufspannung der Platte nur &lt;code&gt;-300mBar&lt;/code&gt; zusammen statt der üblichen &lt;code&gt;-800mBar&lt;/code&gt; ohne Leckströme). Die geringe Materialdicke verstärkt diesen Effekt.&lt;/li&gt;&#10;&lt;li&gt;MDF ist nicht nur senkrecht zur Plattenebene luftdurchlässig, sondern auch waagerecht. Dadurch sind die Haltekräfte in der Nähe von Fräsungen stark reduziert, denn Luft kann nun auch seitlich in das Material eindringen und erhöht so zusätzlich die Leckageströme.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Das hier verlinkte Video &lt;a href="https://www.youtube.com/watch?v=osJik4XjZTY" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Youtube: Verschiebekraftmessungen von &lt;em&gt;Anton CNC&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zeigt anschaulich, wie sich verschiedene Materialien auf einem Vakuumtisch mit poröser Oberfläche verhalten. Ebenfalls sehr empfehlenswert zum Thema ist dieses Video &lt;a href="https://www.youtube.com/watch?v=jk7Clrvtlmo" target="_blank" rel="noopener noreferrer" class="external-link"&gt;YouTube: Haltekraftermittlung während des Zerspanens von &lt;em&gt;vakuumtischDE&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; um einen Eindruck zu gewinnen, unter welcher Belastung das Werkstück während des Fräsvorganges stehen kann.&lt;/p&gt;&#10;&lt;h3 id="geometrie"&gt;Geometrie&lt;/h3&gt;&#10;&lt;p&gt;Mein Design für die Spirographen lässt sich nicht besonders gut niederhalten:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Schablone mit Hohlrad besitzt dünne Wände von nur etwa &lt;code&gt;2cm&lt;/code&gt; Stärke. Am dicksten sind sie an den Seiten links und rechts, weisen in der Mitte aber eine große Aussparung auf, durch die Luft in alle Richtungen in das Material eindringen kann und so die Haltekräfte reduziert.&lt;/li&gt;&#10;&lt;li&gt;Das Zahnrad verfügt über Löcher zur Aufnahme von Stiften. Um die Löcher herum ist der Anpressdruck reduziert.&lt;/li&gt;&#10;&lt;li&gt;Die Zähne von Hohlrad und Zahnrad liegen so frei, dass sie durch Querströme der Luft im Material praktisch keinen Beitrag zur Haltekraft leisten können. Gleichzeitig bieten die Zahnköpfe aber einen großen Hebel für Verdrehung oder Verschiebung des Werkstücks: Eine ungünstige Paarung.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-spirograph-on-vacuum-table.jpg" alt="Image: Spirograph on vacuum table: hole grid overlay" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Nehme ich das als Grundlage für die Praxis, so sind nur die im Bild oben innerhalb des Kreises befindlichen Vakuumlöcher überhaupt in der Lage, nennenswerte Spannkräfte zu erzeugen. Diese sind aber wiederum nahe am Zentrum und bieten folglich nur wenig Widerstand gegen Verdrehen des Werkstücks. Hinzu kommt, dass das Zahnrad hier mehrere Bohrungen und damit zusätzliche &amp;ldquo;Lecks&amp;rdquo; aufweist.&lt;/p&gt;&#10;&lt;h3 id="fräskräfte"&gt;Fräskräfte&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-cuttingforces-z.jpg" alt="Image: Cutting forces XY" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Die durch das Werkzeug (den Fräser) in das Material eingebrachten Kräfte wirken stets in XY-Richtung, bei V-cut Fräsern und Schaftfräsern mit Spirale/Drall zusätzlich in Z-Richtung.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Kräfte durch Vorschub: Je schneller durch das Material gefahren wird, desto stärkere Kräfte in XY-Richtung wirken auf das Material in Verfahrrichtung.&lt;/li&gt;&#10;&lt;li&gt;Kräfte durch Fräserdrehung bzw. Gleich- oder Gegenlauf: Verwendet man einen Schaftfräser ohne Drall, so entstehen Kräfte orthogonal in der Ebene der Verfahrrichtung. Fahre ich also in positive X-Richtung, wird der Fräser auch in Y-Richtung eine Kraft ausüben, die durch seine Eigendrehung eingebracht wird: Im Gleichlauf möchte der Fräser dem Material ausweichen, wird also nach &lt;code&gt;Y+&lt;/code&gt; abgelenkt (wie &lt;a href="https://blog.schallbert.de/cnc-router-overload/"&gt;in diesem Artikel anschaulich gezeigt&lt;/a&gt;). Im Gegenlauf ist die Ablenkrichtung invers und der Fräser möchte nach &lt;code&gt;Y-&lt;/code&gt; ausweichen.&lt;/li&gt;&#10;&lt;li&gt;Kräfte durch Fräsergeometrie: Weist der Fräser eine Spirale auf oder ist er in der Geometrie abgeschrägt, so wirken zusätzlich Kräfte in Z-Richtung. Bei Vcut- und Downcut-Fräsern wird das Material auf das Maschinenbett gedrückt, bei Upcut-Spiralfräsern hingegen wird der Span wie gewünscht ausgeworfen. Das Werkstück erfährt aber auch eine Kraft, die es im Extremfall vom Maschinenbett abheben kann.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-cuttingforces-xy.jpg" alt="Image: Cutting forces Z" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Wird in der Vollnut gefräst (d.h. der Fräser ist statt an der Werkstückkante entlang zu fahren über seinen kompletten Durchmesser im Eingriff), erhöhen sich die Kräfte quer zur Fräsrichtung sogar noch: Im Bild fährt er am oberen Rand der Nut im Gegenlauf, was den Fräser &amp;ldquo;vom Material weg&amp;rdquo;, im Bild also nach unten, drückt. Gleichzeitig fährt er am unteren Nutrand im Gleichlauf, was ihn &amp;ldquo;in das Material&amp;rdquo; zieht - ebenfalls nach unten.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="vakuumspannsystem"&gt;Vakuumspannsystem&lt;/h3&gt;&#10;&lt;p&gt;Wie in den Artikeln &lt;a href="https://blog.schallbert.de/cnc-vacuum-pumps/"&gt;über Vakuumpumpen&lt;/a&gt; und &lt;a href="https://blog.schallbert.de/why-vacuum-table/"&gt;Vakuumtische&lt;/a&gt; beschrieben, verfüge ich über eine trockenlaufende Drehschieberpumpe, angeschlossen an einen Lochrastervakuumtisch. Bei der Bearbeitung poröser Werkstoffe ist diese Kombination ebenso wie Rastervakuumtische mit kleinvolumigen Pumpen problematisch:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Mein Lochrastervakuumtisch besitzt Löcher mit einem Vakuumkanal von &lt;code&gt;0.5mm&lt;/code&gt; Durchmesser. Durch diese Engstelle wird die Pumpe zwar bei offenliegenden Löchern entlastet, denn sie muss nicht den vollen Luftstrom bringen, die das &lt;code&gt;5mm&lt;/code&gt; große Sackloch ermöglicht. Allerdings kann das Loch durch den begrenzten Luftstrom in Kombination mit MDF weniger Unterdruck aufbauen.&lt;/li&gt;&#10;&lt;li&gt;Durch die hohen Leckströme ist meine Vakuumpumpe mit ihrem Volumenstrom an ihrer Grenze. Hier sind andere Pumpentypen deutlich besser geeignet (siehe &lt;a href="https://blog.schallbert.de/cnc-vacuum-pumps/#radial-blower"&gt;Radialgebläse&lt;/a&gt;, &lt;a href="https://blog.schallbert.de/cnc-vacuum-pumps/#seitenkanalverdichter"&gt;Seitenkanalverdichter&lt;/a&gt;).&lt;/li&gt;&#10;&lt;li&gt;Ein Vakuumtisch spannt generell nur nach unten (in Z-Richtung). Allerdings müssen auch Kräfte in XY-Richtung abgefangen werden, damit es nicht zu einer Verschiebung oder Drehung des Werkstückes kommt. Diese sind über den &lt;a href="https://blog.schallbert.de/cnc-vacuum-pumps/#parallelverschiebung"&gt;Haftreibkoeffizienten µ&lt;/a&gt; zwar an den Unterdruck am Werkstück gekoppelt (und lässt sich über Materialpaarung und Oberflächenbeschaffenheit beeinflussen), wenn Letzterer aber zu niedrig ausfällt, wird das Werkstück nicht zuverlässig gehalten.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-vacuumtable-channels-porousmaterials.jpg" alt="Image: Air flow system of vacuum table and porous material" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Obwohl sich die Vakuumkanäle im &lt;code&gt;10mm&lt;/code&gt;-Raster befinden, beträgt der für den Leckstrom maßgebliche Abstand nur &lt;code&gt;5mm&lt;/code&gt;. Die Ränder der Sacklöcher (je &lt;code&gt;5mm&lt;/code&gt;Durchmesser) befinden sich nämlich in diesem Abstand zueinander, sodass der Luftstrom quer zum Material eine geringere Distanz zurücklegen muss als durch das Material hindurch.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="vergleich-zum-widerstandsnetzwerk"&gt;Vergleich zum Widerstandsnetzwerk&lt;/h4&gt;&#10;&lt;p&gt;Die tatsächliche Spannkraft lässt sich gut abschätzen wenn man sich Pumpe, Vakuumtisch und Werkstück als elektrisches Netzwerk vorstellt. Ich habe hierfür ein Modell vorbereitet und stelle es im Folgenden vor.&lt;/p&gt;&#10;&lt;h4 id="widerstände-vakuumtisch-werkstück"&gt;Widerstände: Vakuumtisch, Werkstück&lt;/h4&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-tableworkpiece.jpg" alt="Image: Vacuum table and workpiece" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Die Sauglöcher des Vakuumtisches stellen Widerstände für den Luftstrom dar, welche sich in einer Parallelschaltung befinden. In Reihe dazu ist das Werkstück erneut als Widerstand geschaltet, was mit seinem anderen Pol an &amp;ldquo;Masse&amp;rdquo;, also dem Umgebungsluftdruck, liegt.&lt;/p&gt;&#10;&lt;h4 id="vakuumverteiler--schalter-manometer--spannungsmesser"&gt;Vakuumverteiler = Schalter, Manometer = Spannungsmesser&lt;/h4&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-manoswitch.jpg" alt="Image: Vacuum manifold and Manometer" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Das Manometer im Saugkanal kann als Spannungsmessgerät gesehen werden. Den Vakuumverteiler mit seinen Ventilen zum Schalten der Vakuumsektionen sehe ich als elektrische Schalteinrichtung. Ist der Schalter geschlossen, so stellt er die Verbindung zwischen Pumpe und Vakuumtisch her. Ist er geöffnet, so blockiert er den Luftstrom.&lt;/p&gt;&#10;&lt;h4 id="vakuumpumpe--spannungsquelle"&gt;Vakuumpumpe = Spannungsquelle&lt;/h4&gt;&#10;&lt;p&gt;In diesem Bild stellt die Pumpe eine Spannungsquelle dar, die in meinem Falle maximal &lt;code&gt;U = 8V&lt;/code&gt; entsprechend &lt;code&gt;-800mBar&lt;/code&gt; bereitstellen kann. Durch den bei realen Spannungsquellen vorhandenen inneren Widerstand von &lt;code&gt;0.2Ohm&lt;/code&gt; kann sie einen Kurzschlussstrom von &lt;code&gt;I = 40A&lt;/code&gt; erzeugen, analog zu den &lt;code&gt;40m³/h&lt;/code&gt; Durchsatz. Dieser Vergleich ist physikalisch natürlich nicht korrekt, denn ich müsste hier mit Masse- statt Volumenströmen arbeiten. Außerdem hängt die Fördermenge nicht überall im Kennfeld linear mit dem Druck zusammen und die von mir angenommenen Widerstandswerte sind nur Richtgrößen. Zur Modellierung des Sachverhaltes eignet es sich meiner Meinung nach trotzdem.&lt;/p&gt;&#10;&lt;p&gt;Bei Werkstückmaterialien wie Acrylglas oder Metall ist der Luftdurchfluss nahe Null, ihr &amp;ldquo;Widerstand&amp;rdquo; ist also extrem hoch. Dadurch fällt hier der allergrößte Teil der Spannung (Unterdruck) ab, sodass das Werkstück fest gespannt wird.&lt;/p&gt;&#10;&lt;p&gt;Bei Verwendung luftdichter Materialien ist die Welt entsprechend in Ordnung: Das Manometer zeigt beim noch unbearbeiteten Werkstück den den Druck auf dem Material korrekt an an. Selbst bei Durchfräsungen wird lediglich der Widerstand des Materials &amp;ldquo;in Reihe&amp;rdquo; zu den betroffenen Sauglöchern zu Null gesetzt. So wird der Unterdruck und damit Haltekraft am Teil selbst kaum reduziert.&lt;/p&gt;&#10;&lt;p&gt;Schaut man genau hin, so ist zu erkennen, dass das Manometer die Druckdifferenz vor dem Vakuumverteiler misst. Sein Wert ist also nur dann auch aussagekräftig für die Spannkraft, wenn das Werkstück einen sehr hohen Durchströmungswiderstand bietet.&lt;/p&gt;&#10;&lt;h4 id="sonderfall-poröse-werkstoffe"&gt;Sonderfall poröse Werkstoffe&lt;/h4&gt;&#10;&lt;p&gt;Ganz anders sieht das Bild bei porösen Werkstoffen wie MDF aus:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-porousmaterial-circuitdiagram.jpg" alt="Image: equivalent circuit diagram for porous material on a vacuum table" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Hier liegt der Durchgangswiderstand des Materials nur knapp eine Größenordnung über dem der Vakuumlöcher. Ich habe die Werte durch Versuche am Vakuumtisch ermittelt. Sie basieren auf Druckmessungen und Berechnungen von Werkstückfläche und der Anzahl vom Material abgedeckter, aktiver Sauglöcher. Besonders hervorzuheben sind hier zwei Effekte, die mir das Niederhalten zusätzlich schwer gemacht haben:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Augenmerk auf die Widerstände im Material in Parallelschaltung zum Durchgangswiderstand in &lt;code&gt;Z-Richtung&lt;/code&gt;. Sie entstehen dadurch, dass die Luft sich nun auch &amp;ldquo;zwischen den Löchern&amp;rdquo; durch das Material bewegen kann.&lt;/li&gt;&#10;&lt;li&gt;Auf der rechten Seite des Bildes ist zu erkennen, was passiert, wenn das Werkstück in einen inaktiven Vakuumbereich hineinragt: Die Sauglöcher sind untereinander praktisch ohne Widerstand mittels Vakuumkanälen im Tisch verbunden. Da das Werkstück &amp;ldquo;Querluftzug&amp;rdquo; in &lt;code&gt;XY-Richtung&lt;/code&gt; ermöglicht, muss durch Verbindung der Sauglöcher deutlich weniger Widerstand überwunden werden, als für die Durchdringung in &lt;code&gt;Z&lt;/code&gt;. Hierdurch liegen nur noch &lt;code&gt;R29&lt;/code&gt; und &lt;code&gt;R22||R21&lt;/code&gt; in Reihe. Überdeckt das Werkstück mehrere inaktive Lochfelder, geht &lt;code&gt;R22&lt;/code&gt; gegen 0.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In der Folge werden die Widerstände vor &lt;code&gt;R19&lt;/code&gt;, nämlich &lt;code&gt;R18||(R29+(R21||R22))&lt;/code&gt; so klein, dass die Pumpe hier praktisch nur noch Umgebungsluft zieht und an der Stelle kaum Spannkraft aufbringen kann. Über &lt;code&gt;R25&lt;/code&gt; wirkt sich der Querluftstrom außerdem auf den Druckabfall von &lt;code&gt;R17, R15&lt;/code&gt; aus.&lt;/p&gt;&#10;&lt;p&gt;Jetzt wirken die kleinen Sauglöcher als Spannungsteiler, sodass an ihnen ein nicht unerheblicher Teil des Unterdrucks &amp;ldquo;hängen&amp;rdquo; bleibt.&lt;/p&gt;&#10;&lt;p&gt;Also stimmt auch der auf dem Manometer angezeigte Druck überhaupt nicht mehr mit den Gegebenheiten am Werkstück überein: Obwohl ich hier noch immer &lt;code&gt;-200mBar&lt;/code&gt; angezeigt bekomme, reduziert sich die Spannkraft genau an den für das Niederhalten empfindlichsten Stellen&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-resnetwork.jpg" alt="Image: Simplified equivalent circuit diagram for a vacuum clamping system" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;am Rand des Werkstücks&lt;/li&gt;&#10;&lt;li&gt;in angrenzenden Bereichen zu denen, wo das Werkstück nicht aktiv angesaugt wird&lt;/li&gt;&#10;&lt;li&gt;sowie an jeder Durchfräsung und einem gewissen Umkreis&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;auf nahe null.&lt;/p&gt;&#10;&lt;p&gt;Für alle anderen Bereiche gelten die &lt;a href="https://en.wikipedia.org/wiki/Kirchhoff%27s_circuit_laws" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Kirchhoff&amp;rsquo;schen Gesetze&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;: Gebildet wird ein Spannungsteiler bestehend aus Innenwiderstand der Pumpe, Vakuumsystem sowie dem Durchdringungswiderstand des Werkstücks. Nur die noch am Werkstück abfallende Spannung bestimmt die Niederhaltekraft. Sie ist - ich wiederhole mich jetzt - unter Umständen &lt;em&gt;viel geringer&lt;/em&gt; als der Blick auf das Manometer vermuten lässt.&lt;/p&gt;&#10;&lt;h2 id="lösungen"&gt;Lösungen&lt;/h2&gt;&#10;&lt;p&gt;Ganz schön viel Theorie für die banale Erkenntnis, dass mir das Werkstück auf dem Maschinenbett verdreht wird, oder? Folgende Lösungen habe ich für ein solches Problem im Köcher:&lt;/p&gt;&#10;&lt;h3 id="pumpe--vakuumtisch"&gt;Pumpe / Vakuumtisch&lt;/h3&gt;&#10;&lt;p&gt;Bei der Arbeit mit porösen Werkstoffen ist es wichtig, dass der Tisch selbst einen geringen Durchströmungswiderstand aufweist. Nur so fällt der Großteil des Unterdrucks am Werkstück ab, was zu höherer Spannkraft führt. Hier wäre nach dem Motto &amp;ldquo;viel hilft viel&amp;rdquo; also eine Pumpe mit mehr Durchsatz, ein Tisch mit größeren Saugöffnungen und ein Vakuumverteiler mit größeren Schlauchdurchmessern angemessen. Kommt für mich aber überhaupt nicht in Frage.&lt;/p&gt;&#10;&lt;h3 id="haftvermittler"&gt;Haftvermittler&lt;/h3&gt;&#10;&lt;p&gt;Ich könnte probieren, eine grobmaschigere Gummimatte zu verwenden. Mit einem &lt;code&gt;20mm&lt;/code&gt; Lochraster etwa verdreifacht sich der Widerstand für den Querluftstrom, sodass Außenbereiche nicht mehr so stark vom Druckverlust betroffen sind. Zudem besitzt eine solche Matte mehr Gummifläche, was die Haftreibung erhöht.&lt;/p&gt;&#10;&lt;p&gt;Klebeband statt Frischhaltefolie auf der Materialoberfläche löst das Problem des Verfangens im Fräser und verschließt lokal die Poren des Werkstoffes, kann also die Haltekraft in kritischen Bereichen verbessern.&lt;/p&gt;&#10;&lt;p&gt;Natürlich können auch auf einem Vakuumtisch gängige Methoden wie die Aufbringung doppelseitigen Klebebandes oder das Festschrauben des Werkstückes angewandt werden. Mache ich aber nicht - denn dann müsste ich mir ja eingestehen, dass mein Vakuumtisch nicht für alle Fälle geeignet ist.&lt;/p&gt;&#10;&lt;h3 id="haltestege-spannvorrichtung"&gt;Haltestege, Spannvorrichtung&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-04-02-spirofixtures.jpg" alt="Image: Fixture for holding my gears in place" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Diese Lösung zielt darauf ab, Verdrehen und Verschieben des Werkstückes zu vermeiden. Der Vakuumtisch wird hier nur noch zum Niederhalten verwendet, während eine passend angefertigte Halterung Kräfte in &lt;code&gt;XY-Richtung&lt;/code&gt; aufnimmt. Im Bild sind Halterungen für die Schablone sowie das Zahnrad zu erkennen, die ebendiese Aufgabe erfüllen. Sie werden auf dem Maschinenbett befestigt und benötigen einen präzisen Nullpunkt, um die Kanten versatzfrei brechen zu können.&lt;/p&gt;&#10;&lt;p&gt;Ich verwende eine Spielpassung mit &lt;code&gt;0.1mm&lt;/code&gt; Luft zwischen den Komponenten. Ist das Spiel kleiner, benötige ich zu viel Kraft, den Rohling einzusetzen. Setze ich größere Werte an, lassen sich die Werkstücke zu stark verdrehen.&lt;/p&gt;&#10;&lt;p&gt;Mit dieser Variante kann ich verzogene Werkstücke geradeziehen, weiterhin sehr feine Gravuren anfertigen und gehe trotzdem sicher, dass mir nichts mehr verrutscht.&lt;/p&gt;&#10;&lt;p&gt;Nachteilig sind der Zeit- und Materialaufwand für Planung und Herstellung individueller Halterungen bzw. Schablonen.&lt;/p&gt;&#10;&lt;h3 id="modifikation-des-werkstückes"&gt;Modifikation des Werkstückes&lt;/h3&gt;&#10;&lt;p&gt;Dies liegt auf der Hand: Ich kann das Werkstück in seinen Eigenschaften so verändern, dass es undurchlässiger wird für Luft. So kann ich z.B. die Unterseite mit einer Folie bekleben und damit zusätzlich die Haftreibung verbessern.&lt;/p&gt;&#10;&lt;p&gt;Wer wie ich nicht so gerne Plastik verwendet: Ich habe herausgefunden, dass Backpapier / Frischhaltepapier aus ungebleichtem Zellstoff kaum Luft hindurch lässt. Demnächst probiere ich mal aus, Materialien damit zu bekleben bzw. von Durchfräsung betroffene Bereiche damit abzudecken statt mit Kunststofffolie.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Liegt ein solches Problem regelmäßig vor, sind Rastervakuumtische mit Dichtschnur eine hervorragende Lösung: Hier mittels einer linienförmigen Dichtung an den Außenkanten des Werkstückes gegen den Umgebungsluftdruck abgedichtet, sodass Werkstückkanten keine Chance bekommen, sich hochzuwölben. Bei meinem Lochrastertisch jedoch wirkt die gesamte Fläche der Gummimatte als Dichtung, sodass die Wölbung in Form einer Kontaktlinie auf der Matte vorliegt. Linien haben jedoch keinen Flächeninhalt, und nur durch jenen kann mein Tisch niederhalten. Dies ist übrigens auch der Grund, warum biegsame Materialien wie Folien so leicht vom Vakuumtisch abgezogen werden können.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Maker Faire Ruhr 2024</title><link>https://blog.schallbert.de/makerfaire-ruhr/</link><pubDate>Mon, 25 Mar 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/makerfaire-ruhr/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-maker-faire-logo-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Maker Faire Ruhr Logo / Banner"&#10; title="Maker Faire Ruhr 2024" /&gt;&#10;&lt;h2 id="vorbereitung"&gt;Vorbereitung&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-maker-faire-logo.jpg" alt="Image: Maker Faire Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Seit Anfang Januar bereiten sich zwei meiner Freunde und ich auf die &lt;a href="https://www.makerfaire-ruhr.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Maker Fair Ruhr&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; in der &lt;a href="https://www.dasa-dortmund.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;DASA Dortmund&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; vor. Im Zuge dessen habe ich Faserplatten furniert, Rohlinge für Gravuren ausgeschnitten, Tischdecken besorgt, PC-Gehäuse bearbeitet, Zahnräder gefräst, LED-Streifen programmiert, ein Standschild designt, eine CNC-Maschine eingerichtet, sie für den Gravurbetrieb optimiert und so weiter&amp;hellip;&lt;/p&gt;&#10;&lt;p&gt;Als der Termin dann nahte, wurde es dann doch knapp. Es geht schon eine Menge Zeit und Koordination in so eine Messevorbereitung.&lt;/p&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Image: Vacuumpump and -table"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-vacuumpump.jpg" alt="Image: Vacuumpump and -table" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Image: CNC machine, ready to run."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-hobbyline_readytorun.jpg" alt="Image: CNC machine, ready to run." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Image: Signs for my projects that I&amp;#39;ll showcase."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-standschilder.jpg" alt="Image: Signs for my projects that I&amp;#39;ll showcase." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-3" aria-label="Image: Retrogaming sign for another booth"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-retrogaming_sign.jpg" alt="Image: Retrogaming sign for another booth" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-4" aria-label="Image: Beautiful scrap - residue piece of PMMA in form of a wave"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-pmma_wave.jpg" alt="Image: Beautiful scrap - residue piece of PMMA in form of a wave" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-5" aria-label="Image: The side panel of a midi-tower PC: now with a window"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-pcwindow_sea.jpg" alt="Image: The side panel of a midi-tower PC: now with a window" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-6" aria-label="Image: Schallbert&amp;#39;s booth on Maker Faire Ruhr 2024"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-DerStand.jpg" alt="Image: Schallbert&amp;#39;s booth on Maker Faire Ruhr 2024" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-7" aria-label="Image: 2000s-retro-PCs set up and ready to play."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-Gaming.jpg" alt="Image: 2000s-retro-PCs set up and ready to play." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-8" aria-label="Image: CNC machine detail view"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-StandDetail.jpg" alt="Image: CNC machine detail view" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-9" aria-label="Image: Visitors on Saturday"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-Besucher.jpg" alt="Image: Visitors on Saturday" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-10" aria-label="Image: Schallbert&amp;#39;s booth, rear view"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-Backstage.jpg" alt="Image: Schallbert&amp;#39;s booth, rear view" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-11" aria-label="Image: Many more visitors on Sunday."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-Menschenmenge.jpg" alt="Image: Many more visitors on Sunday." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Impressionen von der Maker Faire Ruhr 2024.&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-vacuumpump.jpg" alt="Image: Vacuumpump and -table" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Hier wird der Vakuumtisch in Betrieb genommen und getestet.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-hobbyline_readytorun.jpg" alt="Image: CNC machine, ready to run." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Die CNC-Maschine für die Messe. Fertig eingerichtet und betriebsbereit.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-standschilder.jpg" alt="Image: Signs for my projects that I&amp;#39;ll showcase." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Selbstgefräster Schilder für die dargestellten Projekte.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-3" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-retrogaming_sign.jpg" alt="Image: Retrogaming sign for another booth" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Das Standschild für einen guten Freund, der Retro-PCs und -Spiele ausstellt.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-4" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-pmma_wave.jpg" alt="Image: Beautiful scrap - residue piece of PMMA in form of a wave" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Schöner Abfall - ein Reststück Acrylglas in Form einer Welle&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-5" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-pcwindow_sea.jpg" alt="Image: The side panel of a midi-tower PC: now with a window" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Das Gegenstück zur Welle: Ein PC mit einzigartigem Fenster, selbstgefräst.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-6" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-DerStand.jpg" alt="Image: Schallbert&amp;#39;s booth on Maker Faire Ruhr 2024" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Die LED-Streifen sind fertig programmiert und alles steht an seinem Platz.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-7" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-Gaming.jpg" alt="Image: 2000s-retro-PCs set up and ready to play." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Retro-2000er-PCs sind aufgebaut und vernetzt. Bereit zum Spielen.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-8" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-StandDetail.jpg" alt="Image: CNC machine detail view" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Die betriebsbereite CNC-Maschine auf der Messe.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-9" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-Besucher.jpg" alt="Image: Visitors on Saturday" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Bereits einige Besucher, Maker und Interessierte am Samstag.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-10" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-Backstage.jpg" alt="Image: Schallbert&amp;#39;s booth, rear view" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Backstage des betriebsbereiten Standes.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-11" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-Menschenmenge.jpg" alt="Image: Many more visitors on Sunday." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Doch am Sonntag war noch viel mehr los.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;h3 id="projekte"&gt;Projekte&lt;/h3&gt;&#10;&lt;p&gt;Auf der Messe wollte ich ein paar meiner schönsten Projekte vorstellen wie etwa den &lt;a href="https://blog.schallbert.de/projects/seifenbutler/"&gt;Seifenbutler&lt;/a&gt;, meine übergroßen &lt;a href="https://blog.schallbert.de/projects/spirograph/"&gt;Spirographen&lt;/a&gt; und Besuchern ermöglichen, eigene Schilder &lt;a href="https://blog.schallbert.de/negative-carving-with-estlcam/"&gt;graviert zu bekommen&lt;/a&gt;, nach Bedarf sogar mit &lt;a href="https://blog.schallbert.de/projects/qr-codengrave/"&gt;QR-Code&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Damit die Fräsarbeiten schnell von der Hand gehen, habe ich Rohlinge gefertigt, die später nur noch graviert werden mussten. Die Spirographen musste ich wegen der erforderlichen doppelseitigen Bearbeitung ebenfalls vorab fertigen.&lt;/p&gt;&#10;&lt;h3 id="maschinen"&gt;Maschinen&lt;/h3&gt;&#10;&lt;p&gt;Ich konnte ein paar Firmen davon überzeugen, mir Maschinen und -Teile für die Messe zu leihen:&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/sorotec.jpg" alt="Bild: Sorotec Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Von &lt;a href="https://www.sorotec.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Sorotec&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; borgte ich mir eine &amp;ldquo;Hobbyline4530&amp;rdquo;, deren kleinste und leichteste Maschine.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/stritzelberger.jpg" alt="Bild: Stritzelberger Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Die Firma &lt;a href="https://vakuumtisch.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Stritzelberger&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; stellte mir einen zur Maschine passenden Vakuumtisch inkl. passender Pumpe zur Verfügung.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/eding.jpg" alt="Bild: EdingCNC Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;a href="https://edingcnc.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;EdingCNC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; unterstützte bei der Einrichtung ihrer Software, lieh mir Aussteller für die aktuellste Steuerungshardware und kam schließlich sogar live an meinem Messestand zwecks Austausch vorbei.&lt;/p&gt;&#10;&lt;h3 id="das-standschild"&gt;Das Standschild&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-23-schallbert-standschild.jpg" alt="Image: My plan for a great booth sign" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Für das Standschild habe ich mir etwas ganz Besonderes ausgedacht: Ein Planetengetriebe! Drei bewegliche Zahnräder umkreisen hierbei ein feststehendes Sonnenrad. Um das Standschild aufhängen und das Getriebe dabei randlos behalten zu können, kam für die Planetenträger keine mittig gelagerte Achse in Frage.&lt;/p&gt;&#10;&lt;p&gt;Diese Aufgabe übernimmt ein durch das Sonnenrad verdecktes, durch drei Kugellager (Bild: Im 120°-Winkel angeordnet, montiert auf den Zähnen des Sonnenrads) gehaltenes Hohlrad. Es wird von einem Schrittmotor mit der Untersetzung 1:5 angetrieben. Das Hohlrad bietet Aufnahmen für die Planetenträger. So bekomme ich den mittleren Bereich des Sonnenrades frei und kann dort sowohl Antriebsmotor als auch Beleuchtung und Schildhalterungen unterbringen.&lt;/p&gt;&#10;&lt;p&gt;Ein auf meinem PeerTube-Kanal befindliches Video zeigt, wie ich das Standschild mit meiner CNC-Portalfräse fertige.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/xsLhWWygyJbtJGwUGrKvVE" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Herstellung Standschild&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="verzögerungen"&gt;Verzögerungen&lt;/h3&gt;&#10;&lt;p&gt;Wie immer bei solchen größeren Unternehmungen ging bei den Vorbereitungen auch einiges schief:&lt;/p&gt;&#10;&lt;h4 id="ablösung-des-vakuums-bei-der-fertigung-der-spirographen"&gt;Ablösung des Vakuums bei der Fertigung der Spirographen&lt;/h4&gt;&#10;&lt;p&gt;Die Spirographen wollte ich aus furniertem MDF herstellen, da das Material sehr schön anzusehen und gleichzeitig stabil genug ist, dass es auch bei rauer Behandlung keinen Schaden nimmt. Also habe ich &lt;code&gt;10mm&lt;/code&gt; starke mitteldichte Faserplatte mit Olivenholzfurnier beklebt (zeitraubend!) und es dann auf meiner Portalfräse beareitet.&lt;/p&gt;&#10;&lt;p&gt;Da ich ein &lt;a href="https://blog.schallbert.de/why-vacuum-table/"&gt;Vakuumspannsystem&lt;/a&gt; verwende, ist es total wichtig, einen Mindestunterdruck zu erreichen, da mir sonst die Werkstücke nicht an Ort und Stelle bleiben. Nun ist MDF luftdurchlässig, sodass die Pumpe selbst bei noch nicht bearbeiteter Platte gut zu tun hat. Die Geometrie meines Entwurfs für die Spirographen ist nun leider so, dass die für das Spannen verbliebene Oberfläche bei der Bearbeitung immer weiter sinkt.&lt;/p&gt;&#10;&lt;p&gt;In letzter Konsequenz hatte ich das Problem, dass sich die Zahnräder spätestens bei der Kantenbearbeitung der Rückseite mitdrehen wollen. Sogar meine verzweifelten Versuche genügend Unterdruck aufrecht zu erhalten, indem ich großflächig Frischhaltefolie auf dem Werkstück aufbrachte, scheiterten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-spirograph.jpg" alt="Image: Spirograph under test" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;So erhielt ich anstatt der geplanten zehn Spirographen nur vier - die Restlichen waren Ausschuss. &lt;em&gt;Merke&lt;/em&gt;: MDF ist auf meinem Vakuumspannsystem nur für großflächige Werkstücke einsetzbar.&lt;/p&gt;&#10;&lt;h4 id="problematische-zahnradpaarung-beim-spirographen"&gt;Problematische Zahnradpaarung beim Spirographen&lt;/h4&gt;&#10;&lt;p&gt;Der erste Spirograph aus MDF war zudem leider gar nicht richtig zu gebrauchen: Ständig verklemmte sich ein Zahn auf der Flanke des Hohlrades und weigerte sich, in Eingriff zu gehen. Die Ursachen dafür liegen darin, dass ich kein Flankenspiel vorgesehen und die Zahnköpfe ohne Radius gefertigt hatte.&lt;/p&gt;&#10;&lt;p&gt;In der Folge muss das Zahnrad immer kräftig gegen das Hohlrad gedrückt werden, um den nächsten Zahneingriff zu ermöglichen. Rutscht das Zahnrad nur ein paar Zehntelmillimeter heraus, so blockiert es. &lt;em&gt;Abhilfe&lt;/em&gt;: Sowohl Zahnrad- als auch Hohlradzahnköpfe mit &lt;code&gt;r=3.0mm&lt;/code&gt; versehen und die Zahnradpaarung mit Flankenspiel von &lt;code&gt;a=0.25mm&lt;/code&gt; konstruieren.&lt;/p&gt;&#10;&lt;h4 id="falsch-gravierte-schriften-auf-dem-großen-standschild"&gt;Falsch gravierte Schriften auf dem großen Standschild&lt;/h4&gt;&#10;&lt;p&gt;Hier habe ich mich sogar zweimal vertan und damit leider viel Ausschuss produziert. Beim ersten Versuch hatte ich übersehen, das Zahnrad für die rückseitige Bearbeitung zu spiegeln. Ich dachte, das sei nicht nötig, weil das Zahnrad selbst spiegelsymmetrisch ist. Leider waren die Haltestifte aber nicht spiegelsymmetrisch eingelassen, sodass sich die Rückseite leider nicht in Deckung mit der Vorderseite befand.&lt;/p&gt;&#10;&lt;p&gt;Beim zweiten Versuch - siehe Video oben - hatte ich dies zwar behoben, jedoch vergessen, den Nullpunkt ensprechend der neuen Zentrierstiftlage auf der Rückseite anzupassen. Zum Glück habe ich das während der Bearbeitung bemerkt und konnte korrigieren.&lt;/p&gt;&#10;&lt;h4 id="retro-pc-fensterfertigung"&gt;Retro-PC Fensterfertigung&lt;/h4&gt;&#10;&lt;p&gt;Bei einem der drei Retro-PCs habe ich die Gehäuserückseite um 180° gedreht aufs Maschinenbett gelegt. Das Fenster wurde also &amp;ldquo;verkehrt herum&amp;rdquo; gefräst, was ich erst nach Beendigung des Fräsjobs bemerkte. Zum Glück konnte mein Freund durch Feilen und Biegen der Rastnasen dafür sorgen, dass die Gehäuserückseite auch &amp;ldquo;verkehrt herum&amp;rdquo; am PC angebracht werden konnte.&lt;/p&gt;&#10;&lt;p&gt;Bei der Fertigung des letzten Fensters verhakte sich der Spanschutz im Gehäusefalz und brach, da ich die Maschine nicht rechtzeitig zum Stehen bekam. Dies konnte überhaupt nur passieren, weil sich der Spanschutz durch die Vibrationen bei der Bearbeitung lockerte. &lt;em&gt;Merke&lt;/em&gt;: Die Fräse ist kein Spielzeug. Also keine Kompromisse machen oder halbgare Provisorien anbringen!&lt;/p&gt;&#10;&lt;h2 id="die-maker-faire-ruhr-2024"&gt;Die Maker Faire Ruhr 2024&lt;/h2&gt;&#10;&lt;p&gt;Die Messe selber war super. Ich wurde sehr freundlich empfangen und seitens der DASA erhielt ich hervorragende Unterstützung beim Ausladen und aller weiterer Logistik. Auch der mir zugeteilte Stand war toll: Sehr groß, gut gelegen, mit ausreichend Steckdosen - was will man mehr.&lt;/p&gt;&#10;&lt;p&gt;Ich hatte eine Firma zu Besuch an meinem Stand, Familie und Freunde waren gekommen und sogar einige Leute aus den CNC-Foren sowie ein paar Arbeitskollegen waren extra angereist. Mehr kann ich mir nicht wünschen.&lt;/p&gt;&#10;&lt;h3 id="aufbau"&gt;Aufbau&lt;/h3&gt;&#10;&lt;p&gt;Beim Ausladen kam uns zum Glück ein Stapler zur Hilfe.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2024-03-25-unloading.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Unloading the CNC&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Alles Weitere war Handarbeit und nicht deutlich anders, als für ein Musikkonzert aufzubauen: Tische rücken, Stromleitungen legen, Lichtanlage montieren, Ausstellungsstücke aufbauen.&lt;/p&gt;&#10;&lt;h3 id="besucher"&gt;Besucher&lt;/h3&gt;&#10;&lt;p&gt;Für meine erste Maker Faire kann ich eine positive Bilanz ziehen: Es waren echt viele Leute an unserem Stand und ich hatte kaum ein paar Minuten zum Ausruhen - ständig gab es etwas zu erzählen, meine Projekte vorzustellen oder über die ausgestellte Maschine zu sprechen. Aber genau das war es ja, was uns an diesem Format gereizt hat. Außerdem hat die Technik funktioniert, die Maschine lief problemlos und alle Dinge, die ich so lange vorher vorbereitet hatte (wie etwa das Standschild), konnten ausgestellt bzw. im Betrieb gezeigt werden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2024-03-25-short_engrave.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;QR-Codengrave Demonstration&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="was-ich-beim-nächsten-mal-trotzdem-anders-machen-würde"&gt;Was ich beim nächsten Mal trotzdem anders machen würde&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Maker Faire ist vom Setup her keine Verkaufsmesse, sondern ein riesiges Show &amp;amp; Tell. Daher würde ich nicht nochmal einen kostenpflichtigen Verkaufsstand buchen, denn das hat sich in meinem Falle nicht gelohnt.&lt;/li&gt;&#10;&lt;li&gt;Ein bis zwei Personen mehr für die Standbetreuung wären prima. Dann könnte man sich mal ablösen.&lt;/li&gt;&#10;&lt;li&gt;Mehr Material für die Spirographen! Die Stifte waren am Ende der Veranstaltung komplett durch und Papier hatten wir schon Mitte des zweiten Tages keines mehr.&lt;/li&gt;&#10;&lt;li&gt;Mehr Vorträge / Demos. Die Fräse lief zwar schon oft, aber es mangelte dann doch an Vorlagen und Wünschen seitens der Besucher. Beim nächsten Mal werde ich mehr eigene Designs mitnehmen und auch ohne Anfrage zu Demozwecken fertigen.&lt;/li&gt;&#10;&lt;li&gt;Bessere Verweise auf die Internetpräsenz vorbereiten, denn ein QR-Code alleine hilft da nicht allen weiter.&lt;/li&gt;&#10;&lt;li&gt;Mehr Kaffee: Die Versorgung mit Heißgetränken (und jetzt wo ich es schreibe, auch die mit Nahrung) war unterdimensioniert und leider auch nicht so schmackhaft, wie ich mir das gewünscht hätte. Also besser selbst Stullen schmieren und Thermoskannen mitbringen.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Seifenbutler - jetzt erhältlich</title><link>https://blog.schallbert.de/announcements/2024-03-01-seifenbutler-out-now/</link><pubDate>Fri, 01 Mar 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/announcements/2024-03-01-seifenbutler-out-now/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/announcements/2024-03-10-seifenbutler-transparent-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Seifenbutler, transparent, photographed from the side"&#10; title="Seifenbutler - jetzt erhältlich" /&gt;&#10;&lt;h2 id="märz-2024--seifenbutler-jetzt-erhältlich-"&gt;März-2024 &amp;mdash; Seifenbutler jetzt erhältlich! &amp;mdash;&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-08-seifenbutler-white.jpg" alt="Image: Seifenbutler, white Acrylic glass, attached to a shower rail" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Mein Langzeitprojekt, der Seifenbutler, ist nun nach 13 Versionen endlich abgeschlossen. Ich habe eine Nullserie gefertigt und glaube, dass ich damit ein gutes Produkt geschaffen habe.&#10;Unterstützt meine Arbeit gern, indem ihr euch &lt;a href="https://mopemaster.com/produkt/seifenbutler-seifenhalter-dusche/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;in Mopemasters Onlineshop&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ein Exemplar kauft oder weiterempfehlt!&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/jqAQ7TFtscSDzLEcC1o8HM" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Montage Seifenbutler&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das obige Video zeigt, wie einfach die Montage vonstatten geht. Die ganze Geschichte zum Seifenbutler erzähle ich auf der entsprechenden &lt;a href="https://blog.schallbert.de/projects/seifenbutler/"&gt;Projektseite&lt;/a&gt;. Weitere Hintergrundinformationen zu Herstellung und Preisfindung finden sich im &lt;a href="https://blog.schallbert.de/seifenbutler-pilot-run/"&gt;Nullserien-Artikel&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Schallbert&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Seifenbutler - Nullserie!</title><link>https://blog.schallbert.de/seifenbutler-pilot-run/</link><pubDate>Thu, 08 Feb 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/seifenbutler-pilot-run/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-08-seifenbutler-white.jpg"&#10; class="post-cover"&#10; alt="Image: Seifenbutler ready for sale"&#10; title="Seifenbutler - Nullserie!" /&gt;&#10;&lt;p&gt;Jetzt ist mein Hardware-Projekt &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;Seifenbutler&lt;/a&gt; abgeschlossen. Nach langer Entwicklungszeit habe ich einige Exemplare in weiß, schwarz, transparent und opalweiß hergestellt und sie in einem ersten &lt;a href="https://mopemaster.com/produkt/seifenbutler-seifenhalter-dusche/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Online-Shop&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; platzieren lassen können.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-08-seifenbutler-collection.jpg" alt="Image: Seifenbutler in three different colours" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="wie-bestimme-ich-den-verkaufspreis"&gt;Wie bestimme ich den Verkaufspreis?&lt;/h2&gt;&#10;&lt;p&gt;Da ich hochwertige Materialien verwende und die Fertigungs- sowie Montagezeit lang ausfällt, habe ich bei der Preisgestaltung nicht so viel Luft nach unten. Andererseits kann ich für einen Gebrauchsgegenstand wie den Seifenbutler keine Mondpreise aufrufen - es sei denn, ich verwendete edle Hölzer oder andere, ausgefallene oder aufwändig zu bearbeitende Materialien. Für solche Kalkulationen gibt es eine Menge Material online. Ich habe mich dafür an einem &lt;a href="https://www.youtube.com/watch?v=mxwggzhe_hs" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Youtube-Video des Kanals &amp;ldquo;Cutting It Close&amp;rdquo;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; orientiert.&lt;/p&gt;&#10;&lt;p&gt;Meine Preisberechnung setzt sich aus folgenden Posten zusammen:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Endpreis = Materialpreis&#10; + Maschinenzeit (Investition, Verschleiß, Werkzeuge, Verbrauchsmaterialien)&#10; + Fertigungs- und Montagezeit &#10; + Verpackungskosten &#10; + Gebühren (Porto, Zahlungsdienstleister, Shops)&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wie zu erkennen ist, habe ich meine eigene Zeit für die Produktentwicklung da überhaupt nicht eingerechnet - müsste ich dies tun, so würden ganz locker noch einmal der Gegenwert zu &lt;code&gt;250h&lt;/code&gt; Arbeitszeit hinzukommen.&lt;/p&gt;&#10;&lt;h3 id="materialpreise"&gt;Materialpreise&lt;/h3&gt;&#10;&lt;p&gt;&lt;strong&gt;Stand Ende 2023&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Ok, dann listen wir mal schnell die Auslagen pro Teil auf:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Sückzahl&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Artikel&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Preis/Einheit&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Verwendete Einheit&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Endpreis&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;PMMA 5mm XT&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;90€/m²&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,03&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2,70€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Rändel M4 A2 22mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7,54€/25St&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,04&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,30€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Scheibe M4 A2 12mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,94€/100St&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,01&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,01€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stift M4 A2 12mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6,70€/50St&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,02&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,13€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schraube M4 A2 8mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12,40€/200St&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,025&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,31€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Materialpreis/Teil&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3,45€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="maschinenzeit"&gt;Maschinenzeit&lt;/h3&gt;&#10;&lt;p&gt;Die Maschinenzeit setze ich für dieses Projekt mit &lt;code&gt;100€/h&lt;/code&gt; an. Der Löwenanteil sind hierbei Investitions-, Wartungs-, sowie Verschleißkosten der Maschine mit &lt;code&gt;50€/h&lt;/code&gt; plus meiner eigenen Arbeitszeit, die ich einfach mal mit &lt;code&gt;30€/h&lt;/code&gt; ansetze.&lt;/p&gt;&#10;&lt;p&gt;Die Werkzeugkosten liegt mit ca. &lt;code&gt;15€/h&lt;/code&gt; recht hoch, da ich vier verschiedene Werkzeuge, allesamt mit kleinem Durchmesser und damit entsprechen hohem Verschleiß benutzen muss.&lt;/p&gt;&#10;&lt;p&gt;Verbrauchsmaterialien sind Vakuumvliese, Positionierstifte, Schnittschutzhandschuhe und Klebeband. Letzeres benötige ich für die Fixierung der kleinen Frästeile sowie der Spirale. Ihr Anteil an den Maschinenzeitkosten liegt bei etwa &lt;code&gt;5€/h&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Die Maschinenzeit beträgt momentan &lt;code&gt;8min/Teil&lt;/code&gt;. Somit beläuft sich Posten &lt;code&gt;Maschinenzeit&lt;/code&gt; auf &lt;code&gt;13,30€/Stück&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="montagezeit"&gt;Montagezeit&lt;/h3&gt;&#10;&lt;p&gt;Für die Montage eines Seifenhalters benötige ich etwa 5 Minuten. Lege ich einen Stundensatz von &lt;code&gt;30€&lt;/code&gt; zugrunde, so komme ich hier auf &lt;code&gt;2,50€/Stück&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-08-seifenbutler-assembly.jpg" alt="Image: Seifenbutler ready to assemble" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="verpackungskosten-und-materialeinkauf"&gt;Verpackungskosten und Materialeinkauf&lt;/h3&gt;&#10;&lt;p&gt;Ich verpacke die Seifenhalter in einfache DIN A6-Briefumschläge, nachdem ich sie sorgfältig in Packpapier eingewickelt habe. Die Kosten belaufen sich hier auf lediglich &lt;code&gt;0,20€/Stück&lt;/code&gt;. Rechne ich nun jedoch meine Arbeitszeit für Verpackung und Versandaufkleber mit ein, liegen wir bereits bei &lt;code&gt;0,90€/Stück&lt;/code&gt;. Hinzu kommen Aufwendungen für Porto beim Materialeinkauf und Kosten für Papier und Druck der Bedienungsanleitung, die ich mit weiteren &lt;code&gt;0,50€/Stück&lt;/code&gt; berechne - hier schlagen die geringen gefertigten Mengen voll durch.&lt;/p&gt;&#10;&lt;h3 id="gebühren"&gt;Gebühren&lt;/h3&gt;&#10;&lt;p&gt;Gebühren fallen z.B. für Versand- und Zahlungsdienstleister an. Hier variieren die Preise je nach Anbieter, sodass ich einen Versandaufschlag von &lt;code&gt;3,50€&lt;/code&gt; (Versand per Großbrief) einplane - dieser entfällt bei Verkäufen &amp;ldquo;an der Theke&amp;rdquo;.&lt;/p&gt;&#10;&lt;h2 id="der-gesamtpreis"&gt;Der Gesamtpreis&lt;/h2&gt;&#10;&lt;p&gt;Summiere ich alle Posten zusammen, so müsste ich einen Ladenpreis von &lt;code&gt;20,15€&lt;/code&gt; (bei Barzahlung) für die unverpackte Ware und &lt;code&gt;24,75€&lt;/code&gt; im Versandfalle mit bargeldloser Zahlung aufrufen. Und dann wäre in der Firma noch immer kein Cent Gewinn gemacht.&lt;/p&gt;&#10;&lt;p&gt;Ich bin mir nicht sicher, ob sich der Seifenbutler für solche Preise verkaufen lässt. Wenn man sich aber ehrlich macht und am Ende nicht draufzahlen will, sind solche Rechnungen zumindest überschlagsweise unumgänglich.&lt;/p&gt;&#10;&lt;h2 id="der-angenommene-kaufmann-mit-ladengeschäft"&gt;Der angenommene Kaufmann mit Ladengeschäft&lt;/h2&gt;&#10;&lt;p&gt;Wäre ich nun Kaufmann&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, so müsste ich noch Mehrwertsteuer aufschlagen und würde auch die Entwicklungskosten irgendwie amortisiert sehen wollen. Ich bin mir auch nicht sicher, ob ich &amp;ldquo;als Kaufmann&amp;rdquo; mit einem Stundensatz von 30€ durch die Tür käme.&lt;/p&gt;&#10;&lt;p&gt;Puh. Rechnet man jetzt noch Dinge wie Ladenmiete, Kosten für Lagerfläche, Werkstattmiete, Strom, Abfallentsorgung, Mitgliedschaft beim &amp;ldquo;Grünen Punkt&amp;rdquo; nach Verpackungsgesetz, Beiträge für IHK, Berufsgenossenschaft sowie Steuern und Abgaben mit ein, so muss selbst bei einem weiteren Preisaufschlag von 20% schon ein ganzer Haufen Seifenhalter verkauft werden, um am Ende nicht ärmer dazustehen als vorher!&lt;/p&gt;&#10;&lt;p&gt;Nehmen wir mal an, ich kann mich in eine Werkstatt einmieten und einen winzigen Laden eröffnen.&lt;/p&gt;&#10;&lt;h3 id="ein-fiktiver-laden"&gt;Ein fiktiver Laden&lt;/h3&gt;&#10;&lt;p&gt;Überschlagen wir mal kurz mit ein paar angenommenen Werten:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Posten&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Preis/Einheit&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Verwendet&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Endpreis&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Laden&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16€/m² u. Monat&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;40m²&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;640€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lagerfläche&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4€/m² u. Monat&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8m²&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;32€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Werkstatt&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8€/m² u. Monat&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30m²&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;240€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Nebenkosten&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;pro Monat&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;200€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Abgaben&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;IHK / BG / Vers.&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;125€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gebühren&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20€/Monat&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;60€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Summe&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1297€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Personal&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1600€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Und würde ich in diesem Laden nun nochmal für 20h/Woche jemanden für den Verkauf anstellen, entstünden hier überschlagsweise 1600€ an Kosten.&lt;/p&gt;&#10;&lt;h3 id="summa-summarum"&gt;Summa summarum&lt;/h3&gt;&#10;&lt;p&gt;Ok, fassen wir zusammen: Ich gehe mit einem Aufschlag von 20% auf die Herstellungskosten ins Rennen. Als angenommener Kaufmann müsste ich allerdings Mehrwertsteuer abführen, die ich erneut aufzuschlagen hätte.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Ladenpreis = Herstellungspreis &#10; + Deckungsbeitrag (20%)&#10; + Mehrwertsteuer (19%)&#10; = (1,2 * 20,15€) * 1,19 = 28,77€&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Oha, damit liegen wir schon bei fast dreißig Euro pro Teil.&lt;/p&gt;&#10;&lt;p&gt;Wie viele Seifenbutler müssen also pro Monat über die Ladentheke gehen, bevor ich als Kaufmann mit Ladengeschäft Gewinn machen würde?&#10;Der Deckungsbeitrag liegt bei &lt;code&gt;4,03€&lt;/code&gt; (20% des Herstellungspreises). Wenn ich jetzt die durch Produktion und Verkauf erzeugten Kosten durch diesen Betrag teile, erhalte ich meine Mindestverkaufsmenge: &lt;code&gt;719 Stück/Monat&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Wie viele Wochenstunden müsste ich nun also mindestens an meiner CNC-Fräse stehen bzw. Seifenbutler zusammenbauen und verpacken?&#10;Nehme ich die Werte von oben, so liege ich bei insgesamt &lt;code&gt;8 + 5 + 2 = 15min/Teil&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Bei der Mindestverkaufsmenge würde mir das eine Wochenarbeitszeit von etwa &lt;code&gt;45h&lt;/code&gt; bescheren!&#10;Ach, 60h pro Woche gehen doch auch locker, oder?! Somit könnte ich 960 Stück pro Monat herstellen - und angenommen, sie verkaufen sich hervorragend, erzielte ich &lt;code&gt;971€&lt;/code&gt; extra zur Amortisation der Entwicklungskosten (Überschlag 250h*30€ = 7.500€). Knaller!&lt;/p&gt;&#10;&lt;p&gt;Ich würde dann mit der Unternehmung nach nur acht Monaten (mit je 60h/Woche, ist klar) in die Gewinnzone marschieren. Oh, oder muss man etwa noch Steuern entrichten? Und wann war noch gleich meine Maschine abbezahlt?&lt;/p&gt;&#10;&lt;h2 id="fazit-zur-wirtschaftlichkeit"&gt;Fazit zur Wirtschaftlichkeit&lt;/h2&gt;&#10;&lt;p&gt;Mir ist klar, dass die Rechnung nicht so einfach zu machen ist. Einige Parameter lassen sich nicht beliebig und linear hoch- oder herunterskalieren und z.B. die Maschinenkosten kann ich gar nicht realistisch abschätzen, weil mir schlicht Erfahrungswerte im Dauerbetrieb fehlen, Stichwort Abnutzung.&lt;/p&gt;&#10;&lt;p&gt;Dennoch genügt mir die meinerseits zu erbringende Arbeitszeit um festzuhalten:&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Das wird nix. Nettes Projekt, lohnt sich aber nicht.&lt;/strong&gt; Jedenfalls nicht aus Eigenmitteln und allein in Eigenregie. Es bleibt bei einer Nullserie und bei meiner Freude über ein gelungenes, funktionales Produkt.&lt;/p&gt;&#10;&lt;p&gt;Glücklicherweise ist das alles hier &amp;ldquo;nur mein Hobby&amp;rdquo;, sodass ich die desaströse Bilanz nicht gezwungen bin zu ziehen 😉&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Obacht. Meine Berufung ist nicht Händler oder Kaufmann, sondern das Ingenieur-dasein. Daher habe ich nur sehr rudimentäre Kenntnis von Betriebswirtschaft (dieses eine Semester damals im Studium eben, wo niemand so recht zuhören mochte). Alle meine Ausführungen hier sind Überschlagsrechnungen. Sie sollten also mit großer Vorsicht genossen werden. Ich freue mich allerdings sehr über Korrekturzuschriften und konstruktive Kritik!&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>'redir' im Caddyserver</title><link>https://blog.schallbert.de/migrating-to-subdomain/</link><pubDate>Tue, 30 Jan 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/migrating-to-subdomain/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-01-30-migrating-to-subdomain-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Thumbnail of correctly rendered &amp;#39;&amp;#39;website under construction&amp;#39;&amp;#39; page on schallbert.de"&#10; title="&amp;#39;redir&amp;#39; im Caddyserver" /&gt;&#10;&lt;p&gt;Als ich meine Website weg von Github Pages auf einen eigenen Server &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;umzog&lt;/a&gt;, hatte ich einige Probleme und offene Fragen bezüglich automatischer Weiterleitungen. Mein Blog ging nämlich von der root-Domain in eine Subdomain (blog.schallbert.de) über.&lt;/p&gt;&#10;&lt;h2 id="anforderungen-an-die-weiterleitung"&gt;Anforderungen an die Weiterleitung&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Bereits auf meine Blogartikel unter altem Namen referenzierte Links z.B. auf &lt;a href="https://blog.schallbert.de/about/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;schallbert.de/about&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sollen korrekt auf die Subdomain weiterleiten (kein &lt;em&gt;404&lt;/em&gt;)&lt;/li&gt;&#10;&lt;li&gt;Die Stammseite unter &lt;code&gt;schallbert.de&lt;/code&gt; soll vollständig nutzbar sein&lt;/li&gt;&#10;&lt;li&gt;Daten und Dateien für Domain und Subdomains sollen unabhängig voneinander sein&lt;/li&gt;&#10;&lt;li&gt;Die Webserver sollen für verschiedene Unterseiten unterschiedliche Technologien verwenden können&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="umsetzung"&gt;Umsetzung&lt;/h2&gt;&#10;&lt;p&gt;Zuerst hatte ich gedacht, dass ich die Weiterleitungen im DNS-provider über &lt;code&gt;A&lt;/code&gt; und &lt;code&gt;CNAME&lt;/code&gt; Einträge erreichen könnte. Schnell wurde mir jedoch klar, dass ich &lt;em&gt;a)&lt;/em&gt; keine Ahnung hatte, wie DNS überhaupt funktioniert und &lt;em&gt;b)&lt;/em&gt; der richtige Weg über die Konfiguration meines Webservers führt.&lt;/p&gt;&#10;&lt;h3 id="erster-versuch-redir-bei-"&gt;Erster Versuch: redir bei &amp;lsquo;/&amp;rsquo;&lt;/h3&gt;&#10;&lt;p&gt;Auf meinem Webserver (Caddy) werden Weiterleitungen in der &lt;code&gt;Caddyfile&lt;/code&gt; per &lt;code&gt;redir&lt;/code&gt; spezifiziert. Dabei kann das &lt;code&gt;permanent&lt;/code&gt; (als sogenannte http &lt;code&gt;301&lt;/code&gt;) oder &lt;code&gt;temporary&lt;/code&gt; per &lt;code&gt;302&lt;/code&gt; (Standardeinstellung) geschehen.&lt;/p&gt;&#10;&lt;p&gt;Mein erster Gedanke war, dass ich &lt;code&gt;schallbert.de&lt;/code&gt; unmodifiziert lasse und alles hinter einem eventuellen &lt;code&gt;/&lt;/code&gt; weiterleite. Dies sieht dann in der Caddyfile wie folgt aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;schallbert.de {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Define webserver&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;root * /www/landing&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encode gzip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;file_server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Redir to subdomain if domain has a slash&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@redirect path_regexp /*&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;redir @redirect https://blog.schallbert.de{uri}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In der Folge hat Caddy schlicht alles an die Subdomain weitergeleitet. Der Haken dabei war, dass die Subdomain selbst ja auch wieder Slashes bzw. eine URI hinter dem Slash enthält und dann wiederum weitergeleitet wird. Beispiel:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;schallbert.de/post -&amp;gt; blog.schallbert.de/post -&amp;gt; blog.schallbert.de/post -&amp;gt; blog.schallbert.de/post -&amp;gt; [...]&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Somit bekam ich eine rekursive Endlosweiterleitung. Ich habe meinen Server damit komplett lahmgelegt 😅&lt;/p&gt;&#10;&lt;h3 id="zweiter-versuch-filterung-per-regexp"&gt;Zweiter Versuch: Filterung per Regexp&lt;/h3&gt;&#10;&lt;p&gt;Ich brauche also eine bessere Filterung, sodass nur Slashes mit weiteren Zeichen dahinter auch wirklich weitergeleitet werden und die Weiterleitung nicht rekursiv ablaufen kann. Zum Glück hatte ich Hilfe von einem guten Freund, sodass kurze Zeit darauf folgendes &lt;code&gt;Caddyfile&lt;/code&gt; entstand:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;schallbert.de {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Define webserver&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;root * /www/landing&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encode gzip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;file_server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Redir to subdomain to maintain links for blog&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@redirect path_regexp ^/[^/]+(/.*)?$&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;redir @redirect https://blog.schallbert.de{uri}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die Regexp prüft ab, ob ein Slash hinter der Domain URL vorliegt und dahinter zumindest ein weiteres Zeichen folgt. Hier funktioniert die Weiterleitung nun wie gewünscht und mein Blog ist auch unter den alten Links weiterhin vollständig erreichbar.&lt;/p&gt;&#10;&lt;p&gt;Trotzdem war ich noch nicht ganz zufrieden. Denn als ich mir eine schöne &amp;ldquo;Landing Page&amp;rdquo; auf meiner Root Domain erstellte, war von ihr plötzlich nur noch das reine HTML zu sehen. Kein favicon, keine Bilder, kein CSS.&lt;/p&gt;&#10;&lt;p&gt;Ein kurzer Blick in die Entwickleroptionen brachte schnell die Erkenntnis dass die Pfade aller Assets, die ich auf meiner Landing Page hinterlegt hatte, durch die Ordnerstruktur mit &lt;code&gt;/&lt;/code&gt; gekennzeichnet, an die Subdomain weitergeleitet wurden. Wo sie natürlich nicht liegen.&lt;/p&gt;&#10;&lt;h3 id="dritter-versuch-regexp-plus-dateisuche"&gt;Dritter Versuch: Regexp plus Dateisuche&lt;/h3&gt;&#10;&lt;p&gt;Zum Glück bin ich wieder mal mit dem Problem nicht allein, sodass im &lt;a href="https://caddy.community/t/redirect-if-file-not-present/7902" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Forum von Caddy&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; die Lösung zu finden war:&lt;/p&gt;&#10;&lt;p&gt;Mit dem &lt;code&gt;not file&lt;/code&gt; Marker kann ich in der &lt;code&gt;Caddyfile&lt;/code&gt; Weiterleitungen definieren, falls Ressourcen auf der aktuellen Seite unauffindbar sind.&lt;/p&gt;&#10;&lt;p&gt;Somit sieht meine Lösung jetzt wie folgt aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;schallbert.de {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@filenotfound {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;path_regexp ^/[^/]+(/.*)?$&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;not file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# show Caddy where to find page resources&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;root * /www/landing&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encode gzip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;route {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Redirect to subdomain if article cannot found on root&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;redir @filenotfound https://blog.schallbert.de{uri}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# else, define webserver and show page&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;file_server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hier ist wichtig, dass ich die Seite mit &lt;code&gt;root * /www/&amp;lt;pagelocation&amp;gt;&lt;/code&gt; definiere, bevor ich die &lt;code&gt;route&lt;/code&gt; anlege. Denn sonst werden die Assets erst verfügbar, nachdem ich bereits aus Ermangelung vorhandener Dateien weitergeleitet habe.&lt;/p&gt;&#10;&lt;h2 id="erfolg"&gt;Erfolg!&lt;/h2&gt;&#10;&lt;p&gt;Jetzt endlich wird meine Baustellenseite korrekt dargestellt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-01-30-migrating-to-subdomain.jpg" alt="Image: Correctly rendered &amp;#39;website under construction&amp;#39; page on schallbert.de" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Automatische Updates für alles!</title><link>https://blog.schallbert.de/server-auto-upgrade/</link><pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/server-auto-upgrade/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-01-20-server-auto-upgrade-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: unattended-upgrades and Watchtower keep my server and application packages up to date."&#10; title="Automatische Updates für alles!" /&gt;&#10;&lt;p&gt;Wie der Titel schon sagt, möchte ich Sicherheitsupdates gern installiert haben, sobald sie erscheinen. Dies gilt gleichsam für das Ubuntu meines Cloud-Servers wie auch für alle Applikationen, die ich in Docker betreibe. Da ich faul bin, möchte ich diese Sicherheitsupdates nicht manuell vornehmen, sondern vollautomatisch ablaufen lassen.&lt;/p&gt;&#10;&lt;p&gt;Für Funktionsupdates und -Erweiterungen hingegen ist es auch weiterhin OK für mich, von Zeit zu Zeit manuell einzugreifen.&#10;Beginnen wir also mit den Systemupgrades.&lt;/p&gt;&#10;&lt;h2 id="sicherheitsupdates-per-unattended-upgrades"&gt;Sicherheitsupdates per &lt;code&gt;unattended-upgrades&lt;/code&gt;&lt;/h2&gt;&#10;&lt;p&gt;Ubuntu stellt - wie eine kurze &lt;a href="https://askubuntu.com/questions/9/how-do-i-enable-automatic-updates" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Suchmaschinenabfrage ergibt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; - bereits ein konfigurierbares Werkzeug für solche automatisierten Updates zur Verfügung: &lt;code&gt;unattended-upgrades&lt;/code&gt;. Ich prüfe zuerst, ob es bereits auf meinem System vorhanden ist:&lt;/p&gt;&#10;&lt;h3 id="installation"&gt;Installation&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert:~# which unattended-upgrades&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/unattended-upgrades&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Prima, es ist bereits da. Ansonsten hätte ich die Installation schnell mit &lt;code&gt;apt install unattended-upgrades&lt;/code&gt; nachgeholt.&lt;/p&gt;&#10;&lt;h3 id="unattended-upgrades-konfigurieren"&gt;unattended-upgrades konfigurieren&lt;/h3&gt;&#10;&lt;p&gt;Hier hilft das &lt;a href="https://wiki.debian.org/UnattendedUpgrades" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Debian Wiki&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; weiter: Die Konfigurationsdateien liegen in &lt;code&gt;/etc/apt/apt.conf.d&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Zuerst gehe ich sicher, dass automatische Updates aktiviert sind. Hierfür prüfe ich die Datei &lt;code&gt;20auto-upgrades&lt;/code&gt; und stelle sicher, dass Paketindex-Updates und Upgradeinstallation aktiviert sind:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;APT::Periodic::Update-Package-Lists &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;APT::Periodic::Unattended-Upgrade &lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sehr gut. Jetzt sollte ich noch festlegen, zu welchem Zeitpunkt das Tool loslaufen und welcher Typ Updates installiert werden soll. Hierfür öffne ich die Datei &lt;code&gt;50unattended-upgrades&lt;/code&gt; und entferne die Kommentare bei den entsprechenden Zeilen.&lt;/p&gt;&#10;&lt;h3 id="updatequellen-festlegen"&gt;Updatequellen festlegen&lt;/h3&gt;&#10;&lt;p&gt;Der erste Abschnitt der Konfigurationsdatei widmet sich der erlaubten Quellen für die Updates. Hier sollten selbstverständlich nur vertrauenswürdige Provider stehen. Meine Konfig sieht hier wie folgt aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Unattended-Upgrade::Allowed-Origins &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;distro_id&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;distro_codename&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;distro_id&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;distro_codename&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;-security&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;distro_id&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;ESMApps:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;distro_codename&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;-apps-security&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;distro_id&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;ESM:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;distro_codename&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;-infra-security&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Erlaubt werden hier Quellen für die von mir verwendete Linux-Distribution im Allgemeinen und Sicherheitsupdates inklusive &lt;code&gt;ESM&lt;/code&gt;, was für &amp;ldquo;Extended Security Maintenance&amp;rdquo; steht.&lt;/p&gt;&#10;&lt;h3 id="updates-auch-für-dev-releases"&gt;Updates auch für Dev Releases?&lt;/h3&gt;&#10;&lt;p&gt;Die folgende Zeile legt fest, ob auch Vorabversionen von Ubuntu Updates erhalten sollen. Bei mir steht der Wert auf &lt;code&gt;auto&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Unattended-Upgrade::DevRelease &lt;span style="color:#e6db74"&gt;&amp;#34;auto&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="automatischer-neustart"&gt;Automatischer Neustart&lt;/h3&gt;&#10;&lt;p&gt;Einige Sicherheitsupdates (z.B. den &lt;a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Kernel&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; betreffend) erfordern einen Neustart der Maschine. Sie werden daher nur in Kraft treten, wenn man in der Datei entsprechende Werte gesetzt hat. Dabei ist &lt;code&gt;WithUsers&lt;/code&gt; natürlich Geschmackssache, denn man wird ausgeloggt, falls zufälligerweise gerade ein Update installiert wurde, was einen Neustart erfordert. Um das praktisch auszuschließen, habe ich unter &lt;code&gt;Reboot-Time&lt;/code&gt; eine Zeit eingetragen, bei der ich sicher nicht an der Maschine hocke.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Unattended-Upgrade::Automatic-Reboot &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Unattended-Upgrade::Automatic-Reboot-WithUsers &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Unattended-Upgrade::Automatic-Reboot-Time &lt;span style="color:#e6db74"&gt;&amp;#34;03:45&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unangekündigte Neustarts haben natürlich Rückwirkung auf alle Programme, die ich verwende. Daher stelle ich in meinen &lt;code&gt;docker-compose.yml&lt;/code&gt; Dateien sicher, dass dort bei &lt;code&gt;restart:&lt;/code&gt; auch wirklich &lt;code&gt;always&lt;/code&gt; oder &lt;code&gt;unless-stopped&lt;/code&gt; notiert ist.&lt;/p&gt;&#10;&lt;h3 id="benachrichtigungen"&gt;Benachrichtigungen&lt;/h3&gt;&#10;&lt;p&gt;Automatische Mails oder anderweitige Push-Benachrichtigungen habe ich erst einmal nicht eingeschaltet, da ich mich noch im Testbetrieb befinde und keine große Lust auf einen Haufen weiterer Nachrichten habe. Sobald ich dies ändere, gibt es hier ganz bestimmt ein Update.&lt;/p&gt;&#10;&lt;h3 id="funktionsprüfung"&gt;Funktionsprüfung&lt;/h3&gt;&#10;&lt;p&gt;Um zu sehen, ob unattended-upgrades tut was es soll, schaue ich ein paar Tage später mal in die Logs unter &lt;code&gt;/var/log/unattended-upgrades&lt;/code&gt;.&#10;Hier steht in der Datei &lt;code&gt;unattended-upgrades.log&lt;/code&gt; zum Beispiel folgendes:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;06&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;59&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;781&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Starting&lt;/span&gt; unattended upgrades script&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;06&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;59&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;781&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Allowed&lt;/span&gt; origins &lt;span style="color:#e6db74"&gt;are&lt;/span&gt;: o&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;Ubuntu&lt;/span&gt;,a&lt;span style="color:#f92672"&gt;=&lt;/span&gt;jammy, o&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;Ubuntu&lt;/span&gt;,a&lt;span style="color:#f92672"&gt;=&lt;/span&gt;jammy&lt;span style="color:#f92672"&gt;-&lt;/span&gt;security, o&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;UbuntuESMApps&lt;/span&gt;,a&lt;span style="color:#f92672"&gt;=&lt;/span&gt;jammy&lt;span style="color:#f92672"&gt;-&lt;/span&gt;apps&lt;span style="color:#f92672"&gt;-&lt;/span&gt;security, o&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;06&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;59&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;781&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Initial&lt;/span&gt; &lt;span style="color:#e6db74"&gt;blacklist&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;06&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;59&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;782&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Initial&lt;/span&gt; whitelist (&lt;span style="color:#f92672"&gt;not&lt;/span&gt; strict):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;06&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;14&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;33&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;956&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Packages&lt;/span&gt; that will be &lt;span style="color:#e6db74"&gt;upgraded&lt;/span&gt;: libc&lt;span style="color:#f92672"&gt;-&lt;/span&gt;bin libc&lt;span style="color:#f92672"&gt;-&lt;/span&gt;dev&lt;span style="color:#f92672"&gt;-&lt;/span&gt;bin libc&lt;span style="color:#f92672"&gt;-&lt;/span&gt;devtools libc6 libc6&lt;span style="color:#f92672"&gt;-&lt;/span&gt;dev locales python3&lt;span style="color:#f92672"&gt;-&lt;/span&gt;twisted&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;06&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;14&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;33&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;956&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Writing&lt;/span&gt; dpkg log to &lt;span style="color:#e6db74"&gt;/var/&lt;/span&gt;log&lt;span style="color:#f92672"&gt;/&lt;/span&gt;unattended&lt;span style="color:#f92672"&gt;-&lt;/span&gt;upgrades&lt;span style="color:#f92672"&gt;/&lt;/span&gt;unattended&lt;span style="color:#f92672"&gt;-&lt;/span&gt;upgrades&lt;span style="color:#f92672"&gt;-&lt;/span&gt;dpkg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;06&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;14&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;54&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;578&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;All&lt;/span&gt; upgrades installed&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die Datei &lt;code&gt;unattended-upgrades-shutdown.log&lt;/code&gt; ist allerdings noch leer. Ich werde zu einem späteren Zeitpunkt prüfen, ob hier auch alles funktioniert.&lt;/p&gt;&#10;&lt;h2 id="container-updates-mit-watchtower-automatisieren"&gt;Container-Updates mit &lt;code&gt;Watchtower&lt;/code&gt; automatisieren&lt;/h2&gt;&#10;&lt;p&gt;Nun zu den Updates meiner Applikationen. Ein kurzes Gespräch mit ein paar Admins ergab:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Es ist mühsam, alle Programme von Hand auf Stand zu halten&lt;/li&gt;&#10;&lt;li&gt;Für Anwendungen im Container gibt es Dienste, die diese Aufgabe zentral lösen&lt;/li&gt;&#10;&lt;li&gt;Es gab bereits schlechte Erfahrungen, wenn in der &lt;code&gt;docker-compose.yml&lt;/code&gt; auf den aktuellsten Release referenziert wird&lt;/li&gt;&#10;&lt;li&gt;Bei &lt;code&gt;image: &amp;lt;application&amp;gt;:latest&lt;/code&gt; seien hier nicht alle Anwendungen stabil bzw. erzeugten Probleme mit Abhängigkeiten&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Einer Lösung für dieses Problem bietet &lt;a href="https://containrrr.dev/watchtower/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Watchtower&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, welches in meinem Falle selbst wieder in einem Container läuft.&lt;/p&gt;&#10;&lt;h3 id="installation-1"&gt;Installation&lt;/h3&gt;&#10;&lt;p&gt;Wie üblich schreibe ich mir in einem neuen Ordner unter &lt;code&gt;opt/watchtower&lt;/code&gt; eine &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# copied from https://containrrr.dev/watchtower/notifications/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# watchtower/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;watchtower&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;containrrr/watchtower&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Watchtower benötigt &lt;code&gt;docker.sock&lt;/code&gt;, um die Updates zu ziehen und auf den vorhandenen Containern anzuwenden.&#10;Ansonsten habe ich hier erst einmal nichts weiter unternommen. Keine Benachrichtigungen (aus obengenannten Gründen), keine Logs, nichts. Ich hoffe, dieses Setup bleibt geräuschlos und ich muss mich hier lange nicht mit einem &lt;em&gt;Update&lt;/em&gt; zu Wort melden.&lt;/p&gt;&#10;&lt;p&gt;Weitergehende Schritte für die Validierung der Funktion von Watchtower finden sich z.B. &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-automatically-update-docker-container-images-with-watchtower-on-ubuntu-22-04" target="_blank" rel="noopener noreferrer" class="external-link"&gt;in einem Tutorial von DigitalOcean&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="ablauf"&gt;Ablauf&lt;/h3&gt;&#10;&lt;p&gt;Watchtower prüft die Installierten Docker images &lt;a href="https://containrrr.dev/watchtower/introduction/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;lt. Doku täglich&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; auf Updates. Sollte des ein neues Image-release geben, schickt Watchtower ein &lt;code&gt;SIGTERM&lt;/code&gt;-Signal an upzudatende Container, worauf sich selbige herunterfahren (&lt;a href="https://containrrr.dev/watchtower/stop-signals/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Quelle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;). Anschließend werden die Container wieder hochgefahren.&lt;/p&gt;&#10;&lt;h3 id="unfreiwillige-funktionsprüfung"&gt;Unfreiwillige Funktionsprüfung&lt;/h3&gt;&#10;&lt;p&gt;Ein paar Tage nach Start des Containers wählte ich mich nochmal in meinen Server ein und sah auf gut Glück ein paar Logs durch. Ich wollte nur kurz nach dem Rechten schauen.&lt;/p&gt;&#10;&lt;p&gt;In den &lt;code&gt;fail2ban&lt;/code&gt; Logdateien sah ich zu einem bestimmten Zeitpunkt, Uhrzeit &lt;code&gt;14:42:56&lt;/code&gt; plötzlich seitenweise Einträge. Mehr davon, als ich die Tage vorher insgesamt gesehen hatte. Als ich zum Beginn dieser Kette von Einträgen hochscrollte, sah ich, dass fail2ban mir wohl kurz ausgestiegen war.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt; 2024-01-26 14:42:50,817 &amp;lt;container_id&amp;gt; INFO Shutdown in progress...&#10; 2024-01-26 14:42:50,817 &amp;lt;container_id&amp;gt; INFO Observer stop ... try to end queue 5 seconds&#10; 2024-01-26 14:42:50,837 &amp;lt;daemon_id&amp;gt; INFO Observer stopped, 0 events remaining.&#10; 2024-01-26 14:42:50,878 &amp;lt;container_id&amp;gt; INFO Stopping all jails&#10; 2024-01-26 14:42:50,879 &amp;lt;container_id&amp;gt; INFO Removed logfile: &amp;#39;/var/log/auth.log&amp;#39;&#10; 2024-01-26 14:42:50,879 &amp;lt;container_id&amp;gt; INFO Removed logfile: &amp;#39;/var/log/caddy2/gitea_access.log&amp;#39;&#10; 2024-01-26 14:42:50,879 &amp;lt;container_id&amp;gt; INFO Removed logfile: &amp;#39;/var/log/caddy2/server_access.log&amp;#39;&#10; 2024-01-26 14:42:51,052 &amp;lt;daemon_id&amp;gt; NOTIC [sshd] Flush ticket(s) with iptables&#10; 2024-01-26 14:42:51,063 &amp;lt;container_id&amp;gt; INFO Jail &amp;#39;sshd&amp;#39; stopped&#10; 2024-01-26 14:42:51,137 &amp;lt;daemon_id&amp;gt; NOTIC [caddy-status] Flush ticket(s) with iptables-multiport&#10; 2024-01-26 14:42:51,137 &amp;lt;container_id&amp;gt; INFO Jail &amp;#39;caddy-status&amp;#39; stopped&#10; 2024-01-26 14:42:51,138 &amp;lt;container_id&amp;gt; INFO Connection to database closed.&#10; 2024-01-26 14:42:51,139 &amp;lt;container_id&amp;gt; INFO Exiting Fail2ban&#10; 2024-01-26 14:42:56,173 &amp;lt;new_container_id&amp;gt; INFO --------------------------------------------------&#10; 2024-01-26 14:42:56,173 &amp;lt;new_container_id&amp;gt; INFO Starting Fail2ban v1.0.2&#10; 2024-01-26 14:42:56,173 &amp;lt;new_container_id&amp;gt; INFO Observer start...&#10;[...]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die ganzen Log-Einträge zur selben Uhrzeit beginnen am Ende des oben gezeigten Abschnitts. Ein ganzer Haufen IP-Adressen wird dort zur Vorbereitung des Banns in Fail2ban geladen.&#10;Leicht beunruhigt schaute ich ins Syslog von Ubuntu:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;Jan 26 14:42:55 schallbert systemd[1]: docker-&amp;lt;ID&amp;gt;.scope: Deactivated successful&#10;Jan 26 14:42:55 schallbert systemd[1]: docker-&amp;lt;ID&amp;gt;.scope: Consumed 21:05 CPU time&#10;Jan 26 14:42:55 schallbert dockerd[690]: time=&amp;#34;2024-01-26T14:42:55.228607617Z&amp;#34; level=info msg=&amp;#34;ignoring event&amp;#34; &#10;Jan 26 14:42:55 schallbert containerd[639]: time=&amp;#34;2024-01-26T14:42:55.229864632Z&amp;#34; level=info msg=&amp;#34;shim disconnected&amp;#34; &#10;Jan 26 14:42:55 schallbert containerd[639]: time=&amp;#34;2024-01-26T14:42:55.230125409Z&amp;#34; level=warning msg=&amp;#34;cleaning up after shim disconnected&amp;#34; i&amp;gt;&#10;Jan 26 14:42:55 schallbert containerd[639]: time=&amp;#34;2024-01-26T14:42:55.230145478Z&amp;#34; level=info msg=&amp;#34;cleaning up dead shim&amp;#34;&#10;Jan 26 14:42:55 schallbert containerd[639]: time=&amp;#34;2024-01-26T14:42:55.240586557Z&amp;#34; level=warning msg=&amp;#34;cleanup warnings time=\&amp;#34;2024-01-26T14:&amp;gt;&#10;Jan 26 14:42:55 schallbert dockerd[690]: time=&amp;#34;2024-01-26T14:42:55.241839655Z&amp;#34; level=warning msg=&amp;#34;ShouldRestart failed&#10;Jan 26 14:42:55 schallbert containerd[639]: time=&amp;#34;2024-01-26T14:42:55.870912387Z&amp;#34; level=info msg=&amp;#34;loading plugin \&amp;#34;io.containerd.event.v1.p&amp;gt;&#10;Jan 26 14:42:55 schallbert containerd[639]: time=&amp;#34;2024-01-26T14:42:55.870997689Z&amp;#34; level=info msg=&amp;#34;loading plugin \&amp;#34;io.containerd.internal.v&amp;gt;&#10;Jan 26 14:42:55 schallbert containerd[639]: time=&amp;#34;2024-01-26T14:42:55.871007368Z&amp;#34; level=info msg=&amp;#34;loading plugin \&amp;#34;io.containerd.ttrpc.v1.t&amp;gt;&#10;Jan 26 14:42:55 schallbert containerd[639]: time=&amp;#34;2024-01-26T14:42:55.871150831Z&amp;#34; level=info msg=&amp;#34;starting signal loop&amp;#34; namespace=moby path&amp;gt;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hm, &lt;code&gt;cleaning up dead shim&lt;/code&gt;. Das klingt ja bedrohlich. Eine kurze &lt;a href="https://iximiuz.com/en/posts/implementing-container-runtime-shim/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Recherche&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zeigt, dass die Shim eine Zwischenschicht zwischen Containermanager und dem Container selbst bildet und Ein-und Ausgaben des Containers weiterleitet. Ihre Laufzeit ist an die des Containers gekoppelt.&lt;/p&gt;&#10;&lt;p&gt;Also war dies ein normales Herunterfahren mit anschließendem Neustart. Zum Glück kein Einbruch!&#10;Bleibt nur noch der Grund für den Neustart von Fail2ban herauszufinden. Da fällt mir ein, dass ich noch gar nicht weiß, wann Watchtower genau die Updates fährt&amp;hellip;&lt;/p&gt;&#10;&lt;p&gt;Also mal schnell in die Logs von Watchtower geschaut mit &lt;code&gt;docker container logs &amp;lt;container_id&amp;gt;&lt;/code&gt; und siehe da:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;time=&amp;#34;2024-01-26T14:42:47Z&amp;#34; level=info msg=&amp;#34;Found new lscr.io/linuxserver/fail2ban:latest image (9bda077d765e)&amp;#34;&#10;time=&amp;#34;2024-01-26T14:42:50Z&amp;#34; level=info msg=&amp;#34;Stopping /fail2ban (e0317a105d53) with SIGTERM&amp;#34;&#10;time=&amp;#34;2024-01-26T14:42:55Z&amp;#34; level=info msg=&amp;#34;Creating /fail2ban&amp;#34;&#10;time=&amp;#34;2024-01-26T14:42:55Z&amp;#34; level=info msg=&amp;#34;Session done&amp;#34; Failed=0 Scanned=6 Updated=1 notify=no&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Damit habe ich den Beweis: Watchtower tut was es soll. Es zieht Container auf eine neue Version hoch, sobald Updates verfügbar werden. Prima!&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;PS:&lt;/strong&gt; Da ich Watchtower nicht groß konfiguriert habe, nehme ich anhand der in den Logs befindlichen Zeitstempel an, dass die Updates täglich zur Zeit des Starts von Watchtower gefahren werden. Bald werde ich von &lt;code&gt;latest&lt;/code&gt; für meine Container wohl auf ein älteres Tag release umschwenken.&lt;/p&gt;&#10;</description></item><item><title>Den Server absichern</title><link>https://blog.schallbert.de/server-protection/</link><pubDate>Fri, 12 Jan 2024 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/server-protection/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-01-12-server-protection-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: side-by-side image of fail2ban and borgmatic logos"&#10; title="Den Server absichern" /&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;OK. Nun läuft mein Server, &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/"&gt;baut automatisch&lt;/a&gt; meinen Blog und &lt;a href="https://blog.schallbert.de/self-hosted-jekyll-page-broken-links/"&gt;zeigt ihn auch problemlos an&lt;/a&gt;. So ganz fertig bin ich aber noch nicht. Denn ich könnte mehr für die Sicherheit des Servers tun, als überall Passwort-Logins zu deaktivieren.&lt;/p&gt;&#10;&lt;p&gt;Außerdem habe ich noch keine Backups. Und das ist ja nie gut. Also, packen wir&amp;rsquo;s an!&lt;/p&gt;&#10;&lt;h2 id="unerwünschte-besucher-aussperren"&gt;Unerwünschte Besucher aussperren&lt;/h2&gt;&#10;&lt;p&gt;Ich bekomme in meinem SSH-Log im Root-Account des Servers einen Haufen Verbindungsversuche, die nicht von mir kommen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;19&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;29&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93842&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Invalid&lt;/span&gt; user admin from &lt;span style="color:#ae81ff"&gt;41&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;207&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;248&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;204&lt;/span&gt; port &lt;span style="color:#ae81ff"&gt;37194&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;19&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;29&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93842&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: pam_unix(&lt;span style="color:#e6db74"&gt;sshd&lt;/span&gt;:auth): check pass; user unknown&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;19&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;29&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93842&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: pam_unix(&lt;span style="color:#e6db74"&gt;sshd&lt;/span&gt;:auth): authentication failure; logname&lt;span style="color:#f92672"&gt;=&lt;/span&gt; uid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; euid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; tty&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ssh ru&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;19&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;31&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93842&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Failed&lt;/span&gt; password &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; invalid user admin from &lt;span style="color:#ae81ff"&gt;41&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;207&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;248&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;204&lt;/span&gt; port &lt;span style="color:#ae81ff"&gt;37194&lt;/span&gt; ssh2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;19&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93842&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Connection&lt;/span&gt; closed by invalid user admin &lt;span style="color:#ae81ff"&gt;41&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;207&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;248&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;204&lt;/span&gt; port &lt;span style="color:#ae81ff"&gt;37194&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;preauth&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93886&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Invalid&lt;/span&gt; user svn from &lt;span style="color:#ae81ff"&gt;84&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;108&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;40&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;27&lt;/span&gt; port &lt;span style="color:#ae81ff"&gt;44968&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93886&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: pam_unix(&lt;span style="color:#e6db74"&gt;sshd&lt;/span&gt;:auth): check pass; user unknown&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93886&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: pam_unix(&lt;span style="color:#e6db74"&gt;sshd&lt;/span&gt;:auth): authentication failure; logname&lt;span style="color:#f92672"&gt;=&lt;/span&gt; uid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; euid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; tty&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ssh ru&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93886&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Failed&lt;/span&gt; password &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; invalid user svn from &lt;span style="color:#ae81ff"&gt;84&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;108&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;40&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;27&lt;/span&gt; port &lt;span style="color:#ae81ff"&gt;44968&lt;/span&gt; ssh2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93886&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Received&lt;/span&gt; disconnect from &lt;span style="color:#ae81ff"&gt;84&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;108&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;40&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;27&lt;/span&gt; port &lt;span style="color:#ae81ff"&gt;44968&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Bye&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Bye&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;preauth&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Jan&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;00&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; sshd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;93886&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Disconnected&lt;/span&gt; from invalid user svn &lt;span style="color:#ae81ff"&gt;84&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;108&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;40&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;27&lt;/span&gt; port &lt;span style="color:#ae81ff"&gt;44968&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;preauth&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wenn ich nicht wüsste, dass dies das inzwischen übliche &amp;ldquo;Rauschen&amp;rdquo; im Internet ist, würde es mich schon etwas beunruhigen. Ist ja fast so, als würde alle paar Sekunden jemand mit bösen Absichten versuchen, irgendeinen Schlüssel an meiner Haustür auszuprobieren. Was kann man dagegen also tun? Wegschicken!&lt;/p&gt;&#10;&lt;h3 id="fail2ban"&gt;fail2ban&lt;/h3&gt;&#10;&lt;p&gt;Genau dies soll die Software &lt;a href="https://github.com/fail2ban/fail2ban" target="_blank" rel="noopener noreferrer" class="external-link"&gt;fail2ban&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für mich übernehmen.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt; __ _ _ ___ _ &#10; / _|__ _(_) |_ ) |__ __ _ _ _ &#10; | _/ _` | | |/ /| &amp;#39;_ \/ _` | &amp;#39; \ &#10; |_| \__,_|_|_/___|_.__/\__,_|_||_|&#10; v1.1.0.dev1 20??/??/??&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Vereinfacht gesagt durchforstet &lt;code&gt;fail2ban&lt;/code&gt; Zugriffslogs&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; nach IP-Adressen, für die fehlgeschlagene Anmeldeversuche registriert wurden, und &amp;ldquo;bannt&amp;rdquo; sie bei Überschreiten einer benutzerdefinierten Anzahl Versuche innerhalb einer bestimmten Zeit für einen gewünschten Zeitraum.&lt;/p&gt;&#10;&lt;p&gt;Wie dieser Bannspruch umgesetzt wird? Fail2ban modifiziert die &lt;a href="https://en.wikipedia.org/wiki/Iptables" target="_blank" rel="noopener noreferrer" class="external-link"&gt;iptables&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, greift also auf Paketfilterregeln zu (Stichwort Firewall), welche sich unten auf der Netzwerkschicht befinden. So werden hereinkommende Anfragen bereits geblockter Adressen gar nicht erst bis zu meinen Applikationen durchkommen&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#10;&lt;h3 id="fail2ban-installieren"&gt;fail2ban installieren&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;fail2ban&lt;/code&gt; scheint so etwas wie Industriestandard bei der Abwehr unerwünschter Zugriffsversuche auf Linux zu sein. Jedem Hobby- und Profiadmin den ich kenne war das Programm geläufig. Ich erntete für meine Unwissenheit des Öfteren ein müdes Lächeln.&lt;/p&gt;&#10;&lt;p&gt;Zu Installation und Konfiguration gibt es bereits einen Haufen Anleitungen da draußen plus der (sehr gut geschriebenen), die im Repo von Fail2ban gleich mitgeliefert wird. Daher gehe ich nicht besonders tief hierauf ein.&lt;/p&gt;&#10;&lt;p&gt;Ich entschied mich für die Installation im Docker-Container, damit ich die üblichen Abhängigkeiten gleich mitgeliefert bekomme. Dafür verwende ich die open-source Distribution von &lt;a href="https://docs.linuxserver.io/images/docker-fail2ban/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;linuxserver&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und verfasse die folgende &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /fail2ban/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2.1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;fail2ban&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;lscr.io/linuxserver/fail2ban:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;fail2ban&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cap_add&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;NET_ADMIN&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;NET_RAW&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;network_mode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;host&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;PUID=1000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;PGID=1000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TZ=Etc/UTC&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;VERBOSITY=-vv&lt;/span&gt; &lt;span style="color:#75715e"&gt;#optional&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./config:/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/log/auth.log:/var/log/auth.log:ro&lt;/span&gt; &lt;span style="color:#75715e"&gt;# host ssh&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/log/caddy2:/var/log/caddy2:ro &lt;/span&gt; &lt;span style="color:#75715e"&gt;# gitea via caddy, caddyserver&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Das Einzige, was hier zu beachten gilt: &lt;code&gt;fail2ban&lt;/code&gt; benötigt die Access logs per Volume zur Verfügung gestellt (oben als &lt;code&gt;:ro&lt;/code&gt; nur mit Lesezugriff angegeben). Eine Menge Filterregeln liegen bereits vorkonfiguriert im &lt;code&gt;config&lt;/code&gt;-Ordner, daher muss ich nur die &lt;code&gt;jail.local&lt;/code&gt; &lt;a href="https://github.com/linuxserver/fail2ban-confs" target="_blank" rel="noopener noreferrer" class="external-link"&gt;nach &lt;code&gt;linuxserver/fail2ban-confs&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sowie &lt;code&gt;filter.d&lt;/code&gt; für Caddy &lt;a href="https://muetsch.io/how-to-integrate-caddy-with-fail2ban.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;nach muetsch.io&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; anpassen und schon habe ich einen automatischen Türwächter.&lt;/p&gt;&#10;&lt;h3 id="fail2ban-beispiel"&gt;fail2ban Beispiel&lt;/h3&gt;&#10;&lt;p&gt;So sieht ein fail2ban-Log nun für meinen SSH-Daemon aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# schallbert:/opt/fail2ban/config/log/fail2ban# grep &amp;#34;220.124.89.47&amp;#34; fail2ban.log &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;600&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;FBB3630BB38&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sshd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Found&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;220&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;124&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;89&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;47&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;23&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;003&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;FBB3630BB38&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sshd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Found&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;220&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;124&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;89&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;47&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;22&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;25&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;205&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;FBB3630BB38&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sshd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Found&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;220&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;124&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;89&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;47&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;27&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;206&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;FBB3630BB38&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sshd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Found&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;220&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;124&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;89&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;47&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;26&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;610&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;FBB3630BB38&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INFO&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sshd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Found&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;220&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;124&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;89&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;47&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2024&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;01&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;17&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;33&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;045&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;FBB36104B38&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOTIC&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sshd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Ban&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;220&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;124&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;89&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;47&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und tschüss!&lt;/p&gt;&#10;&lt;h3 id="was-noch-nicht-funktioniert-gitea--fail2ban"&gt;Was (noch) nicht funktioniert: Gitea &amp;amp; fail2ban&lt;/h3&gt;&#10;&lt;p&gt;Ich bekomme auch auf meiner Gitea-Instanz ssh-Anfragen rein, die ich ebenfalls gern wegblocken möchte. Allerdings kann ich Gitea bis jetzt partout nicht dazu bekommen, die Logs dafür auch in eine Datei zu schreiben. Bis jetzt werden die stets an die Konsole geschickt, wo ich sie fail2ban natürlich nicht zuführen kann und möchte. Tatsächlich wird die Verbindung zu Gitea per SSH zum Glück bereits gesichert, da sie über denselben &lt;code&gt;sshd&lt;/code&gt; geht wie der Shell-Zugriff auf meinen Server selbst.&lt;/p&gt;&#10;&lt;p&gt;Was mir noch fehlt sind nur die Überwachung fehlgeschlagener Logins auf dem Web-Frontend.&lt;/p&gt;&#10;&lt;p&gt;Dabei sieht Giteas &lt;code&gt;app.ini&lt;/code&gt; für mich sauber aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/conf/app.ini&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[log]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;MODE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;LEVEL&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;warn&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ROOT_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLE_ACCESS_LOGS&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLE_SSH_LOG&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;logger.access.MODE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;access-file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[log.access-file]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;MODE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ACCESS&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;LEVEL&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;info&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;FILE_NAME&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;access.log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ich habe sowohl die Access-Logs aktiviert &lt;code&gt;ENABLE_ACCESS_LOGS&lt;/code&gt; und den Access-Logger in eine Datei schreiben lassen. Die Logs werden auch erstellt, nur sind da keine Zugriffe drin aufgelistet - diese gehen nach wie vor in die Konsole des Containers. Ich bin mir aber noch unsicher, ob der Reverse-Proxy von Caddy hier vielleicht einen Einfluss hat und er zum Beispiel die Zugriffe vor Gitea bereits abfängt. Aber das finde ich schon irgendwann noch heraus.&lt;/p&gt;&#10;&lt;h3 id="update-aug-2024"&gt;Update Aug-2024&lt;/h3&gt;&#10;&lt;p&gt;Irgendwann fragte ich mich, ob &lt;em&gt;fail2ban&lt;/em&gt; auch tatsächlich auf den iptables meines Servers arbeitet oder nur innerhalb des Containers korrekt funktioniert. Zum Glück war ich &lt;a href="https://stackoverflow.com/questions/59996070/how-to-understand-if-the-fail2ban-ssh-filter-is-working-with-a-new-port" target="_blank" rel="noopener noreferrer" class="external-link"&gt;nicht der Erste mit dieser Frage (stackoverflow)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und fand die vorgeschlagene Lösung ganz charmant:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;im Docker-Container eine beliebige IP sperren (&lt;code&gt;docker exec -it fail2ban sh&lt;/code&gt;): &lt;code&gt;fail2ban-client set sshd banip 111.111.111.111&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;In den iptables nachsehen, ob diese IP dort auftaucht: &lt;code&gt;iptables -n -L --line-numbers&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;In meinem Falle: freuen, denn sie ist da: &lt;code&gt;1 REJECT all -- 111.111.111.111&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Die IP wieder entsperren &lt;code&gt;fail2ban-client set sshd unbanip 111.111.111.111&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="update-jul-2025"&gt;Update Jul-2025&lt;/h3&gt;&#10;&lt;p&gt;Ich habe jetzt eine Lösung für &lt;em&gt;Gitea&lt;/em&gt;, aber auch alle anderen meiner Websites zustande gebracht:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schutz vor Brute-Force Angriffen per &lt;code&gt;ssh&lt;/code&gt; wie oben beschrieben&lt;/li&gt;&#10;&lt;li&gt;Schutz vor Überlast mittels &lt;a href="https://blog.schallbert.de/fail2ban-with-caddy/"&gt;Rate Limiter&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Schutz vor Angriffen auf die APIs (&lt;code&gt;404/403&lt;/code&gt; Attacken) mittels &lt;code&gt;caddy-status&lt;/code&gt; Konfiguration für fail2ban, Logs in &lt;code&gt;JSON&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /config/fail2ban/filter.d/caddy-status.conf&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# this regex works for caddy with json-style logs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;failregex = &amp;#34;client_ip&amp;#34;:&amp;#34;&amp;lt;HOST&amp;gt;&amp;#34;(.*)&amp;#34;status&amp;#34;:(400|401|403|404|500)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;datepattern = \d+&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;ignoreregex =&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="regelmäßige-backups"&gt;Regelmäßige Backups&lt;/h2&gt;&#10;&lt;p&gt;Ein wichtiger Aspekt für mich ist die Möglichkeit, den Server wiederherstellen zu können. Sollten unvorhergesehene Ereignisse eintreten wie ein Update einer Komponente welches die Funktion anderer Programme beeinträchtigt, der Ausfall des Servers, oder sogar der Zugriff fremder Personen auf selbigen - in jedem Falle könnte ich mit einem Backup recht schnell eine neue Instanz des Servers erzeugen, konfigurieren und die Website wieder zum Laufen bringen.&lt;/p&gt;&#10;&lt;p&gt;Diese Updates möchte ich aber ungern selbst von Hand aus meinen Ordnern erzeugen, komprimieren und per &lt;code&gt;SCP / SFTP&lt;/code&gt; herunterladen. Besser soll das vollautomatisch vonstatten gehen, und das im Optimalfall noch kostenfrei. Nach einer kurzen Recherche stellen sich für meine Zwecke die quelloffenen Werkzeuge &lt;a href="https://torsion.org/borgmatic/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;borgmatic&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und &lt;a href="https://restic.net/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;restic&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; als geeignet heraus.&lt;/p&gt;&#10;&lt;p&gt;Ich entscheide mich willkürlich für Borgmatic.&lt;/p&gt;&#10;&lt;h3 id="borgmatic-in-docker-installieren"&gt;Borgmatic in Docker installieren&lt;/h3&gt;&#10;&lt;p&gt;Wie bei allen anderen Komponenten auch, möchte ich Borgmatic in einem Container laufen lassen. Glücklicherweise gibt es da bereits &lt;a href="https://hub.docker.com/r/b3vis/borgmatic/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;eine fertige Lösung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, die ich nur noch ein wenig konfigurieren muss.&lt;/p&gt;&#10;&lt;p&gt;Mein &lt;code&gt;docker-compose&lt;/code&gt; File für Borgmatic sieht wie folgt aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /borgmatic/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;borgmatic&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ghcr.io/borgmatic-collective/borgmatic&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;borgmatic&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${VOLUME_SOURCE}:/mnt/source:ro &lt;/span&gt; &lt;span style="color:#75715e"&gt;# backup source&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${VOLUME_TARGET}:/mnt/repository &lt;/span&gt; &lt;span style="color:#75715e"&gt;# backup target&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${VOLUME_ETC_BORGMATIC}:/etc/borgmatic.d/ &lt;/span&gt; &lt;span style="color:#75715e"&gt;# borgmatic config file(s) + crontab.txt&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${VOLUME_BORG_CONFIG}:/root/.config/borg &lt;/span&gt; &lt;span style="color:#75715e"&gt;# config and keyfiles&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${VOLUME_SSH}:/root/.ssh &lt;/span&gt; &lt;span style="color:#75715e"&gt;# ssh key for remote repositories&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;${VOLUME_BORG_CACHE}:/root/.cache/borg &lt;/span&gt; &lt;span style="color:#75715e"&gt;# checksums used for deduplication&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - /var/run/docker.sock:/var/run/docker.sock # add docker sock so borgmatic can start/stop containers to be backupped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;TZ=${TZ}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;BORG_PASSPHRASE=${BORG_PASSPHRASE}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Als Inspiration hierfür habe ich reichlich in der &lt;a href="https://github.com/borgmatic-collective/docker-borgmatic/blob/master/README.md" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dokumentation auf Github&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gestöbert.&lt;/p&gt;&#10;&lt;h3 id="borgmatic-konfigurieren"&gt;Borgmatic konfigurieren&lt;/h3&gt;&#10;&lt;p&gt;Alle konkreten Daten (die Angaben in &lt;code&gt;${}&lt;/code&gt;) habe ich zur besseren Übersichtlichkeit in einer &lt;code&gt;.env&lt;/code&gt;-Datei im selben Verzeichnis abgelegt. Ganz nebenbei verhindere ich, aus Versehen Passphrases zu veröffentlichen. Diese Passphrase habe ich mir zusätzlich auf einem Zettel notiert - man weiß ja nie, ob man sie nochmal benötigt.&lt;/p&gt;&#10;&lt;p&gt;Ich muss anschließend nur noch die sich in dem Ordner &lt;code&gt;borgmatic.d/&lt;/code&gt; befindliche &lt;code&gt;config.yml&lt;/code&gt; leicht verändern, indem ich Backup-Quelle, -Ziel und die von mir gewünschten Zeitintervalle für die Sicherungserstellung eintrug und war quasi schon bereit für einen ersten Test.&lt;/p&gt;&#10;&lt;h3 id="borgmatic-funktionstest"&gt;Borgmatic Funktionstest&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec borgmatic bash -c &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;cd &amp;amp;&amp;amp; borgmatic --stats -v 1 --files 2&amp;gt;&amp;amp;1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mit diesem Befehl führe ich über Docker im Container &lt;code&gt;borgmatic&lt;/code&gt; den Befehl zum Anlegen einer Sicherung an, um die Funktion von Borgmatic zu verifizieren.&lt;/p&gt;&#10;&lt;p&gt;Hier kam dann direkt eine Fehlermeldung à la &lt;code&gt;repository does not exist&lt;/code&gt; zurück. Also existiert das Backup-Ziel noch gar nicht. Ein kurzer Blick in die Dokumentation zeigt, dass das Repository erst initialisiert werden muss.&lt;/p&gt;&#10;&lt;p&gt;Dies hole ich mit dem Kommando&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec borgmatic bash -c &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;borgmatic init --encryption repokey-blake2&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;nach, was mit ein &amp;ldquo;leeres&amp;rdquo;, verschlüsseltes Repository anlegt.&#10;Versuche ich erneut, ein Backup zu erzeugen, läuft Borgmatic nun durch und erzeugt mir das Backup im Repository.&lt;/p&gt;&#10;&lt;h3 id="ein-backup-ist-nur-ein-backup"&gt;Ein Backup ist nur ein Backup&amp;hellip;&lt;/h3&gt;&#10;&lt;p&gt;&amp;hellip;wenn man es einspielen kann, sagt mein Kumpel.&lt;/p&gt;&#10;&lt;p&gt;Er hat Recht, aber so richtig traue ich mich nicht, über meine funktionierende Serverkonfiguration drüberzubügeln. Also erstelle ich mir eine &lt;code&gt;docker-compose.restore.yml&lt;/code&gt; Datei nach &lt;a href="https://github.com/borgmatic-collective/docker-borgmatic/blob/master/docker-compose.restore.yml" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Vorbild auf dem Repository&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und ziehe &lt;a href="https://www.modem7.com/books/docker-backup/page/backup-docker-using-borgmatic" target="_blank" rel="noopener noreferrer" class="external-link"&gt;modem7s Anleitung zum Thema&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; hinzu.&lt;/p&gt;&#10;&lt;p&gt;Tatsächlich kann ich durch Ausführen der &lt;code&gt;docker-compose.restore.yml&lt;/code&gt; dann in der Container-Shell folgende Kommandos ausführen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir backuprestoremount&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;borg mount /mnt/repository /backuprestoremount&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir backuprestore&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;borgmatic extract --archive latest --destination /backuprestore&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hier erstelle ich den Ordner &lt;code&gt;backuprestoremount&lt;/code&gt; und lasse ihn von &lt;code&gt;borg&lt;/code&gt; auf mein Backup zeigen. Anschließend extrahiere ich das Backup in den ebenfalls neu erstellten Ordner &lt;code&gt;backuprestore&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Nun prüfe ich, ob auch &amp;ldquo;alles da ist&amp;rdquo;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# cd /mnt/backuprestore&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /restore/mnt/source ls&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;borgmatic caddy2 containerd fail2ban gitea hostedtoolcache watchtower&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yeah, alle Applikationen sind vorhanden und die enthaltenen Daten sind gesichert! Weiter gehe ich jetzt mal nicht, denn ich bin zu feige (und zu faul), die auf dem Server unter &lt;code&gt;/opt&lt;/code&gt; befindlichen Container zu überschreiben.&lt;/p&gt;&#10;&lt;h3 id="was-noch-nicht-funktioniert-borgmatic--docker-compose-down"&gt;Was (noch) nicht funktioniert: Borgmatic &amp;amp; docker-compose down&lt;/h3&gt;&#10;&lt;p&gt;Borgmatic bietet eine einfache Möglichkeit in der Konfigurationsdatei, Aktionen vor- und nach dem Backup auszuführen.&lt;/p&gt;&#10;&lt;p&gt;Da ich die Konsistenz der Datenbanken meiner zu sichernden Container sicherstellen möchte, sollten die Container zum Zeitpunkt des Backups heruntergefahren sein. Daher schrieb ich mir ein Skript, was alle Applikationen vor dem Backup per &lt;code&gt;docker compose down&lt;/code&gt; stoppt und nach dem Backup wieder hochfährt.&lt;/p&gt;&#10;&lt;p&gt;Dies würde auch alles prima funktionieren, wenn Borgmatic nicht selbst in einem Container liefe. Durch die Kapselung bin ich nun nicht in der Lage, die im Stamm-Dateisystem der Maschine befindlichen Skripte auszuführen. Kurzzeitig dachte ich, dass ich dies mittels Durchreichung von &lt;code&gt;var/run/docker.sock&lt;/code&gt; in den Borgmatic-Container umgehen könne, scheiterte dann aber an der Ankopplung an Docker compose.&lt;/p&gt;&#10;&lt;p&gt;Ich bin mir sicher, dass das zu lösen ist. Allerdings habe ich gerade dringendere Themen. Daher begnüge ich mich mit der Annahme, dass schon nichts passiert, wenn ich das Backup nachts ziehe&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#10;&lt;p&gt;Auch bei den Auto-Backups scheint es noch zu haken:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;/ # borgmatic --stats&#10;Starting a backup job.&#10;Failed to create/acquire the lock /mnt/repository/lock (timeout).&#10;local: Error running actions for repository&#10;Command &amp;#39;borg create --stats /mnt/repository::{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f} /etc/borgmatic.d/config.yml /mnt/source /root/.borgmatic&amp;#39; returned non-zero exit status 2.&#10;Error while creating a backup.&#10;/etc/borgmatic.d/config.yml: An error occurred&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dies entnehme ich der über Docker aufgerufenen Log-Konsole von Borgmatic. Ich muss also beizeiten nochmal um meine Backups kümmern.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Beim SSH-Daemon meines Servers liegen die Zugriffslogs z.B. in &lt;code&gt;/var/log/auth.log&lt;/code&gt;, können aber auch als &lt;code&gt;access.log&lt;/code&gt; oder ähnlich abgelegt werden. Auch von mir verwendete Dienste wie Gitea und Caddy legen solche Logdateien an.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;Was ich allerdings noch nicht verstehe ist, wie &lt;code&gt;fail2ban&lt;/code&gt; im Container laufend überhaupt an die iptables herankommt. Ich dachte, dass ein positiver Nebeneffekt der Containerisierung in der Kapselung liegt. Oder ist sie nur bei &amp;ldquo;rootless&amp;rdquo;-Containern zu erreichen?&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:3"&gt;&#10;&lt;p&gt;Hintergrund: Ich bin zur Zeit der Einzige, der Inhalte auf &lt;code&gt;gitea&lt;/code&gt; hochlädt. Meine Webseite ist statisch. Auf dem Webserver &lt;code&gt;caddy&lt;/code&gt; ändern sich also nur dann Dateien, wenn ich sie über Gitea hochlade. Programmupdates über &lt;code&gt;watchtower&lt;/code&gt; und &lt;code&gt;unattended-upgrades&lt;/code&gt; &lt;a href="https://blog.schallbert.de/server-auto-upgrade/"&gt;(Thema eines weiteren Artikels)&lt;/a&gt; habe ich so gelegt, dass sie nicht während des Durchlaufs von &lt;code&gt;borgmatic&lt;/code&gt; stattfindet. Einzig die Logs, welche bei &lt;code&gt;fail2ban&lt;/code&gt; aufschlagen, werden auch zur Zeit der Erstellung des Backups geschrieben. Das Risiko des Datenverlustes gehe ich an der Stelle bewusst aber ein, da die Paketfilterung für geblockte IP-Adressen durch fail2ban nach einer von mir bestimmten Zeit sowieso wieder aufgehoben wird.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Jekyll build: defekte relative Links</title><link>https://blog.schallbert.de/self-hosted-jekyll-page-broken-links/</link><pubDate>Fri, 29 Dec 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/self-hosted-jekyll-page-broken-links/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-12-29_serves_broken_links-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Webpage, displaying Schallbert&amp;#39;&amp;#39;s Blog with broken links"&#10; title="Jekyll build: defekte relative Links" /&gt;&#10;&lt;h2 id="das-problem"&gt;Das Problem&lt;/h2&gt;&#10;&lt;p&gt;Jetzt bin ich nach &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/"&gt;ewigem Probieren&lt;/a&gt; nun endlich in der Lage, meinem Caddyserver die gebauten Dateien per Docker-Volume zur Verfügung zu stellen. Leider sieht die Seite aber wie folgt aus:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-12-29_serves_broken_links.jpg" alt="Image: Broken webpage, displaying Schallbert&amp;#39;s Blog without pictures, and other media, and any links lead to 404-nowhere" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="die-analyse"&gt;Die Analyse&lt;/h2&gt;&#10;&lt;p&gt;Zuerst hatte ich den Webserver in Verdacht.&#10;Wenn ich die Website aber testweise in meinem Browser aufrufe&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, zeigt sich dasselbe Verhalten. Merkwürdig.&lt;/p&gt;&#10;&lt;p&gt;Offensichtlich fehlen sämtliche Stylesheets. Sieht man genau hin, sind aber auch Bilder und andere Medien nicht geladen worden, sondern lediglich ihre Beschreibung.&#10;Dann klicke ich wild auf ein paar Links zu Artikeln und Unterseiten. Dabei fällt auf, dass die Pfade anders aussehen, als ich erwartet hätte:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# --- Erwartung ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Bilddatei&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;/assets/images/test.jpg&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Artikel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;/this-is-a-post&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Stattdessen sehe ich:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# --- Beobachtung ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Bilddatei&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;/pages/&amp;lt;username&amp;gt;/&amp;lt;repositoryname&amp;gt;/assets/images/test.jpg&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Artikel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;/pages/&amp;lt;username&amp;gt;/&amp;lt;repositoryname&amp;gt;/this-is-a-post&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dies zeigen auch die mit &lt;code&gt;F12&lt;/code&gt; zugeschalteten Entwickleroptionen des Browsers (siehe Bild oben): sämtliche per relativen Link eingebundene Quellen können nicht geladen werden. Übrigens ist es im Allgemeinen eine klare Empfehlung, die Entwickleroptionen bei Problemen mit der Darstellung von Webseiten aktiviert zu haben.&lt;/p&gt;&#10;&lt;p&gt;Liegt ja irgendwie nahe, dass meine Jekyll-Konfigurationsdatei &lt;code&gt;_config.yml&lt;/code&gt; oder meine &lt;a href="https://blog.schallbert.de/jekyll-polyglot-language-support/"&gt;Übersetzungsheader&lt;/a&gt; &lt;code&gt;i10.yml&lt;/code&gt; Fehler enthalten. Dafür gibt es zum Glück bereits hervorragende &lt;a href="https://mademistakes.com/mastering-jekyll/site-url-baseurl/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hilfeseiten&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Ein Test zeigt jedoch schnell, dass ich die Angabe &lt;code&gt;repository&lt;/code&gt;, woher der Build &lt;code&gt;&amp;lt;username&amp;gt;/&amp;lt;repositoryname&amp;gt;&lt;/code&gt; herbezieht, nicht einfach leer lassen kann:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# bundle exec jekyll serve&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Generating... &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Jekyll Feed: Generating feed &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; posts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Liquid Exception: No repo name found. &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Specify using PAGES_REPO_NWO environment variables, &lt;span style="color:#e6db74"&gt;&amp;#39;repository&amp;#39;&lt;/span&gt; in your configuration, &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; or set up an &lt;span style="color:#e6db74"&gt;&amp;#39;origin&amp;#39;&lt;/span&gt; git remote pointing to your github.com repository. in /_layouts/default.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ERROR: YOUR SITE COULD NOT BE BUILT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und, verflixt, wo kommt der Unterordner &lt;code&gt;/pages&lt;/code&gt; plötzlich her?&lt;/p&gt;&#10;&lt;h2 id="die-lösung"&gt;Die Lösung&lt;/h2&gt;&#10;&lt;p&gt;Durch das &lt;code&gt;/pages&lt;/code&gt; im Pfad liegt die Vermutung nahe, dass die Ursache in einer Kombination aus &lt;a href="https://jekyllrb.com/docs/configuration/options/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Jekyll-Bauoptionen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und dem &lt;a href="https://github.com/github/pages-gem" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github-Pages Gem&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zu finden ist.&lt;/p&gt;&#10;&lt;p&gt;Natürlich bin ich mit diesem Problem nicht allein, und nach längerer Recherche finde ich &lt;a href="https://stackoverflow.com/questions/51869314/jekyll-serve-generate-wrong-path-in-localhost" target="_blank" rel="noopener noreferrer" class="external-link"&gt;tatsächlich eine Dokumentation der Lösung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Also ändere ich die Umgebungsvariable &lt;code&gt;JEKYLL_ENV&lt;/code&gt; (steht für die Bauumgebung) von &lt;code&gt;production&lt;/code&gt; auf &lt;code&gt;development&lt;/code&gt;, so wie es normalerweise für das lokale Bauen vorgesehen ist.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# workflows/jekyll-build-action.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;env&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;JEKYLL_ENV&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;development&lt;/span&gt; &lt;span style="color:#75715e"&gt;# I had &amp;#34;production&amp;#34; here before&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und, was soll ich sagen: Kaum macht man es richtig, schon funktioniert&amp;rsquo;s!&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-12-29_css_assets_show_correctly.jpg" alt="Image: blog.schallbert.de shows correct CSS and media are loaded along working links" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Die Website offline darstellen funktioniert wie folgt: Die gebauten Dateien (den &lt;code&gt;_site&lt;/code&gt;-Ordner) auf den eigenen Rechner ziehen und die &lt;code&gt;index.html&lt;/code&gt; mit dem Browser öffnen.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>🗸 Gitea Actions Teil2 - Jekyll-Dockerimage</title><link>https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/</link><pubDate>Sat, 09 Dec 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-12-09_gitea_infrastructure-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: giteas infrastructure in a nutshell"&#10; title="🗸 Gitea Actions Teil2 - Jekyll-Dockerimage" /&gt;&#10;&lt;h2 id="die-vorgeschichte"&gt;Die Vorgeschichte&lt;/h2&gt;&#10;&lt;p&gt;Leider hatte ich im &lt;a href="https://blog.schallbert.de/gitea-action-runner-native-jekyll/"&gt;Teil1&lt;/a&gt; mit einer leicht modifizierten Kopie der Github-Action zum Bauen von Jekyll kein Glück. Daher wollte ich nun probieren, Jekyll direkt als Dockerimage laufen zu lassen. Im Docker hub gab es nicht so furchtbar viel Auswahl. Trotzdem habe ich ein (in 2023) aktiv gewartetes und gut dokumentiertes Image finden können.&lt;/p&gt;&#10;&lt;h2 id="set-up-job"&gt;&amp;ldquo;Set up job&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;Und so binde ich das Docker-Jekyllimage dann in meinen workflow ein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# workflows/jekyll-build-action.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobs&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Build job&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu-latest&lt;/span&gt; &lt;span style="color:#75715e"&gt;# this is the &amp;#34;label&amp;#34; the runner will use and map to docker target OS&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jvconseil/jekyll-docker&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hier wird dem &lt;code&gt;gitea act_runner&lt;/code&gt; gesagt, dass er mit dem &lt;code&gt;ubuntu-latest&lt;/code&gt; label laufen soll, was in meinem Fall auf eine sehr kleine und schnell hochzufahrende &lt;code&gt;node16:bullseye&lt;/code&gt; Maschine (abgeleitet vom Betriebssystem Debian11) gemappt wird. Zusätzlich wird Docker mitgeteilt, dass bitte das &lt;code&gt;jekyll-docker&lt;/code&gt; image zu laden ist, wo die von mir benötigten Abhängigkeiten (Ruby, Sass, Jekyll&amp;hellip;) bereits berücksichtigt wurden.&lt;/p&gt;&#10;&lt;h2 id="bundle-install"&gt;bundle install&lt;/h2&gt;&#10;&lt;p&gt;Das Bauen schlägt hier allerdings fehl wegen in der &lt;code&gt;Gemfile&lt;/code&gt; angegebenen, aber nicht installierten Abhängigkeiten. Das kann dann so ählich aussehen wie hier:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;bundler: failed to load command: jekyll (/usr/gem/bin/jekyll)&#10;/usr/local/lib/ruby/gems/3.2.0/gems/bundler-2.4.22/lib/bundler/resolver.rb:332:in `raise_not_found!&amp;#39;: Could not find gem &amp;#39;github-pages&amp;#39; in locally installed gems. (Bundler::GemNotFound)&#10;&#9;from /usr/local/lib/ruby/gems/3.2.0/gems/bundler-2.4.22/lib/bundler/resolver.rb:392:in `block in prepare_dependencies&amp;#39;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://bundler.io" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Bundler&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist der Paketmanager für in Ruby geschriebene Software. Ähnlich wie &lt;code&gt;npm&lt;/code&gt; für Javascript oder &lt;code&gt;pip&lt;/code&gt; für Python ist &lt;code&gt;bundler&lt;/code&gt; in der Lage, von einem Ruby-Programm genutzte Bibliotheken und andere Abhängigkeiten herunterzuladen und so einzubinden, dass sie ohne weitere Konfiguration auf dem Zielsystem verwendet werden können. Damit &lt;code&gt;bundler&lt;/code&gt; weiß, was zu installieren ist, wird eine sogenannte &lt;code&gt;Gemfile&lt;/code&gt; angelegt.&lt;/p&gt;&#10;&lt;p&gt;Also dem Script schnell &lt;code&gt;bundle install&lt;/code&gt; hinzufügen, etwa so:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# workflows/jekyll-build-action.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;CHECKOUT ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;INSTALL GEMS ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;bundle install&lt;/span&gt; &lt;span style="color:#75715e"&gt;# will fail with permissions rights to write to Gemfile.lock but anyways installs required dependencies.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;BUILD WITH JEKYLL ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Outputs to the &amp;#39;./_site&amp;#39; directory by default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;bundle exec jekyll build --destination /opt/blog_staging&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;env&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;JEKYLL_ENV&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;production&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="act_runner-kein-schreibzugriff-auf-gemfilelock"&gt;act_runner: Kein Schreibzugriff auf &lt;code&gt;Gemfile.lock&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Dies schlägt wieder fehl - wegen fehlender Schreibrechte des &lt;code&gt;act_runner&lt;/code&gt;:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;gitea-runner-1 | [Deploy Jekyll site/build] | `/workspace/schallbert/blog/Gemfile.lock`. It is likely that you need to grant&#10;gitea-runner-1 | [Deploy Jekyll site/build] | write permissions for that path.&#10;gitea-runner-1 | [Deploy Jekyll site/build] ❌ Failure - Main ---INSTALL GEMS ---&#10;gitea-runner-1 | [Deploy Jekyll site/build] exitcode &amp;#39;23&amp;#39;: failure&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nach langer Suche muss ich feststellen, dass &lt;code&gt;Gemfile.lock&lt;/code&gt; auf meinem Rechner nicht der Versionskontrolle unterliegt, da es in der &lt;code&gt;.gitignore&lt;/code&gt; auftaucht. Der Bundler des &lt;code&gt;act_runner&lt;/code&gt; versucht nun also, diese Datei selbst aus der &lt;code&gt;Gemfile&lt;/code&gt; zu erzeugen und scheitert, weil keine Schreibrechte auf dem Repository bzw. der temporären Kopie vorliegen.&lt;/p&gt;&#10;&lt;p&gt;Dies lässt sich am besten beheben, indem die &lt;code&gt;Gemfile.lock&lt;/code&gt; in die Versionskontrolle aufgenommen wird, d.h. aus der &lt;code&gt;.gitignore&lt;/code&gt; verschwindet. Somit stelle ich sicher, dass ich für das lokale Bauen auf meinem Laptop dieselben Umgebungsbedingungen habe wie auf dem Server.&lt;/p&gt;&#10;&lt;h2 id="update-nov-2025"&gt;Update Nov-2025&lt;/h2&gt;&#10;&lt;p&gt;Nach einem Update meiner lokalen Bildumgebung per &lt;code&gt;bundle update&lt;/code&gt; stellt sich bei mir nun erneut das oben genannte Problem, obwohl ich &lt;code&gt;Gemfile.lock&lt;/code&gt; inzwischen versioniere. Die Lösung diskutiere ich in einem &lt;a href="https://blog.schallbert.de/bundler-ci-gemfile-issue/"&gt;Artikel zum Thema Bundler&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3 id="abhängigkeiten-sass-embedded"&gt;Abhängigkeiten: &lt;code&gt;sass-embedded&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Nach weniger als 20 Sekunden die nächste Fehlermeldung:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;Resolving dependencies...&#10;Could not find gem &amp;#39;sass-embedded (= 1.69.5)&amp;#39; with platform &amp;#39;x86_64-linux&amp;#39; in&#10;rubygems repository https://rubygems.org/ or installed locally.&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nach weiteren Minuten der Recherche die &lt;a href="https://github.com/helaili/jekyll-action/issues/150#issuecomment-1374388728" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Lösung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;: In der Gemfile eine bestimmte Version des &lt;code&gt;jekyll-sass-converter&lt;/code&gt; spezifizieren oder alternativ das Gem &amp;ldquo;github-pages&amp;rdquo; in der Gemfile belassen. Dieses kümmert sich ebenfalls darum, dass der CascadingStyleSheets (CSS) Präprozessor &lt;a href="https://sass-lang.com/guide/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Sass&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; vorhanden ist.&lt;/p&gt;&#10;&lt;p&gt;Nun endlich läuft &lt;code&gt;bundle install&lt;/code&gt; erfolgreich durch:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;✅ Bundle complete! 5 Gemfile dependencies, 43 gems now installed.&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="jekyll-build"&gt;jekyll build&lt;/h2&gt;&#10;&lt;p&gt;Damit Jekyll die gebauten Dateien auch irgendwo abspeichern kann, habe ich dem &lt;code&gt;act_runner&lt;/code&gt; in der &lt;code&gt;docker-compose.yml&lt;/code&gt; ein weiteres &lt;a href="https://docs.docker.com/storage/volumes/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Volume&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gegönnt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./runner/blog_staging:/opt/blog_staging&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./runner/data:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und jetzt direkt nochmal probieren, ob Jekyll erfolgreich baut:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt; Destination: /opt/blog_staging&#10; Generating... &#10; Jekyll Feed: Generating feed for posts&#10;jekyll 3.9.3 | Error: Permission denied @ dir_s_mkdir - /opt/blog_staging&#10;/usr/local/lib/ruby/3.2.0/fileutils.rb:406:in `mkdir&amp;#39;: Permission denied @ dir_s_mkdir - /opt/blog_staging (Errno::EACCES)&#10;&#9;from /usr/local/lib/ruby/3.2.0/fileutils.rb:406:in `fu_mkdir&amp;#39;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Auch dieses Problem lässt sich leicht lösen, indem man entweder nach &lt;code&gt;/tmp&lt;/code&gt; baut, wo der user &lt;code&gt;jekyll&lt;/code&gt; während des Bauens ebenfalls Zugriff hat, oder indem man der Action vor dem &lt;code&gt;jekyll build&lt;/code&gt;-Befehl einen Nutzerwechsel im Build-Arbeitsverzeichnis vornimmt: &lt;code&gt;chown -R jekyll /opt/blog_staging&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Mit dieser Änderung läuft die Action endlich durch.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-12-09_buildsuccessful.jpg" alt="Image: Gitea snapshot showing a successful build" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="artefakte-transferieren"&gt;Artefakte transferieren&lt;/h2&gt;&#10;&lt;p&gt;Doch wo finde ich jetzt die in der Action gebauten Dateien, welche mein Webserver dann veröffentlichen soll? Der &lt;code&gt;_site&lt;/code&gt;-Ordner ist nirgendwo zu finden - weder auf dem Volume der Hostmaschine, noch im Gitea-Dockercontainer oder im Runner!&lt;/p&gt;&#10;&lt;h3 id="docker-volumes-für-act_runner-können-artefakte-nicht-verteilen"&gt;Docker volumes für &lt;code&gt;act_runner&lt;/code&gt; können Artefakte nicht verteilen&lt;/h3&gt;&#10;&lt;p&gt;Dass ich über Docker volumes nicht dort herankomme erscheint logisch wenn man weiß, wie &lt;code&gt;act-runner&lt;/code&gt; funktioniert: Die Action erzeugt ein eigenes Dockerimage mit wiederum per Task-ID assoziierten Volumes, welches nur für dessen Laufzeit existiert.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;DRIVER VOLUME NAME&#10;--&amp;gt; local GITEA-ACTIONS-TASK-84_WORKFLOW-Deploy-Jekyll-site_JOB-build &amp;lt;--&#10;--&amp;gt; local GITEA-ACTIONS-TASK-84_WORKFLOW-Deploy-Jekyll-site_JOB-build-env &amp;lt;--&#10;local act-toolcache&#10;local blog_staging&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Legt man die Artefakte also nirgendwo extern ab, so werden sie nach Durchlauf der Aktionen zusammen mit dem Image demontiert und sind futsch. Das &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/#jekyll-build"&gt;oben&lt;/a&gt; angelegte Volume des Runners hat gar keine Auswirkung auf die Action, weil sie wieder in einem isolierten Image läuft.&lt;/p&gt;&#10;&lt;p&gt;Dies zu verstehen hat mich einige Zeit und Fehlversuche gekostet, dabei ist es &lt;a href="https://docs.gitea.com/usage/actions/act-runner?_highlight=runner#register-the-runner-with-docker" target="_blank" rel="noopener noreferrer" class="external-link"&gt;in einem Abschnitt der Dokumentation&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; beschrieben.&lt;/p&gt;&#10;&lt;p&gt;Das Ganze gestaltet sich ja deutlich komplizierter als ich dachte. Mit Kanonen auf Spatzen schießen angesichts meiner kleinen Website und als Einziger, der sie pflegt. Egal, ich ziehe das jetzt durch.&lt;/p&gt;&#10;&lt;h3 id="aber-man-kann-doch-upload-artifact-verwenden"&gt;Aber man kann doch &lt;code&gt;upload-artifact&lt;/code&gt; verwenden!&lt;/h3&gt;&#10;&lt;p&gt;Na ja, das dachte ich jedenfalls. Also habe ich mein Action-Skript entsprechend erweitert und die von Github Actions zur Verfügung gestellte Funktion eingebaut:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# workflows/jekyll-build-action.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Automatically upload the build folder to Giteas blog repo folder&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;UPLOAD ARTIFACT ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/upload-artifact@v3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;path&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/workspace/schallbert/blog/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Blog_Staging&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;retention-days&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Super, jetzt erscheint ein herunterladbares Zip-file auf der Weboberfläche von Gitea:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-12-09_uploadsuccessful.jpg" alt="Image: Gitea snapshot showing a successful artifact upload" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Nur: Wie bekomme ich das Ding jetzt automatisiert auf meine Serverinstanz? Schließlich kennt die Action diese nicht - sie hat ja nicht einmal einen eigenen Docker Daemon.&lt;/p&gt;&#10;&lt;p&gt;Also schaue ich mal im Gitea-Container selbst nach, wo die Artefakte unter &lt;code&gt;/data/gitea/actions_artifacts/BUILD_ID&lt;/code&gt; abgelegt sind. Allerdings als ein Haufen (hunderte) &lt;code&gt;.chunk.gz&lt;/code&gt; Dateien mit kryptischen Nummernfolgen als Name, bei denen ich wiederum nicht weiß wie ich sie zu einem Archiv zusammenfügen soll.&lt;/p&gt;&#10;&lt;p&gt;Und noch etwas gefällt mir an &lt;code&gt;upload-artifact&lt;/code&gt; nicht: Da meine Website inzwischen eine Menge Bilder, Audiodateien und Videos enthält, ist sie recht groß. Daher benötigt der uploader für&amp;rsquo;s Verpacken und Versenden schon jetzt &lt;em&gt;fast zwei Minuten&lt;/em&gt; - Tendenz linear steigend mit der Anzahl meiner Medieninhalte.&lt;/p&gt;&#10;&lt;p&gt;Sackgasse.&lt;/p&gt;&#10;&lt;h3 id="auch-docker-cp-funktioniert-in-diesem-falle-nicht"&gt;Auch Docker &lt;code&gt;cp&lt;/code&gt; funktioniert in diesem Falle nicht&lt;/h3&gt;&#10;&lt;p&gt;Dann versuche ich mit Hilfe des Kopierbefehls von Docker, die Artefakte auf meinen Host zu transferieren:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# workflows/jekyll-build-action.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;COPY ARTIFACT ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;docker cp gitea-runner-1:/workspace/schallbert/blog /tmp/blog_staging&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dies schlägt wieder fehl:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | docker cp gitea-runner-1:/workspace/schallbert/blog /tmp/blog_staging&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Deploy Jekyll site/build&lt;span style="color:#f92672"&gt;]&lt;/span&gt; | /var/run/act/workflow/3.sh: line 2: docker: not found&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Deploy Jekyll site/build&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ❌ Failure - Main --- COPY ARTIFACT ---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Auch erwartbar, denn der Action-Container selbst kennt kein Docker. Es ist also nicht möglich, etwas von &amp;ldquo;innerhalb&amp;rdquo; des Containers per Docker heraus zu bekommen. Dies ist für mich aber erforderlich, da die Action ja den ganzen Prozess vollständig automatisieren soll.&lt;/p&gt;&#10;&lt;p&gt;Oder geht es doch irgendwie? Der Daemon der Hostmaschine &lt;code&gt;docker.sock&lt;/code&gt; steht laut Giteas Dokumentation zumindest dem &lt;code&gt;act_runner&lt;/code&gt; per Volume zur Verfügung - wird aber scheinbar nicht in die Action weitergeleitet. Vielleicht aus gutem Grund, sonst wäre die Kapselung futsch und eine schadhafte Action könnte praktisch meinen Docker Daemon auf dem Host kapern 😰.&lt;/p&gt;&#10;&lt;h3 id="transferieren-per-sftp-würde-klappen"&gt;Transferieren per SFTP würde klappen&lt;/h3&gt;&#10;&lt;p&gt;So langsam gehen mir die Alternativen aus. Ich überlege nun, die Artefakte per &lt;a href="https://de.wikipedia.org/wiki/SSH_File_Transfer_Protocol" target="_blank" rel="noopener noreferrer" class="external-link"&gt;SFTP&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; aus dem vom &lt;code&gt;act_runner&lt;/code&gt; erzeugten Action-Dockerimage heraus in den Webserver-Container zu schieben&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-12-09_sftp_infrastructure.jpg" alt="Image: Machine architecture if I used SFTP for artifact share between Gitea and webserver" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Glücklicherweise gibt es hierfür wieder eine vorgefertigte Action: &lt;a href="https://github.com/marketplace/actions/scp-files" target="_blank" rel="noopener noreferrer" class="external-link"&gt;scp-files&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Nun muss ich aber den Container des Webservers so umbauen, dass er per SCP erreichbar ist und den SSH-Schlüssel der Action akzeptiert.&lt;/p&gt;&#10;&lt;p&gt;Andererseits wird davor &lt;a href="https://stackoverflow.com/questions/65381311/run-sshd-in-docker-container" target="_blank" rel="noopener noreferrer" class="external-link"&gt;in vielen Foren&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gewarnt - Ein Container sollte nur eine Applikation beherbergen. Aber auch hier gibt es eine Lösung: &lt;a href="https://hub.docker.com/r/panubo/sshd/#!" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ein eigener Docker-Container mit SSH-Daemon&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Diesem könnte ich dann ein Volume geben, welches er mit dem Webserver teilt.&lt;/p&gt;&#10;&lt;p&gt;Sieht leider nach einigem Aufwand aus. Und ich möchte als fauler Entwickler mein System so einfach wie möglich halten - also recherchiere ich weiter.&lt;/p&gt;&#10;&lt;h3 id="action-volumes"&gt;Dem Action-Container ein Volume zur Verfügung stellen&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-12-09_gitea_infrastructure.jpg" alt="Image: Docker-Gitea infrastructure overview" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Diese ganzen Fehlschläge und Sackgassen begleiten mich jetzt schon seit über einem Monat. Ich will es trotzdem unbedingt schaffen, vollautomatisch bauen, testen und veröffentlichen zu können. Auf dem Weg hierhin habe ich eine Menge gelernt und jetzt hoffe ich, dass dieser Ansatz mich endlich zur Lösung bringt.&lt;/p&gt;&#10;&lt;p&gt;Bei &lt;a href="https://gitea.com/gitea/act_runner/issues/329" target="_blank" rel="noopener noreferrer" class="external-link"&gt;meiner Suche&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; stieß ich auf &lt;a href="https://gitea.com/gitea/act_runner/src/branch/main/internal/pkg/config/config.example.yaml" target="_blank" rel="noopener noreferrer" class="external-link"&gt;die Möglichkeit, die Konfiguration des Runners&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zu verändern. So könnte ich am Ende vielleicht doch noch ein Volume einbinden, welches ich zwischen der Action und meinem Server über den Daemon auf dem Host teilen kann.&lt;/p&gt;&#10;&lt;p&gt;Wenn das klappt, hätte ich kein zusätzliches Sicherheitsrisiko wie ein öffentlich verfügbarer &lt;code&gt;file transfer container&lt;/code&gt; oder die langen Wartezeiten durch &lt;code&gt;upload-artifact&lt;/code&gt;. Also frisch ans Werk! Es gibt noch &lt;a href="https://gitea.com/gitea/act_runner/issues/407" target="_blank" rel="noopener noreferrer" class="external-link"&gt;weitere Gründe&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, weswegen Volumes in der jeweiligen Action Sinn machen können - es muss also bereits Leute da draußen geben, die das hinbekommen haben.&lt;/p&gt;&#10;&lt;p&gt;Ich versuche also, die Volumes im Action-Skript anzulegen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# workflows/jekyll-build-action.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;container&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jvconseil/jekyll-docker:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/tmp/blog_staging:/blog_staging&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/opt/cache:/opt/hostedtoolcache&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Doch irgendwie landen die Artefakte noch immer nicht auf der Platte meines Hosts. Ich vereinfache die Action soweit, dass ich nur noch prüfe, ob die Erzeugung des Volumes auch einen entsprechenden Ordner anlegt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# workflows/jekyll-build-action.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: --- &lt;span style="color:#ae81ff"&gt;CHECK_VOLUME ---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ls -al /blog_staging&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;und schon schlägt die Aktion wieder fehl:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;gitea-runner-1 | [Deploy Jekyll site/build] [DEBUG] Working directory &amp;#39;/workspace/schallbert/blog&amp;#39;&#10;gitea-runner-1 | [Deploy Jekyll site/build] | ls: /blog_staging: No such file or directory&#10;gitea-runner-1 | [Deploy Jekyll site/build] ❌ Failure - Main --- CHECK_VOLUME ---&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nach ein paar weiteren, erfolglosen Versuchen (vielleicht hatte ich etwas falsch geschrieben?) und langer Recherche in den Foren fand ich heraus, dass es das &lt;a href="https://gitea.com/gitea/act_runner/issues/329" target="_blank" rel="noopener noreferrer" class="external-link"&gt;valid_volumes&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Attribut im &lt;code&gt;act&lt;/code&gt; gibt - taucht das Volume dort nicht auf, so wird es auch nicht eingebunden.&lt;/p&gt;&#10;&lt;p&gt;OK, also schnell der Action hinzugefügt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# jekyll-build-action.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;container&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;jvconseil/jekyll-docker:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;valid_volumes&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#39;**&amp;#39;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# This does not work. Also specialized lists indicating the volumes directly won&amp;#39;t work&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/tmp/blog_staging:/blog_staging&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/opt/cache:/opt/hostedtoolcache&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aber immer noch nichts. Immerhin kann ich in den Logs mit dem Wissen über &lt;code&gt;valid_volumes&lt;/code&gt; jetzt eine Warnung entdecken:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Deploy Jekyll site/build&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;/tmp/blog_staging&lt;span style="color:#f92672"&gt;]&lt;/span&gt; is not a valid volume, will be ignored&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Deploy Jekyll site/build&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;/opt/cache&lt;span style="color:#f92672"&gt;]&lt;/span&gt; is not a valid volume, will be ignored&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Etwas stimmt mit der Übergabe dieser Option nicht. Also recherchiere ich weiter. Es gibt &lt;a href="https://gitea.com/gitea/act_runner/issues/407" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Berichte&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, in denen das Anmelden der Volumes funktioniert. Also erzeuge ich nach &lt;a href="https://gitea.com/gitea/act_runner/src/branch/main#configuration" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; eine Konfigurationsdatei, wo ich das Folgende eintrage:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# runner/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# valid_volumes:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - /src/*.json&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# If you want to allow any volume, please use the following configuration:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# valid_volumes:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - &amp;#39;**&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;valid_volumes&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/blog_staging&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/opt/hostedtoolcache&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Wichtig:&lt;/em&gt; Die Notation muss als String vorliegen, kommasepariert, und gibt stets die &lt;code&gt;Source&lt;/code&gt; des Volumes an. Also den Teil, der vor dem &lt;code&gt;:&lt;/code&gt; steht.&#10;Doch noch immer erscheint beim Bauen die Fehlermeldung &amp;ldquo;not a valid volume&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Doch dann fällt mir auf, dass die Config dem &lt;code&gt;act_runner&lt;/code&gt; selbst als Volume zur Verfügung gestellt werden muss - sonst kann der im Container laufende Runner ja gar nicht darauf zugreifen!&lt;/p&gt;&#10;&lt;p&gt;Daher sieht meine &lt;code&gt;docker-compose.yml&lt;/code&gt; für Gitea, Abschnitt &amp;ldquo;runner&amp;rdquo; nun wie folgt aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runner&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea/act_runner:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;CONFIG_FILE=/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_INSTANCE_URL=https://git.schallbert.de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_RUNNER_NAME=ichlaufe&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_RUNNER_REGISTRATION_TOKEN= &amp;lt;redacted&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./runner/config.yml:/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./runner/data:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/opt/hostedtoolcache:/opt/hostedtoolcache&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und endlich:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | ls -al /blog_staging&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Deploy Jekyll site/build&lt;span style="color:#f92672"&gt;]&lt;/span&gt; | total &lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Deploy Jekyll site/build&lt;span style="color:#f92672"&gt;]&lt;/span&gt; | drwxr-xr-x &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;4096&lt;/span&gt; Dec &lt;span style="color:#ae81ff"&gt;27&lt;/span&gt; 07:32 .&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Deploy Jekyll site/build&lt;span style="color:#f92672"&gt;]&lt;/span&gt; | drwxr-xr-x &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; root root &lt;span style="color:#ae81ff"&gt;4096&lt;/span&gt; Dec &lt;span style="color:#ae81ff"&gt;27&lt;/span&gt; 07:46 ..&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Deploy Jekyll site/build&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ✅ Success - Main --- CHECK_VOLUME ---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Was für ein Akt. Ich bin so froh, dass jetzt alles durchläuft und ich auf meinem lokalen Hostsystem tatsächlich die gebauten Dateien vorfinde! 🥳&lt;/p&gt;&#10;&lt;h2 id="reverse-proxy-connection-refused"&gt;Noch ein letzter Tipp&lt;/h2&gt;&#10;&lt;p&gt;Wenn ihr so wie ich mit einem Reverse Proxy arbeitet und so komische &lt;code&gt;connection refused&lt;/code&gt; Fehlermeldungen beim Hochfahren des Runners erhaltet wie:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose up&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Running 2/0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ✔ Container gitea Created 0.0s &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ✔ Container gitea-runner-1 Created 0.0s &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Attaching to gitea, gitea-runner-1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;info msg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Starting runner daemon&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;error msg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;fail to invoke Declare&amp;#34;&lt;/span&gt; error&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;unavailable: dial tcp &amp;lt;address&amp;gt;: connect: connection refused&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 | Error: unavailable: dial tcp &amp;lt;address&amp;gt; connect: connection refused&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea | Server listening on :: port 22.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea-runner-1 exited with code &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dann ist euer Reverse Proxy entweder falsch eingestellt oder - wie in meinem Falle - nicht hochgefahren.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Die Nutzung des SFTP (Secure File Transfer Protocol), also der Dateitransfer über ssh (secure shell), erhöht die Angriffsfläche meines Systems ein wenig. Der SSH-Dienst ist nämlich dann nicht nur für meine Action, sondern ganz allgemein von außen erreichbar.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>🗙 Gitea Actions - Jekyll Workflow</title><link>https://blog.schallbert.de/gitea-action-runner-native-jekyll/</link><pubDate>Thu, 30 Nov 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/gitea-action-runner-native-jekyll/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-30_ghaction-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: github actions workflow to build Jekyll page"&#10; title="🗙 Gitea Actions - Jekyll Workflow" /&gt;&#10;&lt;p&gt;Nachdem ich in &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;diesem Projekt&lt;/a&gt; meinen eigenen, kleinen Server aufgesetzt und mit den entsprechenden Programmen zum Hosten meines Blogs ausgestattet habe, möchte ich ihn nun in die Lage versetzen, die Website automatisch zu bauen.&lt;/p&gt;&#10;&lt;h2 id="gewünschter-ablauf"&gt;Gewünschter Ablauf&lt;/h2&gt;&#10;&lt;p&gt;Ich möchte folgendes erreichen: Sobald Gitea eine &lt;code&gt;push&lt;/code&gt;-Aktion auf dem Blog-Repository verzeichnet, soll ein Action Runner in einem eigenen Docker-Container loslaufen und Ruby, Bundler, Jekyll sowie sämtliche Abhängigkeiten nachladen. Dann soll der Runner meine Quelldateien für den Blog kopieren und dem &lt;code&gt;bundle exec jekyll build&lt;/code&gt; Prozess zur Verfügung stellen, welcher dann die Website baut und schließlich im &lt;code&gt;_site&lt;/code&gt;-Ordner ablegt. Anschließend soll der Runner sich selbst beenden und alle in Anspruch genommenen Ressourcen wieder freigeben.&lt;/p&gt;&#10;&lt;p&gt;An dieser Stelle möchte ich nachkontrollieren können, ob die Website fehlerfrei gebaut wurde und in Zukunft eventuell noch ein paar weitere Prüfungen darüberlaufen lassen - z.B. kaputte Links erkennen, nicht angezeigte Bilder markieren, Rechtschreibprüfung durchführen.&lt;/p&gt;&#10;&lt;p&gt;Die Freigabe sowie die Veröffentlichung soll dann ein anderer Prozess übernehmen, um den ich mich später kümmere.&lt;/p&gt;&#10;&lt;h2 id="ausgangspunkt-github-actions"&gt;Ausgangspunkt: Github Actions&lt;/h2&gt;&#10;&lt;p&gt;Wie üblich wildere ich erst einmal bei anderen Menschen, die ein ähnliches Problem für sich bereits gelöst haben. Ich wurde im &lt;a href="https://github.com/actions/starter-workflows/blob/main/pages/jekyll.yml" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github Actions starter-workflows&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Repository von Github selbst fündig und habe die Workflowdatei für Jekyll mit leichten Modifikationen direkt mal mit meiner Gitea-Instanz ausprobiert:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# jekyll-build-pages.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Sample workflow for building and deploying a Jekyll site&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Deploy Jekyll site&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;run-name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ gitea.actor }} builds Jekyll site&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;on&lt;/span&gt;: [&lt;span style="color:#ae81ff"&gt;push]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobs&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Build job&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu-latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Checkout&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Setup Ruby&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf&lt;/span&gt; &lt;span style="color:#75715e"&gt;# v1.146.0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ruby-version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3.1&amp;#39;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Not needed with a .ruby-version file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;bundler-cache&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt; &lt;span style="color:#75715e"&gt;# runs &amp;#39;bundle install&amp;#39; and caches installed gems automatically&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cache-version&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Increment this number if you need to re-download cached gems&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nachdem ich diesen Workflow in mein Blogverzeichnis unter &lt;code&gt;workflows/jekyll.yml&lt;/code&gt; hochgeladen hatte, lief der Runner tatsächlich direkt los:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-30_rubyfails.jpg" alt="Image: Gitea action runner returns error on ruby install" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="probleme-mit-der-ruby-installation"&gt;Probleme mit der Ruby-Installation&lt;/h2&gt;&#10;&lt;p&gt;Schon mal prima. Allerdings wenig erbaulich, dass bereits bei der Installation von Ruby etwas nicht passt. Werfen wir einen Blick in die Logs:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;::error::The current runner (debian-11-x64) was detected as self-hosted because the platform does not match a GitHub-hosted runner image &#10;(or that image is deprecated and no longer supported).&#10;In such a case, you should install Ruby in the $RUNNER_TOOL_CACHE yourself, for example using https://github.com/rbenv/ruby-build&#10;You can take inspiration from this workflow for more details: &#10;https://github.com/ruby/ruby-builder/blob/master/.github/workflows/build.yml&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Na, das ist doch mal eine hilfreiche Fehlermeldung!&lt;/p&gt;&#10;&lt;p&gt;Der Runner läuft um Ressourcen zu sparen standardmäßig im Docker mit einem abgespeckten Image basierend auf Debian namens &lt;code&gt;node:16-bullseye&lt;/code&gt; &lt;a href="https://github.com/nektos/act/blob/master/IMAGES.md" target="_blank" rel="noopener noreferrer" class="external-link"&gt;(Referenz)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Scheinbar ist dieser nicht kompatibel mit dem unter Ubuntu22 betriebenen Github Actions Runner. Ich habe also ein paar Lösungsmöglichkeiten zur Verfügung:&lt;/p&gt;&#10;&lt;h3 id="möglichkeiten-zur-fehlerbehebung"&gt;Möglichkeiten zur Fehlerbehebung&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Den Runner über seine &amp;ldquo;Labels&amp;rdquo; auf Ubuntu22.04 umsatteln.&lt;/li&gt;&#10;&lt;li&gt;Ren Runner nicht im Docker, sondern nativ auf dem Server laufen lassen.&lt;/li&gt;&#10;&lt;li&gt;Wie in der Fehlermeldung vorgeschlagen, Ruby im Cache des Runners vorinstallieren.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/"&gt;Ein Jekyll-Dockerimage verwenden&lt;/a&gt;, welches Jekyll bereits im Gepäck hat und nicht erst die Installation benötigt.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="den-runner-mit-ubuntu22-statt-node16-betreiben"&gt;Den Runner mit Ubuntu22 statt Node16 betreiben&lt;/h3&gt;&#10;&lt;p&gt;Das entsprechende &lt;a href="https://hub.docker.com/_/ubuntu" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dockerimage&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; existiert zwar. Aber es kostet aber einen Haufen Speicherplatz auf meinem Server, benötigt durch seinen größeren Funktionsumfang mehr RAM und braucht vor allem länger, bis es hochgefahren ist.&lt;/p&gt;&#10;&lt;p&gt;Und Zeit ist bei Runnern wertvoll, schließlich will ich ja möglichst schnell wissen, ob das Bauen geklappt hat.&lt;/p&gt;&#10;&lt;h3 id="den-runner-direkt-auf-der-hostmaschine-ubuntu22-laufen-lassen"&gt;Den Runner direkt auf der Hostmaschine (Ubuntu22) laufen lassen&lt;/h3&gt;&#10;&lt;p&gt;Ist sicherheitstechnisch nicht so sehr zu empfehlen (Bei &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches" target="_blank" rel="noopener noreferrer" class="external-link"&gt;einem ungeschützten Branch&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; würde der Runner von Dritten per &lt;code&gt;git push --force&lt;/code&gt; hochgeschobenen Schadcode unbesehen auf meiner Hostmaschine ausführen) und zudem ginge die Portabilität flöten, weil ich das Zielsystem des Runners dann fest verdrahte.&lt;/p&gt;&#10;&lt;h3 id="gitea-act_runner-direkt-auf-dem-host-laufen-lassen"&gt;Gitea act_runner direkt auf dem Host laufen lassen&lt;/h3&gt;&#10;&lt;p&gt;Für den zweiten Ansatz habe ich mir per &lt;a href="https://docs.gitea.com/1.23/usage/actions/act-runner/#configuration" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; eine Konfiguration für den &lt;code&gt;runner&lt;/code&gt; angelegt und darin das bestehende label-mapping so geändert, dass nicht mehr in Richtung Docker, sondern auf dem Ubuntu des Servers selbst gebaut wird. Dafür genügt es in meinem Fall, das Label &lt;code&gt;ubuntu-latest:host&lt;/code&gt; statt &lt;code&gt;ubuntu-latest&lt;/code&gt; in die Konfiguration zu schreiben. Leider erhielt ich hier Fehlermeldungen bei den Zugriffsrechten des Runners, welcher aus Docker heraus auf dem Hostsystem laufen müsste.&lt;/p&gt;&#10;&lt;p&gt;Nicht so schlimm, ich wollte diese Lösung wegen der oben beschriebenen möglichen Probleme sowieso nicht dauerhaft verwenden. Also investiere ich hier keine weitere Zeit.&lt;/p&gt;&#10;&lt;h3 id="ruby-im-cache-vorinstallieren"&gt;Ruby im Cache vorinstallieren&lt;/h3&gt;&#10;&lt;p&gt;Wie von der Fehlermeldung empfohlen habe ich &lt;a href="https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieser Anleitung folgend&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; in der &lt;code&gt;docker-compose.yml&lt;/code&gt; von Gitea ein Volume angelegt für den Tool Cache des runners und das Zielverzeichnis in der Umgebungsvariable &lt;code&gt;RUNNER_TOOL_CACHE&lt;/code&gt; abgelegt.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;runner&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea/act_runner:nightly&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_INSTANCE_URL=&amp;lt;redacted&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_RUNNER_NAME=&amp;lt;redacted&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;RUNNER_TOOL_CACHE=/opt/hostedtoolcache&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_RUNNER_REGISTRATION_TOKEN= &amp;lt;redacted&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./runner/data:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/opt/hostedtoolcache:/opt/hostedtoolcache&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hiermit wird dem in Docker laufenden Runner der Ordner &lt;code&gt;/opt/hostedtoolcache&lt;/code&gt; im Dateisystem des Hosts unter dem Namen &lt;code&gt;/opt/hostedtoolcache&lt;/code&gt; zur Verfügung gestellt.&#10;Nun probiere ich mal, Ruby direkt dort hinein zu installieren.&lt;/p&gt;&#10;&lt;p&gt;Also lade ich das &lt;a href="https://github.com/rbenv/ruby-build" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;ruby-build&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; repository herunter (&lt;code&gt;git clone&lt;/code&gt;) und lasse das Installerscript mit &lt;code&gt;./ruby-build/install.sh&lt;/code&gt; durchlaufen.&#10;Danach führe ich den Build mit dem in der Fehlermeldung angegebenen Pfad aus: &lt;code&gt;ruby-build 3.1.4 /opt/hostedtoolcache/Ruby/3.1.4/x64&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;==&amp;gt; Downloading openssl-3.1.4.tar.gz...&#10; % Total % Received % Xferd Average Speed Time Time Time Current&#10; Dload Upload Total Spent Left Speed&#10;100 14.8M 100 14.8M 0 0 81.3M 0 --:--:-- --:--:-- --:--:-- 81.1M&#10;==&amp;gt; Installing openssl-3.1.4...&#10;&#10;BUILD FAILED (Ubuntu 22.04 on x86_64 using ruby-build 20231114)&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="probleme-mit-prebuilt-ruby"&gt;Probleme mit prebuilt-Ruby&lt;/h3&gt;&#10;&lt;p&gt;Das Log zeigt das Problem:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;[...] No C compiler found, please specify one with the environment variable CC [...]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also tippe ich &lt;code&gt;apt install build-essential&lt;/code&gt; und stelle dann mit &lt;code&gt;which gcc&lt;/code&gt; fest, dass er nun unter &lt;code&gt;/usr/bin/gcc&lt;/code&gt; installiert ist. Auf ein Neues!&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;crypto/comp/c_zlib.c:36:11: fatal error: zlib.h: No such file or directory&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dann schnell &lt;code&gt;apt install libz-dev&lt;/code&gt; eingeben und erneut probieren. Dieses mal läuft er recht lange, dann:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;*** Following extensions are not compiled:&#10;openssl:&#10; Could not be configured. It will not be installed.&#10; /tmp/ruby-build.20231205114312.9174.0gmhNf/ruby-3.1.4/ext/openssl/extconf.rb:100: OpenSSL library could not be found. You might Check ext/openssl/mkmf.log for more details.&#10;readline:&#10; Could not be configured. It will not be installed.&#10; /tmp/ruby-build.20231205114312.9174.0gmhNf/ruby-3.1.4/ext/readline/extconf.rb:62: Neither readline nor libedit was found&#10; Check ext/readline/mkmf.log for more details.&#10;*** Fix the problems, then remove these directories and try again if you want.&#10;[...]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;OK, nun ist es openssl. Fix nachinstallieren mit &lt;code&gt;apt install libssl-dev&lt;/code&gt; sowie &lt;code&gt;apt install libreadline-dev&lt;/code&gt; und endlich 🎉:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;Installed ruby-3.1.4 to /opt/hostedtoolcache/Ruby/3.1.4/x64&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="noch-immer-fehler-beim-bauen-mit-dem-github-acitons-script"&gt;Noch immer Fehler beim Bauen mit dem Github-Acitons script&lt;/h2&gt;&#10;&lt;p&gt;Merkwürdig, das Log spuckt mir immer noch denselben Fehler aus:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;💬 ::debug::isExplicit: 3.1.4&#10;&#10;gitea-runner-1 | [Deploy Jekyll site/build] 💬 ::debug::checking cache: /opt/hostedtoolcache/Ruby/3.1.4/x64&#10;gitea-runner-1 | [Deploy Jekyll site/build] | ::debug::checking cache: /opt/hostedtoolcache/Ruby/3.1.4/x64&#10;gitea-runner-1 | [Deploy Jekyll site/build] 💬 ::debug::not found&#10;gitea-runner-1 | [Deploy Jekyll site/build] | ::debug::not found&#10;gitea-runner-1 | [Deploy Jekyll site/build] ❗ ::error::The current runner (debian-11-x64) was detected as self-hosted&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Das geht auf folgende Abfage in der ausführenden &lt;a href="https://github.com/ruby/setup-ruby/blob/master/ruby-builder.js#L97" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Action&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zurück:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// setup-ruby/ruby-builder.js&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;common&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;shouldUseToolCache&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;engine&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt;)) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;inToolCache&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;common&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toolCacheFind&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;engine&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;inToolCache&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;rubyPrefix&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;inToolCache&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;toolCacheRubyPrefix&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;common&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getToolCacheRubyPrefix&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;platform&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;engine&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;common&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;isSelfHostedRunner&lt;/span&gt;()) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rubyBuildDefinition&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;engine&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;ruby&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;engine&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;-&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;version&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;core&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;( [...] )&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="update-jan-2024"&gt;Update Jan-2024&lt;/h3&gt;&#10;&lt;p&gt;Es es gibt zwei weitere Möglichkeiten dafür, dass der &lt;code&gt;toolcache&lt;/code&gt; nicht gefunden wird:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Ich habe das Volume nicht der Action, sondern dem Runner zur Verfügung gestellt. Ich bin mir nicht sicher, dass die Daten auch in der Action verfügbar sind.&lt;/li&gt;&#10;&lt;li&gt;Der &lt;code&gt;toolcache&lt;/code&gt;-Pfad ist nicht in der Konfigurationsdatei des runners unter dem Eintrag &lt;code&gt;valid_volumes&lt;/code&gt; enthalten. Eine &lt;a href="https://gitea.com/gitea/act/pulls/60" target="_blank" rel="noopener noreferrer" class="external-link"&gt;pull request Kommunikation&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; lässt mich jedoch vermuten, dass dies automatisch passiert. Dennoch bin ich mir nicht 100% sicher, ob die Information noch dem Stand der Dinge entspricht.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="docker-volume-überprüfen"&gt;Docker volume überprüfen&lt;/h3&gt;&#10;&lt;p&gt;Mist. Prüfen wir schnell mal, ob Ruby in dem Volume auch wirklich verfügbar ist. Dafür suchen wir mit &lt;code&gt;docker ps&lt;/code&gt; die Container ID des runners heraus und geben sie bei folgendem Kommando ein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it &amp;lt;containerID&amp;gt; bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dann navigiere ich in den gesuchten Ordner &lt;code&gt;cd /opt/hostedtoolcache&lt;/code&gt;, liste dort den Inhalt mit &lt;code&gt;ls&lt;/code&gt; und siehe da: &lt;code&gt;Ruby&lt;/code&gt;. Es liegt also alles vor, nur wird es nicht gefunden.&lt;/p&gt;&#10;&lt;h2 id="misserfolg-anerkennen"&gt;Misserfolg anerkennen&lt;/h2&gt;&#10;&lt;p&gt;So komme ich hier also nicht weiter 😖. Daher öffne ich mein Problem für die Community unter dem Titel &lt;a href="https://gitea.com/gitea/act_runner/issues/441" target="_blank" rel="noopener noreferrer" class="external-link"&gt;act_runner cannot find hostedtoolcache&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und probiere&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/"&gt;einen anderen Ansatz&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="problemursache"&gt;Problemursache&lt;/h3&gt;&#10;&lt;p&gt;Der Support der Action &lt;code&gt;setup-ruby&lt;/code&gt; für selbsgehostete CI-Pipelines wie meinen &lt;code&gt;act_runner&lt;/code&gt; wurde &lt;a href="https://github.com/ruby/setup-ruby/issues/475#issuecomment-1455099634" target="_blank" rel="noopener noreferrer" class="external-link"&gt;wohl eingestellt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Die Dokumentation mit den Hinweisen zur Fehlerbehebung bei Verwendung selbstgehosteter Runner allerdings zum Zeitpunkt der Verwendung durch meine Wenigkeit war aber noch nicht auf Stand gebracht worden.&lt;/p&gt;&#10;</description></item><item><title>Mehrsprachige Webseiten - l10n</title><link>https://blog.schallbert.de/jekyll-polyglot-language-support/</link><pubDate>Tue, 14 Nov 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/jekyll-polyglot-language-support/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-14_translations_polyglot_flags-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Flags that can be clicked on to change display language"&#10; title="Mehrsprachige Webseiten - l10n" /&gt;&#10;&lt;p&gt;Die Bereitstellung einer Webseite in mehreren Sprachen wird &amp;ldquo;internationalization&amp;rdquo; - kurz &lt;a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank" rel="noopener noreferrer" class="external-link"&gt;i16n&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; beziehungsweise &amp;ldquo;localization&amp;rdquo; &lt;code&gt;l10n&lt;/code&gt; genannt. Sucht man nach diesen Begriffen im Netz, findet man eine Menge tollen Materials zum Thema. Die Zahlen in den Kurzformen dieser Begriffe geben die Anzahl Buchstaben in der Wortmitte an.&lt;/p&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;Ich bin schon vor langer Zeit gefragt worden, ob ich nicht mal eine &lt;a href="https://hobbyline.info/forum/index.php?thread/603-blog-artikel-zur-portalfr%C3%A4se/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;deutsche Version dieser Webseite&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; anfertigen wolle. Bis dato hielt ich das für &lt;a href="https://hobbyline.info/forum/index.php?thread/603-blog-artikel-zur-portalfr%C3%A4se/&amp;amp;postID=7619#post7619" target="_blank" rel="noopener noreferrer" class="external-link"&gt;zu viel Arbeit&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Dies änderte sich erst vor gut einem halben Jahr, als ich dieselbe Anfrage verbal von mehreren Leuten unabhängig voneinander in kurzer Zeit erhielt.&lt;/p&gt;&#10;&lt;p&gt;Also investierte ich einen Haufen freier Abende in die Fleißarbeit, rund 80 Artikel auf Deutsch zu übertragen. Doch ich war noch immer nicht fertig.&lt;/p&gt;&#10;&lt;h2 id="jekyll-plugin"&gt;Jekyll plugin&lt;/h2&gt;&#10;&lt;p&gt;Ich sah mich nach Möglichkeiten um, meinen Internetauftritt mit Jekyll mehrsprachig zu gestalten. Dabei fand ich unter Anderem &lt;a href="https://demondehellis.github.io/en/posts/how-to-make-jekyll-blog-multilingual/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;demondehellis&amp;rsquo; plugin-freie Lösung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://polyglot.untra.io/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;untras polyglot plugin&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://github.com/kurtsson/jekyll-multiple-languages-plugin" target="_blank" rel="noopener noreferrer" class="external-link"&gt;kurtssons jekyll-multiple-languages-plugin&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und noch ein paar weitere Möglichkeiten, diesen Webauftritt in mehreren Sprachen anzubieten.&lt;/p&gt;&#10;&lt;p&gt;Nun musste ich mich für eine entscheiden. Die plugin-freie Lösung fiel recht schnell heraus, da ich &amp;ldquo;gleiche&amp;rdquo; Posts verschiedener Sprache gleich benennen wollte und damit Interlinks zwischen Beiträgen unabhängig von der verwendeten Sprache bestehen bleiben sollten - irgendwas muss also dafür sorgen, dass sich die Sprachen nicht wild mischen.&lt;/p&gt;&#10;&lt;p&gt;Blieben die Plugins übrig. Kurz gesagt stieß ich zufällig auf &lt;a href="https://leo3418.github.io/collections/multilingual-jekyll-site/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;leo3418s&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; hervorragende Artikelserie für die Implementierung von Polyglot - und so entschied ich mich für ebendieses Plugin.&lt;/p&gt;&#10;&lt;h3 id="polyglot-installation"&gt;Polyglot: Installation&lt;/h3&gt;&#10;&lt;p&gt;Die Installation von Polyglot war einfach, denn sowohl die &lt;a href="https://github.com/untra/polyglot" target="_blank" rel="noopener noreferrer" class="external-link"&gt;im Repository&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bereitgestellte &lt;code&gt;readme.md&lt;/code&gt; als auch der von leo3418 geschriebene &lt;a href="https://leo3418.github.io/collections/multilingual-jekyll-site/set-up-polyglot.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Blogartikel&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; beschäftigen sich ausführlich mit den ersten Schritten. In der Tat fand ich die Dokumentation so gut, dass ich darauf verweisen möchte und hier gar nicht weiter darüber berichten werde.&lt;/p&gt;&#10;&lt;h3 id="restructuring-the-website"&gt;Umstrukturierung der Website&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-14_folderstructure.jpg" alt="image: updated folder structure for this site" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Bei Polyglot gibt es zwei Möglichkeiten, die Ordnerstruktur für die Website auf mehrsprachig umzustellen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Man benutzt das Sprachkürzel im Artikelnamen und ermöglicht so eine Zuordnung. Dieser Artikel hier würde dann z.B. heißen: &lt;code&gt;2023-11-14-jekyll-polyglot-language-support-de.md&lt;/code&gt; und analog in jeder weiteren Sprache mit einem anderen Sprachkürzel&lt;/li&gt;&#10;&lt;li&gt;Man erstellt Ordner mit dem Sprachkürzel, z.b. &lt;code&gt;/de&lt;/code&gt;, behält aber identische Dateinamen für verschiedene Sprachen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Ich habe mich für letzteres entschieden, um der Umbenennung meiner schon vorhandenen Artikel zu entgehen. Die Sprache wird von Polyglot auch bei Verwendung von Interlinks auf der Webseite beibehalten. Klickt man z.B. auf den Artikel &lt;a href="https://blog.schallbert.de/milling-small-parts/"&gt;https://schallbert.de/milling-small-parts/&lt;/a&gt;, so wird Polyglot ausgehend von der Sprache des aktuellen Artikels auch den neuen Artikel in dieser Sprache aufrufen. Einzig bei Überschriften von Abschnitten wird es schwierig: Die sind nämlich übersetzt. Also wird der Link auf die englische Version der Seite für die Überschrift &lt;a href="https://blog.schallbert.de/jekyll-polyglot-language-support/#restructuring-the-website"&gt;#Umstrukturierung-der-Website&lt;/a&gt; in einem 404-Fehler enden.&lt;/p&gt;&#10;&lt;p&gt;Daher muss man für die Überschriften den kompletten Artikelpfad inkl. der Lokalisierung angeben. In diesem Beispiel wäre folglich &lt;a href="https://blog.schallbert.de/jekyll-polyglot-language-support/#restructuring-the-website"&gt;/posts/en/jekyll-polyglot-language-support/#restructuring-the-website&lt;/a&gt; der korrekte Interlink.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Update&lt;/em&gt;: Alternativ kann man der Überschrift auch eine sprachunabhängige &amp;ldquo;ID&amp;rdquo; geben, sodass auch hier die Anchor-Links auf die Überschriften identisch bleiben. Beispiel: &lt;code&gt;## Umstrukturierung der Website {# website-restructuring}&lt;/code&gt;, Aufruf im Link per &lt;code&gt;#website-restucturing&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="übersetzung-von-seitentitel-navigation-kopf--und-fußzeile"&gt;Übersetzung von Seitentitel, Navigation, Kopf- und Fußzeile&lt;/h3&gt;&#10;&lt;p&gt;Hier wird es ein wenig komplizierter. Die &lt;code&gt;_config.yml&lt;/code&gt; von Jekyll lässt ohne Weiteres nicht zu, dass mehrere Varianten von ihr existieren. Man benötigt für sprachsensitive Parameter wie &lt;code&gt;site.title&lt;/code&gt; oder &lt;code&gt;site.description&lt;/code&gt; in der config daher die Möglichkeit, die Standardwerte zu überschreiben. Meine Methode das zu lösen war, im &lt;code&gt;_data&lt;/code&gt;-Ordner eine Datei mit Namen &lt;code&gt;l10n.yml&lt;/code&gt; anzulegen, in der dann diese Parameter abgelegt sind. Um sie dann auch korrekt anzuzeigen, sind die &lt;code&gt;html&lt;/code&gt;-Dateien im &lt;code&gt;_includes&lt;/code&gt;-Ordner so anzupassen, dass Parameter dieser Datei statt der config eingelesen und durch die Templatesprache Liquid dann in html umgesetzt werden. Beispiel: &lt;code&gt;{{ site.data.ui-text[site.data.l10n.locale].follow_label | remove: &amp;quot;:&amp;quot; | default: &amp;quot;Follow&amp;quot; }}&lt;/code&gt;. Hier wird dann wie im Bild gezeigt dann in der deutschen Version &lt;strong&gt;FOLGEN:&lt;/strong&gt; angezeigt und in der englischen &lt;strong&gt;FOLLOW:&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;h3 id="sprachauswahl"&gt;Sprachauswahl&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-14_translations_polyglot_flags.jpg" alt="image: footer section of this website" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Natürlich möchte ich die Möglichkeit geben, die Sprache der Website selbst zu wählen. Ich habe mich hier für das Sprachkürzel sowie die entsprechende Flagge entschieden, welche im unteren Bereich einer jeden Seite eingeblendet werden. Klickt man hier drauf, so wird die Seite in der entsprechenden Sprache neu geladen. Dafür war es notwendig, einen &amp;ldquo;language-switcher&amp;rdquo; in Liquid zu schreiben.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// _includes/language-switcher.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&lt;span style="color:#f92672"&gt;%&lt;/span&gt; raw &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; lang in site.languages &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign url_parts &lt;span style="color:#f92672"&gt;=&lt;/span&gt; page.url &lt;span style="color:#f92672"&gt;|&lt;/span&gt; split: &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign lang_code &lt;span style="color:#f92672"&gt;=&lt;/span&gt; lang &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign collection_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; url_parts[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign post_url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; url_parts &lt;span style="color:#f92672"&gt;|&lt;/span&gt; last &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; lang &lt;span style="color:#f92672"&gt;==&lt;/span&gt; site.default_lang &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; collection_name &lt;span style="color:#f92672"&gt;==&lt;/span&gt; post_url &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign lang_url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: post_url &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign lang_url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: collection_name &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: post_url &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; endif &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; url_parts.size &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign lang_url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: lang_code &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; collection_name &lt;span style="color:#f92672"&gt;==&lt;/span&gt; post_url &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign lang_url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; lang_url &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: post_url &lt;span style="color:#f92672"&gt;%&lt;/span&gt;} &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; assign lang_url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; lang_url &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: collection_name &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: lang_url &lt;span style="color:#f92672"&gt;|&lt;/span&gt; append: post_url &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; endif &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#f92672"&gt;%&lt;/span&gt; endif &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;a href&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; {{ lang_url }}&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;{{ site.data[lang].l10n.lang_name }}&lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;a&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&lt;span style="color:#f92672"&gt;%&lt;/span&gt; endfor &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&lt;span style="color:#f92672"&gt;%&lt;/span&gt; endraw &lt;span style="color:#f92672"&gt;%&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hierfür wird die aktuelle &lt;code&gt;url&lt;/code&gt; am Slash in Einzelteile zerlegt. Anschließend der letzte Teil der url als Artikelname &lt;code&gt;post_url&lt;/code&gt; gesetzt und angenommen, dass der erste Teil der URL der collection entspricht, z.B. &lt;code&gt;posts-hardware&lt;/code&gt;. Die anschließende Logik setzt die Sprachkürzel je nach Bedarf in die url ein, sodass je nach Verwendung einer collection oder ohne die entsprechende Zielurl korrekt erzeugt wird.&lt;/p&gt;&#10;&lt;h2 id="veröffentlichung"&gt;Veröffentlichung&lt;/h2&gt;&#10;&lt;p&gt;Ich war echt stolz auf diesen Umbau. Ich finde, dass sich alles gut einfügt und ich mit der Zweisprachigkeit meinen Nutzerkreis gut erweitern kann. Der Nachteil für mich ist die zusätzliche Übersetzungsarbeit, die von nun an bei jedem Artikel auf mich zukommt.&lt;/p&gt;&#10;&lt;p&gt;Als ich diese Seite das erste mal auf die Github Page hochlud, merkte ich, dass das so überhaupt nicht funktioniert. Github Pages unterstützt Polyglot nämlich nicht, sodass plötzlich alle Artikel unabhängig von ihrer Sprache auf einmal auf meiner Website erscheinen.&lt;/p&gt;&#10;&lt;p&gt;Nun blieben mir nur zwei Möglichkeiten: &lt;a href="https://blog.schallbert.de/struggling-github-actions/"&gt;Eigene Github Actions&lt;/a&gt; für das Deployment aufzusetzen - oder ein Umzug auf einen eigenen Server. Ich habe mich für Letzteres entschieden, da die Github-Server für diese Seite meines Wissens nach in UK stehen und und ich mit einem Server in Deutschland lieber auf der sicheren Seite wäre.&lt;/p&gt;&#10;&lt;p&gt;Der eigentliche Umbau ist aber deutlich zu groß, um ihn in diesem Post abzuhandeln - dafür gibt es ein &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;eigenes Softwareprojekt&lt;/a&gt;.&lt;/p&gt;&#10;</description></item><item><title>Kleinteile fräsen</title><link>https://blog.schallbert.de/milling-small-parts/</link><pubDate>Wed, 01 Nov 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/milling-small-parts/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-01_smallparts-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Milled small part, size comparison to a coin"&#10; title="Kleinteile fräsen" /&gt;&#10;&lt;h2 id="warum-kleine-teile-überhaupt-fräsen"&gt;Warum kleine Teile überhaupt fräsen?&lt;/h2&gt;&#10;&lt;p&gt;Es ist meist schwierig, sehr kleine Teile zu fertigen, zudem wenn sie nur in geringer Stückzahl benötigt werden. Spritzgießen lohnt sich hier nicht und 3D-Drucker wie sie im Hobbybereich eingesetzt werden sind erfüllen nicht die Toleranzanforderungen für meine Passungen. Die Laserschneider, auf die ich Zugriff habe, können das Teil ebenfalls nicht fertigen, weil es verschiedene Oberflächenhöhen hat. Auf die Option, das Teil in Fernost anfertigen zu lassen, möchte ich verzichten.&lt;/p&gt;&#10;&lt;p&gt;Dennoch: Diese Teile können auf einer Fräsmaschine gefertigt werden. Dieses Video zeigt, wie:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/64TS5d5cp2eNqf8SSPvY77" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Kleinteile fräsen&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="welche-ausstattung-muss-die-maschine-dafür-besitzen"&gt;Welche Ausstattung muss die Maschine dafür besitzen?&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Einen Vakuumtisch&lt;/li&gt;&#10;&lt;li&gt;Eine Vakuumpumpe, vorzugsweise mit hohen Unterdrücken&lt;/li&gt;&#10;&lt;li&gt;Etwas Frischhaltefolie&lt;/li&gt;&#10;&lt;li&gt;Ein Vakuumvlies&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="wofür-ist-dieses-kleinteil"&gt;Wofür ist dieses Kleinteil?&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-01_smallparts_soapbutler.jpg" alt="Image: Video splash preview with soapbutler" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das im Video gefertigte Teil gehört zu einem meiner &lt;a href="https://blog.schallbert.de/projects/seifenbutler/"&gt;Seifenbutler&lt;/a&gt;. Die aktuelle Version kann an jeder handelsüblichen Duschstange mit Durchmesser von &lt;code&gt;18-25mm&lt;/code&gt; befestigt werden und benötigt dieses Teilchen zum Klemmen. Es drückt sich an der Rückseite an die Stange und sorgt so dafür, dass der Seifenbutler weder verrutscht noch wackelt.&lt;/p&gt;&#10;&lt;p&gt;Mit diesem Upgrade gegenüber früheren Versionen ist der Seifenbutler nicht nur in der Lage, festes Shampoo zu halten, sondern auch die viel schwereren Shampooflaschen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-11-01_soapbutler_shampoobottle.jpg" alt="Image: black soapbutler holding a shampoo bottle" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der &amp;ldquo;Klemmer&amp;rdquo; muss ohne merkliches Spiel in seine Führung passen und dennoch leichtgängig zu verschieben sein, um sich an verschiedene Duschstangendurchmesser anpassen zu können.&lt;/p&gt;&#10;&lt;h2 id="was-muss-noch-beachtet-werden"&gt;Was muss noch beachtet werden?&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Frischhaltefolie: Wenn Teile ausgeschnitten werden, sollten sie nicht zu groß sein. Ansonsten wird die Folie vom Fräser mitgerissen und hinterlässt hässliche Spuren im Werkstück. Außerdem kann sie eine Unwucht erzeugen, die bei hohen Drehzahlen zu Beschädigung des Fräsers oder sogar der Maschine führen kann. Um das Einziehen zu vermeiden hilft das Anbringen eines kleinen Stückes doppelseitigen Klebebandes auf dem auszuschneidenden Teil.&lt;/li&gt;&#10;&lt;li&gt;Vakuumvlies: Das Vlies wird bei Anlegen des Vakuums auf weniger als die Hälfte seiner Höhe zusammengepresst. Es ist also erforderlich, die Werkstückhöhe bei angelegtem Vakuum abzunullen.&lt;/li&gt;&#10;&lt;li&gt;Nicht ganz durchfräsen sondern eine dünne Materialschicht stehen lassen. Ihre Stärke hängt vom bearteiten Material ab. Bei PMMA lasse ich &lt;code&gt;0.3mm&lt;/code&gt; stehen, bei Holz &lt;code&gt;0.7mm&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Ich berücksichtige die Frischhaltefolie (etwa &lt;code&gt;10µm&lt;/code&gt; stark) nicht beim Einmessen der Werkstückhöhe, da sie sich in der Größenordnung meiner Maschinentoleranz befindet. Dickere Schutzfolien (&lt;code&gt;20-30µm&lt;/code&gt;), wie sie z.B. bei Plexiglas und Dibond verwendet werden, hingegen schon - zumal sie doppelseitig aufgebracht sind.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>DIY Kantenfinder</title><link>https://blog.schallbert.de/touching-off/</link><pubDate>Sun, 21 May 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/touching-off/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-05-21_edgefinder-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: DIY low-cost edge finder"&#10; title="DIY Kantenfinder" /&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-05-21_edge_taperedtool.jpg" alt="Image: getting XY0 of a workpiece visually" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Als CNC-Bediener muss ich bei manchen Arbeiten genau wissen, wo sich mein Werkstück befindet. Daher benötige ich in solchen Fällen die &lt;code&gt;X/Y&lt;/code&gt;-Werte einer Ecke des Werkstücks im Maschinenkoordinatensystem. Bisher habe ich dieses Problem mittels eines V-Fräsers gelöst, dessen Spitze ich manuell auf die Kante des Werkstücks verfahren und immer wieder mit Augenmaß geprüft habe, ob die Positionen übereinstimmen.&lt;/p&gt;&#10;&lt;h2 id="das-problem"&gt;Das Problem&lt;/h2&gt;&#10;&lt;p&gt;Obwohl diese manuelle Methode relativ schnell und unkompliziert anzuwenden ist, so ist sie für die meisten Aufträge nicht mehr genau genug. Als ich in die doppelseitige Bearbeitung eingestiegen bin &lt;a href="https://blog.schallbert.de/cut-dibond/"&gt;wie damals mit Dibond&lt;/a&gt; war der Versatz so groß, dass ich die ersten Teile nur noch entsorgen konnte.&lt;/p&gt;&#10;&lt;h2 id="lösung"&gt;Lösung&lt;/h2&gt;&#10;&lt;p&gt;Ich habe mein Problem mit dem Hersteller meiner Fräsmaschine besprochen - in der Annahme, dass ich einen elektrischen, in die Spindel einzuspannenden Kantentaster &amp;ldquo;3D finder&amp;rdquo; empfohlen bekommen würde und nun wieder viel Geld und Arbeit in Kauf und Einrichtung eines solchen Messwerkzeugs fließen müsse. Da lag ich aber falsch. Stattdessen erhielt ich einen Tipp, wie ich einen einfachen aber genauen Kantenfinder bauen kann, der zudem praktisch nichts kostet.&lt;/p&gt;&#10;&lt;h3 id="einzelteile"&gt;Einzelteile&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-05-21_build.jpg" alt="Image: Things needed to construct the edge finder" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Roy sagte &amp;ldquo;Na ja, das Einzige, was du dafür brauchst, ist ein Kugellager. Befestige es auf einer Achse und wechsle sie in die Spindel ein wie jedes andere Werkzeug auch. Lasse die Spindel im niedrigen Drehzahlbereich laufen und bewege die Maschine langsam in Richtung Werkstückkante. Sobald der äußere Ring des Kugellagers nicht mehr mitläuft, hast du die Kante gefunden.&amp;rdquo; Alles was ich dann noch tun muss, ist, den Radius des Kugellagers auf die Maschinenposition zu addieren und ich habe die Position bestimmt.&lt;/p&gt;&#10;&lt;h3 id="aufbau"&gt;Aufbau&lt;/h3&gt;&#10;&lt;p&gt;Ich fand die Idee genial. Da ich leider kein gut sortiertes Metallfachgeschäft in der Nähe habe, durchwühlte ich den Haushalt nach einem Kugellager. Voilà, meine Kinder haben einen Fidget Spinner, den sie eine Weile nicht mehr benutzt haben. Also habe ich ihn&amp;hellip; stibitzt. Er enthält ein Kugellager mit 22mm Durchmesser für eine 8mm Welle.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-05-21_edgefinder.jpg" alt="Image: The mounted edge finder" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hierfür bot sich der Schaft eines stumpfen 8mm Bohrers an. Also schnitt ich ihn in der Mitte durch, setzte den Schaft in das Kugellager ein und befestigte ihn mit einem Tropfen Kleber - Fertig!&lt;/p&gt;&#10;&lt;p&gt;Update: Kurz nach Veröffentlichung dieses Artikels schrieb ein Leser, dass alte Inline Skates oder Roller auch hervorragende Spender für Kugellager im Haushalt seien.&lt;/p&gt;&#10;&lt;h2 id="kanten-finden"&gt;Kanten finden&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Wechseln Sie den selbstgebauten Kantenfinder in die Spindel ein und verfahren sie in die Nähe des Werkstücks&lt;/li&gt;&#10;&lt;li&gt;Schalten Sie die Spindel bei niedriger Drehzahl ein, z.B. &lt;code&gt;S500....S3000&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Optional: Für eine einfachere Positionsbestimmung wechseln Sie in das Maschinenkoordinatensystem&lt;/li&gt;&#10;&lt;li&gt;Lassen Sie die Maschine langsam in Richtung des Werkstücks fahren, z.B. mit dem Kommando &lt;code&gt;G01 X&amp;lt;endpunktImWerkstück&amp;gt; F1&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Drücken Sie auf &lt;code&gt;Stop/Pause&lt;/code&gt;, sobald das Lager sich nicht mehr mitdreht&lt;/li&gt;&#10;&lt;li&gt;Notieren Sie sich die Maschinenkoordinaten bzw. verschieben das Arbeitskoordinatensystem der angetasteten Achse &lt;code&gt;G92&lt;/code&gt; auf diesen Punkt&lt;/li&gt;&#10;&lt;li&gt;Wiederholen Sie ab Schritt 2 für die nächste Achse&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2023-05-22_edgefinder_action.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Einfacher Kantenfinder im Einsatz.&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Hardware Test</title><link>https://blog.schallbert.de/testing-hardware/</link><pubDate>Sun, 23 Apr 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/testing-hardware/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-04-23_subsystem_test-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Subsystem boundary diagram"&#10; title="Hardware Test" /&gt;&#10;&lt;h2 id="ein-neuer-antrieb"&gt;Ein neuer Antrieb&lt;/h2&gt;&#10;&lt;p&gt;Kürzlich habe ich Zerspanobert ein &lt;a href="https://blog.schallbert.de/projects/spindle-upgrade/"&gt;Spindel-Upgrade&lt;/a&gt; gegönnt. Ich bin jetzt auf eine Drehstrom-Asynchronmaschine umgestiegen, welche mit einem 24V-Lüfter fremdgekühlt wird. Für den Betrieb im benötigten Drehzahlbereich musste ich in einen Frequenzumrichter investieren, der in meinem Falle wie beim vorigen Fräsmotor auch über ein analoges &lt;code&gt;0-10V&lt;/code&gt; Signal gesteuert wird. Aus Sicht der CNC ist die neue Spindel bis auf eine Ausnahme identisch mit der alten: Der Frequenzumrichter kann nun ein &amp;ldquo;Not-Aus&amp;rdquo; Signal erzeugen und an die Steuerung melden, z.B. wenn die Drehzahl ungeplant abfällt, zu lange ein Überlastzustand auftritt etc.&lt;/p&gt;&#10;&lt;p&gt;Wie man sich vorstellen kann ist das neue Subsystem rund um die Spindel deutlich komplizierter als das alte.&lt;/p&gt;&#10;&lt;h2 id="warum-extra-tests"&gt;Warum extra Tests?&lt;/h2&gt;&#10;&lt;p&gt;Mit höherer Systemkomplexität erhöht sich auch das Risiko für Fehlfunktionen. Es sind eine Menge mehr elektrischer, elektronischer und mechanischer Bauteile verbaut als bei meinem Fräsmotor, die selbst Defekte aufweisen oder bei falscher Verdrahtung selbst in Rauch aufgehen können. Da ich als Systemdesigner selbst nur ein Mensch bin der Fehler macht, wäre es nicht besonders schlau, das System irgendwie zusammenzustecken und dann direkt auszuprobieren.&lt;/p&gt;&#10;&lt;p&gt;Während ich die Verdrahtung erstellte, überlegte ich mir also, wie ich das System effizient testen könne, ohne etwas zu zerstören.&lt;/p&gt;&#10;&lt;h2 id="teststrategie"&gt;Teststrategie&lt;/h2&gt;&#10;&lt;p&gt;Ich habe die unterschiedlichen Testbereiche von meiner Arbeit in der Softwareindustrie geborgt. Sie passen hier denke ich recht gut hinein:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Unit&lt;/strong&gt;: Ein Test, der das Verhalten einer Komponente des Systems isoliert überprüft. Alle externen Einflüsse wurden entweder entfernt oder durch Komponenten ersetzt, auf die im Test selbst problemlos zugegriffen werden kann. Beispiel: Prüfe, ob der Lüfter bei Anlegen seiner Betriebsspannung wie erwartet im richtigen Drehsinn anläuft.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt;: Diese Tests prüfen das Zusammenspiel verschiedener Komponenten, welche gemeinsam eine Funktion abbilden. Beispiel: Überprüfe, ob die Luftkühlung der Spindel auch nach Abschalten derselben für eine bestimmte Zeit nachläuft.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Subsystem&lt;/strong&gt;: Verifiziert die korrekte Interaktion aller Komponenten eines Subsystems. Beispiel: Die Spindeldrehzahl kann über eine analoge Spannung am Eingang des Frequenzumrichters verändert werden.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;System (End2End)&lt;/strong&gt;: Diese Tests werden aus Kundensicht ausgeführt und durchlaufen das komplette System. Beispiel: Wenn ich den Not-Aus an der CNC betätige, muss der Frequenzumrichter die Spindel umgehend sicher zum Stillstand bringen.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-04-23_test_scopes.jpg" alt="Image: different test scopes of my CNC system" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="teste-früh"&gt;Teste früh&lt;/h3&gt;&#10;&lt;p&gt;Aus meiner Erfahrung macht es Sinn, die Tests zum frühestmöglichen Zeitpunkt durchzuführen. Nachdem die Spindel geliefert worden war, konnte ich auf diese Weise sofort nachvollziehen, ob alle Lager leichtgängig sind, ob der Lüfter funktioniert und ob der elektrische Widerstand der Statorwicklungen im erwarteten Bereich liegt. Alle diese Tests waren durchführbar noch bevor ich mit dem Bau des Schaltschranks überhaupt begonnen hatte.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2023-04-23-blowertest.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Unit test: Lüfter&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die schnell und einfach durchführbaren Unit Tests können einen Haufen Zeit sparen, denn diese Fehlerquellen kann man später bei der Inbetriebnahme direkt ausschließen. Und findet man doch etwas so früh, kann man sich direkt um Ersatz kümmern und hält nicht den Einrichtbetrieb auf.&lt;/p&gt;&#10;&lt;h3 id="teste-von-unten-nach-oben"&gt;Teste &amp;ldquo;von unten nach oben&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-04-23_test_pyramid.jpg" alt="Image: my test pyramid" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Bei den vier Testebenen versuche ich, so viel wie möglich mit einfachen und schnellen Unit Tests abzudecken. Was hier noch nicht geht, wird jeweils auf der nächsthöheren Ebene abgearbeitet und so weiter. Am Ende möchte ich nur ein paar Wenige Tests aus Kundensicht durchführen, da diese meist einen größeren Aufwand für Vor- und Nachbereitung benötigen und außerdem zumeist nicht genau in der Lage sind, den Fehler zu lokalisieren.&lt;/p&gt;&#10;&lt;p&gt;Dies deckt sich mit dem Konzept der &lt;a href="https://martinfowler.com/testing/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Testpyramide&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, welche in der Softwareentwicklung des Öfteren herangezogen wird.&lt;/p&gt;&#10;&lt;h2 id="test-plan"&gt;Testplan&lt;/h2&gt;&#10;&lt;p&gt;Für meine privaten Hobbyprojekte schreibe ich üblicherweise keinen Testplan. Meine Systeme sind meist deutlich einfacher als dieses. Außerdem benötigen nur die wenigsten potentiell gefährliche Niederspannung wie in diesem Fall.&lt;/p&gt;&#10;&lt;p&gt;Um das System besser zu visualisieren habe ich mal einen groben Schaltplan mit allen Komponenten sowie relevanten Ein- und Ausgängen gezeichnet.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-04-23_system_layout.jpg" alt="Image: motor spindle subsystem layout" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Von diesem Bild habe ich einen Testplan abgeleitet. Hierbei habe ich versucht, die Interaktionen jeder Komponente mit jeder anderen abzudecken und wenn möglich versucht, dem Komponententest den Vorrang zu geben.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-04-23_test_plan.jpg" alt="Image: A test plan for the motor spindle subsystem" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2023-04-23-powersupplytest.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Unit test: Stromversorgung &amp;#43;24V OK&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hier überprüfe ich die Versorgungsspannung für den 24V-Lüfter&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2023-04-23-relaytest.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Unit test: Abfallverzögerung&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Mit diesem Test stelle ich sicher, dass die Konfiguration der Anzugs- und Abfallverzögerung wie gewünscht funktioniert. Für den Test habe ich kürzere Zeiten gewählt als später im produktiven Betrieb.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2023-04-23-vfdtest.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Integrationstest: FU Inbetriebnahme&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hier prüfe ich, ob der Frequenzumrichter die Spindel mit der korrekten Drehrichtung und mit unterschiedlichen Drehzahlen betreiben kann.&lt;/p&gt;&#10;&lt;h2 id="ausführung-der-tests"&gt;Ausführung der Tests&lt;/h2&gt;&#10;&lt;p&gt;Es gibt natürlich noch viele weitere hardwarebasierte Tests die ich oben nicht erwähnt habe. Dinge wie &amp;ldquo;ist die Spindel korrekt mit der Z-Achse ausgerichtet?&amp;rdquo; &amp;ldquo;Habe ich alle Schrauben mit dem vorgeschriebenen Drehmoment angezogen?&amp;rdquo; &amp;ldquo;Sind alle Stecker angeschlossen und verriegelt?&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Bevor ich nach dem Umbau also das erste mal wieder Späne mache, habe ich den Testplan komplett abgearbeitet und tatsächlich ein Problem auf Systemebene entdeckt: Ich habe den Analogausgang zur Vorgabe der Spindeldrehzahl falsch verdrahtet, sodass andere Pins das Signal führten als geplant. Dies führte dazu, dass sich die Spindel trotz Freigabe nicht starten ließ, obwohl die CNC &amp;ldquo;Spindel läuft&amp;rdquo; ausgab.&lt;/p&gt;&#10;&lt;p&gt;Dank der vorausgehenden Tests am Frequenzumrichter (&amp;ldquo;Reacts to speed command&amp;rdquo;) habe ich nur Minuten benötigt um den Fehler zu finden und zu beheben - denn ich wusste, am FU liegt&amp;rsquo;s nicht.&lt;/p&gt;&#10;&lt;h2 id="dokumente"&gt;Dokumente&lt;/h2&gt;&#10;&lt;p&gt;Falls Sie planen, eine Frequenzumrichtergesteuerte Motorspindel an Ihrer Maschine einzusetzen, können Sie sich meinen Testplan kostenlos herunterladen.&#10;In case you plan on adding a VFD-controlled spindle motor to your machine, please find my full test plan below.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/assets/docs/SpindleTests.pdf"&gt;VFD-controlled spindle motor test plan&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Aber bitte betrachten Sie diesen Plan mehr als Unterstützung denn als vollumfängliche Prüfung und beachten Sie vor allem die üblichen Warnhinweise:&lt;/p&gt;&#10;&lt;p&gt;⚠️ &lt;strong&gt;Warnung vor gefährlicher Spannung&lt;/strong&gt; ⚠️&lt;/p&gt;&#10;&lt;p&gt;Dieses System sollte nur von Profis konstruiert, zusammengebaut, angepasst, in Betrieb genommen und schließlich fachgerecht geprüft werden. In diesem Falle meine ich mit &amp;ldquo;Profi&amp;rdquo; jemanden, der eine abgeschlossene Berufsausbildung zum Elektriker oder Elektroniker, optimalerweise in der Fachrichtung Anlagenbau/Betriebstechnik sowie Erfahrung im Umgang mit Industrieelektronik vorzuweisen hat. Vermeiden Sie insbesondere, unter Spannung stehende Teile zu berühren oder an einer der Komponenten zu arbeiten, bevor der Frequenzumrichter nach dem Abschalten genügend Zeit hatte, seine Pufferkondensatoren zu entladen.&lt;/p&gt;&#10;</description></item><item><title>Dibond Tests</title><link>https://blog.schallbert.de/cut-dibond-tests/</link><pubDate>Fri, 10 Mar 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cut-dibond-tests/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_dibond_test-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Dibond engrave test runs"&#10; title="Dibond Tests" /&gt;&#10;&lt;p&gt;Anmerkung: Die in diesem Artikel analysierten Gravuren sind recht fein. Die Schrifthöhe beträgt zwischen 3 und 6mm, und die Größe des Werkstückes liegt bei 80x80mm. Blickt man von einer Distanz von 30cm oder mehr auf das Werkstück, so sind die rauen Kanten etc. nicht mehr wahrnehmbar.&lt;/p&gt;&#10;&lt;h2 id="weitere-qualitätsverbesserungen-bei-arbeiten-mit-dibond"&gt;Weitere Qualitätsverbesserungen bei Arbeiten mit Dibond&lt;/h2&gt;&#10;&lt;p&gt;Die in meinem &lt;a href="https://blog.schallbert.de/cut-dibond/"&gt;letzten Fräsprojekt&lt;/a&gt; in Dibond erzielten, nur mittelmäßigen Ergebnisse wollte ich so nicht auf mir sitzen lassen. Deshalb habe ich mögliche Einflussgrößen ersonnen und niedergeschrieben.&lt;/p&gt;&#10;&lt;p&gt;In diesem Artikel versuche ich, sie Stück für Stück zu analysieren und schließlich, falls erforderlich, auszuschalten.&lt;/p&gt;&#10;&lt;h3 id="mögliche-gründe-für-schlechte-qualität"&gt;Mögliche Gründe für schlechte Qualität&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cut-dibond-tests/#cutter-inspection"&gt;Verschlissene Fräser&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cut-dibond-tests/#cutter-geometry"&gt;Falsche Schneidengeometrie&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cut-dibond-tests/#speed-feed"&gt;Ungeeignete Parameter&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cut-dibond-tests/#milling-style"&gt;Gleichlauf statt Gegenlauf&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cut-dibond-tests/#engrave-depth"&gt;Probleme mit der Gravurtiefe&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cut-dibond-tests/#machine-vibrations"&gt;Maschinenvibrationen&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cut-dibond-tests/#spindle-mount"&gt;Fräsmotorhalterung zu weich&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cut-dibond-tests/#spindle-imbalance"&gt;Unwucht&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="cutter-inspection"&gt;Untersuchung der Fräser&lt;/h3&gt;&#10;&lt;p&gt;Ich bin kein Fräserexperte, aber ich kann mir kaum vorstellen, dass ein VHM-Fräser nach nur dreißig Arbeitsminuten in Kontakt mit &lt;code&gt;0.3mm&lt;/code&gt; starkem &lt;code&gt;AlMg1&lt;/code&gt; stumpf sein soll (Na gut, da mögen ein paar Zusätze drin sein für die gebürstete Oberfläche samt Färbung). Die Schneiden sehen recht sauber aus. Das Einzige, was mir auffällt, ist eine dünne Schicht Aluminium, welche der Schneide anzuhaften scheint.&lt;/p&gt;&#10;&lt;p&gt;Diese habe ich vorsichtig mit einem Cuttermesser entfernt. Nun sehen sie wieder normal aus.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Update Okt-23&lt;/em&gt;: Ich habe mit denselben Graviersticheln - Sie sind inzwischen ca. 4h gelaufen - etwas in Acrylglas (PMMA) gefertigt. Die Schnittkanten sind hier rauer als bei brandneuen Gravierfräsern.&lt;/p&gt;&#10;&lt;h3 id="cutter-geometry"&gt;Schneidengeometrie&lt;/h3&gt;&#10;&lt;p&gt;Die für diesen Test verwendeten Fräser haben keine Spiralnut zur Spanabfuhr, sondern sind auf der Eingriffseite offen ausgeführt. Daher ist aber auch stets die komplette Schneide im Eingriff und ich frage mich, ob das einen Unterschied macht. Vielleicht wäre eine gewundene Schneide (Habichtschnabel, Helix) hier besser geeignet?&lt;/p&gt;&#10;&lt;p&gt;Hintergrund dieser These ist, dass mein CAM-Werkzeug für Gravuren keine Pendelbewegung oder Helix zum Eintauchen verwendet sondern stets gerade in das Material eintaucht. Wenn man bei den Bildern unten (z.B. in der Vergrößerung des &lt;code&gt;t&lt;/code&gt;) genau hinsieht, so erkennt man Kratzer im Klarlack, wo der Fräser ins Material eingetaucht ist und die Späne nicht hat rechtzeitig abführen können.&lt;/p&gt;&#10;&lt;p&gt;Auf der anderen Seite werden spiralgenutete Fräser das Material nach oben ziehen. Da das Aluminiumblech lediglich thermisch mit dem PE-Kern verklebt ist, könnte die Haltekraft besonders bei filigranen Gravuren durchaus zu gering sein und sich die Aluminiumschicht ablösen - die Gefahr ist besonders hoch bei Inseln, wie es sie z.B. bei den Buchstaben &lt;code&gt;e, a, o, g, p...&lt;/code&gt; gibt.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Kurzgesagt&lt;/em&gt; Gravierfräser mit Spiralnut kaufen, ausprobieren und die Fräsergebnisse hier dokumentieren&lt;/p&gt;&#10;&lt;h3 id="speed-feed"&gt;Ungeeignete Parameter für Vorschub und Drehzahl&lt;/h3&gt;&#10;&lt;p&gt;Ich habe die empfohlenen Parameter nochmal geprüft, mit dem Hersteller der Fräser besprochen und sie auch in ein paar Berechnungstools eingegeben. Optimalerweise werden sie bei noch höherer Drehzahl als &lt;code&gt;24000U/min&lt;/code&gt; betrieben, was an meiner Maschine allerdings das Maximum ist - also behalte ich das so bei.&lt;/p&gt;&#10;&lt;p&gt;Also bleibt mir jetzt noch der Vorschub zum Herumspielen übrig. Schnell eine Testgravur im CAD erstellt und im CAM dann mit verschiedenen Vorschüben abfahren lassen: &lt;code&gt;F600mm/min&lt;/code&gt;, &lt;code&gt;F800mm/min&lt;/code&gt; sowie &lt;code&gt;F1000mm/min&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_workpiece.jpg" alt="Image: My Test workpiece with tryout engravings" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;In diesem Bild kann man viele Dinge erkennen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Die Beschriftungen für verschiedene Vorschubgeschwindigkeiten sind so klein geraten, dass der Gravierstichel nicht einmal die Aluminiumschicht vollständig durchbricht. Vielmehr quetscht er das Aluminium in die darunter liegende Polyethylenschicht und zur Seite weg.&lt;/li&gt;&#10;&lt;li&gt;Obwohl ich die Vorschubgeschwindigkeit um bis zu 20% pro Umdrehung verändere, bleibt die Schnittqualität gleich. Dies bestätigt, dass für meinen Stichel in Dibond ein Vorschub von &lt;code&gt;F1000mm/min&lt;/code&gt; in Ordnung ist.&lt;/li&gt;&#10;&lt;li&gt;Es ist deutlich zu sehen, dass die äußeren Kanten der Gravur rau sind. Ich kann dies fühlen, wenn ich mit dem Finger über das Werkstück streiche.&lt;/li&gt;&#10;&lt;li&gt;Die inneren Ränder der Gravuren sind wirklich glatt. Sie glänzen im Gegensatz zu ihren matten Gegenstücken.&lt;/li&gt;&#10;&lt;li&gt;Die schwarzen, inneren Teile der Spirale sind sehr gleichmäßig und ohne Schnittspuren gefertigt.&lt;/li&gt;&#10;&lt;li&gt;Das große &lt;code&gt;T&lt;/code&gt; des Wortes &amp;ldquo;Test&amp;rdquo; sieht etwas seltsam aus, da senkrechte Kerben an der Schnittkante auftreten. Darauf kommen wir später noch zu sprechen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;&lt;em&gt;Kurzgesagt&lt;/em&gt;: Drehzahl- sowie Vorschubparameter sind OK und scheinen für mein Problem nicht die Ursache gewesen zu sein.&lt;/p&gt;&#10;&lt;h3 id="milling-style"&gt;Gleichlauf- vs. Gegenlauffräsen&lt;/h3&gt;&#10;&lt;p&gt;Um zu entscheiden, ob es bei der Verarbeitung von Aluminiumsandwichplatten eine bevorzugte &lt;a href="https://blog.schallbert.de/portal-milling/#milling-style"&gt;Fräsrichtung&lt;/a&gt; gibt, schauen wir uns die Spirale etwas genauer an:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_spiral.jpg" alt="Image: Detail view of the spiral, cut with the engrave bit" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die schmalere Linie wurde in einem Schritt gefertigt während die breitere einen zweiten Durchlauf mit nur &lt;code&gt;Z-0.1mm&lt;/code&gt; Tiefenzustellung hat.&#10;Sofort ist zu erkennen, dass die Gravur außen viel glattere Kanten hat als innen. Die Spirale wurde von außen nach innen mit einer rechtsdrehenden Spindel gefräst.&#10;Das bedeutet also, dass außen im Gegenlauf und innen im Gleichlauf gearbeitet wurde.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Kurzgesagt&lt;/em&gt; wird auf einer CNC zwar eher der Gleichlauf empfohlen, bei der Bearbeitung von Dibond ist unter Verwendung von Graviersticheln jedoch im Gegenlauf mit deutlich besseren Ergebnissen zu rechnen.&lt;/p&gt;&#10;&lt;h3 id="engrave-depth"&gt;Zu niedrige Gravurtiefe&lt;/h3&gt;&#10;&lt;p&gt;Wie die Bilder oben zeigen, sollte die Aluminiumschicht beim Gravieren satt durchbrochen werden. Nach ein paar weiteren Tests konnte ich die minimale Gravurtiefe bei &lt;code&gt;0.3mm&lt;/code&gt; Schichtstärke des Blechs bei etwa &lt;code&gt;0.5mm&lt;/code&gt; ermitteln.&lt;/p&gt;&#10;&lt;h3 id="zu-hohe-gravurtiefe"&gt;Zu hohe Gravurtiefe&lt;/h3&gt;&#10;&lt;p&gt;Diese Mutmaßung ergab sich bei meinem &lt;a href="https://blog.schallbert.de/cut-dibond/#quality-analysis"&gt;vorigen Experiment mit Dibond&lt;/a&gt;. Zum Vergleich habe ich hier zwei verschieden Dicke Gravurpfade gefertigt. Tiefere Gravuren werden tatsächlich etwas sauberer, wenn man sie mit einem Schlichtgang nochmal nachzieht. Für schmale Gravuren halte ich die Verbesserung für vernachlässigbar. Daher ist eine Hohe Gravurtiefe meiner Einschätzung nach kein Problem.&lt;/p&gt;&#10;&lt;h3 id="machine-vibrations"&gt;Vibrationen&lt;/h3&gt;&#10;&lt;p&gt;Betrachtet man erneut die Kerben und Unregelmäßigkeiten an der Schnittkante, so liegt der Schluss nahe, dass sie sich aus der gebürsteten Oberfläche ergeben. Tatsächlich nämlich wird die Oberfläche aber nicht gebürstet, sondern unter hohem Druck mit Hilfe von Walzen geprägt. Die Kerben könnten also durch Dichteunterschiede im Material begünstigt werden. Es gibt aber eine weitere mögliche Ursache: Maschinenvibrationen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_text.jpg" alt="Image: Test texts with different feed rates." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Texte wurden mit verschiedenen Vorschubwerten gefertigt. Die Zahlen geben hierbei den Vorschub an: &lt;code&gt;6&lt;/code&gt; bedeutet &lt;code&gt;600mm/min&lt;/code&gt; und so weiter. Ich habe noch ein weiteres Bild gemacht, was die Buchstaben nochmal stärker vergrößert darstellt. Hier zeigt sich, dass die Texte bei größerem Vorschub ein klein wenig besser aussehen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_ts.jpg" alt="Image: Macro zoom into the `t`&amp;#39;s of the text." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Im letzten halben Jahr habe ich viel mit den mechanischen Werten der Maschine experimentiert, um die Grenzen auszuloten. Ich habe damals recht hohe Werte für Geschwindigkeit und Beschleunigung festgelegt, da ich die Maschine für fähig hielt, diese zu unterstützen. &amp;ldquo;Unüblich hoch&amp;rdquo; nannte der Hersteller der Steuersoftware diese Werte, als ich ihn wegen eines anderen Problems kontaktierte (&lt;a href="https://blog.schallbert.de/projects/one-year-zerspanobert/#velocityerror"&gt;&amp;ldquo;Velocity was higher than max!&amp;rdquo;&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;Also habe ich ein weiteres Teststück designt, wo ich nur gerade Linien und Texte gravieren würde, sodass rechtwinklige Ecken entstehen an denen die Maschine auf null abbremsen und von dort wieder beschleunigen würde.&lt;/p&gt;&#10;&lt;p&gt;Dieses mal habe ich den Vorschub bei &lt;code&gt;F1000mm/min&lt;/code&gt; belassen, aber in jedem Durchgang die Beschleunigungswerte der Maschine verändert.&lt;/p&gt;&#10;&lt;p&gt;Dieses Experiment zeigt, dass die vertikalen Kerben bei geringerer Beschleunigung deutlich weniger sichtbar werden und unterhalb irgendwann sogar ganz verschwinden. Da ich die Maschine für diese Anpassung stets neustarten musste, waren Werkzeug und Z0-Höhe immer neu einzumessen. Möglicherweise hatte ich ein Staubkorn unter dem Werkzeuglängensensor bei der Vermessung für die geringste Beschleunigung, daher kommt die Gravur hier nicht ganz so gut hervor.&lt;/p&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Image: Front view of used V-cut bit. "&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_vcut_front.jpg" alt="Image: Front view of used V-cut bit. " class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Image: 45° Front view of used V-cut bit. It carries some residual aluminium material."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_vcut_front45.jpg" alt="Image: 45° Front view of used V-cut bit. It carries some residual aluminium material." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Image: Side view of used V-cut bit. The cutting edge looks sharp and polished."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_vcut_side.jpg" alt="Image: Side view of used V-cut bit. The cutting edge looks sharp and polished." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-3" aria-label="Image: Rear view of used V-cut bit. At the very tip, it is more shiny than at the rest of its body."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_vcut_back.jpg" alt="Image: Rear view of used V-cut bit. At the very tip, it is more shiny than at the rest of its body." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-4" aria-label="Image: Test text and numbers. At 800mm/s² acceleration, there are visible horizontal marks in the path and the quality of cut is poor."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_test800.jpg" alt="Image: Test text and numbers. At 800mm/s² acceleration, there are visible horizontal marks in the path and the quality of cut is poor." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-5" aria-label="Image: Test text and numbers. At 400mm/s² the highly detailed text does not look too good. There are chatter marks everywhere."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_test400.jpg" alt="Image: Test text and numbers. At 400mm/s² the highly detailed text does not look too good. There are chatter marks everywhere." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-6" aria-label="Image: Test text and numbers. Even with lowest acceleration, the problem is to stay. The aluminium layer tends to squash into the much softer polyurethane core material, creating mediocre results."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_test200.jpg" alt="Image: Test text and numbers. Even with lowest acceleration, the problem is to stay. The aluminium layer tends to squash into the much softer polyurethane core material, creating mediocre results." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Testergebnisse: Detailaufnahmen von Stichel und Material bei variabler Maschinenbeschleunigung.&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_vcut_front.jpg" alt="Image: Front view of used V-cut bit. " class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;The 6mm 90° 0.5m tip V-cut tool. At the cutting edge, there&amp;#39;s a little built-up material.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_vcut_front45.jpg" alt="Image: 45° Front view of used V-cut bit. It carries some residual aluminium material." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;It has been used for roughly 30minutes. The cutting edge looks intact.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_vcut_side.jpg" alt="Image: Side view of used V-cut bit. The cutting edge looks sharp and polished." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Minimal residues of Aluminium seem to stick to its tip.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-3" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_vcut_back.jpg" alt="Image: Rear view of used V-cut bit. At the very tip, it is more shiny than at the rest of its body." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Also here, the tip colour is different than the rest of the bit.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-4" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_test800.jpg" alt="Image: Test text and numbers. At 800mm/s² acceleration, there are visible horizontal marks in the path and the quality of cut is poor." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Result with high machine acceleration&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-5" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_test400.jpg" alt="Image: Test text and numbers. At 400mm/s² the highly detailed text does not look too good. There are chatter marks everywhere." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Result with medium machine acceleration&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-6" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_test200.jpg" alt="Image: Test text and numbers. Even with lowest acceleration, the problem is to stay. The aluminium layer tends to squash into the much softer polyurethane core material, creating mediocre results." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Result with low machine acceleration.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;p&gt;Beleuchten wir kurz, ob die Maschine bei diesen kleinen Strukturen überhaupt die Zielgeschwindigkeit von &lt;code&gt;F1000mm/min&lt;/code&gt; erreichen kann: Die Texthöhe beträgt &lt;code&gt;s0=6mm&lt;/code&gt;, sodass die Maschine bei &lt;code&gt;a=800mm/s²&lt;/code&gt;, etwa &lt;code&gt;t=F/a=20ms&lt;/code&gt; für die Beschleunigung auf den angepeilten Vorschub benötigt. Dabei legt sie eine Distanz von &lt;code&gt;s=a/2*t^2=0.17mm&lt;/code&gt; zurück. Daher wird für &lt;code&gt;s1=6-2*0.17=5.66mm&lt;/code&gt; mit voller Geschwindigkeit verfahren, z.B. für den vertikalen Strich im Buchstaben &lt;code&gt;t&lt;/code&gt;. Hier sind die kinematischen Gleichungen mal wieder sehr nützlich gewesen 😃&lt;/p&gt;&#10;&lt;p&gt;Jetzt wäre es noch interessant, die Eigenfrequenz der Maschine in X-Richtung zu bestimmen, denn hier treten die Kerben ja auf. Meine Überschlagsrechnung ergibt: &lt;code&gt;11&lt;/code&gt; Kerben auf &lt;code&gt;6mm&lt;/code&gt; Weg bei einer Geschwindigkeit von &lt;code&gt;F1000mm/min --&amp;gt; F16.67mm/s&lt;/code&gt; ergibt &lt;code&gt;fE=30Hz&lt;/code&gt;. Da die Anregung der Maschine in der &amp;ldquo;Ansteigenden&amp;rdquo; Halbwelle geschieht, sollte ich stets versuchen, die Maschine weit weg von &lt;code&gt;t=1/(2f) = 16ms&lt;/code&gt; auf Zielgeschwindigkeit zu bringen und so die Resonanzfrequenz vermeiden.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Kurzgesagt&lt;/em&gt;: Die kinematischen Parameter sollten - besonders bei weniger steifen und eher leichten Maschinen - fein auf die jeweilige Fräsmaschine angepasst werden. Hierbei it es hilfreich, die Eigenfrequenz der instabilsten Achse zu kennen und &amp;ldquo;weiträumig&amp;rdquo; zu umschiffen. Für meine Maschine habe ich festgestellt, dass eine Beschleunigung von &lt;code&gt;a=400mm/s²&lt;/code&gt; noch ausreichend zügig ist, meine Probleme mit am Werkstück sichtbaren Schwingungen aber löst.&lt;/p&gt;&#10;&lt;h3 id="spindle-mount"&gt;Halterung des Fräsmotors&lt;/h3&gt;&#10;&lt;p&gt;Ich wusste, dass dies bei &lt;a href="https://blog.schallbert.de/cnc-router-overload/"&gt;Überlastung der Maschine&lt;/a&gt; ein Problem darstellen würde. Für Gravurarbeiten jedoch fräst die Spindel quasi im Leerlauf. Daher klammere ich diesen Aspekt hier aus.&lt;/p&gt;&#10;&lt;h3 id="spindle-imbalance"&gt;Unwucht des Fräsers oder der Spannzange&lt;/h3&gt;&#10;&lt;p&gt;Wenn dies der Fall gewesen wäre, hätte ich ungewöhnliche Geräusche oder Vibrationen der Maschine wahrnehmen müssen. Da dies alles nicht vorlag, schließe ich Unwucht des Werkzeugs als Ursache aus.&lt;/p&gt;&#10;&lt;h2 id="maßnahmen"&gt;Maßnahmen&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Fräser nach Benutzung prüfen und reinigen, bei übermäßigem Verschleiß austauschen.&lt;/li&gt;&#10;&lt;li&gt;Vermeiden von Gleichlauf bei der Verarbeitung von Dibond. Die Gravurpfade lieber minimal weiter auslegen und dann beide Seiten der Gravur im Gegenlauf fertigen.&lt;/li&gt;&#10;&lt;li&gt;Bei höheren Gravurtiefen einen Schlichtgang hinzufügen&lt;/li&gt;&#10;&lt;li&gt;Maschinenbeschleunigung in Verbindung mit Vorschüben bei weniger steifen Maschinen drosseln.&lt;/li&gt;&#10;&lt;li&gt;Vor Einstieg in die Fertigung ein paar Probefräsungen durchführen.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-10_dibondmetallic.jpg" alt="Image: Dibond metallic, cut after all improvements were applied" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dieses Teil wurde nach Ausführung der oben genannten Maßnahmen gefertigt.&lt;/p&gt;&#10;</description></item><item><title>Aluminiumverbundmaterial</title><link>https://blog.schallbert.de/cut-dibond/</link><pubDate>Thu, 02 Mar 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cut-dibond/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_btc_beermat-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Engraving Dibond, required manual rework"&#10; title="Aluminiumverbundmaterial" /&gt;&#10;&lt;h2 id="experimente-mit-dibond"&gt;Experimente mit Dibond&lt;/h2&gt;&#10;&lt;p&gt;Ein Freund von mir gründet gerade eine Unternehmung. Er wollte etwas haben, was er seinen Kunden geben kann, etwas, an das sie sich erinnern und so erneut seine Dienste in Anspruch nehmen würden. Es sollte nicht der Standard-Kugelschreiber oder die Tasse sein. Sondern etwas, das etwas näher an dem dran ist, was er selbst tut. Irgendwas metallisches. Vielleicht eine Visitenkarte aus Metall, mit dreidimsionalen Touch. Etwas Graviertes.&lt;/p&gt;&#10;&lt;p&gt;Als ich passende Materialien recherchierte, stieß ich auf Aluminiumverbundplatten. Dies sind Sandwichplatten, die einen Kern aus &lt;a href="https://en.wikipedia.org/wiki/Polyethylene" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Polyethylen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; besitzen, welcher auf beiden Seiten mit dünnen Aluminiumblechen kaschiert sind. Solche Platten sind bekannt unter den Markennamen Dibond®, Dilite®, Alcubond® etc. und werden blank oder beschichtet, lackiert oder sogar mit strukturierter Oberfläche angeboten.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Der PE-Kern ist schwarz durchgefärbt, also könnte es sich prima für Gravurarbeiten eignen&amp;rdquo;, dachte ich.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Also habe ich ein paar Muster bestellt und sie meinem Freund gezeigt. Er war völlig aus dem Häuschen. Und so begann es.&lt;/p&gt;&#10;&lt;h3 id="der-untersetzer"&gt;Der Untersetzer&lt;/h3&gt;&#10;&lt;p&gt;Wir überlegten, was wir alles mit diesem Material anstellen könnten. Es sollte leicht zu designen sein und nicht zu groß werden, sodass es leicht als Werbegeschenk weitergegeben werden kann.&lt;/p&gt;&#10;&lt;p&gt;Ich wusste, dass Alu-Sandwichplatten zu dreidimensionalen Objekten gefaltet werden können (&lt;a href="https://www.youtube.com/watch?v=9h0RVwTpabk" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Youtube video link&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) - Aber das schien uns zu kompliziert für den Anfang. Daher war die nächste Idee:&lt;/p&gt;&#10;&lt;p&gt;Untersetzer gravieren. Rund oder quadratisch mit abgerundeten Ecken, sogar besondere Formen sind möglich.&lt;/p&gt;&#10;&lt;p&gt;Ich begann, ein paar meiner Gravurfräser für ein paar Experimente auf das Material loszulassen. Die Qualität sah OK aus, war aber noch lange nicht perfekt. Die Aluminiumschicht neigt dazu, zu verschmieren und möchte dem Fräser ausweichen, also drückt es sich beim Eintauchen (90°) in das weichere Polyethylen ein und benötigt so höhere Gravurtiefen als die Blechdicke allein vermuten lassen würde.&lt;/p&gt;&#10;&lt;p&gt;Der Freund von mir meinte jedoch, ich solle es einfach weiter probieren. Und so bestellte ich ein paar Platten und setzte die Experimente fort.&lt;/p&gt;&#10;&lt;h3 id="fräser-vorschübe-drehzahl"&gt;Fräser, Vorschübe, Drehzahl&lt;/h3&gt;&#10;&lt;p&gt;Da die Untersetzer filigrane Teile kleiner als 100x100mm sind, nutze ich kleine Fräser:&lt;/p&gt;&#10;&lt;p&gt;| VHM-Fräser | Schneiden | ⌀ | Drehzahl | Vorschub XY | Vorschub Z | Z+ |&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;[mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;[RPM]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;[mm/min]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;[mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;[mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Acrylfräser 30° upcut&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;24000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1600&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;800&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gravierfräser 90°&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;24000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;500&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.5&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Die Werte mögen für die Aluminiumbearbeitung zu aggressiv sein, ich muss aber einen Mindestvorschub einhalten um den PE-Kern des Plattenmaterials nicht aufzuschmelzen. Die Späne sehen jedenfalls gut aus - kurz und dick. Mit diesen Werten gibt Zerspanobert beim Fräsen eine ruhiges, summendes Geräusch von sich.&lt;/p&gt;&#10;&lt;h3 id="das-erste-teil"&gt;Das erste Teil&lt;/h3&gt;&#10;&lt;p&gt;Ich habe zuerst den Halter für die Untersetzer gefertigt. Er besteht aus vier Teilen, die einfach zusammengesteckt werden können. Sie müssen nicht geklebt werden, da ich eine Übergangspassung gewählt habe. Ich verwende einen Anschlag als Orientierung für den XY-Nullpunkt des Werkstücks, da ich beidseitig fräsen möchte. Auf diese Weise kann ich die Ausschnitte in einem letzten Schritt anbringen und den Vakuumdruck auf meinem Tisch bis zu diesem letzten Schritt aufrechterhalten. Außerdem wollte ich vermeiden, dass ich die Teile manuell nachbearbeiten muss.&lt;/p&gt;&#10;&lt;p&gt;Der erste Versuch war miserabel - die vorderen und hinteren Fräsbahnen waren nicht perfekt ausgerichtet. Schon ein paar Zehntelmillimeter Versatz zwischen Vorder- und Rückseite machen hier einen großen Unterschied, also musste ich die Ursache beheben (Kanten am Anschlag, die ich vorher nicht bemerkt hatte und die ich abschleifen musste) und es erneut versuchen.&lt;/p&gt;&#10;&lt;p&gt;Hier ist das Ergebnis:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_btc_holder.jpg" alt="Image: The coaster holder, made of Aluminium composite" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich bin ziemlich zufrieden damit. Ich musste keine manuelle Nacharbeit leisten - die Teile sind einfach so aus der CNC gefallen. Die Fasen allerdings, die ich hinzugefügt habe, um dem Material eine glatte Oberfläche zu geben, waren leider von sehr unterschiedlicher Qualität. Während sie auf der &amp;ldquo;Rückseite&amp;rdquo; wirklich gut geworden sind, fühlen sie sich auf der Vorderseite viel rauer an. Hier musste das Werkzeug aber auch tiefer eintauchen.&lt;/p&gt;&#10;&lt;h3 id="sechs-stück-auf-einmal"&gt;Sechs Stück auf einmal&lt;/h3&gt;&#10;&lt;p&gt;Dann wählte ich ein einfach herzustellendes Logo - das Bitcoin-Blitz-Symbol - und plante einige einfache Gravurpfade, um es zu gravieren. Auch hier habe ich zweiseitig gefräst, so dass das Material nicht vollständig durchtrennt wird. Unten bleibt eine hauchdünne Schicht Aluminium stehen, die in einem zweiten Durchgang auf der Unterseite beim Fase fräsen entfernt wird.&lt;/p&gt;&#10;&lt;p&gt;Die Maschine brauchte für diesen Job etwa &lt;code&gt;20min&lt;/code&gt;. Leider erwies sich der Anschlag als zu ungenau, um Vorder- und Rückseite mit demselben Nullpunkt passgenau zu fertigen. Der Ausschnitt an der Unterseite wich nur um ein oder zwei Zehntelmillimeter ab, was in meinem Fall die Gesamtabweichung verdoppelt, wenn ich ihn um seine diagonale Achse drehte. Auf diese Weise hatte ein Teil der Kontur meines Untersetzers eine sehr tiefe Fase, während die andere Hälfte überhaupt keine Fase aufwies.&lt;/p&gt;&#10;&lt;p&gt;Ich muss bald eine Lösung dafür finden. Bis dahin schleife ich die Kanten von Hand nach.&#10;Dann habe ich die restlichen Späne entlang der Gravurwege mit einer Zahnbürste entfernt und das obige Titelfoto aufgenommen.&lt;/p&gt;&#10;&lt;h2 id="quality-analysis"&gt;Qualitätsanalyse&lt;/h2&gt;&#10;&lt;p&gt;Nachdem ich das Teil genauer untersucht hatte, machte ich die folgenden Beobachtungen:&lt;/p&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Image: BTC coasters on the machine, already fully cut."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_btc_cutouts.jpg" alt="Image: BTC coasters on the machine, already fully cut." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Image: A coaster holder: A U-shaped assembly holds up to six round coasters."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_holder_cutouts.jpg" alt="Image: A coaster holder: A U-shaped assembly holds up to six round coasters." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Image: Closeup of coaster&amp;#39;s bottom area. The edges look rough on deeper engravings. There is no uniform, shiny cut."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_engrave_closeup.jpg" alt="Image: Closeup of coaster&amp;#39;s bottom area. The edges look rough on deeper engravings. There is no uniform, shiny cut." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-3" aria-label="Image: Closeup of coaster&amp;#39;s lightning area. Its cutting edges look clean and shiny, the engraving is pretty shallow."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_engrave_closeup2.jpg" alt="Image: Closeup of coaster&amp;#39;s lightning area. Its cutting edges look clean and shiny, the engraving is pretty shallow." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-4" aria-label="Image: Closeup of coaster&amp;#39;s top area. Due to the slow feed, PU chips have molten to the cutting channel."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_engrave_closeup3.jpg" alt="Image: Closeup of coaster&amp;#39;s top area. Due to the slow feed, PU chips have molten to the cutting channel." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-5" aria-label="Image: Closeup of coaster&amp;#39;s lightning area, focusing on the deeper engrave. There are some marks on the aluminium edge."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_engrave_closeup4.jpg" alt="Image: Closeup of coaster&amp;#39;s lightning area, focusing on the deeper engrave. There are some marks on the aluminium edge." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Prototypen eines Untersetzers aus Metall.&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_btc_cutouts.jpg" alt="Image: BTC coasters on the machine, already fully cut." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Top sheet machining complete. Cutout and engravings.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_holder_cutouts.jpg" alt="Image: A coaster holder: A U-shaped assembly holds up to six round coasters." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Coaster holder cutouts, bottom view&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_engrave_closeup.jpg" alt="Image: Closeup of coaster&amp;#39;s bottom area. The edges look rough on deeper engravings. There is no uniform, shiny cut." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Rough edges where engravings are more deep&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-3" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_engrave_closeup2.jpg" alt="Image: Closeup of coaster&amp;#39;s lightning area. Its cutting edges look clean and shiny, the engraving is pretty shallow." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Less wide engravings looks more clean.&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-4" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_engrave_closeup3.jpg" alt="Image: Closeup of coaster&amp;#39;s top area. Due to the slow feed, PU chips have molten to the cutting channel." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Residues and chips left in the engrave channels&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-5" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-03-02_engrave_closeup4.jpg" alt="Image: Closeup of coaster&amp;#39;s lightning area, focusing on the deeper engrave. There are some marks on the aluminium edge." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Wondering why engrave quality reduces on deeper engravings&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;h3 id="alle-teile-sehen-gleich-aus"&gt;Alle Teile sehen gleich aus&lt;/h3&gt;&#10;&lt;p&gt;Das sind doch gute Neuigkeiten. Es bedeutet, dass die Fräser, die ich erstmals bei &lt;a href="https://www.vhf.com/de-de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;vhf&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gekauft habe, scharf geblieben sind und ihr Durchmesser daher konstant war. Ich kann mit den eingestellten Schnittweiten also wahrscheinlich viele weitere Prototypen fertigen. Dibond scheint also ein weniger anspruchsvolles Material zu sein als HPL, wo meine Fräser oft nach zwei Teilen bereits abgenutzt sind und z.B. Passungen klemmen - mehr dazu &lt;a href="https://blog.schallbert.de/cnc-router-overload/"&gt;in diesem Beitrag&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Es bedeutet auch, dass die Maschine keine Schrittverluste oder andere mechanische Probleme hat, und dass mein Klemmbereich eben ist, sodass der &lt;code&gt;90°&lt;/code&gt;-Gravierstichel sehr gleichmäßige Fräsergebnisse liefert. Linien behalten durch die zur Ausrichtung der Z-Achse orthogonale Ebene des Maschinentisches eine einheitliche Breite.&lt;/p&gt;&#10;&lt;h3 id="breitere-gravuren-sind-qualitativ-schlechter-als-schmalere"&gt;Breitere Gravuren sind qualitativ schlechter als schmalere&lt;/h3&gt;&#10;&lt;p&gt;Ich bin mir nicht sicher, warum. Da ich noch kein Experte im Umgang mit Dibond bin und keine Ausbildung als Zerspanungsmechaniker vorweisen kann, muss ich raten: Bekommt der Gravierstichel die Späne nicht rechtzeitig aus dem Fräskanal heraus gefördert? Dadurch würde er sie bei der nächsten Drehung erneut erfassen und &amp;ldquo;durch das Material ziehen&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Sollte dies der Fall sein, würde ein zusätzlicher Schlichtgang Abhilfe schaffen.&lt;/p&gt;&#10;&lt;h3 id="kantenbrechen-ohne-vorherige-nutfräsung-ergibt-rauere-schnittkanten"&gt;Kantenbrechen ohne vorherige Nutfräsung ergibt rauere Schnittkanten&lt;/h3&gt;&#10;&lt;p&gt;Hier könnte ich es mit demselben Problem wie oben zu tun haben. Ich probiere es mal mit einem zusätzlichen Schlichtgang aus und schaue, ob es dann besser wird. Eine Tiefgehendere Analyse hierzu gibt es &lt;a href="https://blog.schallbert.de/cut-dibond-tests/"&gt;in diesem Beitrag&lt;/a&gt;&#10;Eine weitere Alternative wäre, mit Vorschub und Drehzahl herumzuprobieren um das Optimum herauszufinden. Dafür benötige ich aber erst einmal frisches Material 😅&lt;/p&gt;&#10;&lt;h3 id="zweiseitige-bearbeitung-werkstückposition-präzise-festlegen"&gt;Zweiseitige Bearbeitung? Werkstückposition präzise festlegen!&lt;/h3&gt;&#10;&lt;p&gt;Offensichtlich war ich nicht in der Lage, die Position des Werkstückes nach dem Umdrehen auf die Rückseite mit Hilfe eines Anschlags genau genug festzulegen. Hier reichen ein paar Zehntelmillimeter Abweichung und die Sandwichplatte ist Schrott.&lt;/p&gt;&#10;&lt;p&gt;Lösungsansätze:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Einen &lt;code&gt;Job00&lt;/code&gt; einfügen, der Locatorlöcher (Positionsgeber) mit &lt;code&gt;5.04mm&lt;/code&gt; Durchmesser in einen Teil des Werkstücks einbringt, was später ein Abfallstück wird. Wenn ich sie symmetrisch platziere und diese Löcher ebenfalls im Frästisch fertige, so kann ich Passstifte einbringen, die mir die genaue Werkstückpositionierung beim Drehen auf die Rückseite sicherstellen.&lt;/li&gt;&#10;&lt;li&gt;Die Position des Anschlags überprüfen und sicherstellen, dass das Rohteil genau rechtwinklig und ohne Lichtspalt dort anliegt.&lt;/li&gt;&#10;&lt;li&gt;Ich könnte auch durchfräsen und die scharfen Kanten auf der Rückseite an einem Frästisch von Hand brechen. Da ich allerdings a) keine Frästisch besitze, b) meine Werkstücke oft sehr klein sind und c) Ich manuelle Nacharbeit verabscheue, wird dies wohl eine Rückfallebene bleiben für den Fall, dass die beiden obigen Lösungen für mich nicht funktionieren.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="einige-späne-kleben-in-der-fräsnut-fest"&gt;Einige Späne kleben in der Fräsnut fest&lt;/h3&gt;&#10;&lt;p&gt;Ich habe keine Ahnung, warum. Werden die Späne zu heiß und schmelzen sich in die Polyethylenschicht ein? Ich werde wohl mal mit dem Fräserhersteller telefonieren müssen, um das zu klären.&lt;/p&gt;&#10;&lt;h2 id="zusammenfassung"&gt;Zusammenfassung&lt;/h2&gt;&#10;&lt;p&gt;Alles in Allem kann ich einigermaßen zufrieden sein. Die Maschine ist in der Lage, Dibond und andere Aluminiumverbundmaterialien zu bearbeiten und die Ergebnisse sehen für einen ersten Versuch OK aus. Das Verbundmaterial nutzt die Fräser nicht übermäßig schnell ab und sieht einfach toll aus im Kontrast mit dem schwarzen Kern. Die Späne stören mich allerdings etwas, denn sie haften überall an und mögen ungern abgesaugt werden.&lt;/p&gt;&#10;&lt;p&gt;Dennoch. Die Qualität überzeugt mich noch nicht voll. Ich werde weiter probieren und meinen Fortschritt im Blog dokumentieren.&lt;/p&gt;&#10;</description></item><item><title>Github Actions: Fehler beheben</title><link>https://blog.schallbert.de/struggling-github-actions/</link><pubDate>Tue, 14 Feb 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/struggling-github-actions/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-02-14_github_actions-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Github Actions Workflow"&#10; title="Github Actions: Fehler beheben" /&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;Warum ich einen Beitrag zu Github Actions schreibe, obwohl es im Internet zahllose Tutorials und Blogeinträge zum Thema gibt? Weil ich für mein letztes Software-Projekt &lt;a href="https://blog.schallbert.de/projects/qr-codengrave/"&gt;QR-codengrave&lt;/a&gt; eine Build Pipeline aufsetzen wollte und sich dies schwieriger gestaltete als angenommen. Sollte ich das an irgend einem Punkt in der Zukunft wiederholen müssen und erinnere mich nicht, wie ich damals in der IDE gebaut, getestet und veröffentlicht habe, kann die Pipeline weiterhelfen.&lt;/p&gt;&#10;&lt;p&gt;Ebenso, wenn sich meine IDE ändert oder meine Virtuelle Umgebung defekt ist, die &lt;code&gt;launch.json&lt;/code&gt; fehlt oder irgend etwas inkompatibel mit dem von mir verwendeten Compiler/Interpreter wird. Dennoch will ich in der Lage sein, Releases zu veröffentlichen, Bugfixes zu erstellen und Tests zu fahren.&lt;/p&gt;&#10;&lt;p&gt;Obwohl das nicht das übliche Argument ist, eine CI/CD Pipeline aufzubauen - normalerweise wird da zuerst die einfachere Zusammenarbeit mehrerer Menschen an einem gemeinsamen Programm(teil) genannt - ist der Anreiz für mich stark genug, mir mal Githubs Automation dafür, bekannt unter dem Namen &lt;a href="https://docs.github.com/en/actions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github Actions&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, auszuprobieren.&lt;/p&gt;&#10;&lt;p&gt;Meine Software ist zu unbedeutend um &amp;ldquo;Nightlies&amp;rdquo; zu bauen oder agil entwickelt zu werden, und daher werde ich die Pipeline auf &amp;ldquo;build&amp;rdquo;, &amp;ldquo;lint/static code checks&amp;rdquo;, &amp;ldquo;testing&amp;rdquo; und &amp;ldquo;deployment&amp;rdquo; beschränken und dies auch nur dann automatisch triggern, wenn es Änderungen auf dem Produktionsbranch gibt.&lt;/p&gt;&#10;&lt;h3 id="alles-klar-aber-warum-ein-extra-blogeintrag"&gt;Alles klar, aber warum ein extra Blogeintrag?&lt;/h3&gt;&#10;&lt;p&gt;Weil es mich so viel Zeit gekostet hat, alles richtig aufzusetzen. Ich habe dafür stundenlang Fehler suchen, oft fluchen und wütend Spaziergänge machen müssen, bevor ich meine in der IDE bereits getestete und lauffähige Software auch mit Github vereinen zu können.&lt;/p&gt;&#10;&lt;p&gt;Wäre ich klüger gewesen, hätte ich gleich zu Anfang nach Tools dafür recherchiert &lt;a href="https://github.com/nektos/act" target="_blank" rel="noopener noreferrer" class="external-link"&gt;wie dieses hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, sodass Github Actions auch lokal ausgeführt werden kann. Dies hätte mir die ständigen Pushes und Wartezeiten auf den nächsten, immer noch fehlerhaften Build ersparen können.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-02-14_gh_actions_struggle.jpg" alt="Image: Github Action failed runs" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="das-script"&gt;Das Script&lt;/h2&gt;&#10;&lt;p&gt;Github Actions benutzt &lt;code&gt;YAML&lt;/code&gt; um Befehle der Nutzer zu interpretieren. Die Verwendung ist sehr gut &lt;a href="https://docs.github.com/en/actions/learn-github-actions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dokumentiert&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und stellt einige &lt;a href="https://github.com/marketplace?type=actions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;plug-and-play&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Beispiele bereit, die in vielen Szenarien und für die meisten Programmiersprachen direkt passen.&lt;/p&gt;&#10;&lt;p&gt;Die sogenannte &amp;ldquo;Workflowdatei&amp;rdquo; sieht so aus:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-YAML" data-lang="YAML"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# This workflow will install Python dependencies, run tests and lint with a single version of Python&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;python_integrate&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;on&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;push&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;branches&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;master&amp;#34;&lt;/span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;pull_request&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;branches&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;master&amp;#34;&lt;/span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;permissions&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;contents&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;read&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobs&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu-latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Set up Python 3.10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/setup-python@v3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;python-version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3.10&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Install dependencies&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; python -m pip install --upgrade pip&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; pip install flake8 pytest&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; pip install qrcodegen # Dependency install of qrcodegen&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; if [ -f requirements.txt ]; then pip install -r requirements.txt; fi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Lint with flake8&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; # stop the build if there are Python syntax errors or undefined names&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Test with pytest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;GabrielBB/xvfb-action@v1 &lt;/span&gt; &lt;span style="color:#75715e"&gt;# Diverts tkinter GUI to a virtual frame buffer (VFB)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; pytest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dieses sieht nicht einmal groß anders aus als die Vorlage mit der ich gestartet bin. Die einzigen Anpassungen waren der nun vorhandene &lt;code&gt;pytest&lt;/code&gt; Aufruf sowie das Hinzufügen eines virtuellen Bildpuffers um Darstellungsprobleme meiner GUI zu lösen. Meine Tests schlugen nämlich fehl sobald die GUI gestartet wurde, da auf den Github Servern gar kein Display zur Verfügung steht.&lt;/p&gt;&#10;&lt;h2 id="github-action-runner-fehlermeldungen"&gt;Github Action runner Fehlermeldungen&lt;/h2&gt;&#10;&lt;p&gt;Die folgende Liste enthält die Fehler (und zugehörige Lösungen) mit denen ich konfrontiert wurde, bevor ich eine stabile CI aufsetzen konnte - erst nach fast 50 Durchläufen.&lt;/p&gt;&#10;&lt;h3 id="ordner-nicht-vorhanden"&gt;Ordner nicht vorhanden?&lt;/h3&gt;&#10;&lt;p&gt;&lt;em&gt;Output&lt;/em&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;opt&lt;span style="color:#f92672"&gt;/&lt;/span&gt;hostedtoolcache&lt;span style="color:#f92672"&gt;/&lt;/span&gt;Python&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3.10.9&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;x64&lt;span style="color:#f92672"&gt;/&lt;/span&gt;lib&lt;span style="color:#f92672"&gt;/&lt;/span&gt;python3&lt;span style="color:#ae81ff"&gt;.10&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;importlib&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py:&lt;span style="color:#ae81ff"&gt;126&lt;/span&gt;: &lt;span style="color:#f92672"&gt;in&lt;/span&gt; import_module&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _bootstrap&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_gcd_import(name[level:], package, level)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;test&lt;span style="color:#f92672"&gt;/&lt;/span&gt;test_machinify_vector&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py:&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;: &lt;span style="color:#f92672"&gt;in&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;module&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;from&lt;/span&gt; bin.platform.machinify_vector &lt;span style="color:#f92672"&gt;import&lt;/span&gt; MachinifyVector, Tool, EngraveParams&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;E &lt;span style="color:#a6e22e"&gt;ModuleNotFoundError&lt;/span&gt;: No module named &lt;span style="color:#e6db74"&gt;&amp;#39;bin&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Hintergrund&lt;/em&gt;:&#10;Ich habe meine Quelldateien unter &lt;code&gt;/bin&lt;/code&gt; abgelegt und meiner lokalen Instanz von Pyinstaller (das Werkzeug, was ich auswählte, um aus den Skripten eine ausführbare Datei zu erzeugen) zur Verfügung gestellt. Beim Upload konnte Github Actions die Pfade nicht auflösen und zeigte diesen Fehler an.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Lösung:&lt;/em&gt;&#10;Eine leere Datei mit Namen &lt;code&gt;__init__.py&lt;/code&gt; im &lt;code&gt;/bin&lt;/code&gt; Ordner ablegen. Dies zeigt alle im Ordner befindlichen Dateien als Package an, die so für Github Actions verfügbar werden.&#10;Die IDE hatte dieses Problem nicht, weil sie alle im Projektordner befindlichen Dateien automatisch indiziert.&lt;/p&gt;&#10;&lt;h3 id="yaml-syntaxfehler"&gt;YAML Syntaxfehler&lt;/h3&gt;&#10;&lt;p&gt;Diese Fehler entstanden, weil ich versucht hatte, mehrere Workflows in eine Datei zu schreiben. Github Actions scheint nur einen Workflow pro Datei zu akzeptieren.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-02-14_gh_actions_syntax.jpg" alt="Image: Github Action syntax error" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="fehler-pfad-nicht-gefunden"&gt;Fehler: Pfad-nicht-gefunden&lt;/h3&gt;&#10;&lt;p&gt;Ich hatte lange Probleme mit solchen Fehlern bedingt durch meine Ordnerstruktur:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-YAML" data-lang="YAML"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#ae81ff"&gt;assets &lt;/span&gt; &lt;span style="color:#75715e"&gt;# images, persistence file, etc.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#ae81ff"&gt;src &lt;/span&gt; &lt;span style="color:#75715e"&gt;# source files&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#ae81ff"&gt;test &lt;/span&gt; &lt;span style="color:#75715e"&gt;# pytest files for unit and integration testing&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#ae81ff"&gt;dist &lt;/span&gt; &lt;span style="color:#75715e"&gt;# build artifacts&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#ae81ff"&gt;build &lt;/span&gt; &lt;span style="color:#75715e"&gt;# build process files&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ich fand die echt schön aufgeräumt. Aber meine lokale IDE, PyInstaller und Github Actions bzw. dessen worker für Pyinstaller waren sich nicht einig, wie sow etwas aussehen soll. Immer würde sich eine der beteiligten Parteien beschweren, dass ein Pfad nicht existiere oder die Datei soundso nicht gelesen werden könne. Daher habe ich am Ende die Verwendung relativer Pfade verworfen und stattdessen pythons &lt;code&gt;importlib_resources&lt;/code&gt; verwendet.&lt;/p&gt;&#10;&lt;p&gt;Aber auch hier gab es Probleme mit Pyinstaller auf Github.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-02-14_gh_actions_paths.jpg" alt="Image: Github Action relative path error" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Letztendlich konnte ich das Problem erst lösen, als ich den Ordner für &lt;code&gt;assets&lt;/code&gt; in &lt;code&gt;src&lt;/code&gt; integrierte. Auf diese weise kann der &lt;code&gt;./&lt;/code&gt; Operator nicht falsch verstanden werden. Obwohl ich dieses Konstrukt nicht so schön finde, war ich immer weniger bereit, noch mehr Zeit dort hinein zu buttern. (Wenn Sie wissen, wie sich das Pfadhandling sowohl lokal als auch remote mit Eleganz lösen lässt, freue ich mich sehr über Ihren Beitrag auf meiner &lt;a href="https://github.com/Schallbert/schallbert.github.io/discussions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dissussionsseite&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;&#10;&lt;h3 id="tkinter-headless"&gt;Tkinter headless&lt;/h3&gt;&#10;&lt;p&gt;Spät im Entwicklungsprozess habe ich meinen Unit Tests ein paar Integrationstests zur Seite gestellt. So wollte ich sicherstellen, dass Popups die erforderlichen Callbacks an Main machen würden und umgekehrt Daten ausgetauscht werden können, sodass z.B. eine Werkzeugliste an das Konfigfenster übergeben werden kann.&lt;/p&gt;&#10;&lt;p&gt;Diese Tests liefen lokal zwar, aber mit einem Nachteil: Wenn ich einen Fehler oder eine Warnung erzeugte, ließ sich das &lt;code&gt;messageBox&lt;/code&gt;-Popup nicht automatisiert schließen. Es wollte unbedingt vom Nutzer selbst per Mausklick geschlossen werden. Dies ließ sich auch nicht durch einen Overlay oder Invoke der Click-Aktion umgehen.&lt;/p&gt;&#10;&lt;p&gt;Was also in der IDE nervig war, würde auf dem Server in der Automation zu Testabbrüchen führen. Also habe ich die bittere Pille geschluckt und eine Wrapperklasse um tkinters &lt;code&gt;messageBox&lt;/code&gt; geschrieben, sodass ich dort ein Mock injizieren konnte was dann wiederum nicht das echte Popup triggern würde.&lt;/p&gt;&#10;&lt;p&gt;Ausschnitt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MsgBox&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Re-implementation due to testing purposes: With this trick, we are able to mock these windows&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; so we do not have to wait for users to manually close the dialog, unblocking the application again.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;showinfo&lt;/span&gt;(self, title, message):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; showinfo(title&lt;span style="color:#f92672"&gt;=&lt;/span&gt;title, message&lt;span style="color:#f92672"&gt;=&lt;/span&gt;message)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(self, title, message):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; showerror(title&lt;span style="color:#f92672"&gt;=&lt;/span&gt;title, message&lt;span style="color:#f92672"&gt;=&lt;/span&gt;message)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Als das funktionierte, habe ich die Änderungen voller Hoffnung nach Github gepusht. Und erhielt folgenden, wunderschönen Fehler:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;_tkinter.TclError: no display name and no $DISPLAY environment variable&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Das war zum Glück leicht zu verstehen - Tkinter wusste nicht, wo es die Fenster hinzeichnen sollte. Erscheint logisch, da der Server headless betrieben wird.&lt;/p&gt;&#10;&lt;p&gt;Also durchsuchte ich Foren und wurde mit nur einer Zeile Code belohnt, die dieses Problem sofort behob:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;uses: GabrielBB/xvfb-action@v1 # Diverts tkinter GUI to a virtual frame buffer (VFB)&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Seitdem führt Github Actions bei Pull request auf &lt;code&gt;main&lt;/code&gt; alle tests aus und endlich ergießt sich ein langersehnter Regen grüner Häkchen kühlend auf meine heißgelaufenen Hirnwindungen. Was für ein schönes Gefühl.&lt;/p&gt;&#10;</description></item><item><title>QR-codengrave V1.1 / V1.2</title><link>https://blog.schallbert.de/qr-codengrave1-1/</link><pubDate>Sun, 12 Feb 2023 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/qr-codengrave1-1/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-02-12_qr-codengrave-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Splash screen of QR-codengrave V1.1"&#10; title="QR-codengrave V1.1 / V1.2" /&gt;&#10;&lt;h2 id="kurze-fakten-des-updates"&gt;Kurze Fakten des Updates&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schnellerer Gravuralgorithmus und -Pfadoptimierung&lt;/li&gt;&#10;&lt;li&gt;Diverse Bugfixes&lt;/li&gt;&#10;&lt;li&gt;Bedienbarkeit verbessert&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="demo-video"&gt;Demo Video&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/jFD7nxftVHjUy6LKPCFE3N" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;QR-codengrave Quick Demo&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="version-11"&gt;Version 1.1&lt;/h2&gt;&#10;&lt;h3 id="algoritmusoptimierung"&gt;Algoritmusoptimierung&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Zeilen des QR-Codes werden nun horizontal eingelesen&lt;/li&gt;&#10;&lt;li&gt;Die Ausgabe der Pfade verläuft nun &amp;ldquo;rechts nach links&amp;rdquo;, danach &amp;ldquo;links nach rechts&amp;rdquo; und reduziert damit Maschinenbewegungen&lt;/li&gt;&#10;&lt;li&gt;Linien werden Punkten vorgezogen, da sie schneller gefertigt werden können&lt;/li&gt;&#10;&lt;li&gt;Linien werden nun vertikal und horizontal berechnet, die längere Linie bekommt den Vorzug&lt;/li&gt;&#10;&lt;li&gt;Eilgänge werden nun per XY-Vektor gefahren und nicht mehr nur eindimensional, was Zeit spart&lt;/li&gt;&#10;&lt;li&gt;Der neue Pfadalgorithmus benötigt 20% weniger Maschinenzeit&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-02-12_qr-codengrave_algo.jpg" alt="Image: Algorithm comparison 1.0 vs. 1.1" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="bugfixes"&gt;Bugfixes&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Bei nicht vorliegendem Datensatz &lt;code&gt;Persistence.dat&lt;/code&gt; wird das Startbild nicht angezeigt (&lt;code&gt;qruwu.jpg&lt;/code&gt;)&lt;/li&gt;&#10;&lt;li&gt;Crash bei Klick auf &lt;code&gt;Stop Draw&lt;/code&gt; wenn zuvor keine Zeichnung angefragt wurde&lt;/li&gt;&#10;&lt;li&gt;Crash bei Hinzufügen eines Werkzeuges bei leerer Werkzeugliste&lt;/li&gt;&#10;&lt;li&gt;Wird das Programm ohne Werkzeugauswahl gestartet, ist das dropdown-Menü gesperrt&lt;/li&gt;&#10;&lt;li&gt;Fehler in der QR-Code Größenschätzung&lt;/li&gt;&#10;&lt;li&gt;Fehler in der Blockbreitenberechnung bei Verwendung von V-Fräsern&lt;/li&gt;&#10;&lt;li&gt;Das gegenwärtig ausgewählte Werkzeug wird bei Start der Anwendung nicht im Dropdown-Feld angezeigt&lt;/li&gt;&#10;&lt;li&gt;QR-codes können größer dargestellt werden der Anzeigebereich&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="bedienbarkeit"&gt;Bedienbarkeit&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Default Werkzeugname geändert von &lt;code&gt;name&lt;/code&gt; nach &lt;code&gt;default&lt;/code&gt; zur besseren Verständlichkeit&lt;/li&gt;&#10;&lt;li&gt;Fräser mit geraden Schneiden (also kein V-cut) ist jetzt das Standardwerkzeug&lt;/li&gt;&#10;&lt;li&gt;Erstellen eines Offsets für den XY-Nullpunkt ist jetzt intuitiver gelöst (Y- nach unten, X+ nach Rechts)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="version-12"&gt;Version 1.2&lt;/h2&gt;&#10;&lt;p&gt;Kleine Änderungen zu 1.1, am Wichtigsten die Behebung eines Bugs, bei dem die Aufforderung zum Werkzeugwechsel verloren gehen kann. Außerdem werden die Winkel von V-Fräsern jetzt bei Berechnung von Pixelbreite und Größe der QR-Codes mit einbezogen.&lt;/p&gt;&#10;&lt;h3 id="bugfixes-1"&gt;Bugfixes&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Werkzeugauswahl &lt;code&gt;Tx&lt;/code&gt; und Werkzeugwechselkommando &lt;code&gt;M06&lt;/code&gt; befinden sich nun in derselben Zeile G-Code wie üblich und können so wie gewünscht Makros auslösen.&lt;/li&gt;&#10;&lt;li&gt;Das beim letzten Speichern verwendete Werkzeug wird nicht angezeigt, stattdessen das erste Werkzeug der Liste&lt;/li&gt;&#10;&lt;li&gt;Programmabsturz wenn versucht wird, den Jobstatus zu aktualisieren obwohl keine Gravurparameter vorliegen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="bedienbarkeit-1"&gt;Bedienbarkeit&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Versionsnummer als Kommentar in G-code hinzugefügt&lt;/li&gt;&#10;&lt;li&gt;Prefix &lt;code&gt;qr_&lt;/code&gt; der Zieldatei im Datei-Speichern-Dialog hinzugefügt&lt;/li&gt;&#10;&lt;li&gt;Genauigkeit der Maschinenzeitabschätzung verbessert&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="download"&gt;Download&lt;/h2&gt;&#10;&lt;p&gt;Der aktuelle Release ist &lt;a href="https://github.com/Schallbert/QR-codengrave/releases" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zum Download verfügbar. Das Programm ist für die private Anwendung sowie Schulungszwecke kostenlos. Für weitere Informationen und Projektdokumentation stehen die &lt;a href="https://github.com/Schallbert/QR-codengrave" target="_blank" rel="noopener noreferrer" class="external-link"&gt;readme&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sowie eine &lt;a href="https://blog.schallbert.de/projects/qr-codengrave/"&gt;Projektseite&lt;/a&gt; zur freien Verfügung.&lt;/p&gt;&#10;&lt;h2 id="upgrade-von-einer-früheren-version"&gt;Upgrade von einer früheren Version&lt;/h2&gt;&#10;&lt;p&gt;Es wird empfohlen, die Persistenzdatei von der alten Version in die neue Version zu übernehmen. Auf diese Weise werden gespeicherte Werkzeuglisten, Gravurparameter und XY-Offsets portiert. Und so geht&amp;rsquo;s:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Wechseln Sie in den Ordner, wo sich das QR-codengrave Programm befindet. Anschließend navigieren Sie zu &lt;code&gt;src/assets&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Kopieren Sie die Datei &lt;code&gt;Persistence.dat&lt;/code&gt; in einen temporären Ordner oder z.B. auf Ihren Desktop.&lt;/li&gt;&#10;&lt;li&gt;Entfernen Sie die alte Version durch Löschen des QR-codengrave Ordners.&lt;/li&gt;&#10;&lt;li&gt;Laden Sie die letzte Version herunter und verschieben Sie sie zur gewünschten Position auf Ihrem Rechner.&lt;/li&gt;&#10;&lt;li&gt;Verschieben Sie nun die vorab zwischengespeicherte &lt;code&gt;Persistence.dat&lt;/code&gt; in den &lt;code&gt;src/assets&lt;/code&gt; Ordner des aktualisierten Programms.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Steckbrett-Lagerfeuer</title><link>https://blog.schallbert.de/led-fireplace-diy/</link><pubDate>Sat, 10 Dec 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/led-fireplace-diy/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/fireplace/fireplace-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: A small electronics Breadboard with microcontroller, LEDs etc. on to make a fireplace simulation."&#10; title="Steckbrett-Lagerfeuer" /&gt;&#10;&lt;h2 id="materialliste"&gt;Materialliste&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Gegenstand&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Menge&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;~Preis [€]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LED: Warmweiß, z.B. Ifwd=30mA, 3lm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1,50&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LED: Gelb, z.B. Ifwd=35mA, 1lm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,60&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LED: Rot, z.B. Ifwd=35mA, 0.4lm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,30&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Arduino Nano Atmega328&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;18&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Steckbrett klein, z.B. 170 pins&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,70&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Brückendrähte, pin-pin, z.B. 5cm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3,30&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Dazu: Ein paar weitere Drähte, Mini USB-Kabel, Powerbank / USB-Ladegerät&lt;/p&gt;&#10;&lt;h2 id="werkzeuge"&gt;Werkzeuge&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Optional: Heißklebepistole&lt;/li&gt;&#10;&lt;li&gt;Optional: Lötkolben&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="aufbau"&gt;Aufbau&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/fireplace/fireplace_detail.jpg" alt="Image: fireplace breadboard design detail view" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/LED%2C_5mm%2C_green_%28en%29.svg/432px-LED%2C_5mm%2C_green_%28en%29.svg.png" alt="Image: LED closeup" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;LED aus der Nähe betrachtet&lt;/span&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:LED,_5mm,_green_%28en%29.svg#file" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Die rechte Pin-Seite des Arduino Nano mit der linken Seite des Steckbretts verbinden. Dabei darauf achten, dass alle Pins auf gleicher Höhe sind und möglichst nicht mit den Fingern zu berühren, da die Gefahr einer elektrostatischen Entladung besteht, die zu Beschädigung führen könnte.&lt;/li&gt;&#10;&lt;li&gt;Die Anoden (&lt;code&gt;+&lt;/code&gt;) der LEDs mit den Ausgängen &lt;code&gt;D2 bis D9&lt;/code&gt; auf dem Steckbrett verbinden. Die Anode ist meistens der Pin der LED, der nicht mit der Lichtquelle belegt ist (im Bild oben wäre das links).&lt;/li&gt;&#10;&lt;li&gt;Die Kathoden (&lt;code&gt;-&lt;/code&gt;) der LEDs auf der rechten Seite des Steckbretts auf gleicher Höhe einstecken.&lt;/li&gt;&#10;&lt;li&gt;Alle Kathoden mit Hilfe von Überbrückungsdrähten verbinden. Alternativ können die Kathoden auch gleich zusammen gelötet oder mittels einer Verteilerbrücke, wie in meinem Beispiel oben verwendet, durchverbunden werden.&lt;/li&gt;&#10;&lt;li&gt;Verbinden Sie die &amp;ldquo;gemeinsame Kathode&amp;rdquo; mit Hilfe eines Brückendrahtes mit einem der &amp;ldquo;GND&amp;rdquo;-Pins der Mikrocontroller-Platine.&lt;/li&gt;&#10;&lt;li&gt;Die Anoden der LEDs sollten sich nicht berühren. Das kann man einfach sicherstellen, indem sie mit etwas Heißkleber fixiert werden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="programmierung"&gt;Programmierung&lt;/h2&gt;&#10;&lt;p&gt;Der Code ist so einfach, dass ich ihn hier in Gänze herunterschreiben kann kann. Kopieren Sie ihn in Ihre Lieblings-IDE, programmieren Sie Ihr Board und schon sind Sie fertig!&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Disclaimer&lt;/em&gt;: Dieser Code ist &amp;ldquo;quick&amp;rsquo;n&amp;rsquo;dirty&amp;rdquo; und hat viel Potential für Refactoring. Zum Beispiel sind die Pin-IDs hartkodiert, ich verwende nicht die tollen und einfachen Features von C++11 wie &lt;a href="https://en.cppreference.com/w/cpp/language/range-for" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Range-based for loop&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und der Geschwindigkeitsalgorithmus wird ziemlich oft die untere Grenze erreichen und kein zufälliges Verhalten zeigen.&#10;Ich freue mich über Ihre Verbesserungsvorschläge auf meiner &lt;a href="https://github.com/Schallbert/schallbert.github.io/discussions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github discussions&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Seite.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;Arduino.h&amp;gt;&lt;/span&gt;&lt;span style="color:#75715e"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#define MAXLONG 0xFFFFFFFF&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#define SPEED_HIGHLIMIT 1000 &lt;/span&gt;&lt;span style="color:#75715e"&gt;// 500Hz&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#define SPEED_LOWLIMIT 0x1FFF &lt;/span&gt;&lt;span style="color:#75715e"&gt;// 20Hz&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;calcFlicker&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint32_t&lt;/span&gt; randomFlicker);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;writeLed&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; columnCode, &lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt; speed);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;uint32_t&lt;/span&gt; iRNG &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;setup&lt;/span&gt;() &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;randomSeed&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;analogRead&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pinMode&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, OUTPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pinMode&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, OUTPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pinMode&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, OUTPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pinMode&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, OUTPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pinMode&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, OUTPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pinMode&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;, OUTPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pinMode&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;, OUTPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pinMode&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;, OUTPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iRNG &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;loop&lt;/span&gt;() &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iRNG &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (iRNG &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;^&lt;/span&gt; (&lt;span style="color:#f92672"&gt;-&lt;/span&gt;(iRNG &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1u&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0xd0000001u&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;calcFlicker&lt;/span&gt;(iRNG);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; This function takes a 32bit random value,&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; and moves this value along a window of an 8bit &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; flicker output value.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;*/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;calcFlicker&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint32_t&lt;/span&gt; randomFlicker)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; currentFlicker &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt;)(randomFlicker &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0xFF&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt; randomSpeed &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;min&lt;/span&gt;(SPEED_LOWLIMIT, (&lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt;)(randomFlicker &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; SPEED_HIGHLIMIT));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; i &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;; &lt;span style="color:#f92672"&gt;++&lt;/span&gt;i) &lt;span style="color:#75715e"&gt;// 24 because of 32bit length of random, may not run empty&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;writeLed&lt;/span&gt;(currentFlicker, randomSpeed);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; randomFlicker &lt;span style="color:#f92672"&gt;=&lt;/span&gt; randomFlicker &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; this function outputs a code to an LED column of 8 LEDs&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; connected to arduino&amp;#39;s pins with a selectable delay.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;*/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;writeLed&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; columnCode, &lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt; speed)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, columnCode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x01&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, columnCode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x02&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, columnCode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x04&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, columnCode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x08&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, columnCode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x10&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;, columnCode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x20&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;, columnCode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x40&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;, columnCode &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0x80&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;delayMicroseconds&lt;/span&gt;(speed);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="testen"&gt;Testen&lt;/h2&gt;&#10;&lt;p&gt;Sobald Sie das Gerät über USB einschalten, sollte die LED-Flackersequenz beginnen. Es sollte sich ein ruhiges Flackern einstellen. Wenn man im Regal einen leeren Platz findet und ein paar Zweige zur Hand hat, könnte das doch ganz schön aussehen, oder?&#10;Mit einer Powerbank kann man dieses Lagerfeuer auf dem Steckbrett sogar überall hin mitnehmen. Das war&amp;rsquo;s, viel Spaß damit!&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/projects/fireplace/"&gt;Zur Hauptseite des Projektes&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Vakuumpumpen</title><link>https://blog.schallbert.de/cnc-vacuum-pumps/</link><pubDate>Fri, 28 Oct 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-vacuum-pumps/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_vacuumpump-thumb.jpg"&#10; class="post-cover"&#10; alt="Image of a Sidechain compressor vacuum pump"&#10; title="Vakuumpumpen" /&gt;&#10;&lt;h2 id="was-ist-das"&gt;Was ist das?&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_manometer.jpg" alt="Image: Vacuum intake manifold" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Eine Vakuumpumpe ist ein Gerät, welches ein Gefäß evakuieren kann. Es ist also in der Lage, eine Menge an Gasatomen aus einem Gefäß zu entnehmen, sodass dieses schließlich weniger Atome pro Volumeneinheit enthält als das umgebende Medium. Dies erzeugt in dem Gefäß einen Unterdruck. Hier auf der Erde &lt;a href="https://physics.stackexchange.com/questions/161107/why-is-it-impossible-to-have-a-perfect-vacuum" target="_blank" rel="noopener noreferrer" class="external-link"&gt;scheint es nicht möglich zu sein&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, die Zahl der Atome in einem Gefäß auf null zu senken, aber das ist natürlich für unsere Zweck auch nicht erforderlich. Ein technisches Grobvakuum ist hier vollkommen ausreichend.&lt;/p&gt;&#10;&lt;p&gt;Es gibt verschiedene Typen von Vakuumpumpen, jeder optimiert für andere Einsatzbedingungen. Doch mehr dazu später.&lt;/p&gt;&#10;&lt;h2 id="wie-kann-die-vakuumpumpe-werkstücke-festhalten"&gt;Wie kann die Vakuumpumpe Werkstücke festhalten?&lt;/h2&gt;&#10;&lt;p&gt;Für Niederhalten und Spannen von Werkstücken mittels Vakuumpumpe wird ein Vakuumtisch (siehe mein &lt;a href="https://blog.schallbert.de/why-vacuum-table/"&gt;Beitrag zum Thema&lt;/a&gt;) beziehungsweise eine Unterdruckklemmvorrichtung benötigt. Hier sind wir an der Druckdifferenz zur Umgebungsluftdruck (&lt;code&gt;103000Pa = 10.3N/cm² = 1kg/cm²&lt;/code&gt;) interessiert. Dies ist die maximale Niederhaltekraft pro Fläche, die mit einem Vakuumtisch und dazu passend ausgelegter Pumpe theoretisch erreicht werden kann.&lt;/p&gt;&#10;&lt;h3 id="volumenförderstrom-vs-unterdruck"&gt;Volumenförderstrom vs. Unterdruck&lt;/h3&gt;&#10;&lt;p&gt;Die wichtigsten Parameter einer Vakuumpumpe sind - wie bei anderen Pumpentypen auch - die maximal erreichbare Förderleistung, gemessen in &lt;code&gt;m³/h&lt;/code&gt; (bei niedriger Druckdifferenz), sowie geringster erreichbarer Absolutdruck im vakuumierten Gefäß in &lt;code&gt;hPa&lt;/code&gt; (bei niedrigem Volumenstrom) auf der Saugseite. Die Druckseite ist bei Vakuumpumpen logischerweise uninteressant.&lt;/p&gt;&#10;&lt;p&gt;Das erinnert mich ein wenig an die elektrische Welt, wo z.B. Leerlaufspannung (max. Druckdifferenz) und Kurschlussstrom (max. Volumenstrom) eine Spannungsquelle charakterisieren und es darum geht, einen optimalen Arbeitspunkt zu finden.&lt;/p&gt;&#10;&lt;p&gt;Wenn man auf eine Spannung aus ist, kann man eine Quelle nur mit einem geringen Strom belasten und so die Spannung oben halten. Anders herum kann bei geringer Spannung ein hoher Strom aus der Quelle bezogen werden. In jedem Falle ist auch bei der Vakuumpumpe ein optimaler Arbeitspunkt zu finden und den Pumpentyp entsprechend auszuwählen. Einige Pumpentypen nämlich sind auf Durchsatz optimiert und damit für andere Zwecke gut geeignet als Pumpen, die große Unterdrücke bei geringerem Volumenstrom erreichen.&lt;/p&gt;&#10;&lt;p&gt;Wird viel von beidem benötigt, steigen Kosten und Energiebedarf der Pumpe stark an - es kommt also wieder einmal darauf an, was man braucht, denn die &amp;ldquo;Alleskönnerpumpe&amp;rdquo; ist ökonomisch oft nicht sinnvoll.&lt;/p&gt;&#10;&lt;h3 id="charakteristika-der-pumpen"&gt;Charakteristika der Pumpen&lt;/h3&gt;&#10;&lt;p&gt;Ich habe mir mal die Mühe gemacht, Volumenstrom über Druckdifferenz in einem Diagramm einzutragen und für verschiedene Pumpentypen in ähnlicher Leistungsklasse auszuwerten. Dann zog ich eine Linie durch die beiden Punkte (sicher, das ist nur eine Annäherung, der wahre Verlauf ist eher asymptotisch), um sie besser vergleichbar zu machen.&lt;/p&gt;&#10;&lt;p&gt;Für das Beispiel habe ich willkürlich eine elektrische Leistung der Pumpe von rund &lt;code&gt;1.5kW&lt;/code&gt; angenommen. Die Werte habe ich aus online abrufbaren Datenblättern verschiedener Pumpenhersteller entnommen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_pump_type_compare.jpg" alt="Image: XY-line graph for pump types of same power" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Natürlich ist das nur eine grobe Annäherung und gilt eher für Pumpentypen mit großem Durchsatz als für diejenigen mit hoher Druckdifferenz. Die Funktion letzterer sieht eher aus wie ein hyperbolisches Parabolid (siehe Abbildung weiter unten), da erreichbare Druckdifferenz von Leckströmen stark gemindert wird.&lt;/p&gt;&#10;&lt;p&gt;Wir können solch eine Kurve gut für die Arbeitspunktbestimmung verwenden: Der Hersteller des Vakuumtisch empfiehlt meist den erforderlichen Volumenstrom für das jeweilige Produkt. Dieser wird nun auf der Linie markiert, und schon ergibt sich das dann erreichbare Vakuum. Am Schluss des Artikels komme ich hierauf zurück und versuche, ein paar Empfehlungen zu geben.&lt;/p&gt;&#10;&lt;h2 id="pumpentypen"&gt;Pumpentypen&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_pump_types.jpg" alt="Image: Comparison of vaccuum pump leakage behavior" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Es gibt viele verschiedene Pumpentypen zur Vakuumerzeugung in der Industrie. Sie haben teils verschiedene Wirkprinzipien und selbstverständlich unterschiedliche Einsatzzwecke. Ich konzentriere mich hier auf die vier Typen, die im Hobbybereich meiner Ansicht nach am Häufigsten zum Einsatz kommen.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Radialgebläse (Sehr hoher Volumenstrom)&lt;/li&gt;&#10;&lt;li&gt;Seitenkanalverdichter (Hoher Volumenstrom)&lt;/li&gt;&#10;&lt;li&gt;Kolbenpumpe (Tiefes Vakuum)&lt;/li&gt;&#10;&lt;li&gt;Drehschieberpumpe (Tiefes Vakuum)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="radial-blower"&gt;Radialgebläse&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_radialblower.jpg" alt="Image: Radial blower symbol view" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Radialgebläse beschleunigen die Luft zwischen ihren Schaufeln. Luft wird axial in Mitte des Gebläses angesaugt und durch Fliehkräfte radial nach außen gedrückt, wo sie über einen Kanal an einer Stelle radial austreten kann.&lt;/p&gt;&#10;&lt;p&gt;Radialgebläse werden für sehr viele Anwendungen gebaut. Sie kommen beispielsweise in Großfeuerungen von Kraftwerken, in Hüpfburgen und dem Automotive-Bereich vor und waren vor fast zwei Jahrzehnten sogar auf Grafikkarten häufiger anzutreffen (Ich hatte mal einen auf meiner ATI Radeon X1900XT). Sie sind sehr einfach im Aufbau, kostengünstig herzustellen und sind in der Lage, einen unglaublich hohen Luftdurchsatz bereitzustellen. Nachteilig sind die hohen Laufgeräuschpegel und die im Vakuumbereich relevanten, geringen erreichbaren Unterdrücke.&lt;/p&gt;&#10;&lt;p&gt;Wenn allerdings mit Plattenmaterial und großflächigen Werkstücken gearbeitet wird, kann diese Vakuumlösung trotzdem sehr gut passen. Im Hobbybereich können hier oft sogar günstige Staubsauger (mit Fremdkühlung durch einen eigenen Lüfter) verwendet werden.&lt;/p&gt;&#10;&lt;h3 id="seitenkanalverdichter"&gt;Seitenkanalverdichter&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_sidechaincompressor.jpg" alt="Image: Sidechain compressor symbol view" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Seitenkanalverdichter kombinieren einen hohen Volumenstrom mit mittleren Druckdifferenzen. Man kann sie als ein- oder mehrstufige Systeme erwerben (Serienschaltung). Dadurch sind sie gut auf die Bedürfnisse der Kundschaft anpassbar. Wie Radialgebläse interagieren sie nur mit dem Medium und verschleißen nicht durch Teil-zu-Teil-Reibung. Dadurch sind sie ebenso wartungsarm und tolerieren auch Dämpfe und staubige Umgebungen.&lt;/p&gt;&#10;&lt;p&gt;Wünscht man einen Seitenkanalverdichter mit hohen Druckdifferenzen, muss man einen sehr leistungsfähigen Motor wählen, denn die charakteristische Kurve verläuft recht flach. Die Geräuschbelästigung kann durch beidseitige Verwendung von Schalldämpfern gut reduziert werden. Dennoch sind sie bei vergleichbaren Leistungswerten teurer als Radialgebläse.&lt;/p&gt;&#10;&lt;p&gt;In dieser Aufzählung sind sie die eierlegende Wollmilchsau: Sie können einen passablen Unterdruck bei hohem Volumenstrom erzeugen, ihr Preis ist moderat, sie benötigen kaum Wartung und ihr unangenehmes, turbinenartiges Geräusch lässt sich mit ein paar technischen Maßnahmen in den Griff bekommen.&lt;/p&gt;&#10;&lt;p&gt;Ich fand diese tiefgehende Erklärung von Seitenkanalgebläsen ebenfalls nützlich: &lt;a href="https://gutmbh.de/SKVM.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Link geht zur &amp;ldquo;GutmbH&amp;rdquo;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Und dieses &lt;a href="https://www.youtube.com/watch?v=8JpBVmqjSu0" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Youtube-Video&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zeigt einen herrlich verspielten Einsatz als Verdichter.&lt;/p&gt;&#10;&lt;h3 id="kolbenpumpe"&gt;Kolbenpumpe&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_pistonpump.jpg" alt="Image: Piston pump symbol view" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Der Klassiker. Kolbenpumpen werden sowohl für Kompressoren als auch für Vakuumpumpen verwendet. Sie stellen bei vergleichbar geringen Volumenströmen hohe Druckdifferenzen bereit, benötigen nicht viel Platz in der Werkstatt, und sind schon allein durch die hohen Produktionsvolumen günstig in der Anschaffung.&lt;/p&gt;&#10;&lt;p&gt;Auf der anderen Seite bieten sie nur einen pulsierenden Luftstrom und sind konzeptbedingt eher laut. Überdies können nur speziell dafür angefertigte Modelle mit Flüssigkeiten (Flutkühlung) oder Stäuben umgehen und benötigen mehr Wartung als die obengenannten Typen. Ich habe längst nicht so viele Anwendungsbeispiele dieses Pumpentyps für Vakuumtische gefunden wie bei den anderen. Das heißt aber nicht, dass ich sie für diese Einsatzzwecke als ungeeignet einstufe.&lt;/p&gt;&#10;&lt;h3 id="drehschieberpumpen"&gt;Drehschieberpumpen&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_rotaryvanepump.jpg" alt="Image: Rotary vane pump symbol view" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Drehschieberpumpen teilen sich wiederum in zwei Klassen: Trockenlaufend und ölgeschmiert.&#10;Während trockenlaufende Drehschieberpumpen gleicher Leistung bei etwas höherem Durchsatz ähnliche Unterdrücke erzeugen können wie Kolbenpumpen, sind ihre ölgeschmierten Pendants in der Lage, deutlich größere Unterdrücke zu erreichen. Zudem benötigen sie weniger Energie zum Betrieb als ihre trockenlaufenden Kollegen.&lt;/p&gt;&#10;&lt;p&gt;Auf der anderen Seite benötigen sie eine Warmlaufzeit für das Öl und einen Entgasungszyklus vor ihrer Abschaltung. Zudem benötigen sie durch Ölwechsel und leichte Ölnebel in der Abluft mehr Wartungsaufwand.&lt;/p&gt;&#10;&lt;p&gt;Das Wirkprinzip: Ein Motor treibt eine auf seiner Welle befindliche geschlitzte Scheibe an, welche exzentrisch in einem Gehäuse montiert ist. In den Schlitzen sind kleine Platten eingelassen, die sich radial frei bewegen können. Startet der Motor nun, werden die Platten durch Fliehkräfte an die Wände der Kammer gedrückt und schließen diese beinahe luftdicht gegen die anderen Kammern ab. Durch die Exzentrizität ist das Volumen dieser Kammern über einen Umlauf variabel und verkleinert sich in Richtung Auslass, sodass auf der Saugseite ein Unterdruck und am Auslass ein Überdruck entsteht.&lt;/p&gt;&#10;&lt;p&gt;Drehschieberpumpen sind nur für kompressible Medien geeignet, benötigen Staubabscheider am Einlass und vertragen keine Flüssigkeiten im Medium. Dafür liefern sie einen kontinuierlichen Saugstrom und sind im Betrieb vergleichsweise leise. Die Drehschieber sind Verschleißteile, die irgendwann ersetzt werden müssen - besonders bei den trockenlaufenden Geräten. Im Vergleich zu Seitenkanalverdichtern gleichen Volumenstroms liegen ihre Kosten zur Zeit des Verfassens dieses Artikels um den Faktor zwei bis drei höher.&lt;/p&gt;&#10;&lt;p&gt;Weitere Details gefällig? Wie oben bereits erwähnt halte ich diese Seite von &lt;a href="https://gutmbh.de/Vakuumpumpen.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;GutmbH&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für lesenswert.&lt;/p&gt;&#10;&lt;h2 id="das-werkstück-bestimmt-die-pumpe"&gt;Das Werkstück bestimmt die Pumpe&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_operating_principle.jpg" alt="Image: Vacuum table principle of operation - pressure difference causes downforce" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Um eine gute Wahl für eine Vakuumpumpe treffen zu können, sollte der Einsatzzweck also klar und der zu verwendende Vakuumtisch eine bekannte Größe sein.&lt;/p&gt;&#10;&lt;p&gt;Wie in der Zeichnung zu erkennen ist, hängt die erreichbare Niederhaltekraft stark von der Größe der Oberfläche des Werkstücks ab - je kleiner sie ist, desto größer muss der Unterdruck sein, den die Pumpe liefert, um dieselbe Niederhaltekraft zu erreichen. Leider ist der Unterdruck wie oben beschrieben durch das absolute bzw. das durch die gewählte Pumpe erreichbare technische Grobvakuum limitiert. Trotzdem können Vakuumtische sehr homogene Niederhaltekräfte bieten.&lt;/p&gt;&#10;&lt;p&gt;Was wir vermeiden wollen, ist, dass das Werkstück durch das Werkzeug beim Eingriff vom Maschinentisch gelöst wird. Ebenfalls möchten wir Parallelverschiebung oder Drehung des Werkstücks durch das Werkzeug verhindern. Um ein paar Anhaltspunkte zu bekommen, können wir zuerst die Niederhaltekraft für unser Werkstück berechnen. In einem zweiten Schritt dann können wir abschätzen, wie viel Widerstand uns dies gegen laterale Verschiebung oder Drehung bietet. Solange die vom Werkzeug in das Werkstück eingebrachte Kraft unter dieser Losbrechkraft liegt, sind wir auf der sicheren Seite und können möglicherweise eine preisgünstigere Pumpe wählen.&lt;/p&gt;&#10;&lt;h3 id="niederhaltekraft"&gt;Niederhaltekraft&lt;/h3&gt;&#10;&lt;p&gt;Die Klemmkraft, die das Werkstück auf dem Maschinentisch festhält, ist leicht zu berechnen:&lt;/p&gt;&#10;$$f_{clamp}= F_N = \Delta p A + F_g $$&lt;p&gt;Sie wird in &lt;code&gt;Newton&lt;/code&gt; angegeben und ergibt sich aus der Druckdifferenz zwischen Ober- und Unterseite des Werkstücks und der Erdanziehungskraft, die das Werkstück nach unten drückt.&lt;/p&gt;&#10;&lt;p&gt;Der erforderliche Wert hängt stark von der Werkzeuggröße- und Geometrie, Werkstückmaterial sowie vom Vorschub und gewählter Drehzahl ab. So wird wesentlich mehr Klemmkraft bei harten und zähen Materialien wie Hochdrucklaminaten oder Stahl benötigt, wenn Werkzeuge mit großen Durchmessern oder Drallwinkeln eingesetzt werden, die zudem bei großen Vorschüben und geringer Drehzahl durch das Material gehen als bei Weichhölzern, wo gern mit hohen Drehzahlen und gerade geschliffenen Fräsern gearbeitet wird.&lt;/p&gt;&#10;&lt;p&gt;Die Berechnung der &lt;a href="https://de.wikipedia.org/wiki/Zerspankraft#Komponenten_der_Zerspankraft" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Zerspankräfte&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, insbesondere der hier für uns interessanten Vorschubkraft, würde einen eigenen Artikel erforderlich machen. Für den Hobbybereich sind meine Erfahrungswerte, dass Werkstücke kleiner als &lt;code&gt;80x80mm&lt;/code&gt; nur mit großen Unterdrücken um &lt;code&gt;-0.8Bar&lt;/code&gt; gehalten werden können, wenn man nicht gerade im Schneckentempo arbeiten möchte. Andersherum reichen bei &lt;code&gt;600x600mm&lt;/code&gt; großen Platten ohne Durchfräsungen bereits &lt;code&gt;-0.1Bar&lt;/code&gt;, um die Platte sicher zu halten.&lt;/p&gt;&#10;&lt;h3 id="parallelverschiebung"&gt;Parallelverschiebung&lt;/h3&gt;&#10;&lt;p&gt;Um Niederhaltekraft in Widerstand gegen Parallelverschiebung umzurechnen, führe ich hier den &lt;a href="https://en.wikipedia.org/wiki/Friction#Coefficient_of_friction" target="_blank" rel="noopener noreferrer" class="external-link"&gt;statischen Reibungskoeffizienten&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; \(µ_s\) ein.&lt;/p&gt;&#10;&lt;p&gt;Nehmen wir an, wir legen eine Lage Gummi zwischen unser (metallisches) Werkstück und den Vakuumtisch. Nehmen wir weiter einen statischen Reibungskoeffizienten &lt;code&gt;µs = 0.7&lt;/code&gt; zwischen Gummi und Werkstück an - &lt;a href="https://www.schweizer-fn.de/stoff/reibwerte/reibwerte.php" target="_blank" rel="noopener noreferrer" class="external-link"&gt;den Wert habe ich online gefunden&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; - was für mich aus mehreren Gründen nach einer konservativen Schätzung aussieht: Erstens klammern wir die Oberflächenrauigkeit von Gummimatte und Werkstück aus, und zweitens wird das Werkstück durch den Unterdruck etwas in die Gummimatte hineingedrückt, was die Losbrechkräfte durch Formschluss nochmal deutlich erhöht.&lt;/p&gt;&#10;&lt;p&gt;Egal, denn alles was zu tun bleibt, ist die oben berechnete Kraft mit dem Reibungskoeffizienten zu multiplizieren.&lt;/p&gt;&#10;$$F_s = F_N µ_s$$&lt;p&gt;Hier kann man also an zwei Stellschrauben drehen: Der Materialpaarung und dem von der Pumpe bereitgestellten Anpressdruck. Es gibt auch spezielle Vakuumvliese, welche besonders hohe Reibungskoeffizienten besitzen und bei Anlegen des Vakuums durch Eindrücken des Werkstücks in das Vlies für zusätzlichen Widerstand gegen Verschiebung und Rotation sorgen.&lt;/p&gt;&#10;&lt;h3 id="rotation"&gt;Rotation&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_rotatingworkpiece.jpg" alt="Image: Worst-case scenario for rotating a workpiece out of position" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hier wird die Berechnung schwierig. Interessanterweise wird weniger Kraft benötigt, ein Werkstück von seiner Position wegzudrehen, als es parallel zu verschieben. Das kann man sich aber zum Glück recht leicht klarmachen:&lt;/p&gt;&#10;&lt;p&gt;Wenn Sie z.B. Ihr Mobiltelefon auf dem Tisch um seinen Mittelpunkt herum drehen (etwa durch Berührung der rechten oberen Kante mit dem Zeigefinger), müssen nicht alle denkbaren Punkte auf dem Smartphone denselben Weg zurücklegen. Punkte, die sich näher am Drehpunkt befinden, legen eine geringere Distanz für den selben überstrichenen Winkel zurück als solche, die weiter in Richtung Kante des Smartphones liegen. Für eine Paralellverschiebung hingegen muss jeder Punkt einen gleich großen Weg zurücklegen, für den ein entsprechender Energieeinsatz erforderlich ist.&lt;/p&gt;&#10;&lt;p&gt;Für das Spannen auf dem Vakuumtisch sind lange, schmale Werkstücke besonders anspruchsvoll. Sie bieten mit ihren langen Kanten einen großen Hebel und bieten zudem keine große Oberfläche, die der Unterdruck zum Niederhalten nutzen kann. Daher lassen sie sich leicht versehentlich verdrehen.&lt;/p&gt;&#10;&lt;p&gt;Am einfachsten zu bearbeiten sind daher runde oder quadratische Werkstücke, zumal wenn sie nahe an ihrem geometrischen Mittelpunkt gefräst werden.&lt;/p&gt;&#10;&lt;h2 id="welche-vakuumpumpe-wird-nun-also-benötigt"&gt;Welche Vakuumpumpe wird nun also benötigt?&lt;/h2&gt;&#10;&lt;p&gt;Ok, fassen wir zusammen:&lt;/p&gt;&#10;&lt;p&gt;Es ist immer eine Kombination aus Werkstückmaterial (leicht/ schwer zu bearbeiten, niedrige/hohe Reibungskoeffizienten), -Größe (je mehr Oberfläche, desto einfacher sind hohe Klemmkräfte realisierbar), Bedürfnissen durch die Bearbeitung wie Durchfräsungen (Toleranz gegenüber Leckageströmen), Vakuumtischgröße- und Typ sowie Bearbeitungsparametern, die die Auswahl der Vakuumpumpe bestimmt.&lt;/p&gt;&#10;&lt;p&gt;Daher sollten ein paar Erfahrungswerte vorliegen und klare Wünsche formuliert werden können: Wie groß sind die Werkstücke typischerweise? Werden oft Teile aus Plattenmaterial ausgeschnitten? Welche Materialien sollen verarbeitet werden? Werden Kühlschmierstoffe benötigt? Muss mit aggressiven Parametern vorgegangen werden, um Fertigungszeiten niedrig zu halten?&lt;/p&gt;&#10;&lt;h3 id="ein-paar-vorschläge-meinerseits"&gt;Ein paar Vorschläge meinerseits:&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Aluminium, kleine Werkstückgröße, keine Durchfräsungen: Kleinvolumige Kolben- oder Drehschieberpumpe mit einem Lochrastertisch&lt;/li&gt;&#10;&lt;li&gt;Holz und MDF, große Werkstücke, Nesting und eine Menge Durchfräsungen: Radialgebläse oder einstufiger Seitenkanalverdichter, Vakuumtisch mit poröser Oberfläche oder Rastervakuumtisch mit Vakuumvlies&lt;/li&gt;&#10;&lt;li&gt;Hochdrucklaminate, kleine Werkstücke, viele Durchfräsungen: Drehschieberpumpe mit großem Volumenstrom oder mehrstufige Seitenkanalgebläse sowie ein Lochrastervakuumtisch. Zudem Verwendung von an das Werkstück angepassten, speziellen Frässtrategien im CAM: späte Durchfräsungen in Kombination mit geringerer Tiefenzustellung.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="meine-wahl"&gt;Meine Wahl&lt;/h3&gt;&#10;&lt;p&gt;Da ich auf meiner Maschine Platten mit &lt;code&gt;60x60cm&lt;/code&gt; Größe in einem Rutsch bearbeiten möchte, habe ich mich für einen &lt;a href="https://blog.schallbert.de/why-vacuum-table/#meine-wahl"&gt;recht großen Lochrastervakuumtisch&lt;/a&gt; (&lt;code&gt;730 x 655 mm&lt;/code&gt;) mit &lt;code&gt;0.5mm&lt;/code&gt; Durchgangsbohrungen in einem &lt;code&gt;10mm&lt;/code&gt; Raster entschieden. Da ich noch immer eine Menge verschiedener Materialien auf meiner Maschine ausprobiere und hauptsächlich Prototypen anfertige, sehen meine Werkstücke selten ähnlich aus und haben sehr unterschiedliche Abmessungen (kleinstes Werkstück bis dato: &lt;code&gt;14x26mm&lt;/code&gt;). Daher musste ich in den sauren Apfel beißen und einen Haufen Geld in eine großzügig dimensionierte, trockenlaufende Drehschieberpumpe investieren, die für kleine Werkstücke einen hohen Unterdruck zur Verfügung stellt und dennoch eine Menge nicht abgedeckter Sauglöcher toleriert, ohne in die Knie zu gehen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_selection.jpg" alt="Image: Grid vacuum table" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Vergleich mehrerer Drehschieberpumpen (Bild bereitgestellt von Stritzelberger GmbH)&lt;/span&gt;&lt;a href="https://www.vakuumtisch.de/" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Pumpe stellt einen Volumenstrom von &lt;code&gt;40m³/h&lt;/code&gt; zur Verfügung und kann maximal &lt;code&gt;880mBar&lt;/code&gt; unter Umgebungsluftdruck gefahren werden. Sie wiegt in etwa &lt;code&gt;40kg&lt;/code&gt; und wird von einem &lt;a href="https://en.wikipedia.org/wiki/Induction_motor" target="_blank" rel="noopener noreferrer" class="external-link"&gt;3-Phasen Asynchronmaschine&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit einer Nennleistung von &lt;code&gt;1.3kW&lt;/code&gt; angetrieben.&lt;/p&gt;&#10;&lt;p&gt;Durch einen Umbau läuft meine Pumpe auf nur einer Phase. Ich habe zudem einen Motorschutzschalter hinzugefügt, um die Maschine vor Überlast zu schützen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-28_my_dst40_pump.jpg" alt="Image: My DST40 dry-running vane-type vacuum pump" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="ist-die-pumpe-laut"&gt;Ist die Pumpe laut?&lt;/h3&gt;&#10;&lt;p&gt;Na ja, &lt;code&gt;68dB(A)&lt;/code&gt; aus einem Meter Abstand sind nicht so richtig leise. Das Geräusch erinnert mich an das Außengerät einer Klimaanlage, die bei voller Last läuft. Ist die Fräsmaschine allerdings in Betrieb, so geht das Geräusch der Pumpe in den Fräsgeräuschen praktisch unter.&lt;/p&gt;&#10;&lt;p&gt;Aber überzeugen Sie sich selbst; ich habe ein kleines Video vom Betrieb der Pumpe erstellt:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/9CsPToFRYf3R7S6FC87gRZ" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Drehschiebervakuumpumpe: Inbetriebnahme&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Vakuumtisch für die CNC</title><link>https://blog.schallbert.de/why-vacuum-table/</link><pubDate>Fri, 21 Oct 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/why-vacuum-table/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_gr_thumb.jpg"&#10; class="post-cover"&#10; alt="Image of my hole grid vacuum table"&#10; title="Vakuumtisch für die CNC" /&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_operating_principle.jpg" alt="Image: Vacuum table principle of operation - pressure difference causes downforce" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Nehmen wir mal an, Sie sind nicht durch Zufall beim Surfen durch das Internet auf diesen Beitrag gestoßen. Sie wissen also bereits was ein Vakuumtisch ist (falls nicht: &lt;a href="https://en.wikipedia.org/wiki/Vacuum_table" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier weiterlesen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;). Sein Wirkprinzip wird im Bild auf der rechten Seite erläutert. Wahrscheinlich wissen Sie außerdem bereits, dass es verschiedene Typen Vakuumtische für unterschiedliche Zwecke gibt, und vielleicht auch schon, dass Sie einen anschaffen möchten.&lt;/p&gt;&#10;&lt;p&gt;Hervorragend! Dieser Artikel beschreibt sowohl meinen eigenen Weg hin zu einem Vakuumtisch, soll aber ebenfalls als Entscheidungshilfe für Sie dienen. Beginnen wir mit einem kleinen Interview.&lt;/p&gt;&#10;&lt;h2 id="warum-möchten-sie-einen-vakuumtisch-haben"&gt;Warum möchten Sie einen Vakuumtisch haben?&lt;/h2&gt;&#10;&lt;p&gt;Gleich zu Anfang die wichtigste Frage.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;🤩 Weil er toll aussieht, eine Menge Gewicht auf die Waage bringt, stabil und teuer ist?&lt;/li&gt;&#10;&lt;li&gt;😪 Weil Sie keine Lust mehr haben, sich ständig über das richtige Klemmen des Werkstücks Gedanken zu machen?&lt;/li&gt;&#10;&lt;li&gt;😖 Oder weil Sie bereits ein paar Fräser verschlissen haben beim Versuch durch Klemmen zu fahren?&lt;/li&gt;&#10;&lt;li&gt;😨 Oder doch weil sich Doppelklebeband als nicht stark genug erwiesen hat und Ihr Werkstück fliegen ging?&lt;/li&gt;&#10;&lt;li&gt;😎 Professionalisieren Sie Ihr Gewerk vielleicht gerade und Sie versuchen, Produktionszeit einzusparen?&lt;/li&gt;&#10;&lt;li&gt;😕 Vielleicht ist auch die Qualität Ihrer Werkstücke nach Bearbeitung unzureichend oder Sie haben Probleme, Plattenware plan zu bekommen?&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="meine-gründe-qualität-und-höhere-fertigungsgeschwindigkeit"&gt;Meine Gründe: Qualität und höhere Fertigungsgeschwindigkeit&lt;/h3&gt;&#10;&lt;p&gt;Dies hier soll mein Vakuumtisch in den Griff kriegen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Bei Gravuren machen nicht vollkommen plan aufliegende Werkstücke mir einen Strich durch die Rechnung, da die Gravurbreite ungleichmäßig wird. Dies ist leider nicht nur auf Holzwerkstoffe beschränkt.&lt;/li&gt;&#10;&lt;li&gt;Momentan arbeite ich mit Anbindungen, um Frästeile in Position zu halten. Meine Frästeile benötigen also manuelle Nacharbeit und die Anbindungen am Werkstück können danach immer noch erahnt werden.&lt;/li&gt;&#10;&lt;li&gt;Wenn ich keine Anbindungen wünsche, muss ich eine &amp;ldquo;Zwiebelhaut&amp;rdquo; übrig lassen, kann also nicht ganz durchfräsen. Das macht die Bearbeitung der Rückseite z.B. mit einem Gravurfräser erforderlich, was durch die genaue Positioniererfordernisse viel Zeit in Anspruch nimmt.&lt;/li&gt;&#10;&lt;li&gt;Mit einem Vakuumtisch werden ballige Werkstücke für die Bearbeitung plan gezogen, sodass Probleme ungleicher Gravuren, nicht erwünschter Durchfräsungen usw. behoben sind.&lt;/li&gt;&#10;&lt;li&gt;Unter bestimmten Bedingungen &lt;a href="https://blog.schallbert.de/cnc-vibrates/"&gt;leidet Meine Maschine an Vibrationen&lt;/a&gt;, besonders bei Verwendung langer Zweizahnfräser bei großer Tiefenzustellung und größeren Geschwindigkeiten. Das zusätzliche Gewicht und die flächige Kraftverteilung durch den Vakuumtisch könnten dieses Problem lösen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="welcher-typ-vakuumtisch"&gt;Welcher Typ Vakuumtisch?&lt;/h2&gt;&#10;&lt;p&gt;Ok, nun sollten wir herausfinden, welche Art Vakuumtisch sich am besten für Ihren Einsatz und für Ihre Randbedingungen eignet. Vakuumtische sind prinzipbedingt nur dann sinnvoll, wenn Plattenmaterial mit entsprechend großer Oberfläche bearbeitet wird. Zudem sollte das Plattenmaterial nicht zu biegsam oder sehr porös sein - Folien, Gummimatten und Weichschäume beispielsweise können von einem Vakuumtisch kaum gehalten werden, da durch die Fräskräfte angehobene Kanten des Plattenmaterials sofort das Vakuum kollabieren lassen.&lt;/p&gt;&#10;&lt;p&gt;Es gibt drei Typen Vakuumtische:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Rastervakuumtische&lt;/li&gt;&#10;&lt;li&gt;Lochrastervakuumtische&lt;/li&gt;&#10;&lt;li&gt;Vakuumtische mit poröser Oberfläche&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="rastervakuumtisch"&gt;Rastervakuumtisch&lt;/h3&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_r.jpg" alt="Image: Ein Rastervakuumtisch. Zu erkennen ist eine Aluminiumplatte, durchzogen von Nuten. Diese sind in einem quadratischen Raster angeordnet. In diese Nuten lässt sich eine Gummischnur einlegen, welche die zu vakuumierende Geometrie festlegt." /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Ein Rastervakuumtisch (Bild zur Verfügung gestellt von Stritzelberger GmbH)&lt;/span&gt;&lt;a href="https://www.vakuumtisch.de/Grid-vacuum-table-R-Series" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Viele Rastervakuumtische haben nur einen Vakuumanschluss, der in der Mitte des Tisches platziert ist. Der Bereich, in dem Unterdruck das Werkstück halten soll, wird mittels einer in das Raster gelegten (Schaum-) Gummidichtung bestimmt. Hierbei muss sich die Dichtung stets innerhalb des Werkstückes befinden.&lt;/p&gt;&#10;&lt;p&gt;Da die Dichtung bei Einschalten des Vakuums zusammengepresst wird und damit Vakuumtisch und Werkstück luftdicht miteinander verbindet, muss die Pumpe nur einen geringen Volumenstrom zur Verfügung stellen. Zudem hält das Werkstück bei entsprechend starkem Unterdruck sehr großen Zug- und Scherkräften stand, ohne sich zu lösen.&#10;Durch die flexible Dichtung können auch nicht ganz plan aufliegende Werkstücke oder solche mit rauen Oberflächen auf dieser Art Tisch gut bearbeitet werden.&lt;/p&gt;&#10;&lt;p&gt;Bei all den Vorteilen haben sie aber zwei gewichtige Nachteile: Werden erstens verschieden große Werkstücke verarbeitet, ist die Verlegung der Dichtung stets anzupassen. Außerdem muss beachtet werden, dass durch das meist in der Mitte befindliche Absaugloch auch die Werkstücke zentriert werden müssen; Anschläge für das vereinfachte Kantenfinden sind daher oft nicht verwendbar.&lt;/p&gt;&#10;&lt;p&gt;Zweitens darf nicht &amp;ldquo;innerhalb der Dichtung&amp;rdquo; durchgefräst werden. Tut man es doch, wird das Vakuum sofort zusammenbrechen, da die Pumpe plötzlich einen riesigen Luftstrom bewältigen muss. Daher darf das Werkstück nicht zu klein werden (das Raster ist das Maß der Dinge) oder man muss zusätzlich Arbeit in Klemmen oder Halterungen stecken, die dieses Problem lösen.&lt;/p&gt;&#10;&lt;p&gt;Kurzgesagt:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Rastervakuumtische funktionieren sehr gut, solange man nicht durchfräst.&lt;/li&gt;&#10;&lt;li&gt;Rastervakuumtische stellen geringe Anforderungen an die Pumpe und halten auch unebene oder verzogene Werkstücke sicher.&lt;/li&gt;&#10;&lt;li&gt;Rastervakuumtische sind günstig, benötigen aber bei unterschiedlichen Werkstückgrößen, kleinen Werkstücken oder Durchfräsungen extra angepasste Halterungen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="lochrastervakuumtisch"&gt;Lochrastervakuumtisch&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_seal.jpg" alt="Image: Hole grid vacuum table" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Ein Lochrastervakuumtisch (Bild zur Verfügung gestellt von Stritzelberger GmbH)&lt;/span&gt;&lt;a href="https://www.vakuumtisch.de/Rastervakuumtisch-R-Serie" class="attr-link" aria-label="Attribution 2"&gt;&#10; &lt;sup class="attr-id"&gt;[2]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Lochrastervakuumtische verfügen über - wie der Name schon sagt - in einem Raster angeordnete Sacklöcher, über die Luft abgesaugt wird. In jedem dieses Sacklöcher befindet sich eine winzige Durchgangsbohrung, die zu einem im Tisch befindlichen Kanalsystem führt. Der Durchmesser der Durchgangsbohrung wird vom vorrangig zu bearbeitenden Material vorgegeben - sie sind etwas größer bei &amp;ldquo;porösen&amp;rdquo; Materialien wie Holz und MDF, und kleiner für die Aluminium- oder Stahlbearbeitung.&lt;/p&gt;&#10;&lt;p&gt;Zumeist sind sie in zuschaltbare Segmente unterteilt, sodass bei kleineren Werkstücken nicht der ganze Tisch abgedeckt werden muss um Leckströme zu vermeiden. Dennoch ist es sinnvoll, weiterhin freiliegende Löcher abzudecken und damit den Vakuumdruck zu erhöhen.&lt;/p&gt;&#10;&lt;p&gt;Diese Vakuumtische bestehen meist aus drei Teilen: Dem Unterbau zur Montage auf dem Maschinenbett, einer mittleren Ebene mit Vakuumanschlüssen und dem Kanalsystem sowie einer oberen Ebene mit Lochraster, was zudem oft eine Bearbeitungsreserve zum Planfräsen aufweist.&lt;/p&gt;&#10;&lt;p&gt;Sie sind in der Lage, Werkstücke jeglicher Form niederzuhalten, vorausgesetzt sie besitzen eine ebene Fläche auf der Unterseite. Die kleinen Durchgangsbohrungen ermöglichen auch das Durchfräsen: die betroffenen Löcher liefern zwar keinen Beitrag mehr zur Klemmkraft, überfordern mit dem geringen Leckstrom aber auch die Vakuumpumpe nicht, sodass sie den Unterdruck bei den anderen Löchern aufrecht erhalten kann (solange man es mit der Anzahl freiliegender Löcher nicht übertreibt). Das Ausrichten des Werkstücks ist auf solchen Tischen ebenfalls einfach, da das Lochraster einfach mit Zentrierstiften bestückt werden kann, die einen Anschlag o.Ä. bilden.&lt;/p&gt;&#10;&lt;p&gt;Nachteile dieses Tischtyps sind ihr hoher Preis durch das aufwändige Design, mehr benötigtes Zubehör wie Vakuumverteiler bei einem Mehrkammersystem (und alles muss leckfrei gehalten werden), sowie hohe Anforderungen an die Vakuumpumpe bei Durchfräsungen.&lt;/p&gt;&#10;&lt;p&gt;Kurzgesagt:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Lochrastervakuumtische sind für jedes feste Werkstück mit genügend Oberfläche und glatter Unterseite geeignet.&lt;/li&gt;&#10;&lt;li&gt;Lochrastervakuumtische tolerieren Durchfräsungen und freiliegende Löcher, erfordern dann aber großzügig dimensionierte Vakuumpumpen.&lt;/li&gt;&#10;&lt;li&gt;Lochrastervakuumtische vereinfachen die Positionierung von Werkstücken ungemein.&lt;/li&gt;&#10;&lt;li&gt;Lochrastervakuumtische sind komplex in der Fertigung und daher vergleichsweise teuer.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="vakuumtische-mit-poröser-oberfläche"&gt;Vakuumtische mit poröser Oberfläche&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_porous.jpg" alt="Image: Porous surface table, MDF top layer" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Ein Porenvakuumtisch (Bild zur Verfügung gestellt von &amp;#39;Jens´ Vakuumtische&amp;#39;)&lt;/span&gt;&lt;a href="https://hobbyline.info/forum/index.php?thread/366-neue-erkenntnisse-von-meinem-vakuumtisch/&amp;amp;pageNo=1" class="attr-link" aria-label="Attribution 3"&gt;&#10; &lt;sup class="attr-id"&gt;[3]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Diese Art Vakuumtisch benötigt weder sichtbare Löcher noch Kanäle an seiner Oberfläche: Stattdessen ist die Oberfläche selbst luftdurchlässig. Für das Fräsen von Leiterplatten wird als Material oft Aluminiumschaum verwendet. Im Hobbybereich gibt es auch gute Erfahrungen mit überplanter mitteldichter Faserplatte. Bei Letzterer ist es beim Durchfräsen nicht einmal schlimm, wenn man sich mit der Materialdicke vertan hat, da MDF solche Fehler gut verzeiht und bei Bedarf einfach eine frische Platte aufgelegt werden kann.&lt;/p&gt;&#10;&lt;p&gt;Die Tische sind leicht und - mit viel Hintergrundwissen über Werkstoffe und Dünwwandfräsen - sogar als &amp;ldquo;Marke Eigenbau&amp;rdquo; zu fertigen. Im Netz gibt es dazu viele Beispiele.&lt;/p&gt;&#10;&lt;p&gt;Sie funktionieren am besten über einen großen Volumenstrom und arbeiten daher meist mit geringen Unterdrücken - sie erhalten ihre Klemmkraft also über die Fläche. Besser noch als Lochrastervakuumtische sind sie also für Durchfräsungen geeignet. Zudem sind sie in der Anschaffung günstig und können sogar mit Staubsaugern betrieben werden, deren Motoren auch ohne Förderstrom durch ein eigenes Gebläse gekühlt werden.&lt;/p&gt;&#10;&lt;p&gt;Ihre Nachteile: Durch ihr geringes Gewicht sind sie anfälliger für Vibrationen. Sie benötigen Werkstücke mit eher großer Oberfläche um adäquate Klemmkräfte zu realisieren und bei hohen Unterdrücken bleibt das poröse Obermaterial nicht maßhaltig, es &amp;ldquo;schrumpft&amp;rdquo; also bei aufgelegtem Werkstück etwas. Außerdem sind sie nicht geeignet bei Verwendung von Kühlschmierstoffen und haben einen ungünstigen statischen Reibungskoeffizienten (Je nach Werkstückpaarung bieten sie keinen besonders guten Halt gegen laterale Verschiebung).&lt;/p&gt;&#10;&lt;p&gt;Kurzgesagt:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Porenvakuumtische sind günstig zu erwerben und zu betreiben.&lt;/li&gt;&#10;&lt;li&gt;Porenvakuumtische sind für größere Werkstücke und Materialien, die keine Kühlschmierstoffe benötigen, gut geeignet.&lt;/li&gt;&#10;&lt;li&gt;Porenvakuumtische sind bei Durchfräsungen sehr gut geeignet.&lt;/li&gt;&#10;&lt;li&gt;Porenvakuumtische sind bei Verwendung von MDF nicht präzise in Z-Richtung.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="was-alle-diese-vakuumtische-gemeinsam-haben"&gt;Was alle diese Vakuumtische gemeinsam haben&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_vacuum_principle.jpg" alt="Image: How surface irregularities help keep pressure differences homogenous" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Die Klemmkraft auf das Werkstück in Z-Richtung kann für einen gegebenen Unterdruck auf identische Art berechnet werden. Aber vorsicht, dieser Unterdruck ist bei Durchfräsungen und anderen Quellen für Leckagen am Werkstück geringer als auf dem Manometer angezeigt.&lt;/p&gt;&#10;&lt;p&gt;Werden keine Durchfräsungen gefertigt, lässt sich die volle Werkstückoberfläche zur Berechnung der Klemmkraft hinzuziehen (und nicht nur die Summe der Fläche aller Löcher im Bereich unter dem Werkstück im Falle des Lochrastertisches). Das kommt daher, dass mikroskopisch kleine Unebenheiten auf der Oberfläche von Werkstück und Tisch einen geringen lateralen Luftfluss an der Grenzschicht zulassen.&lt;/p&gt;&#10;&lt;p&gt;Andererseits gilt dies auch für die Ränder des Werkstücks. Nutzt man also keinen Rastervakuumtisch, wird es immer Leckageluftströme geben. Fräst man zusätzlich komplett durch das Material, werden weitere Randzonen erzeugt, die wiederum geringere Klemmkräfte aufweisen als der Rest des Werkstücks.&lt;/p&gt;&#10;&lt;h2 id="welches-zubehör-für-einen-vakuumtisch"&gt;Welches Zubehör für einen Vakuumtisch?&lt;/h2&gt;&#10;&lt;p&gt;Ohne Pumpe funktioniert der Vakuumtisch natürlich gar nicht. Und obacht, werden hohe Unterdrücke und gleichzeitig große Volumenströme verlangt, bewegt sich der Preis für die gewünschte Pumpe ganz schnell in derselben Liga wie der Vakuumtisch. Aber das führt hier zu weit - ich habe den &lt;a href="https://blog.schallbert.de/cnc-vacuum-pumps/"&gt;Vakuumpumpen einen eigenen Artikel&lt;/a&gt; gewidmet. Aber es wird weiteres Zubehör benötigt:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schrauben, Unterlegscheiben, Nutensteine / Muttern um den Tisch auf der Maschine zu befestigen&lt;/li&gt;&#10;&lt;li&gt;Sortiment verschieden dicker Unterlagsbleche für die plane Montage des Tisches gegenüber der Z-Achse der Maschine&lt;/li&gt;&#10;&lt;li&gt;Einen Vakuumverteiler, falls der Vakuumtisch mehrere Zonen besitzt&lt;/li&gt;&#10;&lt;li&gt;Vakuumleitungen&lt;/li&gt;&#10;&lt;li&gt;Einen Schalter mit Motorschutz für die Vakuumpumpe&lt;/li&gt;&#10;&lt;li&gt;gegebenenfalls ein Vakuumvlies (Holz-, Kunststoffbearbeitung) oder eine Lochrastermatte aus Gummi (Alu, Stahl)&lt;/li&gt;&#10;&lt;li&gt;Dichtband (für Rastervakuumtische)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="zusammenfassung"&gt;Zusammenfassung&lt;/h3&gt;&#10;&lt;p&gt;Alles in allem will die Anschaffung eines Vakuumtisches gut überlegt sein - gerade wegen des großen Kosten- und Zeitaufwandes für Montage, Einrichtung und Betrieb des Tisches. Entscheidet man sich dennoch dafür, so wird man bei zweckmäßigem Einsatz mit höherer Fertigungsqualität durch die flächige Auflage des Werkstücks, kürzerer Richtzeit durch Wegfallen der Klemmen und einfacherer Jobplanung belohnt - denn Materialanbindungen sind nun passé.&lt;/p&gt;&#10;&lt;h2 id="meine-wahl"&gt;Meine Wahl&lt;/h2&gt;&#10;&lt;p&gt;Ich arbeite ausschließlich mit Plattenmaterial. Ich fertige bisher Produkte aus Holz, Hochdrucklaminaten, Acrylglas, mitteldichter Faserplatte oder Aluminium-Verbundmaterial. Zumeist muss ich Konturen aus einer größeren Platte ausschneiden (Durchfräsen) und wende Nestingverfahren an, um möglichst wenig Abfall zu erzeugen. Ich arbeite oft zweiseitig, was eine genaue Lokalisierung des Werkstückes auf dem Maschinentisch erfordert. Daher kommt ein Rastervakuumtisch für mich nicht infrage.&lt;/p&gt;&#10;&lt;p&gt;Um die Kanten auf der Werkstückunterseite anzufasen, schneide ich im ersten Arbeitsgang meist nicht ganz durch, sondern lasse einen V-Fräser diesen letzten Schritt auf der Unterseite erledigen. Da ich hierfür eine sehr exakte Bestimmung der Werkstückhöhe benötige und der Unterdruck sich bei Auffräsen einer Kontur durch den Fasenfräser verringert, muss die Oberfläche des Vakuumtisches starr sein - daher kommt ein Porenvakuumtisch mit Oberfläche aus MDF für mich nicht in Frage.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_bl0607_dwg.jpg" alt="Image: Creating a drawing of my machine bed for the vacuum table manufacturer" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Stattdessen habe ich einen Hersteller für Vakuumtische direkt kontaktiert, um ein paar kleine Lochrastervakuumtische zu kaufen, die ich unabhängig voneinander einschalten und damit je nach Größe des Werkstücks Segmente auf meiner CNC mit Unterdruck versorgen könnte. Dieser meinte, dass die einfachere und bessere Lösung ein für meine Maschine angepasster Vakuumtisch sei, da bei Zusammenstellen mehrerer Tische Höhenunterschiede und Undichtigkeiten entstehen würden und die Zuführung der Vakuumleitung erschwert würde.&lt;/p&gt;&#10;&lt;p&gt;Also habe ich gespart und mir den teureren, großen Tisch zugelegt. Hierfür habe ich dem Hersteller Zeichnungsdaten meiner Maschine zur Verfügung gestellt und nach ein paar Gesprächen und Vorschlägen seitens des Herstellers hatte ich dann einen an meine Bedarfe angepassten Entwurf.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_custom_table.jpg" alt="Image: Custom designed vacuum table to fit my machine" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="lieferung-des-tisches"&gt;Lieferung des Tisches&lt;/h3&gt;&#10;&lt;p&gt;Der von mir ausgewählte Hersteller sitzt nicht zu weit von meinem Wohnort entfernt. Also bin ich hingefahren für eine kleine Führung durch die Produktion und konnte den Tisch dabei direkt mitnehmen. Da ich für die Montage meine alte Werkstückunterlage vom Maschinentisch nehmen musste, konnte ich in einem Zug direkt Schalldämmung im Maschinenrahmen anbringen. So wird die Maschine nochmal etwas leiser.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_mounting.jpg" alt="Image: Vacuum table mounting" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Danach habe ich den Vakuumtisch mit M6 Maschinenschrauben und T-Nut Muttern am Rahmen befestigt. Für eine ebene Oberfläche (gemessene Abweichung in Z über die gesamte Fläche des Maschinentisches unter &lt;code&gt;15/100mm&lt;/code&gt;, Messuhr siehe Bild unten links) habe ich Unterlegbleche verwendet und stundenlang nachkontrolliert und -justiert, bis ich zufrieden war.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/8qP4FeBN2RYAVxkB4SWPHi" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Parallelität der Z-Achse prüfen&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Für noch bessere Ergebnisse könnte man:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Den Vakuumtisch einmal komplett planfräsen&lt;/li&gt;&#10;&lt;li&gt;Dn Tisch wieder herunternehmen und den Maschinenrahmen so nachrustieren, dass die Profile exakt waagerecht aufliegen und die Oberseiten aller Rahmenteile auf das Hundertstel genau auf einer Ebene enden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Dies erschien mir als unverhältnismäßig großer Aufwand angesichts der vorhandenen Maschinentoleranzen (Immerhin handelt es sich hier um einen Eigenbau), also gebe ich mich mit den oben erreichten Werten zufrieden.&lt;/p&gt;&#10;&lt;h3 id="anschluss-des-vakuumverteilers"&gt;Anschluss des Vakuumverteilers&lt;/h3&gt;&#10;&lt;p&gt;Als ich einfach drauflos die dünnen Vakuumleitungen zur Verbindung von Tisch und Verteile zurechtschnitt, dachte ich mir: &amp;ldquo;Warte mal eine Sekunde. Wäre es nicht klüger, die 9mm-Leitungen so kurz wie möglich zu halten, und lieber die 18er-Leitung zur Pumpe länger zu lassen und so den Druckabfall zu reduzieren?&amp;rdquo; Also habe ich mich erst einmal mit dem Taschenrechner zurückgezogen.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Mein Tisch hat etwa 4900 Sauglöcher mit je &lt;code&gt;0.5mm&lt;/code&gt; Durchmesser. Ihre zusammengerechnete Oberfläche beträgt \(A_{table}=962mm^2\), was in etwa einem Loch mit &lt;code&gt;35mm&lt;/code&gt; Durchmesser entspricht (ich ignoriere hier komplett die Widerstände an den Lochrändern.)&lt;/li&gt;&#10;&lt;li&gt;Die sieben Vakuumleitungen zum Verteiler haben einen Durchmesser von &lt;code&gt;9mm&lt;/code&gt;. Zusammengenommen liegt ihre Oberfläche bei \(A_{hose}=445mm^2\), entsprechen also einem Loch mit &lt;code&gt;24mm&lt;/code&gt; Durchmesser; deutlich weniger also als der Gesamtquerschnitt der Löcher des Tisches.&lt;/li&gt;&#10;&lt;li&gt;Für den Saugschlauch mit &lt;code&gt;18mm&lt;/code&gt; Innendurchmesser sieht es sogar noch schlimmer aus: \(A_{pump}=254mm\) liegt bei unter 30% des Querschnitts auf dem Tisch.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Wenn ich jetzt also von einem gleichmäßigen Leckagestrom über die sieben Segmente meines Tisches ausgehe, macht es Sinn, die Pumpenzuleitung so kurz wie möglich zu wählen, da sie bei vielen offenen Sauglöchern die höchste Flussgeschwindigkeit und damit den höchsten Druckverlust aufweist. In einem Szenario mit weniger verwendeten Segmenten wird irgendwann die 9mm-Leitung ausschlaggebend.&lt;/p&gt;&#10;&lt;p&gt;Daher ist also zu beachten, dass das Manometer am Vakuumverteiler bei größeren Leckageluftströmen einen höheren Wert anzeigt, als am Tisch tatsächlich anliegt!&lt;/p&gt;&#10;&lt;p&gt;Da ich die Zahl freiliegender Löcher aber begrenzen werde, kann ich mir einfach eine komfortable Position für den Vakuumverteiler aussuchen und die Verlegung abschließen 😉. Übrigens ist das erste auf meinem neuen Vakuumtisch gefertigte Teil ein Halter für den Vakuumverteiler aus Holz - wie im Bild rechts zu erkennen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-10-21_table_mounted.jpg" alt="Image: Vacuum table mounted" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Estlcam: Inverse Gravuren</title><link>https://blog.schallbert.de/negative-carving-with-estlcam/</link><pubDate>Tue, 27 Sep 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/negative-carving-with-estlcam/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-09-27_carve_results-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: A negative carve in wood"&#10; title="Estlcam: Inverse Gravuren" /&gt;&#10;&lt;p&gt;Der Plan war, ein Holzschild mit meiner CNC zu fertigen. Es sollte jedoch invertiert sein. Das heißt, die Lettern sollten sich vom flachen Hintergrund abheben. Da es verschiedene Wege gibt, so etwas in Estlcam umzusetzen (ein sogenanntes Computer Aided Machining Programm, was aus Zeichnungsdaten Fräspfade und später Maschineninstruktionen erzeugen kann), aber nicht alle Wege leicht zu finden oder gut anwendbar sind, biete ich hier eine Schritt-für-Schritt Anleitung an.&lt;/p&gt;&#10;&lt;h3 id="1-den-rahmen-des-schildes-importieren"&gt;1. Den Rahmen des Schildes importieren&lt;/h3&gt;&#10;&lt;p&gt;Zuerst sollten Zeichnungsdaten des Rahmens von dem zu fertigenden Schild vorliegen, beispielsweise im &lt;code&gt;DXF&lt;/code&gt;-Format. Ich habe ein rechteckiges Schild mit abgerundeten Ecken und definierter Rahmenstärke in meinem CAD-Programm gezeichnet. Es gilt sicherzustellen, dass die Frästiefe später nicht die Breite des Rahmens überschreitet, ansonsten könnte die Fräsarbeit zu instabil werden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-09-27_frame.jpg" alt="Image: Frame for my sign" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="2-den-text-hinzufügen"&gt;2. Den Text hinzufügen&lt;/h3&gt;&#10;&lt;p&gt;Jetzt kann der Inhalt des Schildes in Form von Text durch Tastendruck auf das &amp;ldquo;Carve&amp;rdquo;-Symbol hinzugefügt werden. Dabei ist es sinnvoll, die Breite der Textbox an die inneren Rahmengrenze anzupassen, sodass der Text stets hineinpasst. Man hat verschiedene Carve-Stile zur Auswahl. Für die hier dargestellte Fräsarbeit ist &lt;code&gt;Carve aussen&lt;/code&gt; zu wählen. Nun den Text eintippen, Schriftart selektieren und die Schrifthöhe so anpassen, dass es im Schild ein harmonisches Bild ergibt.&lt;/p&gt;&#10;&lt;p&gt;Abhängig vom zu verwendenden Fräser sollte dann die Frästiefe gewählt werden. Bei kleineren Winkeln der V-Nutfräsers ist die Frästiefe meist größer zu wählen als bei breiten 90°-Fräsern wie ich ihn verwende. Für mein &lt;code&gt;12mm&lt;/code&gt; starkes Plattenmaterial setze ich eine Frästiefe von &lt;code&gt;4.5mm&lt;/code&gt; an. Anschließend wird die maximale Fräsbreite festgelegt. Gemeint ist hier, wie breit die &amp;ldquo;Täler&amp;rdquo; zwischen den Buchstaben sein sollen. Da ich für den invertierten Carve eine &amp;ldquo;Talsohle&amp;rdquo; benötige, habe ich hier einen sehr großen Wert eingestellt. Zum Schluss dann sollte die Taste &lt;code&gt;Umrahmung auswählen&lt;/code&gt; geklickt werden. Hiermit ist die Fräsarbeit dann nach außen begrenzt.&lt;/p&gt;&#10;&lt;p&gt;Bitte dieses Bearbeitungsfenster vorerst geöffnet lassen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-09-27_carve_text.jpg" alt="Image: Settings for the text to carve" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="3-werkzeugauswahl"&gt;3. Werkzeugauswahl&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-09-27_carve_preview1.jpg" alt="Image: Carve preview 1" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Wählen Sie nun das V-Nutwerkzeug durch Klicken in der entsprechenden Zeile Ihrer Werkzeugliste für die Textfräsung aus. Stellen Sie sicher, dass das Häkchen bei &lt;code&gt;Textobjekt zerlegen&lt;/code&gt; im Fenster &amp;ldquo;Text Fräsen&amp;rdquo; nicht erscheint, denn nur dann können Sie weitere Anpassungen vornehmen. Schließen Sie nun dieses Fenster per Klick auf &lt;code&gt;OK&lt;/code&gt;. Nun wählen Sie &lt;code&gt;Vorschau&lt;/code&gt; aus, um sich einen Eindruck von der Fräsarbeit zu verschaffen. Sehen Sie die eng beisammenstehenden parallelen Linien rechts im Bild? Dies wird durch den Parameter &lt;code&gt;Räumzustellung&lt;/code&gt; aus der Werkzeugtabelle verursacht. Für saubere Arbeiten mit einem Gravurfräser sollte dieser Wert bei kleiner gleich &lt;code&gt;5%&lt;/code&gt; bleiben.&lt;/p&gt;&#10;&lt;p&gt;Hmm, so werden Sie noch keine befriedigenden Arbeitsergebnisse erhalten, und die Fräsarbeit wird lange dauern. Wir benötigen also zusätzlich ein paar Räumpfade mit einem normalen Schaftfräser.&lt;/p&gt;&#10;&lt;h3 id="4-räumen"&gt;4. Räumen&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-09-27_carve_clearing_tool.jpg" alt="Image: Carve preview 1" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Speichern Sie Ihr Projekt spätestens jetzt ab. Nach dem aktuellen Schritt kann der Text nicht mehr bearbeitet werden!&lt;/p&gt;&#10;&lt;p&gt;Um den Fräser für die Räumung der &amp;ldquo;Talsohle&amp;rdquo; hinzuzufügen, wählen Sie den Carve-Pfad aus und wählen &lt;code&gt;Textobjekt zerlegen&lt;/code&gt; an. Bestätigen Sie mit &lt;code&gt;OK&lt;/code&gt; und wählen den Fräspfad erneut aus. Nun erscheint die Werkzeugkonfiguration. Fügen Sie das Räumwerkzeug hinzu. Vorzugsweise ist sein Durchmesser geringer als der Abstand zwischen Schrift und Rahmen, sodass eine möglichst große Fläche später glatt geräumt werden kann (Anmerkung: Ich habe das nicht gemacht, weswegen auf meinem Beispiel ein paar raue Stellen übrig geblieben sind). Die Räumzustellung dieses Fräsers wähle ich bei Holz zwischen 20% und 40%.&lt;/p&gt;&#10;&lt;p&gt;Wenn Sie für die Gravur einen Fräser ohne Abflachung am Boden gewählt haben (ich empfehle hier gern &lt;code&gt;0.5mm&lt;/code&gt; als guten Kompromiss zwischen hoher Auflösung und guter Fertigungsqualität), können Sie die Räumzustellung des Fräsers in der Werkzeugtabelle zur Verbesserung der Oberflächenqualität noch einmal nach unten korrigieren.&lt;/p&gt;&#10;&lt;h3 id="5-zweite-vorschau"&gt;5. Zweite Vorschau&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-09-27_carve_preview2.jpg" alt="Image: Carve preview 2" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ok, jetzt sollten wir durch sein. Nochmal kurz die Vorschau ausgewählt und sich über die reduzierte Verarbeitungszeit gefreut, und ran an die Maschine?&lt;/p&gt;&#10;&lt;p&gt;Ah, eine weitere Optimierung kann für das Gravurfräsen angebracht sein: Unter &lt;code&gt;Einstellungen&lt;/code&gt; die Sicherheitshöhe auf ein Minimum reduzieren (aber ohne in Klemmen oder andere Hindernisse zu rauschen). Das ständige Aus- und Eintauchen ins Material kostet bei Gravuren in Estlcam immer recht viel Zeit, wenn sich die Sicherheitshöhe in großem Abstand zur Werkstückoberfläche befindet.&lt;/p&gt;&#10;&lt;p&gt;Keine Sorge, wenn die Schrift in der Vorschau nun nicht mehr lesbar ist. Das ist total normal, weil ja nur die (jetzt wegen Verwendung des Räumfräsers gröber dargestellten) Fräspfade angezeigt werden.&lt;/p&gt;&#10;&lt;p&gt;Nach einer letzten Prüfung von Vorschüben und Werkzeugen kann&amp;rsquo;s jetzt endlich losgehen :)&lt;/p&gt;&#10;&lt;h3 id="engrave"&gt;6. Auf die CNC damit!&lt;/h3&gt;&#10;&lt;p&gt;Viel Spaß beim Fräsen. Ich für meinen Teil fand es sehr entspannend, der Maschine dabei zuzusehen, wie sie langsam ein immer deutlicher werdendes Bild ins Holz schneidet, was aber erst bei Verwendung des V-Fräsers lesbar wird.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-09-27_carve_results.jpg" alt="Image: The completed sign" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;</description></item><item><title>AnywhereAmps Alpha</title><link>https://blog.schallbert.de/anywhereamps-diy/</link><pubDate>Sat, 10 Sep 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/anywhereamps-diy/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/front-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: AnywhereAmps Alpha&amp;#39;s insides"&#10; title="AnywhereAmps Alpha" /&gt;&#10;&lt;h2 id="pläne"&gt;Pläne&lt;/h2&gt;&#10;&lt;p&gt;{% include gallery id=&amp;ldquo;gallery&amp;rdquo; caption=&amp;ldquo;Die von mir angefertigten und verwendeten Pläne für &lt;strong&gt;AnywhereAmps Alpha&lt;/strong&gt;. Zum Vergrößern anklicken.&amp;rdquo; %}&lt;/p&gt;&#10;&lt;h2 id="listen"&gt;Listen&lt;/h2&gt;&#10;&lt;h3 id="werkzeuge"&gt;Werkzeuge&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Benötigte Tools&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Bohrmaschine&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Oberfräse, Zirkelhalterung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Multitool: Schleifen, Holzschnitt, Fräskopf&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hand- oder Tischkreissäge mit Schutzbügel&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Handsäge, fein&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lochschneider d&amp;gt;=100mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stechbeitel, 12mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hammer&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schraubendreher, Bits&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Holzbohrer: 3mm, 4, 5, 6, 8, 10, 12mm, Senkbohrer 45°, Forstner 30mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stahlbohrer: 4,3 mm, Senkung 45°&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Oberfräsere: 8mm gerade, 12mm gerade, 45°/d12mm optional&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lötkolben, Lötzinn, Schrumpfschlauch, Heißkleber, 2m 0,75mm² Kabel etc.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/wood_tools.jpg" alt="Holzschnitzwerkzeuge" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="materialliste"&gt;Materialliste&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Artikel&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Größe [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Anzahl&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;~ Preis [€]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kessel: Pappel Multiplex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&amp;quot; x 6&amp;quot; x 7mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;62&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ober-/Unterteil: Pappel-Multiplex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&amp;quot; x 12mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lautsprecher-Adapterplatten: Pappel Multiplex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;L 300 B 150 W 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Verstärker-Halteplatte: Pappel Multiplex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;L 160 B 95 W 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Verstärkung &amp;amp; Halterungen: Birke Multiplex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;L 220 B 30 W 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schrauben (vorzugsweise Torx)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12 x 3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schrauben (Batteriehalter, Haltegriff)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16 x 3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schrauben &amp;amp; Muttern, Leiterplattenhalter, Unterlegscheiben&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;15 x M4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schaumstoffdichtung&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2000 x 9 x 3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1/4&amp;quot; Eingangsbuchse&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;50 x 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Netzschalter mit LED, 250VAC 4A&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Potentiometer 250k log 6mm Achse + Lautstärkeknopf&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;22mm Durchmesser&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;9V Ausgangsbuchse&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5.6 brl, 2.4pin&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lüftergitter (Lautsprecherschutzabdeckung)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;120 x 120&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&amp;ldquo;I-Type&amp;rdquo; Schaumstoffdichtung (Lautsprecher)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;600 x 9 x 2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Aluminium-Gehäuse&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;143 x 72 x 43&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Class-D Endstufenplatine, z.B. TPA 3110&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;70 x 100 + 22&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Piezo-Hochtöner, z. B. Monacor MPT-005&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;85 x 85 x 72&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&amp;quot; Tief-/Mitteltöner, z. B. Visaton KT100 V 4Ohm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;99 x 99 x 52&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;25&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Batterie, 10V - 18V&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Was Sie bevorzugen&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Elektronikteile für Platine, Sicherung&amp;hellip;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ersatzholz z.B. als Maske, Schablone, Helfer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;z.B. 300 x 300&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="bauteile-auf-der-platine"&gt;Bauteile auf der Platine&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;ID&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Artikel&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Typ&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Wert&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;R1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Widerstand&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0207&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100k&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;C1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Elko&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RM=2 D=5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 µF&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;C2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Elko&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RM=2 D=5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4,7 µF&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;R3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Widerstand&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0207&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10k&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;R2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Widerstand&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0207&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100k&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stiftleiste&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2-polig&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stiftleiste&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4-polig&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stiftleiste&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2-polig&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;C1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Elko&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RM=2 D=5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2,2 µF&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;R4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Widerstand&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0207&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10k&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;IC1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;NE5532&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DIP-8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="brückendrähte"&gt;Brückendrähte&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Von&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Nach&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Länge [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(11/1)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(11/2)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.54&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(10/3)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(10/1)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5.08&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(14/6)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(14/2)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10.6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(8/1)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(8/6)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12.7&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(9/2)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(9/5)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7.62&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(3/2)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(3/0)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5.08&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(22/0)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;(22/1)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.54&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Loch (x/y)&lt;/p&gt;&#10;&lt;h2 id="der-aufbau"&gt;Der Aufbau&lt;/h2&gt;&#10;&lt;h3 id="löcher-schneiden"&gt;Löcher schneiden&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/adapterboard_before.jpg" alt="Bild: Adapterplatte angezeichnet" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;strong&gt;Warnung&lt;/strong&gt; Wenn Sie den Lochschneider als Handwerkzeug verwenden, seien Sie sehr vorsichtig. &lt;em&gt;Ich halte es für ein gefährliches Werkzeug.&lt;/em&gt; Gehen Sie sanft vor, wählen eine relativ hohe Drehzahl &lt;code&gt;&amp;gt;300rpm&lt;/code&gt; und üben Sie nur geringem Druck aus. Verwenden Sie eine Drehmomentstütze und den Drehmomentbegrenzer Ihrer Bohrmaschine. Vergewissern Sie sich, dass die Schneidköpfe richtig befestigt sind und dass das Werkstück sicher befestigt ist. Bereiten Sie sich darauf vor, dass die Maschine plötzlich hängen bleiben kann und beachten Sie das hohe Trägheitsmoment des schweren Werkzeugs.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Bereiten Sie das Brett für die Lautsprecheradapterplatten vor wie &lt;a href="https://blog.schallbert.de/anywhereamps-diy/#pl%c3%a4ne"&gt;in den &amp;ldquo;Plänen&amp;rdquo;&lt;/a&gt; gezeigt.&lt;/li&gt;&#10;&lt;li&gt;Bereiten Sie das Gehäuse vor: Markieren Sie die Mitte der Höhe und zeichnen Sie eine Hilfslinie an der Außenseite. Eventuell sollte ein Maßband verwendet werden, um die Krümmung zu berücksichtigen. Markieren Sie dann die drei Lochmitten. Ich schlage eine geodätische Kurve von &lt;code&gt;125mm&lt;/code&gt; Radius zwischen ihnen vor.&lt;/li&gt;&#10;&lt;li&gt;Bereiten Sie den oberen Kreis für den Ausschnitt des Bedienfeldes vor.&lt;/li&gt;&#10;&lt;li&gt;Wähle einen Lochdurchmesser von &lt;code&gt;75mm&lt;/code&gt; auf dem Lochschneider.&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie zwei Löcher für die Hochtöner in die Adapterplatte. Wenn die Platine nach der Hälfte des Schnitts umgedreht wird, werden die Kanten der Löcher sauberer.&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie nun die beiden Löcher in das Gehäuse. Seien Sie hier sehr vorsichtig, Sie können die innere Oberfläche leicht zerstören, wenn Sie mit zu geringer Geschwindigkeit schneiden oder vertikalen Druck ausüben.&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie ein Loch in die von Ihnen vorbereitete obere Platte des Gehäuses. Ich habe &lt;code&gt;76mm&lt;/code&gt; verwendet.&lt;/li&gt;&#10;&lt;li&gt;Ändern Sie den Lochdurchmesser an Ihrem Cutter auf &lt;code&gt;92mm&lt;/code&gt; und wiederholen Sie den Vorgang für den Bass/Mitten-Lautsprecher.&lt;/li&gt;&#10;&lt;li&gt;Den Schnitt für das mittlere Loch des Gehäuses wiederholen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/shell_holes.jpg" alt="Bild: Löcher in das Gehäuse schneiden" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="fräsen"&gt;Fräsen&lt;/h3&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/router_topbottom.jpg" alt="Bild: Fräsen des Deckels per Zirkel" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Legn Sie den Kessel auf das Brett, aus dem Ober- und Unterteil des Gehäuses gefertig werden sollen. Zeichnen Sie Innen- und Außendurchmesser an.&lt;/li&gt;&#10;&lt;li&gt;Finden Sie den Mittelpunkt dieses Kreises, z.B. mit Hilfe der &lt;a href="https://www.wikihow.com/Find-the-Center-of-a-Circle" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Methode 3 dieser Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Montieren Sie das Zirkelwerkzeug auf Ihrer Oberfräse und spannen Sie einen geraden Fräskopf mit &lt;code&gt;12mm&lt;/code&gt; Durchmesser ein.&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie den &amp;ldquo;inneren&amp;rdquo; Kreis für den Falz. Ich empfehle eine Tiefe von &lt;code&gt;4mm&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;(optional) Montieren Sie den &lt;code&gt;8mm&lt;/code&gt;-Fräskopf.&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie den &amp;ldquo;äußeren&amp;rdquo; Kreis in mehreren Schritten, bis Sie die gesamte Tiefe Ihres Brettes ausgeschnitten haben.&lt;/li&gt;&#10;&lt;li&gt;Drehen Sie den Kreis um, und fasen Sie die Kanten z.B. mit einem Radiusfräser an.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="schnitz--und-modellierarbeiten"&gt;Schnitz- und Modellierarbeiten&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/adapterboard_curve.jpg" alt="Bild: 3D-Schnitzerei der Adapterplatten" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Sowohl die Adapterplatten als auch die Batteriehalterung sitzen am Durchmesser des Gehäuses, so dass sie eine gebogene Form haben müssen. Mit Handwerkzeugen fand ich die Arbeiten recht anspruchsvoll.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Bestimmen Sie die benötigten Tiefen für die parallelen Schnitte, damit beim Verbinden der Schnitte eine gewölbte Oberfläche entsteht. Für die Adapterplatten nehmen Sie den inneren Schalendurchmesser, für die Akkuhalterung den äußeren. Beachten Sie, dass die Adapterplatten für die Lautsprecher konvex sind, während die Batteriehalterung konkav sein muss.&lt;/li&gt;&#10;&lt;li&gt;Verwenden Sie eine Säge, um diese parallelen Schnitte durchzuführen. Berechnen Sie dabei die Breite des Sägeblatts mit ein.&lt;/li&gt;&#10;&lt;li&gt;Verbinden Sie die Schnitte mit einem Stechbeitel oder Meißel. Versuchen Sie, entlang der Maserung zu arbeiten.&lt;/li&gt;&#10;&lt;li&gt;Verwenden Sie einen Bandschleifer, Multitool oder eine Raspel um die Wölbung zu glätten, bis sie sehr gut passt. Die Verbindung muss schließlich am Ende Luftdicht sein.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/adapterboard_done.jpg" alt="Bild: Schleifen der Adapterplatten" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;6. Kleben Sie die Adapterplatten mit Holzleim auf der Innenseite des Kessels fest.&#10;7. Vergewissern Sie sich, dass die Löcher gut ausgerichtet sind. Nehmen Sie dann Heißkleber oder eine Mischung aus Leim und Holzmehl zur Hand, um die Adapter zu fixieren und abzudichten, während der Leim aushärtet.&#10;8. Schleifen Sie den Anschluss zwischen den beiden Teilen an oder nehmen Sie eine Viertelstabfräser und bringen eine Rundung an. Ich fand, dass dies der am wenigsten kontrollierbare Teil des ganzen Baus war, weil die Oberfräse nicht richtig auf der gekrümmten Oberfläche sitzen möchte, was die Kanten unterschiedlich breit werden lässt. Ich musste mit dem Multitool einiges an Nacharbeit leisten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/shell_chamfered.jpg" alt="Bild: Kessel mit Adapterplatten, abgeschrägt" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="weitere-holzarbeiten"&gt;Weitere Holzarbeiten&lt;/h3&gt;&#10;&lt;p&gt;Kopieren Sie die Abmessungen des Verstärkerträgers aus dem Abschnitt &lt;a href="https://blog.schallbert.de/anywhereamps-diy/#pl%c3%a4ne"&gt;Pläne&lt;/a&gt;. Verwenden Sie einen Forstnerbohrer oder den Lochfräser, um die Vertiefung für das Potentiometer zu fertigen. Eine Tiefe von &lt;code&gt;6mm&lt;/code&gt; sollte ausreichen, um das Potentiometer richtig zu montieren. Für den Schalter ist ein Stufenbohrer gut geeignet, sodass der benötigte Durchmesser genau erreicht werden kann. Bohren Sie schließlich die Löcher für die Buchsen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/batterymount.jpg" alt="Bild: Handgefertigte Batteriehalterung" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Da es eine Vielzahl von Batterietypen gibt, habe ich kein spezielles Design für den Schieber und die Batteriekontakte entworfen. Ich habe einfach typische Kabelschuhe verwendet und sie in die Adapterplatte geklebt, damit sie einen guten elektrischen Kontakt zu den Polen der Batterie herstellen können. Da meine Batterie keine Verriegelung hat, habe ich den Schieber wie einen Keil allmählich dicker werden lassen, so dass die Batterie an der richtigen Stelle klemmen würde.&lt;/p&gt;&#10;&lt;p&gt;Um das Gehäuse oben und unten geschlossen zu halten und gleichzeitig die Holzoberfläche vor Stößen zu schützen, habe ich ein paar Holzstangen zugeschnitten. Sie sind &lt;code&gt;25mm&lt;/code&gt; länger als die Gesamthöhe des Gehäuses und ihre Breite liegt bei &lt;code&gt;25mm&lt;/code&gt; und &lt;code&gt;12mm&lt;/code&gt; an den Gehäuseseiten.&lt;/p&gt;&#10;&lt;h3 id="elektronik"&gt;Elektronik&lt;/h3&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/circuit_board_soldered.jpg" alt="Bild: fertig verlötete Leiterplatte" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Schneiden Sie die Platine auf die benötigte Größe. Fertigen Sie Bohrungen für die Schrauben nach &lt;a href="https://blog.schallbert.de/anywhereamps-diy/#pl%c3%a4ne"&gt;Plan&lt;/a&gt;. Es ist ratsam, das Layout maßstabsgetreu auszudrucken, damit Sie ihn als Maske verwenden können.&lt;/li&gt;&#10;&lt;li&gt;Platzieren Sie die Leiterbahnunterbrechungen. Man kann dafür einen &lt;code&gt;3mm&lt;/code&gt; Metallbohrer verwenden und von Hand vorgehen, falls kein spezielles Werkzeug dafür vorhanden ist.&lt;/li&gt;&#10;&lt;li&gt;Fertigen und platzieren Sie die Brückendrähte. Nehmen Sie gern diese &lt;a href="https://blog.schallbert.de/anywhereamps-diy/#br%c3%bcckendr%c3%a4hte"&gt;Liste&lt;/a&gt; als Referenz.&lt;/li&gt;&#10;&lt;li&gt;Platzieren Sie die Widerstände.&lt;/li&gt;&#10;&lt;li&gt;Verwenden Sie etwas Klebeband, um die Teile auf der Platine zu fixieren. Anschließend können Sie diesen Satz Teile verlöten.&lt;/li&gt;&#10;&lt;li&gt;Platzieren Sie die Kondensatoren. Denken Sie daran, dass Sie vielleicht nicht genug Platz in Ihrem Gehäuse haben, so dass Sie sie eventuell lieber waagerecht einbauen möchten.&lt;/li&gt;&#10;&lt;li&gt;Platzieren, fixieren und schließlich löten Sie nun die restlichen Bauteile.&lt;/li&gt;&#10;&lt;li&gt;Bereiten Sie alle Verbindungen und Drähte zur Platine vor: Potentiometer, Instrumenteneingang, Endstufenausgang, &amp;ldquo;VCC/GND&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Überprüfen Sie ob Stromversorgung funktioniert. Wenn nur &lt;code&gt;VCC/GND&lt;/code&gt; angeschlossen wird, sollte die Schaltung zwischen &lt;code&gt;2mA&lt;/code&gt; und &lt;code&gt;10mA&lt;/code&gt; Strom ziehen, je nach Potentiometerstellung und Versorgungsspannung. Achten Sie darauf, dass Sie die Polarität nicht verwechseln, der NE5532 wird dadurch zerstört (glauben Sie mir, ich weiß es!). Sie sollten &lt;code&gt;VCC/2&lt;/code&gt; messen, wenn Sie den Ausgang der Op-Amp-Stufe, die die virtuelle Masse erzeugt gegen Masse abgreifen.&lt;/li&gt;&#10;&lt;li&gt;Überprüfen Sie die Verstärkungsstufe. Wenn Sie ein Oszilloskop und einen Funktionsgenerator haben, sollte das eine einfach sein. Stehen solche Werkzeuge nicht zur Verfügung, schließen Sie den Eingang an eine Audioquelle an, z. B. ein Smartphone, und den Ausgang an einen &lt;code&gt;2k2&lt;/code&gt;-Widerstand in Reihe mit einer LED und einer Diode wie &lt;code&gt;1N4148&lt;/code&gt; oder &lt;code&gt;1N4007&lt;/code&gt;. Drehen Sie Ihr Smartphone auf volle Lautstärke. Die LED sollte anfangen zu flackern, wenn der Widerstand des Potentiometers vergrößert wird. Ab welcher Position das Flackern einsetzt, hängt von der Vorwärtsspannung der LED, der Versorgungsspannung und der maximalen Ausgangsspannung der Quelle ab.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/ampcarrier.jpg" alt="Bild: Verstärkerträger und Elektronikgehäuse" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Ich schlage vor, die gesamte Elektronik und den Audioeingang - mit Ausnahme des &amp;ldquo;9V&amp;rdquo;-Versorgungsreglers - in ein Metallgehäuse einzubauen, um elektromagnetische Störungen zu reduzieren. Je nach Leistung der Endstufe werden passive oder sogar aktive Kühlkörper benötigt. In meinem Fall genügen einige Löcher im Gehäuse.&lt;/li&gt;&#10;&lt;li&gt;Befestigen Sie das Gehäuse an der Adapterplatte der Endstufe.&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie Löcher in das Gehäuse, wie auf der Adapterplatte angegeben.&lt;/li&gt;&#10;&lt;li&gt;Schalter, Potentiometer und Buchsen einsetzen. Die Anschlussdrähte anlöten.&lt;/li&gt;&#10;&lt;li&gt;Fügen Sie eine Sicherung, z.B. &lt;code&gt;4.0A&lt;/code&gt;, in den Aufbau auf dem Minuspfad zur Batterie für zusätzliche Sicherheit ein.&lt;/li&gt;&#10;&lt;li&gt;Montieren Sie Vorverstärker und Endstufe.&lt;/li&gt;&#10;&lt;li&gt;Nun kommt der finale Test. Nehmen Sie dafür z.B. einen alten Lautsprecher. Ohne angeschlossene Quelle sollten Sie bei voller Verstärkung nur ein leichtes Rauschen aus dem Lautsprecher hören. Wenn Sie Ihr Smartphone anschließen, beginnen Sie mit einer Verstärkung von &amp;ldquo;0&amp;rdquo; und einer geringen Lautstärke des Smartphones. Erhöhen Sie dann die Verstärkung des Vorverstärkers, um zu prüfen, ob alles funktioniert.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="endmontage"&gt;Endmontage&lt;/h3&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/speakers_mounted.jpg" alt="Bild: Chassis im Gehäuse montiert" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Montieren Sie die Lautsprecher. Verwenden Sie Heißkleber oder eine Schaumstoff-/Gummidichtung, um die Verbindungen luftdicht zu machen.&lt;/li&gt;&#10;&lt;li&gt;Verschließen Sie alle noch vorhandenen Löcher.&lt;/li&gt;&#10;&lt;li&gt;Löten Sie die Hochtöner parallel zum Basslautsprecher und schließen Sie das Ausgangskabel Ihres Verstärkers an.&lt;/li&gt;&#10;&lt;li&gt;Montieren Sie den Batteriehalter und schließen Sie die Kabel an die Klemmen des Verstärkers an.&lt;/li&gt;&#10;&lt;li&gt;Bringen Sie eine Dichtung an der oberen und unteren Deckelplatte an um das Gehäuse später luftdicht versiegeln zu können.&lt;/li&gt;&#10;&lt;li&gt;Drehen Sie die Baugruppe um und setzen Sie die &amp;ldquo;obere&amp;rdquo; Platte mit dem Verstärker wie auf dem Bild gezeigt ein.&lt;/li&gt;&#10;&lt;li&gt;Achten Sie darauf, dass keine der Lautsprecherklemmen das Verstärkergehäuse berührt, sonst besteht die Gefahr eines Kurzschlusses in der Endstufe.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/inside_thumb.jpg" alt="Bild: alle Komponenten an ihrem Platz" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Die Simulation zeigt, dass die Basswiedergabe besser wird, wenn man Dämmwolle hinzufügt. Ich habe ein altes Kissen &amp;ldquo;geschlachtet&amp;rdquo;, um welche zu bekommen.&lt;/li&gt;&#10;&lt;li&gt;Schließen Sie das Gehäuse und befestigen Sie es mit den Holzstäben, die Sie zuvor vorbereitet haben (oder verwenden Sie stattdessen Schrauben)&lt;/li&gt;&#10;&lt;li&gt;Fertig, Applaus! Sieht AnywhereAmps Alpha nicht wunderschön aus?&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="feinschliff"&gt;Feinschliff&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Die Hochtöner sind zu laut? Prüfen Sie, wie viel höher die Empfindlichkeitswerte Ihrer Hochtöner im Vergleich zum Bass-Chassis sind. Löten Sie ggf. 1W-Widerstände in Reihe zu den Hochtönern. Laut den &lt;a href="https://blog.schallbert.de/projects/aa_alpha/#sound-hardware"&gt;technischen Daten der Chassis&lt;/a&gt; habe ich Werte von &lt;code&gt;96dB@1W/1m&lt;/code&gt; zu &lt;code&gt;80dB@1W/1m&lt;/code&gt;. Ich musste die Hochtöner also deutlich leiser machen. Mit mit einem &lt;code&gt;1k&lt;/code&gt;-Potentiometer in Reihe dämpfte ich die Hochtöner auf das von mir gewünschte Maß herunter und wählte schließlich einen Widerstand von &lt;code&gt;470Ohm&lt;/code&gt;, um das Potentiometer zu ersetzen.&lt;/li&gt;&#10;&lt;li&gt;Der Bass flattert bei hoher Lautstärke? Höchstwahrscheinlich ist das Gehäuse nicht luftdicht. Legen Sie einen Testton auf, z.B. &lt;code&gt;35Hz&lt;/code&gt; &lt;a href="https://www.youtube.com/watch?v=yvHCAne_VLo" target="_blank" rel="noopener noreferrer" class="external-link"&gt;youtube link&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, und prüfen Sie, ob Sie hören oder fühlen, wo die Luft durchkommt. Reparieren Sie das/die Leck(s).&lt;/li&gt;&#10;&lt;li&gt;Ihr 9-Volt-Schaltwandler strahlt Rauschen in Ihre Verstärkeranlage ab? Versuchen Sie, mit einem &lt;code&gt;100nF&lt;/code&gt;-Kondensator zu glätten, und fügen Sie einen &lt;code&gt;10k&lt;/code&gt;-Parallelwiderstand hinzu, um eine Dummy-Last zu erzeugen. Wenn beides nicht hilft, nehmen Sie stattdessen einen Linearwandler wie &lt;a href="https://www.elektronik-kompendium.de/public/schaerer/ureg3pin.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;LM7809&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Da er viel mehr Wärme abgibt als sein schaltender Kollege, sollten Sie ihn an Ihrem Metallgehäuse montieren.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/projects/aa_alpha/"&gt;Zurück zur Projekt-Hauptseite&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>CNC job erstellen</title><link>https://blog.schallbert.de/cnc-job-setup/</link><pubDate>Mon, 22 Aug 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-job-setup/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-22_cnc_job_setup-thumb.jpg"&#10; class="post-cover"&#10; alt="Image of a CNC job demo"&#10; title="CNC job erstellen" /&gt;&#10;&lt;h2 id="demo-video-über-vorbereitung-und-ausführung-eines-fräsjobs"&gt;Demo video über Vorbereitung und Ausführung eines Fräsjobs&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/mFreMUKxXktZH3kpqdENGJ" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;How-to: CNC job setup &amp;amp;amp; quick demo&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die folgenden Abschnitte beschreiben die im Video gezeigten Schritte.&lt;/p&gt;&#10;&lt;h3 id="checkliste-zur-vorbereitung"&gt;Checkliste zur Vorbereitung&lt;/h3&gt;&#10;&lt;p&gt;Bei komplett heruntergefahrener Anlage:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Das Werkstück sicher im Arbeitsbereich der CNC befestigen&lt;/li&gt;&#10;&lt;li&gt;Die G-Code Datei auf den Maschinenrechner übertragen&lt;/li&gt;&#10;&lt;li&gt;Not-Aus aktiviert (verhindert unbeabsichtigtes Anlaufen)&lt;/li&gt;&#10;&lt;li&gt;Energieversorgung zur CNC und Peripherie einschalten&lt;/li&gt;&#10;&lt;li&gt;Maschinenrechner mit der CNC verbinden und CNC-Programm starten&lt;/li&gt;&#10;&lt;li&gt;Not-Aus deaktivieren&lt;/li&gt;&#10;&lt;li&gt;Zu verwendende Fräser bereitlegen und auf Beschädigungen prüfen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="checkliste-für-hochfahren-der-cnc"&gt;Checkliste für Hochfahren der CNC&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Not-Aus quittieren (Schrittmotortreiber aktivieren)&lt;/li&gt;&#10;&lt;li&gt;Maschine referenzieren&lt;/li&gt;&#10;&lt;li&gt;Werkzeugwechsel auf das erste Werkzeug im Job&lt;/li&gt;&#10;&lt;li&gt;X/Y Werkstücknullpunkte anfahren und setzen&lt;/li&gt;&#10;&lt;li&gt;Z-Nullpunkt ermitteln und setzen&lt;/li&gt;&#10;&lt;li&gt;CNC zurück in Warteposition fahren&lt;/li&gt;&#10;&lt;li&gt;Spindelwarmlauf durchführen&lt;/li&gt;&#10;&lt;li&gt;Peripheriegeräte auf Funktion prüfen (Absaugung, Kühlschmierung etc.)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="während-des-automatikbetriebs"&gt;Während des Automatikbetriebs&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Maschine überwachen&#10;&lt;ol&gt;&#10;&lt;li&gt;Hören: Klingt die Maschine &amp;ldquo;wie immer&amp;rdquo;? Gibt es Vibrationen oder hört sich irgend etwas merkwürdig an?&lt;/li&gt;&#10;&lt;li&gt;Sehen: Ist die Düse für Kühlschmierstoff korrekt ausgerichtet? Alle Parameter am PC im grünen Bereich? Verfahrwege OK?&lt;/li&gt;&#10;&lt;li&gt;Fühlen: Treten Vibrationen der Maschine auf? Führen z.B. Kühlgebläse einen merklichen Luftstrom?&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Falls erforderlich: Werkzeugwechsel durchführen bzw. Werkzeuge vermessen&lt;/li&gt;&#10;&lt;li&gt;Falls etwas nicht in Ordnung scheint: Job per &amp;ldquo;Pause&amp;rdquo;-Taste unterbrechen&lt;/li&gt;&#10;&lt;li&gt;Im Falle eines Falles den Not-Aus betätigen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="nach-abschluss-des-fräsjobs"&gt;Nach Abschluss des Fräsjobs&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Maschine zurück vor die Referenzschalter bewegen&lt;/li&gt;&#10;&lt;li&gt;Not-Aus betätigen und Maschinenrechner trennen&lt;/li&gt;&#10;&lt;li&gt;Energieversorgung der Maschine abschalten&lt;/li&gt;&#10;&lt;li&gt;Werkstück und gegebenenfalls Reststücke vom Maschinentisch nehmen&lt;/li&gt;&#10;&lt;li&gt;Reinigung von Maschine und Werkstück&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Einschaltstrom ermitteln</title><link>https://blog.schallbert.de/measure-inrush-current/</link><pubDate>Tue, 16 Aug 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/measure-inrush-current/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_inrush-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Inrush current measurement"&#10; title="Einschaltstrom ermitteln" /&gt;&#10;&lt;h2 id="vorbereiten-des-multimeters--oszilloskops"&gt;Vorbereiten des Multimeters / Oszilloskops&lt;/h2&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_multimeter.jpg" alt="Image: A Multimeter" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Für diesen Artikel habe ich mir ein Multimeter zur Messung von &amp;ldquo;Power Quality&amp;rdquo; direkt vom Hersteller &lt;a href="https://www.gossenmetrawatt.de/en/products/measuring-and-test-technology/multimeters-and-handheld-calibrators/special-applications/multimeters-for-power-and-energy-measurement/metrahit-energy/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;GMC Instruments&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; leihen können. Da dort ein Datenlogger verbaut ist, kann ich die Messungen aufnehmen und später aufbereiten.&lt;/p&gt;&#10;&lt;h3 id="metrahit-energy"&gt;Metrahit Energy&lt;/h3&gt;&#10;&lt;p&gt;Dieses Multimeter ist eher im Profi-Segment angesiedelt. Sowohl der Reichtum an Funktionen als auch sein Preis deuten für mich klar darauf hin. Zusätzlich zum Datenlogger und den üblichen Funktionen eines Multimeters kann es Leistung- und Energie messen bzw. berechnen, harmonische Signalverzerrungen erkennen und nebenbei auch PT100-Temperatursensoren auswerten.&lt;/p&gt;&#10;&lt;h3 id="software-setup"&gt;Software setup&lt;/h3&gt;&#10;&lt;p&gt;So richtet man die Auswertesoftware ein:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Software &lt;a href="https://www.gmc-instruments.de/en/products/metrawin-10" target="_blank" rel="noopener noreferrer" class="external-link"&gt;MetraWin&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; herunterladen und installieren, um die vom Multimeter erfassten Daten zu übertragen und anzuzeigen.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.gmc-instruments.de/en/products/software-and-accessories/software/product-specific-software/test-technology/driver-control/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Driver-Control&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &amp;ldquo;Treiber&amp;rdquo; herunterladen und installieren. Diese Software dient dazu, die Kommunikation zwischen USB und dem im Lieferumfang des Multimeters befindlichen Infrarot-Schnittstellenadapter bereitzustellen.&lt;/li&gt;&#10;&lt;li&gt;In der MetraWin-Software &lt;code&gt;METRAHit Starline Series&lt;/code&gt; auswählen (übrigens ist mir unklar, warum der Name der Serie weder auf dem Gerät noch auf der Website erscheint)&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_metrawin_device.jpg" alt="Image of MetraWin settings to choose device" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Nun wird der Infrarot-Adapter am PC angeschlossen. Der IR-Adapter sollte im Gerätemanager mit einer &amp;ldquo;COM&amp;rdquo;-Anschluss-ID angezeigt werden (&lt;code&gt;Windows-Taste&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;Gerät&lt;/code&gt; in die Suche eingeben und &lt;code&gt;Gerätemanager&lt;/code&gt; auswählen &amp;ndash;&amp;gt; nach unten zu &lt;code&gt;Anschlüsse (COM &amp;amp; LPT)&lt;/code&gt; scrollen, aufklappen). Diese Nummer dann in MetraWins &lt;code&gt;Gerät --&amp;gt; Kommunikation... --&amp;gt; SEL-COM&lt;/code&gt; angeben - fertig.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="einrichtung-des-multimeters-für-die-aufzeichnung-des-einschaltstroms"&gt;Einrichtung des Multimeters für die Aufzeichnung des Einschaltstroms&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_addr.jpg" alt="Bild: Multimeter-Adresseinstellung" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt; In den Einstellungen dieses Multimeters ist darauf zu achten, dass die Adresse auf einen anderen Wert als &lt;code&gt;00&lt;/code&gt; eingestellt ist (&lt;code&gt;Setup --&amp;gt; Set --&amp;gt; System --&amp;gt; Addr&lt;/code&gt;), da die Infrarot-Kommunikation das Gerät mit einem ungültigen Wert hier nicht erkennt (ich habe dies erlebt und brauchte einige Zeit, um herauszufinden, dass nicht die IR-Adapter-Erkennung fehlerhaft ist, sondern die Einstellungen des Multimeters). Hintergrund ist, dass die Software über die Adressierung bis zu 15 Geräte gleichzeitig unterstützt. Warum &lt;code&gt;00&lt;/code&gt; dabei ungültig ist, das wissen wahrscheinlich nur die Entwickler der Software.&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_rate.jpg" alt="Bild: Multimeter Abtastzeit" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt; Stellen Sie die Abtastzeit auf den niedrigsten Wert für eine gute Impulsverfolgung ein: &lt;code&gt;Measure --&amp;gt; Save --&amp;gt; rate&lt;/code&gt; und wählen &lt;code&gt;0.0005sec&lt;/code&gt; auswählen, was bedeutet, dass der Logger Messungen mit &lt;code&gt;2000Hz&lt;/code&gt; Abtastrate durchführt. Dies ist der niedrigste Wert, den man erreichen kann, und diese Werte beeinträchtigen bereits die Verfügbarkeit des Aufzeichnungskanals, z.B. für Energiemessungen, aufgrund der begrenzten Pufferfähigkeit des Geräts.&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_trms.jpg" alt="Bild: Multimeter TRMS-Einstellung" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt; Wählen Sie nun den Wert aus, den Sie messen wollen (bei mir ist es &lt;code&gt;DC&lt;/code&gt; Strom in &lt;code&gt;Ampère&lt;/code&gt;), und passen über die &lt;code&gt;MAN/AUTO&lt;/code&gt; Taste den Wertebereich im manuellen Betrieb so an, dass die zu erwartende Impulshöhe abgedeckt wird. Kleiner Hinweis: Wenn das Multimeter den Zusatz &lt;code&gt;TRMS&lt;/code&gt; oder &lt;code&gt;RMS&lt;/code&gt; anzeigt, ist dies für unsere Messung hier eher nicht erwünscht, da in diesem Falle eventuelle Spitzenwerte geglättet werden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Um den Einschaltstrom meines Verstärkers zu bestimmen, verwende ich den Messbereich &lt;code&gt;&amp;gt;10A&lt;/code&gt;. So habe ich zwar eine für den Normalbetrieb geringe Genauigkeit, kann allerdings die Stromspitze gut abbilden.&lt;/p&gt;&#10;&lt;h3 id="alternative-messgeräte"&gt;Alternative Messgeräte&lt;/h3&gt;&#10;&lt;p&gt;Oszilloskope sind Multimetern bei der Zeitauflösung weit überlegen, benötigen aber weitere Hilfsmittel um Ströme exakt erfassen zu können.&lt;/p&gt;&#10;&lt;p&gt;Dennoch sind sie hier mit einfachen Hilfsmitteln verwendbar: Einen Widerstand mit niedrigem Wert (z.B. &lt;code&gt;0.1 Ohm&lt;/code&gt;) und geringer Toleranz in Reihe mit dem Prüfling schalten und den Spannungsabfall am Widerstand per Oszilloskop bestimmen. Bei einer Auflösung von &lt;code&gt;1V/div&lt;/code&gt; läge dann die Umrechnung für den Einschaltstrom bei &lt;code&gt;10A/division&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Ein günstiges Multimeter hingegen mit &amp;ldquo;Data Max Hold&amp;rdquo; Funktion wird für diesen Anwendungsfall wahrscheinlich aber nicht funktionieren, da die Messintervalle für die zu messenden, sich schnell aufladenden Kondensatoren schlicht zu lang sind.&lt;/p&gt;&#10;&lt;h2 id="der-prüfling"&gt;Der Prüfling&lt;/h2&gt;&#10;&lt;p&gt;Die zu untersuchende Schaltung ist eine Endstufe mit &lt;code&gt;30W&lt;/code&gt; Dauerleistung &lt;a href="https://store.sure-electronics.com/product/AA-AB32996" target="_blank" rel="noopener noreferrer" class="external-link"&gt;AA-AB32996&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, produziert von &amp;ldquo;sure electronics&amp;rdquo;. Er basiert auf dem integrierten Class-D-Verstärkerbaustein &lt;a href="https://www.ti.com/product/TPA3130D2" target="_blank" rel="noopener noreferrer" class="external-link"&gt;TPA3110&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_TPA3110.jpg" alt="Image: sure electronics AA-AB32996 based on TPA3110" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Energieversorgung besitzt einen Verpolschutz (drei parallel geschaltete Schottkydioden) und bindet sechs Pufferkondensatoren mit einer Kapazität von je &lt;code&gt;220uF&lt;/code&gt; ein. Zusätzlich befinden sich nahe der Versorgungspins des IC zwei weitere Entstörkondensatoren.&lt;/p&gt;&#10;&lt;p&gt;Zum Vergleich: Das Referenzlayout des IC-Herstellers sieht der oben beschriebenen Lösung recht ähnlich. Hier werden zusätzlich &lt;code&gt;FB&lt;/code&gt; (Ferritkerne) verwendet, um die Ströme auf der Eingangsseite zu glätten. Dafür wird weniger Pufferkapatizät vorgesehen.&#10;Darüber hinaus verwendet das Referenzdesign einfache Ferritkerne anstatt hochwertigerer Filterspulen am Verstärkerausgang.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_TPA3110_recommended_layout.jpg" alt="Image: TPA3110 recommended PCB layout" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="motivation-ausfall-eines-prototypen-bei-verwendung-von-usb-c-pd"&gt;Motivation: Ausfall eines Prototypen bei Verwendung von USB-C PD&lt;/h3&gt;&#10;&lt;p&gt;Für einen meiner Prototypen hatte ich vorher ein USB-C Power Delivery Entwicklungsboard - &lt;a href="https://blog.schallbert.de/usbpd-explained/#stusb4500"&gt;erklärt in diesem Beitrag&lt;/a&gt; - verwendet. Leider musste ich feststellen, dass dieser nach nur ein paar Einschaltzyklen in Rauch aufging - wahrscheinlich verursacht durch einen hohen Einschaltstrom, für den der IC nicht ausgelegt ist.&lt;/p&gt;&#10;&lt;p&gt;Daher wollte ich genau wissen, wie hoch die Stromspitzen sind und mit welcher Komponente ich den Einschaltstrom so in den Griff bekomme, dass hierdurch verursachte Schäden an elektronischen Bauteilen ausgeschlossen werden können.&lt;/p&gt;&#10;&lt;h2 id="durchführung-der-messung"&gt;Durchführung der Messung&lt;/h2&gt;&#10;&lt;p&gt;Ich verbinde den Messpfad für Strom des Multimeters mit dem Minuspol des Verstärkers und die Masse des Multimeters mit dem Minuspol des Netzteils. Die Messspitze für Spannung verbinde ich mit dem Pluspol des Verstärkers, und letzteren wiederum schließe ich über einen Schalter am Netzteil an.&lt;/p&gt;&#10;&lt;p&gt;Um die Messung zu beginnen, wähle ich beim Multimeter &lt;code&gt;SET --&amp;gt; Store --&amp;gt; Start --&amp;gt; Store&lt;/code&gt; und lege den Schalter an der Spannungsversorgung um. Nach ein paar Sekunden beende ich die Messung mit der Taste &lt;code&gt;Stop&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Da ich zu beginn die höchstmögliche Datenrate eingestellt hatte, musste ich die Daten hernach manuell an den Computer übermitteln und konnte nicht die Liveübertragung wählen. Hierfür wählt man in der Software folgende Einstellung aus: &lt;code&gt;Read Memory --&amp;gt; Get Memory Info --&amp;gt; Move Memory Data to File --&amp;gt; save --&amp;gt; Open Memory Data File --&amp;gt; Display Data from File&lt;/code&gt;. Mir ist dabei nicht ganz klar, warum an der Stelle so viele Schritte erforderlich sind - ich hätte hierfür nur einen Knopfdruck erwartet.&lt;/p&gt;&#10;&lt;p&gt;Das Dateiformat &lt;code&gt;Measurement Data File&lt;/code&gt; hat einen großen Vorteil gegenüber dem ebenfalls möglichen &lt;code&gt;Memory Data File&lt;/code&gt;: Es enthält Metadaten wie Messdatenauswahl für die Ansicht, Labels und Details zur Darstellung wie Einheiten etc. Ohne großen Aufwand lassen sich Auswertungen erstellen und beispielsweise im PDF-Dateiformat exportieren.&lt;/p&gt;&#10;&lt;h3 id="visualisierung-des-einschaltstroms"&gt;Visualisierung des Einschaltstroms&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_TPA3110_inrushcurrent.jpg" alt="Image: TPA3110 current over time" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der ganze Spuk dauert nur &lt;code&gt;~10ms&lt;/code&gt;, also 20 samples. Nach einem hohen Strompuls von &lt;code&gt;&amp;gt;12A&lt;/code&gt; zeigt sich ein Plateau bei rund &lt;code&gt;4A&lt;/code&gt; bis sich die Kondensatoren aufgeladen haben. Danach geht der Strom zurück, bis er dem Leerlaufstrom des Verstärkers entspricht.&lt;/p&gt;&#10;&lt;p&gt;Der Spitzenwert liegt allerdings nur für einen Datenpunkt vor - daher kann ich nicht sagen, ob es sich um einen Messfehler / Ausreißer oder ein tatsächliches Ereignis handelt. Ein paar Annahmen zur Ursache:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Es wurden schaltungsweit Entkopplungskondensatoren mit sehr geringem ESR (Serienwiderstand) verwendet&lt;/li&gt;&#10;&lt;li&gt;Verlassen des Messbereichs des Multimeters&lt;/li&gt;&#10;&lt;li&gt;Limiter des Labornetzteils begrenzt den Strom auf Niveau des Plateaus (war auf &lt;code&gt;5A&lt;/code&gt; konfiguriert, passt also nicht recht zusammen)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="spannungsverlauf-an-den-versorgungspins-des-verstärkers"&gt;Spannungsverlauf an den Versorgungspins des Verstärkers&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-08-16_TPA3110_inrushvoltage.jpg" alt="Image: TPA3110 voltage over time" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Spannungsmessung zeigt einen sprunghaften Anstieg beim ersten Messwert nach Einschalten des Verstärkers, gefolgt von einem fast linearen Anstieg bis die Quellspannung nach etwa &lt;code&gt;~6ms&lt;/code&gt; erreicht wird.&lt;/p&gt;&#10;&lt;p&gt;Diese Werte passen zeitlich prima mit den Strommessungen zusammen und bestätigen sowohl Impuls- als auch Plateauwerte.&lt;/p&gt;&#10;&lt;h3 id="wiederholung--ergebnis"&gt;Wiederholung &amp;amp; Ergebnis&lt;/h3&gt;&#10;&lt;p&gt;Ich wiederholte die Messung mit unterschiedlichen Randbedingungen: Lautsprecher angeschlossen/ nicht verbunden, Verstärker in Standby/ Leerlauf usw. aber die Messergebnisse waren stets ähnlich. Somit wird die Verstärkerschaltung im Moment des Einschalten nicht durch seine Konfiguration oder Peripherie beeinflusst.&lt;/p&gt;&#10;&lt;p&gt;Die Werte sind also bedingt durch den Versorgungspfad und seine Schutzmaßnahmen, Energiespeicher und seine Entkopplungsschaltung.&lt;/p&gt;&#10;&lt;p&gt;Nachteile der Messung mittels Multimeter sind geringe Datenrate (10-100kHz wären besser gewesen), keine Möglichkeit, die Messwerte live abzulesen, und langen Datentransferzeiten zum PC, geschuldet durch die Kommunikation per Infrarot. Andererseits kann ich das Multimeter jetzt schnell mal als &amp;ldquo;offline Datenlogger&amp;rdquo; auch über längere Zeit verwenden, ohne einen Computer zur Konfiguration dabei haben zu müssen. Optimal für die Anwendung im Feld.&lt;/p&gt;&#10;&lt;p&gt;Für den Verstärker werde ich mit diesen Erkenntnissen jetzt eine Schutzschaltung entwickeln, ähnlich wie in &lt;a href="https://blog.schallbert.de/inrushlimiter-circuits/"&gt;diesem Beitrag diskutiert&lt;/a&gt;, und so in Zukunft Probleme mit USB-PD ICs vermeiden.&lt;/p&gt;&#10;</description></item><item><title>Meine CNC vibriert</title><link>https://blog.schallbert.de/cnc-vibrates/</link><pubDate>Thu, 14 Jul 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-vibrates/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-14_vibrations-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Distorted image of a circle cut with my CNC"&#10; title="Meine CNC vibriert" /&gt;&#10;&lt;h2 id="hintergrund"&gt;Hintergrund&lt;/h2&gt;&#10;&lt;p&gt;Langsam werde ich mutiger im Umgang mit meiner Maschine. In letzter Zeit bearbeite ich des Öfteren Birke Multiplex in der Vollnut, möglichst mit nur einem Fräsdurchgang um nicht so lange im kalten Keller stehen zu müssen. Das bedeutet aber auch, dass die Maschine eine Tiefenzustellung &lt;code&gt;Z+&lt;/code&gt; von &lt;code&gt;12mm&lt;/code&gt; bei &lt;code&gt;F2700mm/min&lt;/code&gt; mit einem 6mm 2-Schneider Schruppfräser bei &lt;code&gt;S=24000rpm``Z+&lt;/code&gt; bewältigen muss. Dabei fräse ich im Gleichlauf.&lt;/p&gt;&#10;&lt;p&gt;Nun fängt meine Maschine in bestimmten Situation an, hörbar zu vibrieren.&lt;/p&gt;&#10;&lt;p&gt;Nun liegt es nahe, die Schneidkräfte an der Maschine zu reduzieren, entweder durch Reduktion der Tiefenzustellung oder des Vorschubs. Die Drehzahl kann ich bei meinem Fräsmotor nämlich nicht weiter erhöhen. Das werde ich vielleicht auch tun, aber erst einmal möchte ich verstehen, was die Fräse zum Vibrieren bringt und ob ich das auch durch andere Maßnahmen abstellen kann, ohne die Maschinenzeit zu erhöhen oder Standzeiten meiner Fräser merklich zu reduzieren.&lt;/p&gt;&#10;&lt;h2 id="problembeschreibung"&gt;Problembeschreibung&lt;/h2&gt;&#10;&lt;p&gt;Die Maschine vibriert nicht nur bei Konturschnitten, sondern auch wenn ich Taschen fertige. Allerdings nur bei bestimmten Bewegungsrichtungen. Es fühlt sich so an, als würde sich die ganze Maschine leicht schütteln, und das Geräusch ähnelt einem Stuhl, der unter Last über Parkettboden zurückgeschoben wird.&lt;/p&gt;&#10;&lt;p&gt;Um das etwas zu verdeutlichen, habe ich zwei kleine Referenzvideos gemacht.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-07-14-machine_vibrations_pocketing.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Vibrationen nur an bestimmten Stellen beim Taschenfräsen (80% Räumzustellung)&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-07-14-machine_vibrations_slot.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Vibrationen beim Formatieren&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="analyse"&gt;Analyse&lt;/h2&gt;&#10;&lt;p&gt;Das Fräsbild sieht trotz Vibrationen ordentlich aus.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-14_cut_results.jpg" alt="Image: Despite vibrating, the cut still looks clean" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Ich habe versuchsweise ein paar Einstellungen angepasst: Reduzierte Tiefenzustellung &lt;code&gt;Z+&lt;/code&gt; von nur noch &lt;code&gt;6mm&lt;/code&gt; bei erhöhtem Vorschub von &lt;code&gt;3600mm/min&lt;/code&gt; mit demselben Fräser - Vibrationen abgestellt.&lt;/p&gt;&#10;&lt;p&gt;Nun war es zugegebenermaßen nicht so klug, zwei Parameter gleichzeitig zu ändern - so weiß ich nicht, ob die erhöhten Schnittkräfte oder ein Vorschub im Resonanzbereich der Maschine zu den Vibrationen lag - tsja.&lt;/p&gt;&#10;&lt;p&gt;Dennoch habe ich den Maschinenhersteller auf das Problem angesprochen &lt;a href="https://hobbyline.info/forum/index.php?thread/686-meine-maschine-vibriert-erledigt/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier verlinkt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, und seiner Meinung nach ist der wahrscheinlichste Grund die etwas schwach ausgelegte Verbindung zwischen Fräsmotor und Z-Achse.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-14_routermount.jpg" alt="Image: Router mounting to the Z-axis" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Der Fräsmotor hat weniger hochwertige Lager als professionelle Hochfrequenzspindeln, also sind sie potentiell eher von Spiel betroffen. Außerdem klemmt die &lt;code&gt;42mm&lt;/code&gt; Eurohals-Halterung den Fräsmotor mit nur einer Verschraubung, die zudem bei recht niedrigen &lt;code&gt;7Nm&lt;/code&gt; drehmomentbegrenzt ist, da der vordere Lagersitz des Fräsmotors sonst zu stark gequetscht wird.&lt;/p&gt;&#10;&lt;p&gt;Im Gegensatz dazu werden Motorspindeln meist direkt und vollflächig mit der Z-Achse verschraubt, sodass Kraftschluss und Kontaktfläche viel größer ausfallen.&lt;/p&gt;&#10;&lt;h2 id="ergebnis"&gt;Ergebnis&lt;/h2&gt;&#10;&lt;p&gt;Ich habe jetzt ein paar Optionen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Weiter mit großer Tiefenzustellung arbeiten und die Vibrationen und damit einhergehenden erhöhten Maschinenverschleiß tolerieren&lt;/li&gt;&#10;&lt;li&gt;Geringere Zustellungen fahren, dafür aber Maschinenzeit opfern&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/projects/spindle-upgrade/"&gt;Die Spindel aufrüsten&lt;/a&gt;, welches meine Maschine deutlich teurer macht&lt;/li&gt;&#10;&lt;li&gt;Mein &lt;a href="https://blog.schallbert.de/why-vacuum-table/"&gt;Maschinenbett aufrüsten&lt;/a&gt; und so die Steifigkeit des Grundrahmens erhöhen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="update-september-2023"&gt;Update September 2023&lt;/h2&gt;&#10;&lt;p&gt;So, ich habe jetzt sowohl die Spindel als auch das Maschinenbett aufgerüstet. Die Vibrationen sind leider immer noch da. Das finde ich ziemlich ärgerlich.&lt;/p&gt;&#10;&lt;p&gt;Wenn ich mir die Umstände nochmal genau ansehe, dann treten die Vibrationen immer dann auf, wenn sich das Portal vorwiegend auf der breiten Achse (bei mir X) bewegt, gleichzeitig aber ebenfalls leicht auf der Längsachse (Y) unterwegs ist. Fahre ich nur in &lt;code&gt;Y&lt;/code&gt;, passiert nichts. Fahre ich nur in &lt;code&gt;X&lt;/code&gt;, treten die Vibrationen schwächer auf.&lt;/p&gt;&#10;&lt;p&gt;Ich kan inzwischen ebenfalls sagen, dass die Vibrationen in schwächerer Form auch in Acrylglas bei einer Tiefenzustellung von &lt;code&gt;Z+5mm&lt;/code&gt;, Vorschub &lt;code&gt;F3000mm/min&lt;/code&gt; und einem 4mm-Einschneider auftreten.&lt;/p&gt;&#10;&lt;p&gt;Ich werde einmal ausprobieren, ob ich das Problem auch noch bei &lt;code&gt;F3600mm/min&lt;/code&gt; habe.&lt;/p&gt;&#10;&lt;p&gt;Da ich inzwischen Maschinenbett und Spindelbefestigung als Ursache sicher ausschließen kann, liegt die Ursache der Vibrationen meiner Einschätzung nach am Zusammenspiel der Portalachsen.&lt;/p&gt;&#10;&lt;p&gt;Nun habe ich eine recht breite Maschine ohne zusätzlich verstärkte Portalwangen, die zudem nur mit einer statt zwei Linearführungen auf der Z-Achse ausgestattet ist. Ich spreche die nächsten Wochen nochmal mit dem Hersteller, um diesen Verdacht zu erhärten bzw. aus dem Weg zu räumen.&lt;/p&gt;&#10;</description></item><item><title>Vom Bild zum Pfad</title><link>https://blog.schallbert.de/path-from-image/</link><pubDate>Mon, 04 Jul 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/path-from-image/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-04_cam-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: CAM drawing of Image-to-Path engraving"&#10; title="Vom Bild zum Pfad" /&gt;&#10;&lt;p&gt;Dies ist ein kurzer Beitrag über die Verwendung der beiden Open-Source-Programme &lt;a href="https://www.getpaint.net/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Paint.NET&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und &lt;a href="https://inkscape.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Inkscape&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, um aus Pixelgrafiken Pfade zu erzeugen. Diese können dann z.B. für Gravuren auf einer CNC-Maschine, einem Laserschneider oder einem Plotter verwendet werden. Und das sogar auf Grundlage eines digitalen Bildes von geringer Qualität in einem verlustbehafteten Datenkompressionsformat wie JPEG.&lt;/p&gt;&#10;&lt;p&gt;Der Beitrag wurde inspiriert von &lt;a href="https://graphicdesign.stackexchange.com/questions/113560/convert-parallel-paths-into-one-in-inkscape" target="_blank" rel="noopener noreferrer" class="external-link"&gt;diesem ausgezeichneten Stackexchange-Thread&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="die-bildquelle"&gt;Die Bildquelle&lt;/h2&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-04_original.jpg" alt="Image: Lagerfeuer - wunderschön gezeichnet von Schallbert" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Alles beginnt mit zu konvertierenden dem Bild (bzw. der Pixelgrafik).&lt;/p&gt;&#10;&lt;h3 id="geistiges-eigentum"&gt;Geistiges Eigentum&lt;/h3&gt;&#10;&lt;p&gt;Zu beachten ist, dass Bilder (auch selbstgezeichnete) in der Regel urheberrechtlich geschützt sind, so dass zunächst die (schriftliche) Zustimmung des Eigentümers des geistigen Eigentums eingeholt werden muss.&lt;/p&gt;&#10;&lt;p&gt;Sind Sie der Inhaber der geistigen Eigentumsrechte, ist das viel einfacher. Aus diesem Grund habe ich für diese kurze Demo ein Bild gewählt, das ich selbst erstellt habe.&lt;/p&gt;&#10;&lt;h3 id="bildqualität"&gt;Bildqualität&lt;/h3&gt;&#10;&lt;p&gt;Richtig gut zeichnen kann ich allerdings nicht, wie das Bild klar andeutet. Einige Linien sind übereinander gemalt und der Kontrast ist gering. Perfekte Bedingungen, um zu zeigen, was die Software leisten kann!&lt;/p&gt;&#10;&lt;p&gt;Hinweis: Es gilt wie immer das Credo &amp;ldquo;Garbage in, garbage out&amp;rdquo;. Es ist also sicher zu stellen, dass vor Beginn der Arbeiten die bestmögliche Bildqualität erreicht wird. Auf diese Weise ist die Bereinigung des Bildes viel einfacher und die Gravurpfade fallen simpler und genauer aus.&lt;/p&gt;&#10;&lt;h2 id="paintnet"&gt;Paint.NET&lt;/h2&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-04_luminance.jpg" alt="Image: Luminance change for maximum contrast" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="farbkorrektur-und-freistellung"&gt;Farbkorrektur und Freistellung&lt;/h3&gt;&#10;&lt;p&gt;Schneiden Sie Ihr Bild zunächst auf den Inhalt zu, den Sie später verwenden möchten. Nehmen Sie dann das Werkzeug &lt;code&gt;Helligkeitskurve&lt;/code&gt; (Anpassen -&amp;gt; Kurven) und versuchen Sie, die hellen und dunklen Töne so weit wie möglich zu trennen.&lt;/p&gt;&#10;&lt;p&gt;Zweitens, wenn Ihr Bild so schlecht ist wie meines, verwenden Sie das Werkzeug &lt;code&gt;Zauberstab&lt;/code&gt;. Spielen Sie mit den Toleranzen für die Farberkennung - um so den Hintergrund so gut wie möglich zu entfernen und das Motiv freizustellen.&lt;/p&gt;&#10;&lt;p&gt;Drittens: Verwenden Sie das &lt;code&gt;Kontur&lt;/code&gt;-Werkzeug (Effekte -&amp;gt; Stilisieren -&amp;gt; Kontur), um eine schöne dicke Linie als Umriss zu erhalten. Eine Umwandlung des Bildes in Graustufen und eine weitere Erhöhung des Kontrasts können helfen, diese Linie schwarz zu machen. Falls Sie Linien verlieren, die nicht dunkel genug sind - keine Sorge, das lässt sich später in Inkscape leicht beheben.&lt;/p&gt;&#10;&lt;h3 id="weitere-bereinigung"&gt;Weitere Bereinigung&lt;/h3&gt;&#10;&lt;p&gt;Entfernen Sie so viele &amp;ldquo;Pixelwolken&amp;rdquo; und andere Artefakte aus dem Bild wie möglich mit den Werkzeugen &lt;code&gt;Zauberstab&lt;/code&gt; oder &lt;code&gt;Radiergummi&lt;/code&gt;, das ist jetzt einfacher als später.&lt;/p&gt;&#10;&lt;p&gt;Speichern Sie Ihr Bild als portable Netzwerkgrafik &lt;code&gt;.PNG&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-04_outline.jpg" alt="Image: Outline tool for continuous and thick lines" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="inkscape"&gt;Inkscape&lt;/h2&gt;&#10;&lt;p&gt;Öffnen Sie das Bild in Inkscape. Wählen Sie die Standard-Import-Einstellungen &lt;code&gt;DPI der Datei verwenden&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="nachzeichnen"&gt;Nachzeichnen&lt;/h3&gt;&#10;&lt;p&gt;Wählen Sie &lt;code&gt;Pfad -&amp;gt; Bitmap nachzeichnen&lt;/code&gt;. In dem neu geöffneten Tab wählen Sie &lt;code&gt;Trace Bitmap&lt;/code&gt;, &lt;code&gt;Single Scan&lt;/code&gt;, &lt;code&gt;Brightness Cutoff&lt;/code&gt; und spielen mit den Optionen herum, bis sich die Kontur Ihres Motivs vom Hintergrund abhebt. Stellen Sie sicher, dass Sie das Bild per Mausklick auswählen, bevor Sie auf &amp;ldquo;Aktualisieren&amp;rdquo; oder &amp;ldquo;Anwenden&amp;rdquo; klicken. Wenn Sie mit der Vorschau nicht zufrieden sind, passen Sie die Werte an. Falls Sie bei der Vielzahl der Einstellungen nicht weiterkommen, schauen Sie sich einfach &lt;a href="https://inkscape.org/doc/tutorials/tracing/tutorial-tracing.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieses Tutorial von Inkscape - Link zur Inkscape Website&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; an.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-04_paths.jpg" alt="IMAGE Image to Path tool at its best" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;OK, jetzt haben wir eine Menge Pfade. Per Klick auf &lt;code&gt;Pfad -&amp;gt; Vereinfachen&lt;/code&gt; wird die Anzahl der Stützpunkte reduziert. Entfernen Sie dann manuell Artefakte, die sich bei Verwendung der Luminanzkurven eingeschlichen haben, mit dem &lt;code&gt;Knotenwerkzeug&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="füllen-und-streichen"&gt;Füllen und Streichen&lt;/h3&gt;&#10;&lt;p&gt;Damit gibt es nun genau zwei die Zeichnung außen und innen umreißende Pfade sowie eine Füllung zwischen diesen Pfaden, die die Linienstärke der ursprünglichen Zeichnung darstellt. Da wir nur die äußere Kontur behalten wollen, müssen wir die Füllung und die innere Linie entfernen.&lt;/p&gt;&#10;&lt;p&gt;Wählen Sie das Werkzeug &lt;code&gt;Linie und Füllung&lt;/code&gt; im unteren Teil der rechten Symbolleiste. Wählen Sie die Registerkarte &lt;code&gt;Linienfarbe&lt;/code&gt; und setzen Sie den Strich auf eine &lt;code&gt;deckende Farbe&lt;/code&gt;. Wählen Sie unter &lt;code&gt;Linienstil&lt;/code&gt; eine Linienbreite, die Ihnen zusagt. Gehen Sie dann zur Registerkarte &lt;code&gt;Ausfüllen&lt;/code&gt; und stellen Sie &lt;code&gt;Ohne&lt;/code&gt; ein. Jetzt haben Sie nur noch zwei Umrisslinien, von denen Sie eine löschen können, wenn Sie möchten.&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-04_paths_cleaned.jpg" alt="Image: Paths cleaned up and simplified" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="und-noch-mehr-bereinigung"&gt;Und noch mehr Bereinigung&lt;/h3&gt;&#10;&lt;p&gt;Das sieht doch schon ziemlich gut aus, oder? Wenn es Ihnen nichts ausmacht, dass die Umrisse mehr als ein einziger Strich sind, sind Sie schon fertig. Sie können nun das Bild weiter verbessern, indem Sie Pfade herausziehen oder Kurven umwandeln.&lt;/p&gt;&#10;&lt;p&gt;Verwenden Sie das Werkzeug &lt;code&gt;Pfad an ausgewählten Knoten unterbrechen&lt;/code&gt;, bevor Sie parallele Linien entfernen, damit Sie nicht versehentlich die Linien zerstören, die Sie behalten möchten. Wenn Sie mit dem Ergebnis zufrieden und alle Unsauberkeiten ausgebügelt sind, speichern Sie Ihr Werk als skalierbare Vektorgrafikdatei &lt;code&gt;SVG&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Das war&amp;rsquo;s! Jetzt kann Ihr CAM-System das Bild importieren (denken Sie an die DPI-Einstellung, damit Ihre Skalierung gültig bleibt).&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-07-04_clean.jpg" alt="Image: Fireplace SVG path work complete" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>CNC: Überlastung Fräsmotor</title><link>https://blog.schallbert.de/cnc-router-overload/</link><pubDate>Sat, 18 Jun 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-router-overload/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-18_overload-thumb.jpg"&#10; class="post-cover"&#10; alt="Image of an endmill path that doesn&amp;#39;t look good at all"&#10; title="CNC: Überlastung Fräsmotor" /&gt;&#10;&lt;h2 id="was-ich-eigentlich-wollte"&gt;Was ich eigentlich wollte&lt;/h2&gt;&#10;&lt;p&gt;Ich hatte eine Anfrage zum Schneiden und Gravieren von Hochdrucklaminaten. Dabei handelt es sich um eine Lasagne aus gepresstem Papier und &lt;a href="https://en.wikipedia.org/wiki/Phenol_formaldehyde_resin" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Phenolharz&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, überzogen mit einer dünnen Schicht (farbigen) &lt;a href="https://en.wikipedia.org/wiki/Melamine_resin" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Melaminharzes&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. HPL ist so widerstandsfähig, dass es für Fassadenbeplankungen verwendet wird. Es ist spülmaschinenfest und einige Varianten können auf bis zu 200°C erhitzt werden, ohne Schaden zu nehmen. HPL findet man als Platten auf Spielplätzen, Gartentischen, als Paneele in der Straßenbahn und an vielen anderen Stellen im Alltag.&#10;Also dachte ich mir: &amp;ldquo;Cooles Zeug, Probieren geht über Studieren!&amp;rdquo;&lt;/p&gt;&#10;&lt;h2 id="schnittdaten-in-hochdrucklaminaten-hpl-resopal"&gt;Schnittdaten in Hochdrucklaminaten (HPL, &amp;ldquo;Resopal&amp;rdquo;)&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-18_overload_chips.jpg" alt="Image: HPL und Späne nach dem Schneiden" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich habe die von einigen Websites empfohlenen Schnittdaten mit Werten für &amp;ldquo;Hartholz&amp;rdquo; verwendet.&#10;2-Schneider.Holz-Hartmetallschaftfräser, Schnittgeschwindigkeit &lt;code&gt;450m/min&lt;/code&gt;, &lt;code&gt;d6mm&lt;/code&gt;, &lt;code&gt;Z+6,5mm&lt;/code&gt;, &lt;code&gt;S24000rpm&lt;/code&gt;, &lt;code&gt;F3500mm/min&lt;/code&gt;, Vollnut, Gleichlauffräsen.&lt;/p&gt;&#10;&lt;h2 id="schnittergebnisse"&gt;Schnittergebnisse&lt;/h2&gt;&#10;&lt;p&gt;Und ich war erschrocken, wie der Schnitt aussah, als ich fertig war.&#10;Hier sind die Symptome:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Während des Schneidens fiel die Spindeldrehzahl wiederholt ab, stieg aber schnell wieder auf die gewünschte Drehzahl an.&lt;/li&gt;&#10;&lt;li&gt;hohe Spanlast am Fräser, große Späne, sah alles gut aus&lt;/li&gt;&#10;&lt;li&gt;der Fräser folgte keiner geraden Linie: schien dem Material irgendwie ausweichen zu wollen.&lt;/li&gt;&#10;&lt;li&gt;bei der Herstellung von Anbindungen, um das Werkstück an Ort und Stelle zu halten, bewegt sich der Fräser ein wenig quer zur Fräsbahn, obwohl er das nicht sollte&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="suche-nach-dem-übeltäter"&gt;Suche nach dem Übeltäter&lt;/h2&gt;&#10;&lt;p&gt;Zuerst habe ich die Spindel überprüft. Keine Überhitzung, immer noch fest an der Portalfräse eingespannt, kein Lagerspiel, Schaftfräser sicher befestigt. Dann dachte ich: &amp;ldquo;Vielleicht stimmt etwas mit dem Portal nicht? Kann ich es merklich verwinden?&amp;rdquo; Nein, das konnte ich nicht. Dann sah ich mir den Schaftfräser an.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-18_overload_endmill.jpg" alt="Image: dull carbide endmill after cutting HPL" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Es sah überhaupt nicht gut aus. Die Schnittkanten waren an den Stellen, an denen sie das Material berührt hatten, praktisch weg. Seltsam. Es war nagelneu als ich den Auftrag begann, und der lief nicht einmal 20 Minuten lang.&lt;/p&gt;&#10;&lt;h2 id="analyseergebnisse"&gt;Analyseergebnisse&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Ich habe viel zu aggressive Schnittdaten verwendet.&lt;/li&gt;&#10;&lt;li&gt;Dadurch wurde der Motor der Oberfräse weit über seine Möglichkeiten hinaus betrieben.&lt;/li&gt;&#10;&lt;li&gt;Dies führte zu einem plötzlichen Abfall der Motordrehzahl.&lt;/li&gt;&#10;&lt;li&gt;Dies wiederum führte zu einer erhöhten Spanbelastung meines Fräsers&lt;/li&gt;&#10;&lt;li&gt;Da die Lager und die Befestigung des Fräsmotors am Portal &amp;ldquo;weich&amp;rdquo; sind, versuchte der Fräser, seiner Fräsbahn auszuweichen&lt;/li&gt;&#10;&lt;li&gt;Dies hat mich wahrscheinlich vor dem Bruch des Fräsers bewahrt.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="verbesserungen"&gt;Verbesserungen&lt;/h2&gt;&#10;&lt;h3 id="neubewertung-der-schnittparameter-für-hochdrucklaminat"&gt;Neubewertung der Schnittparameter für Hochdrucklaminat&lt;/h3&gt;&#10;&lt;p&gt;Ich habe nochmal online recherchiert. HPL ist unglaublich hart und steif. Hier mal ein paar Werte zum Vergleich (Brinellhärte):&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Buche: &lt;code&gt;~3.7N/mm²&lt;/code&gt; (&lt;a href="https://www.olivenholzprodukte.de/Holzhaerte-Tabelle-nach-Brinell" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Source&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;)&lt;/li&gt;&#10;&lt;li&gt;HPL: &lt;code&gt;~185N/mm²&lt;/code&gt; (&lt;a href="https://www.pro-hpl.org/assets/uploads/prohpl/files/200330_Technische_Kennziffern_und_physikalische_Eigenschaften_von_HPL.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Source&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;)&lt;/li&gt;&#10;&lt;li&gt;Baustahl ST70: &lt;code&gt;215N/mm²&lt;/code&gt; (&lt;a href="https://www.ingenieurkurse.de/baustofftechnik-1/stoffeigenschaften-im-bauwesen/mechanische-eigenschaften-von-baustoffen/oberflaechenhaerte/brinellhaerte.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Source&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Des Weiteren fand ich einige Empfehlungen zur spanabhebenden Verarbeitung von HPL, z.B.: &amp;ldquo;Vollhartmetallfräser sollten eine Schnittgeschwindigkeit von bis zu &lt;a href="https://www.dekorplatten.de/sites/default/files/dateien/HPL-Verarbeitungshinweise.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;150m/min&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;rdquo; und &amp;ldquo;Diamantbeschichtungen aufweisen oder als Einschneider zur Bearbeitung von Aluminium ausgeführt sein&amp;rdquo;.&lt;/p&gt;&#10;&lt;h3 id="zwischenlösung"&gt;Zwischenlösung&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-18_overload_corrected.jpg" alt="Image: dull carbide endmill after cutting HPL" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Um die Dinge zu korrigieren, habe ich alle Ausschnitte mit den Schlicht-Einstellungen in CAM erneut durchgeführt. Mit einem neuen Schaftfräser und weniger aggressiven Vorschubgeschwindigkeiten von &lt;code&gt;F2500mm/min&lt;/code&gt; und einer aktualisierten Tiefe pro Durchgang von &lt;code&gt;Z+1,5mm&lt;/code&gt;.&#10;Das Ergebnis sieht jetzt viel besser aus.&lt;/p&gt;&#10;&lt;h3 id="optimierung-der-schnittdaten"&gt;Optimierung der Schnittdaten&lt;/h3&gt;&#10;&lt;p&gt;Dann berechnen wir schnell mal neue Schnittdaten aufgrund der oben gewonnenen Erkenntnisse. Ich wähle einen &lt;code&gt;4mm&lt;/code&gt; Einschneider (&lt;code&gt;z=1&lt;/code&gt;) mit Beschichtung zur besseren Wärmeabfuhr für Aluminium und lege die oben genannte Schnittgeschwindigkeit von &lt;code&gt;vc=150m/min&lt;/code&gt; für HPL zugrunde. Weiterhin wähle ich einen Zahnvorschub von &lt;code&gt;fTooth = 0,025mm&lt;/code&gt;, ähnlich wie beim Fräsen von Stahl.&lt;/p&gt;&#10;&lt;p&gt;Zieldrehzahl: &lt;/p&gt;&#10;$$n=\frac{v_c}{\pi d}=\frac{150000\frac{mm}{min}}{\pi \cdot 4mm}=12000\frac{1}{min}$$&lt;p&gt;Zielvorschub: &lt;/p&gt;&#10;$$F=n \cdot f_{Tooth} \cdot z = 12000\frac{1}{min} \cdot 0,025mm \cdot min \cdot 1= 300\frac{mm}{min}$$&lt;p&gt;Ich habe einen &lt;code&gt;4mm&lt;/code&gt;-Schaftfräser gewählt, weil das Drehmoment meines Fräsmotors bei sinkender Drehzahl überproportional abnimmt. Bei größerem Fräserdurchmesser würde dieser Effekt wahrscheinlich zu einer unzureichenden Schnittleistung führen. Spindeln mit einer Asynchronmaschine sind hier durch ihr konstant hohes Drehmoment im unteren Drehzahlbereich (bei Vektorsteuerung kann es sogar deutlich über dem Nennmoment des Motors liegen) ganz klar im Vorteil.&lt;/p&gt;&#10;&lt;p&gt;Der berechnete Vorschub kommt mir so langsam vor. Ich hoffe, dass sich das Material dadurch nicht so stark erhitzt, dass es delaminiert (laut Wikipedia schmilzt es nicht&amp;hellip;)&lt;/p&gt;&#10;&lt;p&gt;Zur Information: &lt;a href="https://hobbyline.info/forum/index.php?thread/678-hpl-platten-fr%C3%A4sen/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hier ist der zugehörige Thread&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; in dem Forum, das ich regelmäßig besuche.&lt;/p&gt;&#10;&lt;h3 id="weitere-tests"&gt;Weitere Tests&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-18_coated_new.jpg" alt="Image: new coated carbide endmill for aluminium" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich habe einige Teile mit oben angesprochenem Fräser und Einstellungen gefertigt. Um die Fertigungszeiten etwas im Zaum zu halten, habe ich die Werte leicht angepasst: &lt;code&gt;ftooth = 0.04mm&lt;/code&gt; und &lt;code&gt;vc = 130m/min&lt;/code&gt;. Für den &lt;code&gt;4mm&lt;/code&gt; Fräser ergibt sich&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;S = 10300U/min&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;F = 410mm/min&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Z+ = 3mm&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;vc = 1%&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Dieser Schaftfräser lebte etwa 1 Stunde lang. Nicht lange genug. Das Schneiden mit einem solchen Fräser könnte nie wirtschaftlich werden. Das HPL, das ich verwende, hat zwei Materialeigenschaften, die Hartmetallfräser schnell zerstören: Erstens ist es abrasiv, sodass die dünne Schneide des Fräsers abgetragen und zerstört wird. Meines ist ganz besonders abrasiv, da es einen unheimlich hohen Faseranteil besitzt. Zweitens leiten die Späne die entstehende Wärme nicht gut von der Schneide ab, so dass ihre Schärfe noch schneller nachlässt. Hitze scheint das Kryptonit für Hartmetallfräser zu sein.&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-18_coated_worn.jpg" alt="Image: coated carbide endmill for aluminium after 1h" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="endgültige-lösung"&gt;Endgültige Lösung&lt;/h3&gt;&#10;&lt;p&gt;Eine wirtschaftlichere Lösung mit 40-facher (Herstellerangabe) Lebensdauer wäre ein Hartmetallfräser mit Diamantschneiden (z. B. wie &lt;a href="https://www.artifex24.de/JSO-5mm-Diamant-PKD-Nutschaftfraeser-Diatec-DUO-5x10/50mm-Z2-S6-rechts" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieser hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;). Der Nachteil ist, dass sie fast zehnmal so teuer sind. Wenn ich dieses Material in Zukunft öfter schneiden muss, kaufe ich vielleicht einen und teile meine Erfahrungen - in der Zwischenzeit lasse ich die Fräser lieber schärfen, um Geld zu sparen.&lt;/p&gt;&#10;</description></item><item><title>CNC 'Opferplatte'</title><link>https://blog.schallbert.de/cnc-spoilboard-diy/</link><pubDate>Sun, 12 Jun 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-spoilboard-diy/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/spoilboard-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Spoilboard for my BasicLine0607 CNC. It features a frame so pasts can be positioned repeatedly, and it has a hole grid to allow clamping devices to be inserted."&#10; title="CNC &amp;#39;Opferplatte&amp;#39;" /&gt;&#10;&lt;h2 id="materialliste"&gt;Materialliste&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Artikel&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Anzahl&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;~ Kosten [€]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MDF, Stärke 19mm, 840x730&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MDF, Stärke 5mm, 840x730&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Einschlagmutter&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;17&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;9&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;M6 Maschinenschraube, 20mm + M6 Hammermutter + Unterlegscheibe&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;M6 Maschinenschraube, 35mm + Unterlegscheibe&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;17&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Holzleim&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;-&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,10&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="werkzeuge"&gt;Werkzeuge&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schraubendreher / Schraubenschlüssel / Steckschlüssel&lt;/li&gt;&#10;&lt;li&gt;Ihre CNC&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="der-aufbau"&gt;Der Aufbau&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Laden Sie die Zeichnungen &lt;a href="https://blog.schallbert.de/assets/docs/basicline0607_spoilboard_drawings_by_schallbert.zip"&gt;als zip-Datei&lt;/a&gt; herunter&lt;/li&gt;&#10;&lt;li&gt;Passen Sie bei Bedarf die Abmessungen an &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#10;&lt;li&gt;Importieren Sie die Zeichnung in Ihr CAM-Werkzeug. Passen Sie die Materialstärke an, falls Sie andere Platten verwenden als ich.&lt;/li&gt;&#10;&lt;li&gt;Bereiten Sie Senkbohrungen für Ihre Befestigungselemente vor, die am Maschinenrahmen befestigt werden. Die Tiefe dieser Bohrungen bestimmt die Reserve für das Planfräsen und damit das Aufbereiten verschlissener Oberflächen.&lt;/li&gt;&#10;&lt;li&gt;Da sowohl der Anschlag als auch Opferplatt größer sind als der von der CNC erreichbare Bereich, müssen Sie mehrere Jobs programmieren. Wählen Sie Ihre XY-Nullpunkte mit Bedacht und ziehen Sie gern die Projektseite zu Rate, auf der ich mein Vorgehen detailliert beschrieben habe (unten verlinkt).&lt;/li&gt;&#10;&lt;li&gt;Beachten Sie auch, dass die T-Nuten &amp;ldquo;von unten&amp;rdquo; gefräst werden müssen, falls Sie keinen Schlüssellochfräser haben.&lt;/li&gt;&#10;&lt;li&gt;Nachdem Ihre CNC die Aufträge abgearbeitet hat, kleben Sie die obere und untere MDF-Platte zusammen, damit die Hammermuttern nicht durch die Schlitze fallen können.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/projects/cnc_spoilboard/"&gt;Zurück zur Projekt-Hauptseite&lt;/a&gt;&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Hinweis: Die mittlere Versteifung des Maschinenrahmens hat keine festgelegte Position, sie kann bei Ihnen also an einer anderen Position stehen.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Mein Fräser 'kreischt'</title><link>https://blog.schallbert.de/my-endmill-screams/</link><pubDate>Mon, 06 Jun 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/my-endmill-screams/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-06_endmill-thumb.jpg"&#10; class="post-cover"&#10; alt="Anfängerproblem mit CNC: Vorschub/Dehzahl"&#10; title="Mein Fräser &amp;#39;kreischt&amp;#39;" /&gt;&#10;&lt;h3 id="einordnung"&gt;Einordnung&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-03_upcut_bit.jpg" alt="Image: a 2-flute upcut bit for wood" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich arbeite gerade an meinem allerersten CNC-Fräsprojekt: Eine &amp;ldquo;Opferplatte&amp;rdquo; mit Schraubmuffen und T-Nuten für die Niederhalter, welche einen Anschlag für Werkstücke und eine Aufnahme für den Werkzeuglängensensor besitzt.&lt;/p&gt;&#10;&lt;p&gt;Auf der Oberfräse habe ich bis jetzt nur gerade genutete HSS-Fräser mit großen Durchmessern verwendet. Für die CNC kaufte ich nun 6-mm-Vollhartmetallfräser &amp;ldquo;Fischschwanz&amp;rdquo; mit zwei Schneiden und einer Arbeitslänge von &lt;code&gt;21mm&lt;/code&gt;. Und der klingt einfach furchtbar, ganz anders als ich es von der Oberfräse gewöhnt bin.&lt;/p&gt;&#10;&lt;h3 id="die-verwirrung-des-anfängers"&gt;Die Verwirrung des Anfängers&lt;/h3&gt;&#10;&lt;p&gt;Mit diesem Fräser habe ich die vom Hersteller empfohlenen Vorschübe und Geschwindigkeiten für Hartholz auf meinem Werkstück aus mitteldichten Faserplatten eingehalten (Tiefe pro Durchgang: &lt;code&gt;6mm&lt;/code&gt;, Vorschub: &lt;code&gt;3500mm/min&lt;/code&gt;, Drehzahl: &lt;code&gt;24000U/min&lt;/code&gt;). Aber offensichtlich mache ich etwas falsch, denn beim Schneiden gibt es ein schrecklich lautes, unangenehmes schrilles und zischendes Geräusch.&lt;/p&gt;&#10;&lt;p&gt;Ich habe die Vorschubgeschwindigkeit allmählich erhöht, weil ich den Eindruck hatte, dass die Fräser wegen zu geringen Vorschubs so klingen. Ich landete bei &lt;code&gt;9100mm/min&lt;/code&gt; (!), wo es etwas besser war. Trotzdem ging dieses unangenehme Geräusch nicht weg.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-06_endmill_screams.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Fräser &amp;#39;kreischt&amp;#39; beim Zerspanen von MDF&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="analyse"&gt;Analyse&lt;/h3&gt;&#10;&lt;p&gt;Nachdem die 15-minütige Arbeit beendet war, habe ich den Schaftfräser angefasst - die Spannuten sind so gut wie neu und der Fräser fühlt sich kaum warm an. Hmm. Auch der Fräsmotor klang nicht gestresst. Meine Schrittmotoren ließen die Maschine buchstäblich durch das MDF pflügen (reibungslos und ohne Probleme), ich musste sogar die Absaugung aufdrehen, um nicht im Staub zu ertrinken. Auch das Ergebnis sieht für mich gut aus; die Toleranzen sind eng und der Schnitt ist so sauber, wie ich es erwarte - viel besser als ich es von Hand hätte machen können.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-03_mdf_result.jpg" alt="Image: milling in MDF - spoilboard t-nut slots" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="offene-fragen"&gt;Offene Fragen&lt;/h3&gt;&#10;&lt;p&gt;Was mache ich also falsch? Ist dieses Geräusch &amp;ldquo;normal&amp;rdquo; für einen 2-Schneider mit so kleinem Durchmesser? Braucht MDF ganz andere Parameter als Hartholz, um leise zu schneiden, z.B. viel weniger Spindeldrehzahl? Muss ich in meinem CAM-Tool eine andere Frässtrategie wählen?&lt;/p&gt;&#10;&lt;p&gt;Diese Fragen stelle ich schnell mal im Forum.&lt;/p&gt;&#10;&lt;h3 id="fragen-beantwortet"&gt;Fragen beantwortet&lt;/h3&gt;&#10;&lt;p&gt;OK, ich habe eine Antwort vom &lt;a href="https://hobbyline.info/forum/index.php?thread/620-mein-fr%C3%A4ser-kreischt/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hersteller des Bits&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Dieses Geräusch ist normal für Schaftfräser, die auf das Schneiden von Holz spezialisiert sind. Nun denn. Wahrscheinlich sollte ich ein Gehäuse für meine Maschine bauen und ein paar sehr gute Ohrstöpsel kaufen. 😬 Nach ein paar Gesprächen mit einem weiteren Werkzeughersteller bestätigt sich diese Aussage nochmal:&lt;/p&gt;&#10;&lt;p&gt;&amp;ldquo;Zweischneider sind immer lauter als gleichgroße Ein- oder Dreischneider. Ihre im 180°-Winkel gegenüberliegenden Flanken sorgen durch die im Wechsel im Eingriff befindlichen Schneiden für zusätzliche Schwingungen des Fräsers. Dafür bieten sie ein besseres Verschleißverhalten als Einschneider und ermöglichen eine bessere Spanabfuhr als Dreischneider.&amp;rdquo;&lt;/p&gt;&#10;&lt;h3 id="vergleich-gleiches-material-anderer-fräser"&gt;Vergleich: Gleiches Material, anderer Fräser&lt;/h3&gt;&#10;&lt;p&gt;Hier ist ein Vergleichsvideo um zu beurteilen, wie unterschiedlich Fräser in demselben Material klingen können:&#10;&lt;code&gt;14mm 3-Schneider&lt;/code&gt; Planfräser, &lt;code&gt;10mm Schaft, S5500, F7100, Z0.3, Fxy 90%&lt;/code&gt;&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-06_leveling.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;14mm Planfräser nivelliert in aller Ruhe meine Opferplatte.&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Zum Testen habe ich in meinem CAM-Werkzeug eine Spiralstrategie verwendet, die 50% mehr Zeit benötigt als die typischen &amp;ldquo;Rasenmäher&amp;rdquo;-Strategien. Auf diese Weise gibt es keine sichtbaren Fräsbahnen im fertigen Material.&lt;/p&gt;&#10;&lt;h2 id="update-april-2024"&gt;Update April-2024:&lt;/h2&gt;&#10;&lt;p&gt;Nach etwa zwei Jahren Betrieb mit diesen Holzfräsern kann ich sagen, dass sie robust laufen und lange halten. Hier ist mehr Vorschub und mehr Tiefenzustellung besser, als zu behutsam vorzugehen (&lt;code&gt;F4000 S24000 Z+12mm&lt;/code&gt; ist in Birke Multiplex auf meiner Maschine durchaus drin). Und die Fräser sind im Gegenlauf deutlich leiser als im Gleichlauf.&lt;/p&gt;&#10;</description></item><item><title>CNC Teil5 - Makros</title><link>https://blog.schallbert.de/macros-for-cnc/</link><pubDate>Wed, 11 May 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/macros-for-cnc/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-11_sub-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: snippet of the tool length sensor self-test macro for my CNC machine. It shows C-style code and checks if the probe touches the sensor."&#10; title="CNC Teil5 - Makros" /&gt;&#10;&lt;p&gt;Dies ist der fünfte Teil meiner Serie über Portalfräsmaschinen. Er befasst sich mit praktischem Zubehör, welches das Leben als CNC-Bediener erleichtert und zu schnelleren und genaueren Fräsergebnissen führen kann.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/portal-milling/"&gt;&lt;em&gt;Teil 1&lt;/em&gt;: Gedanken zu CNCs im Allgemeinen und zur Maschinenauswahl&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-electronics/"&gt;&lt;em&gt;Teil 2&lt;/em&gt;: CNC-Elektronik aufbauen&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/portal-milling-build/"&gt;&lt;em&gt;Teil 3:&lt;/em&gt; CNC-Maschinenbau&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/portal-milling-setup/"&gt;&lt;em&gt;Teil 4:&lt;/em&gt; CNC-Einrichtung&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="übersicht"&gt;Übersicht&lt;/h2&gt;&#10;&lt;p&gt;In diesem Artikel geht es um Makros für die CNC, mit deren Hilfe sich wiederholende Vorgänge automatisiert werden können. Ich decke sowohl grundlegende als auch anspruchsvollere Makros für mehrere Zwecke wie automatische Z-Nullstellung, Werkzeuglängenmessung oder Werkzeugwechsel ab.&#10;Ich führe ein paar &lt;a href="https://www.linuxcnc.org/docs/html/gcode/g-code.html#_g_code_quick_reference_table_a_id_gcode_quick_reference_table_a" target="_blank" rel="noopener noreferrer" class="external-link"&gt;G-Code&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Befehle ein. Die Beschäftigung damit half mir dabei, ein tieferes Verständnis der Funktionsweise zu erarbeiten und damit &amp;ldquo;Vertrauen&amp;rdquo; zu meiner Maschine aufzubauen.&lt;/p&gt;&#10;&lt;h2 id="haftungsausschluss"&gt;Haftungsausschluss&lt;/h2&gt;&#10;&lt;p&gt;Die folgenden Subroutinen wurden für den RS274 NGC Interpreter geschrieben. Ich habe sie mit &lt;a href="https://edingcnc.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;EdingCNC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; validiert, da dies die von mir verwendete CNC-Software ist. Ich habe einige Aspekte von Makro-Dateien abgeleitet, die sowohl &lt;a href="https://webseite.sorotec.de/service/downloads/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Sorotec&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; als auch Eding zusammen mit ihren Maschinen ausliefern.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;⚠️ Ich kann für die Funktion des hier beschriebenen Codes auf anderen Maschinen keine Verantwortung übernehmen. Meine Absicht ist lediglich zu erklären wie die Makros funktionieren.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="was-ist-ein-makro"&gt;Was ist ein Makro?&lt;/h2&gt;&#10;&lt;p&gt;Ein Makro oder Unterprogramm ist eine Sammlung von Anweisungen, die für den CNC-Interpreter geschrieben wurden, um Aktionen auszuführen. Sie werden oft verwendet, um wiederkehrende Vorgänge zu automatisieren. Auf meiner Maschine beginnt ein solches Makro mit einem &lt;code&gt;SUB&lt;/code&gt;-Befehl und endet mit einem &lt;code&gt;ENDSUB&lt;/code&gt;. Makros enthalten oft Verzweigungslogik und G-Code-Befehle, beispielsweise um die Maschine Bewegungen ausführen zu lassen, Parameter abzuspeichern oder Benutzerdialoge anzuzeigen.&lt;/p&gt;&#10;&lt;p&gt;Auf meiner Maschine befinden sich alle Makros in einer einzigen Datei namens &lt;code&gt;macro.cnc&lt;/code&gt;, die von meiner CNC-Software beim Start eingelesen wird. Einige &amp;ldquo;Soft-Buttons&amp;rdquo; der Software rufen direkt Makros aus dieser Datei auf, z.B. für die Referenzfahrt.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-11_macro.jpg" alt="Image: Macro execution in action" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Als kleine Einführung öffne ich das Dokument &lt;code&gt;macro.cnc&lt;/code&gt; mit einem Texteditor. Ich suche nach &lt;code&gt;Sub user_9&lt;/code&gt; (es sollte eine Routine sein, die nichts Relevantes tut), entferne ihren Inhalt und füge stattdessen &lt;code&gt;MSG &amp;quot;hello world!&amp;quot;&lt;/code&gt; ein. Abspeichern, CNC-Software öffnen, die Taste &lt;code&gt;9&lt;/code&gt; im &lt;code&gt;user menu &lt;/code&gt; drücken und Voilà!&lt;/p&gt;&#10;&lt;h2 id="ein-einfaches-makro-zur-erkennung-des-status-des-werkzeuglängensensors"&gt;Ein einfaches Makro zur Erkennung des Status des Werkzeuglängensensors&lt;/h2&gt;&#10;&lt;p&gt;Legen wir los und verbinden das neu erworbene Wissen über Makros mit einem nützlichen Beispiel.&lt;/p&gt;&#10;&lt;h3 id="der-werkzeuglängensensor"&gt;Der Werkzeuglängensensor&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-11_tls3d.jpg" alt="Image: Mein Werkzeuglängensensor" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ein Werkzeuglängensensor (im Folgenden &lt;em&gt;WZLS&lt;/em&gt;) ist ein Zubehörteil, das an einen Eingang der CNC-Maschine angeschlossen wird und bei Berührung (z.B. durch eine Werkzeugspitze) auslöst. Es gibt viele verschiedene Ausführungen, von einem einfachen Mikroschalter mit Gehäuse und Tastmechanismus bis hin zu hochpräzisen induktiven Sensoren aus feingedrehtem Edelstahl mit automatischer Luftspülung um sicherzustellen, dass die Oberfläche des Sensors sauber ist.&lt;/p&gt;&#10;&lt;p&gt;Der Werkzeuglängensensor wird entweder am Maschinenbett, auf dem Ablagetisch oder einfach an einer anderen festen Position an der Maschine angebracht. Meiner ist ein sehr einfacher Schalter. Seine Wiederholgenauigkeit ist mit &lt;code&gt;10µm&lt;/code&gt; angegeben.&lt;/p&gt;&#10;&lt;h3 id="makro-aufgabe"&gt;Makro-Aufgabe&lt;/h3&gt;&#10;&lt;p&gt;Das Makro soll vor der Messung prüfen, ob der WZLS betriebsbereit ist. Ich teste also, ob der Sensorschalter den Status &amp;ldquo;nicht ausgelöst&amp;rdquo; hat. Seine Verdrahtung ist als &amp;ldquo;normally closed&amp;rdquo; (NC) ausgeführt. Falls er doch ausgelöst ist (seine Kontakte also offen sind), könnte das Folgendes bedeuten:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Der Sensor klemmt in der ausgelösten Position&lt;/li&gt;&#10;&lt;li&gt;Kabelbruch in der Zuleitung zum WZLS&lt;/li&gt;&#10;&lt;li&gt;Sensor ist nicht mit der Maschine verbunden&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="code-prüfe-betriebsbereitschaft-wzls"&gt;Code: Prüfe Betriebsbereitschaft WZLS&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SUB&lt;/span&gt; is_sensor_ok&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sensorStatus &lt;span style="color:#f92672"&gt;==&lt;/span&gt; triggered&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# 5068 == 0 (normally closed)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;DLGMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Tool length sensor not connected or already triggered - please check&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;dialogButton &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sensorStatus &lt;span style="color:#f92672"&gt;==&lt;/span&gt; triggered&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Tool length sensor input still unexpected - aborting&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;User abort.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENDSUB&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Der Name des Makros lautet &lt;code&gt;is_sensor_ok&lt;/code&gt;. Ich prüfe den Sensorstatus und zeige eine Dialogmeldung an, falls hier etwas nicht stimmt. Sobald auf &amp;ldquo;OK&amp;rdquo; gedrückt wird, gehe ich davon aus, dass das Problem behoben wurde und versuche es erneut. Wenn er immer noch ausgelöst ist, breche ich die Routine ab.&lt;/p&gt;&#10;&lt;p&gt;Dieses Prüf-Makro ist in allen folgenden Abschnitten zu finden.&lt;/p&gt;&#10;&lt;h3 id="benennung-von-variablen"&gt;Benennung von Variablen&lt;/h3&gt;&#10;&lt;p&gt;Die RS274/NGC-Sprache ist alt. Wirklich alt. Ihre erste Version wurde in den späten 1950er Jahren veröffentlicht. Kein Wunder, dass die Parameter (#1 - #5399 ist der erlaubte Bereich) alle numerisch sind. Sowohl in der Benennung als auch in den Werten, die sie speichern können. Es gibt kein Typsystem (wie &lt;code&gt;Strings&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt; usw.) und keine praktischen Aliase oder menschenlesbare Namen, die das Verständnis eines Parameters wie &lt;code&gt;sensorStatus&lt;/code&gt; erleichtert.&lt;/p&gt;&#10;&lt;p&gt;Im Makro auf der Maschine wird stattdessen &lt;code&gt;#5068&lt;/code&gt; erscheinen man muss sich selbst daran erinnern, dass der Hersteller der CNC-Software such auf diese Variable festgelegt hat, um den Status des Werkzeuglängensensors zu kennzeichnen. Und dass sie vom Typ &lt;code&gt;boolean&lt;/code&gt; ist mit &lt;code&gt;0&lt;/code&gt; = nicht ausgelöst und &lt;code&gt;1&lt;/code&gt; = ausgelöst.&lt;/p&gt;&#10;&lt;p&gt;Um den Makrocode so lesbar und verständlich wie möglich zu gestalten, verzichte ich in diesem Artikel auf die Verwendung von numerischen Parameternamen. Eine Übersetzungstabelle ist &lt;a href="https://blog.schallbert.de/macros-for-cnc/#tables"&gt;ganz unten&lt;/a&gt; zu finden.&lt;/p&gt;&#10;&lt;h2 id="verwendung-des-werkzeuglängensensors-zur-ermittlung-der-werkstückhöhe-z0"&gt;Verwendung des Werkzeuglängensensors zur Ermittlung der Werkstückhöhe (Z=0)&lt;/h2&gt;&#10;&lt;p&gt;Hiermit kann die Werkstückhöhe automatisch eingemessen und auf der Obefläche abgenullt werden. So entfallen das manuelle Absenken der Z-Achse bis das Werkzeug die Oberfläche leicht ankratzt und dann das Festlegen der Werkstückkoordinaten.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-11_tls.jpg" alt="Image: Skizze Werkzeuglängensensor" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Sobald das Makro programmiert und eingerichtet ist, wird der WZLS möglichst mittig auf dem Werkstück platziert und die Spindel der Maschine mit ein paar Millimeter Luft direkt über diesem in Position gebracht. Dann wird das Makro gestartet. Die Maschine wird nun automatisch ihre Z-Achse langsam absenken, bis der Sensor schaltet. Dann fährt sie sehr langsam zurück, bis der Sensorkontakt wieder geschlossen ist. Dieser Punkt wird dann zur Bestimmung von &lt;code&gt;Z0&lt;/code&gt; herangezogen, welcher dann automatisch gesetzt wird.&lt;/p&gt;&#10;&lt;h3 id="voraussetzungen"&gt;Voraussetzungen&lt;/h3&gt;&#10;&lt;p&gt;Bevor wir das Makro schreiben können, benötigen wir die Z-Position des Werkzeuglängensensors &amp;ldquo;zTls&amp;rdquo; zum Zeitpunkt des Umschaltens von &amp;ldquo;ausgelöst&amp;rdquo; auf &amp;ldquo;nicht ausgelöst&amp;rdquo;. Ich habe meine vorerst (denn das ist nicht sehr genau) mit einem Messschieber gemessen und notiert. Wir brauchen diesen Wert, damit die Maschine ihn beim Ansetzen von der Z-Höhe abziehen kann, um die Position der Werkstückoberfläche zu ermitteln.&lt;/p&gt;&#10;&lt;p&gt;Wie man die Messung des Auslösepunktes präziser hinbekommt soll Gegenstand eines eigenen Artikels werden.&lt;/p&gt;&#10;&lt;p&gt;Außerdem ist es wichtig zu wissen, wie der Schalter mit der Maschine verbunden ist. Die bevorzugte Art ist &amp;ldquo;NC&amp;rdquo;, d.h. der Schalter öffnet sich, wenn er ausgelöst wird: &amp;ldquo;Ausgelöst = 0&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Schließlich müssen wir den Vorwärts- und Rückwärtsvorschub des Messtasters festlegen, z.B. &lt;code&gt;Absenken = 100mm/min&lt;/code&gt;, &lt;code&gt;Anheben = 10mm/min&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Das Makro enthält auch einen etwas aufwändigeren Teil: Wenn &lt;code&gt;Z0&lt;/code&gt; gemessen werden soll, obwohl die aktuell Werkzeuglänge noch unbekannt ist, macht das keinen Sinn: Bei einem Werkzeugwechsel wäre sie nicht in der Lage, den Nullabgleich auszuführen.&lt;/p&gt;&#10;&lt;p&gt;Daher wird in diesem Fall die Werkzeugvermessung zwischengeschoben: die Maschine speichert die aktuelle Position &lt;code&gt;posX, posY&lt;/code&gt; vor der Fahrt zum Werkzeuglängensensor in Hilfsvariablen zwischen und führt direkt das Makro &lt;code&gt;get_tool_length&lt;/code&gt; aus. Anschließend fährt sie wieder in die Ausgangsposition zurück, um die Vermessung von &lt;code&gt;Z0&lt;/code&gt; nachzuholen.&lt;/p&gt;&#10;&lt;h3 id="das-unterprogramm-z0-bestimmen"&gt;Das Unterprogramm &amp;ldquo;Z0 bestimmen&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Nach dem Ausschalten der Spindel senkt das Programm mit dem Befehl &lt;code&gt;G38.2&lt;/code&gt; die Z-Achse der Maschine ab, bis der Fräser Werkzeuglängensensor auslöst (oder bis sie ohne eingelegtes Werkzeug so weit nach unten gefahren ist, dass die Spindelnase den Sensor fast berührt, wo das Makro dann wiederum abbrechen würde, weil es den Sensor nicht gefunden hat). Sobald der Sensor ausgelöst, fährt die Maschine vorsichtig die Z-Achse wieder hoch. Der Punkt der erneuten Freigabe des Sensors durch das Werkzeug wird dann als Koordinaten-Offset für die Z-Achse mit dem Befehl &lt;code&gt;G92&lt;/code&gt; gespeichert.&lt;/p&gt;&#10;&lt;h3 id="code-werkstücknullpunkt-bestimmen"&gt;Code: Werkstücknullpunkt bestimmen&lt;/h3&gt;&#10;&lt;p&gt;Bevor wir beginnen stellen wir sicher, dass alle Vorbedingungen erfüllt sind:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Werkzeuglänge ist bekannt&lt;/li&gt;&#10;&lt;li&gt;Der WZLS ist korrekt angeschlossen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SUB&lt;/span&gt; measure_workpiece_z0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;![&lt;/span&gt;toolLengthStat&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;DLGMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;WARNING - Please first measure tool length!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;dialogButton &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; xPosReposition &lt;span style="color:#f92672"&gt;=&lt;/span&gt; xPos&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; yPosReposition &lt;span style="color:#f92672"&gt;=&lt;/span&gt; yPos&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; repositionTo &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;GOSUB&lt;/span&gt; get_tool_length&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;GOSUB&lt;/span&gt; is_sensor_ok ;&lt;span style="color:#75715e"&gt;# Check if tool length sensor status is OK&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Programmieren wir nun die eigentliche Routine, indem wir abfragen, ob der Z-Nullabgleich jetzt durchgeführt werden soll. Wenn die CNC im Simulationsmodus läuft (ohne angeschlossene Hardware), wird der Z-Nullabgleich nicht funktionieren. Also müssen wir auch dies überprüfen und den Schritt gegebenenfalls überspringen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;DLGMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Start Z-Zeroing?&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;dialogButton &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AND&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;operatingMode &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; simulator&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;M5&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;#Switch spindle off&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;M9&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;#Switch coolant off&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G38&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSpindleTip &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; F&lt;span style="color:#f92672"&gt;[&lt;/span&gt;touch&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ;&lt;span style="color:#75715e"&gt;# G38.2 = touch toward probe, stop on contact, flag error on fail&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ;&lt;span style="color:#75715e"&gt;# fail = 5mm before touching spindle tip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;probeOk &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# #5067 == 1 : G38.2 command success&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G38&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G91&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Z20&lt;/span&gt; F&lt;span style="color:#f92672"&gt;[&lt;/span&gt;rev&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# now reverse slowly to find untrigger point (max. 20mm up)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G90&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# back to absolute coordinates&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;probeOk &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zTouched&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# #5063 Go to Z-axis&amp;#39;s probe point&#9;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G92&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zTls&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Set Z-axis&amp;#39;s coordinate offset (0) to tool length sensor&amp;#39;s height&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zTls &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# clear sensor&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Could not locate sensor untrigger point.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;DLGMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Could not locate sensor trigger point. Retry?&amp;#34;&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;dialogButton &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &#9;&#9;&#9;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;GOSUB&lt;/span&gt; measure_workpiece_z0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;User abort.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENDSUB&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-06-11_z_zeroing.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Ausführen des Makros Z-Nullabgleich auf Zerspanobert&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="messung-der-werkzeuglänge"&gt;Messung der Werkzeuglänge&lt;/h2&gt;&#10;&lt;p&gt;Wenn die Maschine die Werkzeuglänge kennt, bevor sie den Z-Nullpunkt misst, muss der Z-Nullpunkt bei anstehenden Werkzeugwechseln während des Auftrags nicht erneut bestimmt werden. Das passende Makro beschreibe ich im Folgenden.&lt;/p&gt;&#10;&lt;h3 id="vorbereitung"&gt;Vorbereitung&lt;/h3&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-06-11_toolLengthVariables.jpg" alt="Image: get_tool_length macro parameters" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die meisten CNCs leiten ihre aktuellen Positionsdaten durch Zählen der Motorschritte ab, die sie nach Abschluss der Referenzfahrt ausgeführt hat. Um nun die Werkzeuglänge zu vermessen, benötigt sie ein paar einprogrammierte Parameter:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Z-Position der Spindelnase &lt;code&gt;zSpindleTip&lt;/code&gt; (berührt Werkzeuglängensensor ohne eingelegtes Werkzeug) damit berechnet werden kann, wie weit das Werkzeug aus der Spindel herausragt&lt;/li&gt;&#10;&lt;li&gt;X-Position &lt;code&gt;xPosTls&lt;/code&gt; des Werkzeuglängensensors beim Werkzeugwechsel&lt;/li&gt;&#10;&lt;li&gt;Y-Position &lt;code&gt;yPosTls&lt;/code&gt; des Werkzeuglängensensors bei Werkzeugwechseln&lt;/li&gt;&#10;&lt;li&gt;Sicherheitshöhe &lt;code&gt;zSafety&lt;/code&gt; (stellt sicher, dass die Spindel auf ihrem Weg zum Werkzeugwechselbereich nicht auf ein Hindernis stößt)&lt;/li&gt;&#10;&lt;li&gt;Tastvorschub vorwärts und rückwärts. Wir können ihn aus dem Makro für den Werkstücknullpunkt wiederverwenden.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="die-unterroutine-werkzeuglänge-messen"&gt;Die Unterroutine &amp;ldquo;Werkzeuglänge messen&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Legen wir zunächst fest, was diese Unterroutine tun soll:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Spindel, Kühlmittel etc. anhalten.&lt;/li&gt;&#10;&lt;li&gt;Dialog: Bediener soll geschätzte Werkzeuglänge eingeben&lt;/li&gt;&#10;&lt;li&gt;Eilgang Z auf Sicherheitshöhe, dann XY zur Position des Werkzeugsensors&lt;/li&gt;&#10;&lt;li&gt;Werkzeuglängensensor antasten, ähnlich wie bei der Werkstücknullpunktbestimmung&lt;/li&gt;&#10;&lt;li&gt;Werkzeuglänge berechnen&lt;/li&gt;&#10;&lt;li&gt;Berechnung der Differenz zwischen der letzten und der aktuellen Werkzeuglänge&lt;/li&gt;&#10;&lt;li&gt;Z-Nullpunkt entsprechend aktualisieren&lt;/li&gt;&#10;&lt;li&gt;Z im Eilgang wieder auf Sicherheitshöhe bringen&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="function-measure-tool-length"&gt;Code: Werkzeug vermessen&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SUB&lt;/span&gt; get_tool_length&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;GOSUB&lt;/span&gt; is_sensor_ok ;&lt;span style="color:#75715e"&gt;# Check if tool length sensor status is OK&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;DLGMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Start tool length measurement? Please enter estimated tool length: &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;toolLengthEst&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;dialogButton &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AND&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;operatingMode &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; simulator&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;toolLengthEst &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Error: tool length cannot be negative.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSpindleTip&lt;span style="color:#f92672"&gt;+&lt;/span&gt; toolLengthEst &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSafety&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Error: tool too long - could collide with sensor.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;M5&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Switch spindle off&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;M9&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Switch coolant off&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSafety&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Go to safety height (machine coordinates)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; X&lt;span style="color:#f92672"&gt;[&lt;/span&gt;xPosTls&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Y&lt;span style="color:#f92672"&gt;[&lt;/span&gt;xPosTls&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Go to tool length sensor&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSpindleTip &lt;span style="color:#f92672"&gt;+&lt;/span&gt; toolLengthEst &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Move Z down to 10mm above estimated tool tip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ;&lt;span style="color:#75715e"&gt;# measure tool length, save results, apply Z-offset if needed&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G38&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSpindleTip&lt;span style="color:#f92672"&gt;]&lt;/span&gt; F&lt;span style="color:#f92672"&gt;[&lt;/span&gt;touch&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# probe sensor, latest stop point is spindle tip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;probeOk &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# #5067 == 1 : G38.2 command success&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G38&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G91&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Z20&lt;/span&gt; F&lt;span style="color:#f92672"&gt;[&lt;/span&gt;rev&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# now reverse slowly to find untrigger point&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G90&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# back to absolute coordinates&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;probeOk &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; toolLength &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;zTouched &lt;span style="color:#f92672"&gt;-&lt;/span&gt; sTip&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;MSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Tool length = &amp;#34;&lt;/span&gt; toolLength&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;toolLengthStat &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# defaults to 0 on startup&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastToolLength &lt;span style="color:#f92672"&gt;=&lt;/span&gt; currToolLength ;&lt;span style="color:#75715e"&gt;# save last tool&amp;#39;s length&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currToolLength &lt;span style="color:#f92672"&gt;=&lt;/span&gt; toolLength ;&lt;span style="color:#75715e"&gt;# save current tool&amp;#39;s length&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; toolLengthDiff &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;currToolLength &lt;span style="color:#f92672"&gt;-&lt;/span&gt; lastToolLength&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G92&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zPos &lt;span style="color:#f92672"&gt;-&lt;/span&gt; toolLengthDiff&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Set Z-axis&amp;#39;s coordinate offset (0)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currToolLength &lt;span style="color:#f92672"&gt;=&lt;/span&gt; toolLength ;&lt;span style="color:#75715e"&gt;# save current tool&amp;#39;s length&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; toolLengthStat &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;GOSUB&lt;/span&gt; reposition_spindle&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Could not locate sensor untrigger point.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Could not locate sensor trigger point.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENDSUB&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-06-11_tool_length_measurement.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Werkzeuglängenmakro auf meiner Maschine&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;In diesem Video ist die Maschine so konfiguriert, dass sie zum XY-Nullpunkt zurückkehrt, wenn die Werkzeugmessung abgeschlossen ist.&lt;/p&gt;&#10;&lt;h2 id="werkzeugwechsel"&gt;Werkzeugwechsel&lt;/h2&gt;&#10;&lt;p&gt;Wenn es kein Makro für den Werkzeugwechsel gibt, hält die Maschine ihren Auftrag an. Sobald die &lt;code&gt;Z0&lt;/code&gt;-Position des Werkstücks erneut ermittelt wurde (denn die Werkzeuglänge hat sich ja wahrscheinlich geändert), kann der Auftrag fortgesetzt werden.&lt;/p&gt;&#10;&lt;p&gt;Diese Aufgabe kann automatisiert werden, indem der Z-Nullpunkt durch Bestimmen der neuen Werkzeuglänge gegenüber der alten neu kalibriert wird.&lt;/p&gt;&#10;&lt;h3 id="voraussetzungen-1"&gt;Voraussetzungen&lt;/h3&gt;&#10;&lt;p&gt;Ein optionales Flag &lt;code&gt;getToolLength&lt;/code&gt; kann das Verhalten des Werkzeugwechsel-Makros konfigurieren - ob die Länge jedes Werkzeugs nach einem Werkzeugwechsel gemessen werden soll oder nicht. Wenn Sie einen automatischen Werkzeugwechsler haben, brauchen Sie dies wahrscheinlich nicht: In diesem Falle ist die Werkzeuglänge in einer Tabelle festgehalten und die Längenkompensation erfolgt ohne Vermessung.&lt;/p&gt;&#10;&lt;p&gt;Die folgenden zusätzlichen Parameter werden ebenfalls benötigt:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;X-Position &lt;code&gt;xToolChg&lt;/code&gt; wo der Werkzeugwechsel stattfindet (bei mir ist es &lt;code&gt;xToolChg = xPosTls&lt;/code&gt;)&lt;/li&gt;&#10;&lt;li&gt;Y-Position &lt;code&gt;yToolChg&lt;/code&gt;, wo der Werkzeugwechsel stattfindet (bei mir ist es &lt;code&gt;yToolChg = yPosTls&lt;/code&gt;)&lt;/li&gt;&#10;&lt;li&gt;Wiederverwendung von &lt;code&gt;zSafety&lt;/code&gt; aus &lt;code&gt;get_tool_length&lt;/code&gt; Makro&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;newToolNumber&lt;/code&gt; zur Angabe des angeforderten Werkzeugs aus dem G-Code (oder bei manueller Eingabe)&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;currToolNumber&lt;/code&gt; zur Angabe des &amp;ldquo;alten&amp;rdquo; Werkzeugs, das ersetzt werden soll&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;ToolChangeDone&lt;/code&gt;-Hilfsflag, um anzuzeigen, ob ein Werkzeugwechsel bereits stattgefunden hat&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="das-makro-werkzeugwechsel"&gt;Das Makro &amp;ldquo;Werkzeugwechsel&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Die folgenden Schritte werden von dem Makro ausgeführt, wenn ein Werkzeugwechsel entweder durch den Befehl &lt;code&gt;M06&lt;/code&gt; &amp;ldquo;Werkzeugwechsel&amp;rdquo; in der G-Code-Datei eines Jobs oder manuell durch Benutzer ausgelöst wird:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Spindel, Kühlmittel etc. anhalten.&lt;/li&gt;&#10;&lt;li&gt;Wenn das angeforderte Werkzeug das aktuelle Werkzeug ist, erscheint ein Dialog mit der Frage, ob es trotzdem gewechselt werden soll.&lt;/li&gt;&#10;&lt;li&gt;Z im Eilgang nach oben (&lt;code&gt;zSafety&lt;/code&gt;), dann XY auf die Werkzeugwechselposition fahren&lt;/li&gt;&#10;&lt;li&gt;Dialog: Angabe des aktuellen Werkzeugs &lt;code&gt;a&lt;/code&gt; und Aufforderung, das gewünschte Werkzeug &lt;code&gt;b&lt;/code&gt; einzusetzen&lt;/li&gt;&#10;&lt;li&gt;Prüfen, ob die Werkzeugwechselkonfiguration die Bestimmung der Werkzeuglänge impliziert. Wenn ja, Makro &amp;ldquo;get_tool_length&amp;rdquo; aufrufen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="code"&gt;Code&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SUB&lt;/span&gt; change_tool&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; toolChangeDone &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;M5&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Switch spindle off&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;M9&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Switch coolant off&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;operatingMode &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; simulator&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;TCAGuard&lt;/span&gt; off ;&lt;span style="color:#75715e"&gt;# tool change area guard: off for tool change&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ;&lt;span style="color:#75715e"&gt;# handle case that tool is already in place&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;newToolNumber &lt;span style="color:#f92672"&gt;==&lt;/span&gt; currToolNumber&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;DLGMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Tool already mounted. Change anyways?&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;dialogButton &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; toolChangeDone &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; toolChangeDone &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ;&lt;span style="color:#75715e"&gt;# go to tool change position and prompt to change tool&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;toolChangeDone &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSafety&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Go to safety height (machine coordinates)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; X&lt;span style="color:#f92672"&gt;[&lt;/span&gt;xToolChg&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Y&lt;span style="color:#f92672"&gt;[&lt;/span&gt;yToolChg&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Go to tool change position&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;DLGMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Please mount tool now. Old tool number: &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;currToolNumber&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34; New tool number: &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;newToolNumber&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;dialogButton &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;newToolNumber &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;99&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;OR&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;newToolNumber &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;TCAGuard&lt;/span&gt; on ;&lt;span style="color:#75715e"&gt;# tool change area guard: on for normal job&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;New tool number implausible.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; toolChangeDone &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ERRMSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Tool change aborted.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ;&lt;span style="color:#75715e"&gt;# prompt when complete and optionally call tool length measurement&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;toolChangeDone &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;MSG&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Tool change from &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;currToolNumber&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34; to &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;newToolNumber&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34; complete.&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;M6&lt;/span&gt; T&lt;span style="color:#f92672"&gt;[&lt;/span&gt;newToolNumber&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#9;&#9;&#9;&#9; ;&lt;span style="color:#75715e"&gt;# set new tool number&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;getToolLength &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# config flag 0 = no, 1 = yes&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;GOSUB&lt;/span&gt; get_tool_length ;&lt;span style="color:#75715e"&gt;# Measure tool length. Careful: To be called after M6 T !&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;GOSUB&lt;/span&gt; reposition_spindle&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;TCAGuard&lt;/span&gt; on ;&lt;span style="color:#75715e"&gt;# tool change area guard: on for normal job&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENDSUB&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="repositionieren-auf-gespeicherte-koordinaten"&gt;Repositionieren (auf gespeicherte Koordinaten)&lt;/h2&gt;&#10;&lt;p&gt;Dieses kurze Makro wird von anderen Unterprogrammen aufgerufen, um die Maschine entweder zu einer gewünschten Position oder zum XY-Werkstücknullpunkt zu fahren. Es kann mit mehreren &lt;code&gt;repositionTo&lt;/code&gt;-Flag-Werten erweitert werden, z.B.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;0 = keine_Neupositionierung, 1 = benutzerdefinierte_Position, 2 = Werkstücknullpunkt, 3 = Maschinennullpunkt ...&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="code-1"&gt;Code&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SUB&lt;/span&gt; reposition_spindle&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G53&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; Z&lt;span style="color:#f92672"&gt;[&lt;/span&gt;zSafety&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Go to safety height (machine coordinates)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;repositionTo &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; X&lt;span style="color:#f92672"&gt;[&lt;/span&gt;xPosReposition&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Y&lt;span style="color:#f92672"&gt;[&lt;/span&gt;yPosReposition&lt;span style="color:#f92672"&gt;]&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Move back to where requested before&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; repositionTo &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# reset reposition flag and values&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; xPosReposition &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; yPosReposition &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ELSE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;G00&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;X0&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Y0&lt;/span&gt; ;&lt;span style="color:#75715e"&gt;# Move back to XY zero (workpiece coordinates)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ENDIF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENDSUB&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="tables"&gt;Tabelle der Variablen&lt;/h2&gt;&#10;&lt;p&gt;Typ-Erläuterungen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;protected&lt;/code&gt;: Parameter gehört zu festen Befehlen oder Zuständen, die schreibgeschützt sind&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;reserved&lt;/code&gt; : Parameter hat eine feste Verwendung innerhalb meiner CNC-Software&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;free&lt;/code&gt; : Parameter kann frei durch den Programmierer verwendet werden (Ist nach Not-Aus oder Neustart aber zurückgesetzt!)&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;persisted&lt;/code&gt;: Parameter wird im nicht-flüchtigen Speicher abgelegt&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="systemparameter"&gt;Systemparameter&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Variable name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter nr.&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Type&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Comment&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;xPos&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5001&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;protected&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;current CNC position&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yPos&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5002&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;protected&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;current CNC position&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;zPos&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5003&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;protected&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;current CNC position&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;currToolNumber&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5008&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;protected&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[1&amp;hellip;99]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;newToolNumber&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5011&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;protected&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[1&amp;hellip;99]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;zTouched&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5063&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;protected&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Z where sensor touched&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;probeOk&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5067&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;protected&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 = OK&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;sensorStatus&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5068&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;protected&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 = triggered&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;operatingMode&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5397&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;reserved&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 = simulator&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;dialogButton&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5398&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;reserved&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 = OK&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="konfig-parameter"&gt;Konfig-Parameter&lt;/h3&gt;&#10;&lt;p&gt;Config-Parameter sind die Parameter, die einmal eingestellt und dann konstant gehalten werden, da sie an die CNC und ihre Geometrie gebunden sind.&lt;/p&gt;&#10;&lt;p&gt;Die Volatilität der Parameter hängt stark von der CNC-Softwarelösung ab, die Sie verwenden, und die Anzahl der freien Parameter kann unterschiedlich sein. In meiner Software werden die Variablen im Bereich &amp;ldquo;#4000 - #4999&amp;rdquo; beibehalten, während alle anderen freien und reservierten Parameter flüchtig und/oder skaliert sind.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Variable name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter nr.&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Type&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Comment&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;triggered&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4400&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 = normally open&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;currToolLength&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4501&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[mm]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;lastToolLength&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4502&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[mm]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;zSafety&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4506&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Z position for move&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;xPosTls&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4507&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;tls position&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;xPosTls&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4508&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;tls position&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;zSpindleTip&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4509&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Z zpindle on tls&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;zTls&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4510&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;TLS height [mm]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;toolLengthEst&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4511&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[mm]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;touch&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4512&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;touch feed [mm/min]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;rev&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4513&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;reverse feed [mm/min]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;getToolLength&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4520&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Flag, 1 = Yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;xToolChg&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4521&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;tool change position&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yToolChg&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#4522&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free, persisted&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;tool change position&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="flag-parameter"&gt;Flag-Parameter&lt;/h3&gt;&#10;&lt;p&gt;Freie Parameter können nach Belieben verwendet werden. Es muss jedoch darauf geachtet werden, dass nicht versehentlich eine bestehende Systemvariable wiederverwendet oder überschrieben wird.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Variable name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter nr.&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Type&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Comment&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;toolLengthStat&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#3501&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 = measured&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;toolLengthDiff&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#3502&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[mm]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;toolChangeDone&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5015&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 = Yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;repositionTo&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5020&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Flag, 1 = Yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;xPosReposition&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5021&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;reposition flag&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yPosReposition&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5022&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;reposition flag&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;toolLength&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;#5024&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;free&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;[mm]&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;PS: Damit die obigen Code-Beispiele ordentlich angezeigt werden, habe ich fürs Markup die Sprachinterpretation &lt;code&gt;ruby&lt;/code&gt; verwendet. Hier kommt die farbige Syntax-Markierung am besten überein mit der Logik des NGC-interpreters.&lt;/p&gt;&#10;</description></item><item><title>CNC Teil 4.1 - Konfiguration</title><link>https://blog.schallbert.de/edingcnc-config/</link><pubDate>Tue, 08 Mar 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/edingcnc-config/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-08_edingcnc-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Zerspanobert&amp;#39;&amp;#39;s switchbox, components labeled"&#10; title="CNC Teil 4.1 - Konfiguration" /&gt;&#10;&lt;p&gt;Dies ist Teil 4.1 meiner Reihe über Portalfräsmaschinen. Er hängt eng zusammen mit &lt;a href="https://blog.schallbert.de/portal-milling-setup/"&gt;Teil4&lt;/a&gt;, konzentriert sich aber auf die korrekte Einstellung meiner Steuersoftware für die Fräse.&lt;/p&gt;&#10;&lt;h2 id="hardware"&gt;Hardware&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://edingcnc.com/product/cpu5a4e/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;EdingCnc V5A-4E&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Prozessorboard oder ähnlich&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.benezan-electronics.de/index.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Interface Advanced PRO&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Breakout board oder ähnlich&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.upload.sorotec.de/doku/manuals/adapter.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Adapter Board&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; falls nötig zum Verbinden von Prozessor- und Breakout board&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Diese Komponenten sind notwendig, um die von der CNC-Software erzeugten Befehle in Signale der physischen Welt zu übersetzen, die mit Materie interagieren können. Die Breakout-Platine wird vom CNC-Prozessor gesteuert und bietet Relaisausgänge für die Oberfräse und meinen Staubabscheider, so dass ich sie über den Laptop einschalten kann. Es gibt auch analoge Spannungsausgänge, z.B. zur Steuerung der Spindeldrehzahl, und mehrere Eingänge, z.B. zum Ablesen des Referenzschalterstatus und zur Rückmeldung von Ereignissen an die CNC-Software.&lt;/p&gt;&#10;&lt;p&gt;Natürlich gibt es viele Hersteller für diese Schaltkreise. Ich verwende das oben genannte Ökosystem, so dass ich nur für dieses sprechen kann. Die zugrundeliegende Technologie ist stets ähnlich oder sogar identisch, und obwohl Sie vielleicht eine andere Benutzeroberfläche oder einen anderen Kommunikationskanal wie USB haben, wird Ihre Software wahrscheinlich ähnliche Einstellungen bieten.&lt;/p&gt;&#10;&lt;h2 id="setup-bildschirm-1"&gt;Setup Bildschirm 1&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-08_settings1.jpg" alt="Image: EdingCNC settings screen1" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="kinematik"&gt;Kinematik&lt;/h3&gt;&#10;&lt;p&gt;Wie bereits ausführlich &lt;a href="https://blog.schallbert.de/portal-milling-setup/#kinematics"&gt;in diesem Beitrag&lt;/a&gt; beschrieben, zeigt der grüne Rahmen die Kinematikeinstellungen. Ein negatives Vorzeichen in &lt;code&gt;Schritte/[mm]&lt;/code&gt; bedeutet, dass die Richtung des Schrittmotors invertiert ist. Zu beachten ist, dass das Feld &amp;ldquo;Umkehrspiel&amp;rdquo; 0 bleiben kann, wenn Kugelumlaufspindeln verwendet werden. Ich verwende &lt;code&gt;einfache Kinematik&lt;/code&gt;, da ich keine Drehachse besitze. Also ist das Koordinatensystem kartesisch.&lt;/p&gt;&#10;&lt;h3 id="referenz--notaus"&gt;Referenz / Notaus&lt;/h3&gt;&#10;&lt;p&gt;Dieser Abschnitt steuert, wie die Software die Signalpegel für die Eingangsschalter auf der Schnittstellenkarte interpretiert. Obwohl ich sicher bin, dass ich alle Eingänge &amp;ldquo;normal geschlossen&amp;rdquo; verdrahtet habe, um einen unentdeckten Kabelbruch zu vermeiden, musste ich den Eingang für den Notschalter invertieren.&lt;/p&gt;&#10;&lt;h3 id="spindel"&gt;Spindel&lt;/h3&gt;&#10;&lt;p&gt;Diese Einstellungen sind sehr spezifisch für die Maschine, die Sie verwenden. Ich wollte, dass die Spindel anhält, wenn ich einen Auftrag unterbreche, und dass sie automatisch wieder anläuft, bevor ich den Auftrag fortsetze.&lt;/p&gt;&#10;&lt;p&gt;Der Parameter &lt;code&gt;ApproachFeed&lt;/code&gt; ist ein wenig verwirrend, weil der Standardwert hier sehr niedrig ist. Seine Bedeutung ist die folgende: Er gibt den Vorschub der Z-Achse nach unten an, wenn ein Auftrag angehalten und die Z-Achse zuvor aufwärts bewegt wurde. Wenn Sie den Auftrag fortsetzen, wird die Z-Achse mit diesem Vorschub abgesenkt, und wenn die Zieltiefe erreicht ist, wird der Auftrag normal fortgesetzt. Ich habe &lt;code&gt;600mm/min&lt;/code&gt; als einen Wert eingegeben, den die meisten Schaftfräser bewältigen können sollten.&lt;/p&gt;&#10;&lt;h2 id="setup-bildschirm-2"&gt;Setup Bildschirm 2&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-08_settings2.jpg" alt="Image: EdingCNC settings screen2" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Viele Einstellungen hier lasse ich (vorerst) bei ihren Standardwerten. Auf meine Anpassungen gehe ich weiter unten ein.&lt;/p&gt;&#10;&lt;h3 id="benutzeroberfläche"&gt;Benutzeroberfläche&lt;/h3&gt;&#10;&lt;p&gt;Ich wollte, dass das Referenzieren der Maschine Pflicht ist, bevor ich einen Auftrag laden oder die Achsen der Maschine im Eilgang verfahren kann.&#10;Außerdem musste ich &amp;ldquo;automatischer Werkzeugwechsel&amp;rdquo; anwählen, obwohl ich keinen Werkzeugwechsler habe: sonst würde mein Makro für den manuellen Werkzeugwechsel nicht aufgerufen werden.&lt;/p&gt;&#10;&lt;h3 id="schnittstelle-ea"&gt;Schnittstelle E/A&lt;/h3&gt;&#10;&lt;p&gt;Um die Hardware-Einstellungen des Breakout-Boards zu berücksichtigen, musste ich einige Software-Ausgänge invertieren. Wenn ich z.B. &lt;code&gt;PWM1&lt;/code&gt; nicht invertiert hätte, würde die Spindel mit voller Drehzahl starten, obwohl ich nur &lt;code&gt;5000rpm&lt;/code&gt; wollte und umgekehrt. Dies liegt an teils unterschiedlicher Interpretation der Logikpegel, welche als &amp;ldquo;open collector&amp;rdquo; oder auch &amp;ldquo;open emitter&amp;rdquo; ausgelegt sein können.&lt;/p&gt;&#10;&lt;h2 id="variablen-bildschirm"&gt;Variablen-Bildschirm&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-08_variables.jpg" alt="IMage: EdingCNC variable screen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="parkposition-g28--g30"&gt;Parkposition G28 / G30&lt;/h3&gt;&#10;&lt;p&gt;Ich stelle meine Parkpositionen so ein, dass ich effizient arbeiten kann. Einer dieser Befehle bewirkt, dass die Achsen auf den XY-Nullpunkt meines Anschlags fahren, den ich auch in CAM zum Werkstück-Nullpunkt mache, so dass das Einnullen wirklich einfach wird.&lt;/p&gt;&#10;&lt;p&gt;Die andere Parkposition wird auf einen Punkt gesetzt, an dem die Referenzschalter aller drei Achsen ganz nah sind, so dass das Referenzieren der Maschine nach dem Hochfahren sehr schnell abläuft.&lt;/p&gt;&#10;&lt;h3 id="überwachung-der-variablen"&gt;Überwachung der Variablen&lt;/h3&gt;&#10;&lt;p&gt;Schauen wir uns das Überwachungsfenster an. Hier können Sie eine Variable aus dem Interpreterbereich der &lt;code&gt;RS274 NGC&lt;/code&gt;eingeben und sich deren aktuelle Werte anzeigen lassen. Dies ist praktisch, wenn Sie Makros debuggen oder einfach nur interne Parameter wie die gemessene Werkzeuglänge, die Lage des aktuellen Z-Nullpunkts etc. ablesen wollen.&lt;/p&gt;&#10;</description></item><item><title>CNC Teil4 - Setup</title><link>https://blog.schallbert.de/portal-milling-setup/</link><pubDate>Thu, 03 Mar 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/portal-milling-setup/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-03_setup-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: First steps with CNC software &amp; config"&#10; title="CNC Teil4 - Setup" /&gt;&#10;&lt;p&gt;Dies ist der vierte Teil meiner Reihe über Portalfräsmaschinen. Er konzentriert sich auf das Zusammenspiel von Hardware und Software, sodass mein fertig aufgebauter &amp;ldquo;Zerspanobert&amp;rdquo; bald im Produktivbetrieb arbeiten kann.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/portal-milling/"&gt;&lt;em&gt;Teil 1&lt;/em&gt;: Gedanken zu Portalfräsmaschinen und ihre Auslegung für den (ambitionierten) Hobbybereich&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-electronics/"&gt;&lt;em&gt;Teil 2&lt;/em&gt;: CNC Elektrik / Elektronik&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/portal-milling-build/"&gt;&lt;em&gt;Teil 3:&lt;/em&gt; Zusammenbau&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/edingcnc-config/"&gt;&lt;em&gt;Teil 4.1:&lt;/em&gt; Konfiguration der Software&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/macros-for-cnc/"&gt;&lt;em&gt;Teil 5:&lt;/em&gt; Makros für die CNC&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="kinematics"&gt;Kinematik&lt;/h2&gt;&#10;&lt;p&gt;Bevor die CNC-Software auf meiner &lt;strong&gt;BasicLine 0607&lt;/strong&gt; arbeiten kann, muss sie korrekt eingerichtet werden. Sie muss die Verfahrbereiche der Maschinenachsen kennen und wissen, wo sich die Referenzschalter befinden. Außerdem ist relevant, in welche Richtung die Schrittmotoren gedreht werden müssen, um eine &amp;ldquo;positive/negative&amp;rdquo; Achsenbewegung zu erreichen.&lt;/p&gt;&#10;&lt;p&gt;In einem ersten Schritt muss entschieden werden, wie die Achsen der Maschine benannt werden (X, Y, Z), wo alle Achsen tatsächlich 0 sein sollen, und in welche Richtung die Bewegung gehen soll (positiv, negativ). Alle diese Koordinatenpunkte sind relativ und können willkürlich gewählt werden, aber es gibt einige Konventionen in der CNC-Welt:&lt;/p&gt;&#10;&lt;p&gt;Achsen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die X-Achse liegt auf dem Portalbett und bewegt die Y-Achse des Portals.&lt;/li&gt;&#10;&lt;li&gt;Die Y-Achse befindet sich oben auf dem Portal und bewegt die Z-Achsen-Baugruppe.&lt;/li&gt;&#10;&lt;li&gt;Die Z-Achse befindet sich an der Spindel und bewegt diese auf- und abwärts.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="maschinen-nullpunkt"&gt;Maschinen-Nullpunkt&lt;/h3&gt;&#10;&lt;p&gt;Maschinen- Nullpunkt und -Bewegungsrichtung:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;X-0 befindet sich auf der &amp;ldquo;hinteren&amp;rdquo; Seite der Achse. Die Bewegungsrichtung ist negativ.&lt;/li&gt;&#10;&lt;li&gt;Y-0 befindet sich auf der &amp;ldquo;linken&amp;rdquo; Seite der Achse. Die Bewegungsrichtung ist positiv.&lt;/li&gt;&#10;&lt;li&gt;Z-0 befindet sich auf der &amp;ldquo;oberen&amp;rdquo; Seite der Achse. Die Bewegungsrichtung ist negativ.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Für meine Maschine (sie ist breiter als lang), ihre Zugänglichkeit und den Raum, den ich zur Verfügung habe, habe ich die Achsen umgelegt. Bei mir ist die X-Achse die lange Achse des Portals und die Y-Achse befindet sich parallel zur Linearschine, die das Portal hält. Vom Arbeitstisch aus betrachtet ist der Maschinen-Nullpunkt bei mir also &amp;ldquo;hinten links oben&amp;rdquo;. Also bewegt sich Z in negative Richtung, Y ebenfalls, nur X geht zu positiven Werten.&lt;/p&gt;&#10;&lt;p&gt;Verwirrend, nicht wahr? Ist aber kein Problem, denn später arbeitet man kaum mehr mit Maschinenkoordinaten ;)&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-03_kinematics.jpg" alt="Image: machine kinematics and 0-point" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="bewegungsrichtungen"&gt;Bewegungsrichtungen&lt;/h3&gt;&#10;&lt;p&gt;Für die Konfiguration habe ich meine CNC-Software &lt;a href="https://edingcnc.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Link zu EdingCNC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit der Maschine verbunden. An ihr erkläre ich das Vorgehen, was bei anderen Steuerungsprogrammen im Kern aber ähnlich funktioniert. In de &lt;em&gt;Setup&lt;/em&gt;-Ansicht gebe ich die Anzahl Schritte ein, die vom Motor zu gehen sind, bis sich die Achse um einen Millimeter bewegt: &lt;code&gt;16 Mikroschritte/Schritt&lt;/code&gt; multipliziert mit &lt;code&gt;200 Schritten/Motorumdrehung&lt;/code&gt; geteilt durch &lt;code&gt;10mm/spindelumdrehung&lt;/code&gt; = &lt;code&gt;320 Mikroschritte/mm&lt;/code&gt;. Für die Z-Achse wird der Wert durch die bei mir engere Gewindespindel verdoppelt.&lt;/p&gt;&#10;&lt;p&gt;Die 16 Mikroschritte/Schritt ist der Standardwert, welcher von meinem Maschinenhersteller vorgeschlagen wurde. Es gibt &lt;a href="https://www.faulhaber.com/en/support/technical-support/motors/tutorials/stepper-motor-tutorial-microstepping-myths-and-realities/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ein paar gute Argumente&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und eine tiefgehende Analyse &lt;a href="https://www.edn.com/why-microstepping-in-stepper-motors-isnt-as-good-as-you-think/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; darüber, dass man Microstepping so wenig wie möglich verwenden sollte. Da ich weder eine Genauigkeit von 1µm erreichen muss, noch die Schrittmotoren bei meiner Anwendung sehr hohe Drehmomente bewältigen müssen, sollte das aber kein Thema sein.&lt;/p&gt;&#10;&lt;p&gt;Ohne vorher eine Referenzfahrt durchzuführen (die sowieso nicht funktionieren würde), habe ich die Maschine per Pfeiltasten vorsichtig verfahren, um zu sehen, welche Taste welche Achse in welche Richtung bewegt. Da mein Setup vom &amp;ldquo;Normal&amp;rdquo; abweicht, können die von mir gewählten Werte individuell ausfallen.&#10;Wenn eine Achse nicht mit der Pfeiltaste übereinstimmt (wenn ich z.B. &amp;ldquo;rechts&amp;rdquo; drücke und erwarte, dass sich meine X-Achse nach rechts bewegt, aber die Z-Achse tatsächlich nach unten geht), so konnte ich die Motoranschlüsse am Schaltkasten vertauschen, bis die Tasten mit den erwarteten Achsen übereinstimmten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-03_setup_kinematics.jpg" alt="Image: machine kinematics EdingCNC setup" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Wenn die Tasten die Maschine nun in die falsche Richtung bewegen - ich drücke z.B: &amp;ldquo;Bild abwärts) aber die Z-Achse bewegt sich nach oben, würde ich das Vorzeichen der Motorschrittauflösung ändern - Nicht besonders intuitiv, was? Ich habe auch ein paar Foreneinträge durchsuchen müssen, bevor ich das raus hatte.&lt;/p&gt;&#10;&lt;p&gt;Dies kam nun dabei heraus:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;X-axis -320.000&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Y-axis 320.000&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Z-axis -640.000&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="grenzen-der-maschinenbewegung"&gt;Grenzen der Maschinenbewegung&lt;/h3&gt;&#10;&lt;p&gt;Meine Maschine hat einen nutzbaren mechanischen Verfahrweg von &lt;code&gt;0 / 732mm&lt;/code&gt; in X-Richtung, &lt;code&gt;-654mm / 0&lt;/code&gt; in Y-Richtung, und &lt;code&gt;-134mm / 0&lt;/code&gt; in Z-Richtung. Diese Werte habe ich in die Maske für &lt;code&gt;negative limit&lt;/code&gt; / &lt;code&gt;positive limit&lt;/code&gt; eingegeben und die Vorzeichen auf Basis der Bewegungsrichtung relativ zum Maschinennullpunkt eingestellt.&lt;/p&gt;&#10;&lt;h3 id="referenzschalter-triggerpegel"&gt;Referenzschalter: Triggerpegel&lt;/h3&gt;&#10;&lt;p&gt;Die Achsen der meisten Maschinen haben einen &lt;a href="https://cncphilosophy.com/cnc-offsets-and-machine-reference-explained/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Referenzschalter&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Diese Schalter werden verwendet, um die Maschine zu &amp;ldquo;referenzieren&amp;rdquo;. Die Referenzfahrt ist bei jedem Start der Maschine erforderlich, da die Software intern nur die XYZ-Position durch Akkumulation der Motorschritte ermittelt. Sobald das System neu gestartet wird, gehen alle diese Werte auf 0 zurück, so dass die Maschine ihre Position vergisst.&lt;/p&gt;&#10;&lt;p&gt;Ich habe meine Schalter so angeschlossen, dass sie &amp;ldquo;normalerweise geschlossen&amp;rdquo; sind: Ihre Kontakte sind stromführend, wenn sie nicht ausgelöst werden. Auf diese Weise kann eine fehlerhafte, offene Verbindung sofort erkannt werden, da die Maschine in diesem Fall (erfolglos) mit einer ungewöhnlich niedrigen Geschwindigkeit anfahren würde: &lt;code&gt;homeVelocitySlow&lt;/code&gt; ist in meiner Konfiguration nur 1/10 der normalen Fahrgeschwindigkeit für das Anfahren der Nullpunkte. Trotzdem müssen wir sicherstellen, dass der Referenzfahrt-Kontakt richtig interpretiert wird. Dies kann im Abschnitt &amp;ldquo;Homing and E-Stop&amp;rdquo; des &amp;ldquo;Setup&amp;rdquo;-Bildschirms eingestellt werden. Ich gebe also eine &amp;ldquo;1&amp;rdquo; für &amp;ldquo;Normalerweise geschlossen&amp;rdquo; ein.&lt;/p&gt;&#10;&lt;h3 id="referenzschalter-position"&gt;Referenzschalter: Position&lt;/h3&gt;&#10;&lt;p&gt;Einige CNC-Maschinen bieten eine Positioniersteuerung mit geschlossenem Regelkreis - sie benötigen keine Referenzschalter, da ihre Motoren mit Positionsgebern ausgestattet sind, die die absolute Position jeder Achse zurückmelden. Diese Maschinen verlieren keine Schritte und sind in der Lage, Positionierungsfehler zu erkennen, die z. B. durch mechanische Überlastung verursacht werden. Man nennt solche Motoren meist &lt;em&gt;Servos&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Mit Hilfe des Referenzschalters kann eine Maschine ohne geregelte Positionierung - wie meine - ihre Schrittzahl an einer bekannten Position zurücksetzen, so dass die Achsbewegung wiederholbar wird. Allerdings muss die Position des Referenzschalters bekannt sein, um die Schrittzählung korrekt den Achsenpositionen zuzuordnen, und deshalb müssen wir die Positionen der Referenzschalter an den Achsen eingeben. Ich habe mit Vorsicht beobachtet, wie weit ich die Maschine nach dem Auslösen des Referenzschalters weiter verfahren kann, bevor ich an das mechanische Ende einer Achse stoße (Menü &amp;ldquo;Jog&amp;rdquo;). Dies sind die Werte, die ich schließlich für meine Maschine eingegeben habe:&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Steps/AppUnit&lt;/em&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;X-axis: 10mm&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Y-axis: -4mm&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Z-axis: -16mm&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="not-aus"&gt;Not-Aus&lt;/h3&gt;&#10;&lt;p&gt;Der Not-Aus ist ein weiterer Eingang für die CNC-Software und -Hardware. Durch Drücken dieses Schalters wird die Achsenbewegung gestoppt und im Zuge dessen sowohl Spindel, Kühlschmierstoffzufuhr als auch Hilfsstromkreise deaktiviert. Er ist auch &amp;ldquo;normal geschlossen&amp;rdquo; verdrahtet, um Verbindungsprobleme zu erkennen, und kann wie ein Referenzschalter konfiguriert werden.&lt;/p&gt;&#10;&lt;p&gt;Wenn der Not-Aus-Schalter aktiviert wurde, müssen die Treiber der Maschine zurückgesetzt und die Maschine erneut referenziert werden, bevor sie nach dem Loslassen der Taste wieder normal arbeiten kann.&lt;/p&gt;&#10;&lt;p&gt;Das Not-Aus Signal wird nicht nur an die Software gemeldet, sondern sorgt auf der Steuerplatine selbst für das Abfallen der entsprechenden Relais, sodass die Maschine in den sicheren Zustand gebracht werden kann.&lt;/p&gt;&#10;&lt;h2 id="bewegungsparameter-der-achsen"&gt;Bewegungsparameter der Achsen&lt;/h2&gt;&#10;&lt;p&gt;Die CNC-Software muss wissen, wie sie die Bewegung der Maschinenachsen richtig handhaben muss: Eine schwerere und steifere Maschine mit starken Schrittmotoren wird natürlich schneller verfahren und starke Beschleunigungen sowie ruckartige Bewegungen besser bewältigen können als weichere, leichtere Maschinen. Aus diesem Grund sollten die Bewegungsparameter Ihrer Maschine sorgfältig ermittelt und in der Software hinterlegt werden.&lt;/p&gt;&#10;&lt;h3 id="achsgeschwindigkeit"&gt;Achsgeschwindigkeit&lt;/h3&gt;&#10;&lt;p&gt;&amp;ldquo;Warum sollte Zerspanobert schnell sein?&amp;rdquo; habe ich mich gefragt. Mir fielen nicht allzu viele gute Antworten ein, da die normalen Vorschubgeschwindigkeiten mit den geplanten Schaftfräsern weit unter den Möglichkeiten der Maschine liegen werden. Aber egal, hier sind einige Argumente, eine Maschine schnell verfahren zu lassen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;die Arbeitszeiten sollten nicht zu lang werden&lt;/li&gt;&#10;&lt;li&gt;lange Verfahrwege, die mit einer großen Maschine zurückzulegen sind&lt;/li&gt;&#10;&lt;li&gt;beim Schneiden weicher Materialien mit geringer Dichte können die Vorschübe viel höher sein&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Wollen Sie noch immer eine schnelle Maschine? Einige Berechnungen helfen uns hier weiter.&lt;/p&gt;&#10;&lt;h4 id="bestimmung-der-fähigkeiten-der-bewegungselektronik"&gt;Bestimmung der Fähigkeiten der Bewegungselektronik&lt;/h4&gt;&#10;&lt;p&gt;Die maximale Geschwindigkeit wird durch mehrere Faktoren begrenzt:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Symbol&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Wert&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Einheit&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;maximale Antriebsfrequenz&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;fD&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;125&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;kHz&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Mikroschritt-Einstellung des Antriebs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;N&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;max. Software-Frequenz&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;fS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;125&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;kHz&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schrittzahl/Umdrehung&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;nS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;200&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schrittmotor max. Drehzahl&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;nSm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3300&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1/min&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kugelgewindebreite&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;P&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,01&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;m&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kugelumlaufspindel max. Drehzahl&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;nLm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1700&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1/min&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Nehmen wir ein Beispiel für einen 125-kHz-Treiber und nehmen wir an, dass die Software diese Schrittfrequenz unterstützt. Nehmen wir weiter an, dass die Motoren 200 Schritte/Umdrehung haben und der Treiber auf 16 Mikroschritte/Schritt konfiguriert ist.&lt;/p&gt;&#10;&lt;p&gt;Damit erreicht man eine Maximaldrehzahl der Kugelumlaufspindel von \(\frac{125000}{3200} = 39\) Umdrehungen pro Sekunde oder &lt;code&gt;2340 U/min&lt;/code&gt;, was für einen Schrittmotor echt zügig ist. Auf einer Kugelumlaufspindel mit &lt;code&gt;10mm&lt;/code&gt; Gewindeweite führt das zu einer theoretischen Verfahrgeschwindigkeit von &lt;code&gt;23.4m/min&lt;/code&gt;, was auf einer kleinen Maschine wie meiner unglaublich schnell aussieht.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;a href="https://blog.schallbert.de/portal-milling-setup/#update"&gt;Achtung! Korrekturen / Updates weiter unten&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h4 id="grenzen-der-mechanik"&gt;Grenzen der Mechanik&lt;/h4&gt;&#10;&lt;p&gt;Gut, dann schauen wir mal, ob die Kugelumlaufspindeln dieses Tempo mitgehen. Die auf meiner Maschine verbaute Serie &lt;code&gt;KGS16&lt;/code&gt; hat eine ungestützte Strecke von &lt;code&gt;~850mm&lt;/code&gt; zwischen den Lagern auf der langen Achse. Aus der &lt;a href="https://www.neff-gewindetriebe.de/fileadmin/PDF-Datenblaetter/berechnungsgrundlagen/kugelgewindetriebe/berechnungsgrundlagen_kgt.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Online-Dokumenation des Herstellers&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; entnehme ich hierbei eine maximale Drehzahl von &lt;code&gt;1700U/min&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Damit bleiben 70% von der anfänglichen Maximalgeschwindigkeit übrig: &lt;code&gt;17m/min&lt;/code&gt;. Immer noch echt flink.&lt;/p&gt;&#10;&lt;p&gt;Nun wissen wir aber, dass das Moment der Schrittmotore bei steigender Geschwindigkeit abnimmt. Wenden wir unseren Blick mal dorthin:&lt;/p&gt;&#10;&lt;h4 id="genügend-motormoment"&gt;Genügend Motormoment?&lt;/h4&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-03_torque_curve.jpg" alt="ESM.6088.42 Schrittmotor: Drehmomentverlauf" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Datasheet: Der Drehmomentverlauf meines Schrittmotors (Bild bereitgestellt durch Sorotec GmbH)&lt;/span&gt;&lt;a href="https://www.sorotec.de/webshop/Datenblaetter/Schrittmotoren/ESM.6088.42/ESM.6088.42_V1.1.pdf" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der blaue Pfeil markiert meinen Zielvorschub zum Schruppen in Holz. Hier habe ich ordentlich Drehmoment zur Verfügung um auch tiefe Nuten in einem Durchgang fertigen zu können. Selbst bei schnellen Schlichtdurchläufen oder sehr weichen Materialien (grüner Pfeil) habe ich noch fast 50% des maximalen Drehmoments zur Verfügung. Um noch genügend Luft bis zur kritischen Drehzahl der Kugelumlaufspindel zu haben, wähle ich eine maximale Geschwindigkeit für den Eilgang (&lt;code&gt;G0&lt;/code&gt;) von &lt;code&gt;12m/min = 200mm/sek&lt;/code&gt;. An diesem Punkt sind von den ursprünglich &lt;code&gt;3Nm&lt;/code&gt; nur noch &lt;code&gt;0.7Nm&lt;/code&gt; verfügbar.&lt;/p&gt;&#10;&lt;p&gt;Mit dieser Konfiguration habe ich eine recht schnelle Maschine bei genügend Luft zu kritischen Werten.&lt;/p&gt;&#10;&lt;h3 id="acceleration"&gt;Beschleunigungen&lt;/h3&gt;&#10;&lt;p&gt;Stellen Sie sich vor, Sie möchten in einem Ihrer Programme einen 90°-Winkel bei einem Vorschub von &lt;code&gt;12m/min&lt;/code&gt; fahren. Ähnlich wie bei dem Versuch, eine sehr enge Kurve auf dem Fahrrad zu nehmen, ist dies nicht bei hoher Geschwindigkeit möglich. Man muss erst auf eine Geschwindigkeit herunterbremsen, bei der die Kurve gefahrlos genommen werden kann und danach wieder hochbeschleunigen.&lt;/p&gt;&#10;&lt;p&gt;Wenn wir bei der Maschine nicht bereit sind zu warten, bis eine Achse quasi zum Stillstand gekommen ist bevor die andere losfährt, schneiden wir die Ecke rund. Wenn wir jedoch bremsen, ist unsere Bahngeschwindigkeit nicht mehr konstant - ein Dilemma. Um hier einen möglichst guten Kompromiss zu erreichen, ist die Ermittlung einer passenden Maschinenbeschleunigung wichtig.&lt;/p&gt;&#10;&lt;p&gt;Auf dem Fahrrad vermeidet man beim Bremsen bewusst das Blockieren eines Rades, um die Stabilität nicht zu gefährden. Auf der CNC gilt wieder um Ähnliches: Hinreichend hohe Beschleunigungen erreichen, ohne Schrittverlust, unnötige Schwingungen, Verklemmung / Blockade einer Achse, starke Rucke oder Torsion zu riskieren. Zudem möchten wir Maschinenverschleiß minimieren.&lt;/p&gt;&#10;&lt;p&gt;Klingt schwierig? Ist machbar, benötigt aber etwas Mathematik und Gefühl.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-03_cnc_cornering.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Kurven zeichnen: `vmax = 200mm/s`, `a=400mm/s^2`&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das Video oben illustriert den Sachverhalt: Bei Kanten und engen Kurven bremst die Maschine merklich ab um den Richtungswechsel hinzubekommen.&lt;/p&gt;&#10;&lt;h4 id="motivation"&gt;Motivation&lt;/h4&gt;&#10;&lt;p&gt;Warum möchte ich, dass Zerspanobert hohe Beschleunigungswerte unterstützt? Ein paar meiner Antworten: Meine Projekte haben oft eine Menge kurzer Liniensegmente und 2.5D-Arbeiten. Die Formen sind komplex und die G-Code Dateien zumeist recht lang. Daher verwende ich auch den &lt;a href="https://www.mmsonline.com/articles/a-closer-look-at-look-ahead" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Look-Ahead Feed&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &lt;code&gt;G64&lt;/code&gt; mit &lt;code&gt;0.1mm&lt;/code&gt; erlaubter Abweichung vom Pfad.&lt;/p&gt;&#10;&lt;p&gt;Außerdem möchte ich möglichst lange mit konstanter Geschwindigkeit durch das Material fahren. So vermeide ich Stellen, an denen das Material heißer wird als nötig. Nicht zuletzt will ich natürlich nicht unnötig viel Zeit an der Maschine verbringen - Die Maschine arbeitet mit höheren Beschleunigungswerten einfach deutlich schneller; dies wirkt sich bei meinen Projekten sogar stärker aus als ein noch flinkerer Eilgang.&lt;/p&gt;&#10;&lt;h4 id="bestimmung-der-maximalen-achsbeschleunigung"&gt;Bestimmung der maximalen Achsbeschleunigung&lt;/h4&gt;&#10;&lt;p&gt;Dann ermitteln wir mal den maximal möglichen Wert für die Maschinenbeschleunigung.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Newton%27s_laws_of_motion" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Newtons zweites Gesetz&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (axiale Bewegung): \(F_{ax} = m_pa\)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Torque" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Drehmoment&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; an der Achse: \(M_a = \frac{a J}{P}\)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Moment_of_inertia" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Trägheitsmoment&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; der Achse (Zylinder): \(J = m \frac{a}{2}r^2\)&lt;/li&gt;&#10;&lt;li&gt;Benötigtes &lt;a href="https://tech.thk.com/de/products/pdf/de_b17_009.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Motormoment&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für die Linearbewegung: \(M_d =\frac{F_{ax} P}{2 \pi \eta} + M_a\)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Wenn ich diese Formeln kombiniere, erhalte ich für die Beschleunigung:&lt;/p&gt;&#10;$$a = \frac{M_d}{\frac{m_p P}{2 \pi \eta} + \frac{J}{P}}$$&lt;p&gt;In Worten: Die höchste Beschleunigung, die die Maschine theoretisch erreichen kann, ist das verfügbare Motormoment \(M_d\) geteilt durch die Masse des Portals \(m_p\), gewichtet mit spezifischen Parametern der Kugelumlaufspindel wie Gewindespreizung \(P\) und Wirkungsgrad des bewegten Systems \(\eta\) zuzüglich des Trägheitsmoment \(J\) der rotierenden Komponenten, wiederung gewichtet durch die Gewindespreizung.&lt;/p&gt;&#10;&lt;h4 id="konkrete-richtwerte"&gt;Konkrete Richtwerte&lt;/h4&gt;&#10;&lt;p&gt;Setzen wir mal ein paar Werte ein - meine BasicLine 0607:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Symbol&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Wert&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Einheit&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stepper torque @ vmax&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Md&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Nm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Portalmasse&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;mp&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;40&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;kg&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Spreizung Gewindespindel&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;P&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.01&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;m&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Radius Gewindespindel&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;r&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.008&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;m&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Rotierende Masse&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ma&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;kg&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Wirkungsgrad der Bewegung&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;n&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;75&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Wo ich keine exakten Werte zur Verfügung hatte, habe ich worst-case Annahmen getroffen. Beim Schrittmotor z.B. meine Maximalgeschwindigkeit bei geringster Versorgungsspannung. Auch das Gewicht der rotierenden Masse (Spindel, Kupplung, Rotor und Achse des Motors) habe ich geschätzt sowie den Wirkungsgrad von Kugelumlaufspindel, Lagern und Linearführungen.&lt;/p&gt;&#10;&lt;p&gt;Dies ist mein Ergebnis (Trommelwirbel):&lt;/p&gt;&#10;$$a = 5.0m/s^2$$&lt;h4 id="was-dieser-wert-bedeutet"&gt;Was dieser Wert bedeutet&lt;/h4&gt;&#10;&lt;p&gt;Wie lange benötigt die Maschine, um auf Höchstgeschwindigkeit zu beschleunigen?&#10;Nehmen wir unser Physikwissen aus der Sekundarstufe: &lt;a href="https://en.wikipedia.org/wiki/Acceleration" target="_blank" rel="noopener noreferrer" class="external-link"&gt;gleichmäßig beschleunigte Bewegung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;$$v(t) = at \Rightarrow t = \frac{v_{max}}{a} = 40ms$$&lt;p&gt;Wie groß ist der Weg, den wir dabei zurücklegen (&lt;/p&gt;&#10;$$s_0 = 0, v_0 = 0$$&lt;p&gt;)?&lt;/p&gt;&#10;$$s(t) = \frac{a}{2}t^2 + v_0t + s0 \Rightarrow s(t=40ms) = 4mm$$&lt;p&gt;Welche Axialkräfte wirken dabei auf das Portal ein? &lt;/p&gt;&#10;$$F_{ax} = m_pa = 200N$$&lt;p&gt;Die von mir hier errechnete Beschleunigung ist mehr als eine Größenordnung über der Empfehlung des Herstellers von &lt;code&gt;300mm/s^2&lt;/code&gt;. Ich habe mich da deutlich verschätzt.&lt;/p&gt;&#10;&lt;p&gt;🤔 &lt;em&gt;Ich muss etwas übersehen haben.&lt;/em&gt;&lt;/p&gt;&#10;&lt;h4 id="zusammenfassung-erfahrung-zählt"&gt;Zusammenfassung: Erfahrung zählt!&lt;/h4&gt;&#10;&lt;p&gt;Die Formeln oben berücksichtigen weder die Steifheit der Maschine noch Ruck, Torsion oder auftretende Schwingungen unter dynamischer Last. Was ein &amp;ldquo;Ruck&amp;rdquo; im CNC-Kontext darstellt? Dieses kurze &lt;a href="https://www.youtube.com/watch?v=9Z9Zws9K5kc" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Youtube-Video&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; klärt auf. Meine Steuersoftware jedoch verfügt nicht über eine Ruckbegrenzung.&lt;/p&gt;&#10;&lt;p&gt;Obwohl ich viel darüber gelernt habe, wie man sich mit Hilfe der Mathematik eine Einordnung der Parameter ermöglicht, läuft alles wieder auf die Erfahrung hinaus:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Wie schwer und stabil ist die Maschine?&lt;/li&gt;&#10;&lt;li&gt;Mit welcher Art von Material arbeite ich: Ist es nachgiebig oder steif?&lt;/li&gt;&#10;&lt;li&gt;Habe ich komplexe oder möglicherweise langwierige Fräsaufgaben zu erledigen?&lt;/li&gt;&#10;&lt;li&gt;Arbeitet meine CNC in einer Produktionsumgebung, in der die Maschinenzeit wichtig ist?&lt;/li&gt;&#10;&lt;li&gt;Wie klingt und fühlt sich die CNC bei hohen Beschleunigungswerten an?&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Ich habe einen Wert von 800 mm/s^2 als Beschleunigung für die X- und Y-Achse eingegeben. Das schien ohne Probleme zu funktionieren. Immerhin bleibt er um den Faktor 6 unter der berechneten Fähigkeit des Bewegungssystems.&lt;/p&gt;&#10;&lt;h2 id="ausprobieren"&gt;Ausprobieren&lt;/h2&gt;&#10;&lt;p&gt;Ich weiß, das war eine lange Lektüre und viel mehr eine fundierte Vermutung als eine strenge Berechnung 😅. Trotzdem sind wir jetzt bereit, die Maschine das erste Mal in Betrieb zu nehmen.&#10;Probieren Sie es aus. Gehen Sie danach im manuellen Betrieb auf allen Achsen auf auf den Maschinennullpunkt. Hält sie an, bevor sie den mechanischen Endstopp erreicht?&lt;/p&gt;&#10;&lt;p&gt;Verwenden Sie dann den kontinuierlichen Tippbetrieb, um sich dem anderen Ende aller Achsen anzunähern. Bewegen Sie vorsichtig die letzten Millimeter. Sind alle Messungen und Einstellungen korrekt?&#10;Wenn ja, versuchen Sie es mit der 100%-Geschwindigkeitstaste (&lt;code&gt;Shift&lt;/code&gt; - auf meiner Tastatur). Bewegt sich die CNC schnell und stoppt automatisch am Achsenende ohne Kollision?&lt;/p&gt;&#10;&lt;p&gt;Herzlichen Glückwunsch!&lt;/p&gt;&#10;&lt;h2 id="rechner-für-maximale-bewegungseinstellungen"&gt;Rechner für maximale Bewegungseinstellungen&lt;/h2&gt;&#10;&lt;p&gt;Sie haben tapfer den ganzen Artikel gelesen. Als Belohnung habe ich ein kleines Excel-Tool vorbereitet: &lt;a href="https://blog.schallbert.de/assets/docs/cnc_kinematics_helper.xlsx"&gt;CNC kinematics helper&lt;/a&gt;, das Sie herunterladen und verwenden können, um Schätzungen für Ihre Maschine zu berechnen. Natürlich kann ich keine Verantwortung für die Ergebnisse übernehmen, die Sie bei der Verwendung dieses Tools erhalten. Selbiges gilt für Schäden, die falsche Werte verursachen können.&lt;/p&gt;&#10;&lt;h2 id="update"&gt;Update&lt;/h2&gt;&#10;&lt;p&gt;Ich habe nochmal mit den maximalen Geschwindigkeits- und Beschleunigungswerten für meine Maschine herumgespielt.&#10;&lt;code&gt;300mm/s&lt;/code&gt; war zu schnell und ich bekam Software-Limit Verletzungsfehler. Bei &lt;code&gt;240mm/s&lt;/code&gt; hat meine Maschine stark vibriert. Also habe ich die Höchstgeschwindigkeit bei &lt;code&gt;220mm/s&lt;/code&gt; belassen, was in Ordnung zu sein scheint.&#10;Außerdem habe ich die Beschleunigung auf &lt;code&gt;1200mm/s^2&lt;/code&gt; erhöhen können. Das einzige Problem mit diesem neuen Wert ist, dass die Maschine bei kurzen Bewegungen ein bisschen schwingt. Wenn die Beschleunigung abgeschlossen ist, wird alles wieder ruhiger. Schauen wir mal, ob sich das auf die Bearbeitungsqualität auswirkt.&lt;/p&gt;&#10;&lt;h2 id="update-2"&gt;Update 2&lt;/h2&gt;&#10;&lt;p&gt;Die obige Berechnung ist zwar mathematisch nicht falsch, aber sie ist &lt;em&gt;irreführend&lt;/em&gt;. Sie suggeriert, dass jede Achse mit dieser Geschwindigkeit laufen könnte, was einfach nicht der Fall ist. Ich habe mir vom &lt;a href="https://edingcnc.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anbieter der Steuerungssoftware für meine Maschine&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bestätigen lassen, dass die 125kHz die maximale Befehlsfrequenz des &lt;strong&gt;Systems&lt;/strong&gt; sind. Wenn ich versuche, höhere Geschwindigkeiten zu fahren, erhalte ich die Fehlermeldung &amp;ldquo;Velocity was higher than max!&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Deshalb sollte ein so hoher Geschwindigkeitswert &lt;em&gt;nicht&lt;/em&gt; verwendet werden, wenn die Maschine mit mehreren Achsen gleichzeitig im Eilgang läuft. Ich habe einen Folgeartikel verfasst und &lt;a href="https://blog.schallbert.de/projects/one-year-zerspanobert/#velocityerror"&gt;hier&lt;/a&gt; zur Information verlinkt. Dort verwende ich Werte, die stabil in jeder Situation funktionieren, auch wenn alle Achsen mit voller Geschwindigkeit gleichzeitig betrieben werden.&lt;/p&gt;&#10;&lt;h2 id="noch-immer-weiterlesen"&gt;Noch immer weiterlesen?&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/edingcnc-config/"&gt;Einrichtung der Maschinensoftware&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>CNC Teil3 - Aufbau</title><link>https://blog.schallbert.de/portal-milling-build/</link><pubDate>Tue, 01 Mar 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/portal-milling-build/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-machine_complete-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Completed build of Zerspanobert - Sorotec Basicline 0607"&#10; title="CNC Teil3 - Aufbau" /&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/aAF2gaBewonh2uvu6mspze" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;&amp;amp;quot;Zerspanobert&amp;amp;quot; CNC assembly&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dies ist der dritte Teil meiner Artikelserie über Portalfräsmaschinen. Er konzentriert sich auf den mechanischen/mechatronischen Aufbau meiner Maschine.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/portal-milling/"&gt;&lt;em&gt;Teil 1&lt;/em&gt;: Gedanken zu Portalfräsmaschinen und ihre Auslegung für den (ambitionierten) Hobbybereich&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/cnc-electronics/"&gt;&lt;em&gt;Teil 2&lt;/em&gt;: CNC Elektrik / Elektronik&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/portal-milling-setup/"&gt;&lt;em&gt;Teil 4:&lt;/em&gt; Einrichten der Maschine&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/edingcnc-config/"&gt;&lt;em&gt;Teil 4.1:&lt;/em&gt; Konfiguration der Software&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/macros-for-cnc/"&gt;&lt;em&gt;Teil 5:&lt;/em&gt; Makros für die CNC&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="marke-sorotec-produkt-basicline-0607"&gt;Marke: Sorotec, Produkt: Basicline 0607&lt;/h2&gt;&#10;&lt;p&gt;Meine Portalfräsmaschine wurde als Komplettbausatz geliefert. Nach einer groben Prüfung auf Vollständigkeit konnte ich voller Vorfreude auf die fertige Maschine mit dem Zusammenbau starten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-allparts.jpg" alt="Image: parts included in the assembly kit" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="eckdaten-der-maschine"&gt;Eckdaten der Maschine&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Stellfläche etwa 1m x 1m x 0.8m&lt;/li&gt;&#10;&lt;li&gt;Arbeitsbereich ~ 654mm x 732mm x 134mm&lt;/li&gt;&#10;&lt;li&gt;Gewicht ohne Spindel, Elektrosatz, Zubehör: 52kg (laut Hersteller)&lt;/li&gt;&#10;&lt;li&gt;Kugelumlauftriebe und Linearführungen auf allen Achsen&lt;/li&gt;&#10;&lt;li&gt;Schrittmotoren: &lt;code&gt;3Nm, 4.2A @48V&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Empfohlene maximale Bewegungsgeschwindigkeit: &lt;code&gt;130mm/s | 7.8m/min, 400mm/s²&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="zusammenbau"&gt;Zusammenbau&lt;/h2&gt;&#10;&lt;p&gt;Der Aufbau der Mechanik und Mechatronik dauerte etwa 16h inkl. Verdrahtung der Schrittmotoren, Stecker und aller Leitungen. Nicht berücksichtigt ist die Maschinensteuerung, worüber ich &lt;a href="https://blog.schallbert.de/cnc-electronics/"&gt;hier&lt;/a&gt; schreibe.&lt;/p&gt;&#10;&lt;p&gt;Die nun folgenden Abschnitte beschreiben je eine Phase des Zusammenbaus. Ich verschweige auch nicht die Schwierigkeiten, die ich dabei hatte.&lt;/p&gt;&#10;&lt;h3 id="1-vorbereitung-der-lager-für-die-kugelumlaufspindeln"&gt;1. Vorbereitung der Lager für die Kugelumlaufspindeln&lt;/h3&gt;&#10;&lt;p&gt;Die Kugelumlaufspindel (&lt;a href="https://www.youtube.com/watch?v=W27pJXlO6ko" target="_blank" rel="noopener noreferrer" class="external-link"&gt;youtube link&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) ist eine mechanische Vorrichtung, die eine Drehbewegung in lineare Bewegung umwandelt. Dabei wird die mit einem Gewinde versehene Spindel gedreht und dadurch ein auf der Spindel befindlicher Schlitten verfahren. Der Unterschied zur auf demselben Konzept basierenden Trapezgewindespindel ist, dass die Spindelmutter über ein umlaufendes System von Kugeln verfügt, welche Reibung und Spiel zwischen Mutter und Spindel reduzieren. Bei meiner CNC-Maschine werden sie eingesetzt, um die Achsen mit Hilfe eines Schrittmotors zu verfahren.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-ballscrews.jpg" alt="Image: ballscrews with bearings" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Kugelumlaufspindeln besitzen zwei Lager. Ein Festlager auf der Schrittmotorseite (der schwarze Block auf den Bildern) und ein Loslager, das die andere Seite der Spindel an ihrem Platz hält. Die Montage des Loslagers in seine Halterung stellte sich als schwierig heraus. Ich habe keinerlei Erfahrung im Metallbau, also habe ich statt der klugen Lösung (Kugellager abkühlen, Halterung erwärmen) die einfache gewählt (Hämmern). Ergebnis: das Lager saß schief.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-01_fitting-bad.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Falsch eingesetztes Loslager&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Glücklicherweise hatte ich eine &lt;code&gt;20mm&lt;/code&gt; Nuss zur Hand die Außendurchmesser des Lagers nahekam. So war ich in der Lage, das Lager erst wieder heraus zu schlagen und dann erneut - sanft und gleichmäßig - in seinem Sitz zu befestigen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-01_fitting-ok.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Verhalten nach Korrektur des Lagersitzes&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich hoffe mal, dass ich das Lager bei meinen Fehlversuchen nicht zu sehr mit Aluminiumspänen verunreinigt habe.&lt;/p&gt;&#10;&lt;h3 id="2-maschinenrahmen"&gt;2. Maschinenrahmen&lt;/h3&gt;&#10;&lt;p&gt;Der Rahmen meiner Maschine besteht aus Aluminium-Stranggussprofilen, welche mit Winkeln und Nutensteinen miteinander verbunden werden. Massive Aluminiumplatten and Vorder- und Rückseite der Maschine versteifen die Konstruktion zusätzlich. Die Linearführungen werden an der Seite des Rahmens befestigt. Sie halten später das Portal und stellen die Bewegung der X-Achse (Definition des Herstellers) sicher.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-frame.jpg" alt="image: frame of Basicline 0607" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich habe ziemlich viel Zeit darauf verwenden müssen, den Rahmen ordentlich auszurichten. Als endlich alle Winkel 90° betrugen, konnte ich einen kleinen Luftspalt an der rechten Rahmenwange lassen, sodass die Portalbreite die Rahmenbreite vorgeben kann und ich nichts verspanne. Als ich die Schrauben jedoch anzog, schien der Rahmen sich stets aus dem rechten Winkel zu verabschieden. Als ich das Spiel ein paarmal erfolglos wiederholt hatte, begann ich, die Schrauben in mehreren Stufen gleichmäßig in &lt;code&gt;3Nm&lt;/code&gt;-Schritten anzuziehen.&lt;/p&gt;&#10;&lt;p&gt;Auf diese Weise konnte ich endlich meine &lt;code&gt;24Nm&lt;/code&gt; bei guter Rechtwinkligkeit 😅 erreichen&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-xaxis.jpg" alt="Image: X-axis fully mounted, view from the rear" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Montage der Front- und Rückplatte, Kugelumlaufspindel der Y-Achse sowie des Referenzschalters stellten sich als angenehm schnell und einfach heraus. Das obige Bild zeigt die fertig montierte X-Achse. Linearführungen (ebenfalls mit Kugelumlauf), die später das Portal tragen werden, sind bereits montiert. So sehen diese Dinger von innen aus:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-01_trolley.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Linearführungen mit Kugelumlauf&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="3-portal"&gt;3. Portal&lt;/h3&gt;&#10;&lt;p&gt;Zuerst werden die Portalwangen an den Linearführungen der Y-Achse befestigt. Dabei vereinfachen Metallstifte, die in bereits vorhandene Bohrungen gesteckt werden, die präzise Positionierung. Anschließend werden die Führungsschienen für die Y-Achse am Querbalken des Portals befestigt und dieser dann an der Portalwange montiert. Winkel an beiden Seiten der Portalwange versteifen die Konstruktion. Um die Montage zu erleichtern verwendete ich zwischenzeitlich Schraubzwingen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-portal.jpg" alt="Image: Gantry mounted, secured with clamps" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Diese Maschine arbeitet mit einem Schrittmotor für die Y-Achse, dessen Kugelumlaufspindel mittig am Portalunterzug befestigt wird. Dieser ist im Video unten gut zu erkennen. Da die Maschine langsam an Gewicht zulegte, habe ich mir einen kleine Holzkonstruktion gebaut, um die für die Montage schräg auf der Seite liegende Maschine zu sichern.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-01_portal-move.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Das Portal lässt sich ohne Kugelumlaufspindel leicht von Hand bewegen.&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Nun konnte ich die Schrauben am Luftspalt des Rahmens nochmal vorsichtig lösen, das Portal ein paar mal bewegen um mögliche Verspannungen heraus zu bekommen und sie anschließend wieder gleichmäßig anziehen.&lt;/p&gt;&#10;&lt;p&gt;Als Nächstes waren die Kugelgewindespindel und Linearführungen der X-Achse zu montieren. Auf diesen wird - wieder mit Positionierstiften versehen - eine Flanschplatte montiert, die später die Z-Achse hält.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-yaxis.jpg" alt="Image: X-axis with Z-flange before attaching to ball screw" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Zum Schluss waren noch Referenzschalter und Schrittmotorhalterung anzubringen.&lt;/p&gt;&#10;&lt;h3 id="4-z-achse"&gt;4. Z-Achse&lt;/h3&gt;&#10;&lt;p&gt;Die Z-Achse wir als eigene Baugruppe unabhängig vom Rest der Maschine montiert. Auch hier kommen wieder Passstifte zum Einsatz für die Flanschplatte. Bei meiner Maschine hält eine einzelne Linearführung die Z-Achse an ihrer Position.&lt;/p&gt;&#10;&lt;p&gt;Dies ist für meine Anwendungsbereiche mit kleinen Fräsern und recht geringen Vorschüben sicherlich OK, wird bei herausfordernderen Materialien, schnellen Vorschüben oder der Verwendung großer Fräser durch die viel höheren Schnittkräfte bestimmt schnell ein Problem. Was soll&amp;rsquo;s, ich mache doch eh nur in Holz und Kunststoffen&amp;hellip;&lt;/p&gt;&#10;&lt;p&gt;Es ist ja immer schön wenn Dinge einfach funktionieren 🤗 - Herrlich, wie die Teile des Bausatzes satt ineinander greifen!&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-03-01_zaxis.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Ich liebe das Geräusch, wenn gut passende Bauteile zusammenkommen.&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Als die Baugruppe soweit fertig war, konnte ich sie an der Flanschplatte des Portals befestigen. Anschließend habe ich die Z-Achse mit Hilfe eines Winkels und einer Messuhr ausgerichtet, sodass sie über den gesamten Fahrweg mit nur ein paar Hundertstel Abweichung senkrecht auf dem Maschinenrahmen steht.&#10;Irgendwann muss man es bei diesem Bausatz aber gut sein lassen, da die Z-Achse mit ordentlichem Druck per Hand am unteren Teil fast 1/10mm gegen die Senkrechte wegbiegen kann.&#10;Es ist halt keine superstabile, entsprechend teure Maschine sondern ein Bausatz für den ambitionierten Hobbybereich.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-zaxis_alignment.jpg" alt="Image: Z-axis alignment with dial gauge" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das Anbringen der seitlichen Versteifungen auf der Z-Achse stellte sich als viel schwieriger heraus als ich dachte. Die T-Nutensteine sind so klein bei der &lt;code&gt;17mm&lt;/code&gt; Schiene und die Schrauben so kurz, dass da kaum Platz blieb um alles an seinen Platz zu bekommen. Ich musste die Nutensteine mehrfach neu positionieren, um die Konstruktion dann einfädeln zu können. Als das endlich klappte, konnte ich den Referenzschalter montieren.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-zaxis_stiffening.jpg" alt="Image: Z-axis stiffening cheeks, reference switch, Y-axis motor mount" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="5-mechatronik"&gt;5. Mechatronik&lt;/h3&gt;&#10;&lt;p&gt;Nun geht es an die vorher &lt;a href="https://blog.schallbert.de/cnc-electronics/#schrittmotortreiber-im-detail"&gt;vorbereiteten Schrittmotoren&lt;/a&gt; . Dafür werden die Mitnehmer der Klauenkopplungen mit Vaseline eingerieben, sodass sie später keine lauten Knarzgeräusche von sich geben. Dann wird die eine Seite der Kupplung an der Kugelumlaufspindel und die andere am Schrittmotor befestigt. Zum Schluss dann montiert man die Motoren und zieht die Schrauben erst richtig fest, wenn Ausrichtung geprüft und ein leichter Lauf sichergestellt werden kann.&lt;/p&gt;&#10;&lt;p&gt;Auf der X-Achse hatte ich ziemliche Schwierigkeiten, die Madenschraube auf der Motorseite festzuziehen. Daher würde ich es anders als in der Anleitung angegeben direkt wie oben beschrieben mit auseinandergenommener Klauenkupplung montieren und die Motoren erst ganz zum Schluss einpassen.&lt;/p&gt;&#10;&lt;p&gt;Da die Motorzuleitung nicht besonders lang ist, gibt es in Nähe der Motoren kleine Klemmenkästen. Dort werden Motorzuleitung und Signaldrähte für die Referenzschalter aufgelegt. Die andere Seite der Klemmen verbindet eine schleppkettentaugliche Leitung später mit der Steuerung.&#10;Zum Schluss wird noch ein Schutzleiter zu jedem Motor gelegt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-xaxis_wiring.jpg" alt="Image: Wiring X-axis stepper motor" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Als das für alle drei Achsen erledigt war, machte ich mich an die Schleppketten. Hier ist es vorteilhaft, die Montagerichtung bewusst zu wählen - die Schleppketten lassen sich z.B. zur nachträglichen Verlegung einer zusätzlichen Leitung nämlich nur in eine Richtung öffnen. Wenn deren Mechanismus dann von der Maschine verdeckt wird, kann das zu einem unmöglichen Unterfangen werden.&lt;/p&gt;&#10;&lt;p&gt;Es ist ebenfalls empfehlenswert, die Steuerleitungen länger als berechnet zu lassen und sie erst in einem letzten Schritt zu kürzen, nämlich bevor sie an der Steuerung montiert werden. Die Zuleitung zur Z-Achse kann beispielsweise nach Führen durch alle Schleppketten gern mal in Richtung 5m Länge gehen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-mechatronics.jpg" alt="Image: All wiring done." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="6-abschmieren"&gt;6. Abschmieren&lt;/h3&gt;&#10;&lt;p&gt;Bonusschritt: Der Wartungsanleitung zum Abschmieren aller Linearführungen und Kugelumlaufspindeln folgen. Herrlich, wenn man nochmal unter die Maschine muss&amp;hellip;&lt;/p&gt;&#10;&lt;p&gt;Erwähnte ich bereits, dass ich kein Metallfritze bin? Diese Fettpressen sind echt frustrierend. Am Anfang bekam ich so gar nichts aus ihr raus und dann muss ich eine Luftblase im System gehabt haben, sodass ich nicht mehr wusste, wohin mit dem ganzen überschüssigen Schmierstoff. Immerhin habe ich jetzt einen gut gefetteten Arbeitstisch.&lt;/p&gt;&#10;&lt;p&gt;Und wann ist es eigentlich genug? Reicht &amp;ldquo;zwei mal drücken&amp;rdquo;, um eine Linearführung ausreichend zu schmieren? Kommt das Fett überhaupt in die Schmiernippel? Na ja, sobald es aus der Dichtung der Führungen herausquillt wird es wohl reichen 😜&lt;/p&gt;&#10;&lt;h3 id="7-fehlende-teile"&gt;7. Fehlende Teile&lt;/h3&gt;&#10;&lt;p&gt;Wie oben beschrieben waren alle wichtigen teile der Maschine korrekt und entsprechend der Packliste geliefert worden. Allerdings fehlten ein paar KLeinigkeiten für meine Maschinenkonfiguration:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;18x 18mm M5 Maschinenschrauben mit Innensechskant (Linearführungen X-Achse)&lt;/li&gt;&#10;&lt;li&gt;6x 0.75mm Kabelschuhe (Schutzerde für die Schrittmotoren)&lt;/li&gt;&#10;&lt;li&gt;4x 22mm M4 Maschinenschrauben mit Innensechskant (Montage Schrittmotoren)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Aber ich hatte auch ein paar Teile zu viel&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;1m extra Schleppkette&lt;/li&gt;&#10;&lt;li&gt;4 M5 Unterlegscheiben&lt;/li&gt;&#10;&lt;li&gt;9 M4x10 Senkkopfschrauben mit Innensechskant&lt;/li&gt;&#10;&lt;li&gt;2 Versteifungen z-Achse &amp;ldquo;standard&amp;rdquo;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Mein Vorschlag wäre, nochmal 100 Kabelbinder auf den Bausatz draufzuschlagen, sodass man alle Leitungen schön und ordentlich führen kann.&lt;/p&gt;&#10;&lt;h3 id="8-kritik"&gt;8. Kritik&lt;/h3&gt;&#10;&lt;p&gt;Die Bauanleitung (Version 2.1.5) gefällt mir sehr gut. Sie enthält eine Teileübersicht und jeder Schritt ist sehr detailliert erklärt. Auch der Bauprozess ist gut durchdacht und ich als nicht-professioneller Mechaniker hatte keine größeren Probleme, die Maschine korrekt zusammenzubauen.&lt;/p&gt;&#10;&lt;p&gt;Die bearbeiteten Teile haben eine hohe Qualität und sind frei von Graten oder scharfen Ecken. Alle notwendigen Gewinde wurden im Voraus geschnitten - sogar die für den optionalen Mechatronik-Bausatz, so dass ich keine Werkzeuge kaufen oder mir Wissen darüber aneignen musste, wie und wo ich sie montieren muss.&lt;/p&gt;&#10;&lt;p&gt;Was mir nicht gefiel, war, dass einige Befestigungselemente fehlten, was die Fertigstellung um ein Wochenende verzögerte, und dass die erwartete Anzahl der Befestigungselemente nicht in der Anleitung abgedruckt war, so dass ich erst merkte, dass Teile fehlten, als es bereits zu spät war. Mir ist klar, dass diese Maschine in verschiedenen Größen und Konfigurationen bestellt werden kann, so dass es vielleicht zu viel verlangt ist, eine Bauanleitung für jede einzelne Kombination zu erwarten.&lt;/p&gt;&#10;&lt;p&gt;Einige der größeren Aluminiumteile kamen mit Kratzern an, da die Palette während des Transports irgendwie gebrochen und durch die Verpackung gerissen sein muss. Für mich zeigt das, dass entweder das Logistikunternehmen oder der Hersteller die Maschine nicht 100%ig sicher verpackt oder vielleicht das Gewicht unterschätzt hat.&lt;/p&gt;&#10;&lt;p&gt;Ein letzter Punkt zu diesen Schmiernippeln (bin immer noch kein Metallbauer): Der für die Y-Achse kann nicht mehr gedreht werden, wenn die Mutter der Kugelumlaufspindel am unteren Portalträger montiert ist, weil das Spiel fehlt. Das ist unglücklich, wenn die Schmierung plötzlich in einem anderen Winkel als vorgesehen erfolgen muss. Außerdem bin ich mir ziemlich sicher, dass der untere Schmiernippel der Z-Achse im Handbuch in die falsche Richtung zeigt.&lt;/p&gt;&#10;&lt;p&gt;Ich habe mein Bestes gegeben, um Dinge zu finden, über die ich meckern kann. Ich habe es aber nicht geschafft, mehr als die oben genannten Kleinigkeiten zu finden. Keiner meiner Kritikpunkte schränkt die Funktionalität der Maschine ein, und keiner von ihnen kostete eine beträchtliche Menge an Zeit oder Geld, um ihn zu beheben.&lt;/p&gt;&#10;&lt;p&gt;Bisher sieht alles so aus als hätte ich ein robustes Gerät gekauft, das mir helfen wird, meine Prototyping-Holzarbeiten präziser und schneller als je zuvor zu erledigen.&lt;/p&gt;&#10;&lt;p&gt;Darf ich vorstellen: &lt;strong&gt;Zerspanobert&lt;/strong&gt;!&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-03-01-machine_complete.jpg" alt="Image: Sorotec BasicLine 0607 fully built" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h3 id="9-dann-nehmen-wir-sie-mal-in-betrieb"&gt;9. Dann nehmen wir sie mal in Betrieb&lt;/h3&gt;&#10;&lt;p&gt;Weiterlesen? Gern! &lt;a href="https://blog.schallbert.de/portal-milling-setup/"&gt;Einrichtung der Maschine&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>CNC Teil2 - Elektrik</title><link>https://blog.schallbert.de/cnc-electronics/</link><pubDate>Fri, 18 Feb 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/cnc-electronics/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-18_switchbox-closed-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: switchbox of my CNC machine"&#10; title="CNC Teil2 - Elektrik" /&gt;&#10;&lt;h2 id="elektrik-der-portalfräse"&gt;Elektrik der Portalfräse&lt;/h2&gt;&#10;&lt;p&gt;Dies ist der zweite Teil aus der Reihe &lt;a href="https://blog.schallbert.de/portal-milling/"&gt;Portalfräsmaschine&lt;/a&gt;. Hier geht es um Elektrik und Elektronik der Maschine.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-18_switchbox.jpg" alt="Image: Switch box, electronics fully assembled, opened" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h3 id="ac--netzkomponenten"&gt;AC / Netzkomponenten&lt;/h3&gt;&#10;&lt;p&gt;Im Schaltkasten befinden sich Sicherungen für die gesamte Maschinensteuerung und ein zentraler Netzschalter. In meinem Fall enthält er auch netzgekoppelte Relais zur Steuerung von Verbrauchern wie Spindeln, Kühlung und Staubabsaugung. Für diese Stromkreise sind zusätzlich abgesicherte Strompfade und Steckdosen vorhanden.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-18_sockets.jpg" alt="Image: Schaltkasten, Steckdosen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Außerdem verfügt er über ein Netzteil für die Kleinspannungs-Gleichstromschiene. Die Ausgangsleistung ist vorrangig angepasst an die Anforderungen der Schrittmotore. Es versorgt zudem die Steuer-/Breakout-Platine und weitere Spannungswandler, die dann die CNC-Steuerplatine sowie Lüfter und andere Kleinverbraucher mit Energie versorgen.&lt;/p&gt;&#10;&lt;p&gt;Zudem gibt es ein Signalrelais, um einen eventuell vorhandenen Frequenzumrichter zum Betrieb von Hochfrequenzspindeln mit der Steuerung zu koppeln.&lt;/p&gt;&#10;&lt;h3 id="vfd"&gt;Frequenzumrichter&lt;/h3&gt;&#10;&lt;p&gt;Ein FU ist allgemein bekannt als ein Gerät, das die Drehzahl einer 3-Phasen-Wechselstrommaschine steuert. Er wird mit 1- oder 3-phasigem Wechselstrom versorgt und puffert die Energie in einem Gleichstrom-Zwischenkreis, von wo aus sie dann an eine 3-phasige &lt;a href="https://en.wikipedia.org/wiki/H-bridge" target="_blank" rel="noopener noreferrer" class="external-link"&gt;H-Brücke&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; weitergeleitet wird, die daraus Drehstrom mit variabler Frequenz erzeugt.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-18_vfd-schematics.jpg" alt="Image: Vereinfachte schematische Darstellung des Leistungspfads eines VFD" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Diese dreiphasige Wechselspannung wird von Transistoren im Schaltbetrieb erzeugt, was die Umwandlungsverluste im Vergleich zum linearen Betrieb enorm verringert. Die Ausgangsspannung ist allerdings zeitlich und betragsmäßig quantifiziert (&amp;ldquo;digital&amp;rdquo;), was zu unerwünschten elektromagnetischen Störaussendungen sowohl am Netzein- als auch am Ausgang des FU führt.&lt;/p&gt;&#10;&lt;p&gt;Fast alle &lt;a href="https://blog.schallbert.de/portal-milling/#spindle-power"&gt;professionellen CNC-Spindeln&lt;/a&gt; sind 3-Phasen-AC-Asynchronmaschinen, die an einen Frequenzumrichter angeschlossen sind.&lt;/p&gt;&#10;&lt;p&gt;FUs haben oft einen standardisierten &lt;code&gt;0...10V&lt;/code&gt; Analogeingang zur Steuerung der Maschinendrehzahl. Sie besitzen zudem mehrere digitale (manchmal sogar potentialfreie) Ein- und Ausgänge, die z.B. von einem Melderelais der Schnittstellenkarte der numerischen Steuerung gespeist werden, und einen Nothalt- oder Fehlersignalausgang zur CNC-Schnittstelle. Sie können auch mit moderneren digitalen Bussystemen und analogen Ausgängen ausgestattet sein, was in meinem Einsatzbereich jedoch wegen der höheren Systemkosten selten genutzt wird.&lt;/p&gt;&#10;&lt;p&gt;Im Moment (Feb-2022) besitze ich keinen FU. Daher kenne ich keine Details zur Einstellung, Parametrierung und Anpassung an eine bestimmte Spindel, über die ich schreiben könnte.&lt;/p&gt;&#10;&lt;h3 id="pelv--dc-komponenten"&gt;PELV / DC-Komponenten&lt;/h3&gt;&#10;&lt;p&gt;Die typischen &amp;ldquo;kleinen&amp;rdquo; CNC-Schrittmotoren werden im PELV-Bereich (&lt;a href="https://en.wikipedia.org/wiki/Extra-low_voltage" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Protected Extra Low Voltage&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) mit Gleichstrom (DC) und unter &lt;code&gt;60V&lt;/code&gt; betrieben, so dass kein zusätzlicher Berührungsschutz erforderlich ist. Das &amp;ldquo;P&amp;rdquo; in PELV steht für &amp;ldquo;protected&amp;rdquo;, was bedeutet, dass die Geräte mit Erdungsleitungen verbunden sind. Auf diese Weise werden die Schaltkreise bei einem Kurzschluss gegen Erde durch auslösende Sicherungen abgeschaltet. Ein weiterer Vorteil der Erdung aller Komponenten ist die bessere Ableitung von Störeinflüssen, welche sonst die empfindliche Elektronik der CNC beeinträchtigen könnte.&lt;/p&gt;&#10;&lt;p&gt;Die Schrittmotortreiber, jeweils einer pro Achse, sind ebenfalls in der Box untergebracht. Sie sind in der Regel SELV (Safety Extra Low Voltage), so dass sie keine zusätzliche Erdung benötigen.&lt;/p&gt;&#10;&lt;p&gt;Dann gibt es noch eine Signalplatine, eine Breakout-Platine oder eine Relaisplatine, die Schrittmotorsignalzüge an die Treiber weiterleitet, Hilfsrelais steuert, Referenzschalter- und Notstopp-Eingänge ausliest und Signale für Spindel, Kühlschmierstoffzufuhr, Absaugung und/oder weitere Hilfssteuerungen ausgibt.&lt;/p&gt;&#10;&lt;p&gt;Ein Lüfter hält die Temperatur im Schaltkasten niedrig. Oft ist auch eine Schnittstellenkarte montiert, die direkt über die CNC-Software gesteuert wird, z. B. über USB oder Ethernet. Sie übersetzt und leitet Informationen an die einzelnen Komponenten der CNC und zurück an das Programm auf dem Computer, z. B. für Referenzschalter oder Not-Aus.&lt;/p&gt;&#10;&lt;h3 id="schrittmotortreiber-im-detail"&gt;Schrittmotortreiber im Detail&lt;/h3&gt;&#10;&lt;p&gt;Die Schnittstelle des Schrittmotortreibers ist recht einfach. Sie nimmt TTL-Pegel von &lt;code&gt;0V (low)&lt;/code&gt; und &lt;code&gt;5V (high)&lt;/code&gt; an und wird durch nur drei Eingänge definiert:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Signalname&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Beschreibung&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Enable (EN)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schaltet die Stromversorgung der Antriebe ein. Ermöglicht z.B. aktives Halten der Position&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Drehrichtung (DIR)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Auswahl der Motordrehrichtung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Pulse (PUL)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lässt den Motor einen (Mikro-)Schritt drehen&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Interessanterweise werden dieselben drei Eingangssignale benötigt, um den Lesekopf eines Diskettenlaufwerks zu steuern. Ich habe dies in einem meiner früheren Projekte ausprobiert, einer Floppy-Bass-Orgel, das auf dem Softwareprojekt &lt;a href="https://blog.arduino.cc/2012/05/29/music-floppy-moppy/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;moppy&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; basiert.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-18_drivers.jpg" alt="Image: Stepper drivers, top view" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Intern interpretiert der Treiber die Eingangssignale und gibt Impulse aus, die für den Antrieb des Schrittmotors geeignet sind. Zwei H-Brücken-Kanäle - Schaltungen mit je vier Schalttransistoren zur Steuerung von Richtung und Höhe der Spannung an den Motorspulen - mit den Bezeichnungen &amp;ldquo;A&amp;rdquo; und &amp;ldquo;B&amp;rdquo; liefern Energie für die Polpaare des Schrittmotors.&lt;/p&gt;&#10;&lt;p&gt;Ausführlichere Informationen über Schrittmotorantriebe, Mikroschrittbetrieb und andere Aspekte finden Sie z. B. &lt;a href="https://blog.poscope.com/stepper-motor-driver/#What_is_a_stepper_motor_driver" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder auf &lt;a href="http://www.schrittmotor-blog.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;diesem Blog&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="schrittmotoren"&gt;Schrittmotoren&lt;/h3&gt;&#10;&lt;p&gt;Schrittmotoren sind - wie 3-Phasen-Wechselstrom- oder bürstenlose Gleichstrommotoren - elektronisch kommutiert. Daher müssen dem Motor Strompulse zugeführt werden, damit er sich kontinuierlich dreht. Die meisten Schrittmotoren sind heutzutage &lt;a href="http://stepcontrol.com/pdf/step101.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hybridmotoren&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Diese weisen einen mehrzahnigen Rotor auf, der in zwei Hälften geteilt ist welche um einen halben Zahn zueinander versetzt sind und jeweils von einer Spule gesteuert werden.&#10;Diese Art von Motor hat mindestens vier Anschlüsse, zwei für Spule &amp;ldquo;A&amp;rdquo; und zwei für Spule &amp;ldquo;B&amp;rdquo;.&#10;Bipolare Hybridmotoren haben sogar acht Leitungen, die die Spulen wiederum in zwei Teile aufteilen - auf diese Weise können sie in Reihe, parallel und auch unipolar geschaltet werden, so dass jede der vier Spulen einzeln gesteuert werden kann.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-18_stepper-cable.jpg" alt="Image: Stepper cables" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Schrittmotoren werden meist in &amp;ldquo;bipolarer Parallelschaltung&amp;rdquo; an CNC-Schrittmotortreiber angeschlossen. Dabei werden die beiden Spulen eines Polpaares mit gleicher Ausrichtung parallel geschaltet. Die beiden Drähte dieser Parallelschaltung werden dann mit den Ausgängen &lt;code&gt;A+&lt;/code&gt; und &lt;code&gt;A-&lt;/code&gt; des Treibers verbunden. Diese Verdrahtung wird für Kanal &lt;code&gt;B&lt;/code&gt; wiederholt.&#10;Ich habe die zusammengehörenden Aderpaare mit einer Bohrmaschine verdrillt weil ich zu faul war, das von Hand zu machen. Verdrillen der spulengleichen Aderpaare &lt;code&gt;A/B&lt;/code&gt; macht Sinn, weil die mit Pulszügen angesteuerten Motoren erhebliche elektromagnetische Störungen erzeugen können. Durch das Verdrillen gleichen sich magnetische Felder von Hin- und Rückleitung aus.&lt;/p&gt;&#10;&lt;p&gt;Anders als bei Drehstrom- oder bürstenlosen Gleichstrommaschinen liegt die Priorität bei der Konstruktion von Schrittmotoren nicht auf einer möglichst hohen Energieeffizienz, geringen Geräuschemissionen, hohen Drehzahlen oder einem guten Wärmemanagement für den Dauerlauf, sondern auf hohem Drehmoment, großen Haltekräften zum Sichern einer Position und einem flüssigen Betrieb bei niedrigen Drehzahlen. Schrittmotoren sind für hohe Beschleunigungen gebaut und sollen stets der Impulsgeschwindigkeit folgen, da die Positionierung der Maschine davon abhängt.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-18_stepper-connect.jpg" alt="Image: Stepper, connected to a driver in bipolar parallel configuration" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Schrittmotoren eignen sich wie Servomotoren auch zum Positionierbetrieb. Hierbei werden sie nicht über die Wellenposition geregelt, sondern die Position wird durch Zählen der Pulse in die jeweilige Richtung &amp;ldquo;geschätzt&amp;rdquo;. Solange kein Schrittverlust (durch Überlast oder zu hoch eingestellte Beschleunigungswerte) auftritt, ist diese Art der Positionssteuerung sowohl kostengünstig als auch hinreichend genau. Daher wird sie häufig verwendet.&lt;/p&gt;&#10;&lt;p&gt;Da die Positionsinformation bei Neustart der Maschine (meist) verloren geht, verfügen CNC-Maschinen mit Schrittmotoren über Referenzschalter an jeder Achse, die bei Inbetriebnahme angefahren werden und die Positionsinformation im Koordinatensystem der Maschine mit der der Schrittmotore verknüpft.&lt;/p&gt;&#10;&lt;h2 id="der-aufbau"&gt;Der Aufbau&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-02-18_switchbox-build.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Schaltkastenbau in neun Sekunden&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der Bau des von mir gekauften Bausatzes für den Schaltkasten und die Verdrahtung nahm nur etwa 8 Stunden in Anspruch. Es erwies sich als entscheidend, alle Schaltpläne und Schritt-für-Schritt-Anleitungen im Voraus gründlich zu studieren. So konnte ich Fehler vermeiden, deren Behebung sonst Abende gekostet hätte.&lt;/p&gt;&#10;&lt;p&gt;An dem Bausatz gefällt mir, dass die Komponenten elektrisch aufeinander abgestimmt sind und alle notwendigen Leitungen, Buchsen (bis auf eine Ethernet-Buchse) und die Dokumentationen vollständig waren. Ich habe nur Kabelkanäle und einige Kabelschuhe vermisst, um das Kabelmanagement und die Verbindungen zum Gehäuse zu vereinfachen. Das Gehäuse ist sehr robust und alle Steckdosen passen gut an ihren Platz.&lt;/p&gt;&#10;&lt;p&gt;Am Frickeligsten beim Aufbau waren die Lötstellen der Schrittmotor/D-Sub-Stecker und deren Isolierung. Ebenso stellte es sich als schwierig heraus, die Steuerplatine im Gehäuse montiert zu bekommen.&lt;/p&gt;&#10;&lt;p&gt;Ich war wirklich aufgeregt, als ich die Steuerung zum ersten Mal einschaltete. Das Geräusch des anlaufenden Lüfters und das Blinken aller möglichen LEDs waren einen Seufzer der Erleichterung wert.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2022-02-18_switchbox-test.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Erster Test: Quittieren des Not-Aus Schaltkreises&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="sicherheitshinweise"&gt;Sicherheitshinweise&lt;/h3&gt;&#10;&lt;p&gt;Nein, ein geöffnetes netzbetriebenes Gerät sollte nicht eingeschaltet werden, wenn man nicht weiß, was man tut.&#10;Ja, ich habe in einen Fehlerstromschutzschalter installiert, um mich vor gefährlichen Spannungen zu schützen, und ja, ich habe alle Sicherungen doppelt überprüft, eine gründliche Sichtprüfung durchgeführt und Durchgangsmessungen an jedem einzelnen Anschluss vorgenommen, um Fehlverdrahtungen auszuschließen. Glücklicherweise bestand die Steuerung den ersten Funktionstest. Der Zustand des Notsignals wurde korrekt interpretiert und die LED der CNC-Platine blinkte mit der erwarteten &lt;code&gt;1Hz&lt;/code&gt;-Rate.&lt;/p&gt;&#10;&lt;h3 id="aufbau-einer-mini-control--serie-c1"&gt;Aufbau einer &amp;ldquo;Mini Control / Serie C1&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Für die Maker Faire Ruhr 2024, bei der ich einer der Aussteller war, habe ich eine weitere Steuerung aufgebaut: Die einfachere &amp;ldquo;Mini Control / Serie C1&amp;rdquo;. Ein Zeitraffervideo davon habe ich hier eingebettet:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/hvZabGKMgP6QWeD5a8wgKg" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;CNC electronics assembly&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Vorteile:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schnelle Montage&lt;/li&gt;&#10;&lt;li&gt;Kein Löten&lt;/li&gt;&#10;&lt;li&gt;Viele Snap-In-Teile&lt;/li&gt;&#10;&lt;li&gt;Kleines Gehäuse&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Nachteile:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Verbindung zur Maschine erfolgt über Schraubklemmen; Trennung zur Maschine nicht vorgesehen&lt;/li&gt;&#10;&lt;li&gt;Lauter Lüfter, auch im Leerlauf aktiv (kann aber mit „Schallberts Fanhack“ gelöst werden)&lt;/li&gt;&#10;&lt;li&gt;Mehrere externe Netzteile (Schrittmotorversorgung, 24V-Versorgung, Spindel-Schaltbox)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Mit der zugehörigen kleinen CNC-Fräse habe ich viel gemacht: &lt;a href="https://blog.schallbert.de/cnc-tuning-kinematics/"&gt;Tuning&lt;/a&gt; auf kürzere Fertigungszeiten, eine Spezialisierung für Gravuren und diverse Projekte wie Stempel, Dithering-Bilder und mehrfarbige &lt;a href="https://blog.schallbert.de/engrave-multicolor/"&gt;Carves&lt;/a&gt;.&lt;/p&gt;&#10;</description></item><item><title>CNC Teil1 - Portalfräsmaschine</title><link>https://blog.schallbert.de/portal-milling/</link><pubDate>Sat, 12 Feb 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/portal-milling/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-12_portalmillingmachine-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: My portal milling machine in mid-construction"&#10; title="CNC Teil1 - Portalfräsmaschine" /&gt;&#10;&lt;h2 id="cnc-portalfräsmaschine---für-anfänger"&gt;CNC Portalfräsmaschine - für Anfänger&lt;/h2&gt;&#10;&lt;p&gt;Es ist Winter 2021, und meine erste CNC-Maschine wird bald geliefert. Es wird ein Bausatz sein, der aus einer Menge Pakete und noch mehr Einzelteilen besteht. Ich habe die Maschine bestellt, weil ich Prototypen für &lt;a href="https://blog.schallbert.de/projects/aa_alpha_1_1/"&gt;AnywhereAmps&lt;/a&gt; schneller und genauer fertigen möchte. Und natürlich um ein paar weitere Hardware-Projekte umzusetzen, die mir schon länger im Kopf herumschwirren.&lt;/p&gt;&#10;&lt;h3 id="cnc"&gt;Moment mal&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-12_gcode.jpg" alt="G-code example" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Oh, Ok. CNC bedeutet &lt;a href="https://en.wikipedia.org/wiki/Numerical_control" target="_blank" rel="noopener noreferrer" class="external-link"&gt;computer numerical control&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und beschreibt, wie die Maschine gesteuert wird - nicht von Hand, sondern durch Instruktionen eines Computers.&lt;/p&gt;&#10;&lt;p&gt;Diese werden &lt;a href="https://en.wikipedia.org/wiki/G-code" target="_blank" rel="noopener noreferrer" class="external-link"&gt;G-Code&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; genannt, und sind in der häufig benutzten Interpretersprache RS274-NGC geschrieben, die Industriestandard für computergesteuerte Bearbeitungszentren geworden ist.&lt;/p&gt;&#10;&lt;p&gt;Eine Portalfräsmaschine ist einer Oberfräse nicht unähnlich. Der Fräsmotor ist auf einem Gestell montiert, das ihn in (meist) drei Achsen X, Y, Z mithilfe von &lt;a href="https://en.wikipedia.org/wiki/Stepper_motor" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Schrittmotoren&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder &lt;a href="https://en.wikipedia.org/wiki/Servomotor" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Servomotoren&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bewegt. Ein Rechner steuert diese Bewegungen und ist so in der Lage, nahezu beliebige Formen zu schneiden. Die Kinematik der Portalfräsmaschine ist ähnlich zu der von &lt;a href="https://en.wikipedia.org/wiki/3D_printing#Processes_and_printers" target="_blank" rel="noopener noreferrer" class="external-link"&gt;3D-Druckern&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, obwohl sie natürlich ganz unterschiedliche Anforderungen an die Mechanik haben - eine Portalfräsmaschine muss viel stabiler, steifer und schwerer konstruiert werden, da die Rahmenkonstruktion sämtliche Schneid- und Beschleunigungskräfte aufnehmen muss.&lt;/p&gt;&#10;&lt;p&gt;Natürlich sind CNC-Maschinen nicht auf 3D-Drucker und Fräsmaschinen beschränkt. Es gibt CNC-&lt;a href="https://en.wikipedia.org/wiki/Lathe" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Drehmaschinen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Plotter" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Plotter&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Laser_cutting" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Laserschneider&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Water_jet_cutter" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Wasserstrahlschneider&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und viele mehr. Streng genommen sind sogar Industrieroboter CNC-Maschinen.&lt;/p&gt;&#10;&lt;h2 id="fragen"&gt;Fragen&lt;/h2&gt;&#10;&lt;p&gt;Dies sind einige der Fragen, die ich hatte, bevor ich dieses ziemlich teure Stück Technik gekauft habe. Einige davon lassen sich leicht in ein paar Zeilen beantworten, andere würden wahrscheinlich einen ganzen Artikel für sich allein füllen.&lt;/p&gt;&#10;&lt;h3 id="cnc-fräsmaschine"&gt;CNC-Fräsmaschine&lt;/h3&gt;&#10;&lt;h4 id="beschleunigt-sie-wirklich-meinen-prototyping-prozess"&gt;Beschleunigt sie wirklich meinen Prototyping-Prozess?&lt;/h4&gt;&#10;&lt;p&gt;Ich weiß es noch nicht, aber ich hoffe es. Die Holzarbeiten für einen einzigen Prototyp nehmen etwa 8 Stunden meiner Zeit in Anspruch. Am meisten Zeit verschlingen die Vorbereitungen für den Zuschnitt, die Messungen und das Abpausen. Allein der Lautsprecheradapter von AnywhereAmps Alpha mit seiner gewölbten Oberfläche nimmt mehr als eine Stunde Fertigungszeit in Anspruch.&lt;/p&gt;&#10;&lt;p&gt;Mit einer CNC-Portalfräsmaschine hoffe ich, mehr wie ein Plätzchenbäcker zu werden: Alle Teile für AnywhereAmps (mit Ausnahme des zylindrischen Gehäuses) in einem einzigen Stück Sperrholz zu verschachteln, die Maschine die Arbeit machen zu lassen und nach einer Weile nur noch minimale Nacharbeiten beim Ausschneiden und Schleifen der Teile zu erledigen.&lt;/p&gt;&#10;&lt;p&gt;Und wenn ich einen weiteren Prototyp oder andere Lautsprecherdurchmesser haben möchte: Ich passe einige Parameter in meinem CAD-Tool an, stelle die Werkzeugwege neu ein, schicke sie an den Computer der Maschine, spanne ein neues Stück Sperrholz ein und drücke auf &amp;ldquo;Auftrag ausführen&amp;rdquo;. Fertig (in der idealen Welt).&lt;/p&gt;&#10;&lt;p&gt;Andererseits braucht das Einarbeiten in die ganzen Software-Tools auch seine Zeit. Mein Einführungsartikel über &lt;a href="https://blog.schallbert.de/freecad-get-started/"&gt;FreeCAD hier verlinkt&lt;/a&gt; ist ein Beispiel dafür.&lt;/p&gt;&#10;&lt;h4 id="mehr-geld-für-portalhardware-oder-lieber-für-den-spindelantrieb-ausgeben"&gt;Mehr Geld für Portalhardware oder lieber für den Spindelantrieb ausgeben?&lt;/h4&gt;&#10;&lt;p&gt;Das ist eine Frage an die Experten, die ich selbst erst beantworten kann, wenn ich schon eine Weile eine CNC besitze. Ich habe mich dafür entschieden, mehr Geld für das Portal und weniger für die Spindel auszugeben, und zwar aus mehreren Gründen, die sich aus dem Bauch heraus ergeben:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Das Portal ist meine Maschine, die Spindel nur ein Teil davon.&lt;/li&gt;&#10;&lt;li&gt;Die Spindel ist günstiger, leichter auszutauschen und bei weitem nicht so aufwendig zu montieren wie das Portal, also möchte ich, dass das Portal eine Konstante ist.&lt;/li&gt;&#10;&lt;li&gt;Wenn ich durch die Fähigkeiten meiner Portalfräsmaschine eingeschränkt bin, müsste ich zuerst herausfinden, warum, und dann an einzelnen Komponenten herumschrauben, um sie zu verbessern. Wenn die Spindel der limitierende Faktor ist, kaufe ich mir einfach eine stärkere und gut ist.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="spindle-power"&gt;Wie viel Spindelleistung?&lt;/h4&gt;&#10;&lt;p&gt;Das ist meine erste schwierige Frage. Ich bin mir fast sicher, dass ich für meine Holzbearbeitung kein 2,2-kW-Monster brauche. Um anständige Vorschubgeschwindigkeiten zu erzielen und genügend Spielraum für Dichteunregelmäßigkeiten wie Astlöcher zu haben, könnte es eine gute Idee sein, sich im Leistungssegment der Handoberfräse zwischen 700W und 1200W zu orientieren, was mir immer gute Dienste geleistet hat. Ich habe mich für eine 1000W &lt;a href="https://en.wikipedia.org/wiki/Universal_motor#cite_note-tm-6" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Universalmotor&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Maschine entschieden.&lt;/p&gt;&#10;&lt;p&gt;Im Gegensatz zu den oft als &amp;ldquo;HF-Spindeln&amp;rdquo; (HF = Hochfrequenz) bezeichneten Drehstrommotoren benötigen sie keinen &lt;a href="https://blog.schallbert.de/cnc-electronics/#vfd"&gt;Frequenzumrichter&lt;/a&gt; und keine externe Kühlung, da ihr Lüfter auf die Welle geschraubt ist. Andererseits sind sie in der Regel lauter (der Lüfter ist nicht unabhängig von der Motordrehzahl) und weniger effizient (Lüfterverluste, Kommutierungsverluste, Kupferverluste, Magnetisierungsverluste usw.) Ich habe im Internet nur grobe Zahlen zum Wirkungsgrad gefunden, also nehmen wir mal an:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;1000W&lt;/code&gt; Universalmotorantrieb &lt;code&gt;*30%&lt;/code&gt; Gesamtwirkungsgrad inkl. Elektronik &amp;amp; Lüfter: &lt;code&gt;300W&lt;/code&gt; @ Motorwelle&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;700W&lt;/code&gt; &lt;a href="https://www.scribd.com/document/776268309/2-4-6polig-e-M-asynchron-IP44-IP56" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Asynchronmaschine&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &lt;code&gt;*85%&lt;/code&gt; Wirkungsgrad &lt;code&gt;*90%&lt;/code&gt; &lt;a href="http://www.variablefrequencydrive.org/vfd-efficiency" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Wirkungsgrad Steuerung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &lt;code&gt;*97%&lt;/code&gt; external cooling: &lt;code&gt;520W&lt;/code&gt; @ motor shaft&lt;/p&gt;&#10;&lt;p&gt;Das wäre ein gewaltiger Unterschied, so dass sich eine spätere Aufrüstung doch lohnen könnte. Außerdem reichen für eine HF-Spindel im Vergleich zu einem konventionellen Antrieb geringere Wattzahlen aus.&lt;/p&gt;&#10;&lt;h4 id="tool-changer"&gt;Automatischer Werkzeugwechsler - ja oder nein?&lt;/h4&gt;&#10;&lt;p&gt;Das hängt wohl davon ab, was ich brauche. Ich kaufe mir zunächst einen einfachen Fräsmotor mit digitaler Drehzahlregelung als Spindel. Ein &amp;ldquo;richtiger&amp;rdquo; Werkzeugwechsler - richtig bedeutet, dass ich auch Werkzeugschäfte von &lt;code&gt;10mm&lt;/code&gt; und größer montieren kann und gewuchtete Spannzangen, 3-Phasen-Antrieb mit eigenständiger Kühlung und Frequenzumrichter habe - würde ~10 mal so viel kosten. Außerdem kostet jedes ER20-Spannzangenfutter für den Werkzeugwechsler mal eben die Hälfte meines Fräsmotors. Davon bräuchte ich mehr als eine, wenn der Werkzeugwechsler Sinn machen soll. Und ich bräuchte eine Druckluftanlage mit Schaltkasten usw. Das alles zusammen würde die Kosten für die Portalfräse locker überschreiten!&lt;/p&gt;&#10;&lt;p&gt;Schlussfolgerung: Wenn ich die CNC in Zukunft sehr oft benutzen werde und wenn meine Projekte unbedingt häufige Werkzeugwechsel zum Schruppen, Glätten, Fasen, 3D usw. erfordern, dann könnte ich diesen teuren Schritt in Betracht ziehen. Vielleicht. Irgendwann.&lt;/p&gt;&#10;&lt;h4 id="welche-größe-ist-die-beste"&gt;Welche Größe ist die beste?&lt;/h4&gt;&#10;&lt;p&gt;Idealerweise könnte man für die Bearbeitung von Sperrholzplatten eine ganze Standardplatte von &amp;ldquo;1250mm x 2500mm&amp;rdquo; oder einen Bruchteil davon, z.B. &amp;ldquo;610mm x 1250mm&amp;rdquo;, auflegen. Aber selbst die kleineren Platten würden nicht in den Keller meiner kleinen Wohnung passen. Also entschied ich mich für eine mit &lt;code&gt;840mm x 740mm&lt;/code&gt; Auflagefläche.&lt;/p&gt;&#10;&lt;p&gt;Für Ihre Wahl: Prüfen Sie, ob Sie platzmäßig eingeschränkt sind. Wenn nicht, dann machen Sie sich gründlich Gedanken darüber, was Sie mit Ihrer Maschine machen wollen. Die Größe der Maschine ergibt sich automatisch, wenn Sie wissen, wofür Sie sie hauptsächlich einsetzen wollen.&lt;/p&gt;&#10;&lt;h4 id="plug-and-play-oder-ein-bausatz"&gt;Plug-and-play oder ein Bausatz?&lt;/h4&gt;&#10;&lt;p&gt;Dies hängt von mehreren Aspekten ab: Von Budget, der Zeit, die man zu investieren bereit ist, und natürlich davon, ob man zumindest ein bisschen Talent im Umgang mit Schraubendrehern, Muttern, Abisolierzangen und dem Lötkolben hat.&#10;Es kommt auch darauf an, ob einem jede Schraube Ihrer Maschine persönlich bekannt sein soll.&lt;/p&gt;&#10;&lt;h4 id="kugelumlaufspindeln-zum-bewegen-der-achsen"&gt;Kugelumlaufspindeln zum Bewegen der Achsen?&lt;/h4&gt;&#10;&lt;p&gt;Flinke Eilgänge, hohe Wiederholgenauigkeit durch reduziertes oder eliminiertes Spiel oder höhere Vorschubgeschwindigkeiten? Fräsen im Gleichlauf, um die Oberflächenqualität und die Standzeit der Werkzeuge zu erhöhen? Wenn Sie einen dieser Punkte bejahen, sollten Sie das zusätzliche Geld für Kugelumlaufspindeln ausgeben.&lt;/p&gt;&#10;&lt;h4 id="zusätzliche-vielseitigkeit"&gt;zusätzliche Vielseitigkeit?&lt;/h4&gt;&#10;&lt;p&gt;Wie im Abschnitt &lt;a href="https://blog.schallbert.de/portal-milling/#cnc"&gt;Moment mal&lt;/a&gt; beschrieben, wäre das Portal selbst auch für andere Zwecke geeignet. Es gibt Schleppmesser zum Plotten, 3D-Druckköpfe und sogar Gravurlaser, die bei Bedarf an das Portal angeflanscht werden könnten.&lt;/p&gt;&#10;&lt;p&gt;Um sich darauf vorzubereiten, ist eine Spindel, die auf den Euronorm-Hals mit einem Durchmesser von 43 mm passt möglicherweise die beste Wahl, da die meisten anderen Werkzeuge auch für diese Passform erhältlich sind. Dies erleichtert einen eventuellen Umbau. Wobei andererseits die meisten Maschinen, die etwas taugen, spezialisiert sind auf ihr Fach - ein 3D-Drucker schafft wahnwitzige Beschleunigungsvorgänge, der Plotter spart beim Platzverbrauch und die CNC-Fräse ist nunmal wegen der Schneidkräfte stabiler gebaut.&lt;/p&gt;&#10;&lt;h3 id="allgemeine-fragen"&gt;Allgemeine Fragen&lt;/h3&gt;&#10;&lt;h4 id="entsteht-dabei-viel-staub"&gt;Entsteht dabei viel Staub?&lt;/h4&gt;&#10;&lt;p&gt;Ja. Besorgen Sie sich eine gute Staubabsaugung. Ich würde ein Zyklonsystem oder einen dieser riesigen Textilbeutel empfehlen, die normalerweise bei stationären Sägen verwendet werden. Wenn Sie Ihre Maschine regelmäßig benutzen, reicht die Aufnahmekapazität eines Werkstattsaugers allein möglicherweise nicht aus.&lt;/p&gt;&#10;&lt;p&gt;Außerdem sollten Sie den Staubsauger gut erden (z. B. indem Sie die Erdungsleitung(en) mit dem Erdungspunkt Ihres Schaltkastens verbinden) und antistatische Saugschläuche kaufen, die Sie an die Erdung des Staubsaugers oder des Staubbehälters anschließen können.&#10;Auf diese Weise vermeiden Sie, dass Sie jedes Mal, wenn Sie vakuumbezogene Dinge berühren, einen leichten Schlag bekommen, und Sie können einen schwer auszumachenden Grund dafür ausschließen, dass Ihre Maschine verrückt spielt, z. B. plötzliches Einfrieren der Software, zufälliges Erkennen von Not-Aus-Ereignissen durch die Steuerung, Falschauslösung eines Referenzschalters oder sogar Schluckauf bei Schrittmotoren. Es gibt viele Fälle in verschiedenen Foren, in denen dies die Ursache war oder gewesen sein könnte, z. B. &lt;a href="https://cambamcnc.com/forum/index.php?topic=4524.0" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h4 id="ist-sie-laut"&gt;Ist sie laut?&lt;/h4&gt;&#10;&lt;p&gt;Ja. Der Geräuschpegel ist vergleichbar mit dem einer Handoberfräse. Sie ist schon im Leerlauf laut, und es wird noch schlimmer, wenn sie durch Material fährt. Es könnte also eine gute Idee sein die leiseren, aber teureren Hochfrequenzspindeln mit externer Kühlung zu kaufen. Darüber hinaus sollte (&lt;a href="https://eur-lex.europa.eu/legal-content/DE/TXT/PDF/?uri=CELEX:02006L0042-20091215&amp;amp;rid=1" target="_blank" rel="noopener noreferrer" class="external-link"&gt;und je nach den örtlichen Vorschriften sogar muss&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) Ihre Maschine aus Sicherheitsgründen, aber auch zur Lärmminderung, eine Einhausung erhalten. Sie hilft auch dabei, Feinstaub in der Raumluft zu reduzieren.&lt;/p&gt;&#10;&lt;p&gt;Die Geräuschentwicklung hängt aber auch stark vom verwendeten Fräser ab. Meiner Erfahrung nach klingen kurze Planfräser mit großem Durchmesser und 10-mm-Schaft &lt;a href="https://blog.schallbert.de/my-endmill-screams/"&gt;angenehmer&lt;/a&gt; und sind vielleicht sogar weniger laut als Fräser mit Fischschwanz oder besonders langem Schaft.&lt;/p&gt;&#10;&lt;h4 id="wie-sieht-der-arbeitsablauf-aus"&gt;Wie sieht der Arbeitsablauf aus?&lt;/h4&gt;&#10;&lt;p&gt;Hier ist eine vereinfachte Darstellung des Arbeitsablaufs mit einer CNC-Maschine.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2022-02-12_cncworkflow.jpg" alt="Bild: CNC-Arbeitsablauf in Kürze" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ähnlich wie beim Handwerk beginnt alles mit einer Idee. Die Zeichnungen und Bemaßungen der Teile werden nicht auf einem leeren Blatt Papier, sondern in einem Computer Aided Design-Tool erstellt. Die Zeichnung oder 3D-Modelldatei wird dann in einem Computer Aided Manufacturing Tool weiterverarbeitet, wo geeignete Fräswerkzeuge ausgewählt, die Frässtrategien festgelegt und die Werkzeugwege erzeugt werden. Die resultierende G-Code-Datei wird dann an die eigentliche CNC Software gesendet, die die Schrittmotortreiber steuert. Diese senden Stromimpulse an die Schrittmotoren, die wiederum die Maschine mechanisch verfahren, um das Teil zu fertigen.&lt;/p&gt;&#10;&lt;h3 id="fragen-zum-betrieb"&gt;Fragen zum Betrieb&lt;/h3&gt;&#10;&lt;h4 id="milling-style"&gt;Fräsen im Gleich- oder Gegenlauf?&lt;/h4&gt;&#10;&lt;p&gt;Dies ist eine Entscheidung, die in der CAM-Software getroffen wird.&lt;/p&gt;&#10;&lt;p&gt;Fräsen im Gegenlauf = der Fräser &amp;ldquo;schnitzt&amp;rdquo; in das Material, seine Schneiden drehen sich entgegen der Materialtransportrichtung. Der Gegenlauf wird bei allen Kreissägen verwendet die ich kenne, um Rückschläge zu vermeiden oder das Werkstück auf dem Tisch zu halten.&#10;Gleichlauffräsen = der Fräser &amp;ldquo;frisst&amp;rdquo; sich in das Material, seine Schneiden rotieren mit der Transportrichtung. Der Fräser neigt dazu, sich selbst in das Material zu ziehen.&#10;Weitere Informationen z.B. auf &lt;a href="https://www.harveyperformance.com/in-the-loupe/conventional-vs-climb-milling/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;harveryperformance&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder &lt;a href="https://blog.tormach.com/climb-milling-versus-conventional-milling-sneaky-cnc-tricks" target="_blank" rel="noopener noreferrer" class="external-link"&gt;tormachs blog&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Für welche der beiden Möglichkeiten Sie sich entscheiden sollten, hängt von der Steifigkeit und dem Gewicht Ihrer Maschine, den Drehmoment Ihres Schrittmotors, der Leistung Ihrer Schrittmotorantriebe und dem Material ab, das Sie bearbeiten. Wenn Sie eine steife, schwere, leistungsstarke Maschine (mit Kugelumlaufspindeln) haben und mit eher weichen Materialien arbeiten, sollten Sie das Gleichlauffräsen verwenden. Beobachten Sie Ihre Maschine genau, um zu sehen, ob sich alles glatt anfühlt und anhört (!). Im Zweifelsfall sollten Sie im Gegenlauf fräsen. Arbeiten Sie bei handgeführten Oberfräsen immer den Gegenlauf!&lt;/p&gt;&#10;&lt;p&gt;Nachtrag: Ich fräse Dibond im Gegenlauf mit guten Ergebnissen, Acrylglas und Holz jedoch bearbeite ich im Gleichlauf.&lt;/p&gt;&#10;&lt;h4 id="wie-spannt-man-das-werkstück-schnell-und-einfach-auf"&gt;Wie spannt man das Werkstück schnell und einfach auf?&lt;/h4&gt;&#10;&lt;p&gt;Das hängt wiederum von Ihrem Material ab:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Große Platten: Vakuumtisch oder Anschlag mit Klemmen, die die Platte gegen den Anschlag drücken&lt;/li&gt;&#10;&lt;li&gt;Kleine Platten: Klemmen oder Verschraubung auf dem Fräsbett&lt;/li&gt;&#10;&lt;li&gt;Kleine Blöcke: Schraubstock&lt;/li&gt;&#10;&lt;li&gt;Unregelmäßige Formen: Klemmen, die das Material auf dem Fräsbett niederhalten&lt;/li&gt;&#10;&lt;li&gt;Beidseitiges Fräsen: projektspezifische Vorrichtungen, die das Material in Position halten&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Hmm, die &lt;a href="https://blog.schallbert.de/projects/cnc_spoilboard/"&gt;Opferplatte mit Anschlag&lt;/a&gt; wird wahrscheinlich eins meiner ersten Projekte mit der neuen Maschine.&lt;/p&gt;&#10;&lt;h4 id="mit-welchen-bits--fräsern-sollte-man-beginnen"&gt;Mit welchen Bits / Fräsern sollte man beginnen?&lt;/h4&gt;&#10;&lt;p&gt;Ich würde für den Anfang die folgenden allgemeinen Fräser empfehlen. Sie sollten natürlich in Herstellung, Spiralform, Nutenzahl usw. zum verwendeten Material passen.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Klassischer Schaftfräser mit &lt;code&gt;6mm&lt;/code&gt; oder 1/4&amp;quot;, Spirale mit &lt;code&gt;15-30°&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;2mm&lt;/code&gt; Schaftfräser mit Freischnitt für feines Material oder kleine Löcher&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;60°&lt;/code&gt; und &lt;code&gt;90°&lt;/code&gt; Gravierfräser für Gravuren und Fasen&lt;/li&gt;&#10;&lt;li&gt;Planfräser für großflächiges Fräsen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="mein-schwerpunkt-ist-holz-was-sind-geeignete-vorschübe-und-drehzahlen"&gt;Mein Schwerpunkt ist Holz, was sind geeignete Vorschübe und Drehzahlen?&lt;/h4&gt;&#10;&lt;p&gt;Birkensperrholz ist hart aber nachgiebig. Die geeigneten Werte berücksichtigen immer die Stabilität der Maschine, die Drehmomente der Schrittmotoren und die Spindelleistung.&lt;/p&gt;&#10;&lt;p&gt;Wichtig ist, dass der Fräser Späne erzeugt und keinen Staub. Deshalb sollten niedrige Vorschubgeschwindigkeiten bei hohen Spindeldrehzahlen vermieden werden. Für den Anfang in Holz: Verwenden Sie die Hälfte Ihres Werkzeugdurchmessers als Tiefe pro Durchgang, berechnen Sie die Spindeldrehzahl mit der Formel für Vorschübe und Drehzahlen oder einem Rechner aus dem Internet, und probieren Sie diese aus. Schauen Sie sich die Späne an. Hören Sie der Maschine bei der Arbeit zu. Hört sie sich &amp;ldquo;gut&amp;rdquo; an? Erhöhen Sie die Tiefe pro Durchgang oder die Vorschubgeschwindigkeit allmählich und hören Sie an dem Punkt auf, an dem entweder Ihre Spindel gestresst klingt, Ihr Finish schlechter wird oder Sie sich mit Ihrer Maschine nicht mehr sicher fühlen. Drehen Sie wieder eine Stufe zurück, und das könnte Ihre ideale Vorschubgeschwindigkeit und Durchgangstiefe für das Material und den Fräser sein, den Sie gerade verwenden.&lt;/p&gt;&#10;&lt;p&gt;Für meine Maschine habe ich mich für &lt;code&gt;F3500mm/min&lt;/code&gt; = &lt;code&gt;138in/m&lt;/code&gt; bei &lt;code&gt;S24000U/min&lt;/code&gt; und einer Tiefenzustellung in Höhe des Fräsdurchmessers &lt;code&gt;Z+6mm&lt;/code&gt;, Bahnüberlappung &lt;code&gt;10%&lt;/code&gt; entschieden. Für die Recherche habe ich die folgenden Websites benutzt: &lt;a href="https://www.cnccookbook.com/feeds-speeds-cnc-wood-cutting/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cnccookbook&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://www.cncrechner.de/rechner/vorschub/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cnc-rechner&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sowie Webseiten von Werkzeugherstellern wie &lt;a href="https://webseite.sorotec.de/download/fraesparameter/schnittwerte.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Sorotec&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="weiterlesen"&gt;Weiterlesen?&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/portal-milling-build/"&gt;CNC: Der Aufbau&lt;/a&gt;&#10;&lt;a href="https://blog.schallbert.de/cnc-electronics/"&gt;CNC Elektronik&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Einschaltstrombegrenzer Teil2</title><link>https://blog.schallbert.de/inrushlimiter-circuits/</link><pubDate>Thu, 10 Feb 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/inrushlimiter-circuits/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/inrushlimiter-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Inrush limiter circuit variant"&#10; title="Einschaltstrombegrenzer Teil2" /&gt;&#10;&lt;h2 id="inhalt"&gt;Inhalt&lt;/h2&gt;&#10;&lt;p&gt;In diesem Artikel konzentriere ich mich auf zwei Möglichkeiten, eine Halbleiter-Einschaltstrombegrenzerschaltung zu erstellen: Die eine kopiert den Relais-Ansatz aus &lt;a href="https://blog.schallbert.de/knowledge-inrushlimiters/"&gt;Elektronikwissen: Einschaltstrombegrenzer&lt;/a&gt;, wobei lediglich das Relais durch einen p-Kanal-MOSFET ersetzt und eine besser definierte Zeitverzögerung hinzugefügt wird. Zum anderen stelle ich einen &amp;ldquo;linearen&amp;rdquo; Ansatz vor, bei dem die Stromversorgung schrittweise durch einen PNP-Leistungstransistor eingeschaltet wird.&lt;/p&gt;&#10;&lt;h2 id="motivation-einschaltstrombegrenzer-für-usb-pd"&gt;Motivation: Einschaltstrombegrenzer für USB-PD&lt;/h2&gt;&#10;&lt;p&gt;Da meine Experimente mit &lt;a href="https://blog.schallbert.de/usbpd-explained/"&gt;USB-PD für AnywhereAmps&lt;/a&gt; vorerst fehlgeschlagen sind, muss ich eine Schaltung erstellen, die den Einschaltstrom in meinem Verstärker begrenzt, bevor ich es erneut versuche.&lt;/p&gt;&#10;&lt;h2 id="schaltungen-erläutert"&gt;Schaltungen erläutert&lt;/h2&gt;&#10;&lt;p&gt;Die beiden Schaltungen sind sich sehr ähnlich. Sie unterscheiden sich eigentlich nur in zwei Komponenten des Leistungsteils. Im Allgemeinen wird die Verzögerungszeit durch eine Vergrößerung des Kondensators &lt;code&gt;C4&lt;/code&gt; oder eine Vergrößerung des Ladewiderstands &lt;code&gt;R9&lt;/code&gt; verlängert.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/inrush_circuit.jpg" alt="Image: inrush current limiter sections" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="eingangsbereich-blau"&gt;Eingangsbereich (blau)&lt;/h3&gt;&#10;&lt;p&gt;Der Eingangsteil verfügt über eine &lt;a href="https://blog.schallbert.de/knowledge-tvsdiodes/"&gt;TVS-Diode&lt;/a&gt;, um Verpolung oder Überspannung zu vermeiden.&lt;/p&gt;&#10;&lt;h3 id="verzögerungsabschnitt-gelb"&gt;Verzögerungsabschnitt (gelb)&lt;/h3&gt;&#10;&lt;p&gt;Nach dem Einschalten wird &lt;code&gt;C4&lt;/code&gt; über &lt;code&gt;R9&lt;/code&gt; allmählich aufgeladen. Wenn die Schwellenspannung des NPN-Bipolartransistors &lt;code&gt;Q2&lt;/code&gt; erreicht ist, wird die Spannung hinter &lt;code&gt;R11&lt;/code&gt; langsam reduziert, was wiederum &lt;code&gt;M2&lt;/code&gt;, den Leistungspfad dieser Schaltung, aktiviert. &lt;code&gt;R12&lt;/code&gt; wurde hinzugefügt, um einen stabilen Arbeitspunkt für &lt;code&gt;Q2&lt;/code&gt; zu schaffen.&lt;/p&gt;&#10;&lt;h3 id="leistungsteil-grün"&gt;Leistungsteil (grün)&lt;/h3&gt;&#10;&lt;p&gt;Die lineare Variante hat einen PNP-Leistungstransistor &lt;code&gt;Q1&lt;/code&gt; im Hauptstrompfad (&lt;a href="https://blog.schallbert.de/inrushlimiter-circuits/#schaltungsvergleich"&gt;Bild unten&lt;/a&gt;). Er wird über &lt;code&gt;Q2&lt;/code&gt; aktiviert, und der Strom, den er liefern kann, wird über &lt;code&gt;R10&lt;/code&gt; gesteuert, der in diesem Fall einen relativ niedrigen Wert hat um hohe Emitter-Kollektor-Ströme zu ermöglichen.&lt;/p&gt;&#10;&lt;p&gt;Während die schaltende Variante einen Strombegrenzungswiderstand &lt;code&gt;R8&lt;/code&gt; verwendet, der durch den p-Kanal-Mosfet &lt;code&gt;M2&lt;/code&gt; kurzgeschlossen wird, erfordert seine spannungsgesteuerte Natur keine besondere Sorgfalt bei der Auswahl von &lt;code&gt;R10&lt;/code&gt;. Man sollte nur darauf achten, dass seine (negative) Gate-Schwellenspannung sicher eingehalten wird.&lt;/p&gt;&#10;&lt;h3 id="simulierte-last-violett"&gt;Simulierte Last (violett)&lt;/h3&gt;&#10;&lt;p&gt;Meine Last ist die Endstufenschaltung. Um den Ruhezustand und das Einschaltverhalten zu simulieren, habe ich einen &lt;code&gt;400Ohm&lt;/code&gt;-Widerstand &lt;code&gt;R5&lt;/code&gt; und einen gepolten &lt;code&gt;1200uF&lt;/code&gt;-Kondensator &lt;code&gt;C3&lt;/code&gt; gewählt. Die TVS-Diode schützt vor Überspannung und Rückströmen. Ich habe die Simulationen mit Kondensatorwerten von bis zu &lt;code&gt;10000uF&lt;/code&gt; und Lastwiderständen bis herunter auf &lt;code&gt;40Ohm&lt;/code&gt; ausprobiert, alle mit ähnlichen und guten Ergebnissen.&lt;/p&gt;&#10;&lt;h2 id="schaltungsvergleich"&gt;Schaltungsvergleich&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/inrushlimiter-variants.jpg" alt="Image: Einschaltstrombegrenzer-Varianten im Vergleich" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die dunkelblaue Linie im Diagramm ist wichtig: Sie beschreibt die Ausgangsspannung des Systems im Zeitverlauf. Beide Schaltungen sind so abgestimmt, dass sie die Ziel-Ausgangsspannung nach 0,2 Sekunden erreichen. Dennoch unterscheiden sich die Kurven ziemlich stark, was die unterschiedliche Natur der beiden Ansätze widerspiegelt.&lt;/p&gt;&#10;&lt;h3 id="p-ch-mosfet-ansatz"&gt;P-CH MOSFET Ansatz&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Aufgrund des Schaltvorgangs ist die Einschaltstrombegrenzung zwar wirksam, aber nicht ganz gleichmäßig&lt;/li&gt;&#10;&lt;li&gt;die Leistungsverluste sind bei dieser Variante sehr gering, da sowohl der auslösende NPN-Transistor als auch der Leistungs-MOSFET für sich genommen nicht viel Strom verbrauchen.&lt;/li&gt;&#10;&lt;li&gt;breiter Einsatzbereich für angeschlossene Geräte, der nur durch den maximalen Dauerstrom des MOSFETs begrenzt wird.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="pnp-leistungstransistor-ansatz"&gt;PNP-Leistungstransistor-Ansatz&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;sehr ruhiger Betrieb, die Einschaltzeit kann aufgrund des großen ohmschen Bereichs des PNP sehr gut gesteuert werden&lt;/li&gt;&#10;&lt;li&gt;höhere Leistungsverluste im eingeschalteten Zustand aufgrund des hohen Basisstroms durch &lt;code&gt;R10&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Nicht sehr vielseitig, da hohe Lastströme einen beträchtlichen Spannungsabfall über den Emitter-Kollektor-Pfad von &lt;code&gt;Q1&lt;/code&gt; erzeugen.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="weitere-verbesserungen"&gt;Weitere Verbesserungen&lt;/h3&gt;&#10;&lt;p&gt;Um die Vielseitigkeit dieser Schaltung zu behalten und gleichzeitig eine glatte Spannungskurve zu erreichen, könnte man einen Operationsverstärker verwenden, um den Laststrom zu überwachen und ihn bei Bedarf auf die zulässigen Werte herunterzuregeln. Der Leistungs-MOSFET könnte beibehalten werden, während der Nachteil des engem ohmschen Betriebsbereichs verschwinden würde. Der Preis dafür wäre zusätzliche Komplexität, wie sie &lt;a href="https://electronics.stackexchange.com/questions/408379/current-limiting-source-using-opamp" target="_blank" rel="noopener noreferrer" class="external-link"&gt;in diesem Stackexchange-Beitrag&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; beschrieben wird.&lt;/p&gt;&#10;</description></item><item><title>Energieversorgung per USB</title><link>https://blog.schallbert.de/usbpd-explained/</link><pubDate>Thu, 27 Jan 2022 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/usbpd-explained/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/stusb4500-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: ST&amp;#39;&amp;#39;s EVAL-SCS001V1: STUSB4500 evaluation board"&#10; title="Energieversorgung per USB" /&gt;&#10;&lt;h2 id="usb-power-delivery"&gt;USB Power Delivery&lt;/h2&gt;&#10;&lt;p&gt;USB Power Delivery ist ein Standard innerhalb des USB 3.x-Protokolls, der die Stromversorgung und die Spannungspegel innerhalb des USB-Netzwerks festlegt.&#10;Mit USB-PD ist es möglich, Verbraucher in Reihe zu schalten, die dann automatisch den Strombedarf mit der/den Quelle(n) aushandeln. Der Standard hat verschiedene Versionen, von denen die neueste (2021) bis zu 5 Ampère bei &lt;code&gt;3,3...48V&lt;/code&gt; und maximal 240 Watt Leistung zulässt.&lt;/p&gt;&#10;&lt;h3 id="usb-pd-rollen"&gt;USB-PD-Rollen&lt;/h3&gt;&#10;&lt;p&gt;Im USB-Power-Delivery-Standard gibt es verschiedene Rollen, die die Teilnehmer an den Enden eines USB-Kabels spielen können. Sie können Host oder Client für Datenzwecke und gleichzeitig Stromquelle, Stromsenke oder beides für andere Teilnehmer sein.&lt;/p&gt;&#10;&lt;h3 id="rollenbegriffe"&gt;Rollenbegriffe&lt;/h3&gt;&#10;&lt;p&gt;USB-C verwendet das Akronym &lt;code&gt;DFP&lt;/code&gt; oder &amp;ldquo;Down-Facing Port&amp;rdquo;, um zu verdeutlichen, dass es sich um den Port eines &lt;a href="https://community.infineon.com/t5/Knowledge-Base-Articles/USB-Type-C-and-USB-PD-FAQs/ta-p/679219#." target="_blank" rel="noopener noreferrer" class="external-link"&gt;Host&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; handelt. &lt;code&gt;UFP&lt;/code&gt; oder &amp;ldquo;Up-Facing Port&amp;rdquo; wird für Clients verwendet, die mit Hosts verbunden werden können. Es gibt aber noch eine dritte Rolle: &lt;code&gt;DRP&lt;/code&gt; oder &amp;ldquo;Dual-Role Port&amp;rdquo;. Dieser Anschluss kann entweder als Host oder als Client dienen, je nachdem, wer der Verbindungspartner auf der anderen Seite der Leitung ist und was das USB-Netzwerk aushandelt.&lt;/p&gt;&#10;&lt;p&gt;Im USB-PD-Kontext kann jeder dieser Ports so konfiguriert werden, dass er als Stromquelle oder Stromsenke dient. Beispiel: Obwohl die Rolle des USB-C-Anschlusses eines Laptops auf &amp;ldquo;DFP&amp;rdquo; konfiguriert sein kann, kann er, wenn er an eine Stromquelle angeschlossen ist, immer noch eine Stromsenke sein. Hmm.&lt;/p&gt;&#10;&lt;p&gt;In diesem Beitrag verwende ich nur die &amp;ldquo;UFP&amp;rdquo;-Rolle sowohl im Sinne des USB-C- als auch des USB-PD-Wortes. Also versuche ich, eine Stromsenke für mein Gerät zu konfigurieren, damit es die benötigte Eingangsspannung und den Strom erhält.&lt;/p&gt;&#10;&lt;h3 id="usb-pd-version"&gt;USB-PD Version&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Vor USB-PD Standard 2.0 unterstützten USB-C-Geräte nur sogenannte &lt;em&gt;Power Profiles&lt;/em&gt; mit festen Spannungen von 5V, 12V, 20V und Strömen von bis zu 2A, 3A, oder 5A.&lt;/li&gt;&#10;&lt;li&gt;Zwischen USB-PD Standard 2.0 und USB-PD 3.0 haben &lt;em&gt;Power Delivery Objects&lt;/em&gt; die alten Power Profiles ersetzt, die es erlauben, verschiedene Spannungen/Stromstärken von der Senke auszuwählen &lt;a href="https://usb.org/sites/default/files/D2T2-1%20-%20USB%20Power%20Delivery.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;je nach Angebot der Stromquelle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Die Spannungen sind auf 5V, 9V, 15V, 20V festgelegt, mit Strombegrenzungen von 2.25A, 3A, 5A, je nach Fähigkeit der Quelle und Spannungseinstellung. &lt;code&gt;PDO&lt;/code&gt;s erlauben Fallback-Strategien, d. h. wenn eine Quelle die höchste Leistungsanforderung nicht unterstützen kann, kann die Senke ein anderes &lt;code&gt;PDO&lt;/code&gt; mit geringerer Spannung/Strom auswählen, das sowohl für die Quelle als auch für die Senke geeignet ist.&lt;/li&gt;&#10;&lt;li&gt;Mit USB-PD Standard 3.0 &lt;code&gt;PPS&lt;/code&gt; bietet das Protokoll mit &lt;em&gt;Programmable Power Supply&lt;/em&gt; noch mehr Flexibilität. Hier fordert eine Stromsenke einen Spannungspegel zwischen &lt;code&gt;3,3V&lt;/code&gt; und &lt;code&gt;21V&lt;/code&gt; an, der in &lt;code&gt;20mV&lt;/code&gt;-Schritten und 0,2 bis 5A in &lt;code&gt;50mA&lt;/code&gt;-Schritten wählbar ist, wobei die volle Rückwärtskompatibilität zu den Power Delivery Objects von USB-PD 2.0 erhalten bleibt.&lt;/li&gt;&#10;&lt;li&gt;Der neueste USB-PD Standard 3.1 erweitert sogar die Grenzen der programmierbaren Stromversorgungen und erlaubt 3,3V - 48V und bis zu 5A &lt;a href="https://www.thephonetalks.com/usb-pd-2-0-vs-3-0-vs-3-1/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;mit AVS&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; - &lt;em&gt;Adaptable Voltage Files&lt;/em&gt;. Puh.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Nicht alle Quellen (Steckdosen, Powerbanks usw.) können alle Spannungspegel oder Stromstärken liefern oder haben überhaupt nur den gleichen Standard in der Umsetzung. Aber eine Reihe von Werten ist ziemlich üblich: 5V, 9V, 12V, 15V, 20V. Einige der Produkte, die ich auf dem Markt gesehen habe, bieten bis zu 4A, aber die meisten halten es mit bis zu 3A und Spannungen bis zu 15V sind am häufigsten (im Jahr 2021).&lt;/p&gt;&#10;&lt;h2 id="asics-für-die-stromversorgung"&gt;ASICs für die Stromversorgung&lt;/h2&gt;&#10;&lt;p&gt;ASIC steht für Application Specific Integrated Circuit (anwendungsspezifischer integrierter Schaltkreis), und in diesem Beitrag versuche ich, ASICs mit der Funktion der Stromsenke zu beleuchten, die eigenständig arbeiten können, d. h. ohne ein externes Steuergerät wie einen Mikroprozessor. Ich habe drei Typen dieser ICs gefunden:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;USB-PD-Senke, Spannungs- und Stromeinstellung über eine Widerstandskaskade&lt;/li&gt;&#10;&lt;li&gt;USB-PD-Senke, Spannungs- und Stromkennlinie über den Datenbus programmierbar (&lt;a href="https://en.wikipedia.org/wiki/I%C2%B2C" target="_blank" rel="noopener noreferrer" class="external-link"&gt;I2C&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;/&lt;a href="https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter" target="_blank" rel="noopener noreferrer" class="external-link"&gt;UART&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;/&lt;a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface" target="_blank" rel="noopener noreferrer" class="external-link"&gt;SPI&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) in den nicht-flüchtigen Speicher des ASIC.&lt;/li&gt;&#10;&lt;li&gt;USB-PD-Senke mit integriertem Mikroprozessor, der programmiert werden muss&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Was alle ASICs gemeinsam haben: Sie benötigen periphere Schaltungen, um zu funktionieren - zumindest einige Widerstände, Kondensatoren und Leistungstransistoren, um die Stromversorgung zu schalten und mit angeschlossenen Stromquellen zu verhandeln.&lt;/p&gt;&#10;&lt;h3 id="usb-pd-ist-komplex-versionierung-und-vertragsverhandlungen"&gt;USB-PD ist komplex: Versionierung und Vertragsverhandlungen&lt;/h3&gt;&#10;&lt;p&gt;Sowohl die Abwärtskompatibilität als auch die Skalierbarkeit von USB-PD fordern ihren Tribut - die USB-PD-Spezifikation ist ein mehr als 600 Seiten starkes Monster und es ist nicht immer klar, welche Quelle oder Senke welche Protokollebene unterstützt.&lt;/p&gt;&#10;&lt;p&gt;Da sowohl die Quelle, die Senke als auch das Kabel einen Power-Delivery-Vertrag aushandeln müssen, der auf den Anforderungen der Senke, den Stromversorgungsoptionen der Quelle und den Möglichkeiten des Kabels basiert, kann man sich vorstellen, dass dieser Prozess sehr komplex ist. Dies gilt umso mehr, als dass alle Beteiligten möglicherweise einen anderen USB-PD-Standard, eine andere Revision oder eine andere Version implementiert haben. Kleiner Fakt am Rande: Der Zip-Ordner des USB-Power-Delivery-Spezifikationspakets auf der &lt;a href="https://usb.org/document-library/usb-power-delivery" target="_blank" rel="noopener noreferrer" class="external-link"&gt;offiziellen Seite&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; hat eine Dateigröße von &lt;code&gt;&amp;gt;42MB&lt;/code&gt;. Obwohl ich dieses Thema für sehr interessant halte, habe ich nicht die Zeit, mich in die Einzelheiten zu vertiefen 😅.&lt;/p&gt;&#10;&lt;p&gt;Lassen wir also all dieses Zeug hinter uns und versuchen, eine einfach zu implementierende Lösung zu finden.&lt;/p&gt;&#10;&lt;h3 id="beispiel-asics"&gt;Beispiel-ASICs&lt;/h3&gt;&#10;&lt;p&gt;Ich habe willkürlich drei ICs aus dem Internet ausgewählt, die von verschiedenen Firmen angeboten werden, jeweils einen pro Gerätetyp:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Cypress/Infinions &lt;a href="https://www.infineon.com/cms/en/product/universal-serial-bus-usb-power-delivery-controller/usb-c-and-power-delivery/ez-pd-barrel-connector-replacement-bcr/cypd3177-24lqxqt/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;CYPD3177&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, ein USB-PD-Sink-Controller mit Widerstandskonfiguration&lt;/li&gt;&#10;&lt;li&gt;STMicroelectronics&amp;rsquo; &lt;a href="https://www.st.com/en/interfaces-and-transceivers/stusb4500.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;STUSB4500&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, ein USB-PD-Sink-Controller mit Speicher-Konfiguration&lt;/li&gt;&#10;&lt;li&gt;Microchips &lt;a href="https://www.microchip.com/en-us/product/UPD301C#document-table" target="_blank" rel="noopener noreferrer" class="external-link"&gt;UPD301C&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, ein voll programmierbarer USB-PD-Dual-Role-Controller&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Ich werde mir zuerst den STUSB4500 genauer anschauen.&lt;/p&gt;&#10;&lt;h3 id="chipkrise--ic-knappheit"&gt;Chipkrise / IC-Knappheit&lt;/h3&gt;&#10;&lt;p&gt;Für die Integration in meinen Entwurf wollte ich einige Muster kaufen. Leider war die Chip-Knappheit so groß, dass keiner der Distributoren, bei denen ich normalerweise Elektronikteile bestelle, für das nächste Jahr welche auf Lager hatte.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/digikey_stusb4500.jpg" alt="Image: STUSB4500 bei DigiKey nicht auf Lager" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/st_stusb4500.jpg" alt="Image: STUSB4500 bei ST nicht mehr vorrätig" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/farnell_stusb4500.jpg" alt="Image: STUSB4500 bei Farnell vergriffen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/mouser_stusb4500.jpg" alt="Image: STUSB4500 bei Mouser nicht mehr lieferbar" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Deshalb habe ich mich entschlossen, eine Entwicklungsplatine zu bestellen, auf der der ASIC bereits montiert ist. Das ist zwar 10x teurer als der Chip selbst, aber hier hatte ich keine Wahl.&lt;/p&gt;&#10;&lt;h3 id="stusb4500"&gt;Integration eines STUSB4500&lt;/h3&gt;&#10;&lt;p&gt;Der STUSB4500 verwendet den USB-PD-Standard 2.0 - also nicht den neuesten und besten - aber die Abwärtskompatibilität mit späteren Standards macht ihn zu einem schnellen und einfachen Ausgangspunkt.&#10;Das Gerät wird mit drei Power Delivery Objects vorprogrammiert:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;PDO1 = 5V/1.5A&lt;/li&gt;&#10;&lt;li&gt;PDO2 = 15V/1,5A&lt;/li&gt;&#10;&lt;li&gt;PDO3 = 20V/1A&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Für meine &lt;a href="https://blog.schallbert.de/projects/aa_alpha_1_1/"&gt;AnywhereAmps Alpha 1.1&lt;/a&gt; evolution 1.2 kann ich das Ding also ohne Neuprogrammierung sofort verwenden. Wenn ich die Power-Delivery-Objekte ändern wollte, würde ich ein Arduino-Board mit I2C verwenden und die STUSB4500-Bibliothek installieren, die z. B. unter &lt;a href="https://registry.platformio.org/libraries/ardnew/STUSB4500" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Platformio&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; leicht erhältlich ist. Damit könnte ich sogar die programmierbare Stromversorgung von USB-PD 3.0 aktivieren, wenn ich wollte. Mal sehen, wenn ich die Zeit finde, werde ich einen speziellen Blogeintrag für die Programmierung des STUSB4500 vorbereiten.&lt;/p&gt;&#10;&lt;h3 id="einschalten-des-verstärkers"&gt;Einschalten des Verstärkers&lt;/h3&gt;&#10;&lt;p&gt;OK, also habe ich das Eval-Board an ein 15V-fähiges 30V USB-C-Netzteil angeschlossen. Die violette LED zeigt an, dass 15V auf der Ausgangsschiene verfügbar sind.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/stusb4500_on.jpg" alt="Image Usb-PD Evaluation Board STUSB4500 stock, 15V mode" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Jetzt habe ich meine Verstärkerschaltungen an den Ausgangsklemmen für einen ersten Test angeschlossen. Die STUSB-Auswertungsplatine leuchtet nicht mehr, Verstärker aus. Meh. Wenigstens habe ich einen Hinweis darauf, was die Ursache sein könnte: Es sind &lt;a href="https://blog.schallbert.de/capacitor-problems/"&gt;wieder kapazitive Einschaltströme&lt;/a&gt;! Um Geräte mit großen Kondensatoren mit Strom versorgen zu können, muss ich entweder den Strom im PDO auf abnorm hohe Werte einstellen, oder ich entscheide mich für die Konstruktion einer geeigneten, miniaturisierten Einschaltstrombegrenzerschaltung. Ich habe bereits einen Prototyp aufgebaut, den ich &lt;a href="https://blog.schallbert.de/knowledge-inrushlimiters/"&gt;hier&lt;/a&gt; vorstelle.&lt;/p&gt;&#10;&lt;h2 id="sollten-sie-usb-pd-für-ihre-projekte-verwenden"&gt;Sollten Sie USB-PD für Ihre Projekte verwenden?&lt;/h2&gt;&#10;&lt;p&gt;Die Antwort lautet eindeutig &amp;ldquo;es kommt darauf an&amp;rdquo;. Obwohl die Möglichkeiten von USB-PD großartig sind, gibt es ein paar Nachteile:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Es wird ein zusätzlicher ASIC mit Peripherie benötigt, um die &amp;ldquo;Verträge&amp;rdquo; auszuhandeln. Dies benötigt Platz auf der Leiterplatte und erhöht die Projektkosten erheblich.&lt;/li&gt;&#10;&lt;li&gt;Um vollständig abwärtskompatibel zu sein, ist es sinnvoll, einen ASIC zu kaufen, der sowohl den Standard 3.x unterstützt, als auch idealerweise programmierbar ist.&lt;/li&gt;&#10;&lt;li&gt;Wenn Sie einen programmierbaren Baustein nehmen, gibt es eine zusätzliche Lernkurve für die Softwareentwicklung, die ASIC-spezifischen Bibliotheken, die Sie verwenden werden, und möglicherweise sogar die Notwendigkeit für Programmierhardware&lt;/li&gt;&#10;&lt;li&gt;Sie müssen sicherstellen, dass die Stromversorgung und das Kabel mit den Anforderungen Ihrer USB-PD-Senke kompatibel sind. Zudem sind Kabel für hohe Ströme teuer&lt;/li&gt;&#10;&lt;li&gt;Der USB-C-Anschluss ist miniaturisiert. SMD-Löten und Einsteiger-Lötstationen werden wohl nie Freunde werden, daher solltest etwas Erfahrung in diesem Bereich mitgebracht werden.&lt;/li&gt;&#10;&lt;li&gt;Hochstecker sind viel einfacher in einem Gehäuse zu montieren als eine Platine mit aufgesetzter USB-C-Buchse, die zudem starken mechanischen Belastungen standhalten muss.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Andererseits ist die Verwendung von USB-PD sehr sinnvoll, wenn&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;ein mobiles Gerät betrieben werden soll, das mitgenommen oder häufig an verschiedenen Stromquellen betrieben werden soll&lt;/li&gt;&#10;&lt;li&gt;die Flexibilität batteriebetriebener Geräte gewünscht ist: Einfach eine kompatible Powerbank anschließen und los geht&amp;rsquo;s&lt;/li&gt;&#10;&lt;li&gt;ohnehin eine Datenverbindung zu Ihrem Gerät benötigt wird&lt;/li&gt;&#10;&lt;li&gt;besondere Anforderungen an Ihre Stromversorgung bestehen und ganz bestimmte Spannungen/Ströme geliefert werden sollen&lt;/li&gt;&#10;&lt;li&gt;ein einfacher Überstrom- und Überspannungsschutz von Haus aus gewünscht ist&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Metabo / Cordless Alliance Systems</title><link>https://blog.schallbert.de/no-can-use-metabo/</link><pubDate>Thu, 09 Dec 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/no-can-use-metabo/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/metabo-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Metabo logo, image courtesy of Metabo"&#10; title="Metabo / Cordless Alliance Systems" /&gt;&#10;&lt;h2 id="energiequelle-für-anywhereamps"&gt;Energiequelle für AnywhereAmps&lt;/h2&gt;&#10;&lt;h3 id="status-quo"&gt;Status quo&lt;/h3&gt;&#10;&lt;p&gt;Ich habe Metabo / CAS (&lt;a href="https://cordless-alliance-system.de/de" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Cordless Alliance System&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) Batterien für beide AnywhereAmps &lt;a href="https://blog.schallbert.de/projects/aa_alpha/"&gt;Alpha 1. 0&lt;/a&gt; und &lt;a href="https://blog.schallbert.de/projects/aa_alpha_1_1/"&gt;Alpha 1.1&lt;/a&gt;. Letzteres verwendet bereits den Power Supervisor IC &lt;a href="https://blog.schallbert.de/configure-max1756x/"&gt;MAX17562&lt;/a&gt;, der sowohl die Batterie als auch die Schaltkreise vor Verpolung, Überstrom und Unterspannung schützt.&lt;/p&gt;&#10;&lt;h3 id="problem"&gt;Problem&lt;/h3&gt;&#10;&lt;p&gt;Obwohl die Verwendung des Power-Supervisor-ICs ein großer Fortschritt war, wäre es noch besser, wenn ich den &lt;a href="https://en.wikipedia.org/wiki/State_of_charge" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Ladezustand der Batterie&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; direkt ablesen könnte. Dies würde nicht nur einen sicheren Betrieb gewährleisten, sondern auch ein automatisches Abschalten des Verstärkers ermöglichen, wenn die Batterie schwach wird.&lt;/p&gt;&#10;&lt;h2 id="diskussion-mit-metabocas"&gt;Diskussion mit Metabo/CAS&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/metabo.jpg" alt="Metabo logo" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Metabo logo&lt;/span&gt;&lt;a href="https://www.metabo.com/de/de/info/aktuell/media/logos/" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Deshalb habe ich mich an das Metabo-CAS-Team gewandt und gefragt, ob ich Unterstützung und die Spezifikationen für die Kommunikation mit dem Akkupack bekommen kann.&#10;Zu meiner großen Freude reagierten sie sehr schnell, und schon bald befand ich mich in einem Telefongespräch um die Angelegenheit zu klären.&lt;/p&gt;&#10;&lt;h3 id="ergebnis"&gt;Ergebnis:&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Die Verwendung von CAS-Akkus in anderen Geräten als der Elektrowerkzeug-Familien der CAS gilt als &amp;ldquo;Hack&amp;rdquo; und wird daher von Metabo nicht genehmigt.&lt;/li&gt;&#10;&lt;li&gt;Metabo ist daran interessiert, dass Hersteller von Akku-Werkzeugen ihrer Akku-Allianz beitreten, aber meine Anwendung in einem Verstärker ist einfach zu weit weg (Metabos Slogan war einmal &lt;em&gt;&amp;ldquo;Work, don&amp;rsquo;t play&amp;rdquo;&lt;/em&gt;)&lt;/li&gt;&#10;&lt;li&gt;Metabo würde von mir erwarten, dass ich mehr als 10.000 Einheiten pro Jahr produziere, bevor ich der Allianz beitreten kann - was einfach unrealistisch ist.&lt;/li&gt;&#10;&lt;li&gt;Ich müsse dies verstehen, denn die gemeinsame Nutzung der Spezifikationen und die Erlaubnis, die Schnittstellen zu nutzen, würde einen Teil von Metabos Ressourcen in Anspruch nehmen, während sie keine Rendite erwarten könnten&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;So enttäuschend 😔 dies auch ist, kam es nicht völlig unerwartet. Der Ingenieur am Telefon nahm sich Zeit für mich. Er sagte mir, dass sie meine Begeisterung für ihre Batteriesysteme sehr zu schätzen wüssten und dass ich mich nicht entmutigen lassen solle.&lt;/p&gt;&#10;&lt;p&gt;Nun ja. Sprechen wir über eine&lt;/p&gt;&#10;&lt;h2 id="alternative"&gt;Alternative.&lt;/h2&gt;&#10;&lt;p&gt;Ich kenne nur einen weiteren Standard, der alle meinen Ansprüchen gerecht zu werden scheint:&lt;/p&gt;&#10;&lt;h3 id="anforderungen"&gt;Anforderungen&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&amp;ldquo;offen genug&amp;rdquo; für meinen Gebrauch&lt;/li&gt;&#10;&lt;li&gt;sicher&lt;/li&gt;&#10;&lt;li&gt;erfüllt die Anforderungen an Spannung und Stromstärke für meinen Zweck&lt;/li&gt;&#10;&lt;li&gt;weithin verfügbar&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/USB_Type-C_Charging_Logo.svg/330px-USB_Type-C_Charging_Logo.svg.png" alt="USB-PD logo" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;USB-PD logo&lt;/span&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:USB_Type-C_Charging_Logo.svg#file" class="attr-link" aria-label="Attribution 2"&gt;&#10; &lt;sup class="attr-id"&gt;[2]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="usb-power-delivery"&gt;USB Power Delivery&lt;/h3&gt;&#10;&lt;p&gt;Der Standard heißt &lt;a href="https://en.wikipedia.org/wiki/USB_hardware#USB_power_delivery" target="_blank" rel="noopener noreferrer" class="external-link"&gt;USB Power Delivery&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Obwohl ich schon früher darüber nachgedacht habe, wollte ich ihn nie wirklich nutzen, da er einen enormen zusätzlichen Entwicklungsaufwand für mich bedeutet.&lt;/p&gt;&#10;&lt;p&gt;USB Power Delivery verwendet ein komplexes Protokoll, um einen &amp;ldquo;Vertrag&amp;rdquo; zwischen Quell- und Senkengerät zu schließen. All dies muss designt, ein spezieller IC ausgewählt und Schaltungen müssen dafür entworfen werden, damit es mit meinem Setup funktioniert.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-12-09_st_stusb4500.jpg" alt="Image: STUSB4500 Bildbeispiel für USB-PD-Sink-IC" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;😱 Oh nein, sie sind nicht mehr auf Lager!&lt;/p&gt;&#10;&lt;p&gt;Ich werde einen separaten Blog-Beitrag schreiben, sobald ich dieses neue Thema in den Griff bekommen habe.&lt;/p&gt;&#10;</description></item><item><title>Konfiguration des IC MAX1756x</title><link>https://blog.schallbert.de/configure-max1756x/</link><pubDate>Sat, 04 Dec 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/configure-max1756x/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-12-04_max1756x-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Max1756x typicall application, image courtesy of Maxim Integrated"&#10; title="Konfiguration des IC MAX1756x" /&gt;&#10;&lt;h2 id="power-supervisor-konfiguration-leicht-gemacht"&gt;Power Supervisor Konfiguration leicht gemacht&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-12-04_max17562.jpg" alt="Image: MAX17562 auf meiner Leiterplatte für AnywhereAmps" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich verwende MAX17562 ICs in meinem Design für &lt;a href="https://blog.schallbert.de/anywhereamps-get-pcb/"&gt;AnywhereAmps Alpha 1.1&lt;/a&gt;. Daher habe ich eine kurze Zusammenfassung der MAX1756x-Funktionen und deren Konfiguration erstellt 🙂.&lt;/p&gt;&#10;&lt;p&gt;Das Datenblatt ist die Grundlage für alle Informationen, die ich hier bereitstelle. Ich füge lediglich einige Überlegungen zur Gestaltung, Details zu meiner Art der Nutzung und Anwendungsvorschläge hinzu.&lt;/p&gt;&#10;&lt;h3 id="was-ist-dieser-chip-nochmal"&gt;Was ist dieser Chip nochmal?&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://www.maximintegrated.com/en/products/power/protection-control/protection-ics/MAX17561.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;MAX1756x&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist ein einstellbarer Überspannungs-/Unterspannungs- und Überstromschutz-IC. Er ist in drei verschiedenen Versionen für die Behandlung von Überstromereignissen erhältlich:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;MAX17561: Schaltet sich bei einem Überstromereignis aus und stellt sich nach typischerweise &lt;code&gt;600ms&lt;/code&gt; selbsttätig ab.&lt;/li&gt;&#10;&lt;li&gt;MAX17562: Schaltet sich bei einem Überstromereignis ab; Neustart nach Freigabezyklus&lt;/li&gt;&#10;&lt;li&gt;MAX17563: Setzt den normalen Betrieb mit aktivem Strombegrenzer fort (führt Wärme ab)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Beide Versionen begrenzen Überstromereignisse auf die konfigurierte Stromgrenze.&lt;/p&gt;&#10;&lt;h3 id="mein-max1756x-anwendungstool"&gt;Mein MAX1756x-Anwendungstool&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-12-04_spreadsheet.jpg" alt="Image: Max1756x-Anwendungsrechner-Tabelle" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Um die Verwendung des Max1756x IC in Ihrer Anwendung zu vereinfachen, können Sie meine &lt;a href="https://github.com/Schallbert/AnywhereAmps/blob/master/Preamp/max1756x_application_calculator.xlsx" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Max1756x Kalkulationstabelle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verwenden.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;h3 id="max1756x-strombegrenzer--überlastdetektor"&gt;MAX1756x Strombegrenzer / Überlastdetektor&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-12-04_seti.jpg" alt="SETI resistor configuration from MAX1756x datasheet" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Courtesy of Maxim Integrated: SETI resistor config&lt;/span&gt;&lt;a href="https://www.analog.com/media/en/technical-documentation/data-sheets/MAX17561-MAX17563.pdf" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Der IC verfügt über einen einstellbaren Strombegrenzer und eine Überstromerkennung (&lt;code&gt;SETI&lt;/code&gt;). Wenn der Strom über den &lt;code&gt;SETI&lt;/code&gt;-Schwellenwert ansteigt, wird er auf den eingestellten Wert begrenzt. Für MAX17561 und MAX17562: Bleibt diese Bedingung länger als die typische &lt;code&gt;20.7ms&lt;/code&gt; Tastzeit bestehen, schaltet der Baustein den Lastkreis ab. Da der MAX17563 seinen normalen Betrieb fortsetzt, kann seine Verlustleistung irgendwann zu einer Übertemperaturabschaltung führen.&lt;/p&gt;&#10;&lt;p&gt;So konfiguriert man den Strombegrenzer / die Überlasterkennung über den Eingang &lt;code&gt;SETI&lt;/code&gt;:&#10;Einen Widerstand entsprechend der Kurve auf der rechten Seite (aus dem Datenblatt) auswählen und verbinden mit &lt;code&gt;SETI&lt;/code&gt; und &lt;code&gt;GND&lt;/code&gt; verbinden. Für AnywhereAmps Alpha 1.2 verwende ich einen &lt;code&gt;3k3&lt;/code&gt;-Widerstand, um eine Stromgrenze von &lt;code&gt;~3.5A&lt;/code&gt; zu erreichen.&lt;/p&gt;&#10;&lt;h3 id="max1756x-unterspannungskonfiguration"&gt;MAX1756x Unterspannungskonfiguration&lt;/h3&gt;&#10;&lt;p&gt;Der IC verfügt über eine Unterspannungsabschaltung (&lt;code&gt;UVLO&lt;/code&gt;). Wenn die Spannung für etwa &lt;code&gt;16,7ms&lt;/code&gt; unter den &lt;code&gt;UVLO&lt;/code&gt;-Schwellenwert sinkt, schalten die Leistungstransistoren des Bausteins den Lastkreis ab. Intern vergleicht der IC die Eingangsspannung am &lt;code&gt;UVLO&lt;/code&gt;-Pin mit der Referenzspannung &amp;ldquo;Vbg = 1,2V&amp;rdquo;. Um den Schwellenwert für die Unterspannungsabschaltung einzustellen, verwenden Sie einen Spannungsteiler, um den &lt;code&gt;UVLO&lt;/code&gt;-Pegel während des normalen Betriebs über &lt;code&gt;Vbg&lt;/code&gt; zu halten.&lt;/p&gt;&#10;&lt;h3 id="max1756x-überspannungskonfiguration"&gt;MAX1756x Überspannungskonfiguration&lt;/h3&gt;&#10;&lt;p&gt;Der IC verfügt über eine Überspannungsabschaltung (&lt;code&gt;OVLO&lt;/code&gt;). Wenn die Spannung für etwa &lt;code&gt;16,7ms&lt;/code&gt; über den &lt;code&gt;OVLO&lt;/code&gt;-Schwellenwert steigt, schalten die Leistungstransistoren des Bausteins den Lastkreis ab.&#10;Die Funktionsweise ist identisch mit der Unterspannungsabschaltung, mit dem einzigen Unterschied, dass die Spannung am &lt;code&gt;OVLO&lt;/code&gt;-Pin unter normalen Betriebsbedingungen unter &lt;code&gt;Vbg&lt;/code&gt; bleiben sollte.&lt;/p&gt;&#10;&lt;h3 id="max1756x-verpolschutz"&gt;MAX1756x Verpolschutz&lt;/h3&gt;&#10;&lt;p&gt;Der Verpolschutz des ICs kann durch Ziehen des &lt;code&gt;*RIEN&lt;/code&gt;-Pins nach &lt;code&gt;GND&lt;/code&gt; aktiviert werden. Soll diese Funktion genutzt werden ist zu beachten, dass ungepolte Bypass-Kondensatoren am Eingang des ICs verwendet werden müssen. Nutzt man &lt;a href="https://blog.schallbert.de/capacitor-problems/#explosion"&gt;wie ich es getan habe&lt;/a&gt; Elkos, so explodieren diese, sollte die Betriebsspannung verpolt werden.&lt;/p&gt;&#10;&lt;h3 id="fehler-flag-ausgang"&gt;Fehler *FLAG Ausgang&lt;/h3&gt;&#10;&lt;p&gt;Der MAX1756x zeigt unter den folgenden Bedingungen einen Stromversorgungsfehler über seinen &lt;code&gt;*FLAG&lt;/code&gt;-Port an:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Thermische Überlast&lt;/li&gt;&#10;&lt;li&gt;Überspannungsereignis definiert durch &lt;code&gt;OVLO&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Verpolung: wenn der Verpolschutz &lt;code&gt;*RIEN&lt;/code&gt; aktiviert wurde&lt;/li&gt;&#10;&lt;li&gt;Überlaststrom, definiert durch &lt;code&gt;SETI&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="empfehlung-tvs-dioden-verwenden"&gt;Empfehlung: TVS-Dioden verwenden&lt;/h3&gt;&#10;&lt;p&gt;Wie das Datenblatt empfiehlt und &lt;a href="https://blog.schallbert.de/capacitor-problems/#inductance"&gt;meine Erfahrung aus früheren Vorfällen&lt;/a&gt; zeigt, würde ich &lt;code&gt;27V&lt;/code&gt; &lt;a href="https://blog.schallbert.de/knowledge-tvsdiodes/"&gt;Transient Voltage Suppressor diodes&lt;/a&gt; parallel zu den beiden Eingangs- und Ausgangsports des MAX1756x verwenden. Sie schützt (alle induktiven) Lastschaltungen vor negativen Spannungsspitzen beim Abschalten und den IC selbst vor Überspannungen beim Anschluss einer spannungsführenden Stromversorgung. Außerdem begrenzt er die Eingangsspannung in umgekehrter Richtung auf unbedenkliche Werte.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-12-04_tvs.jpg" alt="Image: TVS-Verwendung in MAX1756x-Schaltung" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h3 id="empfehlung-2-verwenden-sie-nicht-polarisierte-eingangskondensatoren"&gt;Empfehlung 2: Verwenden Sie nicht polarisierte Eingangskondensatoren&lt;/h3&gt;&#10;&lt;p&gt;Um Probleme beim Neustart des IC zu vermeiden, wenn er an &amp;ldquo;schwache&amp;rdquo; Stromversorgungen oder hochohmige Eingangsleitungen angeschlossen ist, sollten größere nicht-polarisierte Eingangskondensatoren als üblich verwendet werden (nein, &amp;ldquo;100nF&amp;rdquo; sind eher nicht genug!) - insbesondere, wenn kapazitive oder andere Lasten betrieben werden, die Eingangsstoßströme verursachen.&lt;/p&gt;&#10;&lt;p&gt;Für weitere Informationen zur Vermeidung von Problemen in diesem Bereich bitte in meinen Blog-Eintrag über &lt;a href="https://blog.schallbert.de/knowledge-inrushlimiters/"&gt;Einschaltstrombegrenzer&lt;/a&gt; weiterstöbern.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Zu beachten: Obwohl ich das Datenblatt gründlich gelesen und die Daten sorgfältig hinzugefügt habe, kann ich natürlich keinerlei Garantie für die Ergebnisse übernehmen, die Sie mit diesem Tool erzielen.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Einschaltstrombegrenzer - Teil1</title><link>https://blog.schallbert.de/knowledge-inrushlimiters/</link><pubDate>Sun, 14 Nov 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/knowledge-inrushlimiters/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-14_bulb-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Electrical light bulb, 12V 5W"&#10; title="Einschaltstrombegrenzer - Teil1" /&gt;&#10;&lt;h2 id="elektronikwissen-einschaltstrombegrenzer"&gt;Elektronikwissen: Einschaltstrombegrenzer&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2021-11-14_inrush_current_limiter_relay.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt; Einschaltstrombegrenzer in Aktion.&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="was-ist-das"&gt;Was ist das?&lt;/h3&gt;&#10;&lt;p&gt;Ein &lt;a href="https://en.wikipedia.org/wiki/Inrush_current" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Einschaltstrom&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist der Stromstoß, der direkt nach dem Einschalten durch einen Stromkreis fließt, bis der Stromkreis seinen stabilen Zustand erreicht hat. Hohe Einschaltströme sind oft unerwünscht, da sie Schaltungsteile überlasten und deren Lebensdauer verkürzen können, elektromagnetische Störungen verursachen und einen kurzzeitigen Spannungsabfall bewirken, der zu ständigen Neustarts eines gesteuerten Geräts &lt;a href="https://blog.schallbert.de/capacitor-problems/"&gt;wie hier beschrieben&lt;/a&gt; oder zu anderem unerwünschten Verhalten führen kann.&lt;/p&gt;&#10;&lt;h3 id="beispiel"&gt;Beispiel&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://upload.wikimedia.org/wikipedia/commons/f/f9/Lamp_inrush_current.png" alt="Wikipedia: inrush current of an incandescent light bulb" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Oszillograf: Einschaltstrom einer Lampe mit Glühfaden&lt;/span&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:Lamp_inrush_current.png#file" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Glühlampe: Die rosa Linie ist die Ausgangsspannung des Netzteils, die gelbe Linie zeigt den Strom. Man kann erkennen, dass das Netzteil die Ausgangsspannung verringert, um die Stromaufnahme während des Starts zu reduzieren. Nach etwa &lt;code&gt;100ms&lt;/code&gt; stabilisiert sich die Spannung, da der Stromfluss durch die Lampe immer noch ein wenig abnimmt, während der Glühfaden langsam seine Temperatur erreicht.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-14_bulb_resistance.jpg" alt="Image: Measuring the resistance of an incandescent light bulb" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h3 id="hintergrund"&gt;Hintergrund&lt;/h3&gt;&#10;&lt;p&gt;Der Glühfaden des Leutmittels wirkt wie ein Widerstand mit positivem Temperaturkoeffizienten (PTC): Bei Raumtemperatur hat er einen sehr niedrigen Widerstand, bei seiner normalen Betriebstemperatur von &lt;a href="https://hypertextbook.com/facts/1999/AlexanderEng.shtml" target="_blank" rel="noopener noreferrer" class="external-link"&gt;~3300K&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; einen viel höheren.&#10;Ein kleines Experiment beweist dies:&#10;Nehme ich eine &amp;ldquo;116W&amp;rdquo;-Halogenglühbirne, so beträgt ihr Widerstand im &amp;ldquo;eingeschwungenen Zustand&amp;rdquo; bei &lt;code&gt;230V&lt;/code&gt; etwa \(R_{steady} = \frac{U^2}{P} = 456{Ohm}\).&#10;Wenn ich den Widerstand der Lampe im kalten Zustand mit einem Multimeter messe, liegt der Wert bei \(R_{off} = 38.6{Ohm}\).&#10;Während durch die Glühlampe im eingeschalteten Zustand ein Strom von \(I_{steady} = 0.5A\) fließt, liegt ihr Einschaltstrom bei etwa \(I_{inrush} = 6A\) - also zwölf mal so hoch!&lt;/p&gt;&#10;&lt;h2 id="einschaltstrombegrenzer-schaltungen"&gt;Einschaltstrombegrenzer-Schaltungen&lt;/h2&gt;&#10;&lt;p&gt;Einschaltstrombegrenzer werden häufig in Geräten mit großen induktiven oder kapazitiven Lasten eingesetzt.&#10;&lt;a href="https://www.ti.com/lit/an/slvae30e/slvae30e.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Beispiele (Link zu Texas Instruments)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sind Transformatoren, Motoren (induktiv), Leistungsverstärker, Schaltnetzteile (kapazitiv) usw.&lt;/p&gt;&#10;&lt;p&gt;Es gibt &lt;a href="https://e2e.ti.com/blogs_/b/powerhouse/posts/powertips-how-to-limit-inrush-current-in-an-ac-dc-power-supply" target="_blank" rel="noopener noreferrer" class="external-link"&gt;verschiedene Arten von Einschaltstrombegrenzern (TI application note)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit unterschiedlicher Komplexität.&lt;/p&gt;&#10;&lt;h3 id="widerstand-mit-negativem-temperaturkoeffizient-ntc--heißleiter---thermistor"&gt;Widerstand mit negativem Temperaturkoeffizient (NTC) / Heißleiter / Thermistor&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/ntc.jpg" alt="Image: Elektrisches Symbol eines Thermistors mit negativem Temperaturkoeffizienten" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ein Einschaltstrombegrenzer als NTC-Widerstand in Reihe zur Last ist die einfachste Schaltung in dieser Aufzählung. Sie besteht aus nur einem zusätzlichen Bauteil. Das Verhalten des NTC-Widerstands gegenüber der Temperatur ist dem des obigen Glühbirnenbeispiels entgegengesetzt: Sein Widerstand nimmt mit steigender Temperatur ab, so dass mehr Strom durch den Stromkreis fließen kann.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-14_NTC.jpg" alt="Image: NTC curve example" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Bei den meisten NTCs verhält sich der Widerstand logarithmisch zur Temperatur, er sinkt also sehr stark bei zunehmender Temperatur. So kann er Einschaltstrompulse sehr effektiv unterdrücken. Nachteilig ist der Eigenenergiebedarf, der den Widerstand auf Betriebstemperatur hält.&#10;Diese Schaltung wird dort verwendet, wo geringe Kosten wichtig sind, das System einfach gehalten werden soll und der zusätzliche Energieverbrauch kein Problem darstellt.&lt;/p&gt;&#10;&lt;h3 id="widerstand-mit-bypass-relais"&gt;Widerstand mit Bypass-Relais&lt;/h3&gt;&#10;&lt;p&gt;Diese Schaltung vermeidet die Nachteile eines zusätzlichen Serienwiderstands zur Last durch Hinzufügen eines Leistungswiderstands und eines Relais. Sie wird typischerweise in Gleichstromversorgungen mit hoher Leistung oder empfindlichen Geräten wie Verstärkern verwendet.&#10;Der Relaiskontakt ist im unbestromten Zustand offen und parallel zum Leistungswiderstand verdrahtet, der je nach Last einen Wert von typischerweise &amp;ldquo;1&amp;hellip;22 Ohm&amp;rdquo; hat. Dahinter wird die Last angeschlossen, die den Einschaltstrom verursacht. Für dieses Beispiel nehme ich die Glühbirne eines Autos als Last. Beim Einschalten des Geräts begrenzt der Widerstand den Stromkreis und die Spannung auf der Lastseite steigt allmählich an. Sobald die Spannung an der Basis des NPN-&amp;ldquo;BC337&amp;rdquo;-Transistors hoch genug ist, schaltet er sich ein und lässt Strom durch die Relaisspule fließen, die dann den Leistungswiderstand kurzschließt, so dass die Last ihren Nennstrom ziehen kann.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-14_relay.jpg" alt="Image: Circuit plan for a start-up relay inrush current limiter" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Werte von &lt;code&gt;R2, R3&lt;/code&gt; hängen von der Spannung hinter &lt;code&gt;R1&lt;/code&gt; ab, wenn sich die Schaltung bei ausgeschaltetem Relais im eingeschwungenen Zustand befindet. Des Weiteren ist die Charakteristik des Transistors und der Betriebsstrom der Relaisspule zu berücksichtigen. In meinem Beispiel wählte ich &lt;code&gt;R2 = 1kOhm&lt;/code&gt;, da der Transistor &lt;code&gt;~40mA&lt;/code&gt; für den Betrieb des Relais durchlassen muss. Diesen Wert habe ich dem Datenblatt des &lt;code&gt;BC337&lt;/code&gt;, Parameter Stromverstärkung, entnommen. Da ich das Relais nicht zu früh schalten lassen wollte, habe ich &lt;code&gt;R3&lt;/code&gt; hinzugefügt. Je höher sein Widerstand, desto früher wird das Relais aktiv. Ein zu geringer Widerstand hingegen reduziert die Basisspannung des &lt;code&gt;BC337&lt;/code&gt; so weit, dass er das Relais nicht mehr treiben kann - hier muss ein wenig experimentiert werden.&lt;/p&gt;&#10;&lt;p&gt;In diesem &lt;a href="https://www.elektronik-kompendium.de/public/schaerer/onilim.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anwendungsbeispiel für den &amp;ldquo;Soft Start&amp;rdquo; eines Ringtrafos (Elektronik-Kompendium)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; kommt eine ähnliche Schaltung zum Einsatz.&lt;/p&gt;&#10;&lt;p&gt;Und so verhält sich der Schaltkreis in der Realität:&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-14_relay_circuit.jpg" alt="Image: Start-up relay circuit on a breadboard" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich habe mir die Freiheit genommen, einen Schalter hinzuzufügen, damit die Steuerung der Stromversorgung für mich bequemer wird. Das Relais ist mit einer Freilaufdiode ausgestattet, um den Transistor vor negativen Spannungsspitzen zu schützen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-14_compare_inrush.jpg" alt="Image: Comparison of inrush currents with and without limiter" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das Problem des Einschaltstroms ist tatsächlich gelöst. Der Vergleich (gelbe Linie) zeigt einen ungeregelten Einschaltstrom von &lt;code&gt;3.5A&lt;/code&gt;, während der Begrenzer ihn auf &lt;code&gt;0.8A&lt;/code&gt; reduziert, was nur knapp über dem Nennstrom liegt. Ein Leistungswiderstand mit kleinerem Wert hätte diesen Strom noch weiter reduziert.&lt;/p&gt;&#10;&lt;p&gt;Die grüne Linie zeigt die Basisspannung des &lt;code&gt;BC337&lt;/code&gt; an. Man erkennt, dass sie langsam ansteigt (die Genauigkeit des Oszilloskops ist bei &lt;code&gt;40VPP&lt;/code&gt; nicht die beste) und das Relais einschaltet, wenn sich der Stromfluss (gelbe Linie) stabilisiert hat.&lt;/p&gt;&#10;&lt;h3 id="sanftanlauf--konfigurierbare-strombegrenzung"&gt;Sanftanlauf / konfigurierbare Strombegrenzung&lt;/h3&gt;&#10;&lt;p&gt;Es gibt spezielle ICs, die den Sanftanlauf eines Systems unterstützen, je nach&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Leistungsbedarf&lt;/li&gt;&#10;&lt;li&gt;Hochlaufzeit&lt;/li&gt;&#10;&lt;li&gt;Beschränkungen der Anstiegsgeschwindigkeit&lt;/li&gt;&#10;&lt;li&gt;Maximalstrom&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Darüber hinaus gibt es verschiedene Arten von Einschaltstrombegrenzern, die je nach Anwendung ausgewählt werden können:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Zeitgesteuert: Das System wird nach einer bestimmten Zeit vollständig eingeschaltet (einfach, aber dumm)&lt;/li&gt;&#10;&lt;li&gt;Spannungsgesteuert: Das System wird oberhalb eines Spannungsauslösepegels vollständig eingeschaltet (immer noch einfach, mittlere Kosten)&lt;/li&gt;&#10;&lt;li&gt;Stromgesteuert: Begrenzt den Systemstrom oder schaltet das System auf der Grundlage eines Stromauslösepegels vollständig ein (wahrscheinlich teurer)&#10;Hinsichtlich der Anforderungen des Systems muss dann ein passender IC gewählt werden. Typische Namen sind hier &lt;code&gt;Power Supervisors&lt;/code&gt;, &lt;code&gt;IC soft starter&lt;/code&gt;, &lt;code&gt;Supervisor &amp;amp; Reset IC&lt;/code&gt; oder ähnlich. Sie können intern sogar mit Leistungstransistoren ausgestattet sein und selbsttätig schalten, wie z.B. &lt;a href="https://www.analog.com/en/products/MAX17562.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;MAX17562 (Analog Devices)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für meine Evolution von &lt;a href="https://blog.schallbert.de/anywhereamps-get-pcb/#power-supervisor"&gt;AnywhereAmps&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Weiterlesen: &lt;a href="https://blog.schallbert.de/inrushlimiter-circuits/"&gt;Einschaltstrombegrenzer - Teil2&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>TVS-Dioden</title><link>https://blog.schallbert.de/knowledge-tvsdiodes/</link><pubDate>Sat, 13 Nov 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/knowledge-tvsdiodes/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-02_tvs-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Typical TVS voltage diagram"&#10; title="TVS-Dioden" /&gt;&#10;&lt;h2 id="elektronikwissen-zur-tvs-diode"&gt;Elektronikwissen zur TVS-Diode&lt;/h2&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/tvs_diode.jpg" alt="Image: Electric symbol for TVS diodes" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="was-ist-das"&gt;Was ist das?&lt;/h3&gt;&#10;&lt;p&gt;Eine Transient voltage suppressor diode schützt eine Schaltung vor Spannungsspitzen, die deutlich über der Nennspannung des Schaltkreises liegen. Sie sind für verschiedene Auslösespannungen und Ableitströme verfügbar. Zudem gibt es unterschiedliche Typen, wovon einige sogar sowohl vor positiven als auch vor negativen Spannungsspitzen schützen können.&lt;/p&gt;&#10;&lt;h3 id="unterschiede-zwischen-tvs--und-zenerdioden"&gt;Unterschiede zwischen TVS- und Zenerdioden&lt;/h3&gt;&#10;&lt;p&gt;Wie &lt;a href="https://en.wikipedia.org/wiki/Zener_diode" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Zener-Dioden&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, werden TVS-Dioden in Sperrrichtung eingebaut. Sie haben sogar &lt;a href="https://electronics.stackexchange.com/questions/233600/differences-between-tvs-diode-and-zener-diodes-in-diagrams-and-in-practice" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dasselbe Symbol&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Doch währen Zenerdioden ihren Arbeitspunkt sehr nahe der Durchbruchspannung haben, werden TVS-Dioden üblicherweise weit darunter betrieben. Somit &lt;a href="https://toshiba.semicon-storage.com/content/dam/toshiba-ss-v2/master/en/semiconductor/knowledge/e-learning/discrete/discrete-basic-chap2_en.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;schließen sie Überspannungsspitzen kurz (Link zu Toshiba)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; auf für den Schaltkreis ungefährliche Spannungen und sind dafür ausgelegt, sehr hohe Pulsströme ableiten zu können.&lt;/p&gt;&#10;&lt;h3 id="abgrenzung-zwischen-tvs--and-schottkydioden"&gt;Abgrenzung zwischen TVS- and Schottkydioden&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/schottky_diode.jpg" alt="Image: Schottky diode electronics symbol" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Das Symbol für &lt;a href="https://en.wikipedia.org/wiki/Schottky_diode" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Schottkydioden&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; unterscheidet sich nur geringfügig von Zener/TVS-Dioden. Sie haben in der Regel eine niedrigere Durchlassspannung als ihre Siliziumpendants und sind für ihre schnelle Schaltgeschwindigkeit bekannt. Deshalb werden sie neben Gleichrichterschaltungen auch häufig in Verpolungsschutzschaltungen eingesetzt. TVS-Dioden haben das gleiche schnelle Schaltverhalten und die niedrige Vorwärtsspannung, aber im Gegensatz zu Schottkys haben sie einen genau definierten Schwellenwert für die Sperrspannung und vertragen besonders hohe Pulsströme.&lt;/p&gt;&#10;&lt;h3 id="auswahl-der-richtigen-tvs-diode"&gt;Auswahl der richtigen TVS-Diode&lt;/h3&gt;&#10;&lt;p&gt;Für Gleichstromanwendungen sollte eine unidirektionale TVS-Diode verwendet werden. Ihre Durchbruchspannung ist so zu wählen, dass sie etwas über der normalen Betriebsspannung liegt. Des Weiteren ist die Klemmspannung relevant, die weiter &amp;ldquo;unten&amp;rdquo; auf der Linie des zugehörigen Diagramms abzulesen ist. Liegt sie immer noch unter den &amp;ldquo;absoluten Maximalwerten&amp;rdquo; Ihrer zu schützenden Schaltung Schaltung? Wenn nicht ist eine andere TVS-Diode zu wählen oder ein Kompromiss bei der Spannung einzugehen (dabei nicht die Durchbruchspannung des TVS überschreiten). Alternativ kann auch eine andere Vorrichtung zur Unterdrückung von Spannungsspitzen wie einen spannungsabhängiger Widerstand oder ähnliches verwendet werden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-02_tvs_diagram.jpg" alt="Bild: Diagramm einer typischen TVS-Diode" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Strom/Spannungs-Diagramm einer typischen TVS-Diode&lt;/span&gt;&lt;a href="https://www.littelfuse.com/products/tvs-diodes.aspx" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Power supervisor MAX1756x bei kapazitiver Last</title><link>https://blog.schallbert.de/capacitor-problems/</link><pubDate>Tue, 02 Nov 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/capacitor-problems/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-02_ic-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: MAX17562 with polarized capacitors on in- and output. This is fatal."&#10; title="Power supervisor MAX1756x bei kapazitiver Last" /&gt;&#10;&lt;p&gt;Argh, ich habe Teile meiner &lt;a href="https://blog.schallbert.de/pcbs-arrived/"&gt;Power&amp;amp;Preamp Schaltung für AnywhereAmps Alpha&lt;/a&gt; hochgejagt. Beim Testen.&lt;/p&gt;&#10;&lt;h2 id="explosion"&gt;Vorfall&lt;/h2&gt;&#10;&lt;p&gt;Ich habe versehentlich &lt;code&gt;VBat&lt;/code&gt; und &lt;code&gt;GND&lt;/code&gt; auf meiner Platine vertauscht. Man sollte meinen, dass so etwas gar nicht passiert, doch ich muss nicht richtig aufgepasst haben. Der Kondensator für Entkopplung der Spannungseingangs explodierte sofort, aber mein Supervisor-Chip schützte den Rest der Schaltung.&lt;/p&gt;&#10;&lt;h2 id="reparatur-und-tests"&gt;Reparatur und Tests&lt;/h2&gt;&#10;&lt;p&gt;Nach der Reparatur mit einem Keramikkondensator habe ich einige Tests durchgeführt.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Verpolungstest - BESTANDEN&lt;/li&gt;&#10;&lt;li&gt;Harter Kurzschluss gegen Masse - BESTANDEN&lt;/li&gt;&#10;&lt;li&gt;Unterspannungsabschaltung - BESTANDEN&lt;/li&gt;&#10;&lt;li&gt;Wiederholung der normalen Spannung - &amp;hellip;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="unregelmäßigkeit"&gt;Unregelmäßigkeit&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-11-02_oszi.jpg" alt="Image: Oszilloskopbild eines erfolglosen Einschaltversuchs @18V, 5A Stromgrenze" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Als ich dann aber die Spannung langsam erhöhte, um die Unterspannungssperre zu umgehen, ließ sich das System nicht einschalten. Der Stromverbrauch stieg aber trotzdem an. Zur Analyse schloss ich ein Oszilloskop an, um zu sehen, was vor sich ging. Man kann sehen, dass die Kapazität der Endstufe (&lt;code&gt;&amp;gt;1400µF&lt;/code&gt;) wie ein weicher Kurzschluss gegen Masse wirkt, so dass die Versorgungsspannung innerhalb von &lt;code&gt;50µs&lt;/code&gt; um &lt;code&gt;2V&lt;/code&gt; auf den Auslösepegel der Unterspannungsabschaltung abfällt. Der Supervisor-IC schaltet dann die Last ab, so dass die Versorgungsspannung wieder ansteigt. Ich vermute, dass der Regler des Netzteils deswegen so komische Geräusche macht.&lt;/p&gt;&#10;&lt;p&gt;Nach Ablauf der Wartezeit nach Abschalten durch Fehler (Datenblatt MAX1756x) versucht er erneut, sich einzuschalten, und der ganze Vorgang beginnt von vorne.&lt;/p&gt;&#10;&lt;h2 id="inductance"&gt;Vorfall #2&lt;/h2&gt;&#10;&lt;p&gt;Als ich die Spannung an der &lt;code&gt;VCC&lt;/code&gt;-Schiene, die den Vorverstärker, die Endstufe und den 9-V-Spannungsregler versorgt, während des erfolglosen Einschaltens gemessen habe, war mir aufgefallen, dass negative Spannungsspitzen bei etwa &lt;code&gt;-4V&lt;/code&gt; auftreten, wenn das Supervisor-IC abschaltet. Dies wird durch die Induktivitäten in den Versorgungsleitungen der Endstufe verursacht.&#10;Während ich mich noch wunderte, dass diese ständigen Resets nicht gut sein könnten, explodierte mein Vorstufen-IC. Doof.&lt;/p&gt;&#10;&lt;h2 id="was-ich-gelernt-habe"&gt;Was ich gelernt habe&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Niemals einen gepolten Kondensator als Masse/Bypass &amp;ldquo;vor&amp;rdquo; den Schutzschaltungen verwenden!&lt;/li&gt;&#10;&lt;li&gt;Bypass-Kondensatoren größer als &lt;code&gt;100nF&lt;/code&gt; direkt an Spannungsdetektoren verwenden, wenn die geschalteten Lasten kapazitiv sind. Sie helfen, die Spannung lokal zu stabilisieren und wiederkehrende Neustarts zu vermeiden.&lt;/li&gt;&#10;&lt;li&gt;Zum Schutz der Ausgangsseite eines Leistungsschalters eine Schottky- oder TVS-Diode (Transient Voltage Suppressor) hinzufügen, die potenziell schädliche Spannungen abfangen kann.&lt;/li&gt;&#10;&lt;li&gt;Vorsichtshalber den Einschaltstrom (&lt;a href="https://blog.schallbert.de/knowledge-inrushlimiters/"&gt;Folgeartikel&lt;/a&gt;) begrenzen. Bei der nächsten Überarbeitung der Schaltung werde ich auf &lt;code&gt;MAX17563&lt;/code&gt; umsteigen. Er hat eine programmierbare Strombegrenzung, damit die Eingangsspannung gar nicht erst zu niedrig wird.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="schutz-vor-negativen-spannungsspitzen"&gt;Schutz vor negativen Spannungsspitzen&lt;/h3&gt;&#10;&lt;p&gt;Für meine Anwendung reicht eine schnell schaltende Klemmdiode in Schottky-Bauweise aus, da die Schaltung hinter dem MAX1756x-IC keine Überspannungsspitzen, sondern nur Rückwärtsspannungsspitzen (aufgrund der Induktivitäten im Schaltregler- und Verstärkerdesign) abbekommen wird. In &lt;a href="https://blog.schallbert.de/knowledge-tvsdiodes/"&gt;diesem Artikel&lt;/a&gt;, erkläre ich Transient Voltage Suppressors im Detail.&lt;/p&gt;&#10;</description></item><item><title>Videos mit dem VLC player konvertieren</title><link>https://blog.schallbert.de/video-conversion/</link><pubDate>Wed, 27 Oct 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/video-conversion/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-27_vlc-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: VLC media player icon"&#10; title="Videos mit dem VLC player konvertieren" /&gt;&#10;&lt;h2 id="website-medienerstellung"&gt;Website: Medienerstellung&lt;/h2&gt;&#10;&lt;p&gt;Für diese Website habe ich viele Mediendateien konvertiert. Die selbstgemachten Fotos sind sehr groß, was zu langen Ladezeiten der Seite und bildschirmfüllenden Bildern führt. Während es recht einfach ist, ihre Größe zu reduzieren und die JPEG-Komprimierung zu erhöhen, ist dies bei Videos schwieriger.&lt;/p&gt;&#10;&lt;h2 id="das-problem"&gt;Das Problem&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-27_wtf-vlc.jpg" alt="Image: VLCs Vorschau Effekte und Filter Fenster" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hä, VLC?!&#10;Ich verwende den beliebten &lt;a href="https://www.videolan.org/vlc/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;vlc player&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und irgendwie habe ich einige Grundkonzepte nicht richtig verstanden. Entweder wird mein Video nicht wie geplant in Graustufen angezeigt, die Ausgabedatei ist leer (Verwendung von &lt;code&gt;H.264&lt;/code&gt; statt &lt;code&gt;H.265&lt;/code&gt; behebt das Problem), das Seitenverhältnis scheint verzerrt zu sein, oder das Video hat sich plötzlich gedreht, was ich weder wollte noch erwartet habe.&lt;/p&gt;&#10;&lt;h2 id="in-diesem-beitrag"&gt;In diesem Beitrag&lt;/h2&gt;&#10;&lt;p&gt;Eine Reihe von Schritten hilft mir, wie man einfache Aufgaben zur Konvertierung und rudimentäre Bearbeitung von Videos mit vlc umsetzt.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/video-conversion/#colors"&gt;Farben verändern oder auf Graustufen wechseln&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/video-conversion/#rotation"&gt;Das Video rotieren&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/video-conversion/#cropping"&gt;Ausschnitt eines Videos verwenden, Auflösung oder Seitenverhältnis anpassen&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/video-conversion/#cutting"&gt;Ein Video schneiden&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.schallbert.de/video-conversion/#scaling"&gt;Größe des Videos auf dem Datenträger verringern&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="wie-funktioniert-das-mit-den-filtern"&gt;Wie funktioniert das mit den Filtern?&lt;/h2&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-27_video-effects.jpg" alt="Image: VLC&amp;#39;s preview Effects and filters window" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="vorschau-der-filter"&gt;Vorschau der Filter&lt;/h3&gt;&#10;&lt;p&gt;VLC verfügt über eine Schnellvorschau für die gängigsten Filter im Menü &amp;ldquo;Werkzeuge -&amp;gt; Effekte und Filter&amp;rdquo;. Dies wirkt sich direkt auf das Video aus, das Sie ansehen, aber nicht auf Ihre gespeicherte Datei. Sie dient lediglich dazu, eine Vorschau zu erstellen und die Auswirkungen dieser Filter auf Ihr Video zu kontrollieren. In &lt;a href="https://www.videoproc.com/troubleshoot-vlc/how-to-crop-a-video-in-vlc.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;diesem VideoProc-Blogeintrag&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; finden Sie ausführliche Anleitungen - und, was noch wichtiger ist, viele Bilder, die Sie dabei unterstützen. Für die ungeduldige Fraktion: weiterlesen 😉&lt;/p&gt;&#10;&lt;h3 id="gefiltertes-video-in-datei-speichern"&gt;Gefiltertes Video in Datei speichern&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-27_profile.jpg" alt="Image: VLC&amp;#39;s conversion profile window for saving" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Um einen Filter zu speichern, den Sie für Ihre Vorschau ausgewählt haben, müssen Sie ihn zum Konvertierungsprofil hinzufügen. Solange sie angekreuzt bleiben, wird &lt;em&gt;jedes&lt;/em&gt; Speichern mit diesem Profil Ihr Video verändern, auch wenn Sie alle Änderungen, die Sie in der Vergangenheit an den Vorschaufiltern vorgenommen haben, rückgängig gemacht haben!&lt;/p&gt;&#10;&lt;h2 id="colors"&gt;Wie kann ich die Farben in meinem Video ändern?&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Öffnen Sie die Videodatei, die Sie ändern möchten.&lt;/li&gt;&#10;&lt;li&gt;Klicken Sie auf &amp;ldquo;Werkzeuge -&amp;gt; Effekte und Filter&amp;rdquo; (oder drücken Sie &amp;ldquo;Strg+E&amp;rdquo;)&lt;/li&gt;&#10;&lt;li&gt;Es öffnet sich ein Dialogfeld mit drei Registerkarten. Wählen Sie die Registerkarte &amp;ldquo;Videoeffekte&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Markieren Sie unter &amp;ldquo;Essentials&amp;rdquo; die Option &amp;ldquo;Bildanpassung&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Nehmen Sie Ihre Änderungen vor und klicken Sie auf &amp;ldquo;Speichern&amp;rdquo; und dann auf &amp;ldquo;Schließen&amp;rdquo;. Eine Vorschau des Effekts wird im Hauptfenster angezeigt.&lt;/li&gt;&#10;&lt;li&gt;Öffnen Sie die Wiedergabelistenansicht durch Drücken von &amp;ldquo;Wiedergabeliste umschalten&amp;rdquo; (Strg+L).&lt;/li&gt;&#10;&lt;li&gt;Klicken Sie mit der rechten Maustaste auf Ihr Video und wählen Sie &amp;ldquo;Speichern&amp;hellip;&amp;rdquo; im Kontextmenü.&lt;/li&gt;&#10;&lt;li&gt;Drücken Sie das Schraubenschlüssel-Symbol &amp;ldquo;Ausgewähltes Profil bearbeiten&amp;rdquo; und wählen Sie auf der Registerkarte &amp;ldquo;Videocodec&amp;rdquo; die Unterregisterkarte &amp;ldquo;Filter&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Markieren Sie den Filter &amp;ldquo;Bildeigenschaften&amp;rdquo;. Dadurch wird Ihre Änderung durchgeführt, sobald Sie Ihre Datei speichern.&lt;/li&gt;&#10;&lt;li&gt;Geben Sie eine Zieldatei ein und klicken Sie auf &amp;ldquo;Start&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Vergessen Sie nicht, den Filter &amp;ldquo;Bildeigenschaften&amp;rdquo; in Ihrem Profil zu deaktivieren, wenn Sie damit fertig sind. Nehmen Sie auch die Änderungen zurück, die Sie auf der Registerkarte &amp;ldquo;Videoeffekte&amp;rdquo; vorgenommen haben (Schritt 4).&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="rotation"&gt;Wie drehe ich mein Video?&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Öffnen Sie die Videodatei, die Sie ändern möchten.&lt;/li&gt;&#10;&lt;li&gt;Klicken Sie auf &amp;ldquo;Werkzeuge -&amp;gt; Effekte und Filter&amp;rdquo; (oder drücken Sie &amp;ldquo;Strg+E&amp;rdquo;)&lt;/li&gt;&#10;&lt;li&gt;Es öffnet sich ein Dialogfeld mit drei Registerkarten. Wählen Sie die Registerkarte &amp;ldquo;Videoeffekte&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Wählen Sie unter &amp;ldquo;Geometrie&amp;rdquo; die Option &amp;ldquo;Transformieren&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Nehmen Sie Ihre Änderungen vor und klicken Sie auf &amp;ldquo;Speichern&amp;rdquo; und dann auf &amp;ldquo;Schließen&amp;rdquo;. Eine Vorschau des Effekts wird im Hauptfenster angezeigt.&lt;/li&gt;&#10;&lt;li&gt;Öffnen Sie die Wiedergabelistenansicht durch Drücken von &amp;ldquo;Wiedergabeliste umschalten&amp;rdquo; (Strg+L).&lt;/li&gt;&#10;&lt;li&gt;Klicken Sie mit der rechten Maustaste auf Ihr Video und wählen Sie &amp;ldquo;Speichern&amp;hellip;&amp;rdquo; im Kontextmenü.&lt;/li&gt;&#10;&lt;li&gt;Drücken Sie das Schraubenschlüssel-Symbol &amp;ldquo;Ausgewähltes Profil bearbeiten&amp;rdquo; und wählen Sie auf der Registerkarte &amp;ldquo;Videocodec&amp;rdquo; die Unterregisterkarte &amp;ldquo;Filter&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Markieren Sie den Filter &amp;ldquo;Videotransformation&amp;rdquo;. Dadurch wird Ihre Änderung durchgeführt, sobald Sie Ihre Datei speichern.&lt;/li&gt;&#10;&lt;li&gt;Geben Sie eine Zieldatei ein und klicken Sie auf &amp;ldquo;Start&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Vergessen Sie nicht, das Häkchen beim Filter &amp;ldquo;Bildeigenschaften&amp;rdquo; in Ihrem Profil zu entfernen. Nehmen Sie auch die Änderungen zurück, die Sie auf der Registerkarte &amp;ldquo;Videoeffekte&amp;rdquo; vorgenommen haben.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="cropping"&gt;Wie kann ich mein Video zuschneiden?&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Öffnen Sie die Videodatei, die Sie ändern möchten.&lt;/li&gt;&#10;&lt;li&gt;Klicken Sie auf &amp;ldquo;Werkzeuge -&amp;gt; Effekte und Filter&amp;rdquo; (oder drücken Sie &amp;ldquo;Strg+E&amp;rdquo;)&lt;/li&gt;&#10;&lt;li&gt;Es öffnet sich ein Dialogfeld mit drei Registerkarten. Wählen Sie die Registerkarte &amp;ldquo;Videoeffekte&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Spielen Sie unter &amp;ldquo;Zuschneiden&amp;rdquo; mit den Pixelwerten herum und beobachten Sie das Vorschaufenster genau, bis Sie mit dem Zuschnitt zufrieden sind. Notieren oder merken Sie sich die Einstellungen, die Sie anwenden möchten. Schließen Sie den Dialog.&lt;/li&gt;&#10;&lt;li&gt;Klicken Sie auf &amp;ldquo;Werkzeuge -&amp;gt; Voreinstellungen&amp;rdquo; (oder drücken Sie &amp;ldquo;Strg+P&amp;rdquo;)&lt;/li&gt;&#10;&lt;li&gt;Wählen Sie auf der Registerkarte &lt;code&gt;Interface&lt;/code&gt; (Start) die Optionsschaltfläche &lt;code&gt;Show Settings - All&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Scrollen Sie nach unten zum Element &amp;ldquo;Video&amp;rdquo; und öffnen Sie &amp;ldquo;Filter&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Wählen Sie den &amp;ldquo;Video-Zuschneidefilter&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Suchen Sie das Element &lt;code&gt;Cropadd&lt;/code&gt; in der Filterliste auf der linken Seite. Geben Sie die Werte ein, die Sie sich in der Vorschau gemerkt/aufgeschrieben haben. Klicken Sie auf &amp;ldquo;Speichern&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Öffnen Sie die Wiedergabelistenansicht, indem Sie auf &amp;ldquo;Wiedergabeliste umschalten&amp;rdquo; (Strg+L) drücken. Klicken Sie dann mit der rechten Maustaste auf Ihr Video und wählen Sie &amp;ldquo;Speichern&amp;hellip;&amp;rdquo; aus dem Kontextmenü.&lt;/li&gt;&#10;&lt;li&gt;Drücken Sie das Schraubenschlüssel-Symbol &amp;ldquo;Ausgewähltes Profil bearbeiten&amp;rdquo; und wählen Sie auf der Registerkarte &amp;ldquo;Videocodec&amp;rdquo; die Unterregisterkarte &amp;ldquo;Filter&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Aktivieren Sie den Filter &amp;ldquo;Videoschnitt&amp;rdquo;. Dadurch wird die Änderung durchgeführt, sobald Sie Ihre Datei speichern.&lt;/li&gt;&#10;&lt;li&gt;Geben Sie eine Zieldatei ein und klicken Sie auf &amp;ldquo;Start&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Vergessen Sie nicht, das Häkchen bei &amp;ldquo;Video-Zuschnittfilter&amp;rdquo; in Ihrem Profil zu entfernen. Nehmen Sie auch die Änderungen zurück, die Sie in den Einstellungen unter &amp;ldquo;Filter&amp;rdquo; vorgenommen haben.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="cutting"&gt;Wie kürze ich mein Video?&lt;/h2&gt;&#10;&lt;p&gt;Hinweis: Eine ausführlichere Version dieses Themas findet sich in &lt;a href="https://www.movavi.io/how-to-trim-video-in-vlc-en/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;diesem Beitrag von movavi&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Öffnen Sie die Videodatei, die Sie ändern möchten.&lt;/li&gt;&#10;&lt;li&gt;Zeigen Sie die Schaltfläche &amp;ldquo;Aufnehmen&amp;rdquo; im VLC Player an (markieren Sie &amp;ldquo;Ansicht-&amp;gt;Erweiterte Steuerelemente&amp;rdquo;).&lt;/li&gt;&#10;&lt;li&gt;Springen Sie an die Stelle, an der das geschnittene Video beginnen soll.&lt;/li&gt;&#10;&lt;li&gt;Drücken Sie die rote Schaltfläche &amp;ldquo;Aufnehmen&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Spielen Sie die Videosequenz ab. Halten Sie an der Stelle an, an der der Zuschnitt enden soll.&lt;/li&gt;&#10;&lt;li&gt;VLCs Standardspeicherort für die Videoausgabe: &lt;code&gt;C:\Benutzer\Benutzername\Video&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="scaling"&gt;Wie ändere ich die Videoauflösung?&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Öffnen Sie die Videodatei, die Sie ändern möchten.&lt;/li&gt;&#10;&lt;li&gt;Öffnen Sie die Wiedergabelistenansicht, indem Sie &amp;ldquo;Wiedergabeliste umschalten&amp;rdquo; (Strg+L) drücken. Klicken Sie dann mit der rechten Maustaste auf Ihr Video und wählen Sie &amp;ldquo;Speichern&amp;hellip;&amp;rdquo; aus dem Kontextmenü.&lt;/li&gt;&#10;&lt;li&gt;Drücken Sie das Schraubenschlüssel-Symbol &amp;ldquo;Ausgewähltes Profil bearbeiten&amp;rdquo; und wählen Sie auf der Registerkarte &amp;ldquo;Videocodec&amp;rdquo; die Unterregisterkarte &amp;ldquo;Auflösung&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Geben Sie die Videogröße ein, in die Sie konvertieren möchten, und klicken Sie auf &amp;ldquo;Speichern&amp;rdquo;.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Meine Platinen sind angekommen!</title><link>https://blog.schallbert.de/pcbs-arrived/</link><pubDate>Sat, 16 Oct 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/pcbs-arrived/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-16_board-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Freecad example design in 3D view"&#10; title="Meine Platinen sind angekommen!" /&gt;&#10;&lt;p&gt;Fortsetzung des Artikels &lt;a href="https://blog.schallbert.de/anywhereamps-get-pcb/"&gt;PCB Hardware Design&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="lieferung"&gt;Lieferung&lt;/h2&gt;&#10;&lt;p&gt;Nach weniger als zwei Wochen sind meine Platinen endlich angekommen. Zusammen mit allen Teilen, die zum Löten benötigt werden. Ich finde, sie sehen wunderschön aus 🙂&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-16_board-delivered.jpg" alt="Image: The PCB delivered as-is: green, pads tin-coated" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="soldering"&gt;Löten: Schritt-für-Schritt&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-16_board-partial-test.jpg" alt="Image: cicruit testing" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich beschloss, die Platine in Abschnitten zu löten, beginnend mit dem Stromversorgungsbereich. Auf diese Weise kann ich die Abschnitte unabhängig voneinander testen und gegebenenfalls Fehler korrigieren. Der SOIC (Small Outline Integrated Circuit)-Umriss kann immer noch leicht von Hand gelötet werden, wenn man viel Flussmittel und eine Lupe für die optische Inspektion verwendet. Das einzige Problem war, dass der IC ein &amp;ldquo;exponiertes Pad&amp;rdquo; hat, d.h. es gibt eine Massefläche an der Unterseite des ICs zur besseren Wärmeableitung. Das zu löten war ein Albtraum. Schließlich gelang es mir, durch eine der unteren Durchkontaktierungen zu löten, während ich den Lötkolben fast eine Minute lang an einer anderen Durchkontaktierung hielt, bis es endlich klappte.&#10;Mit dem Ergebnis bin ich aber sehr zufrieden. Das System kann durch Ziehen von &lt;code&gt;*HVEN&lt;/code&gt; nach Masse aktiviert werden, wodurch die LED aufleuchtet. Die Tests auf Unterspannung/Kurzschluss verliefen sehr gut - der Akku ist jetzt geschützt.&lt;/p&gt;&#10;&lt;p&gt;Auch die Vorverstärkersektion war leicht zu löten. Nur die gepolten Kondensatoren erwiesen sich als so groß, dass sie einen großen Teil des Lötpads verdeckten - das ist ein Problem, weil das Lötzinn nicht sowohl Kondensator als auch Pad verbinden wollte, sondern nur an einem der beiden Kontakte hängen blieb. Ich half schließlich mit einem winzigen Stück Brückendraht nach.&lt;/p&gt;&#10;&lt;p&gt;Der 9V-Schaltregler-Teil - nun ja&amp;hellip; Ich habe es überhaupt nicht geschafft, ihn von Hand zu löten. Quadruple Flat Packs ohne Pins (QFN) sind schon schwer genug zu löten. Aber dieses Teil hier hat auch noch ein freiliegendes Pad auf der Unterseite und ich musste viel Massefläche vorhalten, um die Hitze von diesem Bauteil weg zu bekommen. Also konnte ich nicht einmal die Temperatur für den Lötvorgang hoch genug bekommen. Also habe ich mir eine Heißluftstation geliehen, und nach einigen ängstlichen Versuchen und dem Ertränken der Komponenten in Flussmittel habe ich es schließlich geschafft.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-16_board-inspection.jpg" alt="Image: optical PCB inspection" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Mit einem Makro-Objektiv auf meiner Kamera habe ich die Lötstellen dann geprüft.&lt;/p&gt;&#10;&lt;h2 id="funktionstest"&gt;Funktionstest&lt;/h2&gt;&#10;&lt;p&gt;Ich habe einen Tiefpass und einen Hochpass in das Design eingebaut, um 2-Wege-Lautsprechersysteme zu unterstützen. Um die drei Vorverstärkerausgänge (Fullrange, Tiefpass, Hochpass) zu testen, habe ich den Vorverstärker direkt an meinen Lautsprecher angeschlossen. Das ist keine gute Idee, denn die Impedanz des Lautsprechers ist viel niedriger als die des OP-Verstärkers, so dass die Ausgangsleistung sehr gering ist. Außerdem verbrauchen die unteren Frequenzen die meiste Leistung, so dass der Klang, den man hört, bereits ein &amp;ldquo;Hochpass&amp;rdquo; ist und die Lautstärke des Tiefpasses wesentlich geringer ist. Trotzdem funktioniert es, und ich kann die Verstärkung wie vorgesehen einstellen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/posts/2021-10-16_preamp-audio-demo.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Preamp test&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das war alles, mein Design funktioniert! Da das alles quelloffen ist, steht es jedem frei zum Download &lt;a href="https://github.com/Schallbert/AnywhereAmps/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;auf meinem Github Repo&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Verfügung.&lt;/p&gt;&#10;&lt;h2 id="lektionen"&gt;Lektionen&lt;/h2&gt;&#10;&lt;p&gt;Hier sind die Designfehler, die in Revision2 korrigiert werden sollten:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Alle Komponenten in der gleichen Ausrichtung platziert werden (z.B. horizontal)&lt;/li&gt;&#10;&lt;li&gt;&amp;ldquo;Thermische Entlastungen&amp;rdquo; (kurze Abschnitte, an denen die Leiterplan einen geringeren Querschnitt aufweist) sollten häufiger verwendet werden, um Lötprobleme aufgrund von zu starker Wärmeleitung zu vermeiden.&lt;/li&gt;&#10;&lt;li&gt;Freiliegende Pads: Beim Handlöten müssen die entsprechenden Lötpads zu den Seiten des ICs hin erweitert werden, um die Wärmeleitung des Lots zu verbessern&lt;/li&gt;&#10;&lt;li&gt;QFN-Fußabdrücke sind beim Handlöten falls möglich zu vermeiden. Sie sind lästig.&lt;/li&gt;&#10;&lt;li&gt;Wenn möglich, keine Bauteile mit &amp;ldquo;exposed pad&amp;rdquo; oder &amp;ldquo;power pad&amp;rdquo; verwenden. Eine Heißluftstation könnte ansonsten unumgänglich werden.&lt;/li&gt;&#10;&lt;li&gt;Schön viel Platz zwischen den Bauteilpads lassen, vor allem bei horizontaler Anordnung.&lt;/li&gt;&#10;&lt;li&gt;Da SMD-Kondensatoren mehr Höhe haben, sollten Sie größere Lötpads vorsehen.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Arbeitsabläufe mit FreeCAD</title><link>https://blog.schallbert.de/freecad-get-started/</link><pubDate>Tue, 12 Oct 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/freecad-get-started/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-12_pad-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Freecad example design in 3D view"&#10; title="Arbeitsabläufe mit FreeCAD" /&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://upload.wikimedia.org/wikipedia/commons/f/f7/FreeCAD-logo.svg" alt="FreeCad logo" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;FreeCad logo&lt;/span&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:FreeCAD-logo.svg#file" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich habe gerade eine zweite Bestellung für AnywhereAmps Alpha erhalten. Mein erster Prototyp hat bereits ein neues Zuhause, also werde ich mindestens drei weitere Exemplare bauen. Alle Holzarbeiten, die von Hand ausgeführt werden, würden 4-5 Stunden pro Exemplar in Anspruch nehmen, also habe ich mich entschlossen, sie auf den eigentlichen Bauprozess zu beschränken, der - hoffentlich - nur eine Stunde dauert.&#10;Dies möchte ich erreichen, indem ich die Holzteile mit einer CNC-Maschine fräsen lasse.&lt;/p&gt;&#10;&lt;h2 id="cad-werkzeug"&gt;CAD-Werkzeug&lt;/h2&gt;&#10;&lt;p&gt;Eine solche Maschine würde die Pläne, die ich von Hand skizziert habe, nicht verarbeiten. Also musste ich mich mit einer Software vertraut machen, die 2D-Skizzen in ein 3D-Modell umwandeln kann, und schließlich mit einem weiteren Werkzeug, das die Komponenten des 3D-Modells in eine Bahnsteuerung für die CNC-Maschine übersetzt, mit der ich meine Teile fräsen will.&#10;Für die 3D-Arbeiten habe ich die Software &lt;a href="https://www.freecad.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;FreeCAD&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gewählt.&lt;/p&gt;&#10;&lt;h2 id="aller-anfang-ist-schwer"&gt;Aller Anfang ist schwer&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-12_freecad_parts3d.jpg" alt="Image: parts construction complete" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Aber FreeCAD ist&amp;hellip; naja, ich bin halt kein CAD-Designer oder Maschinenbauingenieur. Daher ist der Einstieg in dieses Tool für mich ziemlich schwierig. Glücklicherweise gibt es einige gute Tutorials wie dieses: &lt;a href="https://www.youtube.com/watch?v=C1JG6l1uIJw" target="_blank" rel="noopener noreferrer" class="external-link"&gt;youtube link&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, dennoch habe ich einige Stunden gebraucht, bis ich mein erstes Teil gezeichnet hatte. Außerdem ist das, was auf dem Bild rechts zu erkennen ist, noch lange nicht der fertige Entwurf.&lt;/p&gt;&#10;&lt;h2 id="freecad-arbeitsablauf"&gt;FreeCAD Arbeitsablauf&lt;/h2&gt;&#10;&lt;p&gt;FreeCAD ist meiner Meinung nach (Stand Ende 2021) kein selbsterklärendes Werkzeug. Es ist sehr vielseitig und daher komplex und umfangreich. Da ich es zum Zeichnen von 3D-Teilen und/oder zur &lt;a href="https://de.wikipedia.org/wiki/Computerized_Numerical_Control#DIN/ISO-Programmierung_bzw._G-Code" target="_blank" rel="noopener noreferrer" class="external-link"&gt;G-Code&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Generierung verwenden möchte, werde ich nur eine Handvoll seiner Arbeitsbereiche benötigen.&lt;/p&gt;&#10;&lt;p&gt;Natürlich können die folgenden Zeilen nicht mehr als eine Schnellstartanleitung sein. Es gibt tonnenweise hilfreiches Material wie ein FreeCad &lt;a href="https://wiki.freecad.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Wiki&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, ein &lt;a href="https://forum.freecad.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Forum&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, und viele, viele Tutorial-Videos usw., die die Dinge besser und ausführlicher erklären als ich es kann. &lt;a href="https://wiki.freecad.org/Getting_started" target="_blank" rel="noopener noreferrer" class="external-link"&gt;https://wiki.freecad.org/Getting_started&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3 id="erst-die-abmessungen---das-modul-spreadsheet"&gt;Erst die Abmessungen - das Modul &amp;ldquo;Spreadsheet&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Schaffen wir zuerst einen zentralen Ort, an dem wir alle Maße unseres Projekts aufbewahren. Wir werden sie später in den Zeichnungen, 3D-Modellen und Baugruppen verknüpfen, so dass sich geänderte Werte in der Kalkulationstabelle automatisch auf alles andere angewendet werden (für mich übrigens &lt;em&gt;der&lt;/em&gt; Vorteil dieses CAD-Programms gegenüber vielen anderen, bei denen quasi von Vorn begonnen werden muss, wenn man die Größe des Bauteils anpassen möchte).&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Wählen Sie das Modul &amp;ldquo;Spreadsheet&amp;rdquo; an. Erstellen Sie ein Arbeitsblatt und benennen Sie es z. B. in &amp;ldquo;Abmessungen&amp;rdquo; um.&lt;/li&gt;&#10;&lt;li&gt;Tragen Sie Namen Ihrer Dimensionen ein. Ich habe festgestellt, dass es gut lesbar ist, wenn Sie für jede Dimension die Namenskonvention &lt;code&gt;Teil_Beschreibung_Attribut&lt;/code&gt; verwenden.&lt;/li&gt;&#10;&lt;li&gt;Nachdem Sie den Wert Ihrer Dimension eingegeben haben, klicken Sie mit der rechten Maustaste auf die Zelle und wählen Sie &amp;ldquo;Eigenschaften&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Im folgenden Dialog wählen Sie die &amp;ldquo;Alias&amp;rdquo;-Leiste und geben den Namen des Maßes &lt;code&gt;dimensionName&lt;/code&gt; ein.&lt;/li&gt;&#10;&lt;li&gt;Nun sind Sie in jeder Skizze in der Lage, diese Maße unter ihrem Alias auszuwählen. Klicken Sie &amp;ldquo;Save&amp;rdquo;, um den Dialog zu schließen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-12_spreadsheet.jpg" alt="Image: Spreadsheet example in FreeCad" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="skizze-anfertigen---scetcher-in-der-parts-design-workbench"&gt;Skizze anfertigen - scetcher in der &amp;ldquo;Parts Design Workbench&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Nun erstellen wir die erste Skizze des Teils, wobei die soeben erstellten Maße verwendet werden. Am besten beginnt man mit sogenannten Master-Skizzen, die alle Elemente einer Zeichnung in einer bestimmten Ansicht, z.B. auf der XY-Ebene, enthalten. Von ihnen werden dann die für dreidimensionale Modelle erforderlichen Grundriss-Skizzen der jeweiligen Achsenpaare abgeleitet.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Aktivieren Sie das Konstruktionsmodul &amp;ldquo;Part Design&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Erstellen Sie einen neuen Körper mit dem blauen Baustein-Symbol.&lt;/li&gt;&#10;&lt;li&gt;Erstellen Sie eine neue Skizze für diesen Körper mit dem Skizzen-Symbol. Wählen Sie z.B. die XY-Ebene.&lt;/li&gt;&#10;&lt;li&gt;Benennen Sie Ihre Skizze um, damit Sie sich ihren Zweck leicht merken können. Da Ihre ersten Skizzen die &amp;ldquo;Master&amp;rdquo;-Skizzen sind, schlage ich vor, sie &amp;ldquo;Master_IhrTeilename_xy&amp;rdquo; zu nennen.&lt;/li&gt;&#10;&lt;li&gt;Doppelklicken Sie auf die Skizze und beginnen Sie mit dem Zeichnen Ihrer Formen.&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-12_usingalias.jpg" alt="Image: Using spreadsheet alias in Parts Design workbench" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Stellen Sie sicher, dass Sie Ihre Skizze vollständig definieren (&lt;a href="https://www.youtube.com/watch?v=F6H1iPwVg2o" target="_blank" rel="noopener noreferrer" class="external-link"&gt;youtube link&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;). Verwenden Sie die Abmessungen, die Sie in die Kalkulationstabelle eingegeben haben, als Hilfe: Doppelklicken Sie auf Ihre Maßeinschränkung und geben Sie &amp;ldquo;=&amp;rdquo; ein oder drücken Sie die kleine runde Schaltfläche &amp;ldquo;fx&amp;rdquo;. Geben Sie den Namen Ihrer Kalkulationstabelle ein (zum Glück gibt es hier die Autovervollständigung) und dann den Alias der Dimension, die Sie in der Definition widerspiegeln wollen.&lt;/li&gt;&#10;&lt;li&gt;Wiederholen Sie diesen Vorgang für jede Bemaßungsabhängigkeit Ihrer Zeichnung.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Mit Ihren fertigen Master-Skizzen (je eine für jede Achsenkombination) stehen alle Informationen für jeden Drittanbieter zur Verfügung, der Ihre Teile fertigen kann. Wenn Sie das tun möchten, können Sie Ihre Skizzen hier in DXF-Dateien exportieren, und schon sind Sie fertig.&#10;Andernfalls: Lesen Sie unten weiter.&lt;/p&gt;&#10;&lt;h3 id="3d-teile-erstellen---teile-design-workbench"&gt;3D-Teile erstellen - Teile Design Workbench&lt;/h3&gt;&#10;&lt;p&gt;Um ein echtes 3D-Teil aus Ihren Master-Skizzen zu erstellen, müssen Sie davon abgeleitete Skizzen erstellen, die extrudiert oder ausgeschnitten werden können. Sie müssen zunächst verstehen, wie FreeCAD Teile und Skizzen baumartig ausrichtet:&#10;Jedes extrudierte (3D)-Teil hat eine zugrunde liegende Skizze, die definiert, wie es aussieht. Seine Extrusionsrichtung hängt direkt von der Ebene der Skizze ab. Jedes Teil kann eine oder mehrere Bezugsebenen haben, an denen andere Teile befestigt werden können, die wiederum Teile, Löcher oder Taschen auf dieser spezifischen Fläche bilden.&lt;/p&gt;&#10;&lt;p&gt;FreeCAD zeigt nur das &amp;ldquo;letzte&amp;rdquo; Mitglied des Bauteilbaums an, das alle abhängigen Teile enthält, die Sie zuvor entworfen haben. Um die Sichtbarkeit umzuschalten, drücken Sie einfach die Leertaste.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-12_pad.jpg" alt="Image: FreeCAD&amp;#39;s pad design tree view" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Erstellen Sie eine neue Skizze in der Baumstruktur Ihres Teils. Dies wird Ihr erstes &amp;ldquo;Pad&amp;rdquo; sein, d.h. Ihr eigentliches 3D-Teil.&lt;/li&gt;&#10;&lt;li&gt;Wählen Sie das Werkzeug &lt;code&gt;create an edge linked to an external geometry&lt;/code&gt; (Hotkey &amp;ldquo;X&amp;rdquo;) und wählen Sie das Basisteil Ihrer ursprünglichen Master-Skizze aus.&lt;/li&gt;&#10;&lt;li&gt;Zeichnen Sie Ihre Form so, dass sie mit dieser Form übereinstimmt, und legen Sie ihre Maße fest, wiederum unter Verwendung der zuvor erstellten Bemaßungstabelle. Sie sollten dies jedoch nicht allzu oft benötigen, da Sie alle möglichen Kanten, die Sie finden konnten, bereits mit Ihrer Musterskizze verknüpft haben.&lt;/li&gt;&#10;&lt;li&gt;Jetzt kommt der spaßige Teil: Schließen Sie die Skizze, klicken Sie dann auf das Symbol &lt;code&gt;pad a selected sketch&lt;/code&gt; und geben Sie die gewünschte Höhe des Teils ein.&lt;/li&gt;&#10;&lt;li&gt;Das war&amp;rsquo;s!&lt;/li&gt;&#10;&lt;li&gt;Um ein weiteres Teil, eine Tasche oder ein Loch auf einer der Flächen Ihres Teiles zu erstellen, erstellen Sie zunächst eine neue Skizze und geben Sie ihr einen aussagekräftigen Namen. Klicken Sie dann auf die Fläche, auf der diese Skizze basieren soll. Wählen Sie schließlich das Werkzeug &lt;code&gt;set the Support of a scetch&lt;/code&gt; und wählen Sie die zuvor erstellte Skizze aus. Wiederholen Sie den Vorgang ab Schritt 2, bis Ihr Teil vollständig ist.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Hm, Ihr neues Teil sitzt direkt auf dem Master-Sketch, so dass Sie es nicht mehr sehen können? Keine Panik, lesen Sie einfach weiter.&lt;/p&gt;&#10;&lt;h3 id="3d-teile-anordnen---das-modul-draft"&gt;3D-Teile anordnen - das Modul &amp;ldquo;Draft&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Meiner Meinung nach ist es sehr mühsam, Teile in der Konstruktionswerkbank neu anzuordnen. Es wird viel einfacher, wenn Sie die Werkbank &amp;ldquo;Entwurf&amp;rdquo; (Draft) für diese Aufgabe verwenden. Auch hier können Sie Teile einfach klonen und nach Ihren Vorstellungen anordnen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-12_move.jpg" alt="Image: move in FreeCAD&amp;#39;s Draft workbench" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Aktivieren Sie das Modul &amp;ldquo;Draft&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Wählen Sie das Pad/Teil, an dem Sie zuvor gearbeitet haben, im Teilebaum aus.&lt;/li&gt;&#10;&lt;li&gt;Klicken Sie auf das Schaf-Symbol, um Ihr Teil zu klonen.&lt;/li&gt;&#10;&lt;li&gt;Machen Sie das ursprüngliche Pad unsichtbar, indem Sie die Leertaste drücken, während das Teil aktiv ist.&lt;/li&gt;&#10;&lt;li&gt;Wählen Sie das geklonte Teil aus und benennen Sie es sinnvoll um, z.B. &lt;code&gt;derGerät_part&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Wählen Sie das &amp;ldquo;Verschieben&amp;rdquo;-Symbol (im obigen Bild grün markiert).&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-12_neat.jpg" alt="Image: part scetches and pads aligned side by side" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Eine Verschiebe-Operation besteht aus zwei Schritten: Zuerst müssen Sie die Referenzposition für Ihre Verschiebung eingeben. Entweder wählen Sie den Punkt aus, indem Sie irgendwo in der 3D-Ansicht klicken und dann den Cursor ein wenig bewegen (meine Empfehlung), oder Sie geben den Bezugspunkt in die Felder auf der linken Seite ein und bestätigen jede Achse mit &amp;ldquo;Enter&amp;rdquo;.&#10;Der zweite Schritt ist die eigentliche Bewegung gegenüber Ihrem Referenzpunkt. Ich empfehle, diese Werte mit der Tastatur einzugeben, da es unglaublich schwierig ist, mit der Maus eine exakte Position anzufahren.&#10;Bestätigen Sie mit &amp;ldquo;Enter&amp;rdquo;, und die Verschiebung sollte abgeschlossen sein.&lt;/p&gt;&#10;&lt;p&gt;Cool, jetzt haben Sie ein Bemaßungsblatt, Master-Skizzen, die Ihre Teile vollständig spezifizieren, die Teile selbst als 3D-Körper und das alles ordentlich sortiert in Ihrer Ansicht!&lt;/p&gt;&#10;&lt;h3 id="techdraw-erstellen-sie-eine-klassische-papier-2d-zeichnung-als-pdf"&gt;TechDraw: Erstellen Sie eine klassische &amp;ldquo;Papier 2D&amp;rdquo; Zeichnung als PDF&lt;/h3&gt;&#10;&lt;p&gt;Abschließend möchten Sie vielleicht eine klassische 2D-Zeichnung Ihres Teils exportieren? Zum Glück ist keine schwere Aufgabe.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Aktivieren Sie das &lt;code&gt;TechDraw&lt;/code&gt;-Modul.&lt;/li&gt;&#10;&lt;li&gt;Drücken Sie das Symbol &amp;ldquo;Standardseite einfügen&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;Wählen Sie im Teilebaum das 3D-Teil aus, das Sie zeichnen lassen möchten.&lt;/li&gt;&#10;&lt;li&gt;Drücken Sie auf das Symbol &amp;ldquo;Mehrere Ansichten von zeichnungsfähigen Objekten einfügen&amp;rdquo; und wählen Sie die Ansichten aus, die vorhanden sein sollen.&lt;/li&gt;&#10;&lt;li&gt;Fügen Sie mithilfe der Symbolleiste Maße, Anmerkungen usw. hinzu.&lt;/li&gt;&#10;&lt;li&gt;Verwenden Sie die Funktion &amp;ldquo;Ansichtsrahmen ein-/ausschalten&amp;rdquo;, wenn sich Ihre Geometrien überschneiden, damit Sie sie wieder auswählen können.&lt;/li&gt;&#10;&lt;li&gt;Fügen Sie bei Bedarf kosmetische Linien ein, z. B. um Symmetrielinien zu zeichnen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Das resultierende Dokument könnte wie folgt aussehen:&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-12_drawing.jpg" alt="Image: classic drawing in FreeCAD" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Weitere Informationen zu diesem Modul sind auf dem &lt;a href="https://wiki.freecad.org/Basic_TechDraw_Tutorial" target="_blank" rel="noopener noreferrer" class="external-link"&gt;FreeCAD Wiki&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verfügbar.&lt;/p&gt;&#10;</description></item><item><title>Tipps für besseres Hardware Design</title><link>https://blog.schallbert.de/anywhereamps-get-pcb/</link><pubDate>Tue, 21 Sep 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/anywhereamps-get-pcb/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-10-16_board-delivered-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Preamp prototype board soldered and working"&#10; title="Tipps für besseres Hardware Design" /&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;Ich habe meine erste Anfrage für die Fertigung eines AnywhereAmps Alpha. Da ich nicht noch einmal 10h für den Bau aufwenden möchte, habe ich beschlossen, die Zeit in die Beschleunigung des Herstellungsprozesses zu investieren. Ich fand heraus, dass ich etwa eine Stunde pro Verstärker sparen könnte, wenn ich die Platine für mich herstellen lassen würde. Leider hat mich die Umstellung der Platine von manuellem Schneiden, Fräsen, Bohren und Löten auf ein PCB-Design doppelt so viel Zeit gekostet, als wenn ich sie bereits komplett gebaut hätte. Und ich weiß noch nicht einmal, ob das funktioniert. Aber fangen wir von vorne an.&lt;/p&gt;&#10;&lt;h2 id="anforderungen-an-die-neue-leiterplatte"&gt;Anforderungen an die neue Leiterplatte&lt;/h2&gt;&#10;&lt;p&gt;Die Platine soll enthalten&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;die Vorverstärkerschaltung, wie sie für AnywhereAmps Alpha entworfen wurde&lt;/li&gt;&#10;&lt;li&gt;die Zwei-Wege-Weichenschaltung, wie sie für &lt;em&gt;zukünftige&lt;/em&gt; AnywhereAmps Beta entwickelt wurde&lt;/li&gt;&#10;&lt;li&gt;einen schaltenden Abwärtswandler zur Bereitstellung von 9V DC für Aux-Effekte&lt;/li&gt;&#10;&lt;li&gt;eine Stromüberwachungsschaltung, die sowohl Batterie als auch Platine vor Überlastung, Kurzschluss, Unterspannung und Rückspannung schützt&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Auf diese Weise möchte ich die Elektronik schrittweise professionalisieren. Ich beabsichtige, die gleiche Platine für alle Varianten der AnywhereAmps zu verwenden. Außerdem würde ich einige externe Komponenten loswerden, die ich von Hand herstellen und platzieren musste: Sicherung, hochstromfähiger Netzschalter, 9V-Regler und seine Kühlung, sowie einige Kabel.&lt;/p&gt;&#10;&lt;h2 id="bauteilauswahl"&gt;Bauteilauswahl&lt;/h2&gt;&#10;&lt;h3 id="abwärtswandler"&gt;Abwärtswandler&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-09-21_switching_efficiency.jpg" alt="Image: Typische Wirkungsgradkurve eines Abwärtsschaltwandlers im Vergleich zum Laststrom" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich habe im Internet nach einem Abwärtsschaltwandler mit geringer elektromagnetischer Interferenz (EMI) und einem Ausgang von &lt;code&gt;&amp;gt;1A&lt;/code&gt; bei &lt;code&gt;9V&lt;/code&gt; gesucht. Ein Linearregler hätte zwar weniger Störungen ausgesendet, aber bei einer Batterie-Nennspannung von &lt;code&gt;18V&lt;/code&gt; hätte ich im schlimmsten Fall um &lt;code&gt;9W&lt;/code&gt; thermische Verluste in Kauf nehmen müssen. Daher wäre diese Lösung überhaupt nicht batterieschonend gewesen. Ich entschied mich für ein (teures) Design, das alle notwendigen Komponenten in einem Baustein enthält, so dass ich nur noch das PCB-Layout richtig hinbekommen musste, um die EMI zu reduzieren. Ich fügte ein paar Bypass-Kondensatoren und einen Spannungsteiler für den Zielspannungs-Sollwert hinzu. Das war&amp;rsquo;s! (Dachte ich damals noch. &lt;a href="https://blog.schallbert.de/pcbs-arrived/#soldering"&gt;So sah es tatsächlich aus&lt;/a&gt;)&lt;/p&gt;&#10;&lt;h3 id="power-supervisor"&gt;Power Supervisor&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-09-21_converter.jpg" alt="Image: Integrierter Abwärtswandler: Fast ein Würfel" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Der einzige Schutz für meine Verstärker war bisher eine Sicherung, deren Wert auf den höchsten Betriebsstrom eingestellt war, den ich in den Schaltkreisen erwartete. In den frühen Prototypenphasen habe ich einige Operationsverstärker durch versehentliche Verpolung zerstört. Außerdem möchte ich vermeiden, dass der Akku völlig entladen wird, wenn ich vergesse, auf die Ladeanzeigen zu achten. Deshalb habe ich einen &lt;a href="https://blog.schallbert.de/configure-max1756x/"&gt;Power Supervisor IC&lt;/a&gt; ausgewählt, der Über- und Unterspannung und weitere Fehler erkennt und sowohl die Schaltkreise als auch den Akku vor Schäden schützt.&lt;/p&gt;&#10;&lt;h3 id="gruppierung-passiver-bauteile"&gt;Gruppierung passiver Bauteile&lt;/h3&gt;&#10;&lt;p&gt;Ich habe gelernt, dass die Herstellung einer Leiterplatte mit vielen verschiedenen Bauteilen teurer und fehleranfälliger ist als bei Verwendung weniger unterschiedlicher Bauteile, auch wenn ihre Gesamtzahl auf diese Weise steigt. Also habe ich viele Werte überarbeitet und die Simulation für den Audio-Vorverstärker mit diesen neuen Werten erneut durchgeführt. Schließlich gelang es mir, die Schaltung von 8/5 auf 3 verschiedene Widerstands- und 3 verschiedene Kondensatorwerte zu reduzieren!&lt;/p&gt;&#10;&lt;h3 id="kondensatoren---mal-wieder"&gt;Kondensatoren - mal wieder&lt;/h3&gt;&#10;&lt;p&gt;Ich habe zunächst MLCC (Multilayer Ceramic Capacitors) in meinem Design verwendet, sowohl für den Bypass als auch für die Audio-Entkopplungskondensatoren. Nach erneuter Lektüre der Dokumente, auf die ich in meinem Beitrag &lt;a href="https://blog.schallbert.de/preamp-design-considerations/"&gt;preamp-design-considerations/&lt;/a&gt; hingewiesen hatte, stellte ich fest, dass dieses Kondensator-Design Probleme mit Verzerrungen bei Audiosignalen zu haben scheint. Das Bild unten zeigt einen Vergleich verschiedener Kondensatortypen und -größen. Man erkennt, dass Folienkondensatoren hier am besten und MLCCs am schlechtesten abschneiden. Da ich keine Folienkondensatoren in der von mir benötigten Größe auf SMD-Basis finden konnte und Aluminium-Elektrolytkondensatoren zu groß waren, habe ich gepolte Tantalkondensatoren gewählt. Für die Bypass-Kondensatoren wählte ich &lt;a href="https://passive-components.eu/why-low-esr-matters-in-capacitor-design/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;low-ESR&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Typen.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-09-21_capcompare.jpg" alt="Image: capacitor types compared for Audio usage" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;TI Audio Design Journal: Vergleich verschiedener Kondensatortypen&lt;/span&gt;&lt;a href="https://www.ti.com/lit/an/slyt796/slyt796.pdf?ts=1630627717321&amp;amp;ref_url=https%253A%252F%252Fwww.startpage.com%252F" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="schaltungsentwurf"&gt;Schaltungsentwurf&lt;/h2&gt;&#10;&lt;p&gt;Ich wählte die Open-Source-Software &lt;a href="https://www.kicad.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;KiCad&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; als Werkzeug zum Schaltungsentwurf und zeichnete alle meine bestehenden Schaltpläne neu, wobei ich die oben beschriebenen Komponenten hinzufügte. Man erkennt gut, dass der Verstärkerteil nur die rechte Hälfte des Schaltplans füllt, während die linke Hälfte der Stromversorgung und der Überwachungsschaltung gewidmet ist.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-09-21_schematics.jpg" alt="Image: AnywhereAmps Power &amp;amp; Preamp-Schaltung mit KiCad" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich habe versucht, die Schaltpläne so übersichtlich wie möglich zu gestalten, damit sie leicht zu lesen sind. Ich habe häufig globale Bezeichnungen verwendet und Steckverbinder zur einfacheren Montage hinzugefügt.&lt;/p&gt;&#10;&lt;h2 id="pcb-design"&gt;PCB-Design&lt;/h2&gt;&#10;&lt;p&gt;Dies war der schwierigste Teil für mich. Ich habe noch nie eine Leiterplatte für Audioanwendungen entworfen, also musste ich eine Menge Richtlinien lesen, bevor ich anfing.&lt;/p&gt;&#10;&lt;h3 id="anleitungen-und-tutorials"&gt;Anleitungen und Tutorials&lt;/h3&gt;&#10;&lt;p&gt;Ich liste einige davon unten zur Information auf:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://www.allaboutcircuits.com/technical-articles/clean-power-for-every-ic-part-1-understanding-bypass-capacitors/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Rauschen auf Leiterplatten und wie man es vermeidet&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.renesas.com/en/document/apn/an1325-choosing-and-using-bypass-capacitors" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Wie man Bypass-Kondensatoren am besten platziert&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.autodesk.com/products/eagle/blog/8-pcb-grounding-rules/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Massefläche oder nicht?&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://electronics.stackexchange.com/questions/52603/how-to-connect-ground-planes-together" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Wie man verschiedene Massen für Signal, Stromversorgung etc. verbindet&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://resources.altium.com/p/top-5-pcb-design-guidelines-every-pcb-designer-needs-know" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Bauteilpositionen für den Lötvorgang optimieren&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.elektronikpraxis.vogel.de/pcb-design-regeln-sieben-suenden-beim-leiterplatten-design-a-356703/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Übliche Probleme beim Platinendesign&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.multi-circuit-boards.eu/leiterplatten-design-hilfe/oberflaeche/leiterbahn-strombelastbarkeit.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Wie viel Strom kann ich über welche Leiterbahn schicken?&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://community.aisler.net/c/knowledge-base/design-rules/41" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PCB Design Tipps für die maschinelle Fertigung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="anywhereamps-pcb-erklärt"&gt;AnywhereAmps PCB erklärt&lt;/h3&gt;&#10;&lt;p&gt;Das Ergebnis nach ein einigen Stunden Arbeit:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-09-21_pcb.jpg" alt="Image: PCB fully routed on a 2-layer design" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Platine ist in zwei Lagen vollständig geroutet.&lt;/p&gt;&#10;&lt;p&gt;Ich habe die Leiterplatte in drei Bereiche unterteilt.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Links: der 9V-Schaltregler, der seinem Referenzdesign (Datenblatt) folgend mit Hilfe der Bypass-Kondensatoren einen Stromkreis um sich selbst bilden und den größten Teil der elektromagnetischen Störaussendung auslöschen würde. Das thermische Design, lässt viel Platz für Kupfer und einige Durchkontaktierungen, sodass die Wärme vom Chip gut abgeführt werden kann.&lt;/li&gt;&#10;&lt;li&gt;Mitte: Die Stromanschlüsse und die Überwachungsschaltung. Ich habe Lötbrücken gesetzt, um die Nennspannung der Batterie für die Unterspannungserkennung auszuwählen.&lt;/li&gt;&#10;&lt;li&gt;Rechts und oben: Vorverstärker und analoge Signalschaltung, getrennt von der übrigen Elektronik durch eine dicke Massebahn auf der oberen Ebene und eine eigene Massefläche auf der Unterseite. Ich hoffe, das ist genug.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Da ich die ersten Prototypen von Hand löten werde, um zu prüfen, ob alles gut funktioniert, habe ich &amp;ldquo;große&amp;rdquo; 0805 SMD-Bauteile verwendet. Ich habe den Abstand zwischen den Bauteilen größer gemacht, als es die PCB-Spezifikation verlangt. Leider waren die hochintegrierten Leistungs-ICs nicht in &amp;ldquo;Hobby-Löt-Elektronik&amp;rdquo;-Größen erhältlich, so dass ich hier noch einige Mühen auf mich nehmen muss.&lt;/p&gt;&#10;&lt;p&gt;So wird es also aussehen. Die Leiterplatte wird 45mm x 69mm groß sein.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-09-21_pcb3d.jpg" alt="Image: 3D-Ansicht der Leiterplatte" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="herunterladen-oder-eigene-beiträge-erwünscht"&gt;Herunterladen oder eigene Beiträge erwünscht?&lt;/h2&gt;&#10;&lt;p&gt;Alle meine Zeichnungen in CAD sowie Stromlaufpläne sind quelloffen und zum Download auf meinem eigens dafür angelegten &lt;a href="https://github.com/Schallbert/AnywhereAmps/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github Repo&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verfügbar.&lt;/p&gt;&#10;&lt;p&gt;Nächste Schritte: &lt;a href="https://blog.schallbert.de/pcbs-arrived/"&gt;Platinen geliefert und verlötet&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Vorverstärker-Design</title><link>https://blog.schallbert.de/preamp-design-considerations/</link><pubDate>Wed, 25 Aug 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/preamp-design-considerations/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/circuit_board_soldered-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Preamp prototype board soldered and working"&#10; title="Vorverstärker-Design" /&gt;&#10;&lt;h2 id="auswahl-der-entkopplungskondensatoren"&gt;Auswahl der Entkopplungskondensatoren&lt;/h2&gt;&#10;&lt;p&gt;Einige technische Hintergrundinformationen über die Auswahl der Komponenten für meine Vorverstärkerplatine.&lt;/p&gt;&#10;&lt;h3 id="warum-entkopplung-nötig-ist"&gt;Warum Entkopplung nötig ist&lt;/h3&gt;&#10;&lt;p&gt;Mit Entkopplung meine ich in diesem Fall speziell die Gleichstromentkopplung (DC-Entkopplung).&#10;Der Vorverstärker muss sowohl von der Quelle (dem Musikinstrument) als auch von der Senke (dem Endverstärker) entkoppelt werden.&#10;Dies hat mehrere Gründe:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;die Versorgungsspannungen der drei Partner können unterschiedlich sein (z.B. 9V Gitarrenspeisung, 18V Vorverstärker, 52V Endstufe) und ich möchte nicht versehentlich einen von ihnen kurzschließen&lt;/li&gt;&#10;&lt;li&gt;Ausgangsimpedanz, d.h. die Belastbarkeit der Ausgangsstufe könnte begrenzt sein. Wenn ich einen niedrigen Eingangswiderstand z.B. direkt an den Tonabnehmer einer Gitarre anschließe, kann es sein, dass ich überhaupt kein Signal herausbekomme, weil die Quellenspannung zu stark einbricht.&lt;/li&gt;&#10;&lt;li&gt;Rauschunterdrückung durch elektrische Entkopplung&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="warum-kondensatoren-die-aufgabe-übernehmen-können"&gt;Warum Kondensatoren die Aufgabe übernehmen können&lt;/h3&gt;&#10;&lt;p&gt;Es gibt mehrere Möglichkeiten, Schaltungen elektrisch zu entkoppeln. Man kann einen &lt;a href="https://en.wikipedia.org/wiki/Opto-isolator" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Optokoppler&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verwenden, um zwei Schaltkreise vollständig (d.h. galvanisch) voneinander zu trennen. Damit wird nur das erwünschte Signal zwischen den Schaltkreisen durchgelassen. Oder man verwendet - wie in meinem Fall - einen Kondensator, um nur Wechselströme durchzulassen und Gleichstrom zu unterdrücken.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Wie funktioniert das?&lt;/em&gt;, fragen Sie?&#10;Nun, ein Kondensator funktioniert ein bisschen wie ein Wassertank. Wenn man eine konstante Spannung (Druck) anlegt, füllt sich der Tank bis zu einem gewissen Grad. Wenn dieser Pegel erreicht ist, fließt kein Volumenstrom mehr, weder rein noch raus. Variiert man nun aber die Spannung um einen statischen Pegel (Wasserdruckschwankung), so wird man wieder einen Volumenstrom messen, der in den Tank hinein- und herausfließt.&lt;/p&gt;&#10;&lt;p&gt;Genauso verhält es sich mit dem Kondensator. Wenn man ihn in Reihe mit einer Senke schaltet, wird er nur dann Strom durchlassen, wenn sich der die Eingangsspannung ändert.&lt;/p&gt;&#10;&lt;h2 id="typ-des-entkopplungskondensators"&gt;Typ des Entkopplungskondensators&lt;/h2&gt;&#10;&lt;p&gt;Keramik-Vielschichtkondensator, Polypropylen-Kondensator, Elektrolytkondensator&amp;hellip;? Nach Lektüre &lt;a href="https://www.ti.com/lit/an/slyt796/slyt796.pdf?ts=1630627717321&amp;amp;ref_url=https%253A%252F%252Fwww.startpage.com%252F" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieses Leitfadens&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; von Texas Instruments habe ich verstanden, dass es in diesem Dokument nur um Verzerrungen geht. Die Verzerrung würde am stärksten am Instrumenteneingang vor dem Vorverstärker Einfluss ausüben, da dieser von beiden Verstärkerstufen verstärkt wird. Bei der Verwendung eines Elektrolytkondensators sollte eine Vorspannung angelegt werden, um die Verzerrung im unteren Frequenzbereich zu verringern. Bei meinem Design ist die Vorspannung &lt;code&gt;1/2&lt;/code&gt; von &lt;code&gt;VCC&lt;/code&gt;, also noch höher als die in der Anleitung verwendeten &lt;code&gt;5V&lt;/code&gt; - ich denke, ich werde mit einem &amp;ldquo;billigen&amp;rdquo; polarisierten Aluminiumkondensator auskommen um die Verzerrungen &lt;a href="https://en.wikipedia.org/wiki/Total_harmonic_distortion" target="_blank" rel="noopener noreferrer" class="external-link"&gt;THD+n&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gering zu halten.&lt;/p&gt;&#10;&lt;p&gt;Es gibt noch eine andere Sache, die ich bei Mehrschicht-Keramikkondensatoren (MLCCs) festgestellt habe: Wenn die Lautsprecher bei hoher Vorverstärkung nicht angeschlossen sind, spielen die von mir verwendeten Endstufen immer noch Musik! Wie kommt das? Die Endstufenplatine verwendet MLCCs, die wie Piezoaktoren wirken. Ihre Vibration wird durch die Platine, auf der sie montiert sind, verstärkt, was einen gewissen Schallpegel ergibt. Verrückt. Weitere interessante Details finden Sie im &lt;a href="https://www.edn.com/power-tip-49-avoid-these-common-mlcc-pitfalls/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;EDN-Blog&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder - in einem weiteren &lt;a href="https://e2e.ti.com/blogs_/b/powerhouse/posts/how-to-reduce-acoustic-noise-of-mlccs-in-power-applications" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Texas Instruments-Leitfaden&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="kondensatorgröße"&gt;Kondensatorgröße&lt;/h3&gt;&#10;&lt;p&gt;Um die Größe des Kondensators zu beurteilen sind einige Aspekte zu berücksichtigen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Ein größerer Kondensator senkt meine untere Grenzfrequenz&lt;/li&gt;&#10;&lt;li&gt;Ein größerer Kondensator belastet die Eingangsquelle aufgrund von Ladungsverlusten stärker&lt;/li&gt;&#10;&lt;li&gt;Ein größerer Kondensator kostet mehr, insbesondere die hochwertigen Kondensatoren.&lt;/li&gt;&#10;&lt;li&gt;Ein größerer Kondensator kann einen größeren Platzbedarf haben.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Ich wollte eine untere Grenzfrequenz von &lt;code&gt;&amp;lt;20Hz&lt;/code&gt; für volle HiFi-Fähigkeit. Da dies bereits der &lt;code&gt;-3dB&lt;/code&gt;-Punkt ist, wählte ich einen noch niedrigeren Wert von &lt;code&gt;15Hz&lt;/code&gt;, berechnet über \(f=\frac{1}{2 \pi RC}\). Bei einer Eingangsimpedanz von &lt;code&gt;10kOhm&lt;/code&gt; (der Wert des Widerstandes, der an die virtuelle Masse geklemmt wird) ergebe ich eine Zielgröße des Eingangskondensators von &lt;code&gt;1µF&lt;/code&gt;.&#10;Bei diesem Kondensator wird die Eingangsquelle wesentlich stärker durch den Eingangswiderstand von &lt;code&gt;10kOhm&lt;/code&gt; beeinflusst als durch den Kondensator mit seinem niedrigen Serienwiderstand (&lt;a href="https://passive-components.eu/why-low-esr-matters-in-capacitor-design/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ESR&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) von einigen hundert &lt;code&gt;mOhm&lt;/code&gt;, so dass der gewählte Wert in Ordnung ist.&lt;/p&gt;&#10;&lt;p&gt;Für den Ausgangskondensator ist die Berechnung ähnlich. Die Endstufe hat eine Eingangsimpedanz von &lt;code&gt;20kOhm&lt;/code&gt;, so dass die untere Grenzfrequenz von &lt;code&gt;17Hz&lt;/code&gt; bereits mit einem &lt;code&gt;0.47uF&lt;/code&gt; Kondensator erreicht wird. Um die Wertunterschiede in Grenzen zu halten, habe ich mich auch hier für den &lt;code&gt;1µF&lt;/code&gt; entschieden.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/projects/aa_alpha/"&gt;Zurück zur Projekt-Hauptseite&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Shelly13 - Teil2</title><link>https://blog.schallbert.de/thoughts-about-shelly/</link><pubDate>Fri, 09 Jul 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/thoughts-about-shelly/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-07-09_shellydraw-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: shelly13&amp;#39;&amp;#39;s folding mechanism"&#10; title="Shelly13 - Teil2" /&gt;&#10;&lt;h2 id="shelly13-projektstatus-prototypenskizzen"&gt;Shelly13: Projektstatus, Prototypenskizzen&lt;/h2&gt;&#10;&lt;p&gt;Ich denke immer noch viel über &lt;a href="https://blog.schallbert.de/shelly13/"&gt;Shelly13&lt;/a&gt; nach und darüber, wie ein größerer Lautsprecher, ein homogener Klang mit etwas Punch und ein versiegeltes Gehäuse, das sich zusammenklappen lässt, umsetzbar wäre.&lt;/p&gt;&#10;&lt;h3 id="gehäuse-dimensionierung-und-volumen"&gt;Gehäuse: Dimensionierung und Volumen&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/shelly13/scetch_unfolded.jpg" alt="Image: Cabinet, inside view scetch" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt; Die letzten Entwürfe in meinem Kopf benötigen 12x10 und 14x10 Zoll Kessel. Der Combo wird aufgeklappt ein bisschen wie eine Torte aussehen, mit einem Gesamtvolumen von ca. 38l inkl. Lautsprecher und Verstärker im Inneren. Die Gesamthöhe wird mit ausgeklappten Füßen ca. 75cm betragen, zusammengeklappt ist er ca. 29cm hoch.&lt;/p&gt;&#10;&lt;h3 id="lautsprecher"&gt;Lautsprecher&lt;/h3&gt;&#10;&lt;p&gt;Während mein erster Entwurf einen 10&amp;quot;-Lautsprecher an der Vorderseite des Gehäuses vorsieht, werde ich höchstwahrscheinlich zu einem Zwei-Wege-Design wechseln, um die Montage zu vereinfachen und die Probleme mit dem Fräsen einer großen gewölbten Fläche und der Herstellung einer Adapterplatte zu verringern. Ich werde also einen 8&amp;quot;-Basslautsprecher in einem Downfire-Setup und einen 3&amp;quot;-Mittelhochtonlautsprecher an der Front wählen.&lt;/p&gt;&#10;&lt;h3 id="verstärkung"&gt;Verstärkung&lt;/h3&gt;&#10;&lt;p&gt;Ich denke darüber nach, einen Car-Hifi-Verstärker zu verwenden, der sein Signal von einer aktiven Frequenzweiche mit getrennten Ausgängen für tiefe und hohe Frequenzen erhält, wodurch ein Bi-Amp-Design mit gebrückten Anschlüssen entsteht.&lt;/p&gt;&#10;&lt;h3 id="klappmechanismus"&gt;Klappmechanismus&lt;/h3&gt;&#10;&lt;p&gt;Das Zusammenklappen erfolgt mit Hilfe von Teleskopschienen, die zwischen den Kesseln montiert werden. Es ist wichtig, dass das Gehäuse im aufgeklappten Zustand luftdicht verschlossen ist. Deshalb muss ein Dichtungsring vorhanden sein, der einrastet, wenn der untere Holzring des 12&amp;quot;-Kessels auf den oberen Ring des 14&amp;quot;-Kessels trifft. &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/shelly13/scetch_top_rings.jpg" alt="Image: Cabinet ring system scetch" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="nein-das-ist-zu-komplex"&gt;Nein, das ist zu komplex!&lt;/h2&gt;&#10;&lt;p&gt;Ok, vereinfachen wir das Konzept. Lassen wir die Faltbarkeit für den ersten Prototyp weg. Reduzieren wir stattdessen die Gehäusegröße. Konzentrieren wir uns zunächst auf die Elektronik und die Herstellbarkeit.&#10;Das Ergebnis dieser Umfangsänderung ist: &lt;a href="https://blog.schallbert.de/projects/aa_alpha/"&gt;AnywhereAmps Alpha&lt;/a&gt;!&lt;/p&gt;&#10;</description></item><item><title>Shelly13 - Teil1</title><link>https://blog.schallbert.de/shelly13/</link><pubDate>Mon, 28 Jun 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/shelly13/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/shelly13/shelly13-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: shelly13 prototype in CAD"&#10; title="Shelly13 - Teil1" /&gt;&#10;&lt;p&gt;Nachdem ich eine Weile mit &lt;a href="https://blog.schallbert.de/projects/mobfobamp/"&gt;MobFobAmp&lt;/a&gt; gespielt hatte, wollte ich einen weiteren Schritt in der Entwicklung zu einer wirklich guten tragbaren Lösung gehen. Mit diesem Design versuche ich, die Unzulänglichkeiten der vorherigen Prototypen zu beheben:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Ausgangsleistung könnte höher sein&lt;/li&gt;&#10;&lt;li&gt;Mehr Volumen in den tiefen Tönen wäre schön&lt;/li&gt;&#10;&lt;li&gt;Der Klappmechanismus ist ein bisschen fummelig.&lt;/li&gt;&#10;&lt;li&gt;Ich hätte gerne eine Entfaltung in einem Schritt, am besten mit einer Hand möglich&lt;/li&gt;&#10;&lt;li&gt;Mechanische Stabilität und Robustheit könnten verbessert werden&lt;/li&gt;&#10;&lt;li&gt;Empfindliche Bauteile an der Außenseite, die mit Vorsicht zu behandeln sind&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="ein-erster-entwurf"&gt;Ein erster Entwurf&lt;/h2&gt;&#10;&lt;p&gt;Unter Berücksichtigung der oben genannten Punkte habe ich herausgefunden, dass ich bei der Faltbarkeit einige Kompromisse eingehen muss, um alle empfindlichen Komponenten einschließlich der Lautsprecherchassis in das Innere des Gehäuses zu bringen. Ein kugelförmiges Design wäre ideal für ein optimales Verhältnis zwischen Abmessungen und Volumen, aber ich wüsste nicht, wie man ein solches Design herstellt. Also entschied ich mich für einen zylindrischen Ansatz.&#10;Ich möchte, dass der Lautsprecher &amp;ldquo;aufklappt&amp;rdquo;, damit er beim Zusammenklappen vor mechanischen Schäden geschützt ist.&#10;Meine erste Idee sieht also so aus:&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/shelly13/shelly13.jpg" alt="Image: Shelly13 scetch" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="einschränkungen"&gt;Einschränkungen&lt;/h2&gt;&#10;&lt;p&gt;Ich kann die Zylinder nicht selbst herstellen, also müsste ich einen Schlagzeughersteller bitten, mir ein paar unfertige Tom-Toms in Sondergröße zu schicken. Selbst wenn ich diese bekomme, muss ich auf einer gekrümmten Oberfläche schneiden, was ich noch nie gemacht habe, und ich müsste etwas 3D-Drucken / Fräsen, um eine Adapterplatte zwischen Lautsprecher und Zylinder herzustellen&amp;hellip;&#10;Oder werde ich den Lautsprecher einfach oben/unten anbringen?&#10;Außerdem müsste ich mir überlegen, wie ich einen leistungsfähigeren Verstärker in mein Design einbauen kann - es gibt einige Ideen wie &lt;a href="https://pioneer-car.eu/gm-d1004/gm-d1004" target="_blank" rel="noopener noreferrer" class="external-link"&gt;diesen Pioneer-Auto-Verstärker&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; oder den spezialisierten Hi-Fi-Verstärker von &lt;a href="https://www.hypexdirect.com/products/modules/amplifier-modules/ucd180hg-with-hxr" target="_blank" rel="noopener noreferrer" class="external-link"&gt;DIY Class D&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, aber die haben sehr unterschiedliche und möglicherweise hohe Anforderungen an die Stromversorgung&amp;hellip;&lt;/p&gt;&#10;&lt;h2 id="fazit"&gt;Fazit&lt;/h2&gt;&#10;&lt;p&gt;Es gibt also einige offene Baustellen. Ich lasse die Punkte mal auf mich wirken und schaue dann, wie ich mit diesen Problemen am besten umgehe.&lt;/p&gt;&#10;</description></item><item><title>Favicon und Sammlungsgenerator</title><link>https://blog.schallbert.de/jekyll-icon-paginator/</link><pubDate>Sun, 13 Jun 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/jekyll-icon-paginator/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/favicon-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: favicon implementation on a website"&#10; title="Favicon und Sammlungsgenerator" /&gt;&#10;&lt;h3 id="hinzufügen-eines-tab-symbols---favicon"&gt;Hinzufügen eines &amp;ldquo;Tab&amp;rdquo;-Symbols - Favicon&lt;/h3&gt;&#10;&lt;p&gt;Ich war besorgt über eine merkwürdige Fehlermeldung, die Jekyll bei jedem Refresh meiner Seite ausgab: &lt;code&gt;[2021-06-13 22:07:43] ERROR '/favicon.ico' not found.&lt;/code&gt; Einige Online-Recherchen ergaben, dass dies weder ein Jekyll- noch ein Theme-Fehler ist, sondern ein Dateifehler. Siehe &lt;a href="https://blog.schallbert.de/projects/thissite/#favicon"&gt;hier&lt;/a&gt; wie ich das Icon für diese Seite implementiert habe.&lt;/p&gt;&#10;&lt;h3 id="erzeugung-von-kollektionen"&gt;Erzeugung von Kollektionen&lt;/h3&gt;&#10;&lt;p&gt;Ich habe herausgefunden, dass irgendwie die Sammlungserzeugung auf meiner Seite nicht funktioniert. Trotzdem geht das Layouting-Skript aber davon aus, dass es die Seiten vorbereitet hat. Wenn ich nun versuche, die Seite durch Drücken der Schaltfläche &amp;ldquo;Zurück&amp;rdquo; aufzurufen, erhalte ich einen 404-Fehler. Bis jetzt war ich nicht in der Lage, dies zu beheben, weil die Erzeugung stillschweigend fehlschlägt und ich nicht weiß, wo.&#10;Ich schalte sie vorerst komplett ab.&lt;/p&gt;&#10;</description></item><item><title>MobFobAmp</title><link>https://blog.schallbert.de/mobfobamp-diy/</link><pubDate>Thu, 10 Jun 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/mobfobamp-diy/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/unfolded-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: MobFobAmp Folded"&#10; title="MobFobAmp" /&gt;&#10;&lt;h2 id="plans"&gt;Pläne&lt;/h2&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Image: MobFobAmp pencil drawing on a sheet of paper. It features front and side view, and includes a scetch of the speaker chassis which is mounted inside-out. The box itself looks like a rectangle with hinges that allow it to fold flat."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_sidefront.jpg" alt="Image: MobFobAmp pencil drawing on a sheet of paper. It features front and side view, and includes a scetch of the speaker chassis which is mounted inside-out. The box itself looks like a rectangle with hinges that allow it to fold flat." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Image: MobFobAmp pencil drawing on a sheet of paper. It is a top/bottom view, and includes domensions. It looks like a square ."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_topbottom.jpg" alt="Image: MobFobAmp pencil drawing on a sheet of paper. It is a top/bottom view, and includes domensions. It looks like a square ." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Image: MobFobAmp&amp;#39;s circuit diagram as a pencil drawing. It shows a battery, fuse, on/off switch, status indicator LED the amplifier board with in- and output jacks and the speaker it drives."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_circuitdiagram.jpg" alt="Image: MobFobAmp&amp;#39;s circuit diagram as a pencil drawing. It shows a battery, fuse, on/off switch, status indicator LED the amplifier board with in- and output jacks and the speaker it drives." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Plans for my Mobile Foldable Amplifier.&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_sidefront.jpg" alt="Image: MobFobAmp pencil drawing on a sheet of paper. It features front and side view, and includes a scetch of the speaker chassis which is mounted inside-out. The box itself looks like a rectangle with hinges that allow it to fold flat." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Front and side view scetch with dimensions&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_topbottom.jpg" alt="Image: MobFobAmp pencil drawing on a sheet of paper. It is a top/bottom view, and includes domensions. It looks like a square ." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Top and bottom view scetch with dimensions, shows the mounting side of the panels&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_circuitdiagram.jpg" alt="Image: MobFobAmp&amp;#39;s circuit diagram as a pencil drawing. It shows a battery, fuse, on/off switch, status indicator LED the amplifier board with in- and output jacks and the speaker it drives." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Minimal added circuitry: Switch, fuse, LED, 1k Resistor to set amp gain to max&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;h2 id="listen"&gt;Listen&lt;/h2&gt;&#10;&lt;h3 id="werkzeuge"&gt;Werkzeuge&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Benötigte Werkzeuge&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Bohrmaschine&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Oberfräse, Kopierring, Parallelanschlag&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Multitool: Schleifen, Holzschnitt, Schneidkopf, Zirkel&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Optional: Handkreissäge mit Schutzbügel&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Handsäge, fein&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Zwingen&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stechbeitel, 12mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hammer&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schraubendreher, Bits&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Holzbohrer: 3mm, 5mm, 6mm, 8mm, 12mm, Senkbohrer 45°, Forstner 30mm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stahlbohrer: 3mm, Senkbohrung 45°&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Messerköpfe: 8mm gerade, 12mm gerade, 45° optional&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lötkolben, Lötzinn, Schrumpfschlauch, 1m 1x0,75mm² Leitung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="materialliste"&gt;Materialliste&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Artikel&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Größe [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Anzahl&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;~ Kosten [€]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Vorder-/Rückseite: Birke Multiplex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;L 500 B 260 B 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Oben/Unten: Birke Multiplex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;L 260 B 248 B 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Seiten: Birke Multiplex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;L 500 B 110 B 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Verstärkung &amp;amp; Halterungen&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;L 260 B 236 W 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schrauben (vorzugsweise Torx)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12 x 3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schrauben (Batteriehalter, Lenker)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;35 x 4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,20&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schrauben &amp;amp; Muttern (Lautsprecher)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;15 x M4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,20&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Platinenhalter&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;d5 x 16&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;14&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Klavierband&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;500 x 32&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;18&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Verschlüsse&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;50 x 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;9&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Fahrradschlauch (Flexible Scharniere &amp;amp; Riemen)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;28&amp;quot;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&amp;ldquo;P-Typ&amp;rdquo; Schaumstoff- oder Gummidichtung&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;500 x 9 x 7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;P-Typ&amp;quot; Schaumstoff- oder Gummidichtung&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;260 x 9 x 7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schaumstoff- oder Gummidichtung vom Typ &amp;ldquo;P&amp;rdquo;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;236 x 9 x 7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;I-Typ&amp;quot; Schaumstoffdichtung (Lautsprecher)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;600 x 9 x 2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Aluminiumschiene&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;260 x 12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Abstandhalter (Auto-unfold Funktion)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;do 30 di 4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tragegriff&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Was Sie bevorzugen&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Verstärker (z.B. Adafruit 20W Class-D Verstärker)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lautsprecher (z.B. Visaton W 170S 4Ohm)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;d170, W 81&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;35&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Batterie, 10V - 14V&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Was Sie bevorzugen&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sicherung, Schalter, LED, Widerstände: Siehe Schaltplan&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Was Sie bevorzugen&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ersatzholz z.B. als Maske&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;z.B. 300 x 300&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gummikleber oder Heißkleber (luftdichte Lücken)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20g&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="der-bau"&gt;Der Bau&lt;/h2&gt;&#10;&lt;h3 id="verlegung"&gt;Verlegung&lt;/h3&gt;&#10;&lt;p&gt;Zuerst werden alle Falze gefräst damit die Dichtungen in das Gehäuse montiert werden können.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_routerripfence_7mm.png" alt="Image: router_ripfence_7mm" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Montieren Sie den geraden &lt;code&gt;8mm&lt;/code&gt;-Fräser. Bei einer Schnitttiefe von &lt;code&gt;3mm&lt;/code&gt; setzen Sie den Parallelanschlag so ein, dass die Mitte der Fräse &lt;code&gt;7mm&lt;/code&gt; vom Anschlag entfernt ist. Achten Sie darauf, dass Sie die Oberfräse gut führen, damit Ihre Schnitte gerade und sauber werden.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Fahren Sie mit den vorderen und hinteren Platten fort (500x260x12). Fräsen Sie die langen Außenseiten der beiden Bretter.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Schneiden Sie nun die klappbaren Seiten des Schrankes zu. Nehmen Sie dafür zwei der vier Teile &amp;ldquo;500x110x12&amp;rdquo;. Die langen Seiten müssen nach oben zeigen. Verwenden Sie Ihre Oberfräse, um die Innenseiten dieser Bretter wie folgt zu schneiden:&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_bevelsides_8mm.png" alt="Image: bevel_sides_8mm" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Nun ist es an der Zeit, die umlaufenden Dichtungen von Deckel und Boden zu fertigen. Nehmen Sie die &lt;a href="https://blog.schallbert.de/mobfobamp-diy/#plans"&gt;Pläne&lt;/a&gt; zur Hilfe.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Montieren Sie den 12mm-Fräser auf die Oberfräse. Stellen Sie den Parallelanschlag neu ein, damit die Oberfräse ohne Abstand zum Anschlag fräst. Fräsen Sie einen Falz von etwa 4mm Tiefe an eine Seite von Deckel und Boden, damit hier später das Gummischarnier montiert werden kann.&#10;Schleifen Sie überstehende Fasern vorsichtig mit einem 180er-Papier ab.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/rubberhinge_cutout.jpg" alt="Image: cutout for the rubber hinge" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Verwenden Sie einen an der Oberfräse angebrachten Zirkel oder das Multitool, um den Innenring mit einem Durchmesser von &lt;code&gt;148mm&lt;/code&gt; für die Lautsprecheröffnung zu schneiden. Drei Durchgänge auf der Oberfräse sollten genug sein.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Fertigen Sie eine Schablone an. Nehmen Sie dafür ein Reststück, z.B. aus Sperrholz. Der Lochkreis der Schablone sollte wie folgt berechnet werden: Außendurchmesser des Lautsprechers plus Differenz aus Abstand Kopierring zu Achsmitte und Fräserradius. So erreichen Sie einen Enddurchmesser der Falz von &lt;code&gt;170mm&lt;/code&gt;. Es sollte jetzt so aussehen:&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/speakercutout_mask.jpg" alt="Image: mask for the speaker inlay" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Zentrieren Sie die Schablone auf dem &lt;code&gt;148mm&lt;/code&gt; Ausschnitt an der Vorderseite und befestigen Sie sie (z.B. mit doppelseitigem Klebeband). Montieren Sie den Kopierring an der Oberfräse. Stellen Sie sicher, dass Sie den &lt;code&gt;12mm&lt;/code&gt; Fräskopf verwenden und eine Fasen-Tiefe von nicht mehr als &lt;code&gt;8mm&lt;/code&gt; verwenden - der Lautsprecher hat &lt;code&gt;7mm&lt;/code&gt; Tiefe, fügen Sie &lt;code&gt;1mm&lt;/code&gt; für die Dichtung hinzu.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Schleifen Sie die Kanten ein wenig ab. Verwenden Sie das Multitool, um kleine Unebenheiten zu korrigieren, die eventuell noch vorhanden sind. Wiederholen Sie den Vorgang, bis der Lautsprecher gut sitzt.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/speakercutout_ready.jpg" alt="Image: Speaker cutout plus inlay complete" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Schneiden Sie nun die Aussparungen für die Gegenstücke der Verriegelungen. Stellen Sie die Oberfräse auf die Tiefe des Materials + Tiefe der Dichtungsnut ein (damit sie hinter der Dichtung sitzen kann) und verwenden Sie den Parallelanschlag, um die Mitte der oberen und unteren Teile anzupassen. Beachten Sie, dass Sie orthogonal durch die Nuten für die Dichtungen schneiden, die Sie zuvor hergestellt haben.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/latch_counterpart.jpg" alt="Image: latch" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="beschläge"&gt;Beschläge&lt;/h3&gt;&#10;&lt;p&gt;Die Beschläge werden vor dem Zusammenbau des Gehäuses montiert, da der Gegendruck der Dichtungen das Schließen erschwert.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Montieren Sie die Haken an der oberen und unteren Platte mit je zwei Schrauben &lt;code&gt;3x12&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Verwenden Sie Heiß- oder Gummikleber, um die Metallkanten gegen den Ausschnitt abzudichten.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/latch_counterpart_sealed.jpg" alt="Image: latch_counterpart_sealed" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie ein Stück aus dem Fahrradschlauch, Länge: &lt;code&gt;260mm&lt;/code&gt; .&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie dieses Stück parallel zur langen Seite in zwei Hälften, so dass zwei Riemen entstehen.&lt;/li&gt;&#10;&lt;li&gt;Bohren Sie 5 Löcher in gleichem Abstand in jede Aluminiumstange und benutzen Sie die &lt;code&gt;45°&lt;/code&gt; Senker um später die Schraubenköpfe dort einpassen zu können.&lt;/li&gt;&#10;&lt;li&gt;Legen Sie einen Gummiriemen unter die Alustange und befestigen Sie ihn an der oberen Rückseite der Deckelplatte.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/rubberhinge_mounted.jpg" alt="Image: rubberhinge_mounted" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Wiederholen Sie Schritt 6 mit der Bodenplatte.&lt;/li&gt;&#10;&lt;li&gt;Legen Sie das vordere, obere und untere Brett zusammen auf den Tisch, so dass sie ein großes C bilden.&lt;/li&gt;&#10;&lt;li&gt;Bringen Sie die Verschlüsse mittig am vorderen Brett an und stellen Sie sicher, dass sie die oberen/unteren &amp;ldquo;Deckel&amp;rdquo; fest geschlossen halten und die oberen/unteren Bretter in einem 90°-Winkel zur Vorderseite stehen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="positionierstücke"&gt;Positionierstücke&lt;/h3&gt;&#10;&lt;p&gt;Montieren Sie nun die Fixierungselemente, die eigentlich Regalböden an ihrem Platz halten, an der Ober- und Unterseite des Gehäuses. Sie fixieren die Seitenteile an ihrem Platz und versteifen das Gehäuse.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Markieren Sie die Stellen, an denen die Regalbodenhalter am Rand der Nut angebracht werden sollen. Die Abstände finden Sie &lt;a href="https://blog.schallbert.de/mobfobamp-diy/#plans"&gt;in den Plänen&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Verwenden Sie einen &lt;code&gt;5mm&lt;/code&gt; Holzbohrer und markieren mit Klebeband eine Tiefe von &lt;code&gt;8mm&lt;/code&gt;, damit nicht ganz durch das Brett gebohrt wird.&lt;/li&gt;&#10;&lt;li&gt;Bohren Sie die 6 Löcher in Deckel- und Bodenplatte und stecken dann die Positionierstücke dort hinein.&lt;/li&gt;&#10;&lt;li&gt;Stellen Sie sicher, dass die Kragen der Positionsstücke auf gleicher Höhe sind wie die Oberfläche der Platte (und nicht in der Nut versenkt sind).&lt;/li&gt;&#10;&lt;li&gt;Entfernen Sie vorerst die Fixierungen wieder, denn die Dichtungen müssen vorher montiert werden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="dichtungen"&gt;Dichtungen&lt;/h3&gt;&#10;&lt;p&gt;Sie machen das Gehäuse schließlich luftdicht, werden aber bei Klappvorgängen recht stark beansprucht.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Achten Sie darauf, dass die gefrästen Vertiefungen sauber und trocken sind, damit das Klebeband die volle Haltekraft entfalten kann.&lt;/li&gt;&#10;&lt;li&gt;Beginnen Sie mit je 4 Dichtungen für Boden- und Deckelplatte. Sie könnten etwas breiter sein als die Vertiefung, verwenden Sie in diesem Fall eine Plastikkarte oder ein ähnliches, weiches Werkzeug, um sie einzupassen.&lt;/li&gt;&#10;&lt;li&gt;Nehmen Sie ein Rasiermesser oder ein Messer mit einer spitzen Klinge zur Hand um Löcher in die Dichtungen zu schneiden, in die die Fixierstücke hineingedrückt werden können Orientieren Sie sich dabei an den Markierungen für die Löcher, die Sie zuvor angebracht haben.&lt;/li&gt;&#10;&lt;li&gt;Benutzen Sie einen Hammer, um die Positionierungselemente vorsichtig durch die Dichtungen zu drücken.&lt;/li&gt;&#10;&lt;li&gt;Nehmen Sie zum Schluss einen kleinen Schraubenzieher oder eine Klinge, um die Dichtung wie einen kurzen Kragen um die Fixierungen herum anzuheben - das Gehäuse muss luftdicht verschlossen werden.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/gaskets_mounted.jpg" alt="Image: gaskets_topbottom_mounted" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Bringen Sie die Dichtungen an den beiden Seitenteilen an.&lt;/li&gt;&#10;&lt;li&gt;Bringen Sie zwei Dichtungen pro Vor/Rückwand an.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="automatisches-auffalten"&gt;Automatisches Auffalten&lt;/h3&gt;&#10;&lt;p&gt;Dies ist eine optionale Funktion. Mit ihrer Hilfe klappt sich das Gehäuse von selbst auf, sodass das Fixieren mittels Boden- und Deckelplatte leichter fällt.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Nehmen Sie die vier Seitenteile zur Hand. Bohren Sie mit einem Forstnerbohrer einen &lt;code&gt;3mm&lt;/code&gt; tiefen Kreis genau in die Mitte der Innenseiten der Seitenteile.&lt;/li&gt;&#10;&lt;li&gt;Schneiden Sie zwei &lt;code&gt;70mm&lt;/code&gt; lange Stücke aus dem Gummischlauch.&lt;/li&gt;&#10;&lt;li&gt;Befestigen Sie die erhaltenen Riemen an zwei zusammengehörigen Seitenteilen mit Hilfe der &lt;code&gt;30mm&lt;/code&gt; Unterlegscheibe und einer Schraube genau in dem Kreis, der vorher ausgeschnitten wurde.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/autounfold.jpg" alt="Image: auto unfold feature" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Die Unterlegscheiben werden benötigt, um den Bereich zu vergrößern, in dem das Gummiband am Holz befestigt ist. Ohne sie würde das Gummi um die Schraube herum reißen.&lt;/p&gt;&#10;&lt;h3 id="scharniere"&gt;Scharniere&lt;/h3&gt;&#10;&lt;p&gt;Die Montage der Scharniere mit den Dichtungen ist etwas fummelig. Sie könnten ein weiteres Paar Hände oder einige Klammern benötigen.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Legen Sie die Frontplatte auf den Tisch. Setzen Sie die beiden Seitenteile mit den Dichtungen so auf der Frontplatte auf, dass sie auf der Dichtung &amp;ldquo;stehen&amp;rdquo;.&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_pianohinge_corner.png" alt="Bild: Montage der Scharniere an der inneren Ecke" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Während die Seitenteile gegen die Dichtungen der Frontplatte gedrückt werden (orange), setzen Sie die Scharniere (rot) ein und markieren die Positionen der Löcher.&lt;/li&gt;&#10;&lt;li&gt;Montieren Sie die Scharniere. Achten Sie darauf, dass kein Spalt zwischen den Seiten und der Frontplatte entsteht.&lt;/li&gt;&#10;&lt;li&gt;Wiederholen Sie den Vorgang mit den anderen beiden Seiten und der Rückwand.&lt;/li&gt;&#10;&lt;li&gt;Setzen Sie die beiden Hälften zusammen. Sie werden sich aufgrund des Drucks der Dichtung nach innen bewegen wollen, verwenden Sie also die Verstärkungsplatte, um das Gehäuse vollständig aufgeklappt zu halten. Ich habe für diesen Schritt ein Stück Holz mit den Maßen &lt;code&gt;236 x 27 x 12&lt;/code&gt; verwendet.&lt;/li&gt;&#10;&lt;li&gt;Wenn Sie die Auto-Unfold-Funktion vorbereitet haben, ist es jetzt an der Zeit, die losen Enden des Gummibandes auf Spannung zu bringen und sie auf der gegenüberliegenden Seite zu befestigen.&lt;/li&gt;&#10;&lt;li&gt;Montieren Sie schließlich die beiden Scharniere auf beiden Seiten der Schrankaußenfläche. Achten Sie darauf, dass kein Spalt zwischen den Seitenteilen entsteht.&lt;/li&gt;&#10;&lt;li&gt;Probieren Sie den Mechanismus aus. Wenn Sie das Gehäuse zusammenklappen, sollte er sich von selbst entfalten, wenn Sie die Scharniere richtig montiert haben.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="oben-und-unten"&gt;Oben und unten&lt;/h3&gt;&#10;&lt;p&gt;In diesem Schritt werden die Fixierlöcher gebohrt und das Gummischarnier montiert. Auf diese Weise werden nur zwei statt acht Verriegelungen benötigt und der mechanische Druck auf die Dichtungen bleibt hoch genug.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Halten Sie das Gehäuse aufgeklappt.&lt;/li&gt;&#10;&lt;li&gt;Tragen Sie etwas Farbe auf die bereits in Deckel- und Bodenplatte montierten Fixierungen auf.&lt;/li&gt;&#10;&lt;li&gt;markieren Sie die Stellen zum Bohren der Löcher auf der Ober- und Unterseite des Gehäuses, indem Sie diese ausrichten und dann die aufgeklappte Gehäuseschale und die Ober- und Unterteile zusammenpressen.&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/scetch_rubberhinge_rear.png" alt="rubber_hinge" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Benutzen Sie den &lt;code&gt;5mm&lt;/code&gt; Bohrer, um die Löcher an den Stirnseiten des Gehäuses entsprechend der markierten Punkte zu bohren, Tiefe wiederum &lt;code&gt;8mm&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Legen Sie Boden- und Deckelplatte auf und prüfen, ob alles gut passt. Probieren Sie die Verschlüsse aus. Wenn die Riegel nicht 100%ig passen, kann man mit der Bohrmaschine vorsichtig nachhelfen.&lt;/li&gt;&#10;&lt;li&gt;Befestigen Sie die obere und untere Platte mit den beiden verbleibenden Aluminiumstangen (grün) und dem losen Ende des Gummischarniers (schwarz) an der Rückseite des Gehäuses, wie in der Skizze gezeigt.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="funktioniert-alles"&gt;Funktioniert alles?&lt;/h3&gt;&#10;&lt;p&gt;Wow, der Verstärker ist fast fertig! Das Zusammen- und Auseinanderklappen sollte jetzt funktionieren.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/cabinet_complete.jpg" alt="montierter Schrank" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Montieren Sie den Lautsprecher, aber vergessen Sie nicht, zuerst die Dichtung einzubauen.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/speaker_assembly.jpg" alt="speaker gaskets" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Drei Maschinenschrauben &lt;code&gt;M4x15&lt;/code&gt; mit Muttern sorgen für eine sichere Befestigung des Lautsprechers.&lt;/li&gt;&#10;&lt;li&gt;Um den Verstärker zu konfigurieren sind ein paar Brücken zu verlöten. Befolgen Sie dafür die &lt;a href="https://learn.adafruit.com/adafruit-20w-stereo-audio-amplifier-class-d-max9744/analog-control" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung von Adafruit&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für eine analoge Steuerung der Verstärkung. Montieren Sie das Potentiometer oder stellen den Verstärker gleich auf 100% Verstärkung, dem &lt;a href="https://blog.schallbert.de/mobfobamp-diy/#plans"&gt;Schaltplan&lt;/a&gt; folgend.&lt;/li&gt;&#10;&lt;li&gt;Sicherung und Schalter werden eventuell noch nicht benötigt. Anstelle einer Batterie kann ein Netzteil mit Stromregelung angeschlossen werden, zum Schutz vor Kurzschlüssen auf &lt;code&gt;12V @ 1A&lt;/code&gt; einstellen.&lt;/li&gt;&#10;&lt;li&gt;Für einen Funktionstest löten Sie eine &lt;code&gt;6,3mm&lt;/code&gt;-Eingangsbuchse an ein paar Drähte oder ein altes Instrumentenkabel und stecken es in die Klemmen des linken Kanals des Verstärkers.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/electronics_assembly.jpg" alt="Image: Elektronik anschließen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Schließen Sie Ihr Instrument an und probieren den Verstärker aus.&lt;/li&gt;&#10;&lt;li&gt;Hinweis: Wenn Ihr Instrument passiv ist, d.h. keinen eingebauten Vorverstärker hat, benötigen Sie höchstwahrscheinlich einen zusätzlichen Vorverstärker oder eine aktive DI-Box, um eine akzeptable Ausgangslautstärke zu erzielen. &lt;a href="https://www.tech21nyc.com/products/sansamp-2/bassdriver-di/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Z.B. dieses Ding&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; wird die Aufgabe gut erfüllen. Die meisten Effektpedale haben aber ebenfalls Vorverstärker eingebaut - ich habe eines dafür verwendet.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="fertigstellung-des-projekts"&gt;Fertigstellung des Projekts&lt;/h3&gt;&#10;&lt;p&gt;Schauen Sie sich an, wie schlank, leicht und einfach zu transportieren Ihr Verstärker ist, wenn er zusammengefaltet ist!&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/folded_standing.jpg" alt="folded MobFobAmp, standing" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Einige zusätzliche Schritte, die ich unternommen habe, waren, dem Verstärker ein angemessenes Gehäuse zu geben (das leere Schraubengehäuse&amp;hellip;), Sicherung, Eingangsbuchse(n), Schalter, LED und die Batterie hinzuzufügen. Wird der Akku eines Elektrowerkzeugs verwendet, sind die Pole des Akkus mit normalen Kabelschuhen anzuschließen. Mit etwas Holz und einer Oberfräse ist es ein Leichtes, eine Halterung zu bauen in die man den Akku einfach einklicken kann, wie ich es bei meinem Entwurf getan habe.&lt;/p&gt;&#10;&lt;p&gt;Ein Griff könnte eine gute Idee für den Transport sein, und ein Gummiband könnte das Gehäuse in einer gefalteten Position halten und Boden sowie Deckel an der Rückseite fixieren.&lt;/p&gt;&#10;&lt;p&gt;Ich hoffe sehr, dass Sie die Lektüre / den Bau genossen haben und dass Sie so viel Spaß haben werden, dieses kleine Ding überall zum Spielen mitzunehmen, wie ich es tue.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Schallbert&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/projects/mobfobamp/"&gt;Zurück zur Projekt-Hauptseite&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Markdown zu PDF konvertieren</title><link>https://blog.schallbert.de/md-export-pdf/</link><pubDate>Wed, 09 Jun 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/md-export-pdf/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/markdown-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: markdown to pdv converter extension for VS code"&#10; title="Markdown zu PDF konvertieren" /&gt;&#10;&lt;h3 id="die-software"&gt;Die Software&lt;/h3&gt;&#10;&lt;p&gt;Ich habe die Visual Studio Code-Erweiterung &lt;a href="https://github.com/yzane/vscode-markdown-pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Yzane&amp;rsquo;s Markdown PDF&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gewählt. Einfach zu installieren, einfach zu benutzen, gute Dokumentation. Nur die Bilder&amp;hellip; naja&amp;hellip; wurden nicht angezeigt.&lt;/p&gt;&#10;&lt;h3 id="auch-die-bilder-werden-konvertiert"&gt;Auch die Bilder werden konvertiert&lt;/h3&gt;&#10;&lt;p&gt;Die relativen Links zu den Bildern unterscheiden sich zwischen Jekylls Interpretation und der Vorstellung des PDF-Generators. Ich musste die Markdown-Zieldatei in das Stammverzeichnis der Website verschieben und ein &lt;code&gt;.&lt;/code&gt; vor meinem &lt;code&gt;/assets&lt;/code&gt; hinzufügen, damit es funktioniert. Die Seitenumbrüche sind &amp;hellip; meh&amp;hellip; aber ich bin im Moment nicht bereit, mehr Arbeit hineinzustecken, also ist das gut genug für meine Zwecke.&lt;/p&gt;&#10;&lt;h3 id="ein-problem-mit-posts-die-nicht-angezeigt-werden"&gt;Ein Problem mit Posts, die nicht angezeigt werden?&lt;/h3&gt;&#10;&lt;p&gt;Gerade als ich diesen sechsten Beitrag von mir veröffentlicht habe, sehe ich, dass irgendwie nur Seite 2 der Beitragsübersicht angezeigt wird (sieht aus wie 5 Beiträge pro Seite) und ich kann nicht zu Seite 1 mit den &amp;ldquo;neuesten&amp;rdquo; Beiträgen wechseln: &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-06-09_stuckonpage2.jpg" alt="terminal output" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;. Hm.&lt;/p&gt;&#10;</description></item><item><title>Jekyll: Bilder</title><link>https://blog.schallbert.de/jekyll-images/</link><pubDate>Mon, 07 Jun 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/jekyll-images/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/git_mv-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: github move operation for renaming"&#10; title="Jekyll: Bilder" /&gt;&#10;&lt;h3 id="der-trick"&gt;Der Trick&lt;/h3&gt;&#10;&lt;p&gt;Als ich einige Bilder zu meinen Inhalten hinzufügte, war alles in Ordnung, bis ich sie auf GitHub veröffentlichte. Die meisten Bilder waren verschwunden und ich konnte nicht herausfinden, warum. Eine &lt;a href="https://stackoverflow.com/questions/41468951/images-not-displaying-in-github-pages#41469181" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Suche&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ergab, dass Github zwischen Groß- und Kleinschreibung unterscheidet und einige meiner Bildpfade nicht alle kleingeschrieben waren. Dahinter vermute ich Unterschiede zwischen Windows/Unix, da sich die beiden Betriebssysteme hier unterschiedlich verhalten.&lt;/p&gt;&#10;&lt;h3 id="ändern-von-ordnernamen-in-git"&gt;Ändern von Ordnernamen in Git&lt;/h3&gt;&#10;&lt;p&gt;Es funktionierte danach immer noch nicht, weil Git meine Umbenennung nicht erkannte. Ich musste zuerst den umbenannten Ordner in einem anderen Unterverzeichnis erstellen, &lt;code&gt;git mv folder/tArGet* target&lt;/code&gt; verwenden, um diesen an einen temporären Ort zu bringen, &lt;code&gt;tArGet&lt;/code&gt; löschen und &lt;code&gt;target&lt;/code&gt; an die Stelle verschieben, an der &lt;code&gt;tArGet&lt;/code&gt; vorher war. Nur dies hat das Problem behoben und jetzt sind github und meine lokalen Dateien wieder synchron.&lt;/p&gt;&#10;</description></item><item><title>Jekyll Inhaltsverzeichnis</title><link>https://blog.schallbert.de/jekyll-toc/</link><pubDate>Sun, 06 Jun 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/jekyll-toc/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/thissite/desktop-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Schallbert&amp;#39;&amp;#39;s Desktop"&#10; title="Jekyll Inhaltsverzeichnis" /&gt;&#10;&lt;h2 id="inhaltsverzeichnis"&gt;Inhaltsverzeichnis&lt;/h2&gt;&#10;&lt;p&gt;Es gibt eine einfache Lösung, um das Inhaltsverzeichnis der Seite zu fixieren:&#10;&lt;code&gt;toc_sticky: true&lt;/code&gt; zu den Standardeinstellungen oder dem Inhaltsverzeichnis der jeweiligen Seite hinzufügen. Wie ich das Schlüsselwort gefunden habe? Durch die Suche nach &amp;ldquo;toc&amp;rdquo; im Stammverzeichnis des Themes und ein wenig Scrollen durch die Layout-Skizzen. Ich änderte die ursprüngliche Seitennavigation in der linken Seitenleiste und tauschte sie gegen eine Dummy-Implementierung einer Galerie / Audio / Video-Inhaltsanzeige aus.&lt;/p&gt;&#10;&lt;h3 id="toc-auf-der-linken-seite"&gt;ToC auf der linken Seite&lt;/h3&gt;&#10;&lt;p&gt;Um zu verstehen, wie man das Inhaltsverzeichnis anders als in diesem Theme vorgegeben auf der linken Seite erstellt, lesen Sie bitte &lt;a href="https://blog.schallbert.de/projects/thissite/#sidebar"&gt;dieses Kapitel&lt;/a&gt; des mySite-Projekts weiter.&lt;/p&gt;&#10;&lt;h2 id="profil-des-autors"&gt;Profil des Autors&lt;/h2&gt;&#10;&lt;p&gt;Ich habe beschlossen, dass mein &lt;code&gt;author_profile&lt;/code&gt; nur auf Seiten wie &lt;a href="https://blog.schallbert.de/about/"&gt;about&lt;/a&gt;, &lt;a href="https://blog.schallbert.de/legal/"&gt;legal info&lt;/a&gt; und nicht auf den typischen Inhaltsseiten angezeigt werden soll. Also schrieb ich &lt;code&gt;author_profile: false&lt;/code&gt; für die Seiten, auf denen es nicht angezeigt werden sollte - ohne Erfolg. Warum?&#10;Ich hatte es in der &lt;code&gt;config.yml&lt;/code&gt;, Abschnitt &lt;code&gt;type defaults&lt;/code&gt; gesetzt, aber es sollte eigentlich in der Frontmatter der einzelnen Dateien stehen.&lt;/p&gt;&#10;</description></item><item><title>Jekyll `-incremental` Option</title><link>https://blog.schallbert.de/jekyll-items-update/</link><pubDate>Thu, 03 Jun 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/jekyll-items-update/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/jekyll-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Jekyll logo"&#10; title="Jekyll `-incremental` Option" /&gt;&#10;&lt;h3 id="die-kategorie-neueste-beiträge"&gt;die Kategorie &amp;ldquo;Neueste Beiträge&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Ich habe den Grund herausgefunden, warum mein letzter Artikel auf der Landing Page auf meinem lokalen Rechner nicht angezeigt wurde, aber auf dem Server problemlos funktionierte. Es liegt daran, dass ich lokal den Befehl &lt;code&gt;$ bundle exec jekyll serve --incremental&lt;/code&gt; mit der aktiven &lt;code&gt;--incremental&lt;/code&gt; build Option verwende, um die Rebuilds für schnellere Testläufe zu beschleunigen. Aber das Problem liegt hier:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-06-03_jekyll_incremental.jpg" alt="image: terminal output" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Bei einer inkrementellen Ausgabe scheint Jekyll den Ordner &lt;code&gt;_posts&lt;/code&gt; nicht nach neuen Einträgen zu durchsuchen, da die Seite, auf der sie erscheinen, nicht geändert sondern neu angelegt wurde.&#10;Mein Fazit ist, dass ich die Option &lt;code&gt;--incremental&lt;/code&gt; in Zukunft mit Vorsicht und nur für Änderungen bereits bestehender Artikel verwenden werde.&lt;/p&gt;&#10;&lt;h3 id="die-bilder"&gt;Die Bilder&lt;/h3&gt;&#10;&lt;p&gt;Ich habe an meinen Bildern unter &lt;code&gt;assets/images&lt;/code&gt; gearbeitet. Sie benötigen recht viel Speicherplatz, so dass die Ladezeiten der Seite zu lang werden. Ich benutze &lt;a href="https://www.getpaint.net/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;paint.net&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, um sie auf eine Größe zu schrumpfen, die ich für vertretbar halte, und speichere sie als &lt;code&gt;.jpg&lt;/code&gt; mit maximaler Kompression und einigen Kompromissen bei der Bildqualität. Auf diese Weise benötigen die meisten meiner Bilder weniger als 10% der ursprünglichen Größe in &lt;code&gt;kB&lt;/code&gt;.&lt;/p&gt;&#10;</description></item><item><title>MinimalMistakes theme - Seitendesign</title><link>https://blog.schallbert.de/jekyll-open-items/</link><pubDate>Wed, 02 Jun 2021 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/jekyll-open-items/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/mmistakes-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: MinimalMistakes logo"&#10; title="MinimalMistakes theme - Seitendesign" /&gt;&#10;&lt;h2 id="die-landing-page"&gt;Die &amp;ldquo;Landing Page&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;Endlich. Indem ich den Layout-Typ auf &lt;code&gt;single&lt;/code&gt; änderte, konnte ich ein Header-Bild als Typ &lt;code&gt;overlay&lt;/code&gt; einfügen!&#10;Die Schaltflächen werden jetzt innerhalb des Bildes angezeigt. Das Bild ändert sich automatisch mit der Displaygröße und -ausrichtung aufgrund der im Ordner &lt;code&gt;_sass&lt;/code&gt; definierten visuellen Designs.&lt;/p&gt;&#10;&lt;h3 id="bild-der-hauptseite"&gt;Bild der Hauptseite&lt;/h3&gt;&#10;&lt;p&gt;Ich war dabei zu verzweifeln: meine &amp;ldquo;Landing Page&amp;rdquo; zeigte einfach kein Bild mehr an, egal wie sehr ich versuchte, die &lt;code&gt;home.md&lt;/code&gt; Datei zu ändern. Der Grund dafür war, dass es eine Standard-&amp;ldquo;index.html&amp;rdquo;-Datei gab, die mit der Vorlage geliefert wurde und die anscheinend Vorrang hatte. Nun habe ich meine Änderungen einfach direkt dort eingefügt - funktioniert einwandfrei.&#10;Übrigens, die Höhe des Overlay-Bildes wird automatisch in Abhängigkeit von Inhalt und Höhe des Overlay-Textes gewählt.&lt;/p&gt;&#10;&lt;h3 id="hinzufügen-einer-seitennavigation"&gt;Hinzufügen einer Seitennavigation&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Einfache Lösung:&lt;code&gt;toc: true&lt;/code&gt; in die YAML-Struktur des Markdown-Dokument für den gewünschten Artikel einfügen. Leider wird das Inhaltsverzeichnis statisch auf der Webseite angezeigt, sodass es beim Herunterscrollen verschwindet.&lt;/li&gt;&#10;&lt;li&gt;Schwierige Lösung: Fügen Sie die Seitennavigation in die linke Seitenleiste ein. Wie das geht? &lt;a href="https://blog.schallbert.de/projects/thissite/#navigation"&gt;Siehe hier&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="neueste-beiträge"&gt;Neueste Beiträge&lt;/h3&gt;&#10;&lt;p&gt;Im Moment erscheinen meine Artikel nicht in der Spalte &amp;ldquo;Neueste Beiträge&amp;rdquo; auf der Hauptseite. Das liegt daran, dass ich den Dienst neu starten müsste, weil die Beiträge statisch generiert werden &lt;a href="https://blog.schallbert.de/jekyll-items-update/"&gt;Richtig, Jekyll ist ein &lt;strong&gt;statischer&lt;/strong&gt; Seitengenerator.&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>🎄 Steckbrett-Lagerfeuer</title><link>https://blog.schallbert.de/projects/fireplace/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/fireplace/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/fireplace/fireplace-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: LED-Fireplace, Arduino-controlled"&#10; title="🎄 Steckbrett-Lagerfeuer" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeit: Anfänger 1/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: 8&amp;hellip;25€&lt;/li&gt;&#10;&lt;li&gt;Zeit ~1h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="überblick"&gt;Überblick&lt;/h2&gt;&#10;&lt;p&gt;Dies ist ein schnelles und einfaches Projekt, das zeigt, wie man mit einem Microcontrollerboard und einigen LEDs eine warme und gemütliche Wohnzimmeratmosphäre schaffen kann. An eine Powerbank angeschlossen, wird diese kleine Bastelei den ganzen Abend leuchten und eine warme und gemütliche Atmosphäre schaffen. Ich habe Teile der Idee für dieses Projekt dem Buch &amp;ldquo;Tiny AVR for the evil Genius&amp;rdquo; entliehen, in dem ich viel gelesen habe, als ich vor Jahren mit dem Entwurf eingebetteter Software in &lt;code&gt;C&lt;/code&gt; begann.&lt;/p&gt;&#10;&lt;p&gt;Die Projektkosten hängen von den gewählten Komponenten ab. Wenn ein original Arduino verwendet wird, liegen sie am oberen Ende des Bereichs. So kann man den hart arbeitenden Entwicklern und Ingenieuren von &lt;a href="https://www.arduino.cc/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Arduino&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Anerkennung zollen, die Open-Source-Lösungen für uns alle schaffen 🥳.&lt;/p&gt;&#10;&lt;h2 id="die-hardware"&gt;Die Hardware&lt;/h2&gt;&#10;&lt;p&gt;Natürlich kann man jedes Mikrocontroller-Board verwenden, das mindestens 8 digitale Ausgänge und 1 analogen Eingang unterstützt. In meinem Beispiel verwende ich ein &lt;a href="https://www.arduino.cc/hardware#nano-family" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Arduino Nano&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (Nachbau), das ich irgendwo herumfliegen hatte. Zeit für ein Upgrade auf das &amp;ldquo;Original&amp;rdquo;&amp;hellip;&#10;Des Weiteren benötigt man ein Steckbrett, ein paar Drähte und acht LEDs. Ich empfehle, 5 warmweiße LEDs, 2 gelbe LEDs und 1 rote LED mit ähnlicher Helligkeit (Lumenzahl) zu verwenden, um Farben zu erhalten, die einem echten Feuer ähnlich sind.&lt;/p&gt;&#10;&lt;h2 id="die-software"&gt;Die Software&lt;/h2&gt;&#10;&lt;p&gt;Die Software erzeugt einen flackernden Lichteffekt mit unterschiedlicher Geschwindigkeit.&lt;/p&gt;&#10;&lt;h3 id="einrichtung"&gt;Einrichtung&lt;/h3&gt;&#10;&lt;p&gt;Beim Start definiere ich in &lt;code&gt;setup()&lt;/code&gt; die Pins, an die die LEDs angeschlossen sind, als Ausgänge.&#10;Ich lasse einen offenen Analog-Pin einmal auslesen und nehme dessen (undefinierten) Wert als Seed für den später besprochenen Pseudo-Zufallszahlengenerator.&lt;/p&gt;&#10;&lt;h3 id="schleife"&gt;Schleife&lt;/h3&gt;&#10;&lt;p&gt;In der Endlosschleife berechnet das Programm eine neue Zufallszahl und übergibt diese dann an die nachgeschalteten Funktionen. Hier erzeugt ein Zufallszahlengenerator einen Wert, der dann verwendet wird, um&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;zu bestimmen, welche der acht LEDs leuchten soll&lt;/li&gt;&#10;&lt;li&gt;die Zeit festzulegen, für die dieser LED-Status beibehalten werden soll.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Schließlich werden die LEDs entsprechend gesetzt und die Schleife wiederholt sich am Anfang.&lt;/p&gt;&#10;&lt;h3 id="linear-rückgekoppeltes-schieberegister"&gt;Linear rückgekoppeltes Schieberegister&lt;/h3&gt;&#10;&lt;p&gt;Der Zufallszahlengenerator ist als sogenanntes &lt;a href="https://en.wikipedia.org/wiki/Linear-feedback_shift_register" target="_blank" rel="noopener noreferrer" class="external-link"&gt;linear feedback shift register (LSFR)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit einer Länge von 32bit implementiert (wiederholt sein Muster nach max. 2^32 Schritten). Diese Implementierung verwendet den &amp;ldquo;Galois-Typ&amp;rdquo; von LFSRs.&lt;/p&gt;&#10;&lt;h3 id="galois-lfsr-implementierung"&gt;Galois LFSR Implementierung&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;loop&lt;/span&gt;() &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iRandNum &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (iRandNum &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;^&lt;/span&gt; (&lt;span style="color:#f92672"&gt;-&lt;/span&gt;(iRandNum &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1u&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0xd0000001u&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [...]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In dieser Codezeile passiert eine Menge, also schauen wir sie uns mal genauer an.&lt;/p&gt;&#10;&lt;p&gt;Wesentlich für diese Art von Zufallszahlengeneratoren ist die &lt;a href="https://en.wikipedia.org/wiki/XOR_gate" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;XOR&lt;/code&gt;-Operation&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, die durch ein &lt;code&gt;^&lt;/code&gt; in der Mitte der Codezeile angezeigt wird. XOR&amp;quot; bedeutet &amp;ldquo;eXclusive OR&amp;rdquo;, das nur dann eine logische 1 ausgibt, wenn die logischen Eingänge A und B &lt;em&gt;unterschiedlich&lt;/em&gt; sind. Wenn &amp;ldquo;A&amp;rdquo; und &amp;ldquo;B&amp;rdquo; beide &amp;ldquo;0&amp;rdquo; oder beide &amp;ldquo;1&amp;rdquo; sind, wird dieses Gatter &amp;ldquo;0&amp;rdquo; ausgeben.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Beispiel für binäres XOR:&lt;/strong&gt;&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt; A B Ergebnis&#10;0b00001111 ^ 0b00110011 = 00111100&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Links vom &lt;code&gt;XOR&lt;/code&gt;-Operator wird die aktuelle Zufallszahl um &lt;code&gt;1&lt;/code&gt; nach rechts verschoben (&lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;). Dabei wird ihr gesamter Inhalt wird um ein Bit geschoben. In numerischer Hinsicht bedeutet dies, dass der Wert ganzzahlig durch zwei dividiert wird.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Beispiel für binäres SHIFT:&lt;/strong&gt;&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt; A B Ergebnis Als Zahl:&#10;0b00001111 &amp;gt;&amp;gt; 1 = 0b00000111 15 &amp;gt;&amp;gt; 1 = 7&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;OK, analysieren wir die obige Zeile Code weiter.&#10;Direkt neben dem &lt;code&gt;XOR&lt;/code&gt;-Operator findet eine weitere Operation statt: Ein logisches &lt;code&gt;AND&lt;/code&gt; (&lt;code&gt;&amp;amp;&lt;/code&gt;) zwischen unserer aktuellen Zufallszahl und &lt;code&gt;1u&lt;/code&gt;, was &amp;ldquo;unsigned 1&amp;rdquo; bedeutet. Dabei werden alle höheren Bits des Zufallszahlengenerators maskiert. Das Ergebnis dieser Aktion ist eine einfache &amp;ldquo;0&amp;rdquo;, wenn die Zahl eine &amp;ldquo;0&amp;rdquo; als niedrigstwertiges Bit hat usw.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Beispiel für binäres UND:&lt;/strong&gt;&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt; A B Ergebnis &#10;0b00001111 &amp;amp; 1 = 0b00000001&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Die Negation am Anfang dieses Terms bedeutet, dass, wenn das Ergebnis des vorherigen &lt;code&gt;&amp;amp;&lt;/code&gt; &lt;code&gt;0&lt;/code&gt; war, der gesamte Term wieder &lt;code&gt;0b00000000...&lt;/code&gt; sein wird. Wenn das Ergebnis &lt;code&gt;-1&lt;/code&gt; lautet, sieht der 2er-Komplement-Binärcode stattdessen wie folgt aus: &lt;code&gt;0b1111111...&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Dies ist eine einfache Möglichkeit, einen einzelnen booleschen Wert auf einen beliebigen längeren Datentyp anzuwenden, ohne eine Verzweigungslogik wie bei einer &lt;code&gt;if()&lt;/code&gt;-Klausel zu verwenden.&lt;/p&gt;&#10;&lt;p&gt;Und schließlich gibt es ein weiteres logisches &lt;code&gt;AND&lt;/code&gt; zwischen diesem Begriff und einer festen Bitmaske &lt;code&gt;0xd0000001u&lt;/code&gt;. Letztere sind die &amp;ldquo;Anzapfungen&amp;rdquo; des LFSR, d.h. welche Bits des aktuellen Wertes in das System zurückgeführt werden. Die Aufschlüsselung dieses Hex-Wertes in Boolesche Werte zeigt: 0xd = 0b1101&amp;quot;, d.h. die Bits 32, 31, 29 und 1 werden mit der aktuellen Zufallszahl maskiert, so dass nur ihre Werte unberührt bleiben, während alle anderen auf &amp;ldquo;0&amp;rdquo; gesetzt werden.&lt;/p&gt;&#10;&lt;h3 id="galois-lfsr-zusammenfassung"&gt;Galois LFSR Zusammenfassung&lt;/h3&gt;&#10;&lt;p&gt;Puh, das war eine Menge. Es ist Zeit für ein paar Minuten Pause.&lt;/p&gt;&#10;&lt;p&gt;Gut, fassen wir die Codezeile oben nochmal zusammen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;iRandNum &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (iRandNum &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;^&lt;/span&gt; (&lt;span style="color:#f92672"&gt;-&lt;/span&gt;(iRandNum &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1u&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0xd0000001u&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Bedeutung: &amp;ldquo;Wenn das niedrigstwertige Bit der aktuellen Zufallszahl Null ist, dann verschiebe die Zufallszahl einfach nach rechts. Andernfalls XOR anwenden mit den abgegriffenen Bits der aktuellen Zufallszahl.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;LFSRs sind weit verbreitet in der &lt;a href="https://courses.cs.washington.edu/courses/cse466/12au/calendar/06-Modulation-posted.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;drahtlosen Kommunikationstechnik&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, sie sind interessant für &lt;a href="https://stackoverflow.com/questions/25415724/understanding-two-different-ways-of-implementing-crc-generation-with-lfsr" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Prüfsummenberechnungen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, [Kryptographen](https://medium. com/@czapfel/an-introduction-to-lfsrs-for-cryptography-bf2602640e91) und können sogar bei &lt;a href="https://evoniuk.github.io/posts/pitfall.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Programmierspielen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; nützlich sein, z.B. bei der Erstellung prozeduraler Karten. LFSRs sind einfach zu implementieren (wie man oben sieht, benötigen sie im Extremfall sogar nur eine einzige Codezeile!) und verbrauchen nicht viele Ressourcen. Hier ist ein hervorragender &lt;a href="https://datagenetics.com/blog/november12017/index.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Blogpost&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, der sich eingehend mit LFSRs beschäftigt, wenn Sie eher der visuelle Typ sind (und weniger der logische).&#10;Nachteilig ist, dass ihre &amp;ldquo;Zufallsqualität&amp;rdquo; schlecht ist, weil sie deterministisch sind.&lt;/p&gt;&#10;&lt;p&gt;Alles in allem fand ich das Thema so interessant, dass ich mich entschlossen habe, hier einen LFSR manuell zu implementieren, anstatt die Arduino-Funktion &lt;code&gt;random()&lt;/code&gt; zu verwenden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://www.c64-wiki.de/images/9/9d/Pitfall_Animation2.gif" alt="Gif: Pitfall! Game scene" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Pitfall! Spielszene: prozedurales Leveldesign mittels LFSR&lt;/span&gt;&lt;a href="https://www.c64-wiki.de/wiki/Datei:Pitfall_Animation2.gif#file" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="ein-problem-bleibt"&gt;Ein Problem bleibt&lt;/h3&gt;&#10;&lt;p&gt;Nun, wenn Sie die Codezeile vollständig verstanden haben, sehen Sie, was passiert, wenn die Zufallszahl nur Nullen enthält.&lt;/p&gt;&#10;&lt;p&gt;Er wird nie wieder aus diesem Zustand herauskommen 💀.&lt;/p&gt;&#10;&lt;p&gt;Deshalb sollte man diese Art von LFSR niemals mit &amp;ldquo;0&amp;rdquo; initialisieren.&lt;/p&gt;&#10;&lt;h3 id="ansteuerung-der-leds"&gt;Ansteuerung der LEDs&lt;/h3&gt;&#10;&lt;p&gt;Der Rest der Software hat viel mehr Zeilen, macht aber auch viel mehr langweilige Sachen. Sie nimmt den generierten 32-Bit-Zufallswert und lässt ihn an den 8 LED-Ausgängen vorbeilaufen, d.h. jede LED &amp;ldquo;sieht&amp;rdquo; jedes Bit des Wertes. Zwischen jeder dieser Iterationen wird eine zufällige Verzögerung verwendet, so dass die Flackergeschwindigkeit auf eine Änderungsrate begrenzt ist, die wir Menschen noch wahrnehmen können.&lt;/p&gt;&#10;&lt;h2 id="die-hardware-1"&gt;Die Hardware&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/fireplace/fireplace_detail.jpg" alt="Image: fireplace breadboard design detail view" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Zum Glück ist die Hardware einfacher zu erklären als die Software:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Ein Arduino Nano-Board ist mit einem Prototyping-Board verbunden.&lt;/li&gt;&#10;&lt;li&gt;Anoden der acht LEDs &lt;code&gt;(+)&lt;/code&gt; sind mit den digitalen Ausgängen &lt;code&gt;D2...D9&lt;/code&gt; verbunden.&lt;/li&gt;&#10;&lt;li&gt;Ihre Kathoden werden mit Jumpern gebrückt und mit dem &amp;ldquo;GND&amp;rdquo; des Arduino verbunden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Dies ist die einfachste Konstruktion, die mir für diese Aufgabe einfiel.&lt;/p&gt;&#10;&lt;h2 id="verbesserungen"&gt;Verbesserungen&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Jede LED sollte einen Vorwiderstand bekommen.&#10;&lt;ul&gt;&#10;&lt;li&gt;In meinem Design ist der Strom nur durch den maximalen Strom begrenzt, den der Arduino Pin treiben kann.&lt;/li&gt;&#10;&lt;li&gt;Entweder kann die LED deutlich mehr als &lt;code&gt;40mA&lt;/code&gt; Spitzenstrom verkraften oder sie wird unweigerlich irgendwann durchbrennen.&lt;/li&gt;&#10;&lt;li&gt;Bei der Berechnung des Widerstands sollte man sich an das Datenblatt der LED halten, da verschiedene LED-Farben unterschiedliche Spannungen haben.&lt;/li&gt;&#10;&lt;li&gt;Beispiel für eine Widerstandsberechnung bei einer Versorgungsspannung von &lt;code&gt;5V&lt;/code&gt; für eine Warmweiße LED: &lt;code&gt;20mA @ 3.1V --&amp;gt; R = U/I = (Usup-Uled) / I = 1.9 / 0.02 = 95Ohm&lt;/code&gt;. Nächster Widerstand aus der E6-Reihe &lt;code&gt;100Ohm&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Jede LED könnte einen Parallelkondensator erhalten.&#10;&lt;ul&gt;&#10;&lt;li&gt;Dies würde den Flackereffekt glätten.&lt;/li&gt;&#10;&lt;li&gt;Man könnte Videos aufnehmen, in denen die Kamera nicht durch das Flackern der LEDs gestört wird. Das wäre doch schön.&lt;/li&gt;&#10;&lt;li&gt;Beispiel Kondensatorberechnung: Durchschnittlicher Strom im ausgeschalteten Zustand &lt;code&gt;20mA&lt;/code&gt;, erlaubter Spannungsabfall &lt;code&gt;0.6V&lt;/code&gt; innerhalb von &lt;code&gt;1ms&lt;/code&gt;. Vorgeschlagene Kapazität ist dann &lt;code&gt;20mA * 1ms / 0.6V = 33uF&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="zeit-für-eine-diy-replik"&gt;Zeit für eine DIY-Replik?&lt;/h2&gt;&#10;&lt;p&gt;Bitteschön!&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/led-fireplace-diy/"&gt;Steckbrett-Lagerfeuer DIY Anleitung&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>1 Jahr CNC 🎂</title><link>https://blog.schallbert.de/projects/one-year-zerspanobert/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/one-year-zerspanobert/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/machine-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Zerspanobert CNC machine"&#10; title="1 Jahr CNC 🎂" /&gt;&#10;&lt;h2 id="ein-jahr--meine-portalfräsmaschine"&gt;Ein Jahr &lt;a href="https://blog.schallbert.de/portal-milling-build/"&gt;&amp;lsquo;Zerspanobert&amp;rsquo;&lt;/a&gt;, meine Portalfräsmaschine&lt;/h2&gt;&#10;&lt;p&gt;Wir schreiben März 2023. Meine CNC-Maschine ist seit einem Jahr in Betrieb. Es war ein gutes Jahr. Ich habe eine Menge gelernt und hatte viel Spaß beim Schreiben von Artikeln über meine Fortschritte. Natürlich konnte ich nicht jedes Projekt das ich in dieser Zeit durchgeführt habe dokumentieren bloggen, dennoch soll dieser Artikel das Jahr ein wenig Revue passieren lassen&lt;/p&gt;&#10;&lt;h3 id="die-maschine"&gt;Die Maschine&lt;/h3&gt;&#10;&lt;p&gt;Ich kann inzwischen sagen, dass ich die richtige Maschine für meinen Bedarf ausgewählt habe. Es handelt sich um die zweitgünstigste Maschine von Sorotec. Sie bietet viele Eigenschaften &amp;ldquo;gehobenerer&amp;rdquo; Portalfräsmaschinen, wie z.B. Kugelumlaufspindeln, &lt;code&gt;3Nm&lt;/code&gt; Stepper, &lt;code&gt;48V&lt;/code&gt; Kleinspannungsschiene, vorgespannte Linearführungen. Um den Preis moderat zu halten, wurde allerdings auf andere Dinge verzichtet. So besitzt sie weder Riemenantriebe für die Schrittmotoren um Platz zu sparen noch doppelte Linearführungen an der Z-Achse oder zusätzliche Versteifungen für Portal, Z-Achse und Hauptrahmen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/z_axis.jpg" alt="Bild: Detailansicht Z-Achse" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Deshalb muss man wissen, welche Lasten grenzwertig sind und sich etwas zurückhalten. Aber das gilt wahrscheinlich auch für steifere Maschinen - sie heben nur die Grenzen an. Was bei meiner Einstellung schnellere Vorschübe oder höhere Schnitttiefen verhindert, ist der Oberfräsenmotor und die Art und Weise, wie er an der Z-Achse befestigt ist.&lt;/p&gt;&#10;&lt;p&gt;Der Motor ist eindeutig überfordert mit&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Birke Multiplex, Vollnutenfräsen, 6mm Fräser 2-schneidig, &lt;code&gt;S24000&lt;/code&gt;, &lt;code&gt;Z+12mm&lt;/code&gt;, &lt;code&gt;F4000&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;HPL, Vollnutenfräsen, 6mm Fräser 2-Schneide, &lt;code&gt;S24000&lt;/code&gt;, &lt;code&gt;Z+6.5mm&lt;/code&gt;, &lt;code&gt;F3500&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Aluminium AlMg1, Vollnutenfräsen, 6mm Fräser einschneidig, &lt;code&gt;S24000&lt;/code&gt;, &lt;code&gt;Z+3mm&lt;/code&gt;, &lt;code&gt;F1600&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Wenn jedoch Schnitttiefe und/oder den Vorschub reduziert wird, kann die Maschine die Arbeit sehr gut ausführen.&#10;Die Genauigkeit der Maschine ist ziemlich gut; die wirklich begrenzenden Faktoren sind hier die manuelle Bestimmung des Werkstücknullpunkts sowie der Werkzeuglängensensor aber nicht die Maschine selbst.&lt;/p&gt;&#10;&lt;p&gt;Der Vakuumtisch eröffnete die Welt der feinen Gravuren mit Graviersticheln und V-Fräsern. Das Problem war vorher, dass die Verformung des Werkstücks und Ungenauigkeiten des Arbeitstisches dazu führten, dass die Gravuren von sehr unterschiedlicher Qualität waren und größere Toleranzen in der Breite aufwiesen, als ich erwartet hatte. Diese Probleme gibt es jetzt nicht mehr, ebenso wenig wie die zusätzliche Arbeit, Vorrichtungen für kleine oder sperrige Werkstücke herzustellen. Das Halten und Spannen wurde um ein Vielfaches einfacher. Außerdem konnte ich die Z-Sicherheitshöhe reduzieren, was Fräsarbeiten um bis zu 20 % beschleunigte.&lt;/p&gt;&#10;&lt;p&gt;Ich konzentrierte mich darauf, die Bearbeitungszeit so weit wie möglich zu reduzieren, damit ich nicht mehr Zeit als nötig mit der Überwachung der Maschine in der kalten Werkstatt verbringen musste. Deshalb habe ich die Maschine und die Fräser absichtlich an ihre Grenzen gebracht was Eilganggeschwindigkeit, Vorschub und Schnitttiefe angeht. Als ich diese Grenzen herausgefunden hatte, drosselte ich die Parameter um 20-30 %, um auf Nummer sicher zu gehen und die Wiederholgenauigkeit hoch zu halten.&lt;/p&gt;&#10;&lt;h3 id="upgrades"&gt;Upgrades&lt;/h3&gt;&#10;&lt;p&gt;Ich habe die Maschine mit einem &lt;a href="https://blog.schallbert.de/why-vacuum-table/"&gt;Vakuumtisch&lt;/a&gt; aufgerüstet und konnte viel über Haltekräfte und Volumenstrom lernen, was zü meinem Artikel zur &lt;a href="https://blog.schallbert.de/cnc-vacuum-pumps/"&gt;Auswahl der geeigneten Vakuumlösung&lt;/a&gt; führte. Ich habe Schwerschaummatten zur Vibrations- und Schalldämmung installiert. Man kann sie im Bild unten etwas schlecht erkennen, aber der gesamte Arbeittisch ist mit Dämmmatten unterfüttert.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/accessories.jpg" alt="Image: Accessories and machine controller" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Außerdem habe ich eine Absauganlage mit Zyklonabscheider installiert, die während des Fräsvorganges läuft. Die Menge an Spänen, die die Fräse produziert ist wirklich erstaunlich und so bin ich froh, dass ich die Anlage bereits seit Beginn meiner Arbeiten zur Verfügung habe.&lt;/p&gt;&#10;&lt;p&gt;Bald werde ich eine HF-Spindel statt meinem Oberfräsmotor einbauen und so Vibrationen, Toleranzen und Geräuschpegel weiter verringern. Letztendlich sollte ich damit die Vorschubwerte erreichen, die ich oben angegeben habe.&lt;/p&gt;&#10;&lt;h3 id="materialien"&gt;Materialien&lt;/h3&gt;&#10;&lt;p&gt;Ich habe mit Hartholz, &lt;a href="https://blog.schallbert.de/negative-carving-with-estlcam/#engrave"&gt;Multiplex&lt;/a&gt;, &lt;a href="https://blog.schallbert.de/projects/cnc_spoilboard/"&gt;MDF&lt;/a&gt;, Acrylglas (PMMA), &lt;a href="https://hobbyline.info/forum/index.php?thread/717-hobbyglas-polystyrol-fr%C3%A4sen/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Polystyrol&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://blog.schallbert.de/cnc-router-overload/"&gt;Hochdrucklaminaten (HPL)&lt;/a&gt; und &lt;a href="https://blog.schallbert.de/cut-dibond/"&gt;Aluminium-Komposit&lt;/a&gt; gearbeitet.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/materials.jpg" alt="Image: Material samples that I worked with" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Bei all diesen Werkstoffen musste ich lernen, wie sich die Materialien verhalten und mit welchen Parametern die Fräser betrieben werden müssen, um qualitativ hochwertige Ergebnisse zu erzielen. Einige Materialien waren so anspruchsvoll, dass meine Maschine überlastet war, bei anderen hatte ich &lt;a href="https://blog.schallbert.de/cnc-vibrates/"&gt;Vibrationsprobleme&lt;/a&gt; oder Probleme, &lt;a href="https://blog.schallbert.de/cut-dibond-tests/"&gt;die gewünschte Qualität zu erreichen&lt;/a&gt;. Aber ich konnte immer aus Fehlschlägen lernen und meine Abläufe verbessern, um sie in Zukunft zu vermeiden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/pmma.jpg" alt="Bild: Acryl-Absaugschlauchhalter" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das Bild oben zeigt eine Absaugschlauchklemme aus PMMA (Acrylglas), die ich an der Z-Achse meiner CNC angebracht habe. Ich habe recht lange dafür gebraucht, die minimalen Fasen genau richtig hinzubekommen und Schlichtvorgänge angewendet, wo ich glaubte, dass sie eine bessere Oberflächengüte bieten könnten.&lt;/p&gt;&#10;&lt;h3 id="software"&gt;Software&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/assets/images/qr-codengrave/app-thumb.jpg"&gt;Bild: Hauptbildschirm der Qr-codengrave-App&lt;/a&gt;{: .align-right}&#10;Ich habe die &lt;a href="https://blog.schallbert.de/macros-for-cnc/"&gt;Makros&lt;/a&gt; der Maschine sowie &lt;a href="https://blog.schallbert.de/portal-milling-setup/"&gt;die Kinematikparameter&lt;/a&gt; optimiert, und sogar eine vollständige Anwendung, mit der die CNC &lt;a href="https://blog.schallbert.de/projects/qr-codengrave/"&gt;QR-Codes&lt;/a&gt; in praktisch jedes Plattenmaterial gravieren kann, programmiert. Diese QR-Codes werden von Handykameras bestens erkannt.&lt;/p&gt;&#10;&lt;p&gt;Als meine Gravurprojekte immer komplexer wurden, stürzte mein CAM-System bei der Berechnung der Pfade für die Bearbeitung mehrerer Exemplare ständig ab. Daher musste ich die Aneinanderreihung manuell durchführen, was mich dazu brachte, mich mit Koordinatenverschiebungsbefehlen wie &lt;code&gt;G54...G59.3&lt;/code&gt;, &lt;code&gt;G68&lt;/code&gt; und &lt;code&gt;G92&lt;/code&gt; zu beschäftigen, die, wenn sie nicht richtig verstanden werden, die Werkstücke arg in Mitleidenschaft ziehen.&#10;Dies half mir, die numerische Steuerung und ihre Interpretersprache besser zu verstehen.&lt;/p&gt;&#10;&lt;p&gt;Nicht zuletzt war die Lernkurve, die ich durch die Arbeit mit &lt;a href="https://blog.schallbert.de/freecad-get-started/"&gt;FreeCAD&lt;/a&gt; und &lt;a href="https://blog.schallbert.de/negative-carving-with-estlcam/"&gt;Estlcam&lt;/a&gt; durchlaufen musste, ziemlich steil. Aber da musste ich durch, sonst hätte ich meine Teile nicht fertigen können.&lt;/p&gt;&#10;&lt;h3 id="hilfe-und-unterstützung"&gt;Hilfe und Unterstützung&lt;/h3&gt;&#10;&lt;p&gt;Zum Glück hatte ich immer Leute, die mir helfen wollten und konnten. Meine Frau war immer für mich da und erlaubte mir, mir Zeit dafür zu nehmen, und meine Kinder besuchten mich häufig im Keller, so dass ich nie einsam war. Mir meiner Frau und einigen guten Freunden konnte ich immer reden, mir Rat geben lassen, wenn die Arbeit frustrierend wurde. Aber auch von kommerzieller Seite hatte ich hervorragende Unterstützung:&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/sorotec.jpg" alt="Bild: Sorotec Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Vielen Dank an Willy, Roy und Alex von &lt;a href="https://www.sorotec.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Sorotec&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für Ratschläge, Tipps und Tricks, die mir geholfen haben, meine Maschine zu handhaben und eine bessere Lebensdauer aus den Fräsern herauszuholen. Ich habe den direkten Telefonsupport und die schnellen Bearbeitungszeiten für Fräserlieferungen und Garantiereparaturen geschätzt. Ich besuche auch gerne das &lt;a href="https://hobbyline.info/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hobbyline CNC forum&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für Diskussionen und um meine Geschichten dort zu veröffentlichen und zu diskutieren. So kann ich der Gemeinschaft etwas zurückgeben.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/eding.jpg" alt="Bild: EdingCNC Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Auch Dank an das Team von &lt;a href="https://edingcnc.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;EdingCNC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, vor allem an Iwona und Pim für Ihre schnellen und ausführlichen Antworten auf Support-Tickets und Controller-Software bezogene Fragen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/stritzelberger.jpg" alt="Bild: Stritzelberger Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Vielen Dank an Jo von &lt;a href="https://vakuumtisch.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Stritzelberger&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für stundenlange Diskussionen über gute Lösungen für meinen Vakuumtisch und Zubehör.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/vhf.jpg" alt="Bild: vhf logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Von &lt;a href="https://shop.vhf.de/catalogs/Werkzeuge-W.htm?showStartpage=true" target="_blank" rel="noopener noreferrer" class="external-link"&gt;vhf&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; möchte ich mich bei Frau Dressner für die Zeit und die häufigen Gespräche über Schnittqualität, Optimierung der Vorschübe und Geschwindigkeiten für Schaftfräser des Unternehmens sowie Beratung über Materialfrässtrategien bedanken.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/benezan.jpg" alt="Bild: Benezan-Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ein weiterer Dank geht an Nicolas von &lt;a href="https://www.benezan-electronics.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Benezan Electronics&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für seine Unterstützung bei der Analyse eines Problems mit dem Signal-Breakout-Board und schließlich dessen Austausch.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/gmc.jpg" alt="Bild: Gossen Metrawatt Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Dank an Herrn Leibold von &lt;a href="https://www.gmc-instruments.de/en/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Gossen Metrawatt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; für seine Hilfe bei der Auswahl eines guten Instruments zur Messung von Spannungsspitzen und der Leistungsaufnahme des Systems.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/spinogy.jpg" alt="Bild: Spinogy-Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Vielen Dank an Dominik von &lt;a href="https://www.spinogy.de/en/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Spinogy&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, mit dem ich über ein halbes Jahr lang telefoniert habe, um ein geeignetes Spindel-Upgrade und Zubehör für meine Maschine auszuwählen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/cadascam.jpg" alt="Bild: CadAsCam Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Abschließend möchte ich mich bei Achim von &lt;a href="https://www.cadascam.com/de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;CadAsCam&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bedanken, für die freundliche Aufnahme von Software-Verbesserungsvorschlägen, die Beantwortung grundlegender Fragen und die extrem schnelle Reaktion mit Software-Updates und Korrekturen. Außerdem war euer kostenloser 2-Tages-Workshop große Klasse!&lt;/p&gt;&#10;&lt;h2 id="laufzeit-und-aufträge"&gt;Laufzeit und Aufträge&lt;/h2&gt;&#10;&lt;p&gt;Innerhalb eines Jahres habe ich 200 Aufträge mit einer Gesamtlaufzeit von knapp 18 Stunden gefertigt. Wenn ich Einrichten der Fräse auf die Jobs, Montage des Werkstücks und so weiter einbeziehe, kann man die Stundenzahl verdreifachen. Bezieht man zusätzlich meine Arbeitsstunden in CAD und CAM mit ein, wäre die Zahl locker nochmal zu verdoppeln - Man sieht also, dass so ein Hobby zu einem wahren Zeitfresser werden kann - umso wichtiger ist es dann, dass es Spaß macht.&lt;/p&gt;&#10;&lt;p&gt;Wie viel Zeit mich Aufbau, Wartung und Pflege der Maschine gekostet haben? Ich weiß es nicht genau. Aber ich schätze, dass es auf Augenhöhe liegt mit dem Betreiben dieser Website, wo die Erstellung von Inhalten für eine Minute Lesedauer mich in etwa eine Stunde in Beschlag nimmt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/jobs_1year.jpg" alt="Bild: CNC-Softwareanzeige" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Außerdem habe ich viele Stunden damit verbracht, online nach Materialmessgeräten und Fräsern zu suchen, Ratschläge in Foren einzuholen und zu erteilen, nach Maschinen-Upgrades zu suchen und Videos darüber anzusehen, wie andere Leute ihre CNCs benutzen.&lt;/p&gt;&#10;&lt;p&gt;Mein derzeitiger Favorit (Stand: März 2023) ist übrigens von CNC Connect (&lt;a href="https://www.youtube.com/watch?v=iqNTEnlXwO0" target="_blank" rel="noopener noreferrer" class="external-link"&gt;youtube link&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;). Ich werde nicht versuchen, mich damit zu vergleichen - denn ich bin ja kein Profi - aber ich bin immer wieder beeindruckt von Menschen und Herstellern, die ihr Handwerk verstehen.&lt;/p&gt;&#10;&lt;h2 id="kosten"&gt;Kosten&lt;/h2&gt;&#10;&lt;p&gt;Das CNC-Hobby ist &lt;em&gt;teuer&lt;/em&gt;, sowohl in Bezug auf Geld als auch auf Zeit. In meinem Fall habe ich fast 13.000,00€ 😨 in die Maschine, ihr Zubehör, die Spindel, den Maschinentisch, die Stromversorgung und die Schutzausrüstung, die Beleuchtung, die numerische Steuerung, den Laptop, die CAD/CAM/CNC-Software, die Schalldämmung, die Werkzeuge zum Bau der Maschine, die Absauganlage, die persönliche Sicherheitsausrüstung usw. investiert.&lt;/p&gt;&#10;&lt;p&gt;Und dabei habe ich noch nicht einmal die Kosten für Schaftfräser erwähnt.&lt;/p&gt;&#10;&lt;p&gt;Kurz vor dem Kauf denkt man: &amp;ldquo;Die Maschine an sich ist nicht allzu teuer&amp;rdquo;. Aber sobald man sich zum Kauf entschließt, werden sofort weitere Dinge erforderlich. Ich habe fast immer &amp;ldquo;B&amp;rdquo; gesagt nachdem ich &amp;ldquo;A&amp;rdquo; rief, bin nur wenige Kompromisse bei der Qualität der Ausrüstung eingegangen und habe mich nicht auf ein Material spezialisiert, was die Kosten geringer gehalten hätte.&lt;/p&gt;&#10;&lt;p&gt;Ich war fasziniert von den verschiedenen Materialien und ihren Eigenschaften, und es war immer eine Freude, wenn ein Teil wirklich gut wurde, unabhängig von dem Material, aus dem es bestand.&lt;/p&gt;&#10;&lt;h2 id="misserfolge-und-lösungen"&gt;Misserfolge und Lösungen&lt;/h2&gt;&#10;&lt;p&gt;Hier ist eine Liste größerer Probleme, die ich im letzten Jahr erlebt habe.&lt;/p&gt;&#10;&lt;h3 id="oberfräsenmotor-fällt-aus-bzw-wackelkontakt-im-betrieb-"&gt;Oberfräsenmotor fällt aus bzw. Wackelkontakt im Betrieb. Ⓜ️&lt;/h3&gt;&#10;&lt;p&gt;Dieses Problem war in Hartholz und Multiplex nach nur 8h Spindelbetrieb reproduzierbar. Die Lösung war der Einbau einer neuen Elektronikplatine, die Maffell kurzerhand lieferte.&lt;/p&gt;&#10;&lt;h3 id="die-geschwindigkeit-des-oberfräsenmotors-fällt-mitten-im-auftrag-auf-die-mindestdrehzahl-"&gt;Die Geschwindigkeit des Oberfräsenmotors fällt mitten im Auftrag auf die Mindestdrehzahl Ⓜ️&lt;/h3&gt;&#10;&lt;p&gt;Dieses Problem war ebenfalls reproduzierbar und hatte zwei Ursachen:&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/emergency.jpg" alt="Bild: Notschalter-Detailansicht" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Der Not-Aus-Schalter hatte einen geringen Auslöseweg, so dass kurze Kontaktprellungen möglich waren. Der Schalter ist auf dem Maschinentisch montiert und konnte durch Maschinenvibrationen ausgelöst werden. Ich habe dies behoben, indem ich die Position des Mikroschalters im Gehäuse so verändert habe, dass der Schalter nicht vorzeitig ausgelöst wird.&lt;/li&gt;&#10;&lt;li&gt;Auf der Breakout-Platine, die das analoge &lt;code&gt;0-10V&lt;/code&gt;-Signal aus dem Pulsbreitenbefehl der CNC erzeugt, war ein falscher Operationsverstärkertyp montiert (nicht RTR). Unter noch unbekannten Umständen (vielleicht Kontaktprellen des Notschalters?) kam es bei diesem Teil zu einem &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Latch-up" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Latch-up&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;rdquo;, so dass die Geschwindigkeit des Oberfräsenmotors auf die niedrigste Einstellung fiel.&#10;Ein neues Signal-Breakout-Board wurde montiert und das Problem war damit behoben.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="velocityerror"&gt;CNC Meldung: &amp;ldquo;The Velocity was higher than max!&amp;rdquo; ⚠️&lt;/h3&gt;&#10;&lt;p&gt;Dieser Fehler trat bei einigen Gelegenheiten auf. Sie waren alle unangenehm. Einmal habe ich meine Maschine zu schnell bewegen lassen (gleichzeitig &lt;code&gt;240mm/s&lt;/code&gt; auf XY, &lt;code&gt;110mm/s&lt;/code&gt; auf Z), was dazu führte, dass sie quietschte und eine Menge Schritte verlor, die sich in den Zentimeterbereich summierten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/stepfrequency_old.jpg" alt="Bild: Übertaktete Maschinenkinematik, die zu Problemen führte" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ein anderes Mal, als ich weniger hohe (aber im Nachhinein immer noch zu hohe) Geschwindigkeitswerte eingegeben hatte, machte ich einen Fehler, indem ich Eilgang &amp;ldquo;G00 Xxx Yyy Zzz&amp;rdquo; für alle Achsen gleichzeitig befahl, was dazu führte, dass die Maschine beim Abbremsen Schritte verlor und auf der Z-Achse an den mechanischen Endanschlag stieß. Glücklicherweise war es die obere Anschlag, so dass weder die Spindel noch die Maschine bzw. Maschinenbett beschädigt wurden. Nur die Schrittmotorkupplung rutschte durch. Uff.&lt;/p&gt;&#10;&lt;p&gt;Der Grund dafür: Die maximale Impulsfrequenz für &lt;em&gt;kombinierte Achsenbewegungen&lt;/em&gt; beträgt bei meinem Controller &lt;code&gt;125kHz&lt;/code&gt;. Ich dachte, das sei pro Achse. Das ist &lt;em&gt;falsch&lt;/em&gt;. Ich habe das mit dem Hersteller der numerischen Steuerung abgeklärt. Ich musste die Geschwindigkeitswerte der Achsen neu einstellen, um &lt;em&gt;kombiniert&lt;/em&gt; unter &lt;code&gt;125kHz&lt;/code&gt; zu bleiben.&lt;/p&gt;&#10;&lt;p&gt;Um neue Werte zu finden, habe ich mir meine bisherigen Aufträge angesehen. Ich verliere mehr Zeit bei schnellen Bewegungen der Z-Achse als bei den anderen beiden Achsen. Deshalb habe ich das Hauptaugenmerk auf die Z-Achse gelegt, um sie schnell und mit hoher Beschleunigung zu fahren, während ich die Werte für die X- und Y-Achse reduziert habe. Dabei ist zu beachten, dass die Z-Achse aufgrund ihres kürzeren Kugelumlaufgewindes auf meiner Maschine die doppelte Auflösung hat, so dass die Werte verdoppeln müssen, um sie mit den anderen Achsen vergleichen zu können.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/stepfrequency_new.jpg" alt="Bild: Abgestimmte Maschinenkinematik zur Behebung des Problems" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dies alles geschah nach der Verbesserung der Fertigungsqualität, die ich &lt;a href="https://blog.schallbert.de/cut-dibond-tests/#machine-vibrations"&gt;hier&lt;/a&gt; dokumentiert habe. Diese Werte liefern also nachweislich gute Ergebnisse und werden von mir nicht mehr leichtfertig verändert.&lt;/p&gt;&#10;&lt;h3 id="kollision-der-maschine-mit-dem-vakuumtisch-"&gt;Kollision der Maschine mit dem Vakuumtisch 💥&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/vacuumtable_cut.jpg" alt="Bild: 6mm Frässpuren in meinem brandneuen Vakuumtisch" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Nun, das hat mich wirklich verärgert. Ich hatte den Tisch noch nicht einmal zwei Wochen, und schon jetzt hatte ich ihn beschädigt. Das Schlimmste war, dass ich nicht verstand, warum.&lt;/p&gt;&#10;&lt;p&gt;Alles, was ich wollte, war &lt;code&gt;18mm&lt;/code&gt; Birke Multiplex zu fräsen. Ich ließ den Schruppfräser den Umriss erstellen, sah mich aber mit einem Problem konfrontiert, das mich dazu brachte, den Not-Aus zu drücken, als der Vorgang bereits abgeschlossen war (beim Zurückfahren zu XY0 wäre der Fräser auf ein Hindernis gestoßen). Ich korrigierte den Pfad und setzte die Maschine zurück. Ich wechselte das Werkzeug zum Schlichten und startete den Auftrag.&lt;/p&gt;&#10;&lt;p&gt;Als ich merkte, dass die Maschine ungewöhnliche Geräusche von sich gab, betätigte ich den Not-Aus. Aber bei über &lt;code&gt;4000mm/min&lt;/code&gt; zum Schlichten war ich nicht schnell genug, um zu verhindern, dass die Maschine durch den Vakuumtisch fräst.&lt;/p&gt;&#10;&lt;p&gt;Ich schaute mir den Arbeitsversatz für die Z-Achse im Variablenspeicher für &lt;code&gt;G92&lt;/code&gt; an. Er war korrekt. Ich suchte in den flüchtigen Variablen der CNC. Alle stimmten mit meinen Erwartungen überein. Dann begann ich, in den Logdateien der Maschine zu suchen. Glücklicherweise habe ich das obige Bild direkt nach diesem Vorfall gemacht, so dass es nicht schwer war, die richtigen Protokollzeilen herauszufinden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/edinglog.jpg" alt="Bild: Logdatei mit Werkzeuglängenmessungen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Man kann erkennen, dass die Werkzeuglänge sowohl für den Schrupp- als auch für den Standardfräser gemessen wurde. Der Standardfräser ragt um etwa 3,4 mm weiter heraus. Das ist genau die Tiefe, die mir jetzt auf meinem Vakuumtisch fehlte.&lt;/p&gt;&#10;&lt;p&gt;Dennoch hatte ich keine Ahnung, warum die Maschine die aktualisierte Werkzeuglänge nicht berücksichtigte. Also habe ich mir das Makro für den Werkzeugwechsel angesehen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/macro_rootcause.jpg" alt="Bild: Makro mit Werkzeuglängenmessroutine" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Man kan sehen, dass Variable &lt;code&gt;#3501&lt;/code&gt; speichert, ob bereits eine Werkzeuglänge gemessen wurde. Wenn dies der Fall ist, kann einfach die neue Werkzeuglänge gemessen und die Differenz zwischen den beiden Werten als Z-Achsen-Korrektur gespeichert werden. Dann wird das Programm mit dem neuen Werkzeug fortgesetzt.&lt;/p&gt;&#10;&lt;p&gt;Das Problem ist, dass die Variable &lt;code&gt;#3501&lt;/code&gt; flüchtig ist, wie ein Blick in das Handbuch der Steuerung zeigt. Und es scheint, dass flüchtige Variablen nicht nur zurückgesetzt werden, wenn die Anwendung geschlossen wird, sondern auch, wenn der Not-Aus gedrückt wird.&lt;/p&gt;&#10;&lt;p&gt;Ausgehend von meinen vorherigen Aussagen hatte ich das folgende Problem: Das Drücken des Not-Aus löschte den Wert dieser Variablen, behielt aber die ursprüngliche Z0-Höhenmessung bei, die ich ganz am Anfang vor dem Einsatz des Schruppwerkzeugs vorgenommen hatte. Anstatt also die Höhenkorrektur mit dem neuen Werkzeug zu aktualisieren, um der neuen Werkzeuglänge Rechnung zu tragen, dachte das Makro, dass dies das erste verwendete Werkzeug sei &lt;em&gt;und speicherte&lt;/em&gt; einfach dessen Länge in der Variablen &lt;code&gt;#4501&lt;/code&gt; (weil man - logisch - keine Differenz nehmen kann, wenn man vor dem ersten Werkzeug kein Werkzeug vermessen hatte).&lt;/p&gt;&#10;&lt;p&gt;Das allein hätte den Vorfall gar nicht verursacht, wenn der neu eingesetzte Fräser nicht zufällig weiter aus der Spannzange herausgeragt hätte als der vorherige.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/1year_cnc/macro_solution.jpg" alt="Bild: Zwischenlösung war die Änderung des Makros" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich löste das Problem, indem ich dem Makro eine Meldung hinzufügte, wenn es nur die Werkzeuglänge speicherte, aber nicht den Versatz der Z-Achse änderte. Auf diese Weise habe ich zumindest einen Hinweis darauf, dass etwas schief gehen könnte.&lt;/p&gt;&#10;&lt;h2 id="schlussfolgerung"&gt;Schlussfolgerung&lt;/h2&gt;&#10;&lt;p&gt;Zusammenfassend kann ich sagen, dass ich die CNC weiterhin benutzen werde. Ich werde nach und nach weiter dazulernen. Und ich werde mein Wissen darüber vertiefen, wie Software mit Mechatronik interagiert, die physikalische Materie verändert, wie es &amp;lsquo;Zerspanobert&amp;rsquo; tut. Ich werde versuchen, aufzuschreiben, was ich gelernt habe, damit andere meinen Spuren folgen können, wenn sie wollen.&lt;/p&gt;&#10;&lt;p&gt;Ich hoffe, dass ich einen guten Kontakt zu anderen Bastlern, Herstellern und zu den Firmen, mit denen ich bisher zusammenarbeiten durfte, aufrechterhalten kann (und vielleicht noch einige weitere zu meiner Liste hinzufügen können werde).&lt;/p&gt;&#10;&lt;p&gt;Bis bald!&#10;&lt;em&gt;Schallbert&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>AnywhereAmps - Alpha</title><link>https://blog.schallbert.de/projects/aa_alpha/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/aa_alpha/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/front-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: AnywhereAmps Alpha&amp;#39;s insides"&#10; title="AnywhereAmps - Alpha" /&gt;&#10;&lt;p&gt;Mobiler Instrumentenverstärker/Combo-Prototyp basierend auf &lt;a href="https://blog.schallbert.de/shelly13/"&gt;Shelly13&lt;/a&gt;. AnywhereAmps Alpha zeichnet sich durch einen geringen Stromverbrauch und eine kleine Stellfläche aus. Er ist als sehr tragbarer Vollbereichs-Übungsverstärker mit einem Gesamtgewicht von nur 3,1 kg konzipiert. Er verfügt über eine einstellbare Verstärkung und eine 9V DC-Versorgung für externe Effektpedale.&lt;/p&gt;&#10;&lt;h2 id="projekt-statistiken"&gt;Projekt-Statistiken&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeit: Amateur 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: ~170€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: 10h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="impressionen"&gt;Impressionen&lt;/h2&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Bild: AnywhereAmps Alpha, aufrecht stehend."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/upright.jpg" alt="Bild: AnywhereAmps Alpha, aufrecht stehend." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Bild: AnywhereAmps Alpha, mit Spannungsversorgung für Bodeneffektpedale"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/effects_on.jpg" alt="Bild: AnywhereAmps Alpha, mit Spannungsversorgung für Bodeneffektpedale" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Bild: AnywhereAmps Alpha eingeschaltet, Fokus auf die Bedienelemente."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/switched_on.jpg" alt="Bild: AnywhereAmps Alpha eingeschaltet, Fokus auf die Bedienelemente." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-3" aria-label="Image: AnywhereAmps Alpha, top view"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/top.jpg" alt="Image: AnywhereAmps Alpha, top view" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-4" aria-label="Image: AnywhereAmps Alpha, side view"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/side.jpg" alt="Image: AnywhereAmps Alpha, side view" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-5" aria-label="Image: AnywhereAmps Alpha Amplifier Boards"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/amp.jpg" alt="Image: AnywhereAmps Alpha Amplifier Boards" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Impressionen vom Einsatz meines AnywhereAmp Alpha.&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/upright.jpg" alt="Bild: AnywhereAmps Alpha, aufrecht stehend." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Alpha kann auch aufrecht gespielt werden, ideal in kleinen Räumen&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/effects_on.jpg" alt="Bild: AnywhereAmps Alpha, mit Spannungsversorgung für Bodeneffektpedale" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;9V Spannungswandler für Bodeneffektpedale&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/switched_on.jpg" alt="Bild: AnywhereAmps Alpha eingeschaltet, Fokus auf die Bedienelemente." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Minimalistische Bedienelemente: Ein/Aus Schalter, Verstärkung, 6,3mm Klinke Eingang, 9V Ausgang&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-3" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/top.jpg" alt="Image: AnywhereAmps Alpha, top view" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;31cm Durchmesser, 9l Volumen&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-4" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/side.jpg" alt="Image: AnywhereAmps Alpha, side view" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Akku auf der Rückseite montiert, zum Abnehmen nach oben ziehen, Restkapazität leicht ablesbar&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-5" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/amp.jpg" alt="Image: AnywhereAmps Alpha Amplifier Boards" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Vor- und Endstufe sind übereinander angeordnet&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;h2 id="amp-sound-samples"&gt;Klangbeispiele&lt;/h2&gt;&#10;&lt;h3 id="bass-samples"&gt;Bass samples&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;audio controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/audio/aa_alpha_fingered_highnotes.mp3" type="audio/mp3"&gt;&#10; Your browser does not support the audio element.&#10; &lt;/audio&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Hauptsächlich höhere Töne&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;audio controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/audio/aa_alpha_fingered_playedhard.mp3" type="audio/mp3"&gt;&#10; Your browser does not support the audio element.&#10; &lt;/audio&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Hart angeschlagen&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;audio controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/audio/aa_alpha_fingered_flageolet.mp3" type="audio/mp3"&gt;&#10; Your browser does not support the audio element.&#10; &lt;/audio&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Leersaiten &amp;amp; Flageoletts&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Alle Beispiele aufgenommen mit dem H4n von Zoom, Bass: &lt;a href="http://www.hoshinogakki.co.jp/pdf/ibanez/catalog/1992Supplement_USA.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Ibanez SR1500PD&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gebaut 1992, Tonblende auf Hals-Pickups, Lautstärke-Potentiometer voll aufgedreht.&lt;/p&gt;&#10;&lt;h3 id="abmessungen"&gt;Abmessungen&lt;/h3&gt;&#10;&lt;p&gt;Trommelkessel: &lt;code&gt;12 &amp;quot;x6&amp;quot;&lt;/code&gt;. Die Gesamthöhe beträgt etwa 17 cm, die Breite 30 cm und die Tiefe (ohne Batterie) 32 cm. Das umschlossene Volumen inkl. Verstärker und Lautsprecher beträgt &lt;code&gt;~9l&lt;/code&gt;. Ich verwende geschlossenes Gehäusedesign.&lt;/p&gt;&#10;&lt;h3 id="sound-hardware"&gt;Sound Hardware&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Lautsprecher: &lt;a href="https://www.visaton.de/en/products/drivers/woofers/kt-100-v-4-ohm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;4&amp;quot; Basslautsprecher&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und zwei &lt;a href="https://www.monacor.com/products/components/speaker-technology/pa-tweeters-and-horn-drivers-/mpt-005/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Piezo-Hochtöner&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://store.sure-electronics.com/product/AA-AB32996" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Class D-Verstärker&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit &lt;code&gt;30W&lt;/code&gt; Leistung (gebrückt)&lt;/li&gt;&#10;&lt;li&gt;14,4V Li-Ion Werkzeugakku&lt;/li&gt;&#10;&lt;li&gt;Alle empfindlichen Teile befinden sich im Inneren des Gehäuses&lt;/li&gt;&#10;&lt;li&gt;Speziell angefertigter Vorverstärker mit &lt;code&gt;0...27dB&lt;/code&gt; einstellbarer Verstärkung&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="simulation"&gt;Simulation&lt;/h3&gt;&#10;&lt;p&gt;Die Simulation zeigt, dass die Lautstärke des ~9l gedämpften Gehäuses perfekt zu dem gewählten 4&amp;quot; Lautsprecher passt. Sie reicht bis auf &lt;code&gt;44Hz&lt;/code&gt; hinunter (F#1, ein Ganzton oberhalb der tiefsten offenen Saite eines (4-Saiter) Basses, E1). Es gibt druckvolle Bässe bei noch guter Präzision. Der Nachteil ist der geringe Wirkungsgrad von nur &lt;code&gt;78 dB/W/m&lt;/code&gt;, so dass die zu erwartende Maximallautstärke bei der Leistungsaufnahme eher gering ist und der Lautsprecher früh an seine Auslenkungsgrenzen kommt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/simulation.jpg" alt="AA09_1421_30 Sim" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="vorverstärker-im-eigenbau"&gt;Vorverstärker im Eigenbau&lt;/h2&gt;&#10;&lt;p&gt;Der von mir gewählte Class-D-Verstärker benötigt einen Line-Pegel-Eingang, so dass ich einen Vorverstärker in mein Design einbauen musste, um eine ausreichende Lautstärke aus der Endstufe zu erhalten. Selbst die billigsten Vorverstärker wie &lt;a href="https://artproaudio.com/product/tube-mp-the-original/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieser von Art Pro Audio&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; würden das Design erheblich verteuern. Sie bringen recht viel auf die Waagen und benötigen recht viel Energie, vor allem, wenn es sich um Röhrenvorverstärker handelt. Sie haben oft spezielle Anforderungen an die Eingangsspannung (z. B. nur Wechselstrom). Außerdem würde sich ihr Design nicht in meins einfügen.&lt;/p&gt;&#10;&lt;p&gt;Also beschloss ich, selbst eine Verstärkerstufe mit geringem Stromverbrauch aus möglichst wenigen Bauteilen zu entwickeln. &lt;a href="https://blog.schallbert.de/projects/aa_alpha/#sound-quality"&gt;Hier klicken um die technischen Details zu überspringen&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3 id="spannungsschienen"&gt;Spannungsschienen&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/virtualground.jpg" alt="Bild: Virtuelle Masseschaltung" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Da der Vorverstärker Wechselspannungssignale verstärken soll, ich aber nur eine Batteriespannungsschiene zur Verfügung habe, musste ich mir zunächst überlegen, wie ich ein &lt;a href="https://en.wikipedia.org/wiki/Virtual_ground" target="_blank" rel="noopener noreferrer" class="external-link"&gt;virtuelles Masse&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;potenzial schaffen kann, das den Nullpunkt des Signals für die Verstärkungsstufe darstellt. Glücklicherweise gibt es eine Menge &lt;a href="https://mil.ufl.edu/4924/docs/TI_SingleSupply_OpAmp.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Design-Richtlinien&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; darüber, wie man den Verstärker mit einer einzigen Spannungsquelle betreibt, so dass ich nicht bei Null anfangen musste.&lt;/p&gt;&#10;&lt;h3 id="verstärkungsstufe"&gt;Verstärkungsstufe&lt;/h3&gt;&#10;&lt;p&gt;AnywhereAmps Alpha sollte ein Mehrzweck-Instrumentencombo sein, so dass ein breiter Verstärkungsbereich zur Verfügung stehen sollte, um alles von einer passiven Gitarre bis zu einem Line-Level-Keyboard auf Spannungspegel zu bringen, mit denen die Endstufe umgehen kann. Deshalb habe ich mich für einen &lt;a href="http://www.sengpielaudio.com/calculator-levelchange.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;0-27dB Verstärkungsbereich&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; entschieden und als Widerstände für die Rückkopplungsschleife &lt;code&gt;10kOhm&lt;/code&gt; und ein &lt;code&gt;250kOhm&lt;/code&gt; logarithmisches Potentiometer gewählt.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/gainstage.jpg" alt="Schaltung der Verstärkungsstufe" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h3 id="schaltungssimulation"&gt;Schaltungssimulation&lt;/h3&gt;&#10;&lt;p&gt;Ich habe &lt;a href="https://www.analog.com/en/design-center/design-tools-and-calculators/ltspice-simulator.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;LTspice&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; von Analog Devices verwendet um zu simulieren, wie sich diese Schaltung bei einem typischen Audiosignal zwischen &lt;code&gt;20H&lt;/code&gt;z und &lt;code&gt;20kHz&lt;/code&gt; verhalten könnte. Aus meinem Studium erinnere ich mich daran, dass mich ein so genannter Bode-Plot am meisten interessieren müsste. Dieser kann sowohl Amplitude als auch Phasenlage über der Frequenz in einer doppelt-logarithmischen Skala darstellen. Praktischerweise bietet LTspice eine solche &lt;a href="https://www.analog.com/en/technical-articles/how-to-generate-a-bode-plot-with-ltspice.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Funktion&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&#10;Wie man sieht, bleibt der Ausgang des Vorverstärkers - grün - bis in den &lt;code&gt;10kHz&lt;/code&gt;-Bereich flach und die Phase verschiebt sich nur geringfügig, während das Eingangssignal - blau - &lt;code&gt;~27dB&lt;/code&gt; darunter bleibt.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/bodeplot.jpg" alt="Bild: Verstärkerschaltung" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h3 id="op-amp-auswahl"&gt;Op-Amp Auswahl&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/crossover_distortion.jpg" alt="Bild: Übergabeverzerrung" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Tja. Ich habe dieses Design auf meinem Steckbrett mit einem Mehrzweck-&lt;a href="https://en.wikipedia.org/wiki/Operational_amplifier" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Operationsverstärker&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; den ich herumliegen hatte, aufgebaut. Das klappte&amp;hellip; nicht besonders gut. Als ich einen Funktionsgenerator am Eingang anschloss und am Ausgang des &lt;a href="https://www.mikrocontroller.net/part/LM324" target="_blank" rel="noopener noreferrer" class="external-link"&gt;LM324&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; eine Sinuswelle erwartete, erhielt ich am Ausgang überhaupt keine Sinuswelle, sondern einen Halbsinus mit einigen Stufen und entladungsähnlichen Kurven um &lt;code&gt;0&lt;/code&gt;, gefolgt von einem negativen Halbsinus&amp;hellip; Nach einiger Suche fand ich heraus, dass dies &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Crossover_distortion" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Übergabeverzerrung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;rdquo; genannt wird und durch die asymmetrische Ausgangsstufe dieses speziellen Operationsverstärkertyps verursacht wird. Ich konnte den Effekt ein wenig reduzieren, indem ich einen höheren Laststrom am Ausgang anlegte. Aber letztendlich wollte ich einen Operationsverstärker nehmen, der für meine Audioanwendung ohne Rail-to-Rail-Fähigkeit und mit höherem Rauschabstand besser geeignet war. Zum Glück gibt es einige Seiten zu diesem Thema wie &lt;a href="https://www.cycfi.com/projects/six-pack/op-amp-shootout/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Cicfy Research&amp;rsquo;s Op-Amp shootout&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Unter Abwägung eines vernünftigen Stückpreises und der Verfügbarkeit bei meinem Elektronikhändler habe ich mich für den &lt;a href="https://www.ti.com/lit/ds/symlink/ne5532.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;NE5532P&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; entschieden. Das folgende Video zeigt, was ich mit der aktualisierten Op-Amp-Auswahl erreicht habe.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/aa_alpha/preamp_working.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Sinuswelle am Vorverstärker&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="schaltungsentwurf"&gt;Schaltungsentwurf&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/circuit_board.jpg" alt="Bild: Schaltplan AnywhereAmps Alpha" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt; Ich wollte, dass meine Platine so wenig Platz wie möglich beansprucht, während die Leiterbahnen für die Gleichspannungsversorgung wie ein Rahmen an den Außenseiten der Platine liegen. Die linke Hälfte der Platine ist für die Erzeugung des virtuellen Massepotentials zuständig, während die rechte Hälfte den Verstärkungsteil übernimmt. Das Design ist minimalistisch und verwendet nur vier Widerstände und drei Kondensatoren.&lt;/p&gt;&#10;&lt;p&gt;Einen tieferen Einblick in die Technik erhalten Sie in meinen &lt;a href="https://blog.schallbert.de/preamp-design-considerations/"&gt;Design-Überlegungen&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="sound-quality"&gt;Klangqualität&lt;/h2&gt;&#10;&lt;p&gt;Der AnywhereAmps Alpha ist im Leerlauf absolut leise, selbst bei voll aufgedrehter Verstärkung ist nur ein leichtes Zischen der Hochtöner zu vernehmen. Kein Brummen, kein Kratzen beim Drehen des Gain-Reglers. Dank des weiten Gain-Bereichs von &lt;code&gt;0dB&lt;/code&gt; für Line-Pegel wie Keyboards, Preamp-Instrumente, Mobiltelefone und bis zu &lt;code&gt;+27dB&lt;/code&gt; für passive Bässe oder Gitarren-Direkteingänge funktioniert er mit jedem Instrument. Die maximale Lautstärke ist in Ordnung, obwohl die tiefen Töne zu früh &amp;ldquo;abflachen&amp;rdquo;, was den nutzbaren Lautstärkebereich z.B. für Orgeln und 5-saitige Bässe einschränkt. Für Frequenzen im Bassbereich bei hoher Lautstärke kommt es immer noch auf die Physik an - je größer, desto besser. Einige Klangbeispiele finden Sie &lt;a href="https://blog.schallbert.de/projects/aa_alpha/#amp-sound-samples"&gt;oben&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="zusammenfassung"&gt;Zusammenfassung&lt;/h2&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha/shell_raw.jpg" alt="Bild: Tom-Kessel" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Für das Gehäuse habe ich bei &lt;a href="http://cube-drums.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Cube Personal Drums&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; einige rohe Tom-Tom-Kessel in Sondergröße bestellt. Das Bohren und vor allem das Fräsen einer gekrümmten Oberfläche von Hand erwies sich als schwierig, und die Tatsache, dass ich keine richtige Holzwerkstatt besitze und die ganze Arbeit auf dem Balkon meiner Wohnung erledigte, machte es nicht einfacher.&#10;Aber nach ca. 10 Stunden inkl. Löt- und Elektronikarbeiten habe ich jetzt mehr als nur eine super-portable Instrumentenkombination.&lt;/p&gt;&#10;&lt;p&gt;Es ist ein Möbelstück.&lt;/p&gt;&#10;&lt;p&gt;Ich werde viel darüber spielen. Außerdem motiviert mich der gute Klang mehr denn je, den nächsten Schritt zu tun und die Wiedergabe des Tieftonbereichs mit einem weiteren, größeren Prototyp zu verbessern.&lt;/p&gt;&#10;&lt;h2 id="vorteile"&gt;Vorteile:&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Ich mag das Design.&lt;/li&gt;&#10;&lt;li&gt;Passt in Standard-Tom-Taschen!&lt;/li&gt;&#10;&lt;li&gt;Leichtgewicht, &lt;code&gt;3.1kg&lt;/code&gt; inkl. Batterie, &lt;code&gt;2.6kg&lt;/code&gt; ohne&lt;/li&gt;&#10;&lt;li&gt;Ladezustand der Batterie kann auf einen Blick abgelesen werden&lt;/li&gt;&#10;&lt;li&gt;Verdeckte Bedienelemente: Vermeidet ungewollte Bedienung&lt;/li&gt;&#10;&lt;li&gt;Noch robuster: Die gesamte Elektronik und das Lautsprecherchassis sind im Inneren montiert&lt;/li&gt;&#10;&lt;li&gt;Inklusive &amp;ldquo;9V, 1.6A&amp;rdquo;-Stromversorgungsbuchse für Effekte, Pedale, etc.&lt;/li&gt;&#10;&lt;li&gt;Klingt gut für jedes Instrument, da es fast das gesamte hörbare Frequenzspektrum wiedergibt&lt;/li&gt;&#10;&lt;li&gt;Läuft &amp;gt;20h mit einer Batterieladung (14,4V, 4Ah) als Übungsverstärker bei moderater Lautstärke&lt;/li&gt;&#10;&lt;li&gt;Läuft &amp;gt;3 Stunden mit einer Batterieladung bei maximaler Lautstärke&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="unvollkommenheiten"&gt;Unvollkommenheiten:&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Zu klein für Subbass-Wiedergabe.&lt;/li&gt;&#10;&lt;li&gt;Keine automatische Stummschaltung beim Ein- und Ausschalten des Verstärkers -&amp;gt; Pop-Noise&lt;/li&gt;&#10;&lt;li&gt;Natürlich nicht genug Lautstärke, um ein Schlagzeug oder ein Plugged-Band-Setup zu bedienen.&lt;/li&gt;&#10;&lt;li&gt;Sieht komisch aus mit den großen Batteriepacks auf der Rückseite. Die Verwendung eines &amp;ldquo;Flat Pack&amp;rdquo; wäre schön&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="anywhereamps-alpha-diy-replik"&gt;AnywhereAmps Alpha: DIY Replik&lt;/h2&gt;&#10;&lt;p&gt;Eigenbau? Klar, gern. Unten ist die Schritt-für-Schritt-Anleitung verlinkt!&#10;&lt;a href="https://blog.schallbert.de/anywhereamps-diy/"&gt;AnywhereAmps Alpha DIY Anleitung&lt;/a&gt;&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/aa_alpha/anywhereamps_alpha_fulldemo.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Alpha live!&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>AnywhereAmps - Alpha 1.1</title><link>https://blog.schallbert.de/projects/aa_alpha_1_1/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/aa_alpha_1_1/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/top-thumb.jpg"&#10; class="post-cover"&#10; alt="AnywhereAmps Alpha 1.1: teaser"&#10; title="AnywhereAmps - Alpha 1.1" /&gt;&#10;&lt;p&gt;Mobiler Instrumenten-Verstärker/Cabinet-Kombi-Prototyp Version 1.1, entwickelt aus &lt;a href="https://blog.schallbert.de/projects/aa_alpha/"&gt;AnywhereAmps Alpha&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="projekt-statistiken"&gt;Projekt-Statistiken&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeit: Amateur 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: ~200€ plus Batteriesystem&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: 3h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="aktualisierungen-gegenüber-alpha-10"&gt;Aktualisierungen gegenüber Alpha 1.0:&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Hinzufügen einer &lt;a href="https://blog.schallbert.de/configure-max1756x/"&gt;Stromüberwachungsschaltung&lt;/a&gt;, um sowohl den Verstärker als auch das Akkupack vor abnormalen Spannungen / Strömen zu schützen&lt;/li&gt;&#10;&lt;li&gt;Integrierter Ein/Aus-Schalter im Potentiometer&lt;/li&gt;&#10;&lt;li&gt;Geringere Komplexität durch Verwendung eines hocheffizienten Breitbandlautsprechers anstelle eines 2-Wege-Systems mit 3 Lautsprechern&lt;/li&gt;&#10;&lt;li&gt;Hinzufügen eines hocheffizienten Abwärtswandlers zur Erzeugung einer 9-V-Effektspannungsschiene&lt;/li&gt;&#10;&lt;li&gt;Geringere Bauzeit durch Verwendung einer &lt;a href="https://blog.schallbert.de/anywhereamps-get-pcb/"&gt;kundenspezifischen Leiterplatte&lt;/a&gt; mit Vorverstärker, Abwärtswandler und &amp;ldquo;Power Supervisor&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Reduzierte Bauzeit, da Großteil der Holzarbeiten an eine Portalfräse (CNC&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;) ausgelagert werden konnte&lt;/li&gt;&#10;&lt;li&gt;Einschaltknackgeräusche eliminiert durch Hinzufügen eines RC-Filters zum Standby-Eingang der Endstufe&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="bauvarianten"&gt;Bauvarianten&lt;/h3&gt;&#10;&lt;p&gt;Um den Klang meines Prototyps weiter zu verfeinern, habe ich dieses Mal verschiedene Varianten gebaut. Ich habe die Chassis ausgetauscht und den Klang bewertet:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Alpha 1.0: 30W gebrückter Verstärker, 4&amp;quot; Bass und 2x Piezo-Hochtöner: HiFi-ähnlicher Sound mit druckvollen Bässen und klaren Höhen, limitiert in der maximalen Lautstärke&lt;/li&gt;&#10;&lt;li&gt;Alpha 1.1 v1: 30W gebrückter Verstärker, 4&amp;quot; PA-Treiber, gute Lautstärke und hohe Präsenz, aber unterrepräsentierte Bässe und Höhen&lt;/li&gt;&#10;&lt;li&gt;Alpha 1.1 v2: 30W gebrückter Verstärker, 4&amp;quot; PA-Treiber und Passivmembran, satte Bässe aber etwas dumpf in den Höhen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="meine-eigene-platine-für-vorverstärker-und-zusatzfunktionen"&gt;Meine eigene Platine für Vorverstärker und Zusatzfunktionen&lt;/h2&gt;&#10;&lt;p&gt;Die &lt;a href="https://blog.schallbert.de/pcbs-arrived/"&gt;AnywhereAmps 1.1 PCB&lt;/a&gt; bietet neben dem reinen Vorverstärker plus Tiefpass und Hochpass für Bi-Amping noch einige Zusatzfunktionen. Die Details sind in den folgenden Abschnitten beschrieben.&lt;/p&gt;&#10;&lt;h3 id="power-supervisor"&gt;Power Supervisor&lt;/h3&gt;&#10;&lt;p&gt;Ich habe eine Power Supervisor-Schaltung hinzugefügt, um zu verhindern, dass die Batterie komplett leer gezogen wird. Außerdem wollte ich die Risiken durch Kurzschlüsse und andere Fehlerzustände, die die Batterie beschädigen könnten, verringern. Die Integration dieser Technologie funktionierte auf Anhieb (was ich nicht erwartet hatte). Der einzige Nachteil ist, dass der Verstärker bei einer Entladung der Batterie auf z. B. 10 % manchmal neu startet, wenn die Versorgungsspannung unter die Auslösespannung des Überwachungsgeräts fällt. Da die Batteriespannung wieder ansteigt, wenn die Last abfällt, schaltet er sich eine Sekunde später wieder ein.&lt;/p&gt;&#10;&lt;p&gt;Dies ist eindeutig ein &lt;a href="https://blog.schallbert.de/capacitor-problems/"&gt;unerwünschtes Verhalten&lt;/a&gt; und muss in einem weiteren, verbesserten Prototyp behandelt werden.&#10;Ein weiterer Punkt, der diesen Folgeprototypen notwendig macht, ist die Tatsache, dass ich in meinen zukünftigen Entwürfen keine Batterien des Werkzeugherstellers verwenden darf &lt;a href="https://blog.schallbert.de/no-can-use-metabo/"&gt;wie hier diskutiert&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="9v-abwärtswandler"&gt;9V Abwärtswandler&lt;/h3&gt;&#10;&lt;p&gt;Zur Erzeugung von zusätzlichen 9V für Geräte wie Effekte, Looper usw. verwende ich einen Schaltregler. Ich wollte das Rauschen reduzieren, indem ich einen &amp;ldquo;leisen&amp;rdquo; Schaltregler wählte. Der von mir gewählte Regler wurde für Anwendungen im Automobilbereich entwickelt und hat die Induktivitäten in seinem Gehäuse integriert. Von Nachteil ist, dass der Baustein kaum von Hand gelötet werden kann. Außerdem ist er sehr teuer.&lt;/p&gt;&#10;&lt;p&gt;Der Wandler schaltet sich ein, wenn ein Gerät in die entsprechende Buchse eingesteckt wird, so dass es keinen Standby-Strom gibt. Er arbeitet leise (aber nicht ganz rauschfrei) und bietet mehr als genug Leistung für die Anwendungen, die ich bisher habe - ein Looper, ein Kompressor und ein Tuner können problemlos parallel betrieben werden.&lt;/p&gt;&#10;&lt;h2 id="holzbearbeitung"&gt;Holzbearbeitung&lt;/h2&gt;&#10;&lt;p&gt;Ich habe ein &lt;a href="https://blog.schallbert.de/portal-milling/"&gt;Portalfräsmaschinen-Set&lt;/a&gt; gekauft, um die Holzbearbeitung für AnywhereAmps zu delegieren. Ich wollte weniger Zeit für Sägen und Schleifen aufwenden und die Verarbeitungsqualität sowie die Wiederverwendbarkeit verbessern. Aber um die manuelle Holzbearbeitung durch eine mehr oder weniger automatisierte Bearbeitung zu ersetzen, musste ich zunächst meine Pläne in die digitale Welt übertragen.&lt;/p&gt;&#10;&lt;h3 id="computer-aided-design"&gt;Computer Aided Design&lt;/h3&gt;&#10;&lt;p&gt;Ich wählte FreeCAD als Konstruktionswerkzeug, um Zeichnungen zu erstellen. Die Arbeit damit habe ich in einigen &lt;a href="https://blog.schallbert.de/freecad-get-started/"&gt;posts&lt;/a&gt; dokumentiert und meinen Arbeitsablauf z.B. &lt;a href="https://blog.schallbert.de/projects/seifenbutler/#v1-v3-erste-entw%c3%bcrfe-in-hpl"&gt;hier&lt;/a&gt; weiter verbessert. Meine erste Skizze zur Herstellung sah so aus.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/alpha11_cad.jpg" alt="Bild: FreeCAD-Zeichnung Schnappschuss von AnywhereAmps Alpha 1.1" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Mit der Aufteilung der Holzplatte war ich noch nicht ganz zufrieden. Ich fand die Beine zu lang und habe noch einige Stunden in die Verfeinerung des Entwurfs investiert, um weniger Platz zu benötigen, da die Kapazität meiner Portalfräse begrenzt ist.&lt;/p&gt;&#10;&lt;h3 id="computer-aided-manufacturing"&gt;Computer Aided Manufacturing&lt;/h3&gt;&#10;&lt;p&gt;Als ich fertig war, exportierte ich die Skizzen als *.dxf-Dateien (Drawing Interchange File Format (DXF), 1982 von AutoDesk eingeführt. Es wurde schließlich zum Industriestandard für den Austausch von 2D-Zeichnungen) und öffnete sie in meinem CAM-Werkzeug, um Werkzeugwege zu definieren, den XY-Nullpunkt und die Z-Tiefe pro Durchgang festzulegen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/alpha11_cam.jpg" alt="Bild: CAM-Schnappschuss von AnywhereAmps Alpha 1.1" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="g-code"&gt;G-Code&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/alpha11_cut.jpg" alt="Image: job01_nesting: all parts cut out" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Am Ende erhält man G-Code-Dateien, die so nachbearbeitet waren, dass sie zur Maschine passten, mit einer geschätzten Bearbeitungszeit von 32 Minuten pro Exemplar. Ich verwendete nur drei Werkzeuge: einen 6-mm-Fräser für die Ausschnitte, einen 2-mm-Fräser für kleinere Löcher und Strukturen und ein Gravierwerkzeug für Fasen und das Logo.&#10;Ich teilte die Arbeit in drei Aufträge auf:&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/finishing.jpg" alt="Bild: job02_finishing: carving the logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;job00_clamping&lt;/code&gt;, um Löcher und Senkungen zu schneiden, damit das Werkstück am Maschinenbett befestigt werden kann&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;job01_nesting&lt;/code&gt;, um alle Ausschnitte zu fertigen und Gravuren auf dem Verstärkerträger zu machen, aber Anbindungen stehen zu lassen, damit sich die Teile nicht losreißen&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;job02_finishing&lt;/code&gt;, der das Logo auf die (bereits gefärbte) Oberseite graviert und Fasen hinzufügt, wo es nötig ist&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Für die Endbearbeitung musste ich die CAD- und CAM-Dateien aktualisieren, damit sie eine gespiegelte (und um -90° gedrehte) Ansicht des Werkstücks liefern. Dies ermöglicht das zweiseitige Fräsen mit demselben XY-Nullpunkt und den exakt gleichen Seiten, die den Anschlag berühren, um minimale Toleranzen zu erzielen.&lt;/p&gt;&#10;&lt;h3 id="nacharbeit"&gt;Nacharbeit&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/waxit.jpg" alt="Bild: Wachsen der Holzoberflächen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich musste noch ein wenig Handarbeit leisten, z. B. Gewindebohrungen abschleifen, Wachs für eine bessere Oberfläche auftragen, und einige Löcher für Streben und Ständer schneiden, die meine CNC nicht fertigen konnte.&lt;/p&gt;&#10;&lt;p&gt;Mit der CNC habe ich Adapter für meine Handoberfräse hergestellt, damit sie über die gebogene Oberfläche des Zylinders gleiten kann. Auf diese Weise konnten die Löcher, die ich zuvor für die Lautsprecher geschnitten hatte, richtig abgeschrägt werden.&lt;/p&gt;&#10;&lt;p&gt;Außerdem ließ ich von der CNC eine Halterung für das Gehäuse im 3D-Modus anfertigen (mit *.STL-Dateien). Auf diese Weise wird der Kessel an seinem Platz gehalten, wenn ich mit meiner Handbohrmaschine Löcher für die Lautsprecher bohre. Übrigens: 3D-Fräsungen brauchen viel Zeit, da die Nachbearbeitung in kleinen Schritten erfolgt, um eine glatte Oberfläche zu erhalten.&lt;/p&gt;&#10;&lt;p&gt;Das Bohren von Löchern mit einem Durchmesser von mehr als &lt;code&gt;100mm&lt;/code&gt; von Hand lässt keine gute Oberflächenqualität auf der Innenseite des Gehäuses zu und die Handhabung des riesigen Bohrers ist immer noch eine Qual. Leider ist meine CNC nicht in der Lage, diese Aufgabe für mich zu übernehmen, daher wird das wohl weiter ein manueller Prozess bleiben.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/shell_adapter.jpg" alt="Bild: Kesselhalter" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="eindrücke"&gt;Eindrücke&lt;/h2&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Image: AnywhereAmps Alpha V1.1, switched on, with battery attached"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/battery.jpg" alt="Image: AnywhereAmps Alpha V1.1, switched on, with battery attached" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Image: AnywhereAmps Alpha V1.1, front view"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/front_top.jpg" alt="Image: AnywhereAmps Alpha V1.1, front view" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Image: AnywhereAmps Alpha V1.1 bottom view"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/bottom.jpg" alt="Image: AnywhereAmps Alpha V1.1 bottom view" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-3" aria-label="Image: CNC-cut parts alongside with speaker and shell"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/parts.jpg" alt="Image: CNC-cut parts alongside with speaker and shell" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-4" aria-label="Image: AnywhereAmps Alpha V1.1, a view inside"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/top_open.jpg" alt="Image: AnywhereAmps Alpha V1.1, a view inside" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-5" aria-label="Image: Alpha V1.1&amp;#39;s body side view"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/side.jpg" alt="Image: Alpha V1.1&amp;#39;s body side view" class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Eindrücke meines AnywhereAmps Alpha V1.1&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/battery.jpg" alt="Image: AnywhereAmps Alpha V1.1, switched on, with battery attached" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Battery attached and switched on&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/front_top.jpg" alt="Image: AnywhereAmps Alpha V1.1, front view" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Front view, initial version of speaker protective grille&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/bottom.jpg" alt="Image: AnywhereAmps Alpha V1.1 bottom view" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;A view from below shows the passive radiator, extending the speaker&amp;#39;s low-end&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-3" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/parts.jpg" alt="Image: CNC-cut parts alongside with speaker and shell" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Preparing final assembly&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-4" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/top_open.jpg" alt="Image: AnywhereAmps Alpha V1.1, a view inside" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;A view inside: Ampcarrier and circuits&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-5" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/side.jpg" alt="Image: Alpha V1.1&amp;#39;s body side view" class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Allowing the wax to dry before final assembly&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Computerized Numerical Control&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>AnywhereAmps - Alpha 1.2</title><link>https://blog.schallbert.de/projects/aa_alpha_1_2/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/aa_alpha_1_2/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/aa_alpha11/top-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: AnywhereAmps Alpha 1.2: teaser"&#10; title="AnywhereAmps - Alpha 1.2" /&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Alpha 1.2 v1: 2x15W bi-amped, 2x3.2&amp;quot; Hochmitteltöner, 8&amp;quot; Subwoofer: Sehr gut balancierte, trockene Bässe und gute Höhen, in den Mitten etwas zu kräftig, die höchsten Höhen dafür etwas stumpf. Sehr hohes Gewicht.&lt;/li&gt;&#10;&lt;li&gt;Alpha 1.2 v2: 2x15W bi-amped, 4&amp;quot; PA Treiber, 5.5&amp;quot; passive membrane, 2x piezo tweeters: Sehr ausgeglichener Sound mit guten Lautstärkereserven, dafür ziemlich aufwändig in der Herstellung&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I&amp;rsquo;ll continue writing here once I selected the best variant.&lt;/p&gt;&#10;</description></item><item><title>CarSubAmp</title><link>https://blog.schallbert.de/projects/carsubamp/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/carsubamp/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/carsubamp/carsubamp-thumb.jpg"&#10; class="post-cover"&#10; alt="CarSubAmp"&#10; title="CarSubAmp" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeit: Anfänger 1/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: ~50€ ohne Akku&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~2h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/carsubamp/carsubamp.jpg" alt="carsubamp" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Der CarSubAmp war mein erster Prototyp für einen wirklich tragbaren Basscombo (Lautsprecherbox + Verstärker), der nicht einmal eine Steckdose braucht.&lt;/p&gt;&#10;&lt;h3 id="motivation"&gt;Motivation&lt;/h3&gt;&#10;&lt;p&gt;Während der Covid19-Pandemie habe ich gerne draußen im Park oder auf dem Balkon Bass gespielt und geübt. Das ist kein Problem, wenn man Kopfhörer trägt und einen kleinen Vorverstärker dabei hat. Aber es gab Gelegenheiten, bei denen ich z.B. mit einer Akustikgitarre zusammen spielen wollte, oder jemand brachte eine Synth-App mit einem MIDI-Keyboard mit - und ich war gezwungen, einen Bluetooth-Lautsprecher zu benutzen, der nicht die Ausgangsleistung hatte um akustisch mitzuhalten. Zu allem überfluss verzögert die interne Signalverarbeitung den Sound so sehr, dass sich das Spielen total abgekoppelt anfühlt.&lt;/p&gt;&#10;&lt;h3 id="problem"&gt;Problem&lt;/h3&gt;&#10;&lt;p&gt;Mein richtiger Bassverstärker ist alles andere als mobil. Gewicht: 18kg, Größe: riesig, außerdem: Ohne Steckdose in der Nähe ist er unbrauchbar.&lt;/p&gt;&#10;&lt;h3 id="sackgassen"&gt;Sackgassen&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Anstelle eines Class-D-Verstärkers habe ich den Aux-Eingang eines alten Autoradios verwendet, aber der scheint &amp;ldquo;Line&amp;rdquo;-Pegel erwartet zu haben, den mein Bass nicht liefern konnte. Somit war er kaum hörbar. Außerdem war die Stromaufnahme im Leerlauf &amp;gt;0,5A, sodass die Batterie einfach zu schnell leer war.&lt;/li&gt;&#10;&lt;li&gt;Ich habe die Stromversorgung zunächst mit einer Powerbank (Power-Delivery) ausprobiert, aber der integrierte Aufwärtswandler schien nicht mit Stromstößen bei voller Lautstärke umgehen zu können, so dass die Versorgungsspannung abfiel, was zu hörbaren Verzerrungen führte.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="lösung"&gt;Lösung&lt;/h3&gt;&#10;&lt;p&gt;Nach einigem Grübeln kam ich auf die Idee, einen Li-Ion-Akku für ein kabelloses Multitool zu verwenden, der in der Lage ist, Stromstöße problemlos zu bewältigen, ein geringes Gewicht hat, über eine Akkustandsanzeige und die typischen Schutzschaltungen zur Vermeidung von Überlast, Kurzschluss, Übertemperatur, Tiefentladung usw. verfügt. Ich wollte einen &lt;a href="https://en.wikipedia.org/wiki/Class-D_amplifier" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Class-D&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Leistungsverstärker verwenden, der auf den Spannungsbereich dieser Batterie abgestimmt ist, um möglichst energieeffizient und leicht zu sein&#10;wie möglich.&lt;/p&gt;&#10;&lt;h3 id="der-carsubamp"&gt;Der &amp;ldquo;CarSubAmp&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Um zu prüfen, ob das Konzept realisierbar war, entnahm ich die Subwoofer-Baugruppe, die normalerweise in der Reserveradmulde sitzt, aus unserem Auto. Ich kaufte eine 20-W-&lt;a href="https://www.adafruit.com/product/1752" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Verstärkerentwicklungsplatine&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und nachdem ich einige Komponenten angelötet, eine Sicherung und eine 6,3-mm-Eingangsbuchse hinzugefügt hatte, war mein erster Prototyp bereit zum Ausprobieren. &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/carsubamp/carsubamp_explained.jpg" alt="carsubamp components explained" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="zusammenfassung"&gt;Zusammenfassung&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Sehr leicht, &amp;lt;5kg&lt;/li&gt;&#10;&lt;li&gt;4Ohm Subwoofer-Baugruppe bereits fertig&lt;/li&gt;&#10;&lt;li&gt;belüftetes Design für bessere Lautsprechereffizienz&lt;/li&gt;&#10;&lt;li&gt;Der Klang ist in Ordnung. Die Baugruppe ist allerdings nicht als Vollbereichslautsprecher verwendbar, da die Höhen fehlen.&lt;/li&gt;&#10;&lt;li&gt;Bei tiefen Tönen (d.h. bei oder unter &lt;code&gt;g1&lt;/code&gt;) wird das System merklich leiser&lt;/li&gt;&#10;&lt;li&gt;Entwicklerplatine wird ohne Vorverstärker geliefert, daher ist die maximale Lautstärke bereits etwas niedrig&lt;/li&gt;&#10;&lt;li&gt;Bei hoher Belastung führt die belüftete Konstruktion (Bassreflex) zu hörbaren Flattergeräuschen&lt;/li&gt;&#10;&lt;li&gt;Aufgrund des geringen Durchmessers würde der Lautsprecher bei Hammer-ons auf tiefen Saiten an die Grenzen der Auslenkung stoßen&lt;/li&gt;&#10;&lt;li&gt;Alle Komponenten sind mit Heißkleber oder Klebeband befestigt, daher nicht sehr haltbar&lt;/li&gt;&#10;&lt;li&gt;Der Aufbau ist mit Klebstoff versiegelt. Es gibt keine einfache Möglichkeit, dafür ein Gehäuse zu bauen.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Obwohl diese Konstruktion eindeutige Nachteile hatte, gefiel sie mir sehr gut. Denn zum ersten Mal konnte ich buchstäblich überall und jederzeit spielen, wann ich wollte. Ich erwischte mich sogar auf der Couch mit diesem kleinen Verstärker, obwohl der &amp;ldquo;richtige&amp;rdquo; Verstärker nur einen Raum weiter stand.&lt;/p&gt;&#10;&lt;p&gt;Nach einigen Monaten, in denen ich das Ding mit mir herumtrug, 3 Mal kaputte Verbindungen nachlötete und die Batterie immer wieder neu einklebte, beschloss ich, diesem Projekt &lt;a href="https://blog.schallbert.de/projects/mobfobamp/"&gt;ein Upgrade zu gönnen&lt;/a&gt;.&lt;/p&gt;&#10;</description></item><item><title>ClickEncoder</title><link>https://blog.schallbert.de/projects/encoder/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/encoder/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/encoder/encoder-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Encoder on Breadboard"&#10; title="ClickEncoder" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: Mittel 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: 0€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~6h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="überblick"&gt;Überblick&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://raw.githubusercontent.com/isocpp/logos/64ef037049f87ac74875dbe72695e59118b52186/cpp_logo.svg" alt="Image: C&amp;#43;&amp;#43; logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Bibliothek für das &lt;a href="https://www.arduino.cc/en/Reference/Libraries" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Arduino Framework&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, geschrieben in C++.&lt;/p&gt;&#10;&lt;p&gt;Der Quellcode selbst besteht nur aus ein paar hundert Zeilen. Er ist leichtgewichtig und leistungsfähig, da der verbesserte Algorithmus weniger Berechnungen benötigt und gleichzeitig eine bessere Zustandsinterpretation bietet als das ursprüngliche Projekt, von dem sie abgezweigt ist. Die Bibliothek ist offiziell auf &lt;a href="https://platformio.org/lib/show/11808/ClickEncoder" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PlatformIO IDE&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; veröffentlicht und wird auf &lt;a href="https://github.com/Schallbert/encoder" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gehostet und gepflegt. Sie enthält Beispiele sowohl für Arduino- als auch für PlatformIO-IDEs und bietet Unittests, die nach der Anpassung an die spezifische Anwendung ausgeführt werden können.&lt;/p&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;Ich habe zunächst die von &lt;a href="https://github.com/0xPIT/encoder" target="_blank" rel="noopener noreferrer" class="external-link"&gt;0xPIT&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; veröffentlichte Originalbibliothek verwendet, aber diese verursachte bei mir einige Compiler-Warnungen, außerdem waren die Messwerte des Encoders nicht einwandfrei und manchmal gab es Jitter oder zurückspringende Werte, was ich nicht akzeptieren wollte.&lt;/p&gt;&#10;&lt;h2 id="lösung"&gt;Lösung&lt;/h2&gt;&#10;&lt;p&gt;Da das Archiv ungepflegt aussah und meine Anfrage für Verbesserungen nicht beantwortet wurde, beschloss ich, diese Probleme selbst zu beheben. Ich habe also behutsam einen Teil des Codes umgeschrieben. Für mein damaliges Projekt benötigte ich auch ein &amp;ldquo;Wiederholungssignal&amp;rdquo;, wenn eine Taste kontinuierlich gedrückt wird, also fügte ich auch dies hinzu. Schließlich trennte ich den Encoder-Algorithmus von der Tastenbehandlung und stellte separate Klassen bereit, so dass nun bei Bedarf jede beliebige Kombination aus Taste und Encoder verwendet werden kann, jeweils mit konfigurierbarem Verhalten.&lt;/p&gt;&#10;&lt;h2 id="encoder-algorithmus-erklärt"&gt;Encoder Algorithmus erklärt&lt;/h2&gt;&#10;&lt;h2 id="api"&gt;API&lt;/h2&gt;&#10;&lt;p&gt;Die API von ClickEncoder ist einfach und leicht zu verstehen: &lt;code&gt;service()&lt;/code&gt; führt die Geschäftslogik aus und die Zustandsinterpretation durch. Diese Methode muss regelmäßig ausgeführt werden, um zu überprüfen, ob der Encoder seit seinem letzten Aufruf bewegt wurde, in welche Richtung, wie viele Schritte, und diese Werte dann in einer Akkumulatorvariablen speichern.&#10;&lt;code&gt;getIncrement()&lt;/code&gt; gibt die Schrittänderungen des Encoders seit dem letzten Aufruf zurück. &lt;code&gt;getAccumulate()&lt;/code&gt; gibt die Summe der Inkremente seit dem Start zurück. &lt;code&gt;getButton()&lt;/code&gt; gibt den aktuellen Button-Status zurück.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;service&lt;/span&gt;();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;int16_t&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getIncrement&lt;/span&gt;();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;int16_t&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getAccumulate&lt;/span&gt;();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Button&lt;span style="color:#f92672"&gt;::&lt;/span&gt;eButtonStates getButton();&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="encoder-status"&gt;Encoder-Status&lt;/h3&gt;&#10;&lt;p&gt;Der Encoder verwendet einen &lt;a href="https://de.wikipedia.org/wiki/Gray_code" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Gray Code&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Kodierung der Schritte. &lt;code&gt;getBitCode()&lt;/code&gt; wandelt diesen Code, der über die Hardware-Pins A und B des Encoders übertragen wird, in eine &amp;ldquo;0&amp;hellip;3&amp;rdquo;-Rasten-Interpretation um, wobei die Zahlen bei einer Rechtsdrehung ansteigen und bei einer Linksdrehung abfallen. Dies geschieht effizient ohne Verwendung von &lt;code&gt;if()&lt;/code&gt; oder anderer Verzweigungslogik.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; Encoder&lt;span style="color:#f92672"&gt;::&lt;/span&gt;getBitCode()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// GrayCode convert&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// !A &amp;amp;&amp;amp; !B --&amp;gt; 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// !A &amp;amp;&amp;amp; B --&amp;gt; 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// A &amp;amp;&amp;amp; B --&amp;gt; 2&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// A &amp;amp;&amp;amp; !B --&amp;gt; 3&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; currentEncoderRead &lt;span style="color:#f92672"&gt;=&lt;/span&gt; digitalRead(pinA);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currentEncoderRead &lt;span style="color:#f92672"&gt;|=&lt;/span&gt; (currentEncoderRead &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// bit0 of result to be inverted if set&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currentEncoderRead &lt;span style="color:#f92672"&gt;^=&lt;/span&gt; digitalRead(pinB);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; currentEncoderRead;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ich habe hier mit einem Trick gearbeitet, sodass man sich den Code-Schnipsel oben möglicherweise mehrfach ansehen muss. Der Zustand von &lt;code&gt;PinA&lt;/code&gt; wird auf Bit1 von &lt;code&gt;currentEncoderRead&lt;/code&gt; geschrieben, indem ich es mit der &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;-Operation nach links schiebe. &lt;em&gt;Gleichzeitig bleibt der Status des 0. Bits durch &lt;code&gt;|=&lt;/code&gt; aber erhalten&lt;/em&gt;. Würde ich jetzt &lt;code&gt;PinB&lt;/code&gt; einfach logisch &lt;code&gt;&amp;amp;=&lt;/code&gt; anbinden, dann würde ich den Gray-Code verletzen, nachdem &lt;code&gt;A&amp;amp;&amp;amp;B=2&lt;/code&gt; sein sollte und nicht &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Sind &lt;code&gt;A&lt;/code&gt; und &lt;code&gt;B&lt;/code&gt; also beide nicht gesetzt, bleibt das Ergebnis &lt;code&gt;0&lt;/code&gt;, denn &lt;code&gt;00^00=00=0&lt;/code&gt;. Ist nur &lt;code&gt;A&lt;/code&gt; gesetzt, &lt;code&gt;B&lt;/code&gt; aber nicht, so lautet das Ergebnis &lt;code&gt;11^00=11=3&lt;/code&gt;. Wenn &lt;code&gt;B&lt;/code&gt; gesetzt ist, &lt;code&gt;A&lt;/code&gt; aber nicht, ergibt sich &lt;code&gt;00^01=01=1&lt;/code&gt;. Sind beide gesetzt, schließlich &lt;code&gt;11^01=10=2&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Für weitere Informationen zum Thema logische Binäroperationen siehe &lt;a href="https://blog.schallbert.de/projects/fireplace/#galois-lfsr-implementierung"&gt;mein Steckbrett-Lagerfeuer Projekt&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3 id="zustandsinterpretation"&gt;Zustandsinterpretation&lt;/h3&gt;&#10;&lt;p&gt;Nachdem der Bitcode gelesen wurde, wird die Variable &lt;code&gt;rawMovement&lt;/code&gt; gesetzt, die uns sagt, wie weit der Encoder zwischen zwei Durchläufen der &lt;code&gt;service()&lt;/code&gt; Routine gedreht wurde. Hier sieht man, dass dieser Schritt zeitkritisch ist: Es kommt zu Aliasing-Fehlern, wenn der Geber schneller einmal alle Positionen wechselt, als dass &lt;code&gt;service()&lt;/code&gt; erneut aufgerufen wird. In diesem Falle würden Schritte verloren gehen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Encoder&lt;span style="color:#f92672"&gt;::&lt;/span&gt;handleEncoder()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; encoderRead &lt;span style="color:#f92672"&gt;=&lt;/span&gt; getBitCode();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// bit0 set = status changed, bit1 set = &amp;#34;overflow 3&amp;#34; where it goes 0-&amp;gt;3 or 3-&amp;gt;0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; rawMovement &lt;span style="color:#f92672"&gt;=&lt;/span&gt; encoderRead &lt;span style="color:#f92672"&gt;-&lt;/span&gt; lastEncoderRead;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastEncoderRead &lt;span style="color:#f92672"&gt;=&lt;/span&gt; encoderRead;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// This is the uint-&amp;gt;int magic, converts raw to: -1 counterclockwise, 0 no turn, 1 clockwise&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int8_t&lt;/span&gt; signedMovement &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ((rawMovement &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&lt;/span&gt; (rawMovement &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; encoderAccumulate &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; signedMovement;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; encoderAccumulate &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; handleAcceleration(signedMovement);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hier ein weiterer Trick: &lt;code&gt;signedMovement&lt;/code&gt; wird berechnet, indem die Bewegung in eine vorzeichenbehaftete Ganzzahl umgewandelt wird, die &lt;code&gt;0&lt;/code&gt; als &amp;ldquo;nicht gedreht&amp;rdquo;, &lt;code&gt;-1&lt;/code&gt; als &amp;ldquo;links gedreht&amp;rdquo; und &lt;code&gt;1&lt;/code&gt; als &amp;ldquo;rechts gedreht&amp;rdquo; darstellt. Dabei wird das Verhalten bei Integer-Überlauf ausgenutzt, in Kombination mit der Interpretation von &amp;ldquo;signedness&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Abschließend wird die Akkumulation durchgeführt und die Beschleunigung, sofern konfiguriert, dem Akkumulator hinzugefügt, je nachdem, wie schnell der Geber gedreht wird.&lt;/p&gt;&#10;&lt;p&gt;Um diese beiden Methoden ohne Verzweigungen zu programmieren, habe ich mehrere Abende Grübelei und praktisch einen halben Block kariertes Papier benötigt. Offen gesagt haben mich diese paar Zeilen Code an den Rande der Leistungsfähigkeit meines Hirns gebracht. Ich habe das Programmieren eben nicht von der Pike auf gelernt 😅&lt;/p&gt;&#10;&lt;h3 id="die-methode-getaccumulate"&gt;Die Methode getAccumulate()&lt;/h3&gt;&#10;&lt;p&gt;Sie ist wirklich einfach. Sie gibt nur den internen akkumulierten Wert zurück, der der Konfiguration des Encoders entspricht, wie viele Ticks einen Schritt ausmachen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;int16_t&lt;/span&gt; Encoder&lt;span style="color:#f92672"&gt;::&lt;/span&gt;getAccumulate()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; (encoderAccumulate &lt;span style="color:#f92672"&gt;/&lt;/span&gt; stepsPerNotch);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="die-methode-getincrement"&gt;Die Methode getIncrement()&lt;/h3&gt;&#10;&lt;p&gt;Gibt zurück um wie viele Ticks sich der Encoder seit dem letzten Aufruf dieser Methode bewegt hat.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;int16_t&lt;/span&gt; Encoder&lt;span style="color:#f92672"&gt;::&lt;/span&gt;getIncrement()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int16_t&lt;/span&gt; accu &lt;span style="color:#f92672"&gt;=&lt;/span&gt; getAccumulate();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int16_t&lt;/span&gt; encoderIncrements &lt;span style="color:#f92672"&gt;=&lt;/span&gt; accu &lt;span style="color:#f92672"&gt;-&lt;/span&gt; lastEncoderAccumulate;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastEncoderAccumulate &lt;span style="color:#f92672"&gt;=&lt;/span&gt; accu;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; (encoderIncrements);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="ermittlung-des-tasterzustands"&gt;Ermittlung des Tasterzustands&lt;/h2&gt;&#10;&lt;h3 id="die-methode-handlebutton"&gt;Die Methode handleButton()&lt;/h3&gt;&#10;&lt;p&gt;Alle Tasterzustände leiten sich von zwei Basiszuständen ab: gedrückt oder nicht gedrückt. Dies spiegelt sich in der Logik wider:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Button&lt;span style="color:#f92672"&gt;::&lt;/span&gt;handleButton()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (lastGetButtonCount &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; ENC_BUTTONINTERVAL)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastGetButtonCount &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (digitalRead(pinBTN) &lt;span style="color:#f92672"&gt;==&lt;/span&gt; pinActiveState)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; handleButtonPressed();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; handleButtonReleased();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (doubleClickTicks &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;--&lt;/span&gt;doubleClickTicks;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Der Taster kann so konfiguriert werden, dass er seltener ausgelesen wird als der Encoder. Wenn dies der Fall ist, bleibt diese Methode zwischenzeitlich ohne Aktion.&lt;/p&gt;&#10;&lt;h3 id="taste-gedrückt"&gt;Taste gedrückt:&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;handleButtonPressed()&lt;/code&gt; kann entweder &lt;code&gt;Closed&lt;/code&gt;, &lt;code&gt;Held&lt;/code&gt; oder &lt;code&gt;LongPressRepeat&lt;/code&gt; zurückgeben, je nachdem wie der Taster konfiguriert ist und wie lange er bereits gedrückt wurde.&lt;/p&gt;&#10;&lt;h3 id="taste-nicht-gedrückt"&gt;Taste nicht gedrückt:&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;handleButtonReleased()&lt;/code&gt; kann entweder &lt;code&gt;Clicked&lt;/code&gt;, &lt;code&gt;Released&lt;/code&gt; oder &lt;code&gt;Doubleclicked&lt;/code&gt; zurückgeben, abhängig von der Konfiguration des Tasters und der Anzahl der Klicks innerhalb einer bestimmten Zeit.&lt;/p&gt;&#10;&lt;h3 id="konfiguration-der-bibliothek"&gt;Konfiguration der Bibliothek&lt;/h3&gt;&#10;&lt;p&gt;Das Verhalten der Encoder/Tastenerkennung kann in der Datei &lt;code&gt;encoder.h&lt;/code&gt; mit Hilfe der unten aufgeführten Konstanten geändert werden. Die Werte wurden bereits optimiert und fühlen sich, zumindest für mich, natürlich an. Sie sollten geändert werden, wenn Sie ein anderes Serviceintervall als &lt;code&gt;1ms&lt;/code&gt; verwenden möchten, da diese Werte alle dieses Intervall als Referenz nehmen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ----------------------------------------------------------------------------&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Acceleration configuration (for 1ms calls to ::service())&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;constexpr&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; ENC_ACCEL_START &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;150&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// The smaller this value, the quicker you must turn to activate acceleration.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;constexpr&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; ENC_ACCEL_SLOPE &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;75&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// the smaller this value, the stronger the acceleration will manipulate values.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Button configuration (values for 1ms timer service calls)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;constexpr&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; ENC_BUTTONINTERVAL &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// check button every x ms, also debouce time&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;constexpr&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt; ENC_DOUBLECLICKTIME &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;400&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// second click within x ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;constexpr&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt; ENC_LONGPRESSREPEATINTERVAL &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// reports repeating-held every x ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;constexpr&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt; ENC_HOLDTIME &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1200&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// report held button after x ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ----------------------------------------------------------------------------&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="verwenden-sie-sie-in-ihrem-projekt"&gt;Verwenden Sie sie in Ihrem Projekt!&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Bibliothek ist eine Open-Source-Software, die unter der MIT-Lizenz steht.&lt;/li&gt;&#10;&lt;li&gt;PlatformIO-Benutzer? Ganz einfach, suchen Sie sie in der Ansicht &amp;lsquo;Bibliotheken&amp;rsquo;, die Tags sind Arduino, Encoder, Schallbert. Dann ist es nur noch ein Klick, um sie zu Ihrer Lösung hinzuzufügen.&lt;/li&gt;&#10;&lt;li&gt;Alternativ können Sie auch das &lt;a href="https://github.com/Schallbert/encoder" target="_blank" rel="noopener noreferrer" class="external-link"&gt;repository&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; klonen oder forken und die Header/Cpp-Datei nach eigenem Ermessen verwenden.&lt;/li&gt;&#10;&lt;li&gt;Sie möchten etwas beitragen? Vielleicht haben Sie eine effizientere Zustandsberechnung für die Taster? Ich würde mich freuen, Ihren Pull Request in meinem Posteingang zu finden.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>CNC 'Opferplatte'</title><link>https://blog.schallbert.de/projects/cnc_spoilboard/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/cnc_spoilboard/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/spoilboard-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Spoilboard for my CNC"&#10; title="CNC &amp;#39;Opferplatte&amp;#39;" /&gt;&#10;&lt;h2 id="was-ist-eine-opferplatte"&gt;Was ist eine &amp;lsquo;Opferplatte&amp;rsquo;?&lt;/h2&gt;&#10;&lt;p&gt;Eine &amp;lsquo;Opferplatte&amp;rsquo; ist ein Brett, was auf dem Maschinenbett befestigt und zumeist abgeplant wird. Auf ihm wiederum werden dann die Werkstücke befestigt. Es nennt sich Opferplatte, weil die Fräser beim Durchfräsen des Werkstückes meist einige Zehntel Millimeter tiefer in das Material tauchen als es stark ist, um jegliche Fasern sicher zu trennen und die Werkstückunterseite nicht auszufransen.&lt;/p&gt;&#10;&lt;p&gt;Zudem können Werkstücke ohne viel Aufhebens direkt auf der Platte festgeschraubt werden. Ist die Platte einige Zeit in betrieb und droht, uneben zu werden, lässt sie sich meist noch ein paar weitere male abplanen und somit weiternutzen, bevor sie dann nach langem produktiven Einsatz erneuert werden muss.&lt;/p&gt;&#10;&lt;p&gt;Ein weiterer Vorteil der Opferplatte ist, dass selbst bei fehlerhaften Fräsbefehlen nicht direkt der teure Maschinentisch in Mitleidenschaft gezogen wird und zumeist sogar der Fräser diese Fehler überlebt.&lt;/p&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: mittel 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: ~60€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~6h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;a href="https://blog.schallbert.de/cnc-spoilboard-diy/"&gt;Zur Anleitung&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/crop.jpg" alt="Image: spoilboard features" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Das Design verwendet Schlitze für Hammermuttern in verschiedenen Winkeln, um eine sehr flexible Aufspannung zu ermöglichen. Es umfasst auch einen Anschlag, der die Vorbereitung bei Fertigung aus mehreren Plattenrohlingen erleichtert: Der Werkstücknullpunkt liegt immer an der gleichen Stelle, und das Einspannen gegen den Anschlag genügt meist bereits, so dass kein Niederhalten in Z-Richtung erforderlich ist. Der Anschlag hat zwei Aussparungen: Eine zur Aufnahme des Werkzeuglängensensors und eine weitere am XY-Nullpunkt, um Werkzeugkollisionen beim schnellen Positionieren zu vermeiden.&lt;/p&gt;&#10;&lt;h3 id="einfache-einstellung-für-fast-jede-cnc"&gt;Einfache Einstellung für fast jede CNC&lt;/h3&gt;&#10;&lt;p&gt;Die Maße, die ich genommen habe, passen für eine Basicline 0607 Portalfräsmaschine. Da ich aber ein parametrisches CAD-Tool verwende, müssen nur die Abmessungen an Ihre Maschine angepasst und einige Zeichnungsfehler behoben werden, sodass eigene Entwürfe schnell von der Hand gehen sollten.&lt;/p&gt;&#10;&lt;h2 id="die-idee"&gt;Die Idee&lt;/h2&gt;&#10;&lt;p&gt;Wie bereits erwähnt, muss meine Opferplatte die folgenden Kriterien erfüllen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Sie muss auf dem Grundrahmen der CNC-Maschine befestigt werden können (Hammermuttern / M5-Schrauben)&lt;/li&gt;&#10;&lt;li&gt;Befestigungspunkt in der Nähe des XY-Nullpunkts&lt;/li&gt;&#10;&lt;li&gt;T-Nuten für flexible Montage bei 0°, 22,5°, 45°, 67,5°, 90°&lt;/li&gt;&#10;&lt;li&gt;Anschlag, Schnellmontage auf der Opferplatte zur Vereinfachung des Spannens und Verbesserung der Wiederholgenauigkeit&lt;/li&gt;&#10;&lt;li&gt;Anschlag zur Aufnahme des Werkzeuglängensensors&lt;/li&gt;&#10;&lt;li&gt;Anschlag zur Bereitstellung eines Ausschnitts am XY-Nullpunkt&lt;/li&gt;&#10;&lt;li&gt;Soll auf der CNC, für die sie entwickelt wurde, herstellbar sein (bei Bedarf mehrstufig)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="entwurfsphase-cad"&gt;Entwurfsphase: CAD&lt;/h2&gt;&#10;&lt;p&gt;Ich begann mit einer Maßtabelle in Freecad und ein paar leeren Skizzen.&lt;/p&gt;&#10;&lt;h3 id="montage-am-grundrahmen"&gt;Montage am Grundrahmen&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/spoilboard_mount.jpg" alt="Image: Opferplatte Draufsicht, Montage am Rahmen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;In die Tabellenkalkulation habe ich zunächst die Abmessungen des Grundrahmens und des CNC-Verfahrbereichs sowie die Parameter der Einschlagmutter, der Unterlegscheibe und des Befestigungselements eingegeben.&#10;Ich maß die genauen Positionen der T-Muttern des Grundrahmens, an denen ich das Opferplatte befestigen wollte, und gab sie in die Kalkulationstabelle ein.&lt;/p&gt;&#10;&lt;p&gt;Danach öffnete ich die erste Skizze und zeichnete das Opferplatten-Rechteck, wobei ich die Abmessungen mit den Daten aus der Tabelle so hinzufügte, dass die Skizze &amp;ldquo;fully constrained&amp;rdquo; - also vollständig bestimmt ist. Ich fügte Konstruktionslinien hinzu (d. h. Linien, die in der endgültigen Zeichnung nicht zu sehen sind, aber dazu dienen, Zwangsbedingungen zu verankern, Linien zu zeichnen oder die Symmetrie innerhalb einer Zeichnung zu markieren), wo die Zentren der T-Muttern des Grundrahmens positioniert sind.&lt;/p&gt;&#10;&lt;p&gt;Weiter fügte ich konzentrische Kreise für Unterlegscheibensenkungen und Durchgangslöcher in gleichmäßigen Abständen hinzu. Sie werden später die Befestigungselemente und ihre Unterlegscheiben aufnehmen. Über ihre endgültige Position auf der Y-Achse habe ich noch nicht entschieden, da die T-Muttern der Opferplatte einige dieser Befestigungspunkte beeinträchtigen würden.&lt;/p&gt;&#10;&lt;p&gt;Die CNC-Bearbeitung für diese Skizze muss auf der &amp;ldquo;Oberseite&amp;rdquo; der Opferplatte durchgeführt werden.&lt;/p&gt;&#10;&lt;h3 id="schlitze-für-t-muttern-und-befestigungspunkte-für-den-anschlag"&gt;Schlitze für T-Muttern und Befestigungspunkte für den Anschlag&lt;/h3&gt;&#10;&lt;p&gt;Nun war es wieder an der Zeit, der Tabelle einige Daten hinzuzufügen: T-Nut-Parameter, Winkel, Randbreite wie folgt:&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/tslot_data.jpg" alt="Image: spoilboard t-nut slot data" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Zunächst musste ich auf dem Opferplatte Konstruktionslinien hinzufügen, um den Verfahrbereich der CNC zu markieren. Danach musste ich etwas Platz für den Anschlag reservieren. Erst dann konnte ich mit dem Zeichnen und Positionieren der T-Nuten beginnen, die jeweils unterschiedliche Winkel und Längen hatten, je nachdem, welchen Rand ich aus Stabilitätsgründen beibehalten wollte.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/spoilboard_drawing.jpg" alt="Image: spoilboard t-nut slot drawing" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Jede T-Nut erhielt eine Symmetrie-Konstruktionslinie, um die herum zwei Rechtecke zur Markierung der Schlitz- und Falzschultern liegen.&lt;/p&gt;&#10;&lt;p&gt;Dann fügte ich Senkungen für Schraubmuttern hinzu, die später den Anschlag am Rand der Opferplatte halten werden. In ihrer Mitte sind Durchgangsbohrungen für die Befestigungselemente vorgesehen.&lt;/p&gt;&#10;&lt;p&gt;In einem letzten Schritt passte ich die Positionen der Löcher in der Skizze für die Grundrahmenbefestigung an, wo sie mit den Schlitzen für die T-Muttern kollidierten.&lt;/p&gt;&#10;&lt;p&gt;Die CNC-Arbeit für diese Skizze wird auf der &amp;ldquo;Unterseite&amp;rdquo; der Opferplatte ausgeführt - die Hammermuttern funktionieren nur, wenn sie von unten greifen.&lt;/p&gt;&#10;&lt;h3 id="anschlag"&gt;Anschlag&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/fence.jpg" alt="Image: spoilboard fence" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Hier müssen nur noch einige weitere Daten in das Tabellenblatt eingefügt werden: Ellipsoidabmessungen für den XY-Nullpunktausschnitt, den Werkzeuglängensensor und die Breite des Anschlags auf der Y-Achse. Seine Abmessungen auf der X-Achse waren bereits durch den Verfahrbereich der Maschine vorgegeben.&lt;/p&gt;&#10;&lt;p&gt;Das Zeichnen des Anschlags war einfach, da ich mit dem Werkzeug &amp;ldquo;Kante verbinden&amp;rdquo; Positionen von Linien aus anderen Skizzen importieren konnte. Der Anschlag ist ein eigener &amp;ldquo;Job&amp;rdquo; mit einem eigenen physischen Teil auf der CNC, und er wird nur auf der Oberseite gefräst.&lt;/p&gt;&#10;&lt;h2 id="planungsphase-cam"&gt;Planungsphase: CAM&lt;/h2&gt;&#10;&lt;p&gt;Jetzt kam der schwierige Teil: Statt drei Jobs wie oben angegeben, brauchte ich eigentlich sechs. Das liegt daran, dass der Verfahrbereich der CNC kleiner ist als die Abmessungen der Opferplatte. Außerdem plante ich mit Rohlingen, die bereits die richtigen Umrissmaße hatten, damit ich sie auf die Maschine einpassen konnte.&lt;/p&gt;&#10;&lt;p&gt;Der erste Teil dieser Arbeit bestand darin, Senkungen und Löcher für das untere Ende der Opferplatten-Halterungen zu schaffen. Ich setzte den Nullpunkt ganz unten links auf der Maschine (X0, Y-max), damit ich sicher sein konnte, dass die relativen Koordinaten passen würden.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/mount_cam.jpg" alt="Image: spoilboard cam mount_lower" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Für den oberen Abschnitt wurde das oberste Loch auf der linken Seite als XY-Referenznullpunkt verwendet, der in der vorherigen Arbeit erstellt wurde. Auf diese Weise konnte ich das Material flexibler einspannen.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/mount_cam_up.jpg" alt="Image: spoilboard cam mount_upper" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Ich wiederholte diese Vorgehensweise für die beiden anderen Jobs.&lt;/p&gt;&#10;&lt;p&gt;Für die T-Nut Schlitze musste ich die Skizze spiegeln und mein Material um die Y-Achse drehen, da die Unterseite der Opferplatte hier bearbeitet werden musste. Das könnte man auch in CAM machen, aber ich zog es vor, für diese Aufgabe schnell wieder ins CAD zu wechseln.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/tslot_cam.jpg" alt="Image: spoilboard cam tslot_lower" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Für all diese Arbeiten habe ich einen 6-mm-Holzfräser mit 2 Schneiden und einer Fräserlänge von &lt;code&gt;21mm&lt;/code&gt; verwendet. Da dies meine allerersten Schritte mit einer CNC waren, habe ich mit sehr konservativen Parametern für dieses Werkzeug geplant: &lt;code&gt;S24000U/min&lt;/code&gt;, &lt;code&gt;F2800mm/min&lt;/code&gt;, &lt;code&gt;Z+=6mm&lt;/code&gt;, &lt;code&gt;XY+=85%&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Ich habe den ersten Job sogar &amp;ldquo;in der Luft&amp;rdquo; ausgeführt, um sicherzugehen, dass ich alles richtig eingestellt hatte.&lt;/p&gt;&#10;&lt;h2 id="ausführung"&gt;Ausführung&lt;/h2&gt;&#10;&lt;p&gt;Ich war ratlos, als ich das Material endlich auf dem Maschinenbett hatte: Wo sollte ich das bloß einspannen? Die Seiten waren durch das Portal belegt, was sich ja bewegen würde. An der Rückseite der Maschine ist bereits die Kellerwand&amp;hellip; Würde es also ausreichen, das Werkstück nur an der Vorderseite einzuspannen? Und da ich ganz durchfräsen wollte, sollte ich eine weitere Opferplatte unter meiner Opferplatte anbringen, damit ich nicht versehentlich in das Maschinenbett schnitt?&lt;/p&gt;&#10;&lt;p&gt;Ich beschloss, dass diese Einwände berechtigt seien, und wartete, bis das Wochenende vorbei war, um etwas Altholz und mehr doppelseitiges Klebeband zu kaufen.&lt;/p&gt;&#10;&lt;p&gt;So habe ich niedergehalten: Standard-Zwingen unten, doppelseitiges Klebeband oben. Hat sehr gut funktioniert.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/tslot.jpg" alt="Image spoilboard cutting executed" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Bevor ich den Rahmen endgültig auf der Opferplatte befestigte, habe ich ihn mit einer &amp;ldquo;Spiral&amp;rdquo;-Strategie abgeplant. Es genügte bereits &lt;code&gt;Z=0,3mm&lt;/code&gt; tief zu fräsen. Dabei kam ein 14-mm-Planfräser bei einer Geschwindigkeit von nur &lt;code&gt;5500U/min&lt;/code&gt; zum Einsatz. Fast 30 Minuten vergingen während der Ausführung. Das Ergebnis war eine Oberfläche, die so glatt war wie die des Originalmaterials.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/leveling.jpg" alt="Image: spoilboard leveling" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="bewertung"&gt;Bewertung&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/hole.jpg" alt="IMage: Very clean cut in MDF" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Zum Glück hat am Ende alles geklappt. Wie sauber der die Schnitte sind! MDF und scharfe Schaftfräser scheinen gut miteinander zu harmonieren. Hinweis: Für Arbeiten in MDF braucht man eine wirklich gute Absaugung, da bei der Bearbeitung atemberaubende Mengen an Staub entstehen.&lt;/p&gt;&#10;&lt;p&gt;Mit der Gesamtqualität bin ich sehr zufrieden. Jeder einzelne Befestigungspunkt an Maschinenbett und Anschlag passt, und der Werkzeuglängensensor sitzt gut in seiner Aussparung.&lt;/p&gt;&#10;&lt;p&gt;Es gibt zwei Dinge zu bemängeln: Der von mir verwendete Schaftfräser ist sehr laut und gibt ein unangenehmes kreischendes Geräusch von sich, dem ich sogar &lt;a href="https://blog.schallbert.de/my-endmill-screams/"&gt;einen Blogbeitrag gewidmet habe&lt;/a&gt;.&#10;Zweitens habe ich mich in meinem CAM-Tool nicht ausreichend um die Fräsrichtung gekümmert. Auf diese Weise entschied sich das CAM-Tool, einen Durchgang im Gegenlauf und einen weiteren im Gleichlauf durchzuführen (der Schlitz war so breit wie das Werkzeug, für das CAM-Tool ist es also beides Gleichlauffräsen, nur die &amp;ldquo;Seite&amp;rdquo; des Werkzeugeingriffs ändert sich). Der Unterschied ist ein sehr kleiner, aber spürbarer Grat an der Kante des Anschlags.&lt;/p&gt;&#10;&lt;p&gt;Mit dieser Opferplatte kann ich jetzt Platten bis zu einer Größe von 620 mm x 620 mm bearbeiten!&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spoilboard/completed.jpg" alt="Image: spoilboard jobs complete" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Nun, ein Manko ist noch der Werkzeuglängensensor. Er ragt in den Arbeitsbereich hinein, was ein Problem darstellt, wenn ich große Werkstücke bearbeiten will und Werkzeugwechsel geplant sind: In diesem Fall verwende ich Makros zur Messung der Werkzeuglänge, weshalb ich den Sensor nicht entfernen möchte. Als Iteration könnte ich den Anschlag so überarbeiten, dass er den Sensor vollständig umschließt.&lt;/p&gt;&#10;&lt;p&gt;Vielleicht.&lt;/p&gt;&#10;&lt;p&gt;Später.&lt;/p&gt;&#10;</description></item><item><title>Datenschutz</title><link>https://blog.schallbert.de/privacy/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/privacy/</guid><description type="html">&#10;&lt;h2 id="schallberts-blick-auf-den-datenschutz"&gt;Schallberts Blick auf den Datenschutz&lt;/h2&gt;&#10;&lt;p&gt;Ich mag ungern getrackt werden. Also versuche ich ebenfalls, andere nicht zu tracken. Daher ist meine Website statisch, muss also keine Inhalte nachladen oder ständig Kontakt zu Drittanbietern halten um korrekt zu funktionieren. Ausnahme ist lediglich mein &lt;a href="https://makertube.net/c/cnc_corner/videos?s=1" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Kanal auf der Videoplattform MakerTube&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Ich nutze gerade so viele Links und Inhalte dritter wie ich unbedingt benötige, um diese Website einigermaßen modern und ihre Navigation bequem gestalten zu können. Sie werden hier kein Cookie Banner oder Ähnliches finden, da diese Website keine Cookies benötigt.&lt;/p&gt;&#10;&lt;h3 id="datenerhebung-in-stichpunkten"&gt;Datenerhebung in Stichpunkten&lt;/h3&gt;&#10;&lt;p&gt;Diese Website benutzt&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Keine Cookies&lt;/li&gt;&#10;&lt;li&gt;Kein Tracking&lt;/li&gt;&#10;&lt;li&gt;Keine Analyse-Tools wie Google Analytics etc.&lt;/li&gt;&#10;&lt;li&gt;Keine eingebetteten Inhalte dritter wie Youtube, Social Media etc.; &lt;em&gt;Ausnahme: mein Kanal auf MakerTube&lt;/em&gt;&lt;/li&gt;&#10;&lt;li&gt;Alle Links auf meiner Seite sind reine Verweise und verwenden keine Skripte. Solange sie nicht geklickt werden, bekommen Drittanbieter auch nichts von Ihnen mit.&lt;/li&gt;&#10;&lt;li&gt;Ich zeichne allerdings fehlgeschlagene Authentifikationsversuche an meinen Server auf, um ihn vor &lt;a href="https://blog.schallbert.de/server-protection/"&gt;Angriffen zu schützen&lt;/a&gt;. Die aus diesem Anlass registrierten IP-Adressen und Zeitstempel der Loginversuche werden automatisiert in rollierenden Logdateien zwischengepuffert und in IPtables der Netzwerkschicht meines Servers für das &amp;ldquo;Banning&amp;rdquo; hinterlegt. Nach einer Woche werden die Daten ebenso automatisch gelöscht.&lt;/li&gt;&#10;&lt;li&gt;Der Hoster dieser Website ist Hetzner. Auf deren Hardware läuft die von mir gemietete virtuelle Maschine. Hetzner sammelt Logs und einige Daten zur Bereitstellung der Website, welche in &lt;a href="https://www.hetzner.com/legal/privacy-policy/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hetzners Datenschutzerklärung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; eingesehen werden können.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="fragen-oder-bedenken"&gt;Fragen oder Bedenken?&lt;/h3&gt;&#10;&lt;p&gt;Schreiben Sie mir gern formlos per Github Discussions: &lt;a href="https://github.com/Schallbert/schallbert.github.io/discussions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;github.com/Schallbert&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Ansonsten finden Sie meine Kontaktdaten unter &lt;a href="https://blog.schallbert.de/about/"&gt;About&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="datenschutzerklärung-das-kleingedruckte-nach-e-recht24de"&gt;Datenschutzerklärung (das Kleingedruckte nach E-Recht24.de)&lt;/h2&gt;&#10;&lt;h4 id="1-datenschutz-auf-einen-blick"&gt;1. Datenschutz auf einen Blick&lt;/h4&gt;&#10;&lt;h5 id="allgemeine-hinweise"&gt;Allgemeine Hinweise&lt;/h5&gt;&#10;&lt;p&gt;Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten&#10;passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie&#10;persönlich identifiziert werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen&#10;Sie unserer unter diesem Text aufgeführten Datenschutzerklärung.&lt;/p&gt;&#10;&lt;h4 id="datenerfassung-auf-dieser-website"&gt;Datenerfassung auf dieser Website&lt;/h4&gt;&#10;&lt;h5 id="wer-ist-verantwortlich-für-die-datenerfassung-auf-dieser-website"&gt;Wer ist verantwortlich für die Datenerfassung auf dieser Website?&lt;/h5&gt;&#10;&lt;p&gt;Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten&#10;können Sie dem Abschnitt „Hinweis zur Verantwortlichen Stelle“ in dieser Datenschutzerklärung&#10;entnehmen.&lt;/p&gt;&#10;&lt;h5 id="wie-erfassen-wir-ihre-daten"&gt;Wie erfassen wir Ihre Daten?&lt;/h5&gt;&#10;&lt;p&gt;Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z. B. um&#10;Daten handeln, die Sie in ein Kontaktformular eingeben.&#10;Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z. B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie diese Website betreten.&lt;/p&gt;&#10;&lt;h5 id="wofür-nutzen-wir-ihre-daten"&gt;Wofür nutzen wir Ihre Daten?&lt;/h5&gt;&#10;&lt;p&gt;Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu gewährleisten. Andere&#10;Daten können zur Analyse Ihres Nutzerverhaltens verwendet werden.&lt;/p&gt;&#10;&lt;h5 id="welche-rechte-haben-sie-bezüglich-ihrer-daten"&gt;Welche Rechte haben Sie bezüglich Ihrer Daten?&lt;/h5&gt;&#10;&lt;p&gt;Sie haben jederzeit das Recht, unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer&#10;gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung oder&#10;Löschung dieser Daten zu verlangen. Wenn Sie eine Einwilligung zur Datenverarbeitung erteilt haben,&#10;können Sie diese Einwilligung jederzeit für die Zukunft widerrufen. Außerdem haben Sie das Recht, unter&#10;bestimmten Umständen die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.&#10;Des Weiteren steht Ihnen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu.&#10;Hierzu sowie zu weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit an uns wenden.&lt;/p&gt;&#10;&lt;h4 id="2-hosting"&gt;2. Hosting&lt;/h4&gt;&#10;&lt;h5 id="externes-hosting"&gt;Externes Hosting&lt;/h5&gt;&#10;&lt;p&gt;Diese Website wird bei einem externen Dienstleister gehostet (Hoster). Die personenbezogenen Daten, die&#10;auf dieser Website erfasst werden, werden auf den Servern des Hosters gespeichert. Hierbei kann es sich v.&#10;a. um IP-Adressen, Kontaktanfragen, Meta- und Kommunikationsdaten, Vertragsdaten, Kontaktdaten,&#10;Namen, Websitezugriffe und sonstige Daten, die über eine Website generiert werden, handeln.&#10;Der Einsatz des Hosters erfolgt zum Zwecke der Vertragserfüllung gegenüber unseren potenziellen und&#10;bestehenden Kunden (Art. 6 Abs. 1 lit. b DSGVO) und im Interesse einer sicheren, schnellen und effizienten&#10;Bereitstellung unseres Online-Angebots durch einen professionellen Anbieter (Art. 6 Abs. 1 lit. f DSGVO).&#10;Sofern eine entsprechende Einwilligung abgefragt wurde, erfolgt die Verarbeitung ausschließlich auf&#10;Grundlage von Art. 6 Abs. 1 lit. a DSGVO und § 25 Abs. 1 TTDSG, soweit die Einwilligung die Speicherung&#10;von Cookies oder den Zugriff auf Informationen im Endgerät des Nutzers (z. B. Device-Fingerprinting) im&#10;Sinne des TTDSG umfasst. Die Einwilligung ist jederzeit widerrufbar.&#10;Unser Hoster wird Ihre Daten nur insoweit verarbeiten, wie dies zur Erfüllung seiner Leistungspflichten&#10;erforderlich ist und unsere Weisungen in Bezug auf diese Daten befolgen.&lt;/p&gt;&#10;&lt;p&gt;Wir setzen folgenden Hoster ein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Hetzner Online GmbH&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Industriestr. 25&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;91710&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;Gunzenhausen&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;Germany&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Tel.&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;+49 (0)9831 505-0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Fax&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;+49 (0)9831 505-3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;Email&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;info@hetzner.com&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="3-allgemeine-hinweise-und-pflichtinformationen"&gt;3. Allgemeine Hinweise und Pflichtinformationen&lt;/h4&gt;&#10;&lt;h5 id="datenschutz"&gt;Datenschutz&lt;/h5&gt;&#10;&lt;p&gt;Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre&#10;personenbezogenen Daten vertraulich und entsprechend den gesetzlichen Datenschutzvorschriften sowie&#10;dieser Datenschutzerklärung.&#10;Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten erhoben.&#10;Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden können. Die vorliegende&#10;Datenschutzerklärung erläutert, welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie&#10;und zu welchem Zweck das geschieht.&#10;Wir weisen darauf hin, dass die Datenübertragung im Internet (z. B. bei der Kommunikation per E-Mail)&#10;Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht&#10;möglich.&lt;/p&gt;&#10;&lt;h5 id="hinweis-zur-verantwortlichen-stelle"&gt;Hinweis zur verantwortlichen Stelle&lt;/h5&gt;&#10;&lt;p&gt;Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/address.jpg" alt="Image: summonable address" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Verantwortliche Stelle ist die natürliche oder juristische Person, die allein oder gemeinsam mit anderen über&#10;die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z. B. Namen, E-Mail-Adressen o. Ä.)&#10;entscheidet.&lt;/p&gt;&#10;&lt;h5 id="speicherdauer"&gt;Speicherdauer&lt;/h5&gt;&#10;&lt;p&gt;Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer genannt wurde, verbleiben&#10;Ihre personenbezogenen Daten bei uns, bis der Zweck für die Datenverarbeitung entfällt. Wenn Sie ein&#10;berechtigtes Löschersuchen geltend machen oder eine Einwilligung zur Datenverarbeitung widerrufen,&#10;werden Ihre Daten gelöscht, sofern wir keine anderen rechtlich zulässigen Gründe für die Speicherung Ihrer&#10;personenbezogenen Daten haben (z. B. steuer- oder handelsrechtliche Aufbewahrungsfristen); im&#10;letztgenannten Fall erfolgt die Löschung nach Fortfall dieser Gründe.&lt;/p&gt;&#10;&lt;h5 id="allgemeine-hinweise-zu-den-rechtsgrundlagen-der-datenverarbeitung-auf-dieser-website"&gt;Allgemeine Hinweise zu den Rechtsgrundlagen der Datenverarbeitung auf dieser Website&lt;/h5&gt;&#10;&lt;p&gt;Sofern Sie in die Datenverarbeitung eingewilligt haben, verarbeiten wir Ihre personenbezogenen Daten auf&#10;Grundlage von Art. 6 Abs. 1 lit. a DSGVO bzw. Art. 9 Abs. 2 lit. a DSGVO, sofern besondere Datenkategorien&#10;nach Art. 9 Abs. 1 DSGVO verarbeitet werden. Sofern Sie in die Speicherung von Cookies oder in den Zugriff&#10;auf Informationen in Ihr Endgerät (z. B. via Device-Fingerprinting) eingewilligt haben, erfolgt die&#10;Datenverarbeitung zusätzlich auf Grundlage von § 25 Abs. 1 TTDSG. Die Einwilligung ist jederzeit&#10;widerrufbar. Sind Ihre Daten zur Vertragserfüllung oder zur Durchführung vorvertraglicher Maßnahmen&#10;erforderlich, verarbeiten wir Ihre Daten auf Grundlage des Art. 6 Abs. 1 lit. b DSGVO. Des Weiteren&#10;verarbeiten wir Ihre Daten, sofern diese zur Erfüllung einer rechtlichen Verpflichtung erforderlich sind auf&#10;Grundlage von Art. 6 Abs. 1 lit. c DSGVO. Die Datenverarbeitung kann ferner auf Grundlage unseres&#10;berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO erfolgen. Über die jeweils im Einzelfall einschlägigen&#10;Rechtsgrundlagen wird in den folgenden Absätzen dieser Datenschutzerklärung informiert.&lt;/p&gt;&#10;&lt;h5 id="hinweis-zur-datenweitergabe-in-die-usa-und-sonstige-drittstaaten"&gt;Hinweis zur Datenweitergabe in die USA und sonstige Drittstaaten&lt;/h5&gt;&#10;&lt;p&gt;Wir verwenden unter anderem Tools von Unternehmen mit Sitz in den USA oder sonstigen&#10;datenschutzrechtlich nicht sicheren Drittstaaten. Wenn diese Tools aktiv sind, können Ihre&#10;personenbezogene Daten in diese Drittstaaten übertragen und dort verarbeitet werden. Wir weisen darauf&#10;hin, dass in diesen Ländern kein mit der EU vergleichbares Datenschutzniveau garantiert werden kann.&#10;Beispielsweise sind US-Unternehmen dazu verpflichtet, personenbezogene Daten an Sicherheitsbehörden&#10;herauszugeben, ohne dass Sie als Betroffener hiergegen gerichtlich vorgehen könnten. Es kann daher nicht&#10;ausgeschlossen werden, dass US-Behörden (z. B. Geheimdienste) Ihre auf US-Servern befindlichen Daten zu&#10;Überwachungszwecken verarbeiten, auswerten und dauerhaft speichern. Wir haben auf diese Verarbeitungstätigkeiten keinen Einfluss.&lt;/p&gt;&#10;&lt;h5 id="widerruf-ihrer-einwilligung-zur-datenverarbeitung"&gt;Widerruf Ihrer Einwilligung zur Datenverarbeitung&lt;/h5&gt;&#10;&lt;p&gt;Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine&#10;bereits erteilte Einwilligung jederzeit widerrufen. Die Rechtmäßigkeit der bis zum Widerruf erfolgten&#10;Datenverarbeitung bleibt vom Widerruf unberührt.&lt;/p&gt;&#10;&lt;h5 id="widerspruchsrecht-gegen-die-datenerhebung-in-besonderen-fällen-sowie-gegen-direktwerbung-art-21-dsgvo"&gt;Widerspruchsrecht gegen die Datenerhebung in besonderen Fällen sowie gegen Direktwerbung (Art. 21 DSGVO)&lt;/h5&gt;&#10;&lt;p&gt;WENN DIE DATENVERARBEITUNG AUF GRUNDLAGE VON ART. 6 ABS. 1 LIT. E ODER F DSGVO&#10;ERFOLGT, HABEN SIE JEDERZEIT DAS RECHT, AUS GRÜNDEN, DIE SICH AUS IHRER BESONDEREN&#10;SITUATION ERGEBEN, GEGEN DIE VERARBEITUNG IHRER PERSONENBEZOGENEN DATEN&#10;WIDERSPRUCH EINZULEGEN; DIES GILT AUCH FÜR EIN AUF DIESE BESTIMMUNGEN GESTÜTZTES&#10;PROFILING. DIE JEWEILIGE RECHTSGRUNDLAGE, AUF DENEN EINE VERARBEITUNG BERUHT,&#10;ENTNEHMEN SIE DIESER DATENSCHUTZERKLÄRUNG. WENN SIE WIDERSPRUCH EINLEGEN,&#10;WERDEN WIR IHRE BETROFFENEN PERSONENBEZOGENEN DATEN NICHT MEHR VERARBEITEN, ES&#10;SEI DENN, WIR KÖNNEN ZWINGENDE SCHUTZWÜRDIGE GRÜNDE FÜR DIE VERARBEITUNG&#10;NACHWEISEN, DIE IHRE INTERESSEN, RECHTE UND FREIHEITEN ÜBERWIEGEN ODER DIE&#10;VERARBEITUNG DIENT DER GELTENDMACHUNG, AUSÜBUNG ODER VERTEIDIGUNG VON&#10;RECHTSANSPRÜCHEN (WIDERSPRUCH NACH ART. 21 ABS. 1 DSGVO).&#10;WERDEN IHRE PERSONENBEZOGENEN DATEN VERARBEITET, UM DIREKTWERBUNG ZU BETREIBEN,&#10;SO HABEN SIE DAS RECHT, JEDERZEIT WIDERSPRUCH GEGEN DIE VERARBEITUNG SIE&#10;BETREFFENDER PERSONENBEZOGENER DATEN ZUM ZWECKE DERARTIGER WERBUNG&#10;EINZULEGEN; DIES GILT AUCH FÜR DAS PROFILING, SOWEIT ES MIT SOLCHER DIREKTWERBUNG IN&#10;VERBINDUNG STEHT. WENN SIE WIDERSPRECHEN, WERDEN IHRE PERSONENBEZOGENEN DATEN&#10;ANSCHLIESSEND NICHT MEHR ZUM ZWECKE DER DIREKTWERBUNG VERWENDET (WIDERSPRUCH&#10;NACH ART. 21 ABS. 2 DSGVO).&lt;/p&gt;&#10;&lt;h5 id="beschwerderecht-bei-der-zuständigen-aufsichtsbehörde"&gt;Beschwerderecht bei der zuständigen Aufsichtsbehörde&lt;/h5&gt;&#10;&lt;p&gt;Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags&#10;automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format&#10;aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen&#10;verlangen, erfolgt dies nur, soweit es technisch machbar ist.&lt;/p&gt;&#10;&lt;h5 id="ssl--bzw-tls-verschlüsselung"&gt;SSL- bzw. TLS-Verschlüsselung&lt;/h5&gt;&#10;&lt;p&gt;Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher Inhalte, wie zum&#10;Beispiel Bestellungen oder Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von&#10;„http://“ auf „https://“ wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.&#10;Wenn die SSL- bzw. TLS-Verschlüsselung aktiviert ist, können die Daten, die Sie an uns übermitteln, nicht&#10;von Dritten mitgelesen werden.&lt;/p&gt;&#10;&lt;h5 id="auskunft-löschung-und-berichtigung"&gt;Auskunft, Löschung und Berichtigung&lt;/h5&gt;&#10;&lt;p&gt;Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf unentgeltliche&#10;Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger und den&#10;Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung oder Löschung dieser Daten. Hierzu sowie&#10;zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit an uns wenden.&lt;/p&gt;&#10;&lt;h5 id="recht-auf-einschränkung-der-verarbeitung"&gt;Recht auf Einschränkung der Verarbeitung&lt;/h5&gt;&#10;&lt;p&gt;Sie haben das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.&#10;Hierzu können Sie sich jederzeit an uns wenden. Das Recht auf Einschränkung der Verarbeitung besteht in&#10;folgenden Fällen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Wenn Sie die Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten bestreiten, benötigen wir&#10;in der Regel Zeit, um dies zu überprüfen. Für die Dauer der Prüfung haben Sie das Recht, die&#10;Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.&lt;/li&gt;&#10;&lt;li&gt;Wenn die Verarbeitung Ihrer personenbezogenen Daten unrechtmäßig geschah/geschieht, können Sie&#10;statt der Löschung die Einschränkung der Datenverarbeitung verlangen.&lt;/li&gt;&#10;&lt;li&gt;Wenn wir Ihre personenbezogenen Daten nicht mehr benötigen, Sie sie jedoch zur Ausübung,&#10;Verteidigung oder Geltendmachung von Rechtsansprüchen benötigen, haben Sie das Recht, statt der&#10;Löschung die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.&lt;/li&gt;&#10;&lt;li&gt;Wenn Sie einen Widerspruch nach Art. 21 Abs. 1 DSGVO eingelegt haben, muss eine Abwägung zwischen&#10;Ihren und unseren Interessen vorgenommen werden. Solange noch nicht feststeht, wessen Interessen&#10;überwiegen, haben Sie das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten&#10;zu verlangen.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschränkt haben, dürfen diese Daten – von&#10;ihrer Speicherung abgesehen – nur mit Ihrer Einwilligung oder zur Geltendmachung, Ausübung oder&#10;Verteidigung von Rechtsansprüchen oder zum Schutz der Rechte einer anderen natürlichen oder&#10;juristischen Person oder aus Gründen eines wichtigen öffentlichen Interesses der Europäischen Union oder&#10;eines Mitgliedstaats verarbeitet werden.&lt;/p&gt;&#10;&lt;h4 id="4-datenerfassung-auf-dieser-website"&gt;4. Datenerfassung auf dieser Website&lt;/h4&gt;&#10;&lt;h5 id="server-log-dateien"&gt;Server-Log-Dateien&lt;/h5&gt;&#10;&lt;p&gt;Der Provider der Seiten erhebt und speichert automatisch Informationen in so genannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Browsertyp und Browserversion&lt;/li&gt;&#10;&lt;li&gt;verwendetes Betriebssystem&lt;/li&gt;&#10;&lt;li&gt;Referrer URL&lt;/li&gt;&#10;&lt;li&gt;Hostname des zugreifenden Rechners&lt;/li&gt;&#10;&lt;li&gt;Uhrzeit der Serveranfrage&lt;/li&gt;&#10;&lt;li&gt;IP-Adresse&#10;Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen.&#10;Die Erfassung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Der Websitebetreiber hat&#10;ein berechtigtes Interesse an der technisch fehlerfreien Darstellung und der Optimierung seiner Website –&#10;hierzu müssen die Server-Log-Files erfasst werden.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="anfrage-per-e-mail-oder-telefon"&gt;Anfrage per E-Mail oder Telefon&lt;/h5&gt;&#10;&lt;p&gt;Wenn Sie uns per E-Mail oder Telefon kontaktieren, wird Ihre Anfrage inklusive aller daraus&#10;hervorgehenden personenbezogenen Daten (Name, Anfrage) zum Zwecke der Bearbeitung Ihres Anliegens&#10;bei uns gespeichert und verarbeitet. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.&#10;Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO, sofern Ihre Anfrage mit&#10;der Erfüllung eines Vertrags zusammenhängt oder zur Durchführung vorvertraglicher Maßnahmen&#10;erforderlich ist. In allen übrigen Fällen beruht die Verarbeitung auf unserem berechtigten Interesse an der&#10;effektiven Bearbeitung der an uns gerichteten Anfragen (Art. 6 Abs. 1 lit. f DSGVO) oder auf Ihrer&#10;Einwilligung (Art. 6 Abs. 1 lit. a DSGVO) sofern diese abgefragt wurde.&#10;Die von Ihnen an uns per Kontaktanfragen übersandten Daten verbleiben bei uns, bis Sie uns zur Löschung&#10;auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt&#10;(z. B. nach abgeschlossener Bearbeitung Ihres Anliegens). Zwingende gesetzliche Bestimmungen, insbesondere gesetzliche Aufbewahrungsfristen, bleiben unberührt.&lt;/p&gt;&#10;&lt;h4 id="quelle"&gt;Quelle:&lt;/h4&gt;&#10;&lt;p&gt;&lt;a href="https://www.e-recht24.de" target="_blank" rel="noopener noreferrer" class="external-link"&gt;e-recht24.de&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Diese Website programmieren</title><link>https://blog.schallbert.de/projects/thissite/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/thissite/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/thissite/desktop-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Schallbert&amp;#39;s Desktop"&#10; title="Diese Website programmieren" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: Mittel 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: 0€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~15h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="die-idee"&gt;Die Idee&lt;/h2&gt;&#10;&lt;p&gt;Die Idee entstand durch mein &lt;a href="https://blog.schallbert.de/projects/mobfobamp/"&gt;MobFobAmp&lt;/a&gt;-Projekt, für das ich kein Github-Repository hatte, da es keinen Code oder ähnliche Dokumentation gab. Also beschloss ich eine Website einzurichten, auf der ich Anleitungen für dieses Open-Hardware-Projekt bereitstellen kann, damit andere das Projekt nachbauen können. Ich wollte eine einfach zu hostende Lösung, die nicht zu viel Arbeit beim Einrichten erfordert. Da ich keinen eigenen Server habe, habe ich um Hilfe gebeten. &lt;a href="https://github.com/moritzmar" target="_blank" rel="noopener noreferrer" class="external-link"&gt;moritzmar&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; empfahl mir, für den Anfang &amp;ldquo;Github-Pages&amp;rdquo; als Site-Host zu verwenden, um eine einfache automatische Bereitstellung und globale Reichweite zu gewährleisten. Also habe ich das mal ausprobiert.&lt;/p&gt;&#10;&lt;h2 id="erste-schritte"&gt;Erste Schritte&lt;/h2&gt;&#10;&lt;p&gt;Eine statische Site kam mir genau Recht. Ohne die Notwendigkeit einer Datenbank oder eines großen Content-Management-Frameworks bleibt mein Site-Layout einfach und minimalistisch; ich wollte am Anfang nur dieses eine Projekt hosten.&#10;&lt;a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/about-github-pages-and-jekyll" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github empfiehlt die Verwendung von Jekyll&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Jekyll_%28software%29_Logo.png" alt="Image: Jekyll Logo" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Jekyll Logo&lt;/span&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:Jekyll_%28software%29_Logo.png#file" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;als Website-Generator. Ich hatte einige Probleme mit dem Windows Subsystem für Linux (WSL), da es meine Terminaleingaben nicht akzeptierte, also folgte ich der &lt;a href="https://jekyllrb.com/docs/installation/windows/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und verwendete den RubyInstaller, der bei mir sofort funktionierte.&lt;/p&gt;&#10;&lt;h2 id="einrichtung-der-site"&gt;Einrichtung der Site&lt;/h2&gt;&#10;&lt;p&gt;Mir wurde klar, dass ich vielleicht mehrere meiner Projekte dokumentieren möchte. Deshalb kam ich auf die Idee, Projekte in Gruppen zusammenzufassen, jede mit einer Überschrift, einem kleinen Text und einem Miniaturbild. Wenn man darauf klickt, kommt man auf die jeweilige Projektseite. Das würde eine Menge Programmierarbeit für die Seiteneinrichtung und das Layout bedeuten, mit der ich nicht allzu viel Zeit verbringen wollte - meine anderen Projekte warten&amp;hellip;&lt;/p&gt;&#10;&lt;h2 id="erster-versuch-mit-themes"&gt;Erster Versuch mit Themes&lt;/h2&gt;&#10;&lt;p&gt;Also folgte ich einer weiteren Anregung von Github-Pages: Die Verwendung von vordefinierten &amp;ldquo;Themes&amp;rdquo;.&#10;Zuerst habe ich das &lt;a href="http://jekyllthemes.org/themes/agency/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Agency Theme&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verwendet. Es hat eine tolle Landing Page mit einem großen Bild und unterstützt Sammlungen, die das von mir gewünschte Gruppieren von Projekten ermöglichen. Leider ist das Thema ein &amp;ldquo;Remote-Thema&amp;rdquo;, so dass die Anpassungsmöglichkeiten begrenzt waren, z. B. konnte ich eine Sammlung nicht auf eine andere Seite verweisen lassen, sondern es öffnete sich immer ein &amp;ldquo;Popup&amp;rdquo;, und das wollte ich einfach nicht. (Später habe ich herausgefunden, dass Remote-Layouts überschrieben werden können, aber da war es zu spät, denn ich hatte bereits zu einem anderen Theme-Anbieter gewechselt&amp;hellip;)&#10;Das Theme, das ich derzeit verwende, ist das &amp;ldquo;Minimal Mistakes Theme&amp;rdquo; (siehe Fußzeile der Website), das sehr gut dokumentiert ist. Nach dem Herunterladen und ein paar ersten Schritten kämpfte ich mit benutzerdefinierten Farben (_sass) und den Kollektionen - wieder einmal - da ich zunächst nicht verstand, wie Jekyll die Bindungen hinter den Kulissen durchführt. Einige meiner Schwierigkeiten sind in den Blog-Beiträgen vom Juni 2021 zu finden.&lt;/p&gt;&#10;&lt;h3 id="seitenlayouts-hero-screens"&gt;Seitenlayouts: Hero Screens&lt;/h3&gt;&#10;&lt;p&gt;Um die Navigation einfach und intuitiv zu gestalten, sollte man sich ein paar Gedanken darüber machen, wie das Layout der Seite aussehen soll. Die Herangehensweise wird sich bei jedem Seitentyp aufgrund der unterschiedlichen Komplexität und Dokumentstruktur unterscheiden. &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/thissite/hero_landing.jpg" alt="Image: landing hero" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Auf der Landing-/Homepage zum Beispiel soll die Navigation mitlaufen, so dass die Kategorien der Website immer sichtbar sind. Sie soll ein großes, breites Overlay-Bild mit der Seitenbeschreibung erhalten. Darunter sollen die neuesten Beiträge aufgelistet werden.&#10;Dies ist das Beispiel für die &amp;ldquo;Hero Pages&amp;rdquo; der Kollektion: &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/thissite/hero_collection.jpg" alt="Image: collection hero" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Die Projekte und ihre Inhalte enthalten eine Seitennavigation in der linken Seitenleiste, können ein scrollbares Panoramabild enthalten und haben eine breite Einstellung, um eine große Menge an Text im Inhaltsbereich anzuzeigen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/thissite/hero_project.jpg" alt="Image: project hero" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die &amp;ldquo;hero screen&amp;rdquo;-Skizzen für die Layouts dieser Website finden Sie &lt;a href="https://blog.schallbert.de/assets/docs/site_hero_screens.pdf"&gt;hier&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="sammlungen--kollektionen"&gt;Sammlungen / Kollektionen&lt;/h3&gt;&#10;&lt;p&gt;Sammlungen (englisch &amp;ldquo;Collections&amp;rdquo;) sind eine gute Möglichkeit, Inhalte zu gruppieren. Sehen Sie sich meine &lt;a href="https://blog.schallbert.de/projects/"&gt;Open Hardware Projekte&lt;/a&gt; Seite an. Sie können alle Projekte in dieser Kategorie mit einem kleinen Teaser-Bild und einer Beschreibung finden, um einen Überblick zu bekommen.&lt;/p&gt;&#10;&lt;p&gt;So kann die Konfiguration der Sammlung aussehen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Überlegen Sie sich einen passenden Namen für Ihre Sammlung, z.B. &lt;code&gt;electronics&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Der Name der Sammlung wird in &lt;code&gt;_config.yml&lt;/code&gt; als &lt;code&gt;collections: electronics&lt;/code&gt; definiert.&lt;/li&gt;&#10;&lt;li&gt;Der Sammlungsname muss in &lt;code&gt;#defaults&lt;/code&gt; als &lt;code&gt;type: electronics&lt;/code&gt; gesetzt werden, damit Jekyll weiß, wie der tatsächliche Inhalt zu interpretieren ist.&lt;/li&gt;&#10;&lt;li&gt;Eine Sammlungsseite muss erstellt werden (z.B. im Ordner &lt;code&gt;_pages&lt;/code&gt;) mit dem Attribut &lt;code&gt;collection: electronics&lt;/code&gt;. Dies ist die Seite, die die Übersicht der Artikel in der Sammlung anzeigt.&lt;/li&gt;&#10;&lt;li&gt;Es muss ein Ordner mit demselben Namen wie der Name der Sammlung mit führendem Unterstrich erstellt werden: &lt;code&gt;_electronics&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Alle Dokumente in diesem Ordner werden nach einem vollständigen Bauvorgang automatisch auf der Sammlungsseite angezeigt.&lt;/li&gt;&#10;&lt;li&gt;Diese Dokumente sollten ein &amp;ldquo;front matter&amp;rdquo; (Abschnitt vor dem eigentlichen Inhalt in der Datei, gekennzeichnet mit &lt;code&gt;---&lt;/code&gt;) erhalten, damit sie wie in der entsprechenden &lt;code&gt;_layouts&lt;/code&gt;-Datei beschrieben angezeigt werden können, z.B. &lt;code&gt;collection&lt;/code&gt;. Vergisst man dies, wirft einem Jekyll in der Konsole seitenlange, unverständliche Fehlermeldungen um die Ohren.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="mehr-als-das-standard-theme"&gt;Mehr als das Standard-Theme&lt;/h2&gt;&#10;&lt;p&gt;Ich habe zwar einige Theme-Standardeinstellungen überschrieben, verwende aber zumeist die bestehendenTheme-Infrastruktur. Dennoch hat meine Seite ein paar spezielle Anforderungen, so dass ich das Theme anpassen und neue Funktionen hinzufügen musste. Diese werden in den folgenden Abschnitten beschrieben.&lt;/p&gt;&#10;&lt;h3 id="navigation"&gt;Seitenleiste: Seitennavigation&lt;/h3&gt;&#10;&lt;p&gt;Da der Navigationsbaum meiner Website ziemlich flach ist und ich nicht viele Seiten habe, zu denen ich navigieren muss, wollte ich, dass die linke Seitenleiste die Seitennavigation und nicht die Website-Navigation enthält. Daher bräuchte ich keine rechte Seitenleiste, so dass mehr Platz für den Seiteninhalt zur Verfügung stünde.&lt;/p&gt;&#10;&lt;h3 id="inhaltsverzeichnis-auf-der-linken-seite"&gt;Inhaltsverzeichnis auf der linken Seite&lt;/h3&gt;&#10;&lt;p&gt;Wie &lt;a href="https://blog.schallbert.de/jekyll-toc/"&gt;hier&lt;/a&gt; und &lt;a href="https://blog.schallbert.de/jekyll-toc/"&gt;dort&lt;/a&gt; geschrieben, war ich nie 100%ig zufrieden damit, wie meine Projektseiten aussahen: &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-06-17_before_tocleft.jpg" alt="Image: Projektseite bevor ich toc_left einrichtete" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Die ursprüngliche Idee des Themes ist, dass der Inhalt von zwei Seitenleisten eingerahmt wird. Die linke kann entweder Autoreninformationen, benutzerdefinierte Inhalte oder die Seitennavigation anzeigen. Der Inhaltsbereich ist zwischen breit und normal konfigurierbar, wobei in letzterem Fall Platz für eine zweite Seitenleiste auf der rechten Seite bleibt. Die rechte Seitenleiste ist so konfigurierbar, dass sie entweder gar nicht vorhanden ist oder das Inhaltsverzeichnis der Seite anzeigt.&lt;/p&gt;&#10;&lt;p&gt;Das Ziel war also die rechte Seitenleiste für mein Design zu eliminieren und statt der Seitennavigation in der linken Seitenleiste die Seitennavigation dorthin zu verschieben.&lt;/p&gt;&#10;&lt;h3 id="sidebar"&gt;Wo das Design der Seitenleiste definiert wird&lt;/h3&gt;&#10;&lt;p&gt;Es geht um den Ordner &lt;code&gt;/_layouts&lt;/code&gt;, in dem Informationen darüber gespeichert werden, wie ein Seitenlayout aussehen soll. Das fragliche Seitendesign heißt &lt;code&gt;single.html&lt;/code&gt; und ruft an einer bestimmten Stelle&#10;&lt;code&gt;include sidebar.html&lt;/code&gt; auf, das sich in &lt;code&gt;/_includes/sidebar.html&lt;/code&gt; befindet. Also habe ich mir einen neuen Parameter ausgedacht, mit dem ich die Anzeige des Inhaltsverzeichnisses in der linken Seitenleiste steuern kann, habe &lt;code&gt;toc_left&lt;/code&gt; gewählt und eine Bedingung hinzugefügt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;sidebar sticky&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&amp;lt;!-- ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{.% if page.toc_left %}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; {.% include toc_left nav=page.toc_left %}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{.% endif %}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;... --&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;div&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(Trick: Ich musste &lt;a href="https://en.wikipedia.org/wiki/Jinja_%28template_engine%29" target="_blank" rel="noopener noreferrer" class="external-link"&gt;jinja&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; code auskommentieren und mit &lt;code&gt;...&lt;/code&gt; anführen, denn &lt;a href="https://jekyllrb.com/docs/liquid/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Liquid&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; würde sie sonst selbst als Kommandos erkennen)&lt;/p&gt;&#10;&lt;h3 id="implementierung-des-inhaltsverzeichnisses"&gt;Implementierung des Inhaltsverzeichnisses&lt;/h3&gt;&#10;&lt;p&gt;Es sollte in der linken Seitenleiste erscheinen. Also muste ich nur eine Datei namens &lt;strong&gt;toc_left&lt;/strong&gt; erzeugen, die den Generator für das Inhaltsverzeichnis wie folgt aufruft:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;aside&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;toc_left&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;nav&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;toc&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;markdown&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;header&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style="color:#f92672"&gt;h4&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;nav__title&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style="color:#f92672"&gt;i&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;fas fa-{{ include.icon | default: &amp;#39;file-alt&amp;#39; }}&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style="color:#f92672"&gt;i&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&amp;lt;!-- ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{.{ include.title | .default: site.data.ui-text[site.locale].toc_label }}&amp;lt;/h4&amp;gt;&amp;lt;/header&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;{.% include toc.html sanitize=true html=content h_min=1 h_max=6 class=&amp;#34;toc__menu&amp;#34; skip_no_ids=true %} &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;... --&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;nav&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;aside&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="audio-einbettungen"&gt;Audio-Einbettungen&lt;/h3&gt;&#10;&lt;p&gt;Audio-Embeds sind diese kleinen Player-ähnlichen Dinger auf einer Website, die oft zu einer Streaming-Plattform oder ähnlichem weiterleiten. Ich brauche sie für einige meiner Audio-Projekte wie dieses &lt;a href="https://blog.schallbert.de/projects/mobfobamp/"&gt;mobfobamp&lt;/a&gt;: nur eine Überschrift und der Player darunter.&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/embed-audio.jpg" alt="Image: embed-audio" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;a href="https://stackoverflow.com/a/63807971/13757172" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dieser Beitrag&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; hat mir sehr geholfen, so dass ich es einfach in das &lt;code&gt;_include&lt;/code&gt; einfügen konnte, es ein wenig modifizieren konnte, und es funktionierte auf Anhieb.&lt;/p&gt;&#10;&lt;h3 id="favicon"&gt;Platzierung des Favicons&lt;/h3&gt;&#10;&lt;p&gt;Ich folgte &lt;a href="https://ptc-it.de/add-favicon-to-mm-jekyll-site" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieser Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, um ein Favicon für die Website zu platzieren.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2021-06-13_favicon.jpg" alt="Image: favicon" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der Pfad des Favicons muss unabhängig von dem Ordner, in dem sich die gerenderte Seite befindet, gültig sein. Ich musste &lt;code&gt;../&lt;/code&gt; entsprechend der Tiefe der Seiten in der Ordnerstruktur der Website hinzufügen, um sicherzustellen, dass das Favicon nicht nur auf der Startseite, sondern auch auf Unterseiten angezeigt werden kann.&lt;/p&gt;&#10;&lt;h3 id="optimierung-für-suchmaschinen"&gt;Optimierung für Suchmaschinen&lt;/h3&gt;&#10;&lt;p&gt;Da meine Website einige Wochen lang nicht von Suchmaschinen gefunden wurde, beschloss ich, eine so genannte &amp;ldquo;SEO&amp;rdquo; (Suchmaschinenoptimierung) durchzuführen. Deshalb fügte ich &lt;a href="http://jekyll.github.io/jekyll-seo-tag" target="_blank" rel="noopener noreferrer" class="external-link"&gt;jekyll-seo-tag&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; hinzu und überarbeitete z.B. alle meine Links nach &lt;a href="https://jsinibardy.com/optimize-seo-jekyll#seo-101" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Juliette Sinibardy&amp;rsquo;s SEO 101&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Danach habe ich meine Webseite dem Web-Analyse-Audit von Lighthouse unterzogen. &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/thissite/lighthouse_audit.jpg" alt="Image: Lighthouse Audit Ergebnisse" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Für einen ersten Versuch sieht es meiner Meinung nach ziemlich gut aus. Das größte Problem auf der Leistungsseite scheint der CSS-Overhead zu sein.&lt;/p&gt;&#10;&lt;h3 id="sicherheitsrichtlinie-für-inhalte"&gt;Sicherheitsrichtlinie für Inhalte&lt;/h3&gt;&#10;&lt;p&gt;Der Lighthouse-Audit hat gezeigt, dass es eine gute Idee wäre, externe Skripte - wie sie die Basisversion meines Themes enthält - einzuschränken. Also habe ich eine Content Security Policy (CSP) wie folgt eingeführt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;webrick&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;headers&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;Content-Security-Policy&lt;/span&gt;: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;default-src &amp;#39;self&amp;#39;; &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;script-src &amp;#39;self&amp;#39;; &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;connect-src &amp;#39;self&amp;#39;; &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;img-src &amp;#39;self&amp;#39;; &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;style-src &amp;#39;self&amp;#39; &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;font-src &amp;#39;self;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mit dieser strikten Auslegung werden Inhalte Dritter gar nicht mehr geladen (aber leider meine eigenen teils auch nicht).&lt;/p&gt;&#10;&lt;p&gt;Anfänglich wurde das Hintergrundbild meiner Homepage nicht angezeigt, meine eingebetteten Audiodateien konnten nicht abgespielt werden und alle Icons, die von extern geladen wurden, waren ebenfalls weg. Das Hinzufügen von &lt;code&gt;media-src: 'self';&lt;/code&gt; und etwas mehr Lektüre in &lt;a href="https://www.jakobwillforss.com/post/content-security-policy-for-font-awesome-on-netlify" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Jakob Wilforss&amp;rsquo; Blog&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und vor allem &lt;a href="https://web.dev/strict-csp/?utm_source=lighthouse&amp;amp;utm_medium=lr" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Lighthouses eigene Vorschläge&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; halfen bei der Lösung dieses Problems. Mit Hilfe der &lt;em&gt;Web developer Tools&lt;/em&gt; des Browsers habe ich die fehlenden Ressourcen ermittelt, die das Theme im Hintergrund zu laden versucht, so dass mein aktualisiertes CSP wie folgt aussieht:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;webrick&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;headers&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;Content-Security-Policy&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;default-src &amp;#39;self&amp;#39; &amp;#39;unsafe-inline&amp;#39; https://cdn.jsdelivr.net/; &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;script-src &amp;#39;self&amp;#39;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;connect-src &amp;#39;self&amp;#39;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;img-src &amp;#39;self&amp;#39; &amp;#39;unsafe-inline&amp;#39; https://upload.wikimedia.org/wikipedia/commons/ https://raw.githubusercontent.com;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;style-src &amp;#39;self&amp;#39; &amp;#39;unsafe-inline&amp;#39; https://cdn.jsdelivr.net/;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;font-src &amp;#39;self&amp;#39; &amp;#39;unsafe-inline&amp;#39; https://cdn.jsdelivr.net/; &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;media-src &amp;#39;self&amp;#39;; &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;object-src &amp;#39;none&amp;#39;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;base-uri &amp;#39;none&amp;#39;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Ende 2021 bin ich vom Nachladen von Inhalten über Drittanbieter (oben namentlich Font Awesome und Wikimedia) auf die eigene, statische Bereitstellung übergegangen. Somit gibt es keine Dritten mehr, die beim Aufruf meiner Seite etwas von Ihnen mitbekommen - und die Seite lädt etwas schneller. Meine Content Security Policy habe ich wieder auf die Obere der beiden Darstellungen zurückgedreht, &lt;code&gt;media-src 'self';&lt;/code&gt; aber beibehalten.&lt;/p&gt;&#10;&lt;h2 id="fehler"&gt;Fehler&lt;/h2&gt;&#10;&lt;p&gt;Hier sammele ich Fehler, deren Lösung trivial erscheint, die mich aber dennoch ganze Abende gekostet haben.&lt;/p&gt;&#10;&lt;h3 id="jekyll-serve-utf-8-incompatible-character-encoding"&gt;Jekyll serve: &lt;code&gt;UTF-8&lt;/code&gt; incompatible character encoding&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/thissite/bundle_error.jpg" alt="Image: bundle error when front matter is missing" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Man gibt &lt;code&gt;bundle exec jekyll serve&lt;/code&gt; ein und erhält einen seltsamen &lt;code&gt;UTF-8&lt;/code&gt; &amp;ldquo;incompatible character encoding&amp;rdquo; Fehler? Die Ursache ist einfach: Eine der zu konvertierenden Dateien hat höchstwahrscheinlich keinen Header, der in das &amp;ldquo;Front matter&amp;rdquo; der Seite übersetzt werden kann, die aus dieser Datei generiert wird. Einfach die übliche Kopfzeile zur fraglichen Datei hinzufügen und schon funktioniert es wieder:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Feeds and Speeds database&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;description&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;for (light) CNC milling machines&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;header&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;blah/testimage.jpg&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="webseite-wird-nicht-dargestellt-404"&gt;Webseite wird nicht dargestellt: &lt;code&gt;404&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;Dieser Fehler war besonders fies versteckt, denn auf meinem Rechner als &lt;code&gt;localhost&lt;/code&gt; wird die Seite einwandfrei dargestellt - nur &amp;ldquo;in Produktion&amp;rdquo; erscheint, wenn ich die Seite aufrufe, gar nichts.&lt;/p&gt;&#10;&lt;p&gt;Ursache: neuerdings verwende ich das &lt;strong&gt;permalink:&lt;/strong&gt; Attribut in der Frontmatter von Seiten, die übersetzt werden sollen und auf einer tieferen Ebene liegen - für diese möchte ich die Links kurz halten, daher bekommen sie manuell erstellte Permalinks. Nur wenn ich ihnen ein &amp;ldquo;trailing slash&amp;rdquo; gebe, lassen sie sich auch online aufrufen. Beispiel:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Falsch:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;This Page won&amp;#39;t be displayed&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;permalink&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/projects/pageerror&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Richtig:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;This Page will be displayed!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;permalink&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/projects/nopageerror/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Wird auf dem deutschen Blog zu:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="website-hosting"&gt;Website-Hosting&lt;/h2&gt;&#10;&lt;p&gt;Diese Seite ist selbst ein normales Gitea-Repository. Es wird automatisch mit einer von Github Actions entlehnten Aktion gebaut, sobald Änderungen zum &lt;code&gt;Main&lt;/code&gt; des Repositorys hinzugefügt werden.&lt;/p&gt;&#10;&lt;h3 id="update-jan-2024"&gt;Update Jan-2024&lt;/h3&gt;&#10;&lt;p&gt;Inzwischen bin ich von Github-Pages nach Gitea &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/"&gt;umgezogen&lt;/a&gt; und hoste meine Webpräsenz nun selber. Dennoch sind alle Inhalte dieser Seite weiterhin als Repository verfügbar und können leicht von mir gewartet und auf Stand gebracht werden. Außerdem &lt;a href="https://blog.schallbert.de/server-protection/"&gt;schütze ich die Site nun vor unautorisiertem Zugriff&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="zusammenfassung"&gt;Zusammenfassung&lt;/h2&gt;&#10;&lt;p&gt;Sich damit vertraut zu machen wie Jekyll die Seite erstellt, wie das Theme mit meinen Befehlen interagiert und wie man das Verhalten des Themes überschreibt, hat einige Stunden gekostet. Aber ich denke, dass diese Seite eine klare Struktur und ein mehr oder weniger &amp;ldquo;professionelles&amp;rdquo; Aussehen besitzt - und ich musste nicht eine einzige Zeile JavaScript oder HTML schreiben, um das zu erreichen. Toll!&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Inhalt in Markdown &lt;code&gt;*.md&lt;/code&gt; geschrieben&lt;/li&gt;&#10;&lt;li&gt;Grundlegende Konfiguration der Seite in YAML &lt;code&gt;_config.yml&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Seitenlayouts in html und jinja &lt;code&gt;*.html&lt;/code&gt;, abgelegt in &lt;code&gt;_layouts&lt;/code&gt; und &lt;code&gt;_includes&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Seitenvariablen und Aussehen in SASS &lt;code&gt;*.scss&lt;/code&gt;, automatische Generierung von css-Dateien&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="lust-mitzumachen-oder-beiträge-beizusteuern"&gt;Lust mitzumachen oder Beiträge beizusteuern?&lt;/h2&gt;&#10;&lt;p&gt;Sie haben Ideen, Kritik oder interessante Dinge, die Sie mir mitteilen möchten? Nehmen Sie gern über meine &lt;a href="https://github.com/Schallbert/schallbert.github.io/discussions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Diskussions-Seite&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Kontakt auf.&lt;/p&gt;&#10;</description></item><item><title>GoogleTest Demo</title><link>https://blog.schallbert.de/projects/platformio_gtestgmock/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/platformio_gtestgmock/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/platformio_gtestgmock/build_test_success-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Googletest&amp;#39;s successful test results on the example program"&#10; title="GoogleTest Demo" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: Mittel 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: 0€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~2h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;Es ist schön, bei der Entwicklung komplexerer Projekte ein leistungsfähiges Unit-Test-Framework zur Hand zu haben, das viel Zeit bei der Fehlersuche spart (vor allem das On-Target-Debugging in der Embedded-Welt kann sehr zeitaufwändig sein), die Codequalität verbessert und möglicherweise den Einblick in APIs und Architektur für andere verbessert.&lt;/p&gt;&#10;&lt;h2 id="die-welt-der-embedded-controller"&gt;Die Welt der &amp;ldquo;embedded controller&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;Traditionell waren Mikrocontroller auf der Speicherseite begrenzt. In der Vergangenheit hatten sie weder viel RAM noch PROGMEM, also war mein meist in schlankem &lt;code&gt;C&lt;/code&gt; geschrieben und die Konfiguration fand in Listen von &lt;code&gt;#define&lt;/code&gt; statt, die nicht viel Rechenzeit kosten, weil der Präprozessor die Arbeit machen würde und nicht der µC zur Laufzeit.&#10;Meine Unit-Tests waren immer an die Hardware gebunden, auf der sie liefen - nehmen Sie &lt;a href="https://github.com/ThrowTheSwitch/Unity" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Unity&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; als bekanntes Beispiel - aber jetzt wollte ich etwas Neues ausprobieren:&#10;Code objektorientiert schreiben und die Geschäftslogik unabhängig von der Hardware testen lassen. Ich wählte das &lt;a href="http://google.github.io/googletest" target="_blank" rel="noopener noreferrer" class="external-link"&gt;GoogleTest&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Framework und wollte eine nahtlose Integration in meinen Workflow mit &lt;a href="https://code.visualstudio.com" target="_blank" rel="noopener noreferrer" class="external-link"&gt;VScode&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und dessen Erweiterung &lt;a href="https://platformio.org" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PlatformIO&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; erreichen.&lt;/p&gt;&#10;&lt;p&gt;Hierfür blieb bis vor kurzem nur der Weg, &lt;em&gt;GoogleTest&lt;/em&gt; per &lt;em&gt;GNU Compiler Collection&lt;/em&gt; parallel zu betreiben und durch PlatformIO ansteuern zu lassen. Neuerdings allerdings lässt sich &lt;em&gt;GoogleTest&lt;/em&gt; als &lt;a href="https://registry.platformio.org/libraries/google/googletest" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PlatformIO Library&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; direkt nutzen. Daher gehe ich hier auf beide Wege ein.&lt;/p&gt;&#10;&lt;h2 id="googletest-direkt-in-platformio-verwenden"&gt;GoogleTest direkt in PlatformIO verwenden&lt;/h2&gt;&#10;&lt;p&gt;Hier gestaltet sich die Installation besonders einfach:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Mein Github-Repository &lt;a href="https://github.com/Schallbert/PlatformIO_gTestgMock/tree/googletest-native" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PlatformIO-gTestgMock&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; klonen.&lt;/li&gt;&#10;&lt;li&gt;Im Repository-Ordner ein Konsolenfenster öffnen und per &lt;code&gt;git checkout googletest-native&lt;/code&gt; auf den entsprechenden Branch wechseln.&lt;/li&gt;&#10;&lt;li&gt;Den Anweisungen in der &lt;em&gt;README&lt;/em&gt;-Datei des Repository folgen und das Setup ausprobieren.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;So sieht es aus, wenn die Tests erfolgreich durchgeführt wurden:&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/platformio_gtestgmock/native_test.jpg" alt="Image: googletest result success message in PlatformIO&amp;#39;s native environment" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h2 id="googletest-per-gcc-einsetzen"&gt;GoogleTest per GCC einsetzen&lt;/h2&gt;&#10;&lt;p&gt;Die benötigte Software kann man selbst (auch auf einem Windows&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; Rechner) installieren, um mit GoogleTest starten zu können.&lt;/p&gt;&#10;&lt;h3 id="mingw-herunterladen-und-installieren"&gt;MinGW herunterladen und installieren&lt;/h3&gt;&#10;&lt;p&gt;Siehe &lt;a href="https://community.platformio.org/t/unit-testing-with-gtest-gmock-on-env-desktop-on-arduino-platform/14354/7" target="_blank" rel="noopener noreferrer" class="external-link"&gt;diesen Thread&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; im Forum von PlatformIO, wenn es Fragen zu den folgenden Schritten gibt.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Unix-Benutzer: &lt;a href="https://blog.schallbert.de/projects/platformio_gtestgmock/#mingw-konfigurieren"&gt;bis hier&lt;/a&gt; die Schritte überspringen, denn wahrscheinlich werden nur die &lt;code&gt;libpthreadgc&lt;/code&gt;-Bibliotheken zusätzlich benötigt. Prüfen Sie das Vorhandensein aller Bibliotheken (aber lassen Sie das Präfix &lt;code&gt;mingw32-&lt;/code&gt; weg) und installieren ggf. fehlende nach. Stellen Sie sicher, dass die &lt;code&gt;PATH&lt;/code&gt;-Variable korrekt gesetzt ist.&lt;/li&gt;&#10;&lt;li&gt;Windows-Benutzer:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Laden Sie MinGW herunter, z.B. von &lt;a href="https://sourceforge.net/projects/mingw" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Sourceforge&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Installieren Sie durch Ausführen von &lt;code&gt;mingw-get-setup.exe&lt;/code&gt;. Wenn Sie grafische Benutzeroberflächen mögen, lassen Sie das entsprechende Kästchen angekreuzt. Ich empfehle &lt;em&gt;nicht&lt;/em&gt;, das Installationsverzeichnis zu ändern. Wenn Sie das doch tun, müssen Sie sich sich den neuen Pfad merken. &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/platformio_gtestgmock/mingw_install.jpg" alt="MinGW install" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="mingw-konfigurieren"&gt;MinGW konfigurieren&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Damit GoogleTest läuft, müssen zusätzliche Pakete installiert werden:&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;mingw32-gcc-g++&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;mingw32-libmingwex&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;mingw32-libmingwex-dll&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;mingw32-libmingwex-dev&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;mingw32-libpthreadgc-dll&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;mingw32-libpthreadgc-dev&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Um diese zu installieren,&#10;&lt;ul&gt;&#10;&lt;li&gt;Wenn Sie die GUI verwenden: Suchen Sie die Bibliotheken unter &amp;ldquo;MinGW Standard Libraries&amp;rdquo; und fügen Sie sie nacheinander hinzu/aktivieren/installieren Sie sie.&lt;/li&gt;&#10;&lt;li&gt;Benutzer des Kommandozeilen-Interpreters: Geben Sie &lt;code&gt;mingw-get install&lt;/code&gt; ein und fügen Sie dann den Paketnamen hinzu.&#10;Wenn Sie eine Fehlermeldung erhalten, die besagt, dass einige der Pakete bereits existieren, umso besser.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Jetzt müssen Sie den Ordner &amp;ldquo;bin&amp;rdquo; von MinGW zu Ihren Systempfadvariablen hinzufügen, damit PlatformIO sie später finden kann. Wenn Sie ihn nicht von Hand modifiziert haben, sollte er &lt;code&gt;C:\MinGW\bin&lt;/code&gt; lauten.&#10;&lt;ul&gt;&#10;&lt;li&gt;GUI-Fans: folgen Sie &lt;a href="https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieser Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;CLI: Geben Sie &lt;code&gt;set PATH=%PATH%;C:\MinGW\bin&lt;/code&gt; ein und überprüfen Sie, ob es mit &lt;code&gt;echo %PATH:;=&amp;amp;echo.%&lt;/code&gt; funktioniert.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Glückwunsch! Sie haben MinGW installiert, die Umgebungsvariablen so gesetzt, dass es von Drittanwendungen leichter gefunden wird, und es für die Zusammenarbeit mit GoogleTest konfiguriert.&lt;/p&gt;&#10;&lt;h3 id="platformio-konfigurieren"&gt;PlatformIO konfigurieren&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Nachdem die Variable &lt;code&gt;PATH&lt;/code&gt; gesetzt ist, muss VScode neu gestartet werden.&lt;/li&gt;&#10;&lt;li&gt;Damit PlatformIO korrekt mit dem gerade installierten Compiler verbunden wird, muss die &lt;code&gt;native&lt;/code&gt; Umgebung verwendet werden. Dort befindet sich der Compiler mit allen benötigten Umgebungsvariablen. Öffnen Sie ein PlatformIO-Terminal in einem Ihrer Projekte und geben Sie &lt;code&gt;pio platform install native&lt;/code&gt; ein. &lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/platformio_gtestgmock/install_native.jpg" alt="Image: how to install PIO native" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/li&gt;&#10;&lt;li&gt;Zwei Möglichkeiten:&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;A) Befolgen dieser Anleitung unter Verwendung meines Beispielprojektes wie &lt;a href="https://blog.schallbert.de/projects/platformio_gtestgmock/#writing-tests"&gt;unten&lt;/a&gt; beschrieben. Dann kann der nächste Abschnitt getrost übersprungen werden, da dieses Projekt vorkonfiguriert ist. Sie können nach Belieben damit herumspielen denn Sie verfügen von Anfang an über grundlegende Hardware-Abstraktionsschnittstellen.&lt;/li&gt;&#10;&lt;li&gt;B) Nehmen Sie Ihr eigenes Projekt und ändern Sie dessen &lt;code&gt;Platformio.ini&lt;/code&gt; so, dass GoogleTest ausgeführt werden kann.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ol start="4"&gt;&#10;&lt;li&gt;Dies sollten Sie zu Ihrer &lt;code&gt;Platformio.ini&lt;/code&gt; hinzufügen:&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;env:desktop]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;platform = native&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;build_flags = &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;-&lt;span style="color:#ae81ff"&gt;std=gnu++11&lt;/span&gt; &lt;span style="color:#75715e"&gt;# use installed GNU C++11 compiler.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;-&lt;span style="color:#ae81ff"&gt;pthread&lt;/span&gt; &lt;span style="color:#75715e"&gt;# found in gtest documentation&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;lib_deps = &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;&lt;span style="color:#ae81ff"&gt;googletest&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Will automatically load latest googletest lib&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;lib_ignore = &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;src&lt;/span&gt; &lt;span style="color:#75715e"&gt;# most main.cpp&amp;#39;s directly access the hardware. &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Can be removed if your project is different.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# PLUS: Any files that contain bare-metal hardware commands &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# (e.g. digitalWrite), g++ won&amp;#39;t have the headers or specialized compiler knowledge!&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;lib_compat_mode = off&lt;/span&gt; &lt;span style="color:#75715e"&gt;# Must-have for external stuff like gtest!&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Erklärung: Für die Umgebung &lt;code&gt;Desktop&lt;/code&gt; (= Computer ohne Microcontroller) wird der &lt;code&gt;native&lt;/code&gt; Compiler verwendet, d.h. GCC/G++, der oben in der Anleitung installiert und für den die &lt;code&gt;PATH&lt;/code&gt;-Variable festgelegt wurde. GoogleTest verwendet den C++11-Standard und Threading, der zu den Build-Flags hinzugefügt werden muss.&#10;Der Build von der &lt;code&gt;googletest&lt;/code&gt; Bibliothek ab. Es werden alle Dateien/Ordner ignoriert, die Quell- oder Headerdateien mit hardwarebezogenen Befehlen oder Headern enthalten. Der Bibliothekskompatibilitätsmodus muss ausgeschaltet sein, damit der Library Dependency Finder &lt;code&gt;googletest&lt;/code&gt; einbeziehen kann.&lt;/p&gt;&#10;&lt;ol start="5"&gt;&#10;&lt;li&gt;(Optional) Falls Sie lieber GUI-Schaltflächen verwenden als eine Zeile in die Konsole zu tippen um die Tests zu starten, erstellen Sie eine benutzerdefinierte &lt;a href="https://docs.platformio.org/en/latest/projectconf/build_configurations.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Konfiguration&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="tests-ausführen"&gt;Tests ausführen&lt;/h3&gt;&#10;&lt;p&gt;Für einen schnelleren Einstieg kann &lt;a href="https://github.com/Schallbert/PlatformIO_gTestgMock" target="_blank" rel="noopener noreferrer" class="external-link"&gt;mein Beispiel-Repository&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; geklont werden (den &lt;em&gt;main&lt;/em&gt; branch verwenden). Es enthält alle notwendigen Elemente, um zu prüfen, ob &lt;code&gt;gTest&lt;/code&gt; und &lt;code&gt;gMock&lt;/code&gt; korrekt funktionieren. So kann man direkt nachvollziehen, ob die Installation erfolgreich war.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Entpacken Sie die Zip-Datei bzw. verwenden Sie SSH, um das Repository zu klonen.&lt;/li&gt;&#10;&lt;li&gt;Öffnen Sie die Startseite der PlatformIO-Erweiterung, klicken Sie auf &amp;ldquo;Open Project&amp;rdquo;, und wählen Sie den Ordner aus.&lt;/li&gt;&#10;&lt;li&gt;Um die Unittests auszuführen, öffnen Sie ein PlatformIO-Terminal und geben Sie &lt;code&gt;pio test -vvv -e desktop -f test_desktop&lt;/code&gt; ein. (-vvv = wortreich -e = Umgebung, -f = Filter). Die Parameter sind erforderlich, weil die Tests nur auf der &amp;ldquo;Desktop&amp;rdquo;-Umgebung lauffähig sind, nicht aber auf dem Microcontroller.&lt;/li&gt;&#10;&lt;li&gt;Nach einiger Zeit sollte die unten dargestellte Meldung angezeigt werden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/platformio_gtestgmock/build_alltest_success.jpg" alt="successful build and test" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Super! Unit-Tests innerhalb von PlatformIO wurden gebaut und ausgeführt!&lt;/p&gt;&#10;&lt;h2 id="tipps-und-tricks"&gt;Tipps und Tricks&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Zu beachten ist&lt;/strong&gt;, dass der Ordner &lt;code&gt;PlatformIO_gTestgMock/.pio/build/desktop&lt;/code&gt; nun eine ausführbare Datei namens &lt;code&gt;program.exe&lt;/code&gt; enthält. Wird sie über die Kommandozeile ausgeführt, sollte das selbe Ergebnis wie in der obigen Abbildung herauskommen. Dies kann nützlich sein, wenn Tests nicht nur fehlschlagen, sondern das ganze Programm bei der Ausführung abstürzt. Zum Debuggen der Tests ist das auch hilfreich, da die CLI von PlatformIO dazu neigt, sich aufzuhängen. Man hat hier auch leichten Zugang zum Stracktrace - das sprengt den Rahmen dieser Übung aber bei Weitem.&lt;/p&gt;&#10;&lt;p&gt;Ab diesem Punkt&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;können Sie weiterlesen, um den Inhalt des Repositorys und die Funktionsweise von GoogleTest besser zu verstehen&lt;/li&gt;&#10;&lt;li&gt;Sie könnten das Schreiben von Tests üben, indem Sie weitere Fälle hinzufügen, z. B. um zu überprüfen, ob der Zustand &amp;ldquo;LOW&amp;rdquo; korrekt durchlaufen wird.&lt;/li&gt;&#10;&lt;li&gt;Sie könnten auch die Interfaces dieser Grundbausteine verbessern, z. B. durch Hinzufügen der Funktion &lt;code&gt;analogRead()&lt;/code&gt; zur Hardware-Abstraktionsschicht&lt;/li&gt;&#10;&lt;li&gt;Sie könnten Ihr eigenes Projekt darauf aufbauen, die Hardware-Abhängigkeiten sauber ausschalten und es so einfach auf verschiedene Hardware-Geräte und Hersteller portieren&lt;/li&gt;&#10;&lt;li&gt;Oder Sie beschäftigen sich weiter mit diesen anderen genialen Dingen, die Sie in die Tat umsetzen wollten, bevor Sie von diesem Tutorial abgelenkt wurden.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="repository-inhalte-erklärt"&gt;Repository-Inhalte erklärt&lt;/h2&gt;&#10;&lt;p&gt;Die folgenden Abschnitte behandeln die meisten Dateien im Repository und erklären, was sie im Detail tun. Die implementierte Geschäftslogik ist minimal und sollte als Beispielcode betrachtet werden. Ein reales Projekt, das alle auf dieser Seite dargestellten Muster verwendet, ist auf meiner &lt;a href="https://blog.schallbert.de/projects/tonuino/"&gt;Tonuino&lt;/a&gt;-Seite zu finden.&lt;/p&gt;&#10;&lt;h3 id="schnittstelle-der-hardware-abstraktionsschicht"&gt;Schnittstelle der Hardware-Abstraktionsschicht&lt;/h3&gt;&#10;&lt;p&gt;Schauen wir uns das Interface und seine Struktur genauer an.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// hal_if.h&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; hardwareAbstraction {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Hardware Abstraction Layer Interface class&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Hal_IF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; &lt;span style="color:#f92672"&gt;~&lt;/span&gt;Hal_IF() &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;digitalRead&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; pinId) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; pinId, &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; value) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#75715e"&gt;// hardwareAbstraction&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die Klasse &lt;code&gt;Hal_IF&lt;/code&gt; hat zwei Methoden, die beide &lt;code&gt;virtuell&lt;/code&gt; sind. Das bedeutet, dass eine Client-Klasse, die diese Schnittstelle verwendet, die Methode aufrufen kann und somit &amp;ldquo;automatisch&amp;rdquo; an die konkrete Implementierung der Methode weitergeleitet wird. Das &lt;code&gt;= 0;&lt;/code&gt; macht diese Methoden &lt;em&gt;rein virtuell&lt;/em&gt;, so dass das Überschreiben dieser Methoden durch eine abgeleitete Klasse nicht optional sondern vorgeschrieben ist. Das ist genau das, was wir hier wollen, denn wenn diese Methode nicht überschrieben würde, hätte sie kein Verhalten.&#10;Zu beachten ist, dass der Destruktor einer Schnittstellenklasse &lt;a href="https://stackoverflow.com/questions/3628529/should-c-interfaces-have-a-virtual-destructor" target="_blank" rel="noopener noreferrer" class="external-link"&gt;fast immer virtuell sein sollte&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="loopinout"&gt;LoopInOut&lt;/h3&gt;&#10;&lt;p&gt;Die zu testende Logik. Sie greift nicht direkt auf die Ein-/Ausgabe-Hardware des µC zu, sondern auf die Variable &lt;code&gt;m_hal&lt;/code&gt;, die eine Implementierung der Schnittstelle &lt;code&gt;Hal_IF&lt;/code&gt; ist. Sie wird bei der Objekterzeugung per Referenz (gekennzeichnet durch das &lt;code&gt;&amp;amp;&lt;/code&gt; bei der Typdeklaration) an die Klasse übergeben&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// loopInToOut.h&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;LoopInToOut&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LoopInToOut(hardwareAbstraction&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Hal_IF&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; hal)&lt;span style="color:#f92672"&gt;:&lt;/span&gt; m_hal(hal){};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;~&lt;/span&gt;LoopInToOut() &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;loopThrough&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; inPin, &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; outPin);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;private&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hardwareAbstraction&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Hal_IF&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; m_hal;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dies ermöglicht die eigentliche Magie von gMock: Da der Client-Code mit einer Schnittstelle arbeitet, kann ich die &amp;ldquo;echte&amp;rdquo; Implementierung (des Hardware-Zugriffs) zur µC-Laufzeit übergeben, aber für den Test kann ich eine &amp;ldquo;Modell&amp;rdquo;-Implementierung übergeben, über die ich zum Zeitpunkt der Ausführung des Tests die Kontrolle habe.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// loopInToOut.cpp&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; LoopInToOut&lt;span style="color:#f92672"&gt;::&lt;/span&gt;loopThrough(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; inPin, &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; outPin)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; input &lt;span style="color:#f92672"&gt;=&lt;/span&gt; m_hal.digitalRead(inPin);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_hal.digitalWrite(outPin, input);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; input;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;loopThrough()&lt;/code&gt; ist wiederum sehr einfach. Sie liest den Zustand des Eingangspins und ordnet ihn dem Ausgangspin zu. Sie gibt den Zustand des Eingangspins an den Aufrufer zurück.&lt;/p&gt;&#10;&lt;h3 id="modellierte-hardwareabstraktion-mock-hal"&gt;Modellierte Hardwareabstraktion (mock hal)&lt;/h3&gt;&#10;&lt;p&gt;GoogleTest verwendet Makros, um Verhalten zu definieren. Die Mock-Klasse leitet sich ebenfalls von der Schnittstelle ab und stellt lediglich die Deklarationen der Schnittstellenmethoden bereit, die sie überschreibt. Es ist noch kein Verhalten festgelegt.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// hal_mock.h&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Hal_mock&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; hardwareAbstraction&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Hal_IF&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MOCK_METHOD(&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;, digitalRead, (&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; pinId), (&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt;));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MOCK_METHOD(&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt;, digitalWrite, (&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; pinId, &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; value), (&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt;));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="die-testvorrichtung-test-fixture"&gt;Die Testvorrichtung (test fixture)&lt;/h4&gt;&#10;&lt;p&gt;Die Testvorrichtung enthält gemeinsame Daten und Verhaltensweisen für alle Testfälle innerhalb einer Testsuite. Name der Testsuite = Klassenname. Genau dieser Name muss in den Tests, die zu dieser Suite gehören sollen, wiederverwendet werden.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// unittest_loopInToOut.cpp&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;loopInToOut_Test&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#f92672"&gt;::&lt;/span&gt;testing&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Test&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; SetUp()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_loopInToOut &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; LoopInToOut(hal_mock);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TearDown&lt;/span&gt;()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;delete&lt;/span&gt; m_loopInToOut;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NiceMock&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Hal_mock&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; hal_mock{};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LoopInToOut&lt;span style="color:#f92672"&gt;*&lt;/span&gt; m_loopInToOut{&lt;span style="color:#66d9ef"&gt;nullptr&lt;/span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Jeder Test verfügt also über einige vorbereitete Daten, die er verwenden kann: Die zu testende Klasse wird als Zeiger mit einer modellierten (Mock) Implementierung ihrer Abhängigkeit eingerichtet.&lt;/p&gt;&#10;&lt;h3 id="writing-tests"&gt;Der eigentliche Test&lt;/h3&gt;&#10;&lt;p&gt;Der Test wird durch das Makro &lt;code&gt;TEST_F&lt;/code&gt; definiert, was soviel bedeutet wie &amp;ldquo;Test mit Fixture&amp;rdquo;, das eine Testsuite und Testnamen erwartet.&#10;Das Makro &lt;code&gt;ON_CALL&lt;/code&gt; ist ein Befehl an gMock, der der modellierten Klasse vorgibt, wie sie sich zu verhalten hat, wenn &lt;code&gt;digitalRead(_)&lt;/code&gt; mit einer beliebigen Eingabe (Unterstrich = beliebig) aufgerufen wird: die Methode soll standardmäßig &lt;code&gt;true&lt;/code&gt; zurückgeben.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// unittest_loopInToOut.cpp&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TEST_F(loopInToOut_Test, loop_inputHigh_writesCorrectOutputHigh)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ON_CALL(hal_mock, digitalRead(_)).WillByDefault(Return(true));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EXPECT_CALL(hal_mock, digitalWrite(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, true));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m_loopInToOut&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;loopThrough(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dann wird eine &amp;ldquo;Erwartung&amp;rdquo; an die nachgebildete Klasse formuliert, die besagt, dass &lt;code&gt;digitalWrite&lt;/code&gt; mit Eingabewerten von &lt;code&gt;(2, true)&lt;/code&gt; aufgerufen wird, die ebenfalls auf Korrektheit geprüft werden.&lt;/p&gt;&#10;&lt;p&gt;Die letzte Anweisung ruft die echte Implementierung auf, so dass die Erwartungen überprüft werden können.&lt;/p&gt;&#10;&lt;h3 id="implementierung-des-hardwareverhaltens"&gt;Implementierung des Hardwareverhaltens&lt;/h3&gt;&#10;&lt;p&gt;Da &lt;code&gt;main.cpp&lt;/code&gt; auf die Hardware zugreift, muss es dafür eine Art Schnittstellenimplementierung geben.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// hal.h&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Hal&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Hal_IF&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hal() &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;~&lt;/span&gt;Hal() &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;digitalRead&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; pinId) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;digitalWrite&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; pinId, &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; value) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hier leitet &lt;code&gt;Hal&lt;/code&gt; sich von &lt;code&gt;Hal_IF&lt;/code&gt; ab und überschreibt alle seine Methoden.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// hal.cpp&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;Arduino.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#include&lt;/span&gt; &lt;span style="color:#75715e"&gt;&amp;#34;hal.h&amp;#34;&lt;/span&gt;&lt;span style="color:#75715e"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; hardwareAbstraction{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; Hal&lt;span style="color:#f92672"&gt;::&lt;/span&gt;digitalRead(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; pinId) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;digitalRead&lt;/span&gt;(pinId);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Hal&lt;span style="color:#f92672"&gt;::&lt;/span&gt;digitalWrite(&lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; pinId, &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; value) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; digitalWrite(pinId, value);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Man erkennt, dass nur diese Quelldatei tatsächlich die &lt;code&gt;Arduino.h&lt;/code&gt; für den Zugriff auf die Hardware einbindet. Die Abhängigkeit von den Hardware-Funktionen ist also sehr begrenzt und in Schnittstellen-Implementierungen gekapselt, die durch die Programmstruktur ausgewählt werden können. Während die &lt;code&gt;main.cpp&lt;/code&gt; für das Zielgerät also ein Objekt der obigen Implementierung erzeugt, wird der Test stattdessen ein Mock übergeben.&lt;/p&gt;&#10;&lt;h2 id="gtest-schlüsselkonzepte"&gt;Gtest Schlüsselkonzepte&lt;/h2&gt;&#10;&lt;p&gt;Googles offizielle &lt;a href="http://google.github.io/googletest/primer.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Webseite über Googletest&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; kann sehr helfen, nicht nur für den Einstieg&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;. Das gmock Cookbook ist ein hervorragender Leitfaden, den ich im Laufe der Monate benutzt habe, als meine Tests immer komplexer und schwieriger zu schreiben wurden, und das gmock Cheat Sheet hilft, wenn man den Überblick verloren hat, wie ein Test für dieses oder jenes spezielle Problem zu schreiben ist.&lt;/p&gt;&#10;&lt;h3 id="dependency-injection"&gt;Dependency Injection&lt;/h3&gt;&#10;&lt;p&gt;Dependency Injection ist die grundlegende Mechanik, verstanden werden muss, damit man gMock erfolgreich anwenden kann. Ich versuche es mal mit einem Bild zu verdeutlichen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/platformio_gtestgmock/dependencyinjection.jpg" alt="Dependency Injection using interfaces" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;em&gt;Kurzgesagt ist die Schnittstelle lediglich ein Platzhalter für die Implementierung der Abhängigkeit der Client-Klassen.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;Zur Laufzeit, z.B. wenn der Host das Client-Objekt erzeugt, wird er die Implementierung der Abhängigkeit an den Client übergeben (d.h. injizieren), der dann jede seiner Methoden aufrufen kann, da ihre Signaturen zur Kompilierzeit aufgrund der vom Client verwendeten Schnittstelle bekannt sind.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Das Mock ist nun lediglich eine Implementierung der Schnittstelle, die für die Tests spezialisiert ist.&lt;/li&gt;&#10;&lt;li&gt;Die eigentliche Implementierung wird in allen anderen Kontexten verwendet, sie zeigt das &amp;ldquo;echte&amp;rdquo; Verhalten dieser Klasse.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="hardware-abstraktionsschicht"&gt;Hardware-Abstraktionsschicht&lt;/h3&gt;&#10;&lt;p&gt;Dies ist vielleicht einer der kniffligen Punkte bei der Arbeit mit eingebetteter Software.&#10;Irgendwann geht es um die Interaktion mit der Außenwelt, sei es mit GPIO-Pins (General Purpose Input Output), den Zugriff auf eines der unzähligen Hardware-Bussysteme wie I2C, UART, SPI, CAN zur Steuerung externer Hardware oder einfach die Nutzung von Systeminterna wie dem EEPROM - da fangen die Probleme an.&lt;/p&gt;&#10;&lt;p&gt;Gtest läuft auf dem GCC/G++ Compiler, der einfach nichts von dem weiß, was der spezialisierte Compiler des jeweiligen Chips weiß. Er würde nicht einmal die Header-Dateien verstehen. Nun gibt es zwei Möglichkeiten, dieses Problem zu umgehen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&amp;ldquo;Fake Header&amp;rdquo; für jede µC-spezifische Implementierung schreiben, die von GCC/G++ verwendet werden können&lt;/li&gt;&#10;&lt;li&gt;Eine Schnittstelle für jede µC-spezifische Implementierung unter Verwendung einer (Hardware-)Abstraktionsschicht programmieren, so dass Ihre Produktionsumgebung die Chip-Hardware wie gewohnt verwendet, die Testumgebung aber stattdessen Mocks benutzt.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Der zweite Ansatz hat eine Reihe von Vorteilen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Er ist sauber. Er zwingt einen dazu, keine Abstraktionsschichten zu zusammenzufassen die getrennt gehören.&lt;/li&gt;&#10;&lt;li&gt;Er ist einfach. Man kann die Geschäftslogik testen, ohne den µC zu programmieren! Es müssen nur die Eingänge gemockt und die Ausgänge abgefangen werden.&lt;/li&gt;&#10;&lt;li&gt;Er ist portabel. Wenn man die Implementierung Abstraktionsschicht austauscht, kann man sie auf einer anderen Controller-Familie oder sogar auf Chips eines anderen Herstellers auszuführen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="nachteile"&gt;Nachteile&lt;/h3&gt;&#10;&lt;p&gt;Hmm, wo Licht ist, muss es doch irgendwo auch Schatten geben. Meiner Meinung nach sind die Nachteile, wenn man alles ohne angeschlossene Hardware testen will:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/platformio_gtestgmock/memoryfull.jpg" alt="nicht viel Speicher übrig..." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Speicher. Sowohl ROM als auch RAM werden schneller voll, wenn eine große Anzahl von Klassen und vtables existieren.&lt;/li&gt;&#10;&lt;li&gt;Besitz. Die Injektion von Abhängigkeiten trennt den Besitz von der Nutzung eines Objekts, und man muss sich entscheiden, wie beides organisiert werden soll.&lt;/li&gt;&#10;&lt;li&gt;Bibliotheken von Drittanbietern können nicht einfach verwendet werden sondern benötigen Anpassung. Ein Interface-Header, eine &lt;a href="https://refactoring.guru/design-patterns/adapter/cpp/example" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Adapter-Implementierung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und ein Mock für jede Bibliothek, die &amp;ldquo;chip-internals&amp;rdquo; verwendet, sind zu erstellen.&lt;/li&gt;&#10;&lt;li&gt;Komplexität. Da Objekte über eine Schnittstelle injiziert werden, muss jetzt Call-by-Reference oder Übergabe per Zeiger durchgeführt werden 😨. Ersteres ist zusätzlich eingeschränkt, da es nur für Konstruktorinjektion funktioniert, nicht für andere Arten (Methodeninjektion, Setterinjektion usw.).&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="einige-weitere-tipps"&gt;Einige weitere Tipps&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Alles &amp;ldquo;hinter&amp;rdquo; der Abstraktionsschicht sollte so einfach wie möglich gehalten werden: Diese Dinge können nur auf dem Zielgerät getestet und ausgeführt werden. Beispiel: Jede Methode führt nur einen Befehl aus (wie in &lt;a href="https://github.com/Schallbert/PlatformIO_gTestgMock/blob/main/lib/hal/hal.cpp" target="_blank" rel="noopener noreferrer" class="external-link"&gt;meinem Beispielcode&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;).&lt;/li&gt;&#10;&lt;li&gt;Verwendung einer Loader-Klasse oder eines &lt;a href="https://www.fluentcpp.com/2019/06/07/write-your-own-dependency-injection-container" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dependency Injection Container (Nicolas Croad)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; erleichtert die Arbeit, denn er enthält alle zu injizierenden Objekte und kann sie auf Anfrage übergeben.&lt;/li&gt;&#10;&lt;li&gt;Die &lt;a href="https://blog.schallbert.de/projects/platformio_gtestgmock/#nachteile"&gt;oben genannten Nachteile&lt;/a&gt; sollten im Blick behalten werden. Bei kleineren Projekten lohnt sich dieser Aufwand wahrscheinlich nicht.&lt;/li&gt;&#10;&lt;li&gt;Manchmal macht es sogar Sinn, mehrere Ebenen Schnittstellen zu haben. Dieses Muster kann nicht nur für die direkte Interaktion mit der Hardware zu verwendet werden, sondern auch zwischen Softwaremodulen, die dann in einer höheren Granularität getestet werden können. Dies kann sogar schneller sein als ein großen Haufen Klassen zu haben, die nur über Mocks in der Hardware-Abstraktionsschicht kontrolliert werden können.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="schwieriges-thema"&gt;Schwieriges Thema!&lt;/h3&gt;&#10;&lt;p&gt;Sie haben sich jetzt eine Pause verdient. Das waren viele Informationen, die Sie verarbeiten mussten. Nehmen Sie sich Zeit. Kontaktieren Sie mich gern auf der &lt;a href="https://github.com/Schallbert/PlatformIO_gTestgMock/discussions/1" target="_blank" rel="noopener noreferrer" class="external-link"&gt;discussions&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Seite zu diesem Thema, wenn Sie das Gefühl haben, dass etwas fehlt, nicht korrekt dargestellt ist, verbessert werden sollte oder einfach genau das ist, was Sie jetzt brauchten.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Die Installation unter Linux ist einfacher, da &lt;code&gt;gcc&lt;/code&gt; und &lt;code&gt;g++&lt;/code&gt; bereits vorinstalliert sind. Selbst wenn sie noch nicht die richtigen Pakete zur Unterstützung von GoogleTest enthalten, sind ihr Fehlermeldungen viel aussagekräftiger als die unter Windows, z.B. wenn Bibliotheken fehlen. 🙃&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;Dies wird auch &lt;a href="https://github.com/ninject/Ninject/wiki/Injection-Patterns" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Constructor Injection&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; genannt, eine Unterart der &lt;a href="https://github.com/ninject/ninject/wiki/Dependency-Injection-By-Hand" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dependency Injection&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, die ein Mittel zur Erreichung der &lt;a href="https://en.wikipedia.org/wiki/Inversion_of_control" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Inversion Of Control&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ist. Ich habe einige Links zu weiteren Informationen vorbereitet. Dies ist ein umfangreiches Thema, über das ganze Bücher geschrieben worden sind.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:3"&gt;&#10;&lt;p&gt;Übrigens: die googletest-Seite ist auch statisch wie meine und verwendet ebenfalls Jekyll 👋 &amp;hellip;gefällt sie Ihnen? Ich würde gern Ihre Meinung dazu &lt;a href="https://github.com/Schallbert/schallbert.github.io/discussions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;auf meiner Diskussionsseite&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; erfahren.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>MobFobAmp</title><link>https://blog.schallbert.de/projects/mobfobamp/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/mobfobamp/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/unfolded-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: MobFobAmp Unfolded"&#10; title="MobFobAmp" /&gt;&#10;&lt;p&gt;Ein zweiter Prototyp meines mobilen Instrumentenverstärkers, dieses mal mit klappbarem Design. &lt;code&gt;28l&lt;/code&gt; Box mit &lt;code&gt;17cm&lt;/code&gt; Tiefmitteltöner. &lt;code&gt;15W&lt;/code&gt; Verstärkerleistung. &lt;code&gt;7.3kg&lt;/code&gt; inkl. Akku. Geeignet für E-Bass, Akustikgitarre, E-Piano.&lt;/p&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: herausfordernd 4/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: ~140€ ohne Akku&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~15h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="vorstellung"&gt;Vorstellung&lt;/h2&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Image: amp folded, flat. Enclosure is less than 10cm strong, the speaker back protrudes by 8cm."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/folded_lying.jpg" alt="Image: amp folded, flat. Enclosure is less than 10cm strong, the speaker back protrudes by 8cm." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Image: amp folded, standing by itself."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/folded_standing.jpg" alt="Image: amp folded, standing by itself." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Image: amp unfolded, side view, the speaker chassis protrudes from the speaker face."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/unfolded_side.jpg" alt="Image: amp unfolded, side view, the speaker chassis protrudes from the speaker face." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-3" aria-label="Image: amp unfolded, top view. An 18mm thick beech panel keeps the enclosure shut and is sturdy enough to be sat upon."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/unfolded_top.jpg" alt="Image: amp unfolded, top view. An 18mm thick beech panel keeps the enclosure shut and is sturdy enough to be sat upon." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-4" aria-label="Image: amp unfolded, front view. Battery and amplifier board are visible at the combo&amp;#39;s front."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/unfolded.jpg" alt="Image: amp unfolded, front view. Battery and amplifier board are visible at the combo&amp;#39;s front." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-5" aria-label="Image: view inside the amp: Nothing but air and a strengthening panel."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/inside_spacer.jpg" alt="Image: view inside the amp: Nothing but air and a strengthening panel." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Ein paar Bilder von meinem mobilen, klappbaren Bassverstärker 'MobFobAmp'.&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/folded_lying.jpg" alt="Image: amp folded, flat. Enclosure is less than 10cm strong, the speaker back protrudes by 8cm." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Benötigt zusammengeklappt extrem wenig Platz&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/folded_standing.jpg" alt="Image: amp folded, standing by itself." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Flaches Design, leicht zu tragen&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/unfolded_side.jpg" alt="Image: amp unfolded, side view, the speaker chassis protrudes from the speaker face." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;28l Gehäusevolumen wenn ausgeklappt, geschlossenes Design&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-3" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/unfolded_top.jpg" alt="Image: amp unfolded, top view. An 18mm thick beech panel keeps the enclosure shut and is sturdy enough to be sat upon." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Durch eine Höhe von 53cm auch als Hocker verwendbar&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-4" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/unfolded.jpg" alt="Image: amp unfolded, front view. Battery and amplifier board are visible at the combo&amp;#39;s front." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;17cm Bass-Mittenlautsprecher, 4Ah-Akkupack für lange Laufzeit&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-5" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/inside_spacer.jpg" alt="Image: view inside the amp: Nothing but air and a strengthening panel." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Ein Blick nach Innen&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;audio controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/audio/MobFobAmp_sound_sample.mp3" type="audio/mp3"&gt;&#10; Your browser does not support the audio element.&#10; &lt;/audio&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;MobFobAmp Soundbeispiel&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;audio controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/audio/MobFobAmp_sound_ABA_closedOpen.mp3" type="audio/mp3"&gt;&#10; Your browser does not support the audio element.&#10; &lt;/audio&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;A/B/A Vergleich geschlossenes vs. offenes Gehäuse&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;audio controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/audio/MobFobAmp_piano_balade.mp3" type="audio/mp3"&gt;&#10; Your browser does not support the audio element.&#10; &lt;/audio&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;MobFobAmp und Piano, externer Vorverstärker, maximale Lautstärke&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Alle Aufnahmen habe ich mit meinem Smartphone gemacht. Dennoch bringen sie den Klang des Verstärkers realistisch rüber.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;h2 id="mobfobamp-motivation"&gt;&amp;ldquo;MobFoBamp&amp;rdquo;: Motivation&lt;/h2&gt;&#10;&lt;p&gt;Um die Nachteile meines &lt;a href="https://blog.schallbert.de/projects/carsubamp/"&gt;vorherigen Prototypen&lt;/a&gt; zu beheben, wollte ich es mal mit einem größeren Lautsprecher und mehr Volumen probieren. Leider macht einem so eine große Box ja das Tragen schwer, also dachte ich mir, wenn man quasi die Luft raus ließe für den Transport, wäre das nicht eine Lösung&amp;hellip;?&lt;/p&gt;&#10;&lt;h2 id="die-idee-klappbox"&gt;Die Idee: Klappbox&lt;/h2&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://thumb.wikimedia.org/wikipedia/commons/thumb/0/0c/Faltbehaelter01.jpg/330px-Faltbehaelter01.jpg" alt="Image: folding box" /&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Eine Klappbox&lt;/span&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:Faltbehaelter01.jpg" class="attr-link" aria-label="Attribution 1"&gt;&#10; &lt;sup class="attr-id"&gt;[1]&lt;/sup&gt;&#10; &lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;In Anlehnung an das Konzept einer Faltbox, wie es hier gezeigt wird, beschloss ich, dass mein Combo zum einfachen Transport zusammenklappbar sein sollte. Ich entschied mich zunächst für eine geschlossene Konstruktion, damit ich mich nicht um die Berechnung von Lüftungsöffnungen, Resonanzfrequenzen und Gruppenlaufzeiten kümmern musste.&lt;/p&gt;&#10;&lt;p&gt;Ich musste mir Gedanken darüber machen, wie ich Lautsprecher und Elektronik integrieren konnte, ohne die Möglichkeit zu verlieren, das Gehäuse vollständig zusammenzulegen.&lt;/p&gt;&#10;&lt;h3 id="abmessungen"&gt;Abmessungen&lt;/h3&gt;&#10;&lt;p&gt;Ich habe die Breite des Gehäuses so gewählt, dass es bei Bedarf bequem in meine Bass-Gigbag passt. Alle anderen Abmessungen ergaben sich von selbst - die Höhe musste mindestens doppelt so groß sein wie die Breite, damit der obere und untere Deckel im zusammengeklappten Zustand untergebracht werden konnte (aber nicht zu hoch, um die Steifigkeit nicht zu reduzieren). Die &amp;ldquo;Tiefe&amp;rdquo; musste kleiner sein als &amp;ldquo;0,5*Breite&amp;rdquo; - (Materialstärke + Scharnierdicke), sonst würde das Falten nicht funktionieren, weil die Seiten im Weg wären.&lt;/p&gt;&#10;&lt;h3 id="sound-hardware"&gt;Sound-Hardware&lt;/h3&gt;&#10;&lt;p&gt;Für den Leistungsverstärker habe ich mich für das Modul entschieden, das ich schon früher gekauft habe.&#10;Ich wählte einen &lt;a href="https://www.visaton.de/en/products/drivers/woofers/w-170-s-4-ohm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;6,5-Zoll-Hi-Fi-Tieftöner (50W, 4Ohm)&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; mit niedriger Resonanzfrequenz, passend zu meinem versiegelten Gehäuse.&lt;/p&gt;&#10;&lt;h3 id="simulation"&gt;Simulation&lt;/h3&gt;&#10;&lt;p&gt;Dann führte ich einige &lt;a href="https://lautsprechershop.de/tools/t_box_closed.htm" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Simulationen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; durch, um herauszufinden, ob Gehäuse und Lautsprecher gut zusammenpassen würden.&lt;/p&gt;&#10;&lt;p&gt;Der geschätzte Qtc-Wert läge bei 0,63 anstelle des empfohlenen Wertes von 0,71 (optimale Sprungantwort), so dass das Gehäuse etwas überdämpft wäre und &amp;ldquo;straffer&amp;rdquo; klingen würde, aber im Vergleich zu einer optimalen Einstellung an Volumen am unteren Ende verlieren würde. Der Bau einer Box ist leider immer ein Kompromiss zwischen Klang, Leistung, Größe, Gewicht, Frequenzgang und Lautstärke, wobei ich Prioritäten setzen und bestimmte Attribute etwas vernachlässigen musste.&lt;/p&gt;&#10;&lt;h2 id="ausprobieren"&gt;Ausprobieren&lt;/h2&gt;&#10;&lt;p&gt;Als ich nach etwa 15 Stunden mit dem Bau des Gehäuses fertig war, war ich sehr erfreut zu sehen, dass es luftdicht schließt und solide und stabil steht. Für das Ausklappen brauchte ich weniger als 60 Sekunden, für das Zurückklappen in den ursprünglichen Zustand sogar weniger als 30 Sekunden. Da der Maximalpegel immer noch etwas niedrig war, fügte ich dem Setup ein Kompressorpedal hinzu, das einen Gain-Regler hatte, den ich als Vorverstärker verwenden konnte.&lt;/p&gt;&#10;&lt;p&gt;Der Klang dieses Geräts ist erstaunlich. Straffe Bässe, warme Mitten und subtile, aber brillante Höhen passen sehr gut zum charakteristischen Klang der Bassgitarre. Die maximale Lautstärke ist begrenzt, aber sie reicht immer aus, um mit einer Akustikgitarre mithalten zu können. Ich benutze ihn ständig als Übungsverstärker.&lt;/p&gt;&#10;&lt;h2 id="klangqualität"&gt;Klangqualität&lt;/h2&gt;&#10;&lt;p&gt;Der wichtigste Aspekt eines Combos ist sicherlich, wie er klingt. Durch das faltbare Design kann er in zwei verschiedenen Setups betrieben werden.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Geschlossen: Straffer und ausgewogener Klang, guter Tiefbass-Punch. Ohne Vorverstärker ist der maximale Pegel OK, um mit einer Akustikgitarre zu spielen&lt;/li&gt;&#10;&lt;li&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/bottom_vented.jpg" alt="Bild: Belüftetes Setup, Deckel geöffnet" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;Belüftet: Unterer Deckel um &lt;code&gt;3cm&lt;/code&gt; geöffnet, wobei der Riegel das Gehäuse gegen die Bodenplatte drückt. Viel lauterer Klang mit ausgeprägten tiefen Mitten, weniger ausgewogen und etwas dröhnend, Maximalpegel auf dem Niveau von Piano gespielt &lt;em&gt;f&lt;/em&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Ich bin sehr zufrieden mit der Klangqualität, die MobFobAmp bietet. Die Option, &amp;ldquo;offen&amp;rdquo; zu spielen, erhöht die Flexibilität und lässt den Sound wirklich hervorstechen (mit den oben erwähnten Nachteilen).&lt;/p&gt;&#10;&lt;p&gt;Ich habe auch einige Messungen durchgeführt, um zu sehen, wie nahe das Design an einen idealen Frequenzgang herankommt, indem ich etwas &lt;a href="https://en.wikipedia.org/wiki/White_noise" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Weißes Rauschen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; an MobFobAmp schickte und dann den Tonausgang mit einem Mikrofon aufnahm. Zum Vergleich habe ich eine Spektrumanalyse im Frequenzbereich angezeigt. Eine vollkommen neutrale Tonausgabe würde die orangefarbene Linie bei 0 dB für das gesamte Spektrum treffen. &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/frequency_response.jpg" alt="Frequenzgang des weißen Rauschens"&gt;{: .align-center} Man kann sehen, dass die &lt;code&gt;-3dB&lt;/code&gt;-Grenze oder der nutzbare Frequenzbereich von &lt;code&gt;30Hz&lt;/code&gt; bis &lt;code&gt;~1.5kHz&lt;/code&gt; reicht.&lt;/p&gt;&#10;&lt;h2 id="add-ons"&gt;Add-Ons&lt;/h2&gt;&#10;&lt;h3 id="9v-spannungsversorgung-für-pedalboard"&gt;9V Spannungsversorgung für Pedalboard&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/mobfobamp/7809.jpg" alt="Bild: 9V Linearregler TO-220" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich benutze einige Effektpedale beim Üben, und ich wollte sie auch benutzen können, wenn ich MobFobAmp mitnehme. Deshalb habe ich beschlossen, eine &amp;ldquo;+9V&amp;rdquo;-Versorgungsschiene mit Hohlstecker (Außen = +) an Bord zu haben.&#10;Also kaufte ich einen seit Jahrzehnten auf dem Markt befindlichen linearen Spannungsregler aus der 78xx-Serie namens 7809 in einem TO-220-Design, der bis zu 1,5A Strom bei 9V liefern kann und einen Eingangsspannungsbereich von 11-35V akzeptiert. Das Bauteil kann mit wenigen zusätzlichen Teilen verwendet werden, wie in der Abbildung rechts gezeigt. Auf diese Weise kann ich jetzt ein Pedalboard mit der Batterie des MobFobAmp betreiben!&lt;/p&gt;&#10;&lt;h3 id="piezo-hochtöner"&gt;Piezo-Hochtöner&lt;/h3&gt;&#10;&lt;p&gt;Als ich zum ersten Mal einen Drumcomputer mit mobfobamp benutzt habe, hatte ich das Gefühl, dass die Höhen einfach nicht ausreichen. Während sie für einen E-Bass perfekt waren, klangen die Hi-Hat, die Becken und sogar die Snare des Drumcomputers dumpf und muffig. Also bestellte ich einen einfachen Piezo-Treiber, den ich parallel zum Hauptlautsprecher anschließen konnte - das funktioniert perfekt. Ich fügte einen &amp;ldquo;2W/320Ohm&amp;rdquo;-Widerstand in Reihe mit dem Treiber hinzu, um die Pegel für meine Ohren genau richtig einzustellen. Das Ergebnis ist großartig.&lt;/p&gt;&#10;&lt;h2 id="mobfobamp-vorteile"&gt;MobFobAmp Vorteile:&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Zusammenklappbar auf sehr kompakte Maße&lt;/li&gt;&#10;&lt;li&gt;Geringes Gewicht: &lt;code&gt;7.3kg&lt;/code&gt; inkl. Batterie, &lt;code&gt;6.9kg&lt;/code&gt; ohne&lt;/li&gt;&#10;&lt;li&gt;Läuft &lt;code&gt;&amp;gt;20h&lt;/code&gt; mit einer Akkuladung als Übungsverstärker und moderater Lautstärke&lt;/li&gt;&#10;&lt;li&gt;Läuft &lt;code&gt;&amp;gt;3h&lt;/code&gt; mit einer Batterieladung bei maximaler Lautstärke&lt;/li&gt;&#10;&lt;li&gt;Klingt sehr gut für Bass, E-Piano oder Akustikgitarre&lt;/li&gt;&#10;&lt;li&gt;Tiefe Töne bis hinunter zu &amp;ldquo;B0&amp;rdquo; funktionieren gut und klingen druckvoll&lt;/li&gt;&#10;&lt;li&gt;9V DC Ausgang für Vorverstärker oder Effektgeräte&lt;/li&gt;&#10;&lt;li&gt;Kann im &amp;ldquo;Bass Reflex Mode&amp;rdquo; für mehr Tiefmitten betrieben werden&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="zu-verbessern"&gt;Zu verbessern:&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Ohne Piezo-Hochtöner wirken die Höhen matt und gedämpft&lt;/li&gt;&#10;&lt;li&gt;Geringer Headroom der Endstufe: Wenn der externe Vorverstärker zu stark aufgedreht wird, verzerrt die Endstufe vor allem tiefe Töne, erst mit Flattern, dann mit dem typischen Klang der Transistorsättigung&lt;/li&gt;&#10;&lt;li&gt;Die chaotische Verkabelung des Prototyps ist immer noch anfällig für mechanische Beschädigungen, daher wäre ein zusätzliches Gehäuse für die Elektronik schön&lt;/li&gt;&#10;&lt;li&gt;Natürlich reicht die Lautstärke und Leistung nicht aus, um ein Schlagzeug oder eine Band zu beschallen.&lt;/li&gt;&#10;&lt;li&gt;Ein Schutzgitter für den Lautsprecher wäre schön&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="mobfobamp---diy-replica"&gt;MobFobAmp - DIY Replica&lt;/h2&gt;&#10;&lt;p&gt;Natürlich ist der Nachbau erwünscht! Unten ein Link zur Anleitung.&#10;&lt;a href="https://blog.schallbert.de/mobfobamp-diy/"&gt;Mobfobamp DIY-Anleitung&lt;/a&gt;&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Piano gespielt von &lt;a href="https://www.facebook.com/feliks.weber" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Feliks Weber&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>PC-Upgrade</title><link>https://blog.schallbert.de/projects/casemodding-htpc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/casemodding-htpc/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/htpc/htpc-upgrade-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Components for the PC upgrade"&#10; title="PC-Upgrade" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeit: Amateur 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: ~500€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~20h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="ziel-und-motivation"&gt;Ziel und Motivation&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/htpc/htpc-before-upgrade.avif" alt="Image: My HTPC before the upgrade" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Im Jahr 2006 hatte ich mir einen Spiele-PC gegönnt. Als Gehäuse kaufte ich ein HTPC-Gehäuse. Es ist relativ flach und passt in das Rack einer Stereoanlage. 2015 tauschte ich ein erstes mal das Innenleben, als mir die Hardware zu langsam wurde. Ende 2025 war es wieder so weit: Das Netzteil zeigte sich mit der Leistungsaufnahme der neuen Grafikkarte überfordert, die Festplatten liefen voll und Spiele nicht mehr stabil.&lt;/p&gt;&#10;&lt;h2 id="umbauten"&gt;Umbauten&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/htpc/htpc-after-upgrade.avif" alt="Image: Insides of the refreshed PC" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Kurz: Es muss neue Hardware her. Doch diese erzeugt viel mehr Abwärme als die Vorherige. Zur Optimierung des Luftstroms sind daher einige Umbauten vorzunehmen. Klingt doch nach einem tollen Projekt mit vielen Facetten, die dieses Blog ausmachen: Mechanik, Elektronik, Software, CNC-fräsen.&lt;/p&gt;&#10;&lt;h3 id="neue-komponenten"&gt;Neue Komponenten&lt;/h3&gt;&#10;&lt;p&gt;Einzubauen sind&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;650W Enermax Netzteil ersetzt 400W&lt;/li&gt;&#10;&lt;li&gt;Neues Motherboard MSI B550&lt;/li&gt;&#10;&lt;li&gt;AMD Ryzen5600x ersetzt Intel i5-6500&lt;/li&gt;&#10;&lt;li&gt;32GB DDR4 RAM statt 16GB&lt;/li&gt;&#10;&lt;li&gt;1TB PCIE SSD statt SATA SSDs&lt;/li&gt;&#10;&lt;li&gt;140mm Lüfter in 5.25&amp;quot; Schacht&lt;/li&gt;&#10;&lt;li&gt;2x60mm Lüfter in hintere Auslässe&lt;/li&gt;&#10;&lt;li&gt;96mm Lüfter für CPU, neuer Heatpipe-Kühler&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Die meisten Komponenten besorge ich mir gebraucht. Nur Netzteil, Platte und Lüfter sind Neuware. Doch schnell zeigt sich, dass ich das Gehäuse modifizieren muss: Die Grafikkarte kollidiert mit dem 3.5&amp;quot;-Schacht, wo bis dato Festplatten und Kartenlesegerät untergebracht waren. Der große 140mm-Lüfter passt nur an die Stelle, wo sich der 5.25&amp;quot;-Schacht befindet.&lt;/p&gt;&#10;&lt;p&gt;Also müssen beide Schächte entfernt oder mit dem Trennschleifer bearbeitet werden, um Platz zu schaffen. Außerdem benötige ich eine Montagevorrichtung für den Lüfter vor der 5.25&amp;quot;-Öffnung. Hingucker für das Gehäuse wäre es jetzt noch, die Laufwerkklappe automatisch steuern zu können. Dann würde sie nur bei laufendem Lüfter unter hoher Rechenlast geöffnet und im Normalbetrieb geschlossen bleiben.&lt;/p&gt;&#10;&lt;h3 id="anpassung-des-gehäusedeckels"&gt;Anpassung des Gehäusedeckels&lt;/h3&gt;&#10;&lt;p&gt;In meinem Artikel zum Thema &lt;a href="https://blog.schallbert.de/milling-secc/"&gt;Fräsen von SECC-Stahlblech&lt;/a&gt; zeige ich, wie man den Gehäusedeckel zwecks besserer Belüftung der Grafikkarte mit zusätzlichen Schlitzen ausstattet.&lt;/p&gt;&#10;&lt;h3 id="35-schacht"&gt;3.5&amp;quot;-Schacht&lt;/h3&gt;&#10;&lt;p&gt;{&amp;lt; image src=&amp;quot;/assets/images/htpc/floppy-bay-modified.avif&amp;quot; alt=&amp;ldquo;Image: Cutout in 3.5&amp;rdquo; bay&amp;quot; position=&amp;ldquo;right&amp;rdquo; &amp;gt;}&#10;Die Grafikkarte ist so lang, dass sie &lt;code&gt;1cm&lt;/code&gt; Überlappung mit dem 3.5&amp;quot;-Schacht besitzt. Zum Glück lässt sich der Schacht im Handumdrehen ausbauen. Also nehme ich den Trennschleifer zur Hand und bringe einen Ausschnitt in die Halterung ein. Anschließend bohre ich ein paar zusätzliche Löcher in die Seiten des Schachtes, sodass meine 2.5&amp;quot;-SSDs auf der von der Grafikkarte abgewandten Seite des Schachtes befestigt werden können. Auch der Schachtdeckel erhält ein weiteres Loch zur Befestigung am Gehäuse, da zwei Rastnasen durch den Ausschnitt für die Grafikkarte entfallen.&lt;/p&gt;&#10;&lt;p&gt;Der Kartenleser nutzt die volle Tiefe des Schachtes aus. Ich fürchte also schon, ihn gar nicht wieder montiert zu bekommen. Probehalber öffne ich sein Gehäuse und siehe da: Nur das vorderste Drittel wird tatsächlich von Leiterplatten belegt. Der hintere Teil ist anscheinend nur dafür da, Befestigungsmöglichkeiten zu bieten. Also trenne ich den hinteren Teil des Kartenlesergehäuses kurzerhand ab und baue alles wieder zusammen, sodass er sich nun passend im Schacht anbringen lässt.&lt;/p&gt;&#10;&lt;h3 id="525-schacht"&gt;5.25&amp;quot;-Schacht&lt;/h3&gt;&#10;&lt;p&gt;Der Schacht ist hoch genug, um zwei Laufwerke aufzunehmen. Ich benutze aber kaum noch CDs bzw. DVDs - und wenn, dann tut es auch ein externes Laufwerk. Den leeren Laufwerkkäfig kann ich also ausbauen und stattdessen an seiner Stelle einen Lüfter montieren. Dafür entwerfe ich im CAD eine Adapterplatte, die Befestigungsmöglichkeiten und Ausschnitte für den Lüfter enthält.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/htpc/drive-bay-mod.avif" alt="Image: mounting adapter to attach a case fan to the drive bay" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="lüfterplatzierung"&gt;Lüfterplatzierung&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/htpc/aux-fan.avif" alt="Image: External 140mm fan" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich setze Gehäuselüfter gezielt ein, um zwei voneinander getrennte Luftströmungen im PC-Gehäuse zu erzeugen: Die CPU-Netzeil-Strömung verläuft an der Gehäusefront über einen 140mm-Lüfter an den CPU-Kühler, von wo aus die Warme Luft durch Netzteil und zwei weitere 60mm-Gehäuselüfter hinaus geleitet werden.&lt;/p&gt;&#10;&lt;p&gt;Der Luftstrom zur Grafikkarte wird über einen außerhalb des Gehäuses verbauten 140mm-Lüfter gestützt. Die Grafikkarte selbst fördert die erwärmte Luft direkt über den Gehäusedeckel nach draußen.&#10;Um den externen Lüfter anzuschließen, schneide ich ein Loch in eine Slotblende für PCI-Einbauschächte. Dort hinein passt genau die Buchse eines Lüftersteckers. Mittels Heißkleber sichere ich die Buchse in der Blende und schließe sie sonst den Lüfter auch an einen AUX-FAN Anschluss an.&lt;/p&gt;&#10;&lt;h2 id="klappensteuerung"&gt;Klappensteuerung&lt;/h2&gt;&#10;&lt;p&gt;Nun soll die Frontklappe noch automatisch öffnen, sobald der Lüfter dreht. Ist zwar komplizierter als wenn ich das von Hand mache, aber dieses Gimmick macht das Gehäuse doch erst richtig interessant. Ich entferne also das Klappenschloss und füge der Adapterplatte eine Halterung für ein Modellbauservo hinzu, welches die Klappe öffenn und schließen können soll.&lt;/p&gt;&#10;&lt;h3 id="elektronik"&gt;Elektronik&lt;/h3&gt;&#10;&lt;p&gt;Das Servo benötigt eine Ansteuerung. Außerdem möchte ich die Klappenzustand wie den Lüfter auch temperaturabhängig regeln, sodass ich die an dem Lüfter angelegte Spannung einlesen muss. Hierfür setze ich den Microcontroller &lt;a href="https://www.microchip.com/en-us/product/ATTINY44" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ATTINY44&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ein. Die Schaltung habe ich nur mit den allernötigsten Komponenten bestückt:&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/htpc/servo-control-pcb.avif" alt="Iamge: servo control circuit board" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Der Microcontroller gibt ein pulsweitengesteuertes Signal an den Servo aus. Dessen Spannungsversorgung wird über einen Elko gepuffert. Die Energieversorgung für den Gehäuselüfter leite ich über zwei Steckerleisten weiter. Dort greife ich das Signal mittels &lt;a href="https://de.wikipedia.org/wiki/Spannungsteiler" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Spannungsteiler&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; ab und führe es einem Analogeingang zu. Ansonsten ist nur ein weiterer Widerstand verbaut, der den RESET-Eingang des Microcontrollers auf &lt;code&gt;VCC&lt;/code&gt; zieht und ihn so in Betrieb hält.&lt;/p&gt;&#10;&lt;h3 id="software-anforderungen"&gt;Software-Anforderungen&lt;/h3&gt;&#10;&lt;p&gt;Die Anforderungen an die Software sind einfach und klar:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Wenn der Lüfter steht, soll die Klappe schließen&lt;/li&gt;&#10;&lt;li&gt;Ist der Lüfter in Betrieb, soll die Klappe öffnen&lt;/li&gt;&#10;&lt;li&gt;Damit die Klappe nicht zu oft hin- und her fährt, soll der Klappenzustand mit etwas Verzögerung geändert werden und auch nur dann, wenn der Lüfter in der Zwischenzeit seinen Zustand beibehalten hat.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Die daraus entstandene Software ist auf &lt;a href="https://github.com/Schallbert/ServoKlappe/tree/main" target="_blank" rel="noopener noreferrer" class="external-link"&gt;meinem Github-Account&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; frei verfügbar.&lt;/p&gt;&#10;&lt;h3 id="software-hauptschleife"&gt;Software: Hauptschleife&lt;/h3&gt;&#10;&lt;p&gt;Bei der Ansteuerung des Modellbauservos mache ich es mir einfach: Ich verwende die &lt;a href="https://docs.arduino.cc/libraries/servo/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Servo-Bibliothek&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; von Arduino.&lt;/p&gt;&#10;&lt;p&gt;Das Programm macht nicht viel. Ich initialisiere das Servo &lt;code&gt;servoHatch&lt;/code&gt;, welches später im Programm angesteuert wird, definiere ein paar wenige Variablen und lege fest, auf welchem Pin der Spannungsteiler vom Lüfter liegt. Die Hauptschleife begnügt sich damit, die Spannung am Lüfter einzulesen und basierend darauf zu entscheiden, ob sich die Klappe bewegen sollte.&lt;/p&gt;&#10;&lt;p&gt;Die Auswertung übergebe ich dann der Klappensteuerung. Anschließend wartet das Programm &lt;code&gt;15ms&lt;/code&gt; und läuft dann erneut los.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c++" data-lang="c++"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Servo servoHatch; &lt;span style="color:#75715e"&gt;// create Servo object to control a servo&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt; fanVoltage{&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;}; &lt;span style="color:#75715e"&gt;// variable to read the value from the analog pin&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; open{false};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;setup&lt;/span&gt;() {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pinMode(pinFanVoltage, INPUT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;loop&lt;/span&gt;() {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fanVoltage &lt;span style="color:#f92672"&gt;=&lt;/span&gt; analogRead(pinFanVoltage); &lt;span style="color:#75715e"&gt;// reads the value of the fan voltage (value between 0 and 1023)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; open &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mapHatchState(fanVoltage);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; setHatch(open);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; delay(&lt;span style="color:#ae81ff"&gt;15&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// waits for the servo to get there&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="software-hysterese"&gt;Software: Hysterese&lt;/h3&gt;&#10;&lt;p&gt;Mit der Funktion &lt;code&gt;mapHatchState&lt;/code&gt; lege ich den Sollzustand der Klappe in Abhängigkeit der Spannung am Lüfter fest.&lt;/p&gt;&#10;&lt;p&gt;Liegt die Spannung lange genug unter etwa &lt;code&gt;4.8V&lt;/code&gt;, so schließt die Klappe oder behält ihren geschlossenen Zustand bei. Liegt sie darüber, soll die Klappe geöffnet werden. Der Dauer des Zustandswechsels hängt von der Spannung selbst ab; bei Werten weit ab vom Schwellwert läuft der Aggregator schneller voll und der Klappenzustand wird ebenfalls zügiger verändert.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mapHatchState&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;uint16_t&lt;/span&gt; voltage){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#define maxint 32767 - 1024&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#define minint -32768 + 1024&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int16_t&lt;/span&gt; vAggregate{&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; hatchOpen{false};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Divider VCC 33k T 22k GND, 12V fan will yield 4.8V at divider --&amp;gt; 983 digit&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// So a value of 410 is roughly 5V which allows Fan start + hatch open&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vAggregate &lt;span style="color:#f92672"&gt;=&lt;/span&gt; vAggregate &lt;span style="color:#f92672"&gt;+&lt;/span&gt; voltage &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;410&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (vAggregate &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; maxint) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vAggregate &lt;span style="color:#f92672"&gt;=&lt;/span&gt; maxint;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hatchOpen &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (vAggregate &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; minint) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vAggregate &lt;span style="color:#f92672"&gt;=&lt;/span&gt; minint;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hatchOpen &lt;span style="color:#f92672"&gt;=&lt;/span&gt; false;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; hatchOpen;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="software--klappe-bewegen"&gt;Software : Klappe bewegen&lt;/h3&gt;&#10;&lt;p&gt;Die Funktion &lt;code&gt;setHatch()&lt;/code&gt; prüft den gewünschten Zustand der Klappe und bewegt sie, falls sie vom aktuellen Zustand abweicht, dort hin.&#10;Dabei wird dem Servo mittels &lt;code&gt;write()&lt;/code&gt; ein Zielwinkel vorgegeben, der eingestellt und gehalten werden soll. Die Bewegung geschieht graduell und über die Iteration von &lt;code&gt;hatchState&lt;/code&gt;, da die Klappe sich mir sonst viel zu ruckartig und schnell bewegt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/htpc/htpcfancomp.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Test der Klappensteuerung durch Verstellen der Lüfterspannung&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;setHatch&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; open){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint8_t&lt;/span&gt; hatchState{&lt;span style="color:#ae81ff"&gt;180&lt;/span&gt;}; &lt;span style="color:#75715e"&gt;// variable to capture current position, default closed&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; wasMoving{false};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; isMoving{false};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (open &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; (hatchState &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hatchState&lt;span style="color:#f92672"&gt;--&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; isMoving &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;open &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; (hatchState &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;180&lt;/span&gt;)) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hatchState&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; isMoving &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;wasMoving &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; isMoving) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; servoHatch.&lt;span style="color:#a6e22e"&gt;attach&lt;/span&gt;(portServo); &lt;span style="color:#75715e"&gt;// connect servo on start&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (wasMoving &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;isMoving) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; servoHatch.&lt;span style="color:#a6e22e"&gt;detach&lt;/span&gt;(); &lt;span style="color:#75715e"&gt;// disconnect servo on finish&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; servoHatch.&lt;span style="color:#a6e22e"&gt;write&lt;/span&gt;(hatchState);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wasMoving &lt;span style="color:#f92672"&gt;=&lt;/span&gt; isMoving;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die Funktionen &lt;code&gt;attach()&lt;/code&gt; und &lt;code&gt;detach()&lt;/code&gt; definieren den Signalausgang zum Servo, ordnen also den Pin des Microcontrollers intern dem Servo so zu, wie er tatsächlich verdrahtet ist. Bei meinen Experimenten fand ich heraus, dass das Servo im Stillstand bei &lt;code&gt;detached&lt;/code&gt; nur wenig Energie benötigt - viel weniger, als wenn ich es &lt;code&gt;attached&lt;/code&gt; lasse und nur kein weiteres Bewegungssignal sende. Daher melde ich es für jede Bewegung an und danach wieder ab.&lt;/p&gt;&#10;&lt;h2 id="ergebnis"&gt;Ergebnis&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/htpc/htpc-upgrade-complete.avif" alt="Image: HTPC insides with all upgrades applied" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich erhalte einen schön aufgeräumten, modernen PC. Mittels diverser Bloatware-Lösungen (die ich nach getaner Arbeit direkt wieder deinstalliere) schaffe ich es nach einigem Fluchen, alle LED-Farben aufeinander abzustimmen. Besonderes Highlight ist die automatische Klappe, welche bei höherer CPU-Last samt Lüfter aktiviert wird.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/htpc/htpc-case-complete.avif" alt="Image: HTPC case with all upgrades applied" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>QR-Codengrave</title><link>https://blog.schallbert.de/projects/qr-codengrave/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/qr-codengrave/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/qr-codengrave/app-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: qr-code created with my CNC portal milling machine"&#10; title="QR-Codengrave" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: Schwer 4/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: 0€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~50h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="die-idee"&gt;Die Idee&lt;/h2&gt;&#10;&lt;p&gt;Ein Freund bat mich, einen Bierdeckel für ihn anzufertigen. Ein eigenes Logo, ein paar Buchstaben. Nichts allzu Schweres. Auf der Rückseite wollte er einen &lt;a href="https://de.wikipedia.org/wiki/QR_code" target="_blank" rel="noopener noreferrer" class="external-link"&gt;QR-Code&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; eingravieren lassen, der einen Link zu seiner Website enthält.&lt;/p&gt;&#10;&lt;p&gt;Ich habe also einen QR-Code mit einem &lt;a href="https://www.nayuki.io/page/qr-code-generator-library" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Standard-Webtool&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; erstellt und als &lt;code&gt;.svg&lt;/code&gt; exportiert. Leider müsste ich in meinem CAM jedes einzelne Pixel anklicken und eine Carve-Action für dieses Pixel erstellen, was einige Minuten meiner Zeit für Auswahl-Klick-und-Parameter-Wähl-Prozeduren in Anspruch nehmen würde. Außerdem müsste ich einen Gravurstichel mit tiefenvariabler Gravur verwenden, um ein quadratisches Pixel zu erhalten, was viel mehr Bearbeitungszeit erfordert als eine runde Darstellung.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/qr-codengrave/svg_path_issues.jpg" alt="Bild: QR-Code SVG in meinem CAM" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;An diesem Punkt beschloss ich, viele, viele weitere Stunden meiner Zeit auf dieses Problem zu werfen und eine allgemeine Lösung für QR-Code-Gravuren zu entwickeln - eine Software namens &lt;strong&gt;QR-codengrave&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;h2 id="erste-schritte"&gt;Erste Schritte&lt;/h2&gt;&#10;&lt;h2 id="spezifikation"&gt;Spezifikation&lt;/h2&gt;&#10;&lt;p&gt;Das, was ich mit dem Tool erreichen wollte:&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Muss es können:&lt;/em&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Texteingabe verarbeiten und in einen QR-Code konvertieren. Der Benutzer muss keine zusätzliche Konfiguration vornehmen.&lt;/li&gt;&#10;&lt;li&gt;Bietet die Option zur Verwaltung verschiedener CNC-Werkzeuge.&lt;/li&gt;&#10;&lt;li&gt;Ermöglicht Eingabe von Gravurparametern: Gravurtiefe, Eilgangbewegung im Job, Überflughöhe&lt;/li&gt;&#10;&lt;li&gt;Ermöglicht Eingaben für benutzerdefinierte XY0-Offsets für Werkstücke&lt;/li&gt;&#10;&lt;li&gt;Vektorisiert die Pixelausgabe des QR-Codes&lt;/li&gt;&#10;&lt;li&gt;Kann Pfade aus den Vektoren erstellen lassen, beginnt der Einfachheit halber mit einer Spirale nach innen&lt;/li&gt;&#10;&lt;li&gt;Übergabe der Pixellinien an ein Modul, das die Parameter des ausgewählten Werkzeugs verwendet, um Maschinenbahnen zu erstellen&lt;/li&gt;&#10;&lt;li&gt;Maschinenbahnen in G-Code konvertieren, der später von meiner Maschine interpretiert werden kann.&lt;/li&gt;&#10;&lt;li&gt;Gründliche Überprüfung der Algorithmen mit automatisierten Unit-Tests (bitte keine Abstürze in der realen Welt)&lt;/li&gt;&#10;&lt;li&gt;Bietet eine grafische Benutzeroberfläche, um die Parametereingabe so einfach wie möglich zu machen.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;&lt;em&gt;Optional&lt;/em&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Eine Continuous-Integration-Pipeline sowohl für meine IDE als auch für Github-Aktionen erstellen&lt;/li&gt;&#10;&lt;li&gt;Eine Continuous-Deploy-Pipeline für meine IDE (PyInstaller) und Github-Aktionen (Artefakte, Releases) erstellen&lt;/li&gt;&#10;&lt;li&gt;Integrationstests für die GUI hinzufügen&lt;/li&gt;&#10;&lt;li&gt;Optimieren des Algorithmus zur Pfaderzeugung, da die &amp;ldquo;nach innen gerichtete Spirale&amp;rdquo; eine hohe Fertigungsqualität hat und schön anzusehen ist, aber nicht sehr effizient mit der Zeit umgeht.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="unbekannte"&gt;Unbekannte&lt;/h3&gt;&#10;&lt;p&gt;Zu Beginn dieses Projekts hatte ich einige offene Fragen und Risiken, die die Nutzbarkeit des Programms hätten einschränken oder sogar das gesamte Projekt undurchführbar machen können. Sie haben sich alle als falsch herausgestellt (zum Glück), aber dennoch sind hier die Risiken, die meine ganze Arbeit unbrauchbar hätten machen können:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Kameras lesen keine runden Punkt-als-Pixel-QR-Codes.&lt;/li&gt;&#10;&lt;li&gt;Der Kontrast, den meine Graviermaterialien bieten, ist zu gering, so dass das Lesen der Codes schlecht oder gar nicht funktioniert.&lt;/li&gt;&#10;&lt;li&gt;Die Bearbeitungszeit könnte sehr hoch sein, was das Projekt unwirtschaftlich machen würde.&lt;/li&gt;&#10;&lt;li&gt;Beschränkungen der Programmierwerkzeuge oder meiner Programmierfähigkeiten könnten die Entwicklungszeit so verlängern, dass es sich nicht lohnt, weitere Anstrengungen zu unternehmen.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Daher beschloss ich, &lt;a href="https://blog.schallbert.de/projects/qr-codengrave/#unit-tests"&gt;Unit-Tests&lt;/a&gt; zusammen mit dem Code zu schreiben, um ein schnelles Feedback darüber zu erhalten, ob mein Code das leisten kann, was er soll. Außerdem habe ich die Funktionalität des Programms auf eine Gravurstrategie beschränkt und die GUI so gestaltet, dass sie nur eine Vorschau des QR-Codes anzeigt, nicht aber XY-Offsets oder anderen Schnickschnack.&lt;/p&gt;&#10;&lt;h3 id="programmiersprache"&gt;Programmiersprache&lt;/h3&gt;&#10;&lt;p&gt;Ich nahm einige Blätter Papier und notierte mir das Wesentliche.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/qr-codengrave/sketches.jpg" alt="Bild: Softwareplanung auf Papier" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ein Modul soll sich um die Erstellung eines QR-Codes aus einer Texteingabe kümmern, ein anderes soll Vektoren und Pfade erzeugen, um diesen QR-Code zu gravieren, und eine GUI in der Mitte soll die Texteingabe, eine Bearbeitungsvorschau und Eingabemöglichkeiten für Fräswerkzeuge, Gravurparameter und Werkstückversätze enthalten.&lt;/p&gt;&#10;&lt;p&gt;Ich hatte ein paar Programmiersprachen zur Auswahl, die ich gut genug &amp;ldquo;spreche&amp;rdquo;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;C++ mit QT-Framework&lt;/li&gt;&#10;&lt;li&gt;Java&lt;/li&gt;&#10;&lt;li&gt;Python mit dem Tkinter-Framework&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Ich entschied mich für die - meiner Meinung nach - einfachste Lösung, um so schnell wie möglich einen lauffähigen Prototyp zu haben: Python mit Tkinter. Ich kenne Tkinter von einem meiner &lt;a href="https://github.com/Schallbert/DerWinterNaht" target="_blank" rel="noopener noreferrer" class="external-link"&gt;früheren Projekte&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und obwohl es seine Einschränkungen hat, ist es immer noch leistungsstark genug und schnell zu verwenden.&lt;/p&gt;&#10;&lt;h3 id="konzeptphase"&gt;Konzeptphase&lt;/h3&gt;&#10;&lt;p&gt;Nach etwas Recherche fand ich eine ziemlich gute Open-Source-&lt;a href="https://www.nayuki.io/page/qr-code-generator-library" target="_blank" rel="noopener noreferrer" class="external-link"&gt;QR-Code-Bibliothek im Internet&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, die auch in Python verfügbar ist. Auf der Grundlage ihrer Ausgaben habe ich einen Vektorisierungsalgorithmus erstellt, der die QR-Code-Bibliothek abfragen kann, um die Werte der Pixel des QR-Codes in einer Scanzeile zurückzugeben:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_qr_bitstream_from_line&lt;/span&gt;(self, line):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Creates a bitstream from an input line of a QR-code data representation&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; :param line: a QR-code data representation (line of bits within the QR-code)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; :returns bitstream: returns an array of bits reflecting the QR code&amp;#39;s state at the respective&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; point of the line.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bitstream &lt;span style="color:#f92672"&gt;=&lt;/span&gt; []&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_direction() &lt;span style="color:#f92672"&gt;==&lt;/span&gt; Direction&lt;span style="color:#f92672"&gt;.&lt;/span&gt;RIGHT:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; x &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_start()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x, line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_end()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bitstream&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_qr&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_module(x, abs(line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_start()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;y)))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_direction() &lt;span style="color:#f92672"&gt;==&lt;/span&gt; Direction&lt;span style="color:#f92672"&gt;.&lt;/span&gt;LEFT:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; x &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_start()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x, line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_end()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bitstream&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_qr&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_module(x, abs(line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_start()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;y)))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_direction() &lt;span style="color:#f92672"&gt;==&lt;/span&gt; Direction&lt;span style="color:#f92672"&gt;.&lt;/span&gt;UP:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; y &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_start()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;y, line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_end()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;y &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bitstream&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_qr&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_module(line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_start()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x, abs(y)))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_direction() &lt;span style="color:#f92672"&gt;==&lt;/span&gt; Direction&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DOWN:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; y &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_start()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;y, line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_end()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;y &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bitstream&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_qr&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_module(line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_p_start()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;x, abs(y)))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; bitstream&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dann musste ich aus diesen Linien Vektoren erstellen, so dass aufeinanderfolgende Pixel derselben Farbe zu einer Linie zusammengefügt werden konnten, die viel schneller herzustellen ist als einzelne Punkte:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_vectorize_bitstream&lt;/span&gt;(self, bitstream):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Creates a QrLineData object from an input bitstream&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; :param bitstream: an array of bits = a line of the QR-code&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; :returns line_vector: A list of QrLineData objects&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line_vector &lt;span style="color:#f92672"&gt;=&lt;/span&gt; []&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; QrLineData(bitstream[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;], &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; bit &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, len(bitstream)):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; bitstream[bit] &lt;span style="color:#f92672"&gt;==&lt;/span&gt; data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_state():&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add_length()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line_vector&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(data)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; QrLineData(bitstream[bit])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_state():&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;finalize()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line_vector&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(data)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; line_vector&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Diese beiden Methoden würden nun von einer übergeordneten Methode aufgerufen werden, die einen spiralförmigen Pfad durch den QR-Code kennt.&#10;Meine erste GUI hatte nur drei Elemente: Ein Eingabefeld für den Text, der in einen QR-Code umgewandelt werden sollte, eine Schaltfläche, um den Algorithmus auszuführen, und eine Zeichenfläche, auf der der Gravurpfad des Algorithmus ausgegeben werden sollte.&lt;/p&gt;&#10;&lt;p&gt;Ich das Modul &lt;a href="https://docs.python.org/3/library/turtle.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Turtle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verwendet, um den vektorisierten QR-Code-Pfad zu zeichnen, ein Tool, das für Schülerinnen und Schüler geschrieben wurde, die sich in Programmiersprachen wie Python einarbeiten. Es ist zwar langsam, aber einfach einzurichten, so dass mir das für den Anfang genügte. Ich habe für&amp;rsquo;s Erste hart kodiert, um Zeit zu sparen, und meine Schleifen sind eher C-esk als pythonisch, aber egal.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; vect &lt;span style="color:#f92672"&gt;in&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_spiral_path[i]&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_z_vector():&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_stop_draw:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; length &lt;span style="color:#f92672"&gt;=&lt;/span&gt; vect&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_length()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; vect&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_state():&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;turtle&lt;span style="color:#f92672"&gt;.&lt;/span&gt;down()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;turtle&lt;span style="color:#f92672"&gt;.&lt;/span&gt;forward(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pen_size &lt;span style="color:#f92672"&gt;*&lt;/span&gt; length)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;turtle&lt;span style="color:#f92672"&gt;.&lt;/span&gt;up()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;turtle&lt;span style="color:#f92672"&gt;.&lt;/span&gt;forward(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pen_size &lt;span style="color:#f92672"&gt;*&lt;/span&gt; length)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;turtle&lt;span style="color:#f92672"&gt;.&lt;/span&gt;right(&lt;span style="color:#ae81ff"&gt;90&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dieses sehr einfache Konzept sah für mich vielversprechend aus, aber ich musste ein paar Stunden daran feilen, weil meine anfängliche Ausgabe wie folgt aussah:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/qr-codengrave/drawing_issue_cut.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;QR-codengrave GUI test&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Nach einer Weile konnte ich jedoch zuverlässig einen (21x21 Pixel) QR-Code auf den Bildschirm zeichnen und die Kamera meines Smartphones verwenden, um den Link zur Webseite zu erhalten. 🥳 Yeah!&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/qr-codengrave/working_scan.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;QR-codengrave Vektorerzeugung funktioniert&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="unit-tests"&gt;Unit-Tests&lt;/h3&gt;&#10;&lt;p&gt;Die &amp;ldquo;Plattform&amp;rdquo;-Teile - Klassen &lt;code&gt;vectorize_qr&lt;/code&gt; und &lt;code&gt;machinify_vector&lt;/code&gt; - enthalten beide eine Menge überprüfbarer Berechnungen und Geschäftslogik. Deshalb sind meine Tests mit Pythons Standardmodul &lt;a href="https://docs.python.org/3/library/unittest.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;unittest&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; schnell und einfach zu schreiben. In diesem Beispiel überprüfe ich, ob die von mir erstellte Linienklasse eine Länge (n+1 Stil) korrekt berechnet:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TestLine&lt;/span&gt;(unittest&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TestCase):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_line_x_calculates_valid_length&lt;/span&gt;(self):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Line(Point(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;), Point(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertEqual(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, line&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_abs_length())&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aber es wurde schnell komplexer, als ich testen wollte, ob die Ausgabe der QR-Code-Generatorbibliothek in meinem Programm korrekt verarbeitet wird. Dazu musste ich die Bibliothek in meine zu testende Klasse einbinden, damit ich eine Mock-Implementierung einschleusen konnte, die dann im Test abgefragt wird.&lt;/p&gt;&#10;&lt;p&gt;Dieses Muster der Abhängigkeitsinjektion ist der Vorgehensweise in &lt;code&gt;C++&lt;/code&gt; sehr ähnlich, wo ich viel mehr Erfahrung habe (siehe ein Projekt &lt;a href="https://blog.schallbert.de/projects/platformio_gtestgmock/#dependency-injection"&gt;hier&lt;/a&gt; oder ein anderes &lt;a href="https://github.com/Schallbert/Tonuino_alternativeFirmware" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, zum Beispiel), nur dass in Python viel weniger Boilerplate-Code nötig ist, um dorthin zu gelangen. Ein paar Suchvorgänge im Internet und schon war der folgende Testfall geschrieben:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_bitstream_qr_input_loop_xpos_correct&lt;/span&gt;(self):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mock_qr &lt;span style="color:#f92672"&gt;=&lt;/span&gt; QrCode&lt;span style="color:#f92672"&gt;.&lt;/span&gt;encode_text(&lt;span style="color:#e6db74"&gt;&amp;#34;schallbert.de&amp;#34;&lt;/span&gt;, QrCode&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Ecc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;MEDIUM)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mock_qr&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_module &lt;span style="color:#f92672"&gt;=&lt;/span&gt; MagicMock()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cam &lt;span style="color:#f92672"&gt;=&lt;/span&gt; VectorizeQr(mock_qr, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cam&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_qr_bitstream_from_line(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;test_line_x)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mock_qr&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_module&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assert_called_with(&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ich injiziere die &amp;ldquo;echte&amp;rdquo; &lt;code&gt;QrCode&lt;/code&gt;-Bibliothek, aber mit einer Mock-Methode, die für mich von Interesse ist, &lt;code&gt;get_module()&lt;/code&gt;. In dem Test stelle ich sicher, dass die Methode mit den erwarteten Parametern aus meiner zu testenden Klasse aufgerufen wird.&lt;/p&gt;&#10;&lt;p&gt;Mit solchen Tests kann ich sichergehen, dass die Bausteine meines Algorithmus wie vorgesehen funktionieren und dass Regressionen schnell gefunden werden können.&lt;/p&gt;&#10;&lt;h2 id="der-lange-weg-zum-produkt-mit-minimalanforderungen"&gt;Der lange Weg zum Produkt mit Minimalanforderungen&lt;/h2&gt;&#10;&lt;p&gt;Beflügelt von diesem schnellen Erfolg, ging ich aufs Ganze. Vor der Implementierung des G-Code-Generators musste ich einen Werkzeugselektor entwerfen, da ich die Werkzeugeigenschaften zur Verfügung haben musste, bevor ich die Abmessungen und die Werkzeuggeschwindigkeit / den Vorschub für die QR-Code-Gravur einstellen konnte.&lt;/p&gt;&#10;&lt;p&gt;Nach einigen erfolglosen Versuchen im Hauptfenster entschied ich, dass das Werkzeugkonfigurationsfenster stattdessen ein Dialog sein sollte (den Tkinter &lt;a href="https://www.pythontutorial.net/tkinter/tkinter-toplevel/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Toplevel&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; nennt), so dass die Werkzeugparameter eingegeben und nach Abschluss an die Haupt-GUI zurückgeschickt werden können. Ich habe den Dialog modal gemacht mit&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_dialog&lt;span style="color:#f92672"&gt;.&lt;/span&gt;grab_set()&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So hat er immer den Fokus, damit nicht mehrere Instanzen dieses Fensters gleichzeitig vorhanden sein können.&#10;Ich habe die konfigurierten Werkzeuge in einer nummerierten Liste gespeichert, so dass die Anwendung, sobald sie gefüttert wurde, die Liste zur Verfügung stellt, damit der Benutzer das gewünschte Werkzeug für den Job aus einem Dropdown-Menü auswählen kann.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/qr-codengrave/tool_config.jpg" alt="Bild: QR-codengrave&amp;#39;s tool configuration dialog" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;In diesem Schritt musste ich zwei Dinge implementieren:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Überprüfungen für Wertetyp und -bereich&lt;/li&gt;&#10;&lt;li&gt;Ein Persistenzmodul, so dass die eingegebenen Tools einen Neustart der Anwendung überleben würden.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="überprüfungen-von-wertetyp-und--bereich"&gt;Überprüfungen von Wertetyp und -bereich&lt;/h3&gt;&#10;&lt;p&gt;Dies könnte mit einem Ereignis-Auslöser innerhalb des Eingabefeld-Widgets wie folgt erreicht werden:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; config_tool_frame&lt;span style="color:#f92672"&gt;.&lt;/span&gt;register(validate_number)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_nr_entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config(validate&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;, validatecommand&lt;span style="color:#f92672"&gt;=&lt;/span&gt;(reg, &lt;span style="color:#e6db74"&gt;&amp;#39;%P&amp;#39;&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Anschließend wird der eingegebene Wert an einen einfachen Validator übergeben, der lediglich versucht, den Eingabewert in einen Float-Wert umzuwandeln und False zurückgibt, wenn dies einen Fehler erzeugt,&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;validate_number&lt;/span&gt;(entry):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Helper function that is used in validators to check&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; that the entered keystroke is a number&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; entry &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; float(entry)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ValueError&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Eine Prüfung ob der Wert plausibel ist erfolgt erst, wenn die Schaltfläche &amp;ldquo;OK&amp;rdquo; gedrückt wird, um die eingegebenen Daten an die Anwendung weiterzuleiten. Bei Überschreitung des Bereichs würde ein Warn-Popup (Modul &lt;code&gt;tkinter.messagebox&lt;/code&gt;) erscheinen, das auf das mögliche Problem hinweist.&lt;/p&gt;&#10;&lt;h3 id="persistenz"&gt;Persistenz&lt;/h3&gt;&#10;&lt;p&gt;Das Persistenzmodul war etwas schwieriger zu implementieren.&#10;Ich entschied mich dafür, ein Element zu erstellen, das C++-Kenner eine &lt;em&gt;statische Klasse&lt;/em&gt; nennen würden, d.h. eine Klasse, die nicht instanziiert werden muss, um eine ihrer Methoden aufzurufen. Diese Klasse hat nur zwei Methoden namens &lt;code&gt;load&lt;/code&gt; und &lt;code&gt;save&lt;/code&gt;, die das tun, was ihr Name andeutet, indem sie Pythons &lt;a href="https://docs.python.org/3/library/pickle.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;pickle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Modul zur Serialisierung von Objekten verwenden, die ich speichern wollte. In Python habe ich den Dekorator &lt;code&gt;@classmethod&lt;/code&gt; verwendet, um die statische Natur dieser Klasse zu kennzeichnen.&lt;/p&gt;&#10;&lt;p&gt;So einfach kann die Serialisierung von Daten mit den richtigen Werkzeugen werden:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; open(app_persistence_path, &lt;span style="color:#e6db74"&gt;&amp;#39;wb&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; file:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pickle&lt;span style="color:#f92672"&gt;.&lt;/span&gt;dump([cls&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_tool_list,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cls&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_z_params,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cls&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_xy0],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; file, protocol&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Als dies zuverlässig funktionierte, fügte ich ein Widget zur Konfiguration der Gravurparameter hinzu. Von Nachteil bei statischen Methoden ist, dass sie nur schlecht unit-testbar sind, weil Dependency Injection hier nicht funktioniert; sie sind ja instanzlos. Es bliebe mir nichts anderes üblich als &amp;ldquo;Fakes&amp;rdquo; zu schreiben, die im Testfalle die echte Speicher- bzw. Ladefunktion ersetzt.&lt;/p&gt;&#10;&lt;h3 id="gravur-parameter"&gt;Gravur-Parameter&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/qr-codengrave/engrave_params.jpg" alt="Bild: QR-codengrave&amp;#39;s engrave parameter configuration dialog" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Um den G-Code aus meinen QR-Code-Daten zu generieren, benötigte ich nicht nur Werkzeug- und Geschwindigkeits-/Vorschubdaten, sondern ich musste auch wissen, wie tief ich gravieren wollte, wie weit das Werkzeug bei Eilgängen über der Werkstückoberfläche schweben sollte und welche Sicherheitshöhe die CNC für die Rückkehr zum Ursprung oder zur Ausgangsposition verwenden sollte.&lt;/p&gt;&#10;&lt;p&gt;Es wird auf ähnliche Weise wie das Dialogfeld &amp;ldquo;Werkzeugkonfiguration&amp;rdquo; erstellt und verwendet dieselben Mechanismen zur Überprüfung von Typ und Bereich. Der einzige Unterschied besteht darin, dass er nicht über einen Klick auf eine Schaltfläche, sondern direkt über das zugehörige Textfeld gestartet wird, das den aktuellen Wert der Gravurparameter im Hauptfenster anzeigt.&lt;/p&gt;&#10;&lt;h3 id="werkstück-xy-nullpunktverschiebung"&gt;Werkstück XY Nullpunktverschiebung&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/qr-codengrave/xy0_config.jpg" alt="Bild: QR-codengrave&amp;#39;s Werkstückversatz-Konfigurationsdialog" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Der Konfigurationsdialog für den Werkstückversatz enthält einige Radiobuttons zur Auswahl der gängigsten Werkstückursprünge und eine benutzerdefinierte Option, mit der der Benutzer Werte für den X- und Y-Achsennullpunkt des Werkstücks eingeben kann.&#10;Eine Funktion im Hintergrund berechnet den erforderlichen Versatz relativ zum Startpunkt der Gravierspirale.&lt;/p&gt;&#10;&lt;p&gt;Normalerweise würde ich nicht wollen, dass die grafische Benutzeroberfläche Berechnungen durchführt oder viel Verzweigungslogik verwendet, da dies schwieriger zu verifizieren ist, weil die Test Fixtures damit größer und schwieriger zu warten werden. In diesem Fall habe ich mich jedoch entschieden, die Offset-Berechnung mit Hilfe einer Wertetabelle innerhalb des GUI-Moduls durchzuführen, um zu vermeiden, dass alle GUI-Module zur Plattform und zurück kommunizieren müssen (und das durch 4 Klassen hindurch).&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_get_xy_offset&lt;/span&gt;(self, offset):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;calculates an XY coordinate offset from a preset point, taking the selected tool diameter into account.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; :param offset the selected offset from the above enum class&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; :returns a XY Point where XY0 is assumed for the engraving.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qr &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_qr_dimension&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; d &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_tool_diameter&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; offsets &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {Offset&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CENTER: Point((d &lt;span style="color:#f92672"&gt;-&lt;/span&gt; qr) &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, (qr &lt;span style="color:#f92672"&gt;-&lt;/span&gt; d) &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Offset&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TOPLEFT: Point(d &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, &lt;span style="color:#f92672"&gt;-&lt;/span&gt;d &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Offset&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TOPRIGHT: Point(d &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; qr, &lt;span style="color:#f92672"&gt;-&lt;/span&gt;d &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Offset&lt;span style="color:#f92672"&gt;.&lt;/span&gt;BOTTOMLEFT: Point(d &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, qr &lt;span style="color:#f92672"&gt;-&lt;/span&gt; d &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Offset&lt;span style="color:#f92672"&gt;.&lt;/span&gt;BOTTOMRIGHT: Point(d &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; qr, qr &lt;span style="color:#f92672"&gt;-&lt;/span&gt; d &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; offset &lt;span style="color:#f92672"&gt;in&lt;/span&gt; offsets:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; offsets[offset]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="erstes-release-v10"&gt;Erstes Release V1.0&lt;/h2&gt;&#10;&lt;p&gt;Die erste Version ist minimalistisch. Sie erfüllt ihren Zweck, hat aber noch einige Fehler (z.B. eine Fehlermeldung, wenn kein Werkzeug in der Auswahlliste ausgewählt ist) und Unzulänglichkeiten (z.B. ist das Zeichnen eines QR-Codes sehr langsam, beim erneuten Zeichnen kippt der QR-Code ein wenig usw.), aber sie sollte einfach funktionieren und dem Benutzer helfen, die schlimmsten Fehler auf seinem Weg zu vermeiden.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/qr-codengrave/main_application.jpg" alt="Bild: Hauptbildschirm von QR-codengrave" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich habe die Ausführung auf meiner CNC-Maschine getestet und zumindest mein Handy ist in der Lage, zu einer Website zu navigieren, indem es einfach die Kamera auf das Werkstück hält 📱.&lt;/p&gt;&#10;&lt;h2 id="automatisierte-integrationstests-für-die-gui-frustrierende-arbeit"&gt;Automatisierte Integrationstests für die GUI: Frustrierende Arbeit&lt;/h2&gt;&#10;&lt;p&gt;Als meine erste Version herauskam, wollte ich einige Tests für die grafische Benutzeroberfläche hinzufügen. Da ich es nicht geschafft habe, dort absolut keine Geschäftslogik zu haben, wollte ich zumindest Bereichsprüfungen sowie die Kommunikation mit der Plattform überprüfen, um sicherzustellen, dass alles wie vorgesehen funktioniert.&lt;/p&gt;&#10;&lt;h3 id="aufgehängt-in-der-messagebox"&gt;Aufgehängt in der Messagebox&lt;/h3&gt;&#10;&lt;p&gt;Zu diesem Zweck schrieb ich einige Tests, die auf meinem lokalen Rechner liefen, mit dem Nachteil, dass, wenn der Test auf einen Warn- oder Fehlerdialog &lt;code&gt;messagebox.showinfo&lt;/code&gt; / &lt;code&gt;messagebox.showerror&lt;/code&gt; stieß, eine manuelle Benutzerinteraktion unbedingt erforderlich war, damit das Fenster geschlossen und der Test fortgesetzt werden konnte.&lt;/p&gt;&#10;&lt;p&gt;Ich habe keine einfache Möglichkeit gefunden, dieses Problem zu umgehen, da die Messagebox als unabhängiges Widget agiert, das nicht einfach manipuliert werden kann. Auch meine Versuche, einen Tastendruck als Seiteneffekt aus dem Test heraus auszulösen, waren nicht von Erfolg gekrönt (und wären am Ende wohl auch keine saubere Lösung gewesen).&lt;/p&gt;&#10;&lt;p&gt;Meine Lösung nach ein paar Stunden gemurmelten Fluchens ist, dass die Messagebox-Widgets in die GUI-Klassen injiziert werden, die sie verwenden:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@patch&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;src.gui.gui_tool_manage.GuiToolManager&amp;#39;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;setUp&lt;/span&gt;(self, mock_guitoolmanager):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Tk() &lt;span style="color:#75715e"&gt;# required to have tk variables properly instantiated&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mock_guitoolmanager &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mock_guitoolmanager&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mock_msg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; MsgBox()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mock_msg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;showinfo &lt;span style="color:#f92672"&gt;=&lt;/span&gt; MagicMock()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Persistence&lt;span style="color:#f92672"&gt;.&lt;/span&gt;set_mock_msgbox(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mock_msg)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config_tool &lt;span style="color:#f92672"&gt;=&lt;/span&gt; GuiConfigureTool(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mock_guitoolmanager, self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mock_msg, {&lt;span style="color:#e6db74"&gt;&amp;#39;padx&amp;#39;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;pady&amp;#39;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;})&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Auf diese Weise kann ich &lt;code&gt;mock_msg&lt;/code&gt; in die Gui-Klasse werfen, so dass nicht die echte showinfo genommen wird, sondern das Mock weiß, ob er wie vorgesehen aufgerufen wurde. Ich könnte ihr zur Testvorrichtung hinzufügen, so dass mein Testcode selbst &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" target="_blank" rel="noopener noreferrer" class="external-link"&gt;DRY&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; bleiben könnte.&lt;/p&gt;&#10;&lt;p&gt;Es fügt dem Konstruktor mehr Argumente hinzu und delegiert die Instanziierung an eine Klasse höherer Ebene, aber die Verwendung innerhalb der zu testenden Klasse bleibt trivial. Im Einsatz unter Realbedingungen agiert die Schnittstelle wie ein Proxy, der dann die echte &lt;code&gt;showinfo&lt;/code&gt;-Methode des Messagebox Objektes aufruft.&lt;/p&gt;&#10;&lt;h3 id="probleme-mit-statischen-klassen"&gt;Probleme mit statischen Klassen&lt;/h3&gt;&#10;&lt;p&gt;Da die Persistenzklasse, die ich &lt;a href="https://blog.schallbert.de/projects/qr-codengrave/#persistenz"&gt;oben&lt;/a&gt; erwähnt habe, &lt;em&gt;statisch&lt;/em&gt; ist, d.h. keine Instanziierung erfordert, hatte ich das nächste Problem von Anfang an in meinen Code eingebaut: Ein Integrationstest würde nun versuchen, in eine tatsächliche &amp;ldquo;persistence.dat&amp;rdquo;-Datei zu schreiben, eine Abhängigkeit, die ich nicht haben wollte, insbesondere nicht in der kontinuierlichen Integration (CI) - denn dort würde das Programm die Rechte für das Schreiben auf Festplatte nicht besitzen.&lt;/p&gt;&#10;&lt;p&gt;Also habe ich Persistence in die Haupt-GUI-Datei verschoben, die nicht getestet werden würde, und folgte damit &lt;a href="https://blog.stevensanderson.com/2009/11/04/selective-unit-testing-costs-and-benefits/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Steven Andersons großartigem Blogbeitrag&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Die Haupt-GUI sollte ohnehin nur deklarativen Code enthalten, und die geladenen Daten werden dann in die Klassen kaskadiert, die sie verwenden, anstatt in die Persistenzklasse, die die eigentliche Lade-/Speicheroperation durchführt.&lt;/p&gt;&#10;&lt;h2 id="zukünftige-versionen"&gt;Zukünftige Versionen&lt;/h2&gt;&#10;&lt;p&gt;Folgendes ist für spätere Releases geplant:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Zeichnungsgeschwindigkeit des QR-Codes muss erhöht werden&lt;/li&gt;&#10;&lt;li&gt;Grafikfehler (Skalierung, Kippen) und unscharfe Ränder des Zeichenbildschirms sollen behoben werden&lt;/li&gt;&#10;&lt;li&gt;CNC-Bahnen sollen optimiert werden, um die Fertigungszeit zu verringern&lt;/li&gt;&#10;&lt;li&gt;Fehlerbehebungen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Zumindest wenn es welche gibt; mal sehen, wie oft ich dieses Tool benutzen werde. Die Dinge, die mich am meisten stören, werden wahrscheinlich zuerst behoben werden.&lt;/p&gt;&#10;&lt;p&gt;Ach ja, noch etwas: Danke für&amp;rsquo;s Lesen des gesamten Artikels. Zur Belohnung gibt es eine Kopie &lt;a href="https://github.com/Schallbert/QR-codengrave/releases/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;QR-codengrave&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; kostenlos zum Herunterladen. Es kann für den privaten Gebrauch gern verwendet werden. Falls Sie eine kommerzielle Nutzung planen, lassen Sie es mich bitte im Voraus wissen.&lt;/p&gt;&#10;</description></item><item><title>Rechtliches / Impressum</title><link>https://blog.schallbert.de/legal/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/legal/</guid><description type="html">&#10;&lt;h2 id="impressum"&gt;Impressum&lt;/h2&gt;&#10;&lt;h3 id="angaben-gemäß--5-tmg--address"&gt;Angaben gemäß § 5 TMG / address&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/address.jpg" alt="summonable address" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="kontakt"&gt;Kontakt&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/contact.jpg" alt="phone and mail" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="redaktionell-verantwortlich-für-den-inhalt-nach--55-abs-2-rstv"&gt;Redaktionell verantwortlich für den Inhalt nach § 55 Abs. 2 RStV:&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/address.jpg" alt="summonable address" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="quelle"&gt;Quelle&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://www.e-recht24.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;e-recht24.de&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="streitschlichtung"&gt;Streitschlichtung&lt;/h2&gt;&#10;&lt;p&gt;Ich bin weder bereit noch verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.&lt;/p&gt;&#10;&lt;h2 id="haftung"&gt;Haftung&lt;/h2&gt;&#10;&lt;p&gt;Habe ich etwas falsch gemacht oder vergessen?&lt;/p&gt;&#10;&lt;p&gt;Ich bin auch nur ein Mensch und versuche, diese Website nach bestem Wissen und Gewissen der aktuellen Rechtslage entsprechend zu betreiben. Sollte dennoch etwas nicht den gesetzlichen Anforderungen entsprechen, so bitte ich höflich um eine formlose Mitteilung. Berechtigte Anmerkungen und Beschwerden werde ich umgehend bearbeiten und Mängel beseitigen.&lt;/p&gt;&#10;&lt;h3 id="haftung-für-inhalte"&gt;Haftung für Inhalte&lt;/h3&gt;&#10;&lt;p&gt;Als Diensteanbieter bin ich gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG bin ich als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.&lt;/p&gt;&#10;&lt;p&gt;Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werde ich diese Inhalte selbstverständlich umgehend entfernen.&lt;/p&gt;&#10;&lt;h2 id="haftungsausschluss"&gt;Haftungsausschluss&lt;/h2&gt;&#10;&lt;p&gt;Meine Seite enthält Links und Referenzen zu externen Websites Dritter, auf deren Inhalte ich keinen Einfluss habe. Deshalb kann ich für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar.&lt;/p&gt;&#10;&lt;p&gt;Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Links umgehend entfernen.&lt;/p&gt;&#10;&lt;h2 id="urheberrecht"&gt;Urheberrecht&lt;/h2&gt;&#10;&lt;p&gt;Die mir erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen meiner schriftlichen Zustimmung. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet.&lt;/p&gt;&#10;&lt;p&gt;Ich bemühe mich sehr, Inhalte Dritter als solche zu kennzeichnen und mir die Erlaubnis zur Verwendung dieser Inhalte vorab schriftlich einzuholen. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitte ich um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Inhalte selbstverständlich umgehend entfernen.&lt;/p&gt;&#10;</description></item><item><title>Seifenbutler</title><link>https://blog.schallbert.de/projects/seifenbutler/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/seifenbutler/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/soapbutler-thumb.jpg"&#10; class="post-cover"&#10; alt="SoapButler: a soap holder for the shower rail"&#10; title="Seifenbutler" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: Mittel 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: ~250€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: mehr als ein Jahr&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="die-geschichte-vom-seifenbutler"&gt;Die Geschichte vom Seifenbutler&lt;/h2&gt;&#10;&lt;p&gt;Begleiten Sie mich hier über eine Zeitraum von mehr als einem Jahr, in welchem ich beständig an ein- und demselben Projekt arbeite. Dabei geht es von einer Idee mit wenigen Anforderungen hin zur Vorserienreife. Ich erläutere meine Prototypenarbeit, gehe ein wenig auf die vielen Rückschläge ein und nähere mich am Ende einem - wie ich meine - guten Produkt an.&lt;/p&gt;&#10;&lt;h2 id="idee"&gt;Idee&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Mai 2022&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Vor Kurzem bin ich beim Duschen von flüssigem Shampoo auf ein Shampoo bzw. Seife in fester Form umgestiegen. Da ich keine Ablage für die Seife hatte, wurde sie gleich mitgeduscht. So verbrauchte sie sich schneller, wurde tagelang nicht trocken und klebte auch noch am Rand der Dusche fest.&lt;/p&gt;&#10;&lt;p&gt;Ich wollte keinen normalen Seifenhalter kaufen, für den man Löcher in die gefliesten Wände bohren müsste. Also beschloss ich, einen Seifenhalter zum Anklemmen für die Duschstange zu entwerfen. Ich würde das Teil über dem Duschkopf anbringen, damit es nicht mehr Wasser als nötig ausgesetzt ist.&lt;/p&gt;&#10;&lt;p&gt;Außerdem soll der Seifenhalter im Handumdrehen und ohne Werkzeug zu montieren sein.&lt;/p&gt;&#10;&lt;h2 id="v1-v3-erste-entwürfe-in-hpl"&gt;V1-V3: Erste Entwürfe in HPL&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Juli 2022&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Also entwarf ich meinen eigenen Halter. Ich wollte ein einfaches, rundes Design mit einem Klemmloch zur Befestigung an der Duschstange und einem abgesenkten Bereich, in dem die Seife bzw. das feste Shampoo untergebracht werden sollte. Durch einige Schlitze im Boden sollte das Wasser abtropfen und die Seife an der Luft trocknen können, wenn sie nicht benutzt wird. Ich wollte ein Befestigungselement im Inneren des Teils und eine Mutter auf der Innenseite anbringen, damit der Durchmesser des Lochs für die Duschstange verringert und der Seifenbutler - so der Name für das gute Stück - auf diese Weise festgeklemmt werden kann.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/clamping_explained.jpg" alt="Image: soapbutler V1&amp;#39;s clamping mechanism explained " /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Um dies zu ermöglichen war es sinnvoll, zwei Bauteile zu fertigen - ein oberes, das die Seife an Ort und Stelle hält und Platz für die Aufnahme der Befestigungsmechanik bietet und ein unteres, das Ablaufschlitze für überschüssiges Wasser und die Aufnahme der anderen Hälfte der Befestigungsmechanik aufweist.&lt;/p&gt;&#10;&lt;h3 id="freecad-skizzen"&gt;FreeCAD: Skizzen&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/freecad_partbodydwg.jpg" alt="Image: FreeCAD Teil, Körper, Skizze" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;FreeCAD-Artefakte werden auf eine bestimmte Art organisiert, um den Umgang mit verschiedenen Ansichten zu vereinfachen oder die spätere zweiseitige Bearbeitung mit Hilfe der Funktion Klonen vorzubereiten. Daher erstelle ich zunächst eine Reihe von Teilen im Modus &lt;code&gt;Parts Design&lt;/code&gt;. Skizzen können in Körperelementen gruppiert werden, die wiederum in Teilelementen angeordnet sind.&lt;/p&gt;&#10;&lt;p&gt;Ich erstelle also ein Teil (gelbes Element), das &amp;ldquo;Skizzen&amp;rdquo; enthält - alle Originalzeichnungen (rote Elemente) aller Körper (blaue Elemente) des/der zu produzierenden Teils/Teile. Diese Zeichnungen sollten vollständig bestimmt &amp;ldquo;fully constrained&amp;rdquo; sein. Sie können keine Informationen zur Positionierung relativ zu anderen Komponenten enthalten. Das bedeutet, dass sich die Skizzen alle am gleichen Ort befinden. Somit sollten alle inaktiven Skizzen ausgeblendet werden um Verwechslungen zu vermeiden.&lt;/p&gt;&#10;&lt;p&gt;(Einen tieferen Einblick in meinen FreeCAD-Workflow bietet &lt;a href="https://blog.schallbert.de/freecad-get-started/"&gt;dieser Blog-Artikel&lt;/a&gt;.)&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/freecad_scetches.jpg" alt="Image: FreeCAD Scetch" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="freecad-teile"&gt;FreeCAD: Teile&lt;/h3&gt;&#10;&lt;p&gt;Dann erstelle ich Teile, die später die zu schneidenden Komponenten enthalten, z.B. &amp;ldquo;clampingHoles&amp;rdquo;, &amp;ldquo;soapButler&amp;rdquo;, &amp;ldquo;wallHolder&amp;rdquo;. Sie enthalten jeweils eine Untermenge der Skizzen, aber keine Informationen zur endgültigen Positionierung. Zuletzt erstelle ich ein Teil für jeden Auftrag, den meine Produktion auszuführen hat, z. B. &lt;code&gt;job00_clamping&lt;/code&gt;, &lt;code&gt;job01_upperMill&lt;/code&gt;, &lt;code&gt;job02_3dCut&lt;/code&gt;, &lt;code&gt;job03_lowerFinish&lt;/code&gt;. Diese klonen Körperkomponenten, fügen aber Platzierungsinformationen hinzu, enthalten gespiegelte Teile für das 2-seitige Fräsen oder bieten einen Umriss, um die XY-0 richtig einzustellen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/freecad_parts.jpg" alt="Image: FreeCAD Teil" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Der Vorteil einer solchen Projektkonfiguration besteht darin, dass, wenn eine Original-Skizze aus irgendeinem Grund geändert werden muss, alle geklonten Teile automatisch ebenfalls geändert werden. Zudem kann man bei geschickter Organisation erreichen, dass Maße der Teile einfach in der Tabelle geändert werden können und sich dann die gesamte Teilegeometrie darauf anpasst, ohne dass manuell nachgearbeitet werden muss.&lt;/p&gt;&#10;&lt;h3 id="bearbeitung"&gt;Bearbeitung&lt;/h3&gt;&#10;&lt;p&gt;Für meinen ersten Prototyp habe ich Hochdrucklaminat als Material gewählt, da es sehr widerstandsfähig ist, ab und zu nass werden kann und ziemlich gut aussieht. Für die Bearbeitung habe ich Fräser mit niedriger Spiralsteigung verwendet, um die Vibrationen beim Schneiden des zähen HPL-Materials unter Kontrolle zu halten. Ich habe mich für beschichtete Einschneider-Hartmetallfräser entschieden, die ich vor einiger Zeit gekauft habe (für das Schneiden von Aluminium). Ich benutzte Exzenterspanner, um das Teil an seinem Platz zu halten, und nahm nur zwei zusätzliche Niederhalter zur Hand, damit das Teil auf dem Maschinenbett blieb.&lt;/p&gt;&#10;&lt;p&gt;Ich hatte einen Fehler im CAM-Programm, für dessen Behebung ich ein paar Minuten brauchte: Ich hatte die Werkzeugwege für die Spirale nach innen und nicht nach außen eingestellt. Das Problem dabei ist, dass die Spirale mit Fortschreiten des Schnittes immer schwammiger und instabiler wird. Ich konnte die Vibrationen nur dadurch in Schach halten, dass ich den Fräsvorgang häufig unterbrach und Klebeband an den Stellen anbrachte, an denen der Schnitt bereits eingebracht war.&lt;/p&gt;&#10;&lt;h3 id="bewertung"&gt;Bewertung&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/machining_v2.jpg" alt="Bild: Bearbeitung V2 von soapbutler" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Herstellung des ersten Prototyps lief ganz OK. Die Maschine brummte zuversichtlich, und abgesehen von einigen kleinen Fehlern (Befestigungslöcher im unteren Teil zu eng, Länge der Klammerbefestigung falsch angegeben) war ich mit dem Ergebnis einigermaßen zufrieden.&lt;/p&gt;&#10;&lt;p&gt;Ich brauchte etwa 20 Minuten, um das Teil auszulösen und die Anbindungen zu entfernen. Dies erschien mir als deutlich verbesserungswürdig.&lt;/p&gt;&#10;&lt;h2 id="v4-fehlschlag-mit-billigem-hpl"&gt;V4: Fehlschlag mit billigem HPL&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Oktober 2022&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/cam_chamfer_issue.jpg" alt="Bild: Fasenproblem bei schmalen Schlitzen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich habe die Fasenfunktion in meiner CAM noch nie verwendet und dachte, dies wäre eine gute Gelegenheit für einen Test, da die Teileecken bei meinem ersten Prototyp ziemlich scharf waren. Da ich nur einen 4mm Schaftfräser verwende, erschien mir der 90° 10mm Fasenfräser zu groß, zumal ich nach außen versetzen musste, um die Spitze des Fasenfräsers nicht zu verschleißen. Deshalb wählte ich einen 30°-Winkel, was sich schnell als Fehler herausstellte, da die Ecken damit kaum weicher wurden.&lt;/p&gt;&#10;&lt;p&gt;Ich vergaß auch, dass nur die Außenseite der Teile von einer abgeschrägten Kante profitieren würde. Ich hingegen wendete die Fase auf der Innenseite an, was der Verarbeitungsqualität abträglich war. Außerdem stellte ich fest, dass die neue HPL-Platte, aus der ich die Prototypen herstellte, nicht ganz plan auflag, so dass einige Teile des Umrisses nicht vollständig durchgefräst waren. Dies führte wiederum zu einer geringeren Qualität und zu erheblichen manuellen Nacharbeiten.&lt;/p&gt;&#10;&lt;h2 id="v5-korrekturen-im-cam-und-bessere-verarbeitungsqualität"&gt;V5: Korrekturen im CAM und bessere Verarbeitungsqualität&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;November 2022&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/clamping2_explained.jpg" alt="Image: soapbutler V2&amp;#39;s clamping mechanism explained" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich finde die Freiarmklammer mit offenem Befestigungselement nicht so schön anzuschauen, weil sie Asymmetrisch ist. Außerdem würde sie wegen des engen Spalts schwer zu reinigen sein. Nach einigem Nachdenken kam ich auf die Lösung einer &amp;ldquo;Mittelklemme&amp;rdquo;: Das Befestigungselement würde gegen ein Stück Gummi drücken, das direkt an der Duschstange anliegt, um die Seifenablage in Position zu halten.&lt;/p&gt;&#10;&lt;h3 id="fasen"&gt;Fasen&lt;/h3&gt;&#10;&lt;p&gt;Nachdem ich die Zeichnungen entsprechend aktualisiert hatte, erstellte ich ein weiteres, gespiegeltes Teil in FreeCAD im Modus &amp;ldquo;Draft&amp;rdquo;. Dieses Teil klont das Originalteil am Umriss und ist für das zweiseitige Fräsen bestimmt, wobei das Teil um seine linke obere Kante (XY-Null) gedreht wird. Auf diese Weise ist die Fase dann richtig umzusetzen, wenn ich sicherstellen kann, dass sich der XY-Nullpunkt nicht bewegt.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/freecad_clones.jpg" alt="Image: Clone function in FreeCAD to mirror parts" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Das hat in der Praxis sehr gut funktioniert, und ich konnte auch das Anbringen von Senkbohrungen für die unteren Befestigungselemente automatisieren, was die Bauqualität erhöht. Das Ergebnis ist unten zu sehen.&lt;/p&gt;&#10;&lt;h3 id="bewertung-1"&gt;Bewertung&lt;/h3&gt;&#10;&lt;p&gt;Nachdem dieses fünfte Teil bearbeitet war musste ich leider feststellen, dass die Melaminbeschichtung an der Schnittkante leichte Ausrisse aufwies. Außerdem bemerkte ich während der Bearbeitung den Geruch nach verbranntem Material. Ein kurzer Blick auf den Fräser offenbarte die Ursache: Er war nach nur einer Stunde Betriebszeit trotz Beschichtung stumpf geworden. Das macht die Arbeit mit HPL ziemlich teuer und frustrierend. Außerdem war die Zykluszeit für diese Version recht lang: 15 Minuten für ein Teil. Das würde nie wirtschaftlich werden&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/soapbutler_v5.jpg" alt="Bild: Soapbutler_v5 front view with fastener" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Deshalb habe ich versucht, alternative Materialien für den Seifenbutler zu finden.&#10;Inzwischen sollten die Prototypen ein paar Monate lang in der Dusche ausprobiert werden, um Langzeiterfahrungen zu sammeln. Ist HPL geeignet für den Einsatz im Feuchtraum? Kriecht das Wasser zwischen den beiden Schichten hindurch? Ist es leicht zu reinigen?&lt;/p&gt;&#10;&lt;h2 id="v6-fehlschlag-mit-birke-multiplex"&gt;V6: Fehlschlag mit Birke-Multiplex&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Mai 2023&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/cad_v3.jpg" alt="Image: CAD updated for 1-piece approach made wich stronger sheets" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Auf der Suche nach neuen Materialien besorgte ich wasserfeste Holzplatten, verleimt im Multiplexverfahren. Da sie mit 15mm deutlich stärker war als das vorher verwendete Material, vereinfachte ich das Design in eine Ausführung, die aus nur einem Teil besteht und hinten zentral über eine Madenschraube verfügt, die die Klemmung an der Duschstange übernehmen sollte.&lt;/p&gt;&#10;&lt;p&gt;Das Loch für die Madenschraube würde ich nach der Fertigung auf der CNC von Hand bohren.&lt;/p&gt;&#10;&lt;p&gt;Leider jedoch stellte sich das Material auf der CNC als ungeeignet für das Fräsen der Spirale heraus. Ich bekam zahlreiche Ausrisse und hatte Schwierigkeiten, das Werkstück niederzuhalten. Die Konturen meines Seifenbutlers waren für das kreuzverleimte Material schlicht zu fein bzw. die Faserlänge des Holzes zu groß.&lt;/p&gt;&#10;&lt;p&gt;Die fünf gefertigten Teile wanderten direkt in den Restmüll.&lt;/p&gt;&#10;&lt;h2 id="v7-fehlschlag-mit-aluminium-verbundmaterial"&gt;V7: Fehlschlag mit Aluminium-Verbundmaterial&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Juli 2023&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v7.jpg" alt="Image: Seifenbutler V7 in CAD, showing two mirrored parts that open on opposite sides to have the shower rail added in between" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich kehrte nun zum Design als Zweiteiler zurück und überlegte, ob Aluminium-Verbundmateial durch seine Korrosionsbeständigkeit nicht ebenfalls für meine Seifenablage geeignet wäre.&lt;/p&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v7_fabricated.jpg" alt="Image: Seifenbutler V7, manufactured in Aluminium composite material" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Kurzerhand fertigte ich einen neuen Entwurf an&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;, der die Eigenschaften des neuen Materials versucht, so gut wie möglich zu berücksichtigen. Nebenbei überlegte ich mir, dass durch das leichtere Material jetzt endlich eine Halterung über Magnete möglich sein müsste und sah entsprechende Senkungen im Rahmen dafür vor.&lt;/p&gt;&#10;&lt;p&gt;Ich hatte die Idee, dass ein Teil von je einer Seite der Duschstange aus angelegt und dann über die Magnete zusammengehalten würden - damit bin ich endlich die lästige Klemmschraube los und habe ein sehr einfaches Design.&lt;/p&gt;&#10;&lt;p&gt;Leider jedoch erwies sich das Konzept als völlig ungeeignet. Die Haltekraft der Magnete ist nicht hoch genug und die Klemmung funktioniert nur über das Abkippen des Seifenhalters nach vorne, welches einen seltsam schiefen Eindruck an der Duschstange ergibt.&lt;/p&gt;&#10;&lt;p&gt;Also: Weitertüfteln!&lt;/p&gt;&#10;&lt;h2 id="v8-umstieg-auf-acrylglas-spannfutter"&gt;V8: Umstieg auf Acrylglas, Spannfutter&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Juli 2023&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Nach vielen Stunden am Rechner für den Entwurf kam ich auf ein neues Klemmprinzip, abgeschaut vom Bohrfutter eines Akkuschraubers: Drei im 120°-Winkel angebrachte, bewegliche Klemmvorrichtungen würden - mittels eines Kabelbinders zusammengeführt - Duschstangen beliebigen Durchmessers festhalten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v8.jpg" alt="Image: Seifenbutler V8, made of acrylic glass with chuck" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="fertigung"&gt;Fertigung&lt;/h3&gt;&#10;&lt;p&gt;Ich stieß auf vielfältige Schwierigkeiten in der Fertigung, verursacht hauptsächlich durch die teils sehr kleinen Komponenten, welche zudem auch noch Kerben für die Führung der Kabelbinder aufweisen mussten. Hinzu kam bei mir die Einsicht, dass Entwurf und Anfertigung leichtgängiger Passungen ohne merkliches Spiel für mich eine große Herausforderung sind und eine Menge erfolgloser Versuche benötigt, bis ich am Ziel bin.&lt;/p&gt;&#10;&lt;p&gt;Durch die Verwendung verschieden großer (2mm, 4mm) Fräser und Optimierung der Fräsreihenfolge konnte ich die Teile jedoch schließlich erfolgreich und in guter Qualität herstellen. &lt;a href="https://blog.schallbert.de/milling-small-parts/"&gt;Siehe dazu mein Artikel zum Thema Kleinteile Fräsen.&lt;/a&gt; Einzig die Spirale bereitet mir noch Kopfzerbrechen, denn auch in Acrylglas möchte sie dem Fräser am liebsten ausweichen. Dadurch sind ihre Kanten recht scharf.&lt;/p&gt;&#10;&lt;p&gt;Der Zusammenbau gestaltete sich zum Glück einfach - man benötigt nur eine Schraube und etwas Plastikklebstoff. Nur das Einfädeln des Kabelbinders will geübt sein.&lt;/p&gt;&#10;&lt;h3 id="praxistest"&gt;Praxistest&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v8_fabricated.jpg" alt="Image: Seifenbutler V8 after some months on duty" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich hatte diese Variante testweise für ein paar Monate in meiner Dusche. Da die Schraube nur verzinkt war, begann sie leider recht schnell, zu korrodieren. Merke: Edelstahl verwenden.&lt;/p&gt;&#10;&lt;p&gt;Die Stabilität des Seifenbutlers ist weiter verbesserungswürdig. Der Kabelbinder lässt etwas Spiel, sodass mit wenig Kraftaufwand Neigungen von rund 10° gegenüber der Senkrechten der Duschstange möglich sind. Dabei knarzt es ganz schön zwischen Ober- und Unterteil. Da muss noch nachgebessert werden.&lt;/p&gt;&#10;&lt;p&gt;Außerdem würde ich den Klemmmechanismus gern wieder etwas vereinfachen.&lt;/p&gt;&#10;&lt;h2 id="v9-gegossenes-acrylglas-seifenbutler-abnehmbar"&gt;V9: Gegossenes Acrylglas, Seifenbutler abnehmbar&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Juli 2023&lt;/strong&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v9.jpg" alt="Image: Seifenbutler V9 with two separate parts for clamping" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Diese Weiterentwicklung hält Ober- und Unterschale vorn mit Magneten zusammen, welche sich unsichtbar im Material versenken lassen. Im Klemmbereich hinten befinden sich zwei Einsätze, die wie bei einer Schublade in eine passende Aussparung des Seifenbutler passen und ihn so zusammenhalten.&lt;/p&gt;&#10;&lt;p&gt;Ich habe mehrere Prototypen benötigt, bis ich die Binderführung soweit hatte, dass der Kabelbinder einfach hinten in den Seifenbutler gesteckt wird, er sich von selbst um die Duschstange legt und schließlich wieder zum Vorschein kommt, wo er dann zum Festzurren bereit liegt.&lt;/p&gt;&#10;&lt;p&gt;Dies ist das erste Design, was meine Anforderungen hinsichtlich Haltekraft und Einfachheit der Montage erfüllt. Leider ist die Fertigung komplex und die Toleranzen müssen eng gehalten werden, da der Seifenhalter entweder zu leicht aus der Klemmung herausrutscht oder aber durch zu enge Passungen kaum angebracht werden kann.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/soapbutler/soapbutler_v9.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Seifenbutler zip tie demonstration&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="v10-verbessertes-fertigungsverfahren"&gt;V10: Verbessertes Fertigungsverfahren&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;August 2023&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Mit ein paar Optimierungen entspricht diese Version der V9. Ich erreichte eine bessere Wiederholbarkeit in der Fertigung und kann Toleranzen für Passungen etwas großzügiger auslegen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v10.jpg" alt="Image: Seifenbutler V10, multiple parts on a sheet for manufacturing" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Mit diesem Design hole ich mir erste Rückmeldungen aus dem Bekanntenkreis. Einhellige Meinung: Kabelbinder wirken wie ein Provisorium und können den Kunden nicht zugemutet werden.&lt;/p&gt;&#10;&lt;p&gt;Also zurück ans Reißbrett.&lt;/p&gt;&#10;&lt;h2 id="v11-stecksystem-l-nase-in-hpl"&gt;V11: Stecksystem &amp;lsquo;L-Nase&amp;rsquo; in HPL&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;August 2023&lt;/strong&gt;&#10;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v11.jpg" alt="Image: Seifenbutler V11 with L-type puzzle clamping" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Ein weiterer Versuch in HPL, dieses Mal mit Vielzahnfräsern. Sie raspeln das Material eher als dass sie es schneiden und sind für die Bearbeitung abrasiver, faserreicher Materialien wie CFK/GFK ausgelegt. Die hohe Zähigkeit von HPL gepaart mit der schlechten Wärmeableitung jedoch lassen die Fräser nach nur zwei gefertigten Prototypen komplett stumpf werden. Also sind sie auch keine Lösung für effiziente Arbeit in HPL für mich.&lt;/p&gt;&#10;&lt;p&gt;Das Design ist wieder mehrteilig und erreicht die Klemmung im letzten Update über eine hinten montierte Schraube, die nach Zusammensetzen von Vorder- und Rückteil angezogen wird. Dadurch wird der Seifenhalter insgesamt länger, was ich vom Design her nicht optimal finde.&lt;/p&gt;&#10;&lt;p&gt;Die Fertigungsqualität hingegen habe ich inzwischen gut im Griff, finde ich. Die Schnitte sind sauber und auch die Qualität der Kanten ist sehr ansprechend.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v11_fabricated.jpg" alt="Image: Seifenbutler V11 made of HPL, disassembled" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Durch die schnelle Abnutzung der Fräser ist es für mich sehr schwer, in eine leichte Übermaßpassung für die Steckverbindung herzustellen. Allzu leicht geschieht es bei meinen Versuchen, dass der Fräser zu schnell an Durchmesser verliert und die Teile gar nicht oder nur mit roher Gewalt ineinander gesteckt werden können.&lt;/p&gt;&#10;&lt;p&gt;Vielleicht kann ich dieses Problem durch eine andere Passungsgeometrie lösen?&lt;/p&gt;&#10;&lt;h2 id="v12-stecksystem-brio"&gt;V12: Stecksystem &amp;lsquo;Brio&amp;rsquo;&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;September 2023&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Hier verwende ich wieder Acrylglas, diesmal die extrudierte Variante. So sind die Dicketoleranzen gegenüber der gegossenen Variante sehr gering und meine Z-Nullpunkte sind stets passend. Dies ist prima für anzubringende Fasen, die endlich bei jedem Teil gleich breit ausfallen und dem Material eine hohe Wertigkeit verleihen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v12.jpg" alt="Image: CAD drawing for the plug-in system of Seifenbutler V12" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Nun habe ich auch eine Lösung für die Spirale: Doppelseitige Bearbeitung. Dadurch steigt zwar der Aufwand für mich, allerdings verbringe ich lieber mehr Zeit an der Maschine, als mich später über mangelnde Qualität zu ärgern.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v12_fabricated.jpg" alt="Image: Prototype of Seifenbutler V12" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Mein Vorgehen: Ich fräse die Spirale fast ganz durch und lasse nur zarte &lt;code&gt;0.3mm&lt;/code&gt; stehen. Auf der Rückseite dann verwende ich den Fasenfräser und fahre die Spirale von innen nach außen ab. Zusätzlich sichere ich die Unterseite mit Klebeband, sodass mein Vakuumtisch die Spirale sicher halten kann und Schwingungen vermieden werden.&lt;/p&gt;&#10;&lt;p&gt;Das Design sieht ein Rändelrad vor, sodass die Klemmung ohne Werkzeug erfolgen kann. Da die Klemmung starke Zugkräfte auf die Verbinder ausüben und sie daher aus Stabilitätsgründen genau in der Achse der Duschstange positioniert sein müssen, habe ich auch hier ein leichtes Abkipp-Problem. Dieses verstärkt sich, je kleiner der Durchmesser der Duschstange ist.&lt;/p&gt;&#10;&lt;p&gt;Mit einer weiteren Variante konnte ich das Abkippen vermeiden, allerdings sind die Zapfen dann nicht mehr verdeckt. Dies wiederum hat zur Folge, dass dort jetzt Duschwasser eindringt und Rückstände hinterlassen kann.&lt;/p&gt;&#10;&lt;p&gt;Ich bin fast da. Trotzdem kann ich vielleicht noch etwas verbessern.&lt;/p&gt;&#10;&lt;h2 id="v13-klemmautomatik-und-nullserienfertigung"&gt;V13: Klemmautomatik und Nullserienfertigung&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Oktober 2023&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;In Version 13 versuche ich, die Vorteile der vorgehenden Varianten V9-V12 zu bündeln und dabei die Nachteile (Abkippen, Kabelbinder, Montage mit Werkzeug, instabil) so gut wie möglich zu umgehen.&lt;/p&gt;&#10;&lt;h3 id="neues-klemmkonzept"&gt;Neues Klemmkonzept&lt;/h3&gt;&#10;&lt;p&gt;Ich verfolge daher noch ein neues Klemmkonzept. Ich möchte alle handelsüblichen Duschstangen bei der Montage berücksichtigen und muss daher zwischen &lt;code&gt;18mm&lt;/code&gt; und &lt;code&gt;25mm&lt;/code&gt; sicher klemmen. Aus den vorherigen Versionen ist mir ebenfalls klar geworden, dass zusätzlicher Druck von zentral hinter der Duschstange ausgeübt werden muss, um ein Abkippen des Seifenbutlers nach vorn zu verhindern.&lt;/p&gt;&#10;&lt;p&gt;Zusätzlich versuche ich, die Verriegelung um die Duschstange über eine Drehbewegung zu realisieren. Der Drehpunkt ist dabei die Mitte der Spirale, was drei weitere Schraubverbindungen am Umfang des Seifenbutlers erforderlich macht. Mit &lt;code&gt;V13.9&lt;/code&gt; schaffe ich endlich den Durchbruch.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v13.jpg" alt="Image: CAD drawing for V13.9 with multiple stages for closing." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Eine Klemmautomatik wird durch das Zusammenschieben von Ober- und Unterschale betätigt, sodass der Seifenbutler sich dem Durchmesser der Duschstange stufenlos anpasst. Die Arretierung findet über ein Rändelrad an der Unterseite statt, wo eine schiefe Ebene am Fanghaken unbeabsichtigtes Öffnen verhindert.&lt;/p&gt;&#10;&lt;h3 id="fertigung-1"&gt;Fertigung&lt;/h3&gt;&#10;&lt;p&gt;Dieses Design ist unglücklicherweise sehr komplex. Es besteht aus fünf Komponenten aus Acrylglas, fünf Schrauben und einer Rändelschraube mit Federring, deren Zusammenbau etwas Zeit in Anspruch nimmt.&lt;/p&gt;&#10;&lt;p&gt;Die Arbeit an der CNC beträgt über 10 Minuten pro Teil, was doch recht üppig ausfällt. Zudem habe ich einiges an Verbrauchsmaterial durch die erforderliche Werkstückniederhaltung.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/soapbutler/seifenbutler_v13_fabricated.jpg" alt="Image: Seifenbutler V13.9" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dennoch: Die Fertigungsqualität ist so hoch wie nie zuvor und der Seifenbutler ist jetzt kinderleicht und ohne Werkzeug binnen kurzer Zeit anzubringen.&lt;/p&gt;&#10;&lt;h2 id="fazit"&gt;Fazit&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Januar 2024&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Wenn ich meine CAD-Dateien so durchsehe, dann habe ich in der Summe bis jetzt &lt;code&gt;42&lt;/code&gt; Versionen meines Seifenhalters geplant, aufgeteilt in &lt;code&gt;13&lt;/code&gt; teils sehr unterschiedliche Designs. &lt;code&gt;21&lt;/code&gt; dieser Versionen haben es am Ende auch wirklich auf die CNC geschafft und &lt;code&gt;5&lt;/code&gt; von ihnen sind wiederum in einer größeren Stückzahl als &lt;code&gt;3&lt;/code&gt; hergestellt worden.&lt;/p&gt;&#10;&lt;p&gt;Ich habe vier Seifenbutler zwecks Dauertest in meiner Dusche und inzwischen werden sie von der ganzen Familie benutzt.&lt;/p&gt;&#10;&lt;p&gt;Die Anzahl Stunden, die ich mit dem Thema bis jetzt verbracht habe, kann ich gar nicht seriös zählen - es waren aber echt viele. Ich habe eine Menge über Materialien und ihre Eignung für verschiedene Teilegeometrien, Aufnahme von Kräften, ihre Bearbeitbarkeit und dafür notwendige Schnittwerte an meiner Maschine gelernt. Und auch, dass es sich lohnt, Dinge mit Biss auch über lange Zeit weiterzuverfolgen. Vielleicht nicht monetär, doch auf jeden Fall für mich als Mensch.&lt;/p&gt;&#10;&lt;p&gt;Ich möchte auch gar nicht ausschließen, weitere Optimierungen am Seifenbutler vorzunehmen - erst einmal will ich in naher Zukunft aber schauen, &lt;a href="https://blog.schallbert.de/seifenbutler-pilot-run/"&gt;ob er als Nischenprodukt markttauglich ist&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Einen tieferen Einstieg zu den Herausforderungen bei Bearbeitung von HPL gebe ich &lt;a href="https://blog.schallbert.de/cnc-router-overload/"&gt;in diesem Blogbeitrag&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;Wie man an den CAD-Bildern ab hier erkennen kann, bin ich mit meiner Zeichensoftware umgestiegen (von FreeCAD nach CADasCAM). Für 2D-Zeichnungen ist es trotz einiger Nachteile leichter zu bedienen und ich komme schneller zu Ergebnissen. Ein großer Wermutstropfen: CADasCAM ist nicht quelloffen 😥.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Spindel-Upgrade</title><link>https://blog.schallbert.de/projects/spindle-upgrade/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/spindle-upgrade/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/spindleupgrade/mafellvsspinogy-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: The new spindle mounted on Zerspanobert"&#10; title="Spindel-Upgrade" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeit: Experte 5/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: ~2700€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~20h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/bN8MKYr4dmZZd2Tmoga9Ny" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;CNC Spindel-Upgrade&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Entgegen meiner Aussage &lt;a href="https://blog.schallbert.de/portal-milling/#spindle-power"&gt;hier&lt;/a&gt; habe ich mich entschieden, eine Hochfrequenz-Spindel zu kaufen. Ich musste eine Menge lesen, planen, bauen und &lt;a href="https://blog.schallbert.de/testing-hardware/"&gt;testen&lt;/a&gt; bevor ich mit der neuen Spindel endlich &amp;ldquo;Späne machen&amp;rdquo; konnte.&lt;/p&gt;&#10;&lt;h2 id="gründe-für-die-investition"&gt;Gründe für die Investition&lt;/h2&gt;&#10;&lt;p&gt;Es gibt starke und schwache Argumente für solch ein Upgrade. Kurz gesagt waren meine Argumente eher schwach, also hätte ich bei meinem bewährten Oberfräsmotor (Universalmaschine) bleiben können, deren Möglichkeiten und Grenzen nach einem Jahr im Einsatz klar auf der Hand lagen. Letztendlich war es mehr eine emotionale als eine faktenbasierte Entscheidung.&lt;/p&gt;&#10;&lt;h3 id="starke-argumente-"&gt;Starke Argumente 🥰&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Mehr Leistung: &lt;a href="https://blog.schallbert.de/cnc-router-overload/"&gt;Dieses Problem&lt;/a&gt; sollte nun nicht erneut auftreten, denn die Spindel kann jetzt deutlich höhere Zustellungen und Geschwindigkeiten fahren. Daher reduziert sich die Herstellungszeit, vor allem bei Aufträgen, bei denen viel Material abgetragen werden muss.&lt;/li&gt;&#10;&lt;li&gt;Mehr Drehmoment bei niedrigen Drehzahlen: Mein Fräsmotor konnte bei Einsatz eines Planfräsers die Drehzahl kaum halten, zudem überhitzte er sehr schnell weil sein Lüfterrad nicht genügend Luftdurchfluss ermöglichte. Durch die Fremdkühlung ist das Problem jetzt behoben.&lt;/li&gt;&#10;&lt;li&gt;Vibrationen: Ich hoffe, dass mein &lt;a href="https://blog.schallbert.de/cnc-vibrates/"&gt;Vibrationsproblem&lt;/a&gt; durch die gesteigerte Steifigkeit der Spindelmontage nun gelöst ist; das wird sich aber erst noch zeigen müssen.&lt;/li&gt;&#10;&lt;li&gt;Höhere Präzision durch bessere Maschinenlager und höhere Stabilität der Halterung&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="schwache-argumente-"&gt;Schwache Argumente 🤔&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Spindel ist deutlich leiser als mein vorheriger Fräsmotor.&lt;/li&gt;&#10;&lt;li&gt;Ich finde Drehstrom-Asynchronmotoren und ihr Betrieb über Frequenzumrichter (FU) super spannend.&lt;/li&gt;&#10;&lt;li&gt;Ich mag das schicke Aussehen der Spindel.&lt;/li&gt;&#10;&lt;li&gt;Ich habe mich über ein halbes Jahr lang mit den Leuten bei Spinogy unterhalten und verschiedene Lösungen diskutiert. Ich wollte sie am Ende nicht ohne Kauf zurücklassen.&lt;/li&gt;&#10;&lt;li&gt;Der Frequenzumrichter hat zusätzliche interne Sicherheitsmechanismen, die bei Auftreten eines Fehlers Beschädigung von Maschine bzw. Werkstück verhindern können&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="gegenargumente-"&gt;Gegenargumente 🤨&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schlechte Kosten-Nutzen-Bilanz: Die Maschine ist etwas vielseitiger und schneller geworden, die Kosten aber sind deutlich gestiegen&lt;/li&gt;&#10;&lt;li&gt;Aufwärmzeit der Spindel sind nun &lt;code&gt;30 Minuten&lt;/code&gt;, eine echt lange Zeit wenn man die Maschine nicht im täglichen Einsatz hat&lt;/li&gt;&#10;&lt;li&gt;Langwierige Einarbeitung in das Fachgebiet wird benötigt. Über &lt;a href="https://assets.omron.eu/downloads/latest/manual/en/i570_mx2_users_manual_en.pdf?v=8" target="_blank" rel="noopener noreferrer" class="external-link"&gt;400 Seiten Frequenzumrichter-&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; &lt;a href="https://shop.spinogy.de/wp-content/uploads/2022/02/Operating-Manual-X22.pdf" target="_blank" rel="noopener noreferrer" class="external-link"&gt;und Spindel-Handbücher&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; waren zu lesen, Schaltpläne zu zeichnen und ein Schaltschrank aufzubauen.&lt;/li&gt;&#10;&lt;li&gt;Die Portalkonstruktion meiner CNC ist für leichte bis mittelschwere Zerspanungstätigkeiten gedacht. Wenn ich die Leistung der Spindel vollständig ausnutzen möchte, würde es die Maschine überlasten, insbesondere ihren Schwachpunkt, die Z-Achse.&lt;/li&gt;&#10;&lt;li&gt;Die Spindel wiegt das Dreifache verglichen mit dem Fräsmotor, den ich vorher im Einsatz hatte.&lt;/li&gt;&#10;&lt;li&gt;Die komplexere Ansteuerung per Frequenzumrichter fügt eine Menge potentieller Fehlerquellen hinzu.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Zusammenfassend kann ich sagen, dass die Spindel durch ihre hohe Leistungsfähigkeit nicht so richtig zum Rest des Systems passt und ihre Vorteile dadurch nicht zur Gänze ausspielen kann. Ich hoffe, dass ich die Kaufentscheidung nicht bald bereuen werde.&lt;/p&gt;&#10;&lt;h2 id="automatischer-werkzeugwechsel"&gt;Automatischer Werkzeugwechsel?&lt;/h2&gt;&#10;&lt;p&gt;Hätte ich einen gut laufenden Fachbetrieb, z.B. zur Herstellung von Produkten aus Holz, dann wäre ein Werkzeugwechsler sicher eine gute Investition.&lt;/p&gt;&#10;&lt;p&gt;Für die Prototypenherstellung hingegen trifft &lt;a href="https://blog.schallbert.de/portal-milling/#tool-changer"&gt;meine Hypothese hier&lt;/a&gt; noch immer zu. Ich benötige eine Menge verschiedener Fräser, da ich mit verschiedenen Materialien, Durchmessern und Typen experimentiere. Allein die Kosten für genügend Steilkegel-Werkzeughalter allein würden die der Spindel bereits übersteigen.&lt;/p&gt;&#10;&lt;p&gt;Stattdessen habe ich mir eine weitere Spindelmutter besorgt. Somit kann ich die Spannzangen meist in den Muttern belassen, was ein paar weitere Sekunden pro Wechselvorgang einspart.&lt;/p&gt;&#10;&lt;h2 id="kosten-"&gt;Kosten 💶&lt;/h2&gt;&#10;&lt;p&gt;Ich habe noch einige andere Entscheidungen getroffen um meinen nur gelegentlichen Betrieb der Maschine als Nebenerwerb zu berücksichtigen sowie die Kosten gering zu halten. Bei meiner Spindelkonfiguration habe ich die Option mit reduzierter Leistung gewählt und die Spezifikationen der Lager nicht erhöht (obwohl ich für kleine Fräser schon die &lt;code&gt;50.000U/min&lt;/code&gt; gebrauchen könnte), so dass die Maschine auf &lt;code&gt;30kU/min&lt;/code&gt; begrenzt ist. Ich habe Luftkühlung der wassergekühlten Variante vorgezogen und nicht in ein System zur kontinuierlichen Temperaturüberwachung investiert.&lt;/p&gt;&#10;&lt;p&gt;Dennoch betragen die Gesamtkosten des Systems mehr als das Doppelte der Spindelkosten. Obwohl der FU nicht allzu teuer ist, summieren sich abgeschirmte Motorkabel, Steckverbinder, Drehmomentschlüssel, Netzfilter, Spannzangen/Muttern, der Schaltkasten und viele zusätzliche Kleinteile ganz schön auf.&lt;/p&gt;&#10;&lt;h2 id="informationsphase"&gt;Informationsphase&lt;/h2&gt;&#10;&lt;p&gt;Um die neue Spindel zum Laufen zu bringen, musste ich ein komplettes Subsystem planen, bauen und konfigurieren das Spindelantrieb, Kühlung und Hilfsstromversorgung umfasst. Aufgrund der Komplexität des Projekts habe ich meine Arbeit in verschiedenen, aufeinander aufbauenden Phasen durchgeführt. Ich begann mit der Informationsphase, in der ich mir einige Fragen beantworten musste, die ich im Folgenden erläutere.&lt;/p&gt;&#10;&lt;h3 id="antrieb-mit-variabler-frequenz"&gt;Antrieb mit variabler Frequenz&lt;/h3&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spindleupgrade/switchbox.jpg" alt="Bild: Schaltkasten mit FU" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Wie ich in &lt;a href="https://blog.schallbert.de/cnc-electronics/#vfd"&gt;diesem Artikel&lt;/a&gt; bereits schrieb, wird ein Frequenzumrichter zur Steuerung der Drehzahl einer HF-Spindel verwendet. Der Motor heißt so, weil er weit oberhalb der Netzfrequenz (in meinem Kontinent 50 Hz) arbeitet, um höhere Drehzahlen zu erreichen wie sie für meine Anwendungen oft erforderlich sind.&lt;/p&gt;&#10;&lt;p&gt;Um einige Fragen in der Tiefe beantworten zu können, telefonierte ich mit einem Experten von &lt;a href="https://industrial.omron.de/de/products/mx2" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Omron&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (Hersteller von Frequenzumrichtern), Herrn Wagner.&lt;/p&gt;&#10;&lt;h3 id="uf-kennlinie-oder-sensorlose-vektorregelung"&gt;U/f-Kennlinie&amp;quot; oder &amp;ldquo;sensorlose Vektorregelung&amp;rdquo;?&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Wird die U/f-Kennlinie gewählt, reduziert der Frequenzumrichter die Spannung zusammen mit der Frequenz, solange der Motor unterhalb seiner Eckfrequenz arbeitet (die bei meiner Maschine 400 Hz beträgt). Das liegt daran, dass die Induktivität der Ständerwicklung mit niedrigerer Frequenz abnimmt. Das wiederum erhöht den Stromfluss durch den Stator, und damit steigen auch Drehmoment und Verlustleistung. Durch die Anwendung der U/f-Kennlinie wird der Strom und damit das Drehmoment letztlich konstant gehalten. Bei sehr langsamen Drehzahlen nimmt die Induktivität im Vergleich zum Statorwiderstand jedoch so sehr ab, dass das Drehmoment überproportional abfällt. Daher ist diese Option für den Betrieb im unteren Drehzahlbereich eine schlechte Wahl. An oder oberhalb der Eckfrequenz des Motors hingegen bleibt die Spannung stabil und nur die Frequenz wird erhöht, was das Drehmoment verringert.&lt;/li&gt;&#10;&lt;li&gt;Bei der &amp;ldquo;sensorlosen Vektorregelung&amp;rdquo; verwendet der FU ein mathematisches Modell des Motors den er antreibt. So können optimale Schaltzeiten für die Treiber erreicht werden und damit kann unabhängig von der Motordrehzahl immer das Nenndrehmoment (oder sogar mehr) zur Verfügung gestellt werden. Dies ist besonders nützlich für Anwendungen, die einen Schweranlauf erfordern wie z. B. Aufzüge usw. Das Modell des Motors wird zumeist über eine automatische Kalibrierroutine ermittelt, wobei die Antriebsmaschine mit dem FU verbunden sein muss.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Hier ist ein Link zu einem &lt;a href="https://www.youtube.com/watch?v=qEM9VRSfXUo" target="_blank" rel="noopener noreferrer" class="external-link"&gt;youtube-Video&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, in dem die beiden Optionen bei niedrigen Drehzahlen verglichen werden. &lt;a href="https://www.youtube.com/watch?v=tWpChgsAIN4" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Ein weiteres Video&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zeigt, wie der FU bei großer Last sogar mehr als das Nenndrehmoment des Motors zur Verfügung stellen kann.&lt;/p&gt;&#10;&lt;p&gt;Da die meisten der von mir verwendeten Schaftfräser eine Drehzahl von &lt;code&gt;15000 U/min&lt;/code&gt; oder mehr erfordern, werde ich die Nachteile der U/f-Kennlinie wahrscheinlich nicht spüren. Außerdem ist bei meinem FU die Option &amp;ldquo;sensorlose Vektorregelung&amp;rdquo; nur bis &lt;code&gt;400 Hz&lt;/code&gt; verfügbar, so dass ich nur 80 % des Drehzahlbereichs der Spindel nutzen könnte. Daher entscheide ich mich für die U/f-Kennlinie.&lt;/p&gt;&#10;&lt;h3 id="auslauf-bremswiderstand-oder-energierückgewinnungssystem"&gt;Auslauf, Bremswiderstand oder Energierückgewinnungssystem?&lt;/h3&gt;&#10;&lt;p&gt;Zum Abbremsen des Motors kann der Frequenzumrichter mit verschiedenen Optionen konfiguriert werden. Wenn der Motor per Standardeinstellung im Freilauf ausläuft, kann es lange dauern, bis die Spindel bei voller Drehzahl zum Stillstand kommt.&lt;/p&gt;&#10;&lt;p&gt;Wenn ein Bremswiderstand konfiguriert wird wie ich es bei meiner Anwendung mache, benötigt das Abbremsen nur zwei Sekunden. Ich habe außerdem eine Gleichstrombremse konfiguriert, sodass die Welle vollständig zum Stillstand gebracht wird.&lt;/p&gt;&#10;&lt;p&gt;Für leistungsstärkere Maschinen oder Anwendungen, bei denen große Massen bewegt werden, kann ein Energierückgewinnungssystem sinnvoll sein. Es wird an den Zwischenkreis des FU angeschlossen und ist in der Lage, die durch die überschüssige Bewegungsenergie des Motors erzeugte Leistung ins Netz zurückzuspeisen. Um diese Funktion zu nutzen, ist ein zusätzliches Gerät erforderlich und die Kompatibilität des FU-Zwischenkreises mit diesem Einspeisewandler muss sichergestellt werden. Die hohen Systemkosten zahlen sich nur aus, wenn große Energiemengen zurückgewonnen werden können.&lt;/p&gt;&#10;&lt;h3 id="welche-modulationsfrequenz-bzw-taktfrequenz-sollte-ich-verwenden"&gt;Welche Modulationsfrequenz bzw. Taktfrequenz sollte ich verwenden?&lt;/h3&gt;&#10;&lt;p&gt;Die Takt-/Modulations-/Trägerfrequenz ist die Häufigkeit, mit der die Leistungstransistoren im FU schalten, um eine sinusähnliche Ausgangsspannung für den Motor zu liefern. Bei meinem FU kann sie in einem Bereich von &lt;code&gt;2-15kHz&lt;/code&gt; konfiguriert werden.&lt;/p&gt;&#10;&lt;p&gt;Um eine optimale Frequenz zu wählen, muss ein Gleichgewicht zwischen mehreren Faktoren gefunden werden:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Die Schaltverluste der Transistoren steigen bei höheren Frequenzen.&lt;/li&gt;&#10;&lt;li&gt;Die Durchlassverluste von Transistoren nehmen bei niedrigeren Frequenzen zu.&lt;/li&gt;&#10;&lt;li&gt;Das &lt;a href="https://en.wikipedia.org/wiki/Equal-loudness_contour" target="_blank" rel="noopener noreferrer" class="external-link"&gt;menschliche Ohr ist besonders empfindlich&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; im Bereich von &lt;code&gt;2-5kHz&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Resonanzfrequenzen des Motorsystems sollten vermieden werden.&lt;/li&gt;&#10;&lt;li&gt;Elektromagnetische Störaussendungen nehmen bei höheren Frequenzen zu.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Wenn der Motorhersteller eine Empfehlung für die Modulationsfrequenz abgibt, sollten diese natürlich befolgt werden. Ich habe mit verschiedenen Werten herumgespielt und mich für &lt;code&gt;5kHz&lt;/code&gt; als guten Kompromiss zwischen den oben genannten Faktoren entschieden. Ich hatte auch den Eindruck, dass dieser Wert bei meiner Maschine besser klang als 6 oder 4 kHz.&lt;/p&gt;&#10;&lt;h3 id="kann-ich-die-luftkühlung-in-abhängigkeit-von-der-spindeltemperatur-steuern"&gt;Kann ich die Luftkühlung in Abhängigkeit von der Spindeltemperatur steuern?&lt;/h3&gt;&#10;&lt;p&gt;Ja, das ist möglich. Alles, was dafür benötigt wird ist ein kontinuierlicher (nicht schaltender) Temperatursensor, z. B. ein &lt;code&gt;PT100&lt;/code&gt; oder ein anderes Thermoelement, ein Messwertgeber und ein freier Analogeingang am Frequenzumrichter. Dieser wird so konfiguriert, dass er über eine Fensterfunktion einen der freien digitalen Ausgänge ansteuert, der wiederum ein Relais schaltet, an das der Lüfter angeschlossen werden muss.&lt;/p&gt;&#10;&lt;p&gt;Meine Spindel ist leider mit einem schaltenden Thermistor ausgestattet, der über einen Temperaturalarm-Eingang mit dem FU verbunden ist. So kann er das System bei Überhitzung abschalten. Er ist aber nicht für eine kontinuierliche Temperaturüberwachung/-steuerung des Lüfters geeignet.&lt;/p&gt;&#10;&lt;h3 id="reicht-eine-spannung-von-18-v-aus-um-an-einem-24-v-digitaleingang-high-signalpegel-zu-erkennen"&gt;Reicht eine Spannung von 18 V aus, um an einem 24 V-Digitaleingang &amp;ldquo;High&amp;rdquo;-Signalpegel zu erkennen?&lt;/h3&gt;&#10;&lt;p&gt;Ja, bei meinem FU konnte ich eine niedrigere Spannung als die angestrebten 24 V an einen Logikeingang anlegen und trotzdem wurde der Pegel &lt;code&gt;HIGH&lt;/code&gt; erkannt.&lt;/p&gt;&#10;&lt;h3 id="kann-ich-den-logischen-masseanschluss-des-fu-ohne-probleme-an-die-masse-meiner-gleichstromversorgung-anschließen"&gt;Kann ich den logischen Masseanschluss des FU ohne Probleme an die Masse meiner Gleichstromversorgung anschließen?&lt;/h3&gt;&#10;&lt;p&gt;Ja. Muss man sogar. Der &amp;lsquo;Run&amp;rsquo;-Ausgang meiner CNC ist potenzialfrei. So musste ich die positive Steuerspannung des FU auf den Kontakt legen und dessen Ausgang mit dem &amp;lsquo;Run&amp;rsquo;-Eingang des FU verbinden. Der Stromkreis ist aber nur dann geschlossen, wenn im FU-Schaltschrank Logik-Masse und DC-Masse verbunden werden. Auch die analoge Ausgangsspannung meiner CNC zur Steuerung der Spindeldrehzahl muss dieselbe Erdungsreferenz wie der FU verwenden, also ist sie ebenfalls mit der logischen Masse des FU verbunden. Der Schaltplan zeigt diesen Sachverhalt an der &lt;a href="https://blog.schallbert.de/projects/spindle-upgrade/#die-planungsphase"&gt;Masseverbindung von T1&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3 id="was-muss-ich-hinsichtlich-der-elektromagnetischen-verträglichkeit-beachten"&gt;Was muss ich hinsichtlich der elektromagnetischen Verträglichkeit beachten?&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Netzfilter zwischen Netzschalter und FU verwenden (Das ist in Deutschland sogar Vorschrift).&lt;/li&gt;&#10;&lt;li&gt;Abgeschirmte Kabel für den angeschlossenen Motor verwenden.&lt;/li&gt;&#10;&lt;li&gt;Die Motoranschlussleitung so kurz wie möglich halten.&lt;/li&gt;&#10;&lt;li&gt;Müssen doch lange Leitungen verwenden (&amp;gt;10m), HF-Rauschfilter bzw. Drossel einbauen.&lt;/li&gt;&#10;&lt;li&gt;Abschirmung zusammen mit den Schutzerdungsleitungen des Motors an die Schutzerdungsplatte des FU anschließen.&lt;/li&gt;&#10;&lt;li&gt;Sicherstellen, dass die Abschirmung einen leitenden Kontakt zu den Kabelsteckern herstellt.&lt;/li&gt;&#10;&lt;li&gt;Durchgangsprüfung nach DIN VDE 0701-0702 durchführen.&lt;/li&gt;&#10;&lt;li&gt;Vergewissern, dass der Schaltkasten über eine zentrale Schutzerdungsklemme verfügt, an die jedes Gerät innerhalb des Schaltkastens angeschlossen wird.&lt;/li&gt;&#10;&lt;li&gt;Vergewissern, dass die Schutzerde der Netzversorgung ebenfalls an diese Klemme angeschlossen ist.&lt;/li&gt;&#10;&lt;li&gt;Bei der Verkabelung des Schaltkastens die Motorzuleitung so weit wie möglich von den Signalleitungen entfernt verlegen.&lt;/li&gt;&#10;&lt;li&gt;Zur Reduktion elektromagnetischer Störabstrahlung sind Schaltkastengehäuse aus Metall solchen aus Kunststoff vorzuziehen. Zumindest ist eine metallische Trägerplatte zu verwenden, auf der alle Komponenten montiert werden.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="spindel"&gt;Spindel&lt;/h3&gt;&#10;&lt;p&gt;Um die passende Spindel für Ihre Maschine und Anwendung auszuwählen, gibt es - unabhängig vom Motortyp - einige Punkte zu beachten.&lt;/p&gt;&#10;&lt;h3 id="leistung"&gt;Leistung&lt;/h3&gt;&#10;&lt;p&gt;Die Spindelleistung ist vielleicht weniger wichtig als gedacht. Hier mache ich ein paar Beispiele zur Orientierung.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;&amp;lt;=400W&lt;/code&gt; HF-Spindel oder &lt;code&gt;&amp;lt;=800W&lt;/code&gt; Oberfräsenmotor: Für eine Hobby-Maschine, die nicht unbedingt mit hohen Vorschüben betrieben werden kann. Bei schwerer zu schneidenden Materialien wie Aluminium oder HPL reicht eine geringe oder mittlere Schnitttiefe pro Durchgang aus. Der Maschinenrahmen wiegt weniger als 60 kg und ist für sein Gewicht relativ groß, z.B. bei einer Arbeitsfläche von &lt;code&gt;&amp;gt;0,5 m²&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;&amp;lt;=700W&lt;/code&gt; HF-Spindel oder &lt;code&gt;&amp;lt;=1200W&lt;/code&gt; Oberfräsenmotor: Die Maschine wiegt mehr oder ist steifer als die oben genannte Option. Häufig werden Fräser mit einem Durchmesser um die 6mm verwendet oder es muss viel mit Planfräsern gearbeitet werden. Die Z-Achse der Maschine ist kräftig ausgelegt und man hat das Gefühl, dass die Maschine bei &lt;code&gt;3000mm/min&lt;/code&gt;, &lt;code&gt;Z+6mm&lt;/code&gt; und einem 6mm Fräser locker mit Hartholz zurechtkommt.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;1,1kW/1,5kW&lt;/code&gt; HF-Spindel: Eine kompakte, schwerere Maschine (ca. 500x500mm Verfahrweg bei 100kg Gewicht), die Aluminium oder Holz mit höheren Vorschubgeschwindigkeiten schneiden soll. Bei der Bearbeitung von Holz verwenden Sie häufig Schruppfräser, um Zeit zu sparen. Die Maschine kann höhere Beschleunigungen und schnelle Bewegungen bewältigen (z.B. &lt;code&gt;1000mm/s²&lt;/code&gt; und &lt;code&gt;300mm/s&lt;/code&gt;).&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;&amp;gt;=2,2kW&lt;/code&gt;: Sie haben den Hobbybereich verlassen - wahrscheinlich für immer - sowohl mit Ihrer Maschine als auch mit dem Auftragsbestand, der sich auf Ihrem Schreibtisch angesammelt hat. Sie haben eine Werkstatt, eine professionelle Staubabsaugung und eine schwerere Maschine, der Sie zutrauen, den ganzen Tag lang ohne intensive Überwachung zu laufen. Vielleicht betrachten Sie diesen Blog als &amp;ldquo;nicht anspruchsvoll genug&amp;rdquo; und machen sich auf zu neuen Ufern, wo man Ihnen als Profi wirklich helfen kann.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="gewicht"&gt;Gewicht&lt;/h3&gt;&#10;&lt;p&gt;Das Gewicht hängt von der Leistung ab. Ein 800-Watt-Motor für eine Oberfräse kann einschließlich Halterung unter 2 kg wiegen, während eine 2,2-kW-HF-Spindel mit automatischem Werkzeugwechsel ohne Werkzeughalter 6,5 kg oder mehr auf die Waage bringt. Auch Halterung und Leitungen werden tendenziell größer.&#10;Ein hohes Spindelgewicht kann sich vor allem bei leichten Maschinen negativ auswirken. Im Extremfall kann sich die Z-Achse nach unten bewegen, wenn die Schrittmotortreiber inaktiv sind. Die Maschinenbeschleunigung, insbesondere an der Portalbewegungsachse, muss möglicherweise reduziert werden, damit keine Schritte verloren gehen. Es ist zu bedenken, dass schwerere Spindeln in der Regel voluminöser sind, so dass sich die Schnittkräfte aufgrund einer größeren Hebelwirkung ungünstig auf die Z-Achse auswirken können.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spindleupgrade/mafellvsspinogy.jpg" alt="Bild: Vergleich von Routermotor und HF-Spindel" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;h3 id="2-polige-oder-4-polige-ausführung"&gt;2-polige oder 4-polige Ausführung?&lt;/h3&gt;&#10;&lt;p&gt;Wird insbesondere bei niedrigen Drehzahlen ein hohes Drehmoment benötigt, sollten Sie sich für eine 4-polige Ausführung entscheiden. Dann wird allerdings die doppelte Frequenz benötigt, um die gleiche Drehzahl wie bei einem 2-poligen Motor zu erreichen.&#10;Wird die Maschine häufig mit hohen Drehzahlen betrieben, sollte stattdessen eine 2-polige Spindel Anwendung finden.&lt;/p&gt;&#10;&lt;h3 id="wasser--oder-luftgekühlt"&gt;Wasser- oder luftgekühlt?&lt;/h3&gt;&#10;&lt;p&gt;Auch dies ist eher eine Frage der Kosten und der Professionalität als alles andere. Natürlich funktioniert die Selbstkühlung durch einen auf der Welle fest montierten Lüfter gut. Eine Fremdkühlung per Lüfter ist etwas teurer, aber auch leiser. Ihr Hauptvorteil besteht darin, dass die Spindel unabhängig von ihrer Drehzahl nicht zu warm wird.&lt;/p&gt;&#10;&lt;p&gt;Eine Wasserkühlung führt die Wärme nochmal deutlich effizienter aus dem Motor ab, und die hohe Wärmekapazität des Wassers führt zu homogenen Temperaturen in der gesamten Spindel. Sie dämpft die Vibrationen und ist die leiseste Kühlungslösung von allen. Andererseits ist das System viel teurer, komplexer, benötigt mehr Platz für Pumpe, Kühler und Ausgleichsbehälter usw. und muss öfter gewartet werden.&lt;/p&gt;&#10;&lt;h2 id="die-planungsphase"&gt;Die Planungsphase&lt;/h2&gt;&#10;&lt;p&gt;Nachdem die Handbücher aller bestellten Teile überflogen waren, konnte ich einen schematischen Schaltplan für das Spindel-Subsystem zeichnen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spindleupgrade/vfd_circuit_plans_1.jpg" alt="Bild: Schaltplan FU-Subsystem" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Oben ist der Frequenzumrichter mit seinen Klemmen zu sehen. &lt;code&gt;X1&lt;/code&gt; ist der Netzstromeingang mit dem Trennschalter &lt;code&gt;S2&lt;/code&gt;. &lt;code&gt;X2&lt;/code&gt; verbindet die Spindel mit dem FU und &lt;code&gt;X3&lt;/code&gt; ist der Signalanschluss für die CNC. Rechts unten befindet sich die Gleichstromversorgung &lt;code&gt;T1&lt;/code&gt;, die die Luftkühlung versorgt und die logische Spannungsschiene bereitstellt. Die Temperatur des Spindelmotors &lt;code&gt;M1&lt;/code&gt; wird durch den Thermistor &lt;code&gt;R3&lt;/code&gt; überwacht (hier habe ich vergessen, die Beschriftung hinzuzufügen). Der Spindelkühlmotor &lt;code&gt;M2&lt;/code&gt; wird durch ein Zeitrelais gesteuert, das wiederum durch das &amp;ldquo;RUN&amp;rdquo;-Signal gesteuert wird. Ein digitaler Mehrzweckausgang ist mit einer roten LED &lt;code&gt;D1&lt;/code&gt; verbunden, die so konfiguriert ist, dass sie aufleuchtet, wenn eine Überlastwarnung vorliegt.&lt;/p&gt;&#10;&lt;p&gt;Der Schaltplan ist ziemlich einfach gehalten. Deshalb habe ich keine Zeit in saubere Zeichnungen investiert. Um es noch authentischer aussehen zu lassen, habe ich etwas ☕ auf das Papier verschüttet.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spindleupgrade/vfd_circuit_plans_3.jpg" alt="Bild: Anschlussbelegung der Spindel" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Dies ist die Pinbelegung des Spindelsteckers &lt;code&gt;X2&lt;/code&gt; und des entsprechenden Anschlusses am FU. Sie war mehrmals nützlich, als ich dem abgeschirmten Motorkabel (orangefarben, siehe Bannerbild oben) Steckverbinder hinzufügte sowie während der Geräteprüfung.&lt;/p&gt;&#10;&lt;p&gt;Ich notiere mir stets das erforderliche Anzugsdrehmoment für jede Schraube, einschließlich der für die Klemmen, und lege die benötigten Werkzeuge vor Baubeginn bereit.&lt;/p&gt;&#10;&lt;h2 id="die-bauphase"&gt;Die Bauphase&lt;/h2&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;iframe src="https://makertube.net/videos/embed/u8gDBQnsdxYFEZfFwk6jfU" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Schaltschrankbau für den Umrichter der Spindel&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="inbetriebnahme"&gt;Inbetriebnahme&lt;/h2&gt;&#10;&lt;p&gt;Vor Inbetriebnahme war der &lt;a href="https://blog.schallbert.de/testing-hardware/#test-plan"&gt;Testplan&lt;/a&gt; abzuarbeiten. Danach war der Spindel-Fettverteilungslauf gemäß Anweisungen des Herstellers zu starten.&#10;Schließlich kalibrierte ich den Analogausgang &amp;ldquo;0-10V&amp;rdquo; in der CNC-Software, um die Frequenz des Frequenzumrichters mit der Drehzahl der Spindel abzugleichen. Beispiel: &lt;code&gt;S10000&lt;/code&gt; sollte &lt;code&gt;3,33V&lt;/code&gt; am Ausgang erzeugen und den FU mit einer Frequenz von etwa &lt;code&gt;175Hz&lt;/code&gt; laufen lassen, um den Schlupf zu berücksichtigen.&lt;/p&gt;&#10;&lt;p&gt;Um zu sehen, dass das System wie vorgesehen funktioniert, habe ich ein paar Testläufe gemacht: 8mm PMMA, Vollnut, einmaliger Durchlauf in &lt;code&gt;Z+=8mm&lt;/code&gt;, &lt;code&gt;4mm&lt;/code&gt; Einschneider (poliert) Hartmetallfräser bei &lt;code&gt;S=26kUPM&lt;/code&gt; und &lt;code&gt;F=3000mm/min&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spindleupgrade/spindle_commisioning_initjob.jpg" alt="Bild: Spindel mit einem Werkstück aus PMMA" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="die-analysephase"&gt;Die Analysephase&lt;/h2&gt;&#10;&lt;p&gt;Diese Phase wird später folgen, vielleicht ein Jahr nach der Arbeit mit der neuen Spindel. Ich werde mir Folgendes ansehen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Hat sich die Investition gelohnt (etwa 8x teurer als ein Oberfräsenmotor)?&lt;/li&gt;&#10;&lt;li&gt;Habe ich die zusätzlichen Möglichkeiten (höhere Leistung und mehr Drehzahl) genutzt?&lt;/li&gt;&#10;&lt;li&gt;Erzielt sie spürbar bessere Ergebnisse?&lt;/li&gt;&#10;&lt;li&gt;Ist sie zuverlässiger und wartungsärmer als mein bisheriger Motor?&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Aber was ich schon jetzt sagen kann: Ich habe viel gelernt. Es hat Spaß gemacht, das System zu entwerfen, zu verkabeln und einzurichten. Und es war aufregend zu sehen, dass es tatsächlich so funktioniert, wie ich es mir vorgestellt habe 🙂.&lt;/p&gt;&#10;</description></item><item><title>Spirograph</title><link>https://blog.schallbert.de/projects/spirograph/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/spirograph/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/spirograph-thumb.jpg"&#10; class="post-cover"&#10; alt="Bild: Spirograph aus Acrylglas, gefertigt auf meiner CNC &amp;#39;Zerspanobert&amp;#39;"&#10; title="Spirograph" /&gt;&#10;&lt;h2 id="der-spirograph"&gt;Der Spirograph&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://de.wikipedia.org/wiki/Spirograph" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Spirographen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sind mathematische Spielzeuge. Mit ihrer Hilfe kann man ganz leicht sich wiederholende Kurven zeichnen. Die einfachste Version besteht aus einem Zahnrad, das auf der Innenseite eines feststehenden Zahnkranzes läuft. Das Zahnrad hat ein paar Löcher, in die die Spitze eines Stiftes passt. Die Löcher befinden sich außerhalb der Mitte des Zahnrads, so dass beim Drehen des Rads im Inneren des Hohlrads parabolische Formen entstehen.&lt;/p&gt;&#10;&lt;p&gt;Die Anzahl der Umdrehungen, die nötig sind um eine geschlossene Form zu zeichnen, hängt vom Verhältnis der Zahnanzahl zwischen den beiden Zahnrädern ab.&lt;/p&gt;&#10;&lt;h3 id="version-0"&gt;Version 0&lt;/h3&gt;&#10;&lt;p&gt;Der erste Entwurf haben wir gemeinsam mit den Jungs von CADasCAM &lt;a href="https://hobbyline.info/forum/index.php?thread/839-hobby-line-tage-27-28-5/&amp;amp;postID=9215#post9215" target="_blank" rel="noopener noreferrer" class="external-link"&gt;in der Werkhalle&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; erstellt. Es handelt sich um eine 14:9-Zahnkombination. Das kleinste gemeinsame Vielfache (auch KGV genannt) der beiden Zahnräder beträgt 126, was zu Zeichnungen mit vielen (&lt;code&gt;126 / 9 = 14&lt;/code&gt;) Kanten führt. Wir entschieden uns für Aluminium als Material.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/spirograph/20230528_sorotecworkshop_spirograph_aluminium.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Fräsen von Spirograph_V0 auf einer übertakteten CompactLine CNC&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Die Fertigung war erfolgreich und die Räder ließen sich von Zahn zu Zahn platzieren aber leider nicht drehen, weil der nächste Zahn des inneren Zahnrads nicht auf die Flanke, sondern auf den Kopf des Hohlradzahns traf. Die Krümmung des Hohlrads hatten wir nämlich nicht berücksichtigt, sodass die langen Zahnköpfe zu eng zusammen standen.&lt;/p&gt;&#10;&lt;h3 id="version-1"&gt;Version 1&lt;/h3&gt;&#10;&lt;p&gt;Als der Workshop vorbei war, nahm ich den Entwurf mit nach Hause und versuchte ihn zu verbessern. Ich kürzte die Zahnköpfe des Hohlrads, um das vorherige Problem zu beheben und fügte Verrundungen hinzu, damit die Zahnräder nicht so schwergängig ineinander griffen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/V1_upper.jpg" alt="Bild: Spirograph in Buche, Oberseite fertig gefräst" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich habe den Entwurf in &lt;code&gt;18mm&lt;/code&gt; Buche Leimholz gefräst. Ich verwende eine zweiseitige Bearbeitung, weil sich das Anfasen von Hand auf der Rückseite bei diesen komplexen Formen als etwas mühsam erwies. Dazu schneide ich nicht komplett durch das Material, sondern lasse unten eine &lt;code&gt;1mm&lt;/code&gt; dicke &amp;ldquo;Zwiebelhaut&amp;rdquo; stehen, die alles an ihrem Platz hält für den zweiten, rückseitigen Bearbeitungsvorgang.&lt;/p&gt;&#10;&lt;p&gt;Danach wird das Werkstück gedreht, sodass die Unterseite oben ist. So können die Kanten der Unterseite abgeschrägt werden. Dabei durchbohrt der Fräser die &amp;ldquo;Zwiebelhaut&amp;rdquo; und schneidet die Werkstücke praktisch aus dem Rohmaterial heraus, sodass ich sie nur noch vom Maschinenbett nehmen muss. So waren nur die Grate abzuschleifen, was weniger als fünf Minuten manuelle Nacharbeit erforderte.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/V1_detail.jpg" alt="Bild: Spirograph_V1 in Buche, Detailansicht der Zahnräder, die nicht passen wollten" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Aber es passte immer noch nicht. Diesmal, weil die Flanken des inneren Rades zu breit waren. Das hatte zur Folge, dass sie nicht die volle Eingriffstiefe zuließen was wiederum dazu führte, dass der nächste Zahn des Innenrads erneut auf den Zahnkopf des Zahnkranzes traf.&lt;/p&gt;&#10;&lt;h3 id="version-2"&gt;Version 2&lt;/h3&gt;&#10;&lt;p&gt;Ich habe herausgefunden, dass in Version_1 das kleine Zahnrad eine positiven &amp;ldquo;Zahnprofilverschiebung&amp;rdquo; hatte, während der Innenkranz auf Null belassen wurde. Das konnte überhaupt nicht funktionieren, also habe ich es korrigiert. Ich habe auch die konkaven Radien beider Räder größer geplant, sodass ein 6mm-Schaftfräser in der Lage sein würde, alle Details richtig herauszuarbeiten. Schließlich fügte ich etwas Spiel für die Zahnflanken hinzu und sorgte für mehr Platz am Zahnfuß, damit die Zähne besser ineinander greifen.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/V2_pmma.jpg" alt="Bild: Spirograph_V2 in PMMA, zusammen mit Beispielzeichnungen" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Um dem inneren Rad mehr Platz zu geben, habe ich die Zahnradkombination auf 16/7 verändert, was dann &lt;code&gt;112 / 7 = 16&lt;/code&gt; Schleifen zeichnen wird (noch engere Darstellung), bevor sich das Muster wiederholt.&lt;/p&gt;&#10;&lt;p&gt;Diesmal habe ich es aus 5mm extrudiertem Acryl (PMMA), opak weiß, gefertigt. Ich wählte eine ähnliche zweiseitige Frässtrategie wie oben beschrieben, fügte aber einen Schlichtlauf hinzu, um die Kantenqualität zu verbessern.&#10;Folgende Parameter kamen zur Verwendung:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Fräser, Material: PMMA (Acryl)&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Werkzeugtyp&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Zahn&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Durchmesser [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Drehzahl [U/min]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Vorschub [mm/min]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Z+ [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;XY+ [mm]&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;VHM-Habichtschnabel FSECO1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schruppen&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;24000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3,6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FSAC Acrylschneider poliert&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Schlichten&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;26000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hartmetall Kegel 90° FEF&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Fasen&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;15000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Und es funktionierte einwandfrei 😄. Die Schnittqualität war sehr gut. An manueller Nacharbeit blieb mir weniger als eine Minute Arbeit zum Entgraten.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&lt;video controls&gt;&#10; &lt;source src="https://blog.schallbert.de/assets/video/spirograph/spirograph_V2_teethengaged.mp4" type="video/mp4"&gt;&#10; Your browser does not support the video tag.&#10; &lt;/video&gt;&lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Aktualisierte Zahnparameter ermöglichen reibungslosen Eingriff&lt;/span&gt;&#10; &lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Aber es gab noch Raum für Verbesserungen:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Löcher direkt in den Zähnen machen keinen Sinn, weil sich das innere Rad bei Eingriff dieses Zahns nicht mehr durch den Stift weiterdrehen lässt&lt;/li&gt;&#10;&lt;li&gt;5mm Löcher sind etwas zu klein, um alle Arten von Buntstiften unterzubringen&lt;/li&gt;&#10;&lt;li&gt;16 Zähne &lt;code&gt;modul10&lt;/code&gt; sind nicht genug, um ein DIN A4-Papier zu füllen&lt;/li&gt;&#10;&lt;li&gt;Die elliptische Form sieht schön aus, ist aber etwas schwer festzuhalten&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Deshalb habe ich einen weiteren Schritt in der Entwicklung gemacht:&lt;/p&gt;&#10;&lt;h3 id="version-3"&gt;Version 3&lt;/h3&gt;&#10;&lt;p&gt;Eine kleine Aktualisierung gegenüber Version_2, dieses Mal mit einer 18/10-Paarung (KGV von 90, also 9 Schleifen pro Durchlauf). Ich habe die Lochabstände in Form einer &lt;a href="https://de.wikipedia.org/wiki/Fibonacci-Folge" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Fibonacci&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Spirale überarbeitet, um ausdrucksstärkere Designs zu erzeugen. Schließlich habe ich den Umriss an die Größe von DIN A4-Papier angepasst, so dass die Entwürfe nun zentriert sind.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/V3_design.jpg" alt="Bild: Spirograph_V3 Entwurf" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/V3_detail.jpg" alt="Bild: Spirograph_V3 in PMMA, Detailaufnahme eines Zahns" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Für die Herstellung habe ich dieselben Parameter wie bei der vorherigen Version verwendet. Wie gut das Ergebnis geworden ist! Ich frage mich, ob ich die letzten 5% an Qualität herausholen kann, indem ich die Grate ganz vermeide. Der Grat entsteht, wenn der konische Fräser die Unterseite des Materials durchsticht, um den endgültigen Ausschnitt auf meinem Vakuumtisch herzustellen. Ich muss also in Zukunft etwas &amp;ldquo;tiefer&amp;rdquo; fräsen als das verbliebene Material stark ist. Der Hersteller der Fräser empfahl mir im Nachgang, Vorschub und Drehzahl bei Arbeit in Acrylglas für die Fasenfräser zu senken, wenn der Zielvorschub wegen ständiger Beschleunigungs- und Abbremsvorgänge nicht erreicht werden kann.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/V3_chattermarks_acceleration.jpg" alt="Bild: Spirograph_V3, Kontur-Detailansicht" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Außerdem ist mir aufgefallen, dass die Maschine Rattermarken erzeugt, wenn sie bei &amp;ldquo;G01&amp;rdquo;-Bearbeitungen auf die Zielgeschwindigkeit beschleunigt. Ich vermute, dass dies auf die begrenzte Steifigkeit des Rahmens zurückzuführen ist. Es ist nur unter einem bestimmten Winkel sichtbar, aber nicht spürbar.&lt;/p&gt;&#10;&lt;p&gt;Das fertige Produkt sieht dann so aus. Durch die Fasen und abgerundeten Kanten liegt es sehr angenehm in der Hand. Die Zahnräder laufen mühelos und die 18/10-Zahnradkombination hat so viele Zähne, dass auch Kinder kein Problem damit haben, die Formen herzustellen (ausprobiert).&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/V3_pmma.jpg" alt="Bild: Spirograph_V3 in Acryl gefräst" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Und schließlich bin ich auch damit zufrieden, wie die Zeichnungen aussehen. Mit kräftigen Strichen und groß genug (15x15cm). Die Linien stehen nicht zu eng zusammen. Die höhere Anzahl von Löchern, die das größere innere Rad erlaubt, hilft auch dabei, eine größere Vielfalt an Figuren zu erzeugen.&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/spirograph/V3_drawing.jpg" alt="Bild: Zeichnungsbeispiel mit Spirograph_V3" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Ich habe die Versionen 2 sowie 3 einer nahegelegenen Schule sowie einem Kindergarten gespendet. Bin gespannt, wie sie im Alltag damit zurechtkommen.&lt;/p&gt;&#10;&lt;h2 id="lust-auf-einen-nachbau-bekommen"&gt;Lust auf einen Nachbau bekommen?&lt;/h2&gt;&#10;&lt;p&gt;Das ist kein Problem. &lt;a href="https://blog.schallbert.de/assets/docs/spirograph_V3_by_schallbert.zip"&gt;Hier steht ein &lt;code&gt;ZIP&lt;/code&gt;-Ordner zum Download bereit&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="was-ist-im-ordner-enthalten"&gt;Was ist im Ordner enthalten?&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://www.cadascam.com/de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;CADasCAM&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Projekt, das alle Arten von Zahnradpaarungskonfigurationen ermöglicht&lt;/li&gt;&#10;&lt;li&gt;Zeichnungsdateien (DXF) für das Fräsen von Vorder- und Rückseite&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.estlcam.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;EstlCAM&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Projektdateien zusammen mit meiner Werkzeugdatenbank&lt;/li&gt;&#10;&lt;li&gt;CNC-Betriebsdateien (&lt;code&gt;tap&lt;/code&gt;), die zu meiner Maschine passen&lt;/li&gt;&#10;&lt;li&gt;Ein Readme (&lt;code&gt;txt&lt;/code&gt;), das den XY-Nullpunkt und die erforderlichen Offsets definiert&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="kostet-es-etwas"&gt;Kostet es etwas?&lt;/h3&gt;&#10;&lt;p&gt;Nein. Beachten Sie, dass die von mir verwendete Lizenz das Paket zu &amp;ldquo;freier Software&amp;rdquo; macht. Bitte kontaktieren Sie mich jedoch, falls Sie die Entwürfe gewerblich verwenden möchten.&lt;/p&gt;&#10;&lt;h3 id="haftungsausschluss"&gt;Haftungsausschluss&lt;/h3&gt;&#10;&lt;p&gt;Wie üblich stehen die Dateien kostenlos zum Download zur Verfügung. Im Gegenzug übernehme ich keine Verantwortung oder Haftung für deren Inhalt.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;WARNUNG&lt;/strong&gt; ⚠️&lt;/p&gt;&#10;&lt;p&gt;Diese Dateien bieten oder ermöglichen die Ausgabe von G-Code-Anweisungen für echte Maschinen, die in der physischen Welt tatsächlich Dinge tun. Es könnten Code-Fehler oder Bugs enthalten sein, die möglicherweise zu Maschinenabstürzen oder Schlimmerem führen können. Ich übernehme keine Haftung für Arbeitsunfälle, Systemausfälle, Geräteausfälle, Produktionsausfälle, Ablaufstörungen oder andere negative Auswirkungen, die durch die von mir zur Verfügung gestellten Dateien verursacht werden könnten.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;HINWEIS&lt;/strong&gt; ℹ️&lt;/p&gt;&#10;&lt;p&gt;Es liegt in der Verantwortung des Maschinenbetreibers, den G-Code sorgfältig zu prüfen und sicherzustellen, dass er auf der jeweiligen Maschine wie vorgesehen funktioniert und keinen Schaden verursacht.&lt;/p&gt;&#10;&lt;h2 id="update"&gt;Update&lt;/h2&gt;&#10;&lt;p&gt;Ein paar weitere sehr schöne Spirographen habe ich Anfang 2024 für die Maker Faire angefertigt. Das Grundmaterial ist mitteldichte Faserplatte (MDF), auf welche ich eine Lage Olivenholzfurnier aufgebracht habe.&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-03-25-spirograph.jpg" alt="Image: Spirograph under test" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;</description></item><item><title>Tonuino - alternative Firmware</title><link>https://blog.schallbert.de/projects/tonuino/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/tonuino/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/tonuino-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Tonuino as a Bird&amp;#39;s house"&#10; title="Tonuino - alternative Firmware" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Difficulty: Experte 5/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: 0€&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~400h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="galerie"&gt;Galerie&lt;/h2&gt;&#10;&lt;section class="hugo-gallery"&gt;&#10; &lt;div class="hugo-gallery__frame"&gt;&#10; &lt;div class="hugo-gallery__grid" role="list"&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-0" aria-label="Image: NFC tags have been woven into little birds made of felt. They can be positioned inside the bird&amp;#39;s house to play music."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/birdshouse_front.jpg" alt="Image: NFC tags have been woven into little birds made of felt. They can be positioned inside the bird&amp;#39;s house to play music." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-1" aria-label="Image: birdshouse opened. Its contents: An RFID reader, MP3 player, speaker, charger socket, a powerbank, one multicolor-LED and the clickEncoder as human-machine interface."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/birdshouse_inside.jpg" alt="Image: birdshouse opened. Its contents: An RFID reader, MP3 player, speaker, charger socket, a powerbank, one multicolor-LED and the clickEncoder as human-machine interface." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-2" aria-label="Image: Guitar Amp design: All components are built in an old guitar amp model. Its battery, amplifier and speaker have been reused for Tonuino, and some buttons have been added to control the mp3 player. Cards are placed on top of the amp to be read and select content to be played."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/guitaramp_top.jpg" alt="Image: Guitar Amp design: All components are built in an old guitar amp model. Its battery, amplifier and speaker have been reused for Tonuino, and some buttons have been added to control the mp3 player. Cards are placed on top of the amp to be read and select content to be played." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;a class="hugo-gallery__thumb" href="#gallery-full-3" aria-label="Image: insides of the guitar amp: An Arduino nano controls all functions. A Bistable relay keeps the system alive as long as it plays music and switches off automatically when done to save battery power."&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/guitaramp_inside.jpg" alt="Image: insides of the guitar amp: An Arduino nano controls all functions. A Bistable relay keeps the system alive as long as it plays music and switches off automatically when done to save battery power." class="hugo-gallery__thumb-img" loading="lazy" /&gt;&#10; &lt;/a&gt;&lt;/div&gt;&lt;div class="hugo-gallery__caption"&gt;Die alternative Tonuino-Firmware im Einsatz.&lt;/div&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&#10; &lt;div class="hugo-gallery__full" aria-live="polite"&gt;&lt;figure id="gallery-full-0" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/birdshouse_front.jpg" alt="Image: NFC tags have been woven into little birds made of felt. They can be positioned inside the bird&amp;#39;s house to play music." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;NFC-Tags sind Vögel, die zum Abspielen ins Häuschen gesetzt werden&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-1" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/birdshouse_inside.jpg" alt="Image: birdshouse opened. Its contents: An RFID reader, MP3 player, speaker, charger socket, a powerbank, one multicolor-LED and the clickEncoder as human-machine interface." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Anwendungsbeispiel mit Microcontroller unter Verwendung meiner ClickEncoder-Bibliothek&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-2" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/guitaramp_top.jpg" alt="Image: Guitar Amp design: All components are built in an old guitar amp model. Its battery, amplifier and speaker have been reused for Tonuino, and some buttons have been added to control the mp3 player. Cards are placed on top of the amp to be read and select content to be played." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Anwendungsbeispiel mit Arduino in einem Gitarrenverstärker&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure id="gallery-full-3" class="hugo-gallery__figure"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/tonuino/guitaramp_inside.jpg" alt="Image: insides of the guitar amp: An Arduino nano controls all functions. A Bistable relay keeps the system alive as long as it plays music and switches off automatically when done to save battery power." class="hugo-gallery__full-img" /&gt;&lt;figcaption class="hugo-gallery__figcaption"&gt;Gesteuert wird der Player in diesem Fall mit drei Tasten.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;/section&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper right"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://raw.githubusercontent.com/isocpp/logos/64ef037049f87ac74875dbe72695e59118b52186/cpp_logo.svg" alt="Image: C&amp;#43;&amp;#43; Logo" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;In disem Projekt entstand eine Software für eine NFC-Tag gesteuerte Jukebox. Sie ist für das Arduino-Framework optimiert und verbindet die Hardware eines Mp3-Players (mit SD-Karte), ein NFC-Tag-Lesegerät und ein paar Bedienelemente.&lt;/p&gt;&#10;&lt;p&gt;Ein NFC-Tag wird mit einem Ordner auf der SD-Karte verknüpft, welcher Audiodateien enthält. Das Tag speichert auch Informationen über den gewünschten Wiedergabemodus für dieses Album, z. B. &amp;ldquo;Zufällig&amp;rdquo;. Sobald ein verknüpfter Tag in die Nähe der Jukebox gebracht wird, wird der konfigurierte Ordner im entsprechenden Wiedergabemodus abgespielt.&#10;Mit den Bedienelementen kann der Titel ausgewählt, die Lautstärke geändert und sogar durch ein Sprachmenü navigiert werden, was das Löschen, Verknüpfen und Konfigurieren eines Tags sowie Sperren/Entsperren der Eingabetasten ermöglicht.&lt;/p&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;Idee und fantastische Umsetzung hatte ich auf &lt;a href="https://www.voss.earth/tonuino" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Thorsten Voß&amp;rsquo; blog&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gesehen. Es begann mit der Funktionalität (&lt;a href="https://blog.schallbert.de/projects/encoder/"&gt;encoder support&lt;/a&gt;), die ich hinzufügen wollte. Leider war der ursprüngliche Code ein zusammenhängender Block mit mehreren tausend Zeilen Code und für mich schlechter Lesbarkeit, so dass es mir schwer fiel, meine Änderungen einzubringen.&lt;/p&gt;&#10;&lt;p&gt;Also habe ich den Code von Grund auf neu geschrieben, um eine bessere Struktur, Lesbarkeit, Wartbarkeit und Erweiterbarkeit zu erreichen. Parallel dazu las ich einige Bücher über objektorientiertes Design in &lt;code&gt;C++&lt;/code&gt;, &lt;a href="https://en.wikipedia.org/wiki/Robert_C._Martin" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Clean Code&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und &lt;a href="https://en.wikipedia.org/wiki/Software_design_pattern" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Software Design Patterns&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; und wandte einige davon dort an, wo ich sie für geeignet hielt. Auf diese Weise konnte ich sowohl die Modularität als auch die Lesbarkeit des Codes verbessern und habe außerdem gelernt, wie man Code in einem &amp;ldquo;größeren&amp;rdquo; Projekt schreibt.&lt;/p&gt;&#10;&lt;h2 id="schwierigkeiten"&gt;Schwierigkeiten&lt;/h2&gt;&#10;&lt;p&gt;Dies war mein erstes eigenes &lt;code&gt;C++&lt;/code&gt;-Projekt, und vielleicht war es für den Anfang ein wenig zu groß. All die für mich neuen Konzepte, das Schreiben an eine Schnittstelle, die erstmalige Verwendung von &lt;a href="https://platformio.org" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Platformio als IDE&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, die Verwendung des &lt;a href="https://google.github.io/googletest" target="_blank" rel="noopener noreferrer" class="external-link"&gt;googletest unit test framework&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, die Einsatz von Coding Patterns wie [factory](https://www. oodesign.com/factory-pattern.html) oder &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dependency Injection&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; kostete mich Abend um Abend über fast ein Jahr, bis ich dieses Projekt schließlich fertigstellen konnte. Und es gibt immer noch etwas zu überarbeiten und zu verbessern.&lt;/p&gt;&#10;&lt;h2 id="übersicht"&gt;Übersicht&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Unit-Test-Suite mit über 250 Testfällen&lt;/li&gt;&#10;&lt;li&gt;Serielle Debug-Ausgabe konfigurierbar&lt;/li&gt;&#10;&lt;li&gt;Lose gekoppelte, objektorientierte Architektur in &lt;code&gt;C++&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Einzelne Module mit klaren Aufgaben, skalierbar und leicht zugänglich für zukünftige Funktionen&lt;/li&gt;&#10;&lt;li&gt;benutzerdefinierte Hardware-Abstraktionsschicht; der größte Teil des Codes sollte ohne Änderungen auf andere MCUs portierbar sein&lt;/li&gt;&#10;&lt;li&gt;eigenes Dependency Injection Framework (Loader Klasse)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="funktionen"&gt;Funktionen&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Konfigurierbare Benutzereingabe (Tasten oder Encoder)&lt;/li&gt;&#10;&lt;li&gt;Auto-Poweroff, wenn für eine konfigurierbare Zeit keine Taste gedrückt wird&lt;/li&gt;&#10;&lt;li&gt;Einschalten durch Drücken der &amp;ldquo;Play&amp;rdquo;-Taste&lt;/li&gt;&#10;&lt;li&gt;Autoplay-Funktion&lt;/li&gt;&#10;&lt;li&gt;Status-Led-Funktion&lt;/li&gt;&#10;&lt;li&gt;Sperren/Entsperren der Benutzereingabe&lt;/li&gt;&#10;&lt;li&gt;Mehrere Wiedergabemodi [Album, Zufall, Speichern des Titelfortschritts, Nur ein Titel] pro Nfc-Tag verfügbar&lt;/li&gt;&#10;&lt;li&gt;Sprachmenüs zum Verknüpfen oder Löschen von Nfc-Tags&lt;/li&gt;&#10;&lt;li&gt;Optimiert für Batterieanwendungen (z.B. Powerbank) unter Verwendung von Ruhezuständen&lt;/li&gt;&#10;&lt;li&gt;Niedriger Stromverbrauch &lt;code&gt;@5V&lt;/code&gt;: &lt;code&gt;~40mA&lt;/code&gt; im Leerlauf, &lt;code&gt;~75mA&lt;/code&gt; bei mittlerer Lautstärke&lt;/li&gt;&#10;&lt;li&gt;Konfigurationsdatei für Startlautstärke, Einschlaf-Timer etc.&lt;/li&gt;&#10;&lt;li&gt;Automatische Wiederherstellung bei Eingabeaufforderung, die niemand bedient&lt;/li&gt;&#10;&lt;li&gt;Sprachansagen für die häufigsten Fehlermeldungen&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="nicht-enthaltene-funktionen"&gt;Nicht enthaltene Funktionen&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Keine Erkennung des Ladezustands der Powerbank&lt;/li&gt;&#10;&lt;li&gt;Kein Konfigurationsmenü (Startlautstärke, Dauer des Einschlaf-Timers, Standby-Dauer usw.)&lt;/li&gt;&#10;&lt;li&gt;&amp;hellip;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="dokumentation"&gt;Dokumentation&lt;/h2&gt;&#10;&lt;p&gt;Das Projekt ist für das Arduino Framework erstellt - getestet auf einem Arduino nano Board - unter Verwendung der &lt;a href="https://platformio.org" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PlatformIO IDE&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&#10;Die folgenden Abschnitte zeigen das Design.&lt;/p&gt;&#10;&lt;h3 id="projekt-modulübersicht"&gt;Projekt Modulübersicht&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Ordnername in &lt;code&gt;/lib&lt;/code&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Zweck&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Arduino&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;minimalistische hardwarenahe Implementierungen, nicht Unit-testbar&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Arduino_HardwareAbstraction&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hardware-Abstraktion, um Portabilität und Testen zu ermöglichen&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Config&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Systemkonfigurationsparameter&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Folder&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Geschäftslogik für Wiedergabeliste und Wiedergabemodus&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Loader&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Dependency Injection Framework&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MessageHandler&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Systemnachrichten und Debug-Framework&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Mp3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Mp3-Steuerung (Status, Ordner, Sprachausgabe, Anzeigen)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Nfc&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tag-Steuerung (Status, Lesen, Schreiben, Löschen)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;PowerManager&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Steuerung von Status-LED und Ruhezustand abhängig vom Systemstatus&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tonuino&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Haupt-Task-Scheduler&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;UserInput&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Verarbeitung von Tasten- oder Encoder-Eingaben&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Utilities&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Timer, Led-Steuerung, Pin-Steuerung&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;VoiceMenu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Geschäftslogik für das Menü Link / Löschen / Konfiguration&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="verwendete-externe-bibliotheken"&gt;Verwendete externe Bibliotheken&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://platformio.org/lib/show/131/TimerOne" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Arduino&amp;rsquo;s TimerOne&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://platformio.org/lib/show/7212/SoftwareSerial" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Arduino&amp;rsquo;s SoftwareSerial&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://platformio.org/lib/show/11808/ClickEncoder" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Schallbert&amp;rsquo;s ClickEncoder&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://platformio.org/lib/show/1561/DFPlayer%20Mini%20Mp3%20by%20Makuna" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Makuna&amp;rsquo;s DFMini Mp3&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://platformio.org/lib/show/63/MFRC522" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Miguel Balboa&amp;rsquo;s MFRC522&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Automatische Installation durch Platformios &amp;ldquo;Library Dependency Finder&amp;rdquo; beim erstem Kompiliervorgang&lt;/p&gt;&#10;&lt;h3 id="klassendiagramme"&gt;Klassendiagramme&lt;/h3&gt;&#10;&lt;p&gt;Klassendiagramme wären zu viel nicht-automatisierte Arbeit gewesen. Stattdessen habe ich das hier vorbereitet:&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/Schallbert/Tonuino_alternativeFirmware/master/docs/ProjectModulesOverview.png" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Software-Modulübersicht&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Sie sollte ein genaues Verständnis dafür vermitteln, wie die Softwaremodule zusammenwirken und welche APIs die Module anbieten.&lt;/p&gt;&#10;&lt;h2 id="los-gehts"&gt;Los geht&amp;rsquo;s!&lt;/h2&gt;&#10;&lt;p&gt;Zuerst muss mein &lt;a href="https://github.com/Schallbert/Tonuino_alternativeFirmware" target="_blank" rel="noopener noreferrer" class="external-link"&gt;repository&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, geklont werden - entweder per &lt;code&gt;git clone&lt;/code&gt;, Download als &lt;code&gt;zip&lt;/code&gt;-Datei oder per &lt;code&gt;https&lt;/code&gt;. Das &lt;code&gt;README.md&lt;/code&gt; Dokument bietet einen leichten Einstieg. Ich empfehle die Verwendung von PlatformIO für dieses Projekt.&#10;In den folgenden Abschnitten sind die Teilfunktionen des Programms erläutert, und wie deren korrekte Funktion sichergestellt wird.&lt;/p&gt;&#10;&lt;h2 id="testen"&gt;Testen&lt;/h2&gt;&#10;&lt;h3 id="unit-tests"&gt;Unit-Tests&lt;/h3&gt;&#10;&lt;p&gt;Unit-Tests wurden mit dem &lt;code&gt;gtest&lt;/code&gt; C++ Unit-Test-Framework geschrieben. Sie befinden sich im Ordner &lt;code&gt;test/desktop&lt;/code&gt;. Zu beachten ist, dass &lt;em&gt;googletest&lt;/em&gt; die Installation von &lt;code&gt;gcc&lt;/code&gt; mit einigen Bibliotheken voraussetzt. Die Anleitung dazu findet man &lt;a href="https://blog.schallbert.de/projects/platformio_gtestgmock/"&gt;in einem meiner anderen Projekte&lt;/a&gt;.&#10;Sobald diese Voraussetzungen erfüllt sind und ein einfacher &lt;code&gt;ASSERT_TRUE(false);&lt;/code&gt;-Test mit der gtest-Umgebung erwartungsgemäß fehlschlägt, können die Unit-Tests des Projekts erstellt und mit &lt;code&gt;pio test -e desktop -f desktop&lt;/code&gt;, in der PlatformIO CLI (Terminal) ausgeführt werden.&lt;/p&gt;&#10;&lt;h3 id="akzeptanztests"&gt;Akzeptanztests&lt;/h3&gt;&#10;&lt;p&gt;Obwohl diese Tests automatisiert werden könnten, ist es viel einfacher, diese Tests von Hand durchzuführen, nachdem der Mikrocontroller programmiert und alle elektronischen Komponenten zusammengesteckt wurden.&#10;Jede Zeile in den folgenden Tabellen ist ein eigenständiger Testfall. Die Testsuiten (&amp;ldquo;Überschrift Name&amp;rdquo;) haben eine &lt;strong&gt;System Pre:&lt;/strong&gt; Eigenschaft, die vor der Ausführung der einzelnen Testfälle wiederhergestellt werden muss. Wenn die Erwartungsklausel erfüllt ist, ist der Test &lt;strong&gt;BESTANDEN&lt;/strong&gt;. Ich habe die Tests auf Englisch belassen, weil sie recht eng mit dem Code korrespondieren.&lt;/p&gt;&#10;&lt;h4 id="switching-on"&gt;Switching ON&lt;/h4&gt;&#10;&lt;p&gt;System Pre: System is OFF&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Action&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Expectation&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LED flashes slowly?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Play/pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Welcome prompt plays?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h4 id="switching-off"&gt;Switching OFF&lt;/h4&gt;&#10;&lt;p&gt;System Pre: System is ON&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Action&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Expectation&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Playback on pause, no button input&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;System switches off after a time?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;System shutdown&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Farewell prompt plays?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;System shutdown&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LED switched off?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h4 id="play-help-prompt"&gt;Play Help Prompt&lt;/h4&gt;&#10;&lt;p&gt;System Pre: System is ON&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Action&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Expectation&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Long press Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Help prompt plays?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Help prompt playing&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Can be interrupted with any button press?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h4 id="behavior-without-tag"&gt;Behavior without Tag&lt;/h4&gt;&#10;&lt;p&gt;System Pre: System is ON, no Tag present&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Action&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Expectation&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;prompts &amp;ldquo;couldn&amp;rsquo;t find track&amp;rdquo; error?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Next button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;prompts &amp;ldquo;couldn&amp;rsquo;t find track&amp;rdquo; error?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Prev button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;prompts &amp;ldquo;couldn&amp;rsquo;t find track&amp;rdquo; error?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;doubleclick Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Delete Menu prompt played?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h4 id="behavior-with-tag"&gt;Behavior with Tag&lt;/h4&gt;&#10;&lt;p&gt;System Pre: System is ON, linked Tag available&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Precondition&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Action&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Expectation&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;no Tag placed&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;place known Tag&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;starts Playback of correct Folder?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Next button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;plays next track?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Next button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;next track in accordance with selected playMode of Folder?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;long press Next button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;increases volume?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Prev button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;plays previous track?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Prev button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;previous track in accordance with selected playMode of Folder?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;long press Prev button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;decreases volume?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;pauses track?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;paused playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;press Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;resumes track?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;doubleclick Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lock prompt played?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;doubleclick Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Playback resumes after Lock prompt played?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;active playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;doubleclick Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;locks button input?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;locked button input&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;doubleclick Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Unlock prompt played?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;locked button input&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;doubleclick Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Playback resumes after Unlock prompt played?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;locked button input&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;doubleclick Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;unlocks button input?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;paused playback&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;doubleclick Play/Pause button&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Playback resumes after Lock prompt played?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h4 id="behavior-with-unlinked-tag"&gt;Behavior with unlinked Tag&lt;/h4&gt;&#10;&lt;p&gt;System Pre: System is ON&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Action&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Expectation&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;place unlinked Tag&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;plays LinkMenu Prompt?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;navigate through LinkMenu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;plays Configuration Success Prompt?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;place Tag again&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;plays linked folder?&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="hardware"&gt;Hardware&lt;/h2&gt;&#10;&lt;h3 id="materialliste"&gt;Materialliste&lt;/h3&gt;&#10;&lt;p&gt;Ich habe versucht mit möglichst wenigen Einzelteilen auszukommen. Für ein Projekt dieser Größe ist die Liste denke ich recht kurz geblieben.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Menge&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Artikel&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Zweck&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Arduino (z.B. Nano)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gehirn&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Dfplayer Mini Mp3 Player&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Mund&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Micro SD Karte&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Speicher&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MFRC522 Nfc-Leser&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Psi-Sense&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Bistabiles Relais 5V, z.B. HFE20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kaffee - kein Schlaf&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Dioden z.B. 1n4007&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Venenklappe&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;PNP-Transistor z. B. BC327&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Kaffeemaschinenschalter&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Widerstände 1k&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Blutstromaufbereiter&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Widerstand 220&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Blinkstärke dimmen&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LED, Farbe nach Wahl&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;blinken&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Powerbank/ 5V Versorgung&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lebensmittel&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Drucktasten, z.B. Cherry MX Tasten&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Drucksensoren&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Lautsprecher, z.B. 5W@4Ohms&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Stimmbänder&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;USB-A-Stecker&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Strohhalm&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gehäuse&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Körper&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="zusätzliche-bauteile-und-tipps"&gt;Zusätzliche Bauteile und Tipps&lt;/h3&gt;&#10;&lt;p&gt;Überbrückungsdrähte, Platine, Buchsen und Verbrauchsmaterial nach eigenem Ermessen&#10;Wie beim &amp;ldquo;Original&amp;rdquo;-Projekt; alternativ einen Drehgeber anstelle der Drucktasten kaufen und das Projekt so konfigurieren, dass er statt der Taster ausgelesen wird.&#10;Da das System für den Batteriebetrieb optimiert ist, nehmen Sie ein bi-stabiles Relais oder einen JFET-Transistor (mit niedriger Gate-Spannung und niedrigem Source-Drain-Spannungsabfall) für die KeepAlive-Funktionalität, die den zusätzlichen Strom durch die ständig eingeschalteten Relaisspulen vermeidet. Eine billige Powerbank reicht für viele Stunden Betrieb. Alternativ können z.B. 3xAA-Batterien verwendet werden - allerdings macht der Dfmini-Player ordentlich Lärm, wenn die Spannung zu sehr einbricht. Er kann sogar bei wiederholten Neustarts durch Stromstöße zerstört werden, also Vorsicht bei nachlassender Akkuladung.&lt;/p&gt;&#10;&lt;h3 id="schaltplan"&gt;Schaltplan&lt;/h3&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://raw.githubusercontent.com/Schallbert/Tonuino_alternativeFirmware/master/docs/CircuitDiagram.jpg" alt="Stromlaufplan" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Über mich</title><link>https://blog.schallbert.de/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/about/</guid><description type="html">&#10;&lt;h2 id="sinn-und-zweck-dieser-website"&gt;Sinn und Zweck dieser Website&lt;/h2&gt;&#10;&lt;p&gt;Ich betreibe &lt;a href="https://blog.schallbert.de/projects/thissite/"&gt;meinen Weblog&lt;/a&gt; als Hobby auf freiwilliger Basis. Mit dieser kostenlosen Webpräsenz möchte ich Interesse an Technik wecken. Ich behandele hier Themen der Mechanik, Elektronik und Informationstechnik. Besondere Schwerpunkte lege ich auf die Fertigung von Dingen mittels CNC-Frästechnologien sowie auf Einrichtung, Betrieb, Deployment und Wartung meines Servers und seiner Inhalte. Gelegentliche Artikel über Elektronikentwicklung und IT-Security runden den Blog ab.&lt;/p&gt;&#10;&lt;p&gt;Wahrscheinlich bin ich selbst mein bester Kunde: Wie oft ich den Blog als Dokumentation für eigene Belange lese! Eigennützig sind zudem die Lernerfolge für mich selbst 😄&lt;/p&gt;&#10;&lt;h3 id="über-ki"&gt;Über &amp;ldquo;KI&amp;rdquo;&lt;/h3&gt;&#10;&lt;p&gt;Diese Website ist und bleibt frei vom Output von &amp;ldquo;KI&amp;rdquo;-Werkzeugen. Die Artikel sind handverfasst (Das merkt man denke ich auch) und ich habe nicht vor, dies zu ändern. Für Übersetzungen hingegen nehme ich &amp;ldquo;KI&amp;rdquo;-basierte Translators zur Hand. In den Anfangszeiten bis Q1-2023 betraf dies ausschließlich deutsche Artikel, da ich den Blog auf Englisch geführt hatte.&lt;/p&gt;&#10;&lt;p&gt;Danach jedoch schreibe ich auf Deutsch und lasse die Artikel auf Englisch übersetzen, weil sich die &amp;ldquo;KI&amp;rdquo; in diese Richtung viel leichter tut und meiner Meinung nach bessere Ergebnisse erzeugt. Dennoch halte ich bei jedem übersetzten Post Lektorat und gebe mir viel Mühe, qualitativ hochwertige und interessante Artikel zu veröffentlichen.&lt;/p&gt;&#10;&lt;p&gt;Auch die Recherche und alles drum herum passieren auf diesem Blog stets manuell.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Daher muss ich für jede Minute Lesezeit grob eine Stunde Arbeit investieren.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Wo ich &amp;ldquo;KI&amp;rdquo; allerdings nutze: Als &amp;ldquo;Rubber Ducky&amp;rdquo; für Ideen und Lösungsansätze, auf die ich bei fehlendem eigenen Horizont nicht kommen würde. Praktisch als erweiterte Suchmaschine. Aber auch von diesem Output kommt nichts in den Artikeln an.&lt;/p&gt;&#10;&lt;h2 id="wer-ist-schallbert"&gt;Wer ist Schallbert?&lt;/h2&gt;&#10;&lt;p&gt;&lt;div class="media-wrapper left"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/root/Schallbert.avif" alt="Image: A computer-drawn image of Schallbert, pixelized, and in 256 colors." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;Ich bin Schallbert&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&#10;Amateurbastler und Entwickler, Elektroniker für Betriebstechnik, Elektroingenieur. Ich arbeite seit über zehn Jahren in der Industrie sowohl als System- und Entwicklungsingenieur für Elektronik und Hardware wie auch als Softwareprojektleiter, -Entwickler und Scrum Master.&#10;Ich bin passionierter Hobby-Bassist.&lt;/p&gt;&#10;&lt;h3 id="woher-kommt-der-name"&gt;Woher kommt der Name?&lt;/h3&gt;&#10;&lt;p&gt;Schallbert ist eine Zusammensetzung aus den Worten &lt;code&gt;Schall&lt;/code&gt; (ein sich &lt;a href="https://de.wikipedia.org/wiki/Schall" target="_blank" rel="noopener noreferrer" class="external-link"&gt;im Raum ausbreitender Klang&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;) und &lt;code&gt;Bert&lt;/code&gt; (ein &lt;a href="https://de.wikipedia.org/wiki/Bert_%28Name%29" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Vorname&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;). Phonetisch wie &lt;em&gt;Albert&lt;/em&gt;, hatte ich ihn ursprünglich mal als Spielernamen gewählt: Er ist kurz und eingängig, als Nickname hinreichend selten und wenn man mag, kann eine Freude an Musik und Bewunderung für Albert Einstein hineingelesen werden.&lt;/p&gt;&#10;&lt;h3 id="kontakt"&gt;Kontakt&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;via Github Discussions: &lt;a href="https://github.com/Schallbert/schallbert.github.io/discussions/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;schallbert.github.io&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;auf meinem Kanal bei PeerTube: &lt;a href="https://makertube.net/c/cnc_corner/videos?s=1" target="_blank" rel="noopener noreferrer" class="external-link"&gt;CNC corner bei MakerTube&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;via PlatformIO (Bei µC-Themen): Ihr Konto &amp;ndash;&amp;gt; Nachrichten &amp;ndash;&amp;gt; Neue Nachricht &lt;a href="https://community.platformio.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PIO Community&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;via Sorotec Forum (Bei CNC-Themen): &lt;a href="https://hobbyline.info/forum/index.php?thread/603-blog-artikel-zur-portalfr%C3%A4se/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hobbyline.info&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;via CNCzone Forum (CNC-Themen): &lt;a href="https://www.cnczone.nl/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;cnczone.nl&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; -&amp;gt; Private Message&lt;/li&gt;&#10;&lt;li&gt;Direkter Kontakt (nicht bevorzugt): Siehe &lt;a href="https://blog.schallbert.de/legal/"&gt;Rechtliches&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Bild freundlicherweise gezeichnet und zur Verfügung gestellt von &lt;a href="https://github.com/Likosdev" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Likosdev&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Umzug von Github Pages</title><link>https://blog.schallbert.de/projects/move-blog-to-own-server/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/projects/move-blog-to-own-server/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/movetoownserver/giteawebdashboard-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Gitea Dashboard, served on my machine, proxied by caddy"&#10; title="Umzug von Github Pages" /&gt;&#10;&lt;h2 id="projekt-steckbrief"&gt;Projekt-Steckbrief&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Schwierigkeitsgrad: Mittel 3/5&lt;/li&gt;&#10;&lt;li&gt;Kosten: 5-50€/Monat&lt;/li&gt;&#10;&lt;li&gt;Zeitaufwand: ~10h&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;Ich habe mich entschieden, &lt;a href="https://blog.schallbert.de/projects/thissite/"&gt;meine Website&lt;/a&gt; von &lt;a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Github-Pages&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; auf meinen eigenen Server umzuziehen. Dies geschieht aus zwei Gründen: Zum einen möchte ich den Server in Deutschland stehen haben (Datenschutz) und zum anderen bietet mir Github Pages keine einfach zu handhabende Möglichkeit, meine Site zweisprachig anzubieten.&lt;/p&gt;&#10;&lt;p&gt;Zusätzlich kann ich ebenfalls ein paar lang gehegte Wünsche realisieren: Ein eigener mini-Dateiserver und eine Webseite für meinen kleinen Nebenerwerb werden jetzt möglich. Aufsetzen werde ich sie allerdings in einem späteren Schritt.&lt;/p&gt;&#10;&lt;h2 id="überblick"&gt;Überblick&lt;/h2&gt;&#10;&lt;p&gt;In diesem Projekt werde ich einen virtuellen Server anmieten und dort per &lt;code&gt;SSH&lt;/code&gt; das Container-Managementsystem &lt;a href="https://www.docker.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Docker&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; installieren.&lt;/p&gt;&#10;&lt;p&gt;In einem Container soll eine Instanz von &lt;a href="https://docs.gitea.com/next/installation/install-with-docker#startup" target="_blank" rel="noopener noreferrer" class="external-link"&gt;gitea&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; laufen, welche das Versionsmanagement für mich übernehmen und über sogenannte Actions eine CI/CD-Pipeline&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; bereitstellen wird.&lt;/p&gt;&#10;&lt;p&gt;Durch die Automation mit &lt;a href="https://docs.gitea.com/next/usage/actions/overview" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Gitea Actions&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; möchte ich dann in der Lage sein, die von &lt;code&gt;Jekyll&lt;/code&gt; per &lt;a href="https://docs.gitea.com/next/usage/actions/quickstart#use-actions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Workflow-Datei&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; gebaute Site auf einem &lt;code&gt;caddy&lt;/code&gt;-Server zu veröffentlichen, also in &amp;ldquo;Produktion&amp;rdquo; zu geben.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://caddyserver.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;caddy&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; stellt hier nicht nur den Server, sondern auch einen &lt;a href="https://en.wikipedia.org/wiki/Reverse_proxy" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Reverse Proxy&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Verfügung, welcher mir Portmapping und die Adresszuordnung abnehmen kann. Ganz nebenbei kümmert sich &lt;code&gt;caddy&lt;/code&gt; noch vollautomatisiert um die Zertifikatsverwaltung für &lt;code&gt;https&lt;/code&gt; über &lt;a href="https://letsencrypt.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Let&amp;rsquo;s Encrypt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Da auch caddy in Docker laufen wird, müssen virtuelle Netzwerke angelegt und korrekt konfiguriert werden, sodass die Container miteinander &amp;ldquo;reden&amp;rdquo; können.&lt;/p&gt;&#10;&lt;p&gt;Schreiten wir zur Tat - aber immer Schritt für Schritt.&lt;/p&gt;&#10;&lt;h2 id="konfiguration-meines-servers"&gt;Konfiguration meines Servers&lt;/h2&gt;&#10;&lt;p&gt;Die folgenden Abschnitte beschreiben Schritt für Schritt, wie ich vorgegangen bin, meinen Cloud-Server einzurichten und für das Veröffentlichen dieses Blogs vorzubereiten.&lt;/p&gt;&#10;&lt;h3 id="ssh-auf-dem-server-einrichten"&gt;SSH auf dem Server einrichten&lt;/h3&gt;&#10;&lt;p&gt;Direkt nachdem ich den sogenannten &amp;ldquo;Cloud-Server&amp;rdquo; angemietet hatte, habe ich den Zugriff per SSH konfiguriert und damit den Passwort-Login deaktiviert.&#10;Dafür prüfe ich zuerst, ob mein &lt;code&gt;ssh-agent&lt;/code&gt; bereits läuft bzw. starte ihn direkt mit dem Befehl &lt;code&gt;eval ssh-agent&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Sodann erstelle ich mir ein neues Schlüsselpaar per Befehl&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;ssh-keygen -t ed25519 -C &amp;quot;your_email@example.com&amp;quot; -f &amp;quot;~/.ssh/mein-cloud-server&amp;quot;&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Dieses Beispiel erzeugt Schlüsseldateien mit dem Verfahren (&lt;code&gt;-t&lt;/code&gt;) &lt;code&gt;ed25519&lt;/code&gt;, welche per Kommentar (&lt;code&gt;-C&lt;/code&gt;) eine Mailadresse enthalten und unter dem Dateinamen (&lt;code&gt;-f&lt;/code&gt;) &lt;code&gt;mein-cloud-server&lt;/code&gt; gespeichert werden.&lt;/p&gt;&#10;&lt;p&gt;Den öffentlichen Schlüssel &lt;code&gt;mein-cloud-server.pub&lt;/code&gt; habe ich dann im Webportal meines Servers unter &amp;ldquo;Security&amp;rdquo; eingegeben und den dann erhaltenen Fingerprint verifiziert mittels&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;ssh-keygen -lf &amp;lt;fingerprint_from_server&amp;gt; &amp;quot;~/.ssh/mein-cloud-server&amp;quot;&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;Anschließend habe ich die &lt;code&gt;config&lt;/code&gt;-Datei im &lt;code&gt;.ssh&lt;/code&gt;-Verzeichnis so angepasst, dass ich nur noch &lt;code&gt;ssh server&lt;/code&gt; statt der IP-Adresse eingeben muss, um mich drauf zu verbinden - Faulheit siegt 😀&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#.ssh/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Host server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;User root&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Hostname ip_address_of_server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PreferredAuthentications publickey&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;IdentityFile ~/.ssh/mein-cloud-server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Gebe ich nun &lt;code&gt;ssh server&lt;/code&gt; in die Konsole ein, so erscheint beim ersten Mal die Meldung, dass der Verbindungsaufbau zu einem unbekannten Partner mit Fingerprint soundso stattfinden wird und bittet um explizite Zustimmung. Nun kann der Fingerprint mit dem des Servers (auf der Webseite des Anbieters) abgeglichen und somit sichergestellt werden, dass man auch wirklich zu dem angestrebten, eigenen Server verbindet und nicht zu einem Dritten, der die Anfragen lediglich transparent weiterleitet. Stimmt man mit &lt;code&gt;yes&lt;/code&gt; zu, so wird der Fingerprint der &lt;code&gt;known_hosts&lt;/code&gt;-Datei hinzugefügt und das Zielsystem in Zukunft als bekannt angesehen.&lt;/p&gt;&#10;&lt;h2 id="docker-installieren-"&gt;Docker installieren 🐳&lt;/h2&gt;&#10;&lt;p&gt;Auf meinem Server läuft ein Standard-Ubuntu, für die die von Docker bereitgestellte &lt;a href="https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Installation des Docker-Repository per &lt;code&gt;apt&lt;/code&gt; reibungslos funktioniert. Einfach per SSH drauf einwählen und die entsprechenden Befehle ausführen - fertig. Dabei wird der von mir benutzte Dienst &lt;code&gt;docker-compose&lt;/code&gt; gleich mit installiert, welcher Konfigurationsdateien für die Zielcontainer interpretieren und die Container mit den entsprechenden Parametern gleich richtig hoch- und herunterfahren kann.&lt;/p&gt;&#10;&lt;h2 id="caddy"&gt;caddy installieren 🛒&lt;/h2&gt;&#10;&lt;p&gt;Zuerst lege ich den Ordner per &lt;code&gt;mkdir caddy2&lt;/code&gt; im Stammverzeichnis meines Servers an - soviel Zeit muss sein. Um caddy im Docker laufen lassen zu können, habe ich in diesem Ordner die folgende &lt;code&gt;docker-compose.yml&lt;/code&gt; erstellt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#caddy2/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;caddy&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;caddy:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;caddy&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;80:80&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;443:443&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;ACME_AGREE=true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./Caddyfile:/etc/caddy/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./caddy_data:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./caddy_config:/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./www:/www&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;caddy-proxy&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;caddy-proxy&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;external&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Diese Datei sagt Docker, dass es für den Service das aktuellste Image von &lt;code&gt;caddy&lt;/code&gt; verwenden und dem Container den Namen &lt;code&gt;caddy&lt;/code&gt; geben soll. Des Weiteren sollen die Ports &lt;code&gt;80&lt;/code&gt; (http) sowie &lt;code&gt;443&lt;/code&gt; (https) verfügbar gemacht und die Zertifikatsverwaltung per &lt;a href="https://www.howtogeek.com/devops/how-to-request-a-letsencrypt-certificate-using-acme/#installing-acme-sh" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;ACME_AGREE&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zugelassen werden. Der Container bindet die von Caddy benötigten Dateien (Caddyfile, data, config) ein und kann ebenso auf die von mir unter &lt;code&gt;/www&lt;/code&gt; abgelegten Dateien zum späteren Veröffentlichen meiner Website zugreifen. Der Container befindet sich im Netzwerk &lt;code&gt;caddy-proxy&lt;/code&gt; und durch &lt;code&gt;external: true&lt;/code&gt; weiß der Container, dass dieses Netzwerk unabhängig von diesem Container bereits besteht.&lt;/p&gt;&#10;&lt;p&gt;Damit wir den caddy-Container korrekt aufsetzen können, müssen wir also das Docker-Netzwerk vorher per &lt;code&gt;docker network create caddy-proxy&lt;/code&gt; erstellen. Mit &lt;code&gt;docker network ls&lt;/code&gt; kann man nun prüfen, ob das Netzwerk korrekt angelegt wurde.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;NETWORK ID NAME DRIVER SCOPE&#10;&amp;lt;redacted&amp;gt; bridge bridge local&#10;&amp;lt;redacted&amp;gt; caddy2_default bridge local&#10;&amp;lt;redacted&amp;gt; caddy-proxy bridge local&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sieht gut aus!&lt;/p&gt;&#10;&lt;p&gt;Anschließend erstelle ich eine &lt;code&gt;Caddyfile&lt;/code&gt; zur Konfiguration von Caddy:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# caddy2/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;blog.schallbert.de {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;root * /www/blog&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encode gzip&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;file_server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dieser Eintrag sagt caddy, dass der Inhalt des Ordners &lt;code&gt;/www/blog&lt;/code&gt; als Dateiserver, der die &lt;code&gt;gzip&lt;/code&gt;-Komprimierung unterstützt, unter &lt;a href="https://blog.schallbert.de/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;blog.schallbert.de&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; verfügbar machen soll.&lt;/p&gt;&#10;&lt;h3 id="test-website-erstellen-"&gt;Test-Website erstellen 🧪&lt;/h3&gt;&#10;&lt;p&gt;Ach ja, was auch noch wichtig ist, bevor wir den Container starten: Der DNS&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; sollte die Adresse auch auflösen können. Dafür logge ich mich in meinem DNS-Manager ein und ordne der Subdomain die feste IP-Adresse meines Servers zu:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;TYPE NAME VALUE TTL&#10;A blog.schallbert.de &amp;lt;redacted&amp;gt; 86400&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Schlussendlich soll der Server ja auch etwas anzeigen können. Eine Testseite genügt ja schon. Also ganz fix:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir ~/caddy2/www/blog&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ~/caddy2/www/blog&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nano index.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Und dort dann ein unverkennbares Hello World eintragen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&amp;lt;!-- blog/index.html --&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;h1&lt;/span&gt;&amp;gt;ALL YOUR BASE ARE BELONG TO US&amp;lt;/&lt;span style="color:#f92672"&gt;h1&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Speichern, fertig.&lt;/p&gt;&#10;&lt;p&gt;Das Stammverzeichnis von caddy sieht nun so aus:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;*caddy_config* *caddy_data* Caddyfile docker-compose.yml *www*&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jetzt endlich kann der Container erstellt und caddy darin hochgefahren werden:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die Option &lt;code&gt;-d&lt;/code&gt; bedeutet &amp;ldquo;detach&amp;rdquo;, also wird der Container hochgefahren und von der Konsole abgekoppelt, sodass sie nicht blockiert wird.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;[+] Running 1/1&#10; ✔ Container caddy Started&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Super, das sieht auch gut aus. Jetzt schnell mal auf die Seite navigieren:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/movetoownserver/address.jpg" alt="Image: caddy is displaying the test page correctly" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="gitea-installieren-"&gt;Gitea installieren 🍵&lt;/h2&gt;&#10;&lt;p&gt;Wie oben kurz angerissen, benötige ich Gitea für zwei Zwecke: Erstens möchte ich für meine Dateien wie von Github gewohnt ein Versionsmanagement haben und zweitens möchte ich meine Seite mit Hilfe von Gitea über Jekyll bauen und dann auf den Server schieben.&lt;/p&gt;&#10;&lt;p&gt;Wie schon bei caddy, beginnt alles in einem entsprechenden Ordner:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ~&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir gitea&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd gitea&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nano docker-compose.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die &lt;code&gt;docker-compose.yml&lt;/code&gt; für Gitea habe ich mir aus verschiedenen &lt;a href="https://docs.gitea.com/installation/install-with-docker" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Beispielen zusammengebastelt&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Hier ist sie:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gitea&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;external&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;caddy-proxy&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;external&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;caddy-proxy&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gitea&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea/gitea:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;USER_UID=1000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;USER_GID=1000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;SSH_DOMAIN=git.schallbert.de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_HOSTNAME=git.schallbert.de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;caddy-proxy&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./gitea:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./log:/app/gitea/log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/etc/timezone:/etc/timezone:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:3000:3000&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;222:22&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runner&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea/act_runner:nightly&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;CONFIG_FILE=/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_INSTANCE_URL=https://git.schallbert.de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_RUNNER_NAME=ichlaufe&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_RUNNER_REGISTRATION_TOKEN=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./runner/config.yml:/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./runner/data:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Die Bedeutung ist in etwa wie folgt: &lt;code&gt;gitea&lt;/code&gt; ist Teil der Docker-Netzwerke &lt;code&gt;gitea&lt;/code&gt; sowie &lt;code&gt;caddy-proxy&lt;/code&gt;. Der Container, in dem der Service läuft, heißt &lt;code&gt;gitea&lt;/code&gt; und wird bei Absturz stets automatisch neu gestartet. Die User-ID wird mit &lt;code&gt;1000&lt;/code&gt; in einem Wertebereich gesetzt, welcher weder für Administratoren noch andere besondere Benutzer reserviert ist. Per SSH und http ist gitea unter &lt;code&gt;git.schallbert.de&lt;/code&gt; erreichbar, und zwar auf Ports &lt;code&gt;22&lt;/code&gt; bzw. &lt;code&gt;3000&lt;/code&gt; - dort aber nur per localhost, also nicht &amp;ldquo;von außen&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Gitea hat Zugriff auf Zeitzone und Lokalzeit des Servers, außerdem legt es Logs unter &lt;code&gt;/log&lt;/code&gt; und Dateien unter &lt;code&gt;/data&lt;/code&gt; ab. Gitea verfügt über einen &lt;code&gt;action runner&lt;/code&gt;, der auf der gitea-Instanz läuft, &lt;code&gt;ichlaufe&lt;/code&gt; heißt und dessen Konfiguration in der &lt;code&gt;config.yml&lt;/code&gt; zu finden ist. Der Runner kann im Docker über dessen &lt;a href="https://stackoverflow.com/questions/35110146/what-is-the-purpose-of-the-file-docker-sock" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Standard-Socket&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Aktionen ausführen, z.B. &lt;code&gt;ubuntu-latest&lt;/code&gt; installieren und darauf &lt;code&gt;Jekyll&lt;/code&gt; zum Laufen bringen. Die Basiskonfiguration des Runners habe ich &lt;a href="https://gitea.com/gitea/act_runner/src/branch/main/examples/docker-compose" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dem offiziellen Repository&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; entnommen.&lt;/p&gt;&#10;&lt;p&gt;Gitea akzeptiert nur Runner, deren Token unter &lt;code&gt;GITEA_RUNNER_REGISTRATION_TOKEN&lt;/code&gt; registriert sind - Dies holen wir zu einem späteren Zeitpunkt nach.&lt;/p&gt;&#10;&lt;p&gt;Nun starten wir Gitea mal, sodass wir die erforderlichen Ordner zur Konfiguration erstellt bekommen.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose up&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So sieht die Konsolenausgabe bei mir aus:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;[+] Running 4/2&#10; ✔ Network gitea_default Created 0.0s &#10; ✔ Network gitea_gitea Created 0.1s &#10; ✔ Container gitea Created 0.1s &#10; ✔ Container gitea-runner-1 Created 0.0s &#10;Attaching to gitea, gitea-runner-1&#10;gitea | Server listening on :: port 22.&#10;gitea | Server listening on 0.0.0.0 port 22.&#10;gitea | 2023/11/18 17:19:52 cmd/web.go:242:runWeb() [I] Starting Gitea on PID: 15&#10;gitea | 2023/11/18 17:19:52 cmd/web.go:111:showWebStartupMessage() [I] Gitea version: 1.21.0 built with GNU Make 4.4.1, go1.21.4 : bindata, timetzdata, sqlite, sqlite_unlock_notify&#10;gitea | 2023/11/18 17:19:52 cmd/web.go:112:showWebStartupMessage() [I] * RunMode: prod&#10;gitea | 2023/11/18 17:19:52 cmd/web.go:113:showWebStartupMessage() [I] * AppPath: /usr/local/bin/gitea&#10;gitea | 2023/11/18 17:19:52 cmd/web.go:114:showWebStartupMessage() [I] * WorkPath: /data/gitea&#10;gitea | 2023/11/18 17:19:52 cmd/web.go:115:showWebStartupMessage() [I] * CustomPath: /data/gitea&#10;gitea | 2023/11/18 17:19:52 cmd/web.go:116:showWebStartupMessage() [I] * ConfigFile: /data/gitea/conf/app.ini&#10;gitea | 2023/11/18 17:19:52 cmd/web.go:117:showWebStartupMessage() [I] Prepare to run web server&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="web-access"&gt;Webzugriff einrichten&lt;/h3&gt;&#10;&lt;p&gt;Nun gehe ich erneut in meinen DNS-Manager und setze die Adresse für den Webzugriff:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-log" data-lang="log"&gt;TYPE NAME VALUE TTL&#10;A git.schallbert.de &amp;lt;redacted&amp;gt; 86400&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Außerdem muss die &lt;code&gt;Caddyfile&lt;/code&gt; angepasst werden, damit Anfragen aus dem Web auch richtig weitergeleitet werden können. Also zurück in den &lt;code&gt;caddy2&lt;/code&gt; Ordner und mit &lt;code&gt;nano Caddyfile&lt;/code&gt; folgenden Abschnitt hinzufügen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# caddy2/Caddyfile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;git.schallbert.de {&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;reverse_proxy * http://gitea:3000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nun werden Anfragen nach &lt;code&gt;git.schallbert.de&lt;/code&gt; auf den im Docker geöffneten Port &lt;code&gt;3000&lt;/code&gt; des Gitea-Containers weitergeleitet.&lt;/p&gt;&#10;&lt;h3 id="gitea-per-web-oberfläche-konfigurieren"&gt;Gitea per Web-Oberfläche konfigurieren&lt;/h3&gt;&#10;&lt;p&gt;Navigiere ich im Browser jetzt auf die entsprechende Adresse, so bekomme ich ein Login-Fenster angezeigt - genau wie in &lt;a href="https://gitea.com/user/login" target="_blank" rel="noopener noreferrer" class="external-link"&gt;dieser Demo&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Nun denke ich mir ein schönes Passwort aus, klicke auf &lt;code&gt;Register&lt;/code&gt; und bekomme hernach die Möglichkeit, Gitea per Weboberfläche zu konfigurieren.&#10;Dies tue ich aber nur rudimentär, denn ich möchte am Ende ja meine eigene Konfiguration verwenden.&lt;/p&gt;&#10;&lt;p&gt;Anschließend erstelle ich wie im Kapitel &lt;a href="https://blog.schallbert.de/projects/move-blog-to-own-server/#ssh-auf-dem-server-einrichten"&gt;SSH einrichten&lt;/a&gt; oben beschrieben ein weiteres SSH-Schlüsselpaar per &lt;code&gt;ssh-keygen&lt;/code&gt; und passendem Dateinamen &lt;code&gt;-f ~/.ssh/gitea&lt;/code&gt;. Diesen lade ich dann auf Gitea unter &lt;code&gt;Settings-&amp;gt;SSH/GPG Keys&lt;/code&gt; hoch und verifiziere ihn.&lt;/p&gt;&#10;&lt;p&gt;Nun kann ich auf meinem Rechner erneut in der &lt;code&gt;config&lt;/code&gt;-Datei des &lt;code&gt;.ssh&lt;/code&gt;-Verzeichnisses den Abschnitt zum Login auf Gitea erstellen:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# .ssh/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Host gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;User schallbert&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Hostname git.schallbert.de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PreferredAuthentications publickey&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Identityfile ~/.ssh/schallbert_gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Jetzt schnell prüfen, ob auch alles funktioniert:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh gitea&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host &lt;span style="color:#e6db74"&gt;&amp;#39;git.schallbert.de&amp;#39;&lt;/span&gt; is known and matches the ED25519 host key.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;debug1: Found key in /home/schallbert/.ssh/known_hosts&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Perfekt.&lt;/p&gt;&#10;&lt;h3 id="gitea-konfiguration-per-appini-fertigstellen"&gt;Gitea-Konfiguration per &lt;code&gt;app.ini&lt;/code&gt; fertigstellen&lt;/h3&gt;&#10;&lt;p&gt;Da die Konfiguration aber noch nicht abgeschlossen ist, fahre ich den Container nun wieder herunter:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose down&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;und navigiere nach &lt;code&gt;/gitea/gitea/conf&lt;/code&gt;. Dort öffne ich die &lt;code&gt;app.ini&lt;/code&gt; und modifiziere sie wie folgt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# conf/app.ini&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;APP_NAME&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Gitea: Git with a cup of tea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;RUN_MODE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;prod&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;RUN_USER&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;git&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;WORK_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[repository]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ROOT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/git/repositories&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DEFAULT_BRANCH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;main&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[repository.local]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;LOCAL_COPY_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/tmp/local-repo&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[repository.upload]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;TEMP_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/uploads&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[server]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;APP_DATA_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DOMAIN&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;git.schallbert.de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;SSH_DOMAIN&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;git.schallbert.de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;HTTP_PORT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;3000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ROOT_URL&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;https://git.schallbert.de/&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DISABLE_SSH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;SSH_PORT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;222&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;SSH_LISTEN_PORT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;22&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;LFS_START_SERVER&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;LFS_JWT_SECRET&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;lt;redacted&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;OFFLINE_MODE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[database]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/gitea.db&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DB_TYPE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;sqlite3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;HOST&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;localhost:3306&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;NAME&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;gitea&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;USER&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;root&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PASSWD&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;LOG_SQL&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;SCHEMA&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;SSL_MODE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;disable&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[indexer]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ISSUE_INDEXER_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/indexers/issues.bleve&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[session]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PROVIDER_CONFIG&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/sessions&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PROVIDER&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[picture]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;AVATAR_UPLOAD_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/avatars&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;REPOSITORY_AVATAR_UPLOAD_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/repo-avatars&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[attachment]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/attachments&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[log]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;MODE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;LEVEL&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;info&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ROOT_PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/gitea/log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[security]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;INSTALL_LOCK&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;SECRET_KEY&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;REVERSE_PROXY_LIMIT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;REVERSE_PROXY_TRUSTED_PROXIES&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;*&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;INTERNAL_TOKEN&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;lt;redacted&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PASSWORD_HASH_ALGO&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;pbkdf2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[service]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DISABLE_REGISTRATION&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;REQUIRE_SIGNIN_VIEW&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;REGISTER_EMAIL_CONFIRM&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLE_NOTIFY_MAIL&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ALLOW_ONLY_EXTERNAL_REGISTRATION&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLE_CAPTCHA&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DEFAULT_KEEP_EMAIL_PRIVATE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DEFAULT_ALLOW_CREATE_ORGANIZATION&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DEFAULT_ENABLE_TIMETRACKING&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;NO_REPLY_ADDRESS&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;noreply.localhost&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[lfs]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;PATH&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/data/git/lfs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[mailer]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLED&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[openid]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLE_OPENID_SIGNIN&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLE_OPENID_SIGNUP&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[cron.update_checker]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLED&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[repository.pull-request]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DEFAULT_MERGE_STYLE&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;merge&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[repository.signing]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;DEFAULT_TRUST_MODEL&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;committer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[oauth2]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;JWT_SECRET&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;lt;redacted&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;[actions]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ENABLED&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hier ist wichtig zu sicherzustellen, dass &lt;code&gt;ROOT_URL&lt;/code&gt; und die Portkonfiguration mit jener der &lt;code&gt;docker-compose.yml&lt;/code&gt; übereinstimmt. Die Datenbank (sqlite3) wird konfiguriert und gitea zugänglich gemacht. Außerdem deaktiviere ich aus Sicherheitsgründen die Registrierungsmöglichkeit neuer Benutzer &lt;code&gt;DISABLE_REGISTRATION = true&lt;/code&gt; sowie jene per Openid &lt;code&gt;ENABLE_OPENID_SIGNIN = false ENABLE_OPENID_SIGNUP = false&lt;/code&gt; und schalte Gitea Actions per &lt;code&gt;ENABLED = true&lt;/code&gt; ein.&lt;/p&gt;&#10;&lt;p&gt;Nun kann der Container neu gestartet werden:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Running 4/4&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ✔ Network gitea_default Created 0.1s &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ✔ Network gitea_gitea Created 0.0s &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ✔ Container gitea Started 0.0s &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; x Container gitea-runner-1 Error 0.1s &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Gut, Gitea läuft schonmal. In einem letzten Schritt kümmere ich mich nun um den Action Runner.&lt;/p&gt;&#10;&lt;h3 id="action-runner-einrichten-"&gt;Action Runner einrichten 🏃&lt;/h3&gt;&#10;&lt;p&gt;Für die ersten Schritte hilft die offizielle &lt;a href="https://docs.gitea.com/usage/actions/act-runner" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Anleitung&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; weiter.&lt;/p&gt;&#10;&lt;p&gt;Ich logge mich erneut auf der Weboberfläche über &lt;code&gt;git.schallbert.de&lt;/code&gt; ein. Die Aktivierung von Actions in der &lt;code&gt;app.ini&lt;/code&gt; im vorigen Abschnitt lässt jetzt bei &lt;code&gt;Settings-&amp;gt;Actions&lt;/code&gt; ein neues Menü erscheinen.&#10;Hier klicke ich nun auf &lt;code&gt;Create new Runner&lt;/code&gt; und kopiere mir das &lt;code&gt;REGISTRATION TOKEN&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Anschließend öffne ich die &lt;code&gt;docker-compose.yml&lt;/code&gt; im Gitea-Stammverzeichnis erneut und füge wie &lt;a href="https://docs.gitea.com/usage/actions/act-runner#set-up-the-runner-using-docker-compose" target="_blank" rel="noopener noreferrer" class="external-link"&gt;hier vorgeschlagen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; Folgendes ein:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# gitea/docker-compose.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;runner&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitea/act_runner:nightly&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;CONFIG_FILE=/config.yml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_INSTANCE_URL=https://git.schallbert.de&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_RUNNER_NAME=ichlaufe&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GITEA_RUNNER_REGISTRATION_TOKEN= &amp;lt;redacted&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nach einem Neustart des Containers mit &lt;code&gt;docker compose down &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt; erscheint nun der Runner als &amp;ldquo;gestartet&amp;rdquo; im Log:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✔ Network gitea_default Created 0.1s &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✔ Network gitea_gitea Created 0.0s &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✔ Container gitea Started 0.0s &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✔ Container gitea-runner-1 Started 0.1s &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hervorragend, jetzt schauen wir auf der Weboberfläche nach und siehe da:&lt;/p&gt;&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/movetoownserver/actionrunner.jpg" alt="Image: Gitea action runner is up and - running." /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Fehlt noch der Beweis, dass der Runner auch wirklich arbeitet.&lt;/p&gt;&#10;&lt;h3 id="funktionsprüfung-runner"&gt;Funktionsprüfung Runner&lt;/h3&gt;&#10;&lt;p&gt;Für die Prüfung des Action Runner nehme ich erneut die &lt;a href="https://blog.gitea.com/hacking-on-gitea-actions/#use-actions" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Dokumentation von Gitea&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; zur Hilfe.&#10;Ich erstelle ein neues Repository auf der Gitea-Weboberfläche mit Namen &lt;code&gt;runner-test&lt;/code&gt; und füge den Inhalt der oben verlinkten Datei im Verzeichnis &lt;code&gt;/.gitea/workflows/01-test.yml&lt;/code&gt; hinzu.&lt;/p&gt;&#10;&lt;p&gt;Commit, push, und - nichts.&lt;/p&gt;&#10;&lt;p&gt;Ah, der Workflow startet nur &lt;code&gt;on: [push]&lt;/code&gt;. Er kann also gar nicht starten, wenn er gerade selbst erst auf den Server geladen wurde.&lt;/p&gt;&#10;&lt;p&gt;Gut, erstelle ich halt noch eine Datei, commit, push. Ergebnis:&#10;&lt;div class="media-wrapper center"&gt;&#10; &lt;figure class="media-frame"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/movetoownserver/actionsuccess.jpg" alt="Image: Gitea action runner demo passes integration" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&lt;/p&gt;&#10;&lt;p&gt;Wahnsinn! 🥳&lt;/p&gt;&#10;&lt;h2 id="zwischenfazit"&gt;Zwischenfazit&lt;/h2&gt;&#10;&lt;p&gt;Ich habe hier so getan, als wäre der Weg in einer geraden Linie verlaufen und als hätte alles reibungslos geklappt. Leider traf das so nicht zu. Es war zwar überraschend einfach, die Programme in Docker zum Laufen zu bringen, doch miteinander reden wollten sie lange nicht. Ich hatte zudem ziemliche Probleme mit der &lt;code&gt;Caddyfile&lt;/code&gt;&#10;So einfach sie auch erscheinen mag, lange wollte so gar nichts in meinem Browser erscheinen - weder Gitea noch die blog-Testseite. Ein Ping an die Seite vom Terminal des Servers aus funktionierte aber, also konnte ich den Fehler schnell auf die Konfiguration von caddy zurückführen.&lt;/p&gt;&#10;&lt;p&gt;Die Ursache: Da caddy im Docker läuft, muss die Caddyfile als Volume eingebunden werden. Im Caddyfile selber muss der Ordner vom Root des Volumes aus, also mit führendem &lt;code&gt;/&lt;/code&gt;, angegeben werden.&lt;/p&gt;&#10;&lt;p&gt;Auf der anderen Seite hat die Konfiguration von Gitea viel reibungsloser geklappt, als ich dachte. Sogar das aufwändige Freischalten des Runners, welcher auch im Docker nochmal extra geroutet werden muss, war weniger schlimm als ich dachte.&lt;/p&gt;&#10;&lt;p&gt;Mit diesem Grundgerüst habe ich jetzt einen flinken Server, welcher meinen Blog später bestimmt reibungslos hosten kann.&lt;/p&gt;&#10;&lt;p&gt;Die nächsten Schritte - das Aufsetzen des Bauprozesses für meine Website - werden &lt;a href="https://blog.schallbert.de/gitea-action-runner-native-jekyll/"&gt;in diesem Artikel hier&lt;/a&gt; und dann auch in Jekyll nativ &lt;a href="https://blog.schallbert.de/gitea-action-runner-jekyll-dockerimage/"&gt;hier&lt;/a&gt; abgehandelt.&lt;/p&gt;&#10;&lt;h2 id="update-server-architektur-im-januar-2024"&gt;Update: Server-Architektur im Januar 2024&lt;/h2&gt;&#10;&lt;p&gt;Ich habe den Server inzwischen mehrfach um Peripheriefunktionen erweitert. So habe ich mich um &lt;a href="https://blog.schallbert.de/server-protection/"&gt;Sicherheit und Backups&lt;/a&gt; ebenso gekümmert wie um &lt;a href="https://blog.schallbert.de/server-auto-upgrade/"&gt;automatische Updates&lt;/a&gt;, wobei mir die Kapselung durch Docker nicht nur einmal auf die Füße gefallen ist und einiges an Zeit gekostet hat. Auch wenn noch immer nicht alle Aspekte vollständig abgedeckt sind (Benachrichtigungen sind z.B noch ein offenes Thema), komme ich meinem Ziel, einen weitgehend selbstständig laufendes System zu schaffen, spürbar näher.&lt;/p&gt;&#10;&lt;p&gt;Am Ende möchte ich schließlich wieder dahin zurückkehren, Inhalte zu erzeugen. Und mich nicht mehr als nötig mit dem Drumherum beschäftigen.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;CI/CD steht für &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Continuous_integration" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Continuous Integration&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; / &lt;a href="https://en.wikipedia.org/wiki/Continuous_deployment" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Continuous deployment&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;rdquo; und bedeutet, dass das System bei Update der verwalteten Dateien automatisch die Website neu baut, testet und veröffentlicht.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;DNS steht für &lt;a href="https://en.wikipedia.org/wiki/Domain_Name_System" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Domain Name System&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, ein verteiltes hierarchisches System zur Namensauflösung von Websites. Hintergrund ist, dass es viel schwieriger ist, sich eine IPV6-Adresse wie &lt;code&gt;2a02:ec80:300:ed1a:0:0:0:1&lt;/code&gt; zu merken statt &lt;code&gt;en.wikipedia.org&lt;/code&gt; in die Adresszeile eines Browsers einzugeben.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item></channel></rss>