<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on jbetzen.net</title>
    <link>https://jbetzen.net/posts/</link>
    <description>Recent content in Posts on jbetzen.net</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>de</language>
    
    <lastBuildDate>Tue, 17 Feb 2026 01:42:13 +0200</lastBuildDate><atom:link href="https://jbetzen.net/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Leben mit Grundeinkommen</title>
      <link>https://jbetzen.net/posts/grundeinkommen/</link>
      <pubDate>Tue, 17 Feb 2026 01:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/grundeinkommen/</guid>
      <description>Im Oktober 2024 ereilte mich das Glück ein bedingungsloses Grundeinkommen zu gewinnen. Was ich mit dem Geld gemacht habe und wie es sich auf mein Leben ausgewirkt hat, habe ich in diesem Post festgehalten.</description>
      <content:encoded><![CDATA[ <p>Der gemeinnützige Verein <a href="https://www.mein-grundeinkommen.de"  target="_blank" rel="noreferrer">Mein Grundeinkommen e.V.</a> verlost seit vielen Jahren ein monatliches Grundeinkommen, das die Gewinnenden für ein Jahr bedingungslos erhalten. Im Oktober 2024 zählte ich zu den glücklichen Gewinnern und bekam fortan für ein Jahr ein <em>utopisches Grundeinkommen</em> von <strong>1000 €</strong> überwiesen.</p>
<p>Das Grundeinkommen kann entscheidender Faktor für stabile Lebensverhältnisse und die Steigerung der Qualität und Teilhabe am gesellschaftlichen Leben sein. Ich selber verfolge das Projekt <em>Mein Grundeinkommen e.V.</em> bereits lange und verbinde es mit persönlichen Lebensumständen unmittelbar nach dem Studium, in denen ich es mir immer gewünscht hätte zu den glücklichen Gewinnern zu gehören. Umso mehr habe ich geschmunzelt, als mich das Glück ereilte zu den Gewinnenden zu gehören.</p>


  

<figure class="centered"><img src="/posts/grundeinkommen/nope.png"
    alt="Screenshot, der eine weitere Teilname an der Verlosung des Grundeinkommens verhindert, da man gerade Grundeinkommen bezieht.">
</figure>


<p>Was ein solcher Gewinn für mich persönlich bedeutete, was er ermöglichte und wie ich im Detail den monatlichen Obolus genutzt habe, möchte ich in diesem Post festhalten.</p>

<h2 class="relative group">Lebenssituation
    <div id="lebenssituation" class="anchor"></div>
    
</h2>
<p>Ich habe im Jahr 2019 für mich entschieden, dass fünf Arbeitstage in der Woche in keinem gesunden menschenwürdigen Verhältnis zu lediglich zwei Wochen­end­tagen stehen und bin damals auf eine 4-Tage-Woche à 32 Wochenstunden gewechselt. Mir ist völlig klar, dass dies nicht jeder Mensch machen kann und ich ein Privileg genieße. In meinem persönlichen Fall habe ich die Entscheidung gewissenhaft durchgerechnet und für mich entschieden, dass ich alles habe, was ich zum Leben brauche und Geld ab einem Gewissen Punkt keinen zusätzlichen Zauber mehr ins Leben bringt. Statt eines fünften Arbeitstages habe ich jetzt einen <em>altruistischen freien Tag</em> an dem ich mich Menschen, Themen und Aufgaben widme, die primär nicht mir, sondern anderen zu Gute kommen. Das ist gut für den Kopf, schafft Abstand zur beruflichen Tätigkeit und bringt mich schneller gedanklich ins Wochenende.</p>
<blockquote><p>&ldquo;Wer zu viel arbeitet, begeht einen doppelten Betrug: Er betrügt sich selbst um sein Leben und seine Arbeit um ihre Güte.&rdquo;</p>
<p>&ndash; Marie von Ebner-Eschenbach</p>
</blockquote><p>Ich würde mich selbst als Puristen beschreiben, der Verpflichtungen lieber aus sicherer Entfernung betrachtet und auch eine Dekade nach Beendigung des Studiums den <em>Studierendenmodus</em> nie gänzlich verlassen hat. Ich habe, abseits von meiner Miete, keine hohen Ausgaben, habe keine laufenden Langzeit­verträge. Ich habe keine Schulden, kein Haus, das ich abbezahlen muss, keinen Handyvertrag. Ich führe keinen teuren Scheidungskrieg, muss keinen Unterhalt zahlen und fahre seit jeher nur treue <a href="https://de.wikipedia.org/wiki/Mercedes-Benz_W_201"  target="_blank" rel="noreferrer">190er</a> Benz, die ich ausnahmslos alle <em>Bar auf die Kralle</em> bezahlt habe und liebevoll <em>mein Bluesmobil</em> nenne.</p>


  

<figure class="centered nozoom"><img src="/posts/grundeinkommen/bluesmobil.jpg"
    alt="Wer einmal hinterm Stern gesessen, wird es niemals mehr vergessen."><figcaption>
      <p><em>Wer einmal hinterm Stern gesessen, wird es niemals mehr vergessen.</em></p>
    </figcaption>
</figure>


<p>Meine teuersten Investitionen der letzten Dekade waren ein <a href="/posts/android-zu-ios/" >MacBook Air M1 und ein iPhone 12</a>, sowie mein aktueller 190er. Alles zusammen keine 6000 €. Mit dem Konsum ist es für mich wie mit dem Geld und der Arbeit: <em>Irgendwann ist weniger mehr.</em></p>

<h2 class="relative group">Erste Gedanken zum Gewinn
    <div id="erste-gedanken-zum-gewinn" class="anchor"></div>
    
</h2>
<p>Als ich von dem Gewinn erfahren habe, waren es im Kern drei Gedanken, die mir initial durch den Kopf schossen:</p>
<ol>
<li>Ich habe das Grundeinkommen im Tandem mit einer wunderbaren Person gewonnen, deren Lebensverhältnisse nicht deckungsgleich mit meinen waren und die in einem toxischen Bürojob gefangen war. Ihr Tag war geprägt von Arbeitskollegen mit unterkomplex vernetzten Gehirnen, Alltagsrassismen und intellektueller Stumpfheit. Diesen Job konnte sie nun glücklicherweise kündigen. Für ihr Leben würde der Gewinn sehr viel mehr ermöglichen, als für mich und daher habe ich mich in erster Linie viel mehr für <strong>sie</strong> gefreut, als über meinen eigenen Gewinn.</li>
<li>Mit zusätzlichen 1000 € im Monat hätte ich temporär meine Arbeitszeit weiter reduzieren und für ein Jahr auf eine 3-Tage-Woche à 24 Wochenstunden wechseln können. Den Gedanken habe ich aber relativ schnell verworfen, denn vermutlich wäre es mental für mich nicht verkraftbar gewesen erneut von drei auf vier Arbeitstage zu wechseln, sobald die Zahlungen des Grundeinkommens eingestellt würden.</li>
<li>Warum sollte ich den Gewinn ausschließlich für mich nutzen? Wäre es nicht zielführender das Geld, gemäß den Grundsätzen des <a href="https://de.wikipedia.org/wiki/Effektiver_Altruismus"  target="_blank" rel="noreferrer"><em>Effektiven Altruismus</em></a>, zum Nutzen Anderer einzusetzen und <em>Gutes</em> zu tun?</li>
</ol>
<p>Ich habe mich dafür entschieden die Hälfte des Geldes für mich zu nutzen und die andere Hälfte wurde verwendet für Menschen, Organisationen, Open-Source-Projekte, etc., bei denen es Besser aufgehoben war, als bei mir.</p>

<h2 class="relative group">Verwendungszwecke
    <div id="verwendungszwecke" class="anchor"></div>
    
</h2>
<p>An dieser Stelle sollen exemplarisch einige Ausgaben gelistet werden, für die ich das Grundeinkommen genutzt habe:</p>
<ul>
<li><strong>Weihnachtsgeschenke:</strong> Persönlich hervorzuheben sei ein höherpreisiges Lego-Set für meinen ältesten Neffen, das er sich selber ausgesucht hat und mir sonst vermutlich zu teuer gewesen wäre. Da er meine kindheitliche Liebe für Lego teilt und der Weg hin zu Lego Technik geebnet werden muss, war es mir ein Anliegen ihm diesen Wunsch zu erfüllen.</li>
<li>Ich habe meinem geschätzten Nachbarn und guten Freund einen <strong>Fender Akustik Bass</strong> gekauft, damit wir im Sommer zusammen stümperhaft Lieder von Nirvana im Vorgarten covern und dabei warmes <a href="https://bierpedia.org/bier/perlenbacher/"  target="_blank" rel="noreferrer">Perli</a> trinken können. Leider erwies sich sein Gehirn und Physiognomie als zu <em>steiff</em> und <em>ungelenk</em>, um sich der musischen Entfaltung auf (lediglich) vier Saiten hinzugeben. Vielleicht wird das ja in Zukunft noch was&hellip; Bis dahin habe ich einen formschönen Staubfänger im Wohnzimmer, den ich als Linkshänder nur bedingt spielen kann.</li>
<li>Da ich seit zwei Jahrzehnten drei Paar Sneaker pro Jahr durchlaufe, habe ich schon länger einen nachhaltigeren Ansatz für die Fußbeplankung angesonnen und daher zwei Paar <strong>Schuhe</strong> aus dem tradierten Hause <a href="https://eu.nps-solovair.com/"  target="_blank" rel="noreferrer">Solovair</a> gekauft, und zwar die Modelle <a href="https://eu.nps-solovair.com/collections/solovair-classic-collection/products/s6-969-ch-g"  target="_blank" rel="noreferrer">6 Eye Astronaut Boot</a> und <a href="https://eu.nps-solovair.com/collections/mens/products/s0-900-bg-g"  target="_blank" rel="noreferrer">Black Greasy Dealer Boot</a>. Insbesondere letzteres Modell ist schlichtweg ein Traum, wenn man schnell mal in bequeme Schuhe springen muss. Für den Astronaut Boot empfehle ich den eigenen Horizont zu erweitern und einen <a href="https://www.youtube.com/shorts/FFeLKe3xsdY"  target="_blank" rel="noreferrer">Berlutti-Knoten</a> zu lernen, da diese Schuhe ohne gewachste Schnürsenkel ausgeliefert werden.</li>
<li>Ich habe einem feinen Menschen, den ich mehr schätze als ihr vermutlich bewusst ist, bei einer ausstehenden Zahlung und endlosem Inkasso-Papierkrieg-Terror mit dem <strong>Job Center</strong> unter die Arme gegriffen und zeitgleich, in Teilen, die Kosten für eine anstehende <strong>Katzenoperation</strong> bezahlt. Gut Pfote, Mush, und auf weitere sechs Leben!</li>
<li>Ich habe das lokale <strong>Kinderhospiz</strong> mit einer Spende beglückt, da ich aus meiner Zivildienstzeit weiß, wie unglaublich wichtig deren Arbeit ist. Das mache ich generell jährlich, aber dieses Jahr konnte ich einen höheren Betrag auf den Tisch legen und das hat mich innerlich freudig gestimmt.</li>
<li>Ich habe das gesamte Mehrparteienhaus zum <strong>Essen beim Italiener</strong> eingeladen, weil es allesamt tolle Menschen sind und ich mich glücklich schätzen kann, dass wir so eine innige und umgängliche Nachbarschaft formen, trotz anonymen Großstadtlebens. Haupttreiber der Einladung war der feierliche Auszug eines verwirrten Hausbewohners, der die gesamte Hausgemeinschaft auf Trab hielt und dessen Eskapaden in dem separaten Beitrag <a href="/posts/krawallant-im-haus/">Ein Krawallant im Haus</a> festgehalten wurden.</li>
<li>Da ich selber ein großer Freund von <strong>Open-Source-Software</strong> und freiem Wissen bin, habe ich diverse Projekte und Dienste unterstützt, unter Anderem:
<ul>
<li><a href="https://signal.org"  target="_blank" rel="noreferrer">Signal</a>, da es mir bis heute unverständlich ist, wie Menschen einen Messenger wie WhatsApp nutzen können, ohne sich dabei selber ihrer äffischen Abstammung bewusst zu werden.</li>
<li>Die <a href="https://www.mozilla.org"  target="_blank" rel="noreferrer">Mozilla Foundation</a>, die - trotz ihrer <a href="https://rubenerd.com/mozillas-latest-quagmire/"  target="_blank" rel="noreferrer">unsäglichen</a> <a href="https://tante.cc/2024/06/26/mozilla-ai-did-what-when-silliness-goes-dangerous/"  target="_blank" rel="noreferrer">AI-Irrwege</a> - nach wie vor den einzigen brauchbaren freien Browser Firefox entwickelt.</li>
<li>Die <a href="https://matrix.org/foundation/about/"  target="_blank" rel="noreferrer">Matrix Foundation</a>, da sie mit dem Messenger <a href="https://element.io/de"  target="_blank" rel="noreferrer">Element</a> eine (noch nicht ganz brauchbare) Alternative zu <a href="https://www.forbes.com/sites/zakdoffman/2025/10/25/microsoft-teams-starts-telling-your-company-if-youre-not-at-work/"  target="_blank" rel="noreferrer">Microsoft Teams</a> und <a href="https://www.theverge.com/tech/875309/discord-age-verification-global-roll-out"  target="_blank" rel="noreferrer">Discord</a> entwickeln und mich an gute alte förderierende <a href="https://de.wikipedia.org/wiki/Extensible_Messaging_and_Presence_Protocol"  target="_blank" rel="noreferrer">XMPP</a>-Zeiten erinnert.</li>
</ul>
</li>
</ul>

<h2 class="relative group">Restrospektive
    <div id="restrospektive" class="anchor"></div>
    
</h2>
<p>Das vergangene Jahr mit einem Grundeinkommen hinterlässt rückblickend auf für mich folgende Erkenntnisse:</p>
<ol>
<li>Der Gedanke, jederzeit kündigen zu können und ein Jahr lang keine finanziellen Sorgen zu haben, ist wunderschön und sollte gesellschaftlicher <em>De-Facto-Standard</em> werden. Ich bin der festen Überzeugung, dass viele Menschen sich beruflich umorientieren und genau die Sicherheit des Grundeinkommens nutzen würden, um in Ruhe, ohne Druck und mit Bedacht Ausschau nach einem Job zu halten, ohne sich den bekannten Gängeleien des Job Centers hingeben zu müssen.</li>
<li>Würde ich ein kontinuierlich ausgezahltes Grundeinkommen beziehen, hätte ich gekündigt und einen lang gehegten Traum verfolgt und eine Ausbildung zum Gitarrenbauer begonnen.</li>
<li>Mit dem Grundeinkommen ist es wie mit dem Glück, und es gilt <em>&ldquo;Sharing is caring&rdquo;</em>. Andere an seinem Gewinn teilhaben zu lassen, war und ist für mich der schönste Gedanke, der sich bei mir verfestigt hat.</li>
</ol>
<p>Allen zukünftigen Gewinnern kann man nur aus tiefstem Herzen gratulieren, denn ein Grundeinkommen macht was im Kopf!</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Automation Templates</title>
      <link>https://jbetzen.net/posts/automation-templates/</link>
      <pubDate>Tue, 03 Feb 2026 12:29:27 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/automation-templates/</guid>
      <description>Automationen in Home Assistant haben einen Trigger, der eben jene auslöst. Zusätzlich werden Informationen bereit gestellt, bspw. welche Platform die Automation getriggert hat oder welcher numerische Ausgangswert zuvor vorhanden war.</description>
      <content:encoded><![CDATA[ <p>Im Post <a href="/posts/pixelclock-custom-app-collection/#bitcoin" >Pixelclock: Custom App Collection</a> wurde bereits eine Custom App zur Anzeige des Bitcoin Kurses auf der <a href="/tags/ulanzi-pixelclock/" >Ulanzi Pixelclock</a> vorgestellt. Diese nutzt die <a href="https://www.home-assistant.io/integrations/bitcoin/"  target="_blank" rel="noreferrer">Bitcoin Integration</a> und stellt den aktuellen Kurs auf dem Display dar:</p>


  

<figure class="centered"><img src="/posts/pixelclock-custom-app-collection/bitcoin.jpg"
    alt="Foto der Custom Bitcoin App auf der Ulanzi Pixelclock">
</figure>



<h2 class="relative group">Vorhaben
    <div id="vorhaben" class="anchor"></div>
    
</h2>
<p>Ziel dieses Beitrags ist Home Assistants mächtige Funktion der <a href="https://www.home-assistant.io/docs/automation/templating"  target="_blank" rel="noreferrer">Automation Templates</a> näherzubringen und diese zu nutzen, um die bestehende Custom App zu optimieren. Dazu wird die Automation erweitert und die <a href="https://www.home-assistant.io/docs/automation/templating/#available-trigger-data"  target="_blank" rel="noreferrer">Trigger Data</a> verwendet, um den dargestellten Text des Bitcoin Kurse einzufärben. Steigt der Kurs, gegenüber dem zuletzt gelieferten Ausgangswert, wird der Text grün dargestellt. Fällt der Kurs, wird er in roter Textfarbe ausgegeben. Ist der Wert gleichbleibend erfolgt eine Ausgabe in Gelb:</p>


  

<figure class="centered"><img src="/posts/automation-templates/pixelclock.gif"
    alt="Animiertes GIF des aktuellen Bitcoin Kurses in gefärbter Ausgabe auf der Ulanzi Pixeclock">
</figure>


<p>Zur Einfärbung des Texts muss die <a href="https://www.home-assistant.io/docs/automation/templating/#state"  target="_blank" rel="noreferrer">State Trigger Data</a> herangezogen werden, die zwei Variablen namens <code>trigger.from_state</code> und <code>trigger.to_state</code> bereitstellt:</p>


  

<figure class="centered"><img src="/posts/automation-templates/state-trigger-data.png"
    alt="Tabelle mit den Variablen für Home Assistants State Trigger Data">
</figure>



<h2 class="relative group">Automation anpassen
    <div id="automation-anpassen" class="anchor"></div>
    
</h2>
<p>Die Farbausgabe auf dem Display wird über den Payload Key <a href="https://blueforcer.github.io/awtrix3/#/api?id=json-properties"  target="_blank" rel="noreferrer"><code>color</code></a> definiert. Zuvor muss jedoch ermittelt werden, ob der letzte gelieferte Wert des Sensors <code>sensor.exchange_rate_1_btc</code> größer oder kleiner ist als der aktuelle Wert:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_bitcoin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.exchange_rate_1_btc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/bitcoin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;text&#34;: &#34;{{ states(&#39;sensor.exchange_rate_1_btc&#39;) | round(0) }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;icon&#34;: &#34;bitcoin&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;color&#34;:  {% if is_number(trigger.from_state.state) and (float(trigger.to_state.state,default=0) &gt; float(trigger.from_state.state,default=0)) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &#34;#006400&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    {% elif is_number(trigger.from_state.state) and (float(trigger.to_state.state,default=0) &lt; float(trigger.from_state.state,default=0)) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &#34;#FF0000&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &#34;#FFCC00&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    {% endif %}
</span></span></span><span class="line"><span class="cl"><span class="sd">        }</span></span></span></code></pre></div></div>
<p>Handelt es sich beim aktuellen Sensorwert um einen numerischen Wert (<code>is_number()</code>) und ist dieser größer als der vorherige Wert wird der Text nun in Grün ausgegeben:</p>


  

<figure class="centered"><img src="/posts/automation-templates/awtrix-green.png"
    alt="Grüne Textausgabe des Bitcoin Kurs auf der Ulanzi Pixelclock">
</figure>


<p>Fällt der aktuelle Kurs, wird der Text Rot ausgegeben:</p>


  

<figure class="centered"><img src="/posts/automation-templates/awtrix-red.png"
    alt="Rote Textausgabe des Bitcoin Kurs auf der Ulanzi Pixelclock">
</figure>


<p>Im Falle eines gleichbleibenden Werts, wird der Text in gelber Farbe ausgegeben.</p>


  

<figure class="centered"><img src="/posts/automation-templates/awtrix-yellow.png"
    alt="Gelbe Textausgabe des Bitcoin Kurs auf der Ulanzi Pixelclock">
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>LocoTV Video Handheld</title>
      <link>https://jbetzen.net/posts/locotv/</link>
      <pubDate>Fri, 30 Jan 2026 09:42:23 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/locotv/</guid>
      <description>Mein Neffe ist ein waschechter Pufferküsser mit einer ausgeprägten Begeisterung für die Schienenverkehrstechnik. Dieser Post dreht sich um ein kindgerechtes Hardwareprojekt, das Videos von Zügen im Hochkantformat abspielen kann.</description>
      <content:encoded><![CDATA[ <p>Nachdem mein erster Neffe vor Jahren mit seiner eigenen <a href="/posts/phoniebox/" >Phoniebox</a> beglückt wurde, war es an der Zeit seinem jüngeren Bruder eine kleine Freude zu machen.</p>
<p>Mein Neffe ist ein waschechter <a href="https://de.wiktionary.org/wiki/Pufferk%C3%BCsser"  target="_blank" rel="noreferrer">Pufferküsser</a> mit einer ausgeprägten Begeisterung für Züge, insbesondere für alte Dampflokomotiven. Da er regelmäßig sämtliche Bildschirme einnimmt, um Videos von Schienen­fahrzeugen zu konsumieren, habe ich mich ans Werk gesetzt und ihm ein kleines feines Gerät gebaut, mit dem er diese eigenständig schauen kann.</p>
<p>Initialer Funke für den Eigenbau war ein Video des Youtube Kanals <a href="https://www.youtube.com/@thelastoutpostworkshop"  target="_blank" rel="noreferrer">thelastoutpostworkshop</a>, das mir zufällig über den Weg lief. In diesem Video wird eindrucksvoll demonstriert, was ein kleiner <a href="/tags/esp32/" >ESP32</a>-Microcontroller, in der Größe einer Briefmarke, <em>in Puncto</em> Videowiedergabe alles zu leisten vermag:</p>
<lite-youtube videoid="jYcxUgxz9ks" playlabel="jYcxUgxz9ks" params=""></lite-youtube>

<p>Endprodukt meines Eigenbaus ist der <strong>LocoTV</strong>, ein kleiner Handheld im Hoch­kant­format, mit einem gedrucktem Gehäuse, einer Batterie und Ladeelektronik:</p>


  

<figure class="centered"><img src="/posts/locotv/final-iso.jpg"
    alt="Ergebnis des Zusammenbaus"><figcaption>
      <p>Der finale LocoTV</p>
    </figcaption>
</figure>



<h2 class="relative group">Hardware
    <div id="hardware" class="anchor"></div>
    
</h2>
<p>Zentrale Rolle in diesem Projekt spielt das <strong>Cheap Yellow Display</strong>, kurz auch CYD genannt. Der Name ist etwas sonderbar, beschreibt aber anschaulich, worum es sich hierbei handelt: Es ist eine kompakte Displayplatine mit einer Auflösung von <code>320x240</code> Pixeln und Touch-Funktionalität mit diversen Zusatzkomponenten, bspw. einem Helligkeitssensor, einer LED und einem Micro SD-Kartenleser. Die Platine kommt in gelber Farbe daher, besitzt einen SD-Card Slot und ist unter <strong>10 €</strong> zu erwerben. <em>Cheap!</em> Angetrieben wird es durch einen <a href="/tags/esp32/" >ESP32</a> Microcontroller.</p>
<p>Das Display ist in unzähligen Varianten erhältlich, die sich hauptsächlich in den Anschlüssen der Stromversorgung (Micro USB vs. USB-C), SPI-Taktung, aber auch in der eingesetzten Variante des ESP32 unterscheiden. Ich nutze für dieses Projekt die Version 2.0 des CYD mit USB-C Anschluss.</p>
<p>Die Materialkosten für die Hardware belaufen sich mit Versand auf ca. <strong>30 €</strong>:</p>
<table>
  <thead>
      <tr>
          <th>Funktion</th>
          <th>Bezeichnung</th>
          <th style="text-align: right">Kosten</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Display</td>
          <td><a href="https://de.aliexpress.com/item/1005006470918908.html"  target="_blank" rel="noreferrer">Cheap Yellow Display 2.8&quot; 320x240 (ESP32-2432S028)</a></td>
          <td style="text-align: right">8,29 €</td>
      </tr>
      <tr>
          <td>Spannungswandler</td>
          <td><a href="https://de.aliexpress.com/item/1005010670665225.html"  target="_blank" rel="noreferrer">MT3608 High Efficiency 1.2MHz 2A DC-DC Step Up Converter</a></td>
          <td style="text-align: right">1,89 €</td>
      </tr>
      <tr>
          <td>Power Button</td>
          <td><a href="https://de.aliexpress.com/item/1005009523726118.html"  target="_blank" rel="noreferrer">Blue circle light, 16MM, Latching, 6V</a><br>Wichtig ist, dass es sich um einen Button mit Rastfunktion (<em>latching switch</em>) und keinen Drucktaster (<em>momentary switch</em>) handelt.</td>
          <td style="text-align: right">2,19 €</td>
      </tr>
      <tr>
          <td>Batterielademodul</td>
          <td><a href="https://de.aliexpress.com/item/1005009203438591.html"  target="_blank" rel="noreferrer">TP4056 1A Standalone Linear Li-lon Battery Charger with Thermal Regulation in SOP-8</a></td>
          <td style="text-align: right">1,39 €</td>
      </tr>
      <tr>
          <td>Batterie</td>
          <td><a href="https://de.aliexpress.com/item/1005009136818706.html"  target="_blank" rel="noreferrer">NCR18650B Pointed 18650 3400mAh 3.7V Li-ion</a></td>
          <td style="text-align: right">5,69 €</td>
      </tr>
      <tr>
          <td>Batteriehalterung</td>
          <td><a href="https://de.aliexpress.com/item/1005008905937032.html"  target="_blank" rel="noreferrer">18650 Battery Holder Case</a></td>
          <td style="text-align: right">4,97 €</td>
      </tr>
      <tr>
          <td>JST-Connector</td>
          <td><a href="https://de.aliexpress.com/item/1005009732449641.html"  target="_blank" rel="noreferrer">JST 1.25mm MX1.25 4 Pin</a></td>
          <td style="text-align: right">2,89 €</td>
      </tr>
      <tr>
          <td>SD-Karte</td>
          <td>Eine <code>8  GB</code> SD-Karte flog bei mir noch rum.</td>
          <td style="text-align: right">0,00 €</td>
      </tr>
  </tbody>
</table>

<h2 class="relative group">Software
    <div id="software" class="anchor"></div>
    
</h2>
<p>Wie bereits in der Einführung erwähnt, stammt der Code vom Autoren <a href="https://www.youtube.com/@thelastoutpostworkshop"  target="_blank" rel="noreferrer">thelastoutpostworkshop</a>. Das Repository <a href="https://github.com/thelastoutpostworkshop/esp32-2432S028_video_player"  target="_blank" rel="noreferrer">esp32-2432S028_video_player</a> ist bei Github verfügbar. Als Codebase für meinen Eigenbau habe ich jedoch den Fork <a href="https://github.com/kiwiholmberg/cyd-video-player"  target="_blank" rel="noreferrer">cyd-video-player</a> verwendet, da dieser Support für den Touchscreen ergänzt. Dies ermöglicht, mit einem kindgerechten Touch auf das Display, das nächste Video abzuspielen.</p>
<p>Alle meine Anpassungen sind im Repository <a href="https://github.com/schneekluth/locotv"  target="_blank" rel="noreferrer"><code>schneekluth/locotv</code></a> verfügbar:</p>
<div class="github-card-wrapper">
    <a id="github-c51fd9279ff266f02077a71ed93e2181" target="_blank" href="https://github.com/schneekluth/locotv" class="cursor-pointer">
      <div
        class="w-full md:w-auto p-0 m-0 border border-neutral-200 dark:border-neutral-700 border rounded-md shadow-2xl"><div class="w-full nozoom">
            <img
              src="https://opengraph.githubassets.com/0/schneekluth/locotv"
              alt="GitHub Repository Thumbnail"
              class="nozoom mt-0 mb-0 w-full h-full object-cover">
          </div><div class="w-full md:w-auto pt-3 p-5">
          <div class="flex items-center">
            <span class="text-2xl text-neutral-800 dark:text-neutral me-2">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
</span>
            </span>
            <div
              id="github-c51fd9279ff266f02077a71ed93e2181-full_name"
              class="m-0 font-bold text-xl text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral">
              schneekluth/locotv
            </div>
          </div>

          <p id="github-c51fd9279ff266f02077a71ed93e2181-description" class="m-0 mt-2 text-md text-neutral-800 dark:text-neutral">
            A children&rsquo;s video player based on the Cheap Yellow Display.
          </p>

          <div class="m-0 mt-2 flex items-center">
            <span class="mr-1 inline-block h-3 w-3 rounded-full language-dot" data-language="C&#43;&#43;"></span>
            <div class="m-0 mr-5 text-md text-neutral-800 dark:text-neutral">
              C&#43;&#43;
            </div>

            <span class="text-md mr-1 text-neutral-800 dark:text-neutral">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z"/></svg></span>
            </span>
            <div id="github-c51fd9279ff266f02077a71ed93e2181-stargazers" class="m-0 mr-5 text-md text-neutral-800 dark:text-neutral">
              0
            </div>

            <span class="text-md mr-1 text-neutral-800 dark:text-neutral">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M80 104c13.3 0 24-10.7 24-24s-10.7-24-24-24S56 66.7 56 80s10.7 24 24 24zm80-24c0 32.8-19.7 61-48 73.3V192c0 17.7 14.3 32 32 32H304c17.7 0 32-14.3 32-32V153.3C307.7 141 288 112.8 288 80c0-44.2 35.8-80 80-80s80 35.8 80 80c0 32.8-19.7 61-48 73.3V192c0 53-43 96-96 96H256v70.7c28.3 12.3 48 40.5 48 73.3c0 44.2-35.8 80-80 80s-80-35.8-80-80c0-32.8 19.7-61 48-73.3V288H144c-53 0-96-43-96-96V153.3C19.7 141 0 112.8 0 80C0 35.8 35.8 0 80 0s80 35.8 80 80zm208 24c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24zM248 432c0-13.3-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24s24-10.7 24-24z"/></svg></span>
            </span>
            <div id="github-c51fd9279ff266f02077a71ed93e2181-forks" class="m-0 mr-5 text-md text-neutral-800 dark:text-neutral">
              0
            </div>
          </div>
        </div>
      </div>
      
      
      <script
        async
        type="text/javascript"
        src="/js/fetch-repo.min.dc5533c50cefd50405344b235937142271f26229fe39cbee27fd4960e8bb897a0beebfad77a1091ca91cd0d1fb14e70fc37cc114dd9674fb2c32e0ab512ec8a4.js"
        integrity="sha512-3FUzxQzv1QQFNEsjWTcUInHyYin&#43;OcvuJ/1JYOi7iXoL7r&#43;td6EJHKkc0NH7FOcPw3zBFN2WdPssMuCrUS7IpA=="
        data-repo-url="https://api.github.com/repos/schneekluth/locotv"
        data-repo-id="github-c51fd9279ff266f02077a71ed93e2181"></script>
    </a>
  </div>
<p>Die benötigten Schritte zum Bezug der Software:</p>
<ol>
<li>Den Code als <code>ZIP</code> Datei <a href="https://github.com/schneekluth/locotv/archive/refs/heads/main.zip"  target="_blank" rel="noreferrer">herunterladen</a> und entpacken.</li>
<li>Den Ordnernamen von <code>locotv-main</code> in <code>locotv</code> umbenenen.</li>
<li>Ein Doppelklick auf die Datei <code>locotv.ino</code> öffnet die Arduino IDE.</li>
</ol>

<h2 class="relative group">SD Karte vorbereiten
    <div id="sd-karte-vorbereiten" class="anchor"></div>
    
</h2>
<p>Im ersten Schritt muss die SD-Karte im FAT32-Dateisystem formatiert werden:</p>


  

<figure class="centered"><img src="/posts/locotv/sd-format.png"
    alt="FAT32 Formatierung für die SD-Karte">
</figure>


<p>Anschließend in den heruntergeladenen Ordner <code>locotv/</code> ins Verzeichnis <code>SD Content/</code> wechseln. Darin befindet sich ein Ordner <code>mjpeg</code> mit Beispielvideos. Dieser Ordner muss auf die SD-Karte kopiert werden:</p>


  

<figure class="centered"><img src="/posts/locotv/sd-folder.png"
    alt="Ordnerstruktur auf der SD-Karte">
</figure>


<p>Die SD-Karte ist damit einsatzbereit und kann in den SD-Kartenleser vom Cheap Yellow Display gesteckt werden.</p>
<p>Das Cheap Yellow Display kann nun per USB mit dem Computer verbunden werden. Ab Werk begrüßt das Display den Nutzer mit einer Demo-Anwendung, mit der auch der Touchscreen ausprobiert werden kann.</p>

<h2 class="relative group">Arduino IDE
    <div id="arduino-ide" class="anchor"></div>
    
</h2>
<p>Programmiert habe ich das Displayboard mit der <a href="https://www.arduino.cc/en/software/"  target="_blank" rel="noreferrer">Arduino IDE</a> in Version <code>2.3.5</code>. Dazu muss, sofern nicht bereits erfolgt, als erstes der ESP32 in der Arduino IDE als Board <a href="https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#installing-using-arduino-ide"  target="_blank" rel="noreferrer">hinterlegt</a> werden.</p>
<p>Dazu die Einstellungen der IDE mit <kbd>CTRL</kbd>+<kbd>,</kbd> aufrufen und auf den Button hinter dem Eingabefeld mit der Bezeichnung <kbd>Additional board manager URLs</kbd> klicken:</p>


  

<figure class="centered"><img src="/posts/locotv/additional-board-btn.png"
    alt="Preference Menü der Arduino IDE mit Button zum hinzufügen von zusätzlichen Boards">
</figure>


<p>In das sich öffnende Textfeld muss die folgende URL eingetragen und anschließend mit <kbd>OK</kbd> bestätigt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">https://espressif.github.io/arduino-esp32/package_esp32_index.json</span></span></code></pre></div></div>


  

<figure class="centered"><img src="/posts/locotv/additional-board-url.png"
    alt="Texteingabe der zusätzlichen Board URL für den ESP32">
</figure>


<p>Der ESP32 ist nun in allen gängigen Varianten mit der Arduino IDE nutzbar und steht im Board Manager zur Verfügung.</p>

<h3 class="relative group">Board konfigurieren
    <div id="board-konfigurieren" class="anchor"></div>
    
</h3>
<p>Um den ESP32 als Board zuweisen zu können, muss zuerst der Board Manager unter <kbd>Tools &gt; Board &gt; Board Manager&hellip;</kbd> aufgerufen werden. Ins Suchfeld den Begriff <code>esp32</code> eingeben und anschließen im Dropdown Menü die Version <code>3.2.0</code> auswählen und installieren:</p>


  

<figure class="centered"><img src="/posts/locotv/ide-installed-board.png"
    alt="ESP32 Board in Version 3.2.0 installieren">
</figure>


<p>Weiterführend muss als Microcontroller der Eintrag <code>ESP32 Dev Module</code> ausgewählt und der korrekte Port des USB-Anschluss zugewiesen werden:</p>


  

<figure class="centered"><img src="/posts/locotv/ide-select-board.png"
    alt="Board Selector in der Arduino IDE">
</figure>


<p>Anschließend muss nur noch unter <kbd>Tools &gt; Upload Speed</kbd> die <a href="https://de.wikipedia.org/wiki/Baud"  target="_blank" rel="noreferrer">Baudrate</a> auf den Wert <code>115200</code> gesetzt werden und schon ist das Board programmierbereit.</p>

<h3 class="relative group">Libraries installieren
    <div id="libraries-installieren" class="anchor"></div>
    
</h3>
<p>Folgende Libraries müssen in den <strong>expliziten</strong> Version verwendet werden. Dazu, analog wie im Board Manager, in der Arduino IDE die jeweiligen Versionen im Library Manager unter <kbd>Tools &gt; Manage Libraries</kbd> suchen und installieren:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Library/Framework</th>
          <th>Version</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://github.com/moononournation/Arduino_GFX"  target="_blank" rel="noreferrer">GFX Library for Arduino</a></td>
          <td>1.6.0</td>
      </tr>
      <tr>
          <td><a href="https://github.com/bitbank2/JPEGDEC"  target="_blank" rel="noreferrer">JPGEGDEC</a></td>
          <td>1.8.2</td>
      </tr>
  </tbody>
</table>
</div>


  

<figure class="centered"><img src="/posts/locotv/ide-installed-libraries.png"
    alt="Installierte Library Versionen">
</figure>


<p>Zusätzlich liegt in dem heruntergeladenen Ordner noch eine lokale Bibliothek <code>TFT_Touch_v0.3.zip</code> für die Touchscreen-Unterstützung bei. Diese muss in der Arduino IDE importiert werden. Dazu unter <kbd>Sketch &gt; Include Library &gt; Import .ZIP Library&hellip;</kbd> klicken und die Datei <code>TFT_Touch_v0.3.zip</code> auswählen.</p>

<h3 class="relative group">Code Upload
    <div id="code-upload" class="anchor"></div>
    
</h3>
<p>Die Datei <code>locotv.ino</code> kann aus der Arduino IDE mit dem Shortcut <kbd>CTRL</kbd>+<kbd>U</kbd> kompiliert und auf das Cheap Yellow Display geladen werden:</p>


  

<figure class="centered"><img src="/posts/locotv/ide-upload.png"
    alt="Upload-Vorgang in der Arduino IDE">
</figure>


<p>Treten beim Kompilieren keine Fehler auf, sollten nach dem Upload die zuvor auf die SD-Karte kopierten Beispielvideos auf dem Display erscheinen. Durch berühren des Displays wird zum nächsten Video gewechselt.</p>

<h3 class="relative group">Code Anpassungen
    <div id="code-anpassungen" class="anchor"></div>
    
</h3>
<p>Das Cheap Yellow Display existiert in zigfachen Ausführungen, die sich nicht immer gleich verhalten. Ich habe für meine Version und mein Vorhaben insgesamt drei Änderungen am Code vorgenommen:</p>
<ol>
<li>
<p>Die Farben des Displays invertieren:</p>
<div class="highlight-wrapper"><div class="codeblock-title">vorher</div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-arduino" data-lang="arduino"><span class="line"><span class="cl"><span class="n">gfx</span><span class="o">-&gt;</span><span class="n">fillScreen</span><span class="p">(</span><span class="n">RGB565_BLACK</span><span class="p">);</span>
</span></span><span class="line hl"><span class="cl"><span class="c1">// gfx-&gt;invertDisplay(true); // on some cheap yellow models, display must be inverted
</span></span></span><span class="line"><span class="cl"><span class="nc">Serial</span><span class="p">.</span><span class="n">printf</span><span class="p">(</span><span class="s">&#34;Screeen size Width=%d,Height=%d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">gfx</span><span class="o">-&gt;</span><span class="nf">width</span><span class="p">(),</span> <span class="n">gfx</span><span class="o">-&gt;</span><span class="nf">height</span><span class="p">());</span></span></span></code></pre></div></div>
<div class="highlight-wrapper"><div class="codeblock-title">nachher</div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-arduino" data-lang="arduino"><span class="line"><span class="cl"><span class="n">gfx</span><span class="o">-&gt;</span><span class="n">fillScreen</span><span class="p">(</span><span class="n">RGB565_BLACK</span><span class="p">);</span>
</span></span><span class="line hl"><span class="cl"><span class="n">gfx</span><span class="o">-&gt;</span><span class="n">invertDisplay</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span> <span class="c1">// on some cheap yellow models, display must be inverted
</span></span></span><span class="line"><span class="cl"><span class="nc">Serial</span><span class="p">.</span><span class="n">printf</span><span class="p">(</span><span class="s">&#34;Screeen size Width=%d,Height=%d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">gfx</span><span class="o">-&gt;</span><span class="nf">width</span><span class="p">(),</span> <span class="n">gfx</span><span class="o">-&gt;</span><span class="nf">height</span><span class="p">());</span></span></span></code></pre></div></div>
</li>
<li>
<p>Zusätzlich muss für meine gewählte Einbaurichtung im Gehäuse der Portraitmodus des Displays gespiegelt werden, indem die Werte <code>setRotation()</code> und <code>touch.setRotation()</code> angepasst werden:</p>
<div class="highlight-wrapper"><div class="codeblock-title">vorher</div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-arduino" data-lang="arduino"><span class="line"><span class="cl"><span class="n">gfx</span><span class="o">-&gt;</span><span class="n">setRotation</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">touch</span><span class="p">.</span><span class="n">setRotation</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span></span></span></code></pre></div></div>
<div class="highlight-wrapper"><div class="codeblock-title">nachher</div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-arduino" data-lang="arduino"><span class="line"><span class="cl"><span class="n">gfx</span><span class="o">-&gt;</span><span class="n">setRotation</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">touch</span><span class="p">.</span><span class="n">setRotation</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span></span></span></code></pre></div></div>
</li>
<li>
<p>Das Display mit <code>80 Mhz</code> statt <code>40 Mhz</code> Taktung per 


<abbr title="Serial Periphal Interface">SPI</abbr> betreiben:</p>
<div class="highlight-wrapper"><div class="codeblock-title">vorher</div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-arduino" data-lang="arduino"><span class="line"><span class="cl"><span class="c1">// Some model of cheap Yellow display works only at 40Mhz
</span></span></span><span class="line hl"><span class="cl"><span class="c1">//#define DISPLAY_SPI_SPEED 80000000L // 80MHz
</span></span></span><span class="line hl"><span class="cl"><span class="cp">#define DISPLAY_SPI_SPEED 40000000L </span><span class="c1">// 40MHz
</span></span></span></code></pre></div></div>
<div class="highlight-wrapper"><div class="codeblock-title">nachher</div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-arduino" data-lang="arduino"><span class="line"><span class="cl"><span class="c1">// Some model of cheap Yellow display works only at 40Mhz
</span></span></span><span class="line hl"><span class="cl"><span class="cp">#define DISPLAY_SPI_SPEED 80000000L </span><span class="c1">// 80MHz
</span></span></span><span class="line hl"><span class="cl"><span class="c1">//#define DISPLAY_SPI_SPEED 40000000L // 40MHz
</span></span></span></code></pre></div></div>
</li>
</ol>

<h2 class="relative group">Eigene Videos erstellen
    <div id="eigene-videos-erstellen" class="anchor"></div>
    
</h2>
<p>Ideale Quelle für Videos von Lokomotiven sind <a href="https://www.youtube.com/results?search_query=steam%20locomotives&amp;sp=EgIQCQ%253D%253D"  target="_blank" rel="noreferrer">YouTube Shorts</a>. Diese sind, dem spinösen Zeitgeist verschuldet, im Hochkantformat vorhanden und somit optimales Ausgangsmaterial, wenn das Display in dieser Ausrichtung betrieben wird.</p>
<p>Im Folgenden gilt es Videos herunterzuladen und ins <a href="https://de.wikipedia.org/wiki/Motion_JPEG"  target="_blank" rel="noreferrer">Motion Jpeg Format</a> Format zu konvertieren. Das Format <code>MJPEG</code> wird gewählt, da der ESP32 <a href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32p4/api-reference/peripherals/jpeg.html"  target="_blank" rel="noreferrer">hardwareseitig</a> die Funktion besitzt JPEG-Dateien zu Encoden und Decoden.</p>
<p>Für den Download und die Konvertierung der Videos nutze ich zwei Kommandozeilen-Tools:</p>
<ul>
<li><a href="https://github.com/yt-dlp/yt-dlp"  target="_blank" rel="noreferrer"><strong>yt-dlp</strong></a>: Zum Herunterladen der Videos</li>
<li><a href="https://www.ffmpeg.org/"  target="_blank" rel="noreferrer"><strong>ffmpeg</strong></a>: Zum Konvertieren der Videos</li>
</ul>
<p>Beide Tools können unter Windows 11 einfach über die Paketverwaltung <a href="https://github.com/microsoft/winget-cli"  target="_blank" rel="noreferrer"><code>winget</code></a> installiert werden. Dazu das Terminal öffnen und den Einzeiler eintippen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="n">winget</span> <span class="n">install</span> <span class="nb">yt-dlp</span><span class="p">.</span><span class="nb">yt-dlp</span> <span class="n">Gyan</span><span class="p">.</span><span class="n">FFmpeg</span></span></span></code></pre></div></div>
<p>Nach der Installation muss das Terminal geschlossen und erneut geöffnet werden.</p>

<h3 class="relative group">Einzelkonvertierung
    <div id="einzelkonvertierung" class="anchor"></div>
    
</h3>
<p>Als Beispiel für die einzelne Konvertierung eines Videos dient <a href="https://www.youtube.com/shorts/cKKkR6O_jYQ"  target="_blank" rel="noreferrer">dieser Short</a>. Seine URL lautet: <code>https://www.youtube.com/shorts/cKKkR6O_jYQ</code>.</p>
<p>Zum Herunterladen kann nun <code>yt-dlp</code> im Terminal verwendet werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="nb">cd </span><span class="p">~\</span><span class="n">Desktop</span>
</span></span><span class="line"><span class="cl"><span class="nb">yt-dlp</span> <span class="s2">&#34;https://www.youtube.com/shorts/cKKkR6O_jYQ&#34;</span> <span class="n">-o</span> <span class="n">test</span></span></span></code></pre></div></div>
<p>Und schon befindet sich das Video mit dem Dateinamen <code>test.webm</code> auf dem Desktop.</p>
<p>Zur Konvertierung sind im Repository des ursprünglichen Autoren <a href="https://github.com/thelastoutpostworkshop/esp32-2432S028_video_player?tab=readme-ov-file#-how-to-use-these-ffmpeg-commands"  target="_blank" rel="noreferrer">diverse ffmpeg Kommandos</a> gelistet. Diese skalieren Videos jedoch nur auf die benötigte Bildschirmauflösung von 320x240 Pixel. Dies ist ein Problem, da YouTube Shorts in der Regel hochkant im Format 16:9 aufgenommen werden. Das Display besitzt jedoch ein Bildschirmverhältnis von 4:3. Ich habe das Kommando angepasst, dass mit ffmpeg im ersten Schritt ein Zoom in das Video und anschließend ein Zuschnitt auf die 320x240 Pixel erfolgt. Somit gehen in der Höhe zwar Teile des Videos verloren, jedoch entstehen so keine schwarzen Balken auf der linken und rechten Seite neben dem Video.</p>
<p>Das folgende optimierte Kommando wird zur Konvertierung der heruntergeladenen Datei <code>test.webm</code> in das <code>mjpeg</code> Format verwendet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="nb">cd </span><span class="p">~\</span><span class="n">Desktop</span>
</span></span><span class="line"><span class="cl"><span class="n">ffmpeg</span> <span class="n">-y</span> <span class="n">-i</span> <span class="p">.\</span><span class="n">test</span><span class="p">.</span><span class="py">webm</span> <span class="n">-pix_fmt</span> <span class="n">yuvj420p</span> <span class="n">-q:v</span> <span class="mf">7</span> <span class="n">-vf</span> <span class="s2">&#34;fps=24,crop=in_w:in_w*4/3:0:(in_h-in_w*4/3)/2,scale=240:-1:flags=lanczos&#34;</span> <span class="n">test</span><span class="p">.</span><span class="n">mjpeg</span></span></span></code></pre></div></div>
<p>Die generierte Datei <code>test.mjpeg</code> liegt ebenfalls auf dem Desktop und kann so direkt auf die SD-Karte kopiert und auf dem Display abgespielt werden.</p>

<h3 class="relative group">Batchkonvertierung
    <div id="batchkonvertierung" class="anchor"></div>
    
</h3>
<p>Da das einzelne Herunterladen und Konvertieren mühsam ist, macht es Sinn dies im Batch zu erledigen. Dazu in das Verzeichnis <code>locotv/script</code> wechseln. Darin liegen zwei Dateien:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">./locotv/script
</span></span><span class="line"><span class="cl">  ├── 00_convert.ps1
</span></span><span class="line"><span class="cl">  └── 00_videos.txt</span></span></code></pre></div></div>
<p>In der Datei <code>00_videos.txt</code> können nun zeilenweise Links zu YouTube Shorts mit einem Texteditor hinterlegt werden:</p>
<div class="highlight-wrapper"><div class="codeblock-title">00_videos.txt | Beispieleinträge</div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">https://www.youtube.com/shorts/cKKkR6O_jYQ
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/S3O2S8ZE8ec
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/-1p6VCfEhfw
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/lFymosFJC1c
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/QhkMK151dy8
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/X2mo7eP74u4
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/tXyq57_Cvc0
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/Q2YHydMnHRQ
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/hI4E1KZAd84
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/Ou3pGGidRgM
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/nM4f9Vzdo4Y
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/6WNYhWgIdS4
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/ewbTSRsFC-0
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/af9Om_3LcQk
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/mKP9dpXhcx0
</span></span><span class="line"><span class="cl">https://www.youtube.com/shorts/tfQvw7QjGa8</span></span></code></pre></div></div>
<p>Die Datei <code>00_convert.ps1</code> enthält den Code für die Konvertierung der Videos ins MJPEG-Format. Es liest zeilenweise den Inhalt der Datei <code>00_videos.txt</code> ein, lädt die darin hinterlegten Videos herunter, konvertiert sie und löscht die Ursprungsdateien.</p>
<p>In diesem Ordner einen Rechtsklick machen und den Eintrag <kbd>In Terminal öffnen</kbd> auswählen:</p>


  

<figure class="centered"><img src="/posts/locotv/open-in-terminal.png">
</figure>


<p>Anschließend kann das Skript über diesen Befehl gestartet werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="p">.\</span><span class="n">00_convert</span><span class="p">.</span><span class="n">ps1</span></span></span></code></pre></div></div>
<p>Das Skript <code>00_convert.ps1</code> macht im Detail folgendes in dieser Reihenfolge:</p>
<ul>
<li>Erstellt einen Ordner <code>locotv/script/mjpeg</code>.</li>
<li>Lädt sämtliche Videos aus der Datei <code>00_videos.txt</code> in den Ordner <code>locotv/script/</code> herunter.</li>
<li>Prüft mit <a href="https://ffmpeg.org/ffprobe.html"  target="_blank" rel="noreferrer">ffprobe</a>, ob das Video im Hochkant- oder Querformat aufgenommen wurde.</li>
<li>Konvertiert das Video in MJPEG-Format. Für Hochkant- und Querformate werden unterschiedliche ffmpeg Kommandos angewandt, um keine schwarzen Balken in den Videos vorzufinden.</li>
<li>Die konvertierten Dateien werden im Ordner <code>locotv/script/mjpeg</code> abgelegt. Ein Dateinamenpräfix <code>portrait_</code> (Hochkantformat) und <code>landscape_</code> (Querformat) wird angewandt.</li>
<li>Die Ursprungsvideos werden gelöscht. Lediglich die konvertierten Videos bleiben erhalten.</li>
</ul>


  

<figure class="centered"><img src="/posts/locotv/script-folder-output.png"
    alt="Ordnerstruktur, die vom PowerShell Skript angelegt wird und in der die konvertierten Videos abgelegt werden.">
</figure>


<hr>
<p>Wer nicht regelmäßig mit PowerShell auf dem eigenen Computer arbeitet, muss in der Regel noch die notwendigen Rechte zur Ausführung von Skripten setzen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="nb">Set-ExecutionPolicy</span> <span class="n">-Scope</span> <span class="n">CurrentUser</span> <span class="n">-ExecutionPolicy</span> <span class="n">RemoteSigned</span></span></span></code></pre></div></div>

<h3 class="relative group"><code>MAX_FILES</code> anpassen
    <div id="max_files-anpassen" class="anchor"></div>
    
</h3>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>UPDATE 12.02.26:</strong><br>
Der Wert <code>MAX_FILES</code> scheint nur eine Rolle zu spielen, wenn mehr Videos auf der SD-Karte existieren, als in <code>MAX_FILES</code> definiert ist. Der Wert kann beliebig hoch gesetzt werden und trotzdem starten die Videos erneut von vorne, wenn das letzte Video erreicht ist.</span>
</div>

<p>Der Arduino Code in der Datei <code>locotv.ino</code> hat per Default einen Counter, der nach jedem abgespielten Video um das Inkrement <code>1</code> erhöht wird. Die maximale Anzahl an abzuspielenden  Videodateien ist leider <em>hardgecoded</em> und wird nicht dynamisch ermittelt. Standardmäßig steht der Wert bei <code>20</code> Dateien. Hat man mehr als 20 Dateien werden also trotzdem nur die ersten 20 Dateien abgespielt. Der Wert sollte also, gemäß der vorhandenen Anzahl an Videodateien auf der SD-Karte, angepasst werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-arduino" data-lang="arduino"><span class="line"><span class="cl"><span class="cp">#define MAX_FILES 20 </span><span class="c1">// Maximum number of files, adjust as needed
</span></span></span></code></pre></div></div>
<p>Das vorgestellte PowerShell Skript zur <a href="/posts/locotv/#batchkonvertierung" >Batch-Konvertierung</a> mehrerer Videos ermittelt die Anzahl der Dateien und gibt diesen Wert, nach dem Konvertierungsvorgang, im Terminal aus:</p>


  

<figure class="centered"><img src="/posts/locotv/max-files-terminal.png"
    alt="Terminal-Ausgabe zur ermittelten Anzahl an gefundenen Video-Dateien">
</figure>


<p><strong>Wichtig:</strong> Eine Anpassung des Codes erfordert stets auch einen neues Kompilieren und einen erneuten <a href="/posts/locotv/#code-upload" >Upload</a> über die Arduino IDE.</p>

<h2 class="relative group">Gehäuse drucken
    <div id="gehäuse-drucken" class="anchor"></div>
    
</h2>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><figure class="centered"><img src="/posts/locotv/gehaeuse-v2.png">
</figure>
<p><strong>UPDATE 12.02.26:</strong><br>
Das Gehäuse wurde optimiert. Die SD-Karte kann nun ohne Auseinanderschrauben entnommen und neu mit Videomaterial bestückt werden. Gleichzeitig gibt es nun ein kleines Sichtloch, um den aktuellen Ladezustand des <strong>TP 4056 Batterielademoduls</strong> anzuzeigen:</p>
<ul>
<li><strong>LED leuchtet <span style="color:red">ROT</span></strong> = Batterie lädt</li>
<li><strong>LED leuchtet <span style="color:limegreen">GRÜN</span></strong> = Batterie geladen</li>
</ul></span>
</div>

<p>Ich habe in <a href="/categories/autodesk-inventor/" >Autodesk Inventor</a> ein simples Gehäuse, bestehend aus Ober- und Unterseite, entworfen. Die Gehäusehälften werden mittels Schrauben und passender Hülsenmuttern zusammengehalten. Die ermöglicht ein zukünftiges verschleißfreies Auseinanderbauen.</p>
<p>In der Unterseite finden Batteriehaltertung, Ladeelektronik und der Spannungs­wandler platz. In der oberen Gehäusehälfte sind das Display und der Ein-Aus-Schalter verbaut. Aussparungen für die SD-Karte und den USB-C Anschluss zum Laden der Batterie sind enthalten.</p>


  

<figure class="centered"><img src="/posts/locotv/case-iso.png"
    alt="Isometrische Ansicht vom 3D gedruckten Gehäuse">
</figure>


<p>Verwendet wird das <em>gröbste</em> Druckprofil <kbd>Draft</kbd> im <a href="https://ultimaker.com/de/software/ultimaker-cura/"  target="_blank" rel="noreferrer">Cura Slicer</a> mit einer Druckstärke von <code>0,3 mm</code> auf meinem in die Jahre gekommenen Anycubic i3 Mega.</p>
<p>Die STL-Dateien der Gehäusehälften sind im Ordner <a href="https://github.com/schneekluth/locotv/tree/main/stl"  target="_blank" rel="noreferrer"><code>locotv/stl</code></a> zum Nachdrucken vorhanden.</p>

<h2 class="relative group">Spannungswandler einstellen
    <div id="spannungswandler-einstellen" class="anchor"></div>
    
</h2>
<p>Für diesen Schritt wird ein Multimeter benötigt. Als Vorbereitung für den <a href="/posts/locotv/#zusammenbau" >Zusammenbau</a> muss vorerst der Spannungswandler so eingestellt werden, dass bei <code>3,7 V</code> Eingangsspannung eine Ausgangsspannung von <code>5 V</code> erreicht wird, die das Display für seinen Betrieb erfordert. Dazu wird die Justierschraube auf dem MT3608 verwendet. Videos dazu gibt es im Weltnetz zu Hauf:</p>
<lite-youtube videoid="sYo8TrKGqDM" playlabel="sYo8TrKGqDM" params=""></lite-youtube>

<p>Die 18650er-Batterie in das Batteriegehäuse stecken, das rote Kabel an den <code>VIN+</code> und das schwarze Kabel an den <code>VIN-</code> Kontakt anschließen. Das kann mit Krokodilsklemmen, geschickter Positionierung oder über ein temporäres Löten erfolgen. Anschließend das Multimeter auf Gleichstrommessung stellen und analog das rote Kabel des Multimeters an <code>VOUT+</code> und das schwarze an <code>VOUT-</code> halten. Nun muss die Justierschraube des MT3608 so eingestellt werden, dass <code>5 V</code> Ausgangs­spannung auf dem Multimeter angezeigt wird:</p>


  

<figure class="centered"><img src="/posts/locotv/spannungswandler.png"
    alt="Justierung der Ausgangsspannung an der Stellschraube des Spannungswandlers">
</figure>



<h2 class="relative group">Verdrahtung
    <div id="verdrahtung" class="anchor"></div>
    
</h2>


  

<figure class="centered"><img src="/posts/locotv/verdrahtung.png"
    alt="Schematische Verdrahtung der Elektronik">
</figure>



<h2 class="relative group">Zusammenbau
    <div id="zusammenbau" class="anchor"></div>
    
</h2>
<p>Für den Zusammenbau des Gehäuses und der Verdrahtung der elektronischen Bauteile werden, neben Lötkolben und Kabelage, folgende Bauteile benötigt:</p>
<table>
  <thead>
      <tr>
          <th>Bauteil</th>
          <th style="text-align: center">Anzahl</th>
          <th>Bezeichnung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>M2x5</td>
          <td style="text-align: center">4</td>
          <td>Schrauben zur Befestigung des Displays an der oberen Gehäusehälfte.</td>
      </tr>
      <tr>
          <td>M4</td>
          <td style="text-align: center">4</td>
          <td>Tellerkopfschrauben + Hülsenmutter, oft auch im Set als <em>Möbelverbinder</em> bezeichnet, mit Klemmweite von <code>34,2 mm</code> zur Befestigung der oberen und unteren Gehäusehälfte.</td>
      </tr>
      <tr>
          <td>Heißkleber</td>
          <td style="text-align: center">1</td>
          <td>Zur Fixierung des Spannungsreglers, des Batteriehalters und der Ladeelektronik in der unteren Gehäusehälfte.</td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Gehäuseoberseite
    <div id="gehäuseoberseite" class="anchor"></div>
    
</h3>
<p>Im ersten Schritt wird das Display mit den <code>M2x5</code> Schrauben befestigt. Es empfiehlt sich die Schrauben vorher <strong>ohne</strong> Display vorsichtig in die dafür vorgesehenen Löcher zu schrauben, um das Gewinde im PLA zu formen. Dabei immer wieder vor- und zurückschrauben, bis die benötigte Tiefe erreicht ist.</p>
<p>Im zweiten Schritt muss der 


<abbr title="Japan Solderless Terminal">JST</abbr>-Connector auf einer Seite, direkt hinter dem Stecker, mit einem Seitenschneider abgetrennt werden. Von den vier verfügbaren Pins werden nur die beiden Äußeren in den Farben Rot und Grün verwendet. Die Mittleren können ebenfalls abgetrennt werden:</p>


  

<figure class="centered"><img src="/posts/locotv/jst.png"
    alt="Vorbereitung des JST-Connectors">
</figure>


<p>Der JST-Connector kann nun an dem vorhandenen Anschluss am Displayboard gesteckt werden.</p>
<p>Als finaler Schritt wird der Power Button eingesetzt. Den Feststellring habe ich aus Platzgründen entfallen lassen, da der Button auch ohne ihn gut fixiert platziert war. Theoretisch ist der Platz aber vorhanden, falls er beim Nachbau platziert werden muss.</p>


  

<figure class="centered"><img src="/posts/locotv/assembly_front.png"
    alt="Zusammenbau der oberen Gehäusehälften mit Power Button und verschraubtem Display">
</figure>


<p>Die Oberseite ist damit fertig bestückt und Einsatzbereit.</p>

<h3 class="relative group">Gehäuseunterseite
    <div id="gehäuseunterseite" class="anchor"></div>
    
</h3>
<p>Die Gehäuseunterseite ist anspruchsvoller. Ich habe zuerst alle Komponenten darin platziert und dann die Kabellängen markiert und zugeschnitten. Danach habe ich alle Bauteile wieder herausgeholt und zuerst alle Bauteile verlötet, die in der Unterseite platziert werden. Danach legt/stellt man die Vorderseite so nah es geht daneben und lässt für diese die Kabel länger, damit die Verbindungen nicht reißen, wenn das Gehäuse mal geöffnet werden muss:</p>


  

<figure class="centered"><img src="/posts/locotv/aufgeklappt.png">
</figure>


<p>Batterielademodul, Spannungswandler und die Batteriehalterung habe ich anschließend mit Heißkleber in der unteren Gehäusehälfte fixiert:</p>


  

<figure class="centered"><img src="/posts/locotv/klebefl%C3%A4chen.png"
    alt="Klebeflächen der elektronischen Komponenten in der Gehäuseunterseite"><figcaption>
      <p>Klebeflächen in roter Farbmarkierung</p>
    </figcaption>
</figure>



<h3 class="relative group">Hochzeit
    <div id="hochzeit" class="anchor"></div>
    
</h3>
<p>Nachdem beide Gehäusehälften montiert und verdrahtet sind, können sie zusammengefügt werden. Dabei darauf achten, dass die Kabel nicht zu sehr unter Spannung sind oder Schaden beim Schließvorgang nehmen.</p>
<p>Die beiden Gehäusehälften werden durch Hülsenmuttern und Schrauben der Gewindegröße <code>M4</code> zusammengehalten. Das hat den Vorteil, dass nicht selber ins Druckmaterial geschraubt werden muss und eine zukünftige verschleißfreie Öffnung des Gehäuse gegeben ist. Hülsen- und Schraubenlänge kann variiert werden, solange die errechnete ideale Klemmweite von <code>34,2 mm</code> erreicht wird:</p>


  

<figure class="centered"><img src="/posts/locotv/klemmweite.png"
    alt="Schnittansicht des CAD-Modells für die Klemmweite von 34,2 mm">
</figure>


<p>Für meinen Eigenbau habe ich eine <code>M4x18</code> Tellerkopfschraube und eine Hülsenmutter mit <code>M4</code> Innengewinde einer Länge von <code>25 mm</code> verwendet. Diese stammen aus einem <a href="https://www.ikea.com/de/de/assembly_instructions/pax-korpus-kleiderschrank-weiss__AA-2174072-9-2.pdf#page=6"  target="_blank" rel="noreferrer">IKEA Pax Schrank</a> und haben bei IKEA die <a href="https://www.ikea.com/de/de/customer-service/returns-claims/spareparts/"  target="_blank" rel="noreferrer">Teilenummern</a> <code>100644</code> und <code>100402</code>:</p>


  

<figure class="centered"><img src="/posts/locotv/schraube-huelse.png"
    alt="Schraube und Hülsenmutter">
</figure>



<h2 class="relative group">Resultat
    <div id="resultat" class="anchor"></div>
    
</h2>
<p>Das Ergebnis ist ein kleines, neckische, portables <em>Brikett</em>, das auch in der Zukunft mit Videomaterial aller Art bespielt werden kann. Es dient nur dem Zweck der Video­wiedergabe, hat keine nervige Sound-Ausgabe und der abgespielte Content liegt zu 100% in eigener Hand:</p>


  

<figure class="centered"><img src="/posts/locotv/final-iso.jpg"
    alt="Ergebnis des Zusammenbaus">
</figure>


<p>Betätigt man den Power-Button, rastet dieser ein, der LED-Ring des Buttons beginnt zu leuchten und ca. eine Sekunde später beginnt die Videowiedergabe:</p>


  

<figure class="centered"><img src="/posts/locotv/brikett.gif"
    alt="Animiertes gif der Funktionsweise">
</figure>


<p>Ist die Batterie einmal leer gesaugt, kann sie mit einem handelsüblichen USB-C Ladegerät wieder aufgeladen werden.</p>

<h2 class="relative group">Neue Videos hinterlegen
    <div id="neue-videos-hinterlegen" class="anchor"></div>
    
</h2>
<p>Es wird notgedrungen immer wieder zu der Situation kommen, dass neue Videos auf der SD-Karte hinterlegt werden müssen. Da das CYD in zig Varianten existiert, ist der sicherste weg den JST-Connector, vor einem erneuten <a href="/posts/locotv/#code-upload" >Code Upload</a> zu trennen, bevor der USB-C anschluss mit dem Computer verbunden wird, um <em>Voltage Backfeeding</em> zu vermeiden.</p>


  

<figure class="centered"><img src="/posts/locotv/jst-remove.png">
</figure>


<p>Generell empfiehlt es sich den Wert für <a href="/posts/locotv/#max_files-anpassen" ><code>MAX_FILES</code></a> relativ hoch, bspw. <code>300</code> anzusetzen, da so nur <strong>kein</strong> neuer <a href="/posts/locotv/#code-upload" >Code Upload</a> erfoderlich ist, wenn die reale anzahl der gespeicherten Video den Wert in der Variable <code>MAX_FILES</code> überschreitet.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Zigbee Rauchmelder</title>
      <link>https://jbetzen.net/posts/smoke-detector/</link>
      <pubDate>Tue, 30 Dec 2025 12:29:27 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/smoke-detector/</guid>
      <description>Die Marke Honeywell bietet Zigbee kompatible Rauchmelder für unter 30€ an, die in Homeassistant integriert werden können. Somit kann, im Falle eines Brandes, eine Push Benachrichtigung abgesetzt werden.</description>
      <content:encoded><![CDATA[ <p>Die Marke <strong>Honeywell</strong> bietet günstige Zigbee Rauchmelder an, die in Homeassistant integriert werden können. In diesem Beispiel nutze ich das Modell <a href="https://de.aliexpress.com/item/1005009046923297.html"  target="_blank" rel="noreferrer">Honeywell Smoke Detector</a>, das für unter 30€ bei AliExpress erworben werden kann.</p>

<h2 class="relative group">Einbindung über Phoscon App
    <div id="einbindung-über-phoscon-app" class="anchor"></div>
    
</h2>
<p>Der Rauchmelder kann über die <a href="https://www.home-assistant.io/integrations/deconz/"  target="_blank" rel="noreferrer">deCONZ Integration</a> eingebunden und muss zuvor über die Phoscon App verbunden werden. Wie bereits in allen anderen Beiträgen zum Thema <a href="/categories/zigbee/" >Zigbee Geräte</a> nutze ich dafür das offizielle <a href="https://www.home-assistant.io/integrations/deconz/#recommended-way-of-running-deconz"  target="_blank" rel="noreferrer">deCONZ Add-On</a>.</p>
<p>Der <strong>Pairing Mode</strong> des Rauchmelders wird über <a href="https://github.com/Koenkk/zigbee2mqtt/issues/136"  target="_blank" rel="noreferrer">drei schnelle kurze Klicks</a> auf den einzigen Button des Geräts aktiviert. Anschließend steht der Rauchmelder in der Phoscon App in der Sektion <strong>Sensoren</strong> zur Verfügung:</p>


  

<figure class="centered"><img src="/posts/smoke-detector/phoscon.png"
    alt="Screenshot of Smoke Detector in Phoscon App">
</figure>


<p>Der Rauchmelder erscheint in Homeassistant als <a href="https://www.home-assistant.io/integrations/binary_sensor/"  target="_blank" rel="noreferrer">Binary Sensor</a>, der detektierten Rauch mit einem State <code>on</code> anzeigt. Zusätzlich sind drei weitere Sensoren vorhanden:</p>
<ol>
<li>Ein Binary Sensor, der einen niedrigen Batteriestand mit dem State <code>on</code> anzeigt.</li>
<li>Ein Binary Sensor, der die Ausführung des Test-Modus mit dem State <code>on</code> anzeigt.</li>
<li>Ein numerischer Sensor, der den Batteriestand in Prozent anzeigt.</li>
</ol>


  

<figure class="centered"><img src="/posts/smoke-detector/sensors.png"
    alt="Screenshot of Smoke Detector Sensors in Homeassistant">
</figure>



<h2 class="relative group">Rauchmelder Gruppe erstellen
    <div id="rauchmelder-gruppe-erstellen" class="anchor"></div>
    
</h2>
<p>Da ein Rauchmelder selten alleine daherkommt, werden alle Geräte in einer Gruppe mittels <a href="https://www.home-assistant.io/integrations/group/"  target="_blank" rel="noreferrer">Group Integration</a> zusammengefasst. Dazu folgenden Eintrag in der <code>group.yaml</code> erstellen und die Namen der Entities entsprechend anpassen:</p>
<div class="highlight-wrapper"><div class="codeblock-title">group.yml</div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">rauchmelder</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Rauchmelder</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">binary_sensor.rauchmelder_flur</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">binary_sensor.rauchmelder_ballsaal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">binary_sensor.rauchmelder_schlafzimmer</span></span></span></code></pre></div></div>
<p>Der Eintrag erzeugt in Homeassistant nun eine Gruppe:</p>


  

<figure class="centered"><img src="/posts/smoke-detector/group.png"
    alt="Screenshot of grouped Smoke Detectors in Homeassistant">
</figure>


<p>Der Vorteil der Group Integration liegt darin, dass nur einer der Sensoren einen Feueralarm melden muss, um den State der Gruppe auf <code>on</code> zu setzen. Dieser State der Gruppe wird in der nun zu erstellenden Automation genutzt.</p>

<h2 class="relative group">Automation für Push-Message
    <div id="automation-für-push-message" class="anchor"></div>
    
</h2>
<p>Die folgende Automation erzeugt eine Push-Nachricht, die den Namen des Rauchmelders enthält, der einen Brand/Rauch detektiert hat. Trigger ist der State der anglegten <a href="/posts/smoke-detector/#rauchmelder-gruppe-erstellen" >Gruppe</a>. Im Falle eines Brandes werden alle Lichter des Hauses blinkend geschaltet, mittels des Parameters <code>flash: long</code> der Action <a href="https://www.home-assistant.io/integrations/light#action-lightturn_on"  target="_blank" rel="noreferrer"><code>light.turn_on</code></a>.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Feueralarm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">group.rauchmelder</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w">  </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_iphone</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;🔥 Feueralarm 🔥&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ states | selectattr(&#39;entity_id&#39;,&#39;in&#39;, state_attr(&#39;group.rauchmelder&#39;,&#39;entity_id&#39;)) | selectattr(&#39;state&#39;,&#39;eq&#39;,&#39;on&#39;) | map(attribute=&#39;name&#39;) | join(&#39;, &#39;) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;push_feueralarm&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">channel</span><span class="p">:</span><span class="w"> </span><span class="l">Alarm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">light.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">light.deconz_licht_wohnzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">light.deconz_licht_flur</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">light.deconz_licht_schlafzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">light.deconz_licht_ballsaal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">flash</span><span class="p">:</span><span class="w"> </span><span class="l">long</span></span></span></code></pre></div></div>

<h3 class="relative group">Automation Testen
    <div id="automation-testen" class="anchor"></div>
    
</h3>
<p>Hält man den Button des Rauchmelders lange gedrückt, wird der Test-Modus aktiviert. Die erzeugte Gruppe wechselt in den State <code>on</code> und die Automation wird getriggert:</p>


  

<figure class="centered"><img src="/posts/smoke-detector/push.png"
    alt="Screenshot of Push Notification indicating smoke detection">
</figure>



<h3 class="relative group">Nachricht auf Ulanzi Pixelclock
    <div id="nachricht-auf-ulanzi-pixelclock" class="anchor"></div>
    
</h3>
<p>Wer eine <a href="/tags/ulanzi-pixelclock/" >Ulanzi Pixelclock</a> im Eigenheim betreibt, kann zusätzlich auf dieser eine Warnung ausgeben:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34;Feuer!&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;fire&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;hold&#34;: true
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/notify</span></span></span></code></pre></div></div>
<p>Als <a href="https://developer.lametric.com/icons"  target="_blank" rel="noreferrer">Icon</a> nutze ich dafür die animierte Flamme mit der Icon ID <code>7756</code>, die unter dem Dateinamen <code>fire.gif</code> auf der Pixelclock abgespeichert ist:</p>


  

<figure class="centered"><img src="/posts/smoke-detector/pixelclock-icon.png"
    alt="Screenshot of Pixelclock Flame Icon">
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>Last Motion Sensor</title>
      <link>https://jbetzen.net/posts/last-motion-sensor/</link>
      <pubDate>Thu, 04 Dec 2025 11:29:27 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/last-motion-sensor/</guid>
      <description>Befindet sich im trauten Eigenheim in jedem Raum ein Bewegungssensor, kann man über diesen ermitteln, in welchem Raum zuletzt eine Bewegung detektiert wurde. In einem Single-Haushalt entspricht diese Information dem Raum, in dem man sich gerade aufhält.</description>
      <content:encoded><![CDATA[ 
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Dieser Beitrag enthält eine optimierte Methode zur Erstellung eines Last Motion Sensors, wie bereits im Post <a href="/posts/room-based-presence/">Room Based Presence mit Motion Sensoren</a> erläutert. Dieser Sensor wurde auch im Beitrag <a href="/posts/autopause-mediaplayer/">Automatisch pausierender Media Player</a> verwendet.</span>
</div>

<p>Home Assistant hat die Konfiguration für <a href="https://www.home-assistant.io/integrations/template/#sensor"  target="_blank" rel="noreferrer">Template Sensoren</a> überarbeitet und mich mit haufenweise Warnmeldungen überschüttet. Siehe: <a href="https://community.home-assistant.io/t/deprecation-of-legacy-template-entities-in-2025-12/955562"  target="_blank" rel="noreferrer">Deprecation of legacy template entities in 2025.12</a></p>
<p>Die <em>Deprecation Warnings</em> betrafen unter Anderem den Post <a href="/posts/room-based-presence/">Room Based Presence mit Motion Sensoren</a>, was ich als Anlass genommen habe das Setup neu zu überdenken. Da ich vor kurzem <a href="/posts/ilogic-serienende/" >arbeitsfrei geworden</a> bin, ist die Zeit dafür vorhanden.</p>


  

<figure class="centered"><img src="/posts/last-motion-sensor/arno.png"
    alt="Ein Bild von Arno Dübel. Dem Schutzpatrin der Hedonisten und Hartzer.">
</figure>



<h2 class="relative group">Vorhaben
    <div id="vorhaben" class="anchor"></div>
    
</h2>
<p>Befindet sich im trauten Eigenheim ein Bewegungssensor in jedem Raum, kann man über diese ermitteln, in welchem Raum zuletzt eine Bewegung erkannt wurde. In einem Single-Haushalt entspricht diese Information dem Raum, in dem man sich gerade aufhält. Diese Information kann zielführend genutzt werden, wie bereits im Post <a href="/posts/room-based-presence/">Room Based Presence mit Motion Sensoren</a> erläutert wurde.</p>
<p>An meinem aktuellen Setup hat mir nie gefallen, dass ich auf eine zusätzliche Custom Component namens <a href="https://github.com/snarky-snark/home-assistant-variables"  target="_blank" rel="noreferrer">home-assistant-variables</a> angewiesen war. Im Kern war dies immer eine Lösung, die zuverlässig funktioniert hat und mich vor der Auseinandersetzung mit <a href="https://www.home-assistant.io/integrations/template/#sensor"  target="_blank" rel="noreferrer">Template Sensoren</a> freigesprochen hat.</p>
<p>Template Sensoren nutzen eine sperrige Syntax aus der Hölle, die durch die Python Template Engine <a href="https://github.com/noirbizarre/jinja2"  target="_blank" rel="noreferrer">Jinja2</a> bereitgestellt wird. Im <a href="https://community.home-assistant.io"  target="_blank" rel="noreferrer">Home Assistant Forum</a> gibt es herausstechende Planetenbürger wie <a href="https://community.home-assistant.io/u/petro/summary"  target="_blank" rel="noreferrer">Petro</a>, die diese Syntax mit einer Leichtigkeit beherrschen, als würde man Pfannkuchen backen. Zu diesen Menschen zähle ich jedoch nicht. Nichtsdestotrotz habe ich noch einmal das Forum durchforstet und eine funktionierende Lösung in diesem Thread <a href="https://community.home-assistant.io/t/quick-sensor-template-question-last-active-motion-sensor/396458/1"  target="_blank" rel="noreferrer">Quick sensor/template question - Last active motion sensor</a> gefunden.</p>
<p>Aus einer vordefinierten Liste an <a href="/categories/motion-sensor/" >Bewegungssensoren</a> soll jener ermittelt werden, der als letztes den State <code>on</code> gemeldet hat. Die Bewegungssensoren sind naturgemäß als <a href="https://www.home-assistant.io/integrations/binary_sensor/"  target="_blank" rel="noreferrer">Binary Sensors</a> in Home Assistant eingebunden, das sie lediglich die States <code>on</code> und <code>off</code> besitzen können. Bewegung wird detektiert oder eben nicht.</p>
<p>Als Beispiel eingesetzter Bewegungsmelder werden folgende Entities und Namen verwendet:</p>
<table>
  <thead>
      <tr>
          <th>Entity Name</th>
          <th>Friendly Name</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>binary_sensor.hue_motion_kueche</code></td>
          <td>Hue Motion Küche</td>
      </tr>
      <tr>
          <td><code>binary_sensor.hue_motion_schlafzimmer</code></td>
          <td>Hue Motion  Schlafzimmmer</td>
      </tr>
      <tr>
          <td><code>binary_sensor.hue_motion_wohnzimmer</code></td>
          <td>Hue Motion Wohnzimmer</td>
      </tr>
      <tr>
          <td><code>binary_sensor.hue_motion_flur</code></td>
          <td>Hue Motion Flur</td>
      </tr>
  </tbody>
</table>


  

<figure class="centered"><img src="/posts/last-motion-sensor/entities.png">
</figure>



<h2 class="relative group">Last Motion Sensor
    <div id="last-motion-sensor" class="anchor"></div>
    
</h2>
<p>Der Code des Template Sensors macht folgendes:</p>
<ul>
<li>Er definiert die Entity Names der Bewegungssensoren in einer Liste <code>sensors</code>.</li>
<li>Er ermittelt alle Entities in der Liste <code>sensors</code>, die den State <code>on</code> besitzen und speichert diese als eine Liste.</li>
<li>Wenn mehr als ein Bewegungssensor in dieser Liste den State <code>on</code> besitzt, wird der Name des zuletzt getriggerten Sensors zurückgegeben.</li>
<li>Für meinen persönlichen Fall habe ich noch einen <code>regex_replace()</code> gesetzt, der mir den Präfix <code>Hue Motion </code> aus dem Rückgabewert entfernt. Somit bleibt lediglich der Raumname über, bspw. <code>Hue Motion Flur</code> wird reduziert auf <code>Flur</code>.</li>
<li>Für jeden Raum wird ein <a href="https://www.home-assistant.io/integrations/template/#common-device-configuration-options"  target="_blank" rel="noreferrer">Custom Icon</a> definiert, das sich dynamisch anpasst, je nachdem wo zuletzt eine Bewegung registriert wurde.</li>
</ul>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">sensor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">last_motion</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {%- set sensors = &#39;binary_sensor.hue_motion_flur&#39;, &#39;binary_sensor.hue_motion_kueche&#39;, &#39;binary_sensor.hue_motion_schlafzimmer&#39;, &#39;binary_sensor.hue_motion_wohnzimmer&#39; %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% set sensors_on = expand(sensors) | selectattr(&#39;state&#39;, &#39;eq&#39;, &#39;on&#39;) | map(attribute=&#39;name&#39;) | list %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% if sensors_on | count &gt; 0 %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            {{ expand(sensors) | selectattr(&#39;state&#39;, &#39;eq&#39;, &#39;on&#39;) | sort(reverse=true, attribute=&#34;last_changed&#34;) | map(attribute=&#39;name&#39;) | list | first | default(none) | regex_replace(find=&#39;Hue Motion &#39;, replace=&#39;&#39;) }}
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            {{ expand(sensors) | sort(reverse=true, attribute=&#34;last_changed&#34;) | map(attribute=&#39;name&#39;) | list | first | default(none) | regex_replace(find=&#39;Hue Motion &#39;, replace=&#39;&#39;) }}
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% endif %}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% if is_state(&#39;sensor.last_motion&#39;, &#39;Küche&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            mdi:food-variant
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif is_state(&#39;sensor.last_motion&#39;, &#39;Wohnzimmer&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            mdi:sofa
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif is_state(&#39;sensor.last_motion&#39;, &#39;Flur&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            mdi:cube-unfolded
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif is_state(&#39;sensor.last_motion&#39;, &#39;Schlafzimmer&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            mdi:bed
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            mdi:account-question
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% endif %}</span></span></span></code></pre></div></div>
<p>Der Sensor ersetzt nahtlos das alte Setup, reduziert unnötige Komplexität und liefert identische Rückgabewerte, wie die alte Lösung mittels <a href="https://github.com/snarky-snark/home-assistant-variables"  target="_blank" rel="noreferrer">home-assistant-variables</a>:</p>


  

<figure class="centered"><img src="/posts/last-motion-sensor/result.gif"
    alt="Resultat des Sensors in der HASS UI">
</figure>


<p>Er kann für viele Automationen genutzt werden, bspw. wenn ein Media-Player bei aktiver Wiedergabe <a href="/posts/autopause-mediaplayer/" >pausiert</a> werden soll, wenn man den Raum verlässt oder aber, wenn das Licht in einem Raum nicht ausgeschaltet werden soll, wenn man sich darin aufhält.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Ende der Artikelserie</title>
      <link>https://jbetzen.net/posts/ilogic-serienende/</link>
      <pubDate>Fri, 14 Nov 2025 12:48:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-serienende/</guid>
      <description>Die Serie zum Thema iLogic und Inventor wird mit diesem Beitrag ihr vorläufiges Ende finden. Ich habe diesen Monat meinen Job verloren und nur noch eingeschränkten Zugriff auf Autodesk Produkte und deren Lizenzen.</description>
      <content:encoded><![CDATA[ <p>Dieser Post gehört wohl in die Kategorie <em><strong>In eigener Sache</strong></em> und kann vorerst notgedrungen nur das Ende der <a href="/series/inventor-ilogic/" >Artikelserie iLogic mit Autodesk Inventor</a> auf diesem Blog bedeuten.</p>
<p><strong>Ich habe diesen Monat unerwartet meinen Job als Konstrukteur und CAD Power User verloren und werde mich beruflich neu umschauen müssen.</strong></p>
<p>Derzeit habe ich nur noch eingeschränkten Zugriff auf Autodesk Vault und Inventor, was ich selber schwer bedauere, da ich eine komplette Serie zum Umgang der <a href="https://aps.autodesk.com/developer/overview/vault"  target="_blank" rel="noreferrer">Vault API</a> mittels PowerShell und der neuen spannenden <a href="https://aps.autodesk.com/developer/overview/vault-data-api"  target="_blank" rel="noreferrer">Vault Data API</a> in der Mache hatte. Dies kann und wird nun nicht mehr stattfinden können.</p>
<p>Ob ich einen neuen Arbeitgeber finde, der Autodesk Produkte produktiv einsetzt, ist für mich derzeit nicht absehbar. Primär gilt es für mich beruflich nach Vorne zu schauen. Zeitgleich lohnt aber auch ein Rückblick auf die existierenden <a href="/series/inventor-ilogic/" ><strong>25 Beiträge</strong></a> der iLogic Artikelserie, die über die letzten 3 Jahre entstanden sind, denn sie vermitteln gutes Basiswissen für Anfänger und auch Anwendungsfälle, die weit über iLogic Funktionalitäten hinausgehen.</p>

<h2 class="relative group">Wie kam es zu der Serie?
    <div id="wie-kam-es-zu-der-serie" class="anchor"></div>
    
</h2>
<p>Ich habe damals in meiner Firma nicht aktiv darum gebeten die iLogics zu schreiben und zu betreuen, sondern bin notgedrungen in diese Rolle geraten. Ursprünglich wurde der Code durch den Administrator der Vault Datenbank geschrieben und auch per Vault Startroutine an die User ausgerollt.</p>
<p>Es stellte sich bedauerlicherweise zunehmend heraus, dass der Administrator weder die Fähigkeiten, noch den erforderlichen Willen besaß, sich in das Thema iLogic hineinzudenken, geschweige denn die persönliche Größe, diesen Zustand offen zuzugeben und Verantwortung für seinen Code zu übernehmen.</p>
<p><strong>Das Resultat waren:</strong></p>
<ul>
<li>unzählige tagesaktuelle Fehlermeldungen, die bei mir als CAD Power User aufliefen, und kontinuierlichen User Support hervorriefen</li>
<li>iLogics, die sich selber widersprachen und gegeneinander arbeiteten, da konzeptfrei, strukturlos und tagesaktuell ohne Versionshistorie gearbeitet wurde</li>
<li>iLogics, die unzählige unnötige Revisionen erforderten, da sie automatisiert fehlerhafte Produktdaten erzeugten, die anschließend in die Freigabe gingen</li>
<li>iLogics, die kontinuierlich <em>on the fly</em> modifiziert wurden, ohne die Nutzer oder mich in Kenntnis zu setzen</li>
</ul>
<p>Über zwei Jahre hinweg blieb jede Verbesserung der Situation aus. Ich nahm mich der Thematik an, weil es sonst keiner getan hätte und weil es mir zunehmend zutiefst zuwider war, tagesaktuellen Support für Fehlmeldungen zu leisten, die ich selber nicht produziert hatte.</p>
<p>Im Ingenieursstudium hatte ich zwei Semester VBA-Programmierung mit Excel. Einen realen Anwendungsfall habe ich dafür im Berufsleben nie gefunden. Daher war die Auseinandersetzung mit dem Thema iLogic eine willkommene Abwechslung zum Alltag als Konstrukteur, da die Thematik Realbezug aufwies und iLogic <a href="/posts/ilogic-010-ilogic-vba-vbnet/" >Schnittmengen mit VBA</a> besitzt.</p>

<h2 class="relative group">Farewell
    <div id="farewell" class="anchor"></div>
    
</h2>
<p>Rückwirkend betrachtet bedeutet mir die Serie viel, da sie für mich selber eine kleine chronologische Dokumentation meines beruflichen Schaffens darstellt und zeitgleich Zeuge meiner persönlichen Lernkurve ist. Nahezu alle Beiträge waren inspiriert durch tagesaktuelle Probleme oder Hürden, die es zu meistern galt. Ich habe diverse Mails anderer Konstrukteure erhalten, die sich ebenfalls an iLogic versucht haben und fand den Gedanken immer schön, dass meine Serie auch anklang in der Welt gefunden hat.</p>
<p>An dieser Stelle möchte ich auch jeden Konstrukteur ermutigen, sich offen dem Thema iLogic zu stellen, denn es ist ein mächtiges Werkzeug, das Zeitersparnis produzieren kann und zeitgleich lernt man die Funktionsweise von Inventor aus Sicht der Programmierschnittstelle kennen. Die Schnittstelle zwischen Engineering und Programmierung ist ein spannendes Themenfeld, das in vielen Unternehmen bis heute noch nicht ausreichend wertgeschätzt, und in Zukunft zunehmend an Bedeutung gewinnen wird.</p>
<p>Sollte ich in Zukunft erneut beruflich mit Inventor arbeiten, werde ich die Serie ggf. fortsetzen. Bis dahin bleibt jedoch nur eins zu sagen: <em>&ldquo;Adieu iLogic!&rdquo;</em></p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Datums- und Zeitangaben</title>
      <link>https://jbetzen.net/posts/ilogic-date-time/</link>
      <pubDate>Fri, 14 Nov 2025 10:48:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-date-time/</guid>
      <description>iLogic bietet die Möglichkeit mit Datums- und Zeitangaben zu arbeiten. Dies kann sinnvoll sein, wenn bspw. berechnet werden soll, vor wie vielen Tagen eine Datei erzeugt wurde oder um zu ermitteln, wann eine iLogic das letzte mal gelaufen ist.</description>
      <content:encoded><![CDATA[ <p>iLogic bietet im <a href="/posts/ilogic-editor/" >iLogic Editor</a> einige vordefinierte Snippets für die Arbeit mit Datums- und Zeitangaben:</p>


  

<figure class="centered"><img src="/posts/ilogic-date-time/date-time-snippets.png"
    alt="iLogic Snippets für Datums- und Zeitangaben">
</figure>


<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Methode</th>
          <th style="text-align: center">Datentyp</th>
          <th>Beschreibung von Autodesk</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Now()</code></td>
          <td style="text-align: center">String</td>
          <td>Returns a string containing Current Date and Time.</td>
      </tr>
      <tr>
          <td><code>DateString</code></td>
          <td style="text-align: center">String</td>
          <td>Returns the current Date.</td>
      </tr>
      <tr>
          <td><code>TimeString</code></td>
          <td style="text-align: center">String</td>
          <td>Returns the current Time.</td>
      </tr>
  </tbody>
</table>
</div>
<p>Die Ausgabeformate sind in Teilen jedoch abhängig von global gesetzten <em>Region Settings</em> im Betriebssystem und unterliegen auch <a href="https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo?view=net-8.0"  target="_blank" rel="noreferrer">kulturellen Gepflogenheiten</a>. So kennt jeder die amerikanischen Unsitten ein Datum im Format <a href="https://de.wikipedia.org/wiki/Terroranschl%C3%A4ge_am_11._September_2001"  target="_blank" rel="noreferrer">MM-DD-YYYY</a> anzugeben und auch sonst alles zu geben, um nicht mit dem metrischen System in Kontakt zu kommen:</p>


  

<figure class="centered"><img src="/posts/ilogic-date-time/murica.jpg"
    alt="Bill Gates zum Launch von Windows XP vor einem Schild mit amerikanischen Datumsangabe">
</figure>



<h2 class="relative group">iLogic Snippets
    <div id="ilogic-snippets" class="anchor"></div>
    
</h2>
<p>Die eingebauten Snippets können einfach über den <a href="/posts/ilogic-logger/" >iLogic Logger</a> getestet werden und liefern ohne zusätzliche Parameter folgende Ausgaben:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sNow</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">Now</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDate</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">DateString</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sTime</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">TimeString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;&#39;Now()&#39; liefert: {0}&#34;</span><span class="p">,</span> <span class="n">sNow</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;&#39;DateString&#39; liefert: {0}&#34;</span><span class="p">,</span> <span class="n">sDate</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;&#39;TimeString&#39; liefert: {0}&#34;</span><span class="p">,</span> <span class="n">sTime</span><span class="p">)</span></span></span></code></pre></div></div>
<p><strong>Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-date-time/snippets-output.png">
</figure>



<h2 class="relative group">Ausgabe Formatieren
    <div id="ausgabe-formatieren" class="anchor"></div>
    
</h2>
<p>Zur Formatierung der Zeitangaben stehen <a href="https://learn.microsoft.com/en-us/dotnet/api/system.datetime?view=net-9.0#methods"  target="_blank" rel="noreferrer">sämtliche Methoden</a> des <a href="https://learn.microsoft.com/en-us/dotnet/api/system.datetime.now?view=net-9.0"  target="_blank" rel="noreferrer">DateTime Namespace</a> zur Verfügung. Am geläufigsten unter iLogic Code-Beispielen im Inventor Forum ist die Methode <a href="https://learn.microsoft.com/en-us/dotnet/api/system.datetime.tostring?view=net-9.0"  target="_blank" rel="noreferrer">DateTime.ToString</a>. Mit ihr können die Ausgaben leicht mittels <a href="https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings#table-of-format-specifiers"  target="_blank" rel="noreferrer">Date &amp; Time Format Specifiers</a> den eigenen Ansprüchen angepasst werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="n">Now</span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="s">&#34;dd.MM.yyyy&#34;</span><span class="p">)</span>        <span class="c">&#39; 23.05.2025
</span></span></span><span class="line"><span class="cl"><span class="n">Now</span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="s">&#34;MMMM d, yyyy&#34;</span><span class="p">)</span>      <span class="c">&#39; May 23, 2025
</span></span></span><span class="line"><span class="cl"><span class="n">Now</span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="s">&#34;dddd, MMMM dd&#34;</span><span class="p">)</span>     <span class="c">&#39; Friday, May 23
</span></span></span><span class="line"><span class="cl"><span class="n">Now</span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="s">&#34;yyyy-MM-dd HH:mm&#34;</span><span class="p">)</span>  <span class="err">&#39;</span> <span class="n">2025</span><span class="o">-</span><span class="n">05</span><span class="o">-</span><span class="n">23</span> <span class="n">14</span><span class="p">:</span><span class="n">30</span></span></span></code></pre></div></div>
<p>Auch stehen die Methoden <a href="https://learn.microsoft.com/en-us/dotnet/api/system.datetime.tolongdatestring?view=net-9.0"  target="_blank" rel="noreferrer">ToLongDateString</a>, <a href="https://learn.microsoft.com/en-us/dotnet/api/system.datetime.toshortdatestring?view=net-9.0"  target="_blank" rel="noreferrer">ToShortDateString</a>, <a href="https://learn.microsoft.com/en-us/dotnet/api/system.datetime.toshorttimestring?view=net-9.0"  target="_blank" rel="noreferrer">ToShortTimeString</a> und <a href="https://learn.microsoft.com/en-us/dotnet/api/system.datetime.tolongtimestring?view=net-9.0"  target="_blank" rel="noreferrer">ToLongTimeString</a> zur Verfügung:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">Now</span><span class="p">.</span><span class="n">ToShortTimeString</span><span class="p">)</span>  <span class="c">&#39; 16:02
</span></span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">Now</span><span class="p">.</span><span class="n">ToLongTimeString</span><span class="p">)</span>   <span class="c">&#39; 16:02:39
</span></span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">Now</span><span class="p">.</span><span class="n">ToShortDateString</span><span class="p">)</span>  <span class="c">&#39; 23.05.2025
</span></span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">Now</span><span class="p">.</span><span class="n">ToLongDateString</span><span class="p">)</span>   <span class="err">&#39;</span> <span class="n">14</span><span class="p">.</span> <span class="n">Mai</span> <span class="n">2025</span></span></span></code></pre></div></div>
<p>Das Problem beider Beispiele wird schnell ersichtlich, denn sie wurden auf zwei unterschiedlich konfigurierten Computern ausgeführt und berücksichtigen die <strong>Region Settings</strong> des Betriebssystems:</p>


  

<figure class="centered"><img src="/posts/ilogic-date-time/region-settings.png"
    alt="Windows Region Settings">
</figure>


<p>Monatsangaben erscheinen bspw. einmal auf Englisch (<code>May</code>) und im zweiten Beispiel auf Deutsch (<code>Mai</code>). Dies kann zu Problemen in internationalen Unternehmen führen, da die gewohnten Ausgabeformate für Zeit- und Datumsangaben kulturell variieren und der gemeine Nutzer sein Betriebssystem in der eigenen Landessprache betreibt.</p>
<p>Möchte man ungeachtet der Region Settings immer ein einheitliches Ausgabeformat erzwingen, muss zusätzlich die <a href="https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo?view=net-9.0"  target="_blank" rel="noreferrer">CultureInfo Class</a> aus dem <a href="https://learn.microsoft.com/en-us/dotnet/api/system.globalization?view=net-9.0"  target="_blank" rel="noreferrer">System.Globalization Namespace</a> hinzugezogen werden.</p>
<p>Das folgende Beispiel formatiert die Ausgaben gemäß amerikanischer Notation (<code>en-US</code>):</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Imports</span> <span class="nn">System.Globalization</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Beispieldatum: 28.05.2024
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">dDate</span> <span class="ow">As</span> <span class="kt">Date</span> <span class="o">=</span> <span class="k">New</span> <span class="kt">Date</span><span class="p">(</span><span class="n">2024</span><span class="p">,</span> <span class="n">05</span><span class="p">,</span> <span class="n">28</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">culture</span> <span class="ow">As</span> <span class="n">CultureInfo</span> <span class="o">=</span> <span class="n">CultureInfo</span><span class="p">.</span><span class="n">GetCultureInfo</span><span class="p">(</span><span class="s">&#34;en-US&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDateWithoutCulture</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">dDate</span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="s">&#34;MMMM d, yyyy&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDateCulture</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">dDate</span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="s">&#34;MMMM d, yyyy&#34;</span><span class="p">,</span> <span class="n">culture</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Ohne Culture Info: {0}&#34;</span><span class="p">,</span> <span class="n">sDateWithoutCulture</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Mit Culture Info: {0}&#34;</span><span class="p">,</span> <span class="n">sDateCulture</span><span class="p">)</span></span></span></code></pre></div></div>
<p><strong>Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-date-time/cultureinfo-output.png"
    alt="Ausgabe mit und ohne CultureInfo">
</figure>



<h2 class="relative group">Zeitdifferenzen berechnen
    <div id="zeitdifferenzen-berechnen" class="anchor"></div>
    
</h2>
<p>Zeitdifferenzen können ebenfalls einfach berechnet werden. Das folgende Beispiel ermittelt die vergangenen Tage zwischen den Daten <code>05.10.2025</code> und <code>14.11.2025</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">dStartDate</span> <span class="ow">As</span> <span class="kt">Date</span> <span class="o">=</span> <span class="k">New</span> <span class="kt">Date</span><span class="p">(</span><span class="n">2025</span><span class="p">,</span> <span class="n">10</span><span class="p">,</span> <span class="n">05</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">dEndDate</span> <span class="ow">As</span> <span class="kt">Date</span> <span class="o">=</span> <span class="k">New</span> <span class="kt">Date</span><span class="p">(</span><span class="n">2025</span><span class="p">,</span> <span class="n">11</span><span class="p">,</span> <span class="n">14</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">iDaysDifference</span> <span class="ow">As</span> <span class="kt">Integer</span> <span class="o">=</span> <span class="p">(</span><span class="n">dEndDate</span> <span class="o">-</span> <span class="n">dStartDate</span><span class="p">).</span><span class="n">Days</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Differenz: {0} Tage&#34;</span><span class="p">,</span> <span class="n">iDaysDifference</span><span class="p">)</span></span></span></code></pre></div></div>
<p><strong>Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-date-time/datediff-output.png"
    alt="Augabe der Differenzberechnung zweiter Daten">
</figure>



<h2 class="relative group">Zeiten Stoppen
    <div id="zeiten-stoppen" class="anchor"></div>
    
</h2>
<p>Man kann zusätzlich mit der <a href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=net-9.0"  target="_blank" rel="noreferrer">StopWatch Class</a> die Zeit messen, die für die Ausführung einer iLogic benötigt wird. Das kann sinnvoll sein, wenn man unterschiedliche Lösungsansätze für ein Problem gegeneinander auf Performance testen möchte.</p>
<p>Das folgende Beispiel ermittelt, wie lang eine Schleife mit dem Startwert <code>1</code>, dem Endwert <code>5</code>, einem Inkrement von <code>1</code> und einem künstlichem Delay von <code>500 ms</code> mittels <a href="https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.sleep?view=net-9.0"  target="_blank" rel="noreferrer">Thread.Sleep Method</a> zur Ausführung benötigt.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sw</span> <span class="ow">As</span> <span class="k">New</span> <span class="n">Stopwatch</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">sw</span><span class="p">.</span><span class="n">Start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="n">i</span> <span class="ow">As</span> <span class="kt">Integer</span> <span class="o">=</span> <span class="n">1</span> <span class="k">To</span> <span class="n">5</span> <span class="k">Step</span> <span class="n">1</span>
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Schleife {0}&#34;</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">Threading</span><span class="p">.</span><span class="n">Thread</span><span class="p">.</span><span class="n">Sleep</span><span class="p">(</span><span class="n">500</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sw</span><span class="p">.</span><span class="n">Stop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Elapsed Time: &#34;</span> <span class="o">&amp;</span> <span class="n">sw</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span> <span class="o">&amp;</span> <span class="s">&#34; ms&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p><strong>Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-date-time/stopwatch-output.png"
    alt="Output einer StopWatch Messung">
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Dateien und Dateipfade</title>
      <link>https://jbetzen.net/posts/ilogic-dateien-pfade/</link>
      <pubDate>Wed, 12 Nov 2025 18:44:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-dateien-pfade/</guid>
      <description>Geöffnete Dokumente in Inventor haben eine Repräsentation als Datei im lokalen Dateisystem. Diese Dateien haben Dateiendungen, Dateinamen und Pfade, die einfach mittels iLogic ausgelesen und genutzt werden können.</description>
      <content:encoded><![CDATA[ <p>Die Arbeit mit Dateien und Pfaden ist in iLogic ein leichtes Spiel. Es stehen diverse vordefinierte Snippets bereit. Zusätzlich kann auch die gesamte Bandbreite an Funktionen des Namespace <a href="https://learn.microsoft.com/en-us/dotnet/api/system.io?view=net-9.0"  target="_blank" rel="noreferrer">System.IO</a> aus VB.NET genutzt werden.</p>
<p>Als Beispiel dient eine in Inventor geöffnete Datei mit dem Namen <code>LST_Bender.ipt</code>. Anhand dieser Datei werden die beiden unterschiedlichen Herangehensweisen erläutert.</p>

<h2 class="relative group">iLogic Snippets
    <div id="ilogic-snippets" class="anchor"></div>
    
</h2>
<p>iLogic stellt vordefinierte Snippets bereit, die den Umgang mit Dateien und Pfaden spielerisch leicht gestalten. Die Snippets sind in der Dokumentation in der Sektion <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-622DED58-5CCB-423C-89E6-57ABBEB139A5"  target="_blank" rel="noreferrer">Document Functions Reference</a> gelistet. Zur Verfügung stehen:</p>
<table>
  <thead>
      <tr>
          <th>Snippet</th>
          <th>Funktionsweise</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>ThisDoc.FileName(False)</code></td>
          <td>Liefert den Dateinamen ohne Dateiendung.</td>
      </tr>
      <tr>
          <td><code>ThisDoc.FileName(True)</code></td>
          <td>Liefert den Dateinamen mit Dateiendung.</td>
      </tr>
      <tr>
          <td><code>ThisDoc.Path</code></td>
          <td>Liefert den Dateipfad, in dem die Datei abgelegt ist.</td>
      </tr>
      <tr>
          <td><code>ThisDoc.PathAndFileName(False)</code></td>
          <td>Liefert einen absoluten Pfad, inkl. Dateiname, jedoch ohne Dateiendung.</td>
      </tr>
      <tr>
          <td><code>ThisDoc.PathAndFileName(True)</code></td>
          <td>Liefert einen absoluten Pfad, inkl. Dateiname und Dateiendung.</td>
      </tr>
  </tbody>
</table>
<p>Die zurückgelieferten Werte können einfach im <a href="/posts/ilogic-logger/" >iLogic Logger</a> betrachtet werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">False</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">ThisDoc</span><span class="p">.</span><span class="n">PathAndFileName</span><span class="p">(</span><span class="k">False</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">ThisDoc</span><span class="p">.</span><span class="n">PathAndFileName</span><span class="p">(</span><span class="k">True</span><span class="p">))</span></span></span></code></pre></div></div>
<p><strong>Das Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-dateien-pfade/logger-output.png"
    alt="Logger Output">
</figure>



<h2 class="relative group">System.IO.Path
    <div id="systemiopath" class="anchor"></div>
    
</h2>
<p>Die eingebauten iLogic Snippets können in VB.NET auch identisch über die Klasse <a href="https://learn.microsoft.com/en-us/dotnet/api/system.io.path.changeextension?view=net-9.0"  target="_blank" rel="noreferrer">System.IO.Path</a> ermittelt werden. Die Klasse bietet zusätzlich noch deutlich mehr Funktionen, bspw. das erfragen der Dateiendung.</p>
<p>Genutzt wird das Property <code>FullFileName</code>, das letzten Endes dem iLogic Snippet <code>ThisDoc.PathAndFileName(True)</code> entspricht und eine absolute Pfadangabe als String liefert:</p>


  

<figure class="centered"><img src="/posts/ilogic-dateien-pfade/api-fullfilename.png"
    alt="API Property FullFileName">
</figure>


<p>Der folgende Code liefert den identischen Logger Output, wie im Beispiel der <a href="/posts/ilogic-dateien-pfade/#ilogic-snippets" >iLogic Snippets</a>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sFullFileName</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">FullFileName</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileNameWithoutExtension</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileName</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetDirectoryName</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">Combine</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetDirectoryName</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">),</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileNameWithoutExtension</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFullPath</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">))</span></span></span></code></pre></div></div>
<p>Für das iLogic Snippet <code>ThisDoc.PathAndFileName(False)</code> steht keine native VB.NET-Methode zur Verfügung. Das gewünschte Ergebnis kann aber mittel der Methode <a href="https://learn.microsoft.com/en-us/dotnet/api/system.io.path.combine?view=net-9.0"  target="_blank" rel="noreferrer">Path.Combine</a> erzielt werden.</p>

<h2 class="relative group">Anwendungsfall
    <div id="anwendungsfall" class="anchor"></div>
    
</h2>
<p>Ein naheliegender Anwendungsfall in größeren Baugruppen ist die Prüfung, ob alle verbauten Komponenten in den korrekten Ablagepfaden - bspw. der Ordnerstruktur in Vault - abgelegt sind. Da die Pfadangaben Strings sind, kann einfach mit der Methode <a href="https://learn.microsoft.com/de-de/dotnet/api/system.string.contains?view=net-9.0"  target="_blank" rel="noreferrer">String.Contains</a> geprüft werden, ob der gewünschte Ablagepfad identisch mit dem realen Ablagepfad der aktiven Datei ist.</p>
<p>Das folgende Beispiel prüft, ob der <code>FullFileName</code> die Pfadangabe <code>C:\Users\horst\Documents\Inventor\</code> enthält:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sFullFileName</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">FullFileName</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">sFullFileName</span><span class="p">.</span><span class="n">Contains</span><span class="p">(</span><span class="s">&#34;C:\Users\horst\Documents\Inventor\&#34;</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Der Pfad der Datei &#39;{0}&#39; ist korrekt.&#34;</span><span class="p">,</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileName</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">Else</span>
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">&#34;Der Pfad der Datei &#39;{0}&#39; ist nicht korrekt.&#34;</span><span class="p">,</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileName</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span></span></span></code></pre></div></div>
<p>Eine solche Prüfung kann auch über alle verbauten Komponenten einer Baugruppe erfolgen. Dazu muss die <code>If-Else-Schleife</code> nur auf die <code>AllReferencedDocuments</code> angewendet werden. Mehr zu diesem Thema ist im vorangegangen Post <a href="/posts/ilogic-referenced-docs/">iLogic: Referenzierte Dokumente in Baugruppen</a> zu finden.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Referenzierte Dokumente in Baugruppen</title>
      <link>https://jbetzen.net/posts/ilogic-referenced-docs/</link>
      <pubDate>Tue, 11 Nov 2025 17:44:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-referenced-docs/</guid>
      <description>Die Inventor API stellt in Baugruppen verschiedene Wege und Möglichkeiten bereit Auskunft über verbaute Dokumente in Baugruppen zu geben. Dabei geht es im Wesentlichen darum, das Konzept von Occurrences und Referenced Files zu verstehen.</description>
      <content:encoded><![CDATA[ <p>Die Inventor API erlaubt es mit einfachen Mitteln über verbaute Komponenten in Baugruppen zu iterieren. Dies kann nützlich sein, wenn eine Konstruktion finalisiert wird und man bspw. Prüfen möchte, ob alle verbauten Komponenten sauber gepflegte Properties, Stücklistenoptionen, etc. gesetzt haben.</p>
<p>Autodesk hat eine ausführliche Hilfeseite, die sich dem Thema widmet und das programmspezifische Wording erläutert: <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-0123304B-EC33-4430-B4F1-CBF225CB5B8F"  target="_blank" rel="noreferrer">File and Document References</a></p>
<p>Grundsätzlich wird unterschieden zwischen <code>File Obejects</code> und <code>Document Objects</code>. Ersteres behandelt die referenzierten Komponenten auf Datei- und Arbeitsspeicherebene, zweiteres auf Dokumentenebene. In diesem Post werden die <code>Document Objects</code> genutzt, genauer gesagt die Properties:</p>
<ul>
<li><a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=Document_AllReferencedDocuments"  target="_blank" rel="noreferrer">AllReferencedDocuments</a>, die rekursiv Komponenten aller Baugruppenebenen auflistet.</li>
<li><a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=Document_ReferencedDocuments"  target="_blank" rel="noreferrer">ReferencedDocuments</a>, die nur die erste Ebene verbauter Komponenten auflistet.</li>
</ul>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/document-object.png"
    alt="Tabelle mit API Endpunkten für das Document Object">
</figure>


<p>Zusätzlich gibt es noch das Konzept der <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=ComponentDefinition_Occurrences"  target="_blank" rel="noreferrer">Occurrences</a>, das Inventor unter der <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-ComponentDefinition"  target="_blank" rel="noreferrer">Component Definition</a> eines Baugruppendokuments bereit stellt. Die Properties <code>AllReferencedDocuments</code> und <code>ReferencedDocuments</code> liefern immer eine eindeutige Liste mit referenzierten Dokumenten, können aber <strong>keine</strong> Auskunft darüber geben, wie oft eine Komponente verbaut ist. Dies kann für die erste Baugruppenebene über die <code>Occurrences</code> erfolgen.</p>

<h2 class="relative group">Beispielbaugruppe
    <div id="beispielbaugruppe" class="anchor"></div>
    
</h2>
<p>Für die folgenden drei Code-Beispiele wird die Beispielhafte Baugruppe <code>Nested Assembly.iam</code> mit insgesamt <strong>3</strong> verschachtelten Baugruppenebenen und <strong>7</strong> einzigarten Komponenten verwendet.</p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/beispielbaugruppe.png"
    alt="Ansicht Model Browser einer Beispielbaugruppe in Inventor">
</figure>


<p>Der strukturelle Baugruppenaufbau als <a href="https://tree.nathanfriend.com/?s=%28%27options!%28%27fancy!true~fullPath5~trailingSlash5~rootDot5%29~7%28%277%27Nest2Weld2-3-40-Alphabet8--A0--B66034C%27%29~version!%271%27%29*%5Cn--%20%200C*2ed%20Assembly83SheetMetal904Revolved95!false60LST_Bender7source!8.iam*9PartC.ipt%01C987654320-*"  target="_blank" rel="noreferrer">Tree Diagram</a>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Nested Assembly.iam
</span></span><span class="line"><span class="cl">├── Welded Assembly.iam
</span></span><span class="line"><span class="cl">│   ├── SheetMetalPart.ipt
</span></span><span class="line"><span class="cl">│   ├── RevolvedPart.ipt
</span></span><span class="line"><span class="cl">│   └── Alphabet.iam
</span></span><span class="line"><span class="cl">│       ├── A.ipt
</span></span><span class="line"><span class="cl">│       └── B.ipt
</span></span><span class="line"><span class="cl">├── LST_Bender.ipt
</span></span><span class="line"><span class="cl">├── LST_Bender.ipt
</span></span><span class="line"><span class="cl">├── SheetMetalPart.ipt
</span></span><span class="line"><span class="cl">└── RevolvedPart.ipt</span></span></code></pre></div></div>

<h2 class="relative group">Code-Beispiele
    <div id="code-beispiele" class="anchor"></div>
    
</h2>
<p>Die folgenden drei Beispiele zeigen sowohl die Methodik mittels <code>AllReferencedDocuments</code>, <code>ReferencedDocuments</code> und <code>Occurrences</code>. Es gilt zu beachten, wie alle drei Methoden eine unterschiedliche Anzahl an referenzierten Dokumenten im Property <code>Count</code> zurückliefern.</p>
<p>Zum besseren Verständnis werden für alle drei Methoden identischer Code demonstriert, der für jedes referenzierte Dokument den Dateinamen im iLogic Logger ausgibt.</p>

<h3 class="relative group">AllReferencedDocuments
    <div id="allreferenceddocuments" class="anchor"></div>
    
</h3>
<p>Diese Methode nutzt das <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-DocumentsEnumerator"  target="_blank" rel="noreferrer">DocumentsEnumerator Object</a>, um über alle referenzierten Dokumente zu iterieren:</p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/AllReferencedDocuments.png"
    alt="API für den Endpoint AllReferencedDocuments">
</figure>


<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oAllRefDocs</span> <span class="ow">As</span> <span class="n">DocumentsEnumerator</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">AllReferencedDocuments</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="k">Each</span> <span class="n">oRefDoc</span> <span class="ow">As</span> <span class="kt">Object</span> <span class="ow">In</span> <span class="n">oAllRefDocs</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sFileName</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileName</span><span class="p">(</span><span class="n">oRefDoc</span><span class="p">.</span><span class="n">FullFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Filename: {0}&#34;</span><span class="p">,</span> <span class="n">sFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span></span></span></code></pre></div></div>
<p>Es werden alle <strong>7 Dokumente</strong>, über alle Baugruppenebenen, in einer Liste mit eindeutigen Werten zurückgeliefert:</p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/logger-allrefrerenceddocuments.png"
    alt="Logger Output für AllReferencedDocuments Property">
</figure>


<p>Die Methode eignet sich hervorragend, wenn man alle verbauten Komponenten, über alle Baugruppenebenen, auf bestimmte Bedingungen prüfen möchte.</p>

<h3 class="relative group">ReferencedDocuments
    <div id="referenceddocuments" class="anchor"></div>
    
</h3>
<p>Diese Methode nutzt ebenfalls das <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-DocumentsEnumerator"  target="_blank" rel="noreferrer">DocumentsEnumerator Object</a>, um über alle referenzierten Dokumente der <strong>ersten</strong> Baugruppenebene zu iterieren:</p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/ReferencedDocuments.png"
    alt="API für den Endpoint ReferencedDocuments">
</figure>


<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oRefDocs</span> <span class="ow">As</span> <span class="n">DocumentsEnumerator</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ReferencedDocuments</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="k">Each</span> <span class="n">oRefDoc</span> <span class="ow">As</span> <span class="kt">Object</span> <span class="ow">In</span> <span class="n">oRefDocs</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sFileName</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileName</span><span class="p">(</span><span class="n">oRefDoc</span><span class="p">.</span><span class="n">FullFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Filename: {0}&#34;</span><span class="p">,</span> <span class="n">sFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span></span></span></code></pre></div></div>
<p>Es werden lediglich <strong>4 Dokumente</strong> in einer Liste mit eindeutigen Werten zurückgeliefert, die identisch mit den verbauten Komponenten auf der ersten Baugruppenebene ist. Untergeordnete Bauteile und weitere verschachtelte Baugruppen, abseits der ersten Baugruppenebene, werden <strong>nicht</strong> berücksichtigt:</p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/logger-refrerenceddocuments.png"
    alt="Logger Output für ReferencedDocuments Property">
</figure>



<h3 class="relative group">Occurrences
    <div id="occurrences" class="anchor"></div>
    
</h3>
<p>Occurrences sind, wie bereits erwähnt, ein Sonderfall, da sie keine eindeutige Liste zurückliefern, sondern auch Auskunft über die <strong>Anzahl</strong> der verbauten Komponenten auf der ersten Baugruppenebene geben können.</p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/Occurrences.png"
    alt="API für den Endpoint Occurrences">
</figure>


<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oOccurences</span> <span class="ow">As</span> <span class="n">ComponentOccurrences</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">Occurrences</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="k">Each</span> <span class="n">oOccurrence</span> <span class="ow">As</span> <span class="n">ComponentOccurrence</span> <span class="ow">In</span> <span class="n">oOccurences</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sFileName</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileName</span><span class="p">(</span><span class="n">oOccurrence</span><span class="p">.</span><span class="n">Definition</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">FullFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Filename: {0}&#34;</span><span class="p">,</span> <span class="n">sFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span></span></span></code></pre></div></div>
<p>Es werden <strong>5 Dokumente</strong> zurückgeliefert, das das Bauteil <code>LST_Bender.ipt</code> auf der ersten Baugruppenebene zweimal platziert wurde:</p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/logger-occurrences.png"
    alt="Logger Output für Occurrences Property">
</figure>



<h2 class="relative group">Anwendungsfall
    <div id="anwendungsfall" class="anchor"></div>
    
</h2>
<p>Möchte man bspw. herausfinden, ob eine der verbauten Komponenten ein bestimmtes Property <strong>nicht</strong> gepflegt hat, kann man dafür das <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=Document_AllReferencedDocuments"  target="_blank" rel="noreferrer">AllReferencedDocuments Property</a> nutzen. Zum besseren Verständnis von Inventors Property Sets sei an dieser Stelle der Post <a href="/posts/ilogic-propertysets/">iLogic: Propertysets</a> empfohlen.</p>
<p>Als Beispiel werden alle Bauteile gelistet, die <strong>keinen</strong> Eintrag im Project Property <code>Part Number</code> besitzen. Exemplarisch haben alle Dokumente einen gepflegten Wert im Property <code>Part Number</code> bis auf das Bauteil <code>LST_Bender.ipt</code>:</p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/example-partnumber-missing.png"
    alt="Fehlender Eintrag im Property Part Number">
</figure>


<p>Für die Abfrage werden zwei kombinierte Methoden aus dem Namespace <code>System.String</code> verwendet. <a href="https://learn.microsoft.com/de-de/dotnet/api/system.string.isnullorwhitespace?view=net-9.0"  target="_blank" rel="noreferrer">IsNullOrWhiteSpace()</a> prüft ob ein Wert vorhanden ist und ob dieser ein Leerzeichen ist und <a href="https://learn.microsoft.com/de-de/dotnet/api/system.string.isnullorempty?view=net-9.0"  target="_blank" rel="noreferrer">IsNullOrEmpty()</a> prüft ob ein Wert vorhanden ist und dieser gar kein hinterlegtes Zeichen besitzt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oAllRefDocs</span> <span class="ow">As</span> <span class="n">DocumentsEnumerator</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">AllReferencedDocuments</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="k">Each</span> <span class="n">oRefDoc</span> <span class="ow">As</span> <span class="kt">Object</span> <span class="ow">In</span> <span class="n">oAllRefDocs</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sFileName</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetFileName</span><span class="p">(</span><span class="n">oRefDoc</span><span class="p">.</span><span class="n">FullFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sPartNumber</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oRefDoc</span><span class="p">.</span><span class="n">PropertySets</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="s">&#34;Design Tracking Properties&#34;</span><span class="p">).</span><span class="n">Item</span><span class="p">(</span><span class="s">&#34;Part Number&#34;</span><span class="p">).</span><span class="n">Value</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">String</span><span class="p">.</span><span class="n">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">sPartNumber</span><span class="p">)</span> <span class="ow">Or</span> <span class="n">System</span><span class="p">.</span><span class="n">String</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">sPartNumber</span><span class="p">))</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Filename: {0} hat keinen Eintrag im Property &#39;Part Number&#39;.&#34;</span><span class="p">,</span> <span class="n">sFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span></span></span></code></pre></div></div>
<p><strong>Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-referenced-docs/logger-missing-partnumber.png"
    alt="Logger Output für alle Komponenten ohne gepglegtes Property Part Number">
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Masse in Custom Property eintragen</title>
      <link>https://jbetzen.net/posts/ilogic-mass-property/</link>
      <pubDate>Mon, 03 Nov 2025 11:44:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-mass-property/</guid>
      <description>Inventor kann die Masse einer Konstruktion automatisch in Abhängigkeit von Geometrie und Dichte berechnen und stellt diese Angabe als Variable in Zeichnungen bereit. Man kann die Masse zusätzlich mit einfachen Mitteln in ein Custom Property schreiben.</description>
      <content:encoded><![CDATA[ <p>Inventor stellt die Angabe über die Masse der Konstruktion über das <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-MassProperties"  target="_blank" rel="noreferrer">MassProperties Object</a> bereit. Die Angabe erfolgt in Gramm.</p>


  

<figure class="centered"><img src="/posts/ilogic-mass-property/mass-properties-api.png">
</figure>


<p>Dieser Wert kann auch auf Zeichnungen als Textbaustein mittels <code>Physical Properties - Model &gt; MASS</code> genutzt werden:</p>


  

<figure class="centered"><img src="/posts/ilogic-mass-property/mass-property-variable.png">
</figure>


<p>Der Massenwert, den die API liefert ist trügerisch, denn er stellt nur den letzten Wert dar, der erfolgreich berechnet werden konnte. Wurde bspw. eine Unterkomponente nicht sauber aktualisiert, bescheinigt Inventor dies mit einem Blitz-Icon und liefert in der Application keinen sauberen Massenwert, sondern <code>N/A</code> zurück:</p>


  

<figure class="centered"><img src="/posts/ilogic-mass-property/mass-na-update.png">
</figure>


<p>Das Thema wird seit Jahren lang und breit im Inventor-Forum diskutiert und sorgt oftmals für fehlende Masseangaben in Schriftköpfen:</p>
<ul>
<li><a href="https://forums.autodesk.com/t5/inventor-forum/doesnt-always-update-mass-on-drawing/td-p/6813187"  target="_blank" rel="noreferrer">Doesnt always update Mass on drawing</a></li>
<li><a href="https://forums.autodesk.com/t5/inventor-ideas/weight-mass-n-a/idi-p/7744862"  target="_blank" rel="noreferrer">Weight/Mass N/A</a></li>
<li><a href="https://forums.autodesk.com/t5/inventor-forum/mass-property-n-a-for-iassembly/td-p/7435279"  target="_blank" rel="noreferrer">Mass property N/A for iAssembly</a></li>
<li><a href="https://forums.autodesk.com/t5/inventor-forum/losing-mass-property-constantly/td-p/6751058"  target="_blank" rel="noreferrer">Losing Mass property constantly</a></li>
</ul>
<p>Autodesk hat dazu ebenfalls ein Support-Dokument veröffentlicht <a href="https://www.autodesk.com/support/technical/article/caas/sfdcarticles/sfdcarticles/Physical-properties-of-model-are-shown-as-NA-in-Inventor-drawing.html"  target="_blank" rel="noreferrer">Properties are shown as N/A in Inventor drawing and Vault batch plot</a> und nennt wortkarg folgenden Grund:</p>
<blockquote><p>Physical properties are not updated in the model and also shown as N/A.</p>
</blockquote><p>Der Grund liegt, wie bereits erwähnt, in nicht sauber aktualisierten Unterkomponenten. Weist man einer Unterkomponente ein neues Material zu, ändern sich folgerichtig auch die Massenwerte der Komponente. Inventor erfordert nun ein auschecken und eine neue Speicherung der übergeordneten Komponente. Das kann bei kleinen Konstruktionen ein schneller Akt sein, stößt aber bei größeren Designs mit Freigabeprozessen schnell auf langwierige Hürden.</p>

<h2 class="relative group">Code Beispiele
    <div id="code-beispiele" class="anchor"></div>
    
</h2>
<p>Im folgenden sind zwei Methoden aufgelistet, um den Massenwert zu ermitteln, auf drei Nachkommastellen zu runden und in ein Custom Property mit dem Namen <code>Masse</code> zu schreiben. Dieses Property kann dann als <em>dreckiger Workaround</em> bspw. in den Schriftköpfen von Zeichnungen verwendet werden.</p>

<h3 class="relative group">iLogic Snippet iProperties.Mass
    <div id="ilogic-snippet-ipropertiesmass" class="anchor"></div>
    
</h3>
<p>iLogic hat ein natives Snippet <code>iProperties.Mass</code>, das den Massenwert ermitteln kann. Es hat jedoch einen kritischen <strong>Nachteil</strong>, denn es liefert den Rückgabewert immer in der Masseneinheit, die in den Document Settings hinterlegt ist:</p>


  

<figure class="centered"><img src="/posts/ilogic-mass-property/mass-shortcut-docsettings.png">
</figure>


<p>Möchte man dieses Snippet benutzen, sollte vorher <strong>immer</strong> sichergestellt sein, dass ein einheitliches Einheitensystem in jedem Bauteil vorhanden ist, um zu verhindern dass Werte mal in Gramm und mal in Kilogramm rausgeschrieben werden. Wie das geht wurde bereits im Post <a href="/posts/ilogic-uom/">iLogic: Units of Measurement</a> beschrieben. Anschließend kann das Snippet folgendermaßen benutzt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">dMass</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Mass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Custom&#34;</span><span class="p">,</span> <span class="s">&#34;Masse&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="n">Round</span><span class="p">(</span><span class="n">dMass</span><span class="p">,</span> <span class="n">3</span><span class="p">)</span></span></span></code></pre></div></div>

<h3 class="relative group">Alternative: Inventor API
    <div id="alternative-inventor-api" class="anchor"></div>
    
</h3>
<p>Eine alternative Methode besteht darin, dass man den letzten ermittelbaren Wert der Masse über die API ausliest:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">UOM</span> <span class="ow">As</span> <span class="n">UnitsOfMeasure</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">dMass</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">MassProperties</span><span class="p">.</span><span class="n">Mass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Konvertiere Masse in Kilogramm
</span></span></span><span class="line"><span class="cl"><span class="n">dMass</span> <span class="o">=</span> <span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">dMass</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseMassUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kKilogramMassUnits</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c">&#39; Runde Massenwert auf 3 Nachkommastellen
</span></span></span><span class="line"><span class="cl"><span class="n">dMass</span> <span class="o">=</span> <span class="n">Round</span><span class="p">(</span><span class="n">dMass</span><span class="p">,</span> <span class="n">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Custom&#34;</span><span class="p">,</span> <span class="s">&#34;Masse&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="n">dMass</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Masse: {0} [kg]&#34;</span><span class="p">,</span> <span class="n">dMass</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Der Code liefert verlässlich unabhängig von der Masseneinheit in den Document Settings eine Massenwert in der Einheit <code>kg</code>, der auf drei Nachkommastellen gerundet ist.</p>
<hr>
<p><strong>Achtung:</strong> Sollte die iLogic automatisch mit einem Trigger ausgelöst werden, darf sie nicht einer Zeichnung zugeordnet sein, denn diese haben keinen Massenwert. Alternativ kann man den Code umschreiben und explizit den Dokumententyp abfragen, wie im Post <a href="/posts/ilogic-documenttypes/">iLogic: Dokumententypen</a> beschrieben.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Modelldimensionen per RangeBox ermitteln</title>
      <link>https://jbetzen.net/posts/ilogic-rangebox/</link>
      <pubDate>Sat, 01 Nov 2025 21:44:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-rangebox/</guid>
      <description>Eine Bounding Box beschreibt einen virtuellen Rahmen um ein 3D-Modell. Die Bestimmung der Abmaße können hilfreich bei der Planung notwendigen Lagerplatzes sein und werden in Inventor über Box Objekte bestimmt.</description>
      <content:encoded><![CDATA[ <p>Das Konzept einer <a href="https://de.wikipedia.org/wiki/Minimal_umgebendes_Rechteck"  target="_blank" rel="noreferrer">Bounding Box</a> ist in der CAD-Welt geläufig. Es stellt einen virtuellen umschließenden Rahmen um ein 3D-Modell dar, dessen Werte Aufschluss über dessen Größe geben und bspw. bei der Planung von etwaigen Lagerplatz hilfreich sein kann.</p>
<p>Die Inventor API nutzt zur Bestimmung ein sogenanntes <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-Box"  target="_blank" rel="noreferrer">Box Objekt</a>. Dieses definiert die Bounding Box über zwei Werte (Max/Min) pro Achse und kann über das <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-ComponentDefinition"  target="_blank" rel="noreferrer">ComponentDefinition Objekt</a> auf zweierlei Weisen benutzt werden:</p>

<h2 class="relative group">RangeBox vs. PreciseRangeBox
    <div id="rangebox-vs-preciserangebox" class="anchor"></div>
    
</h2>
<p>Neben der <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=ComponentDefinition_RangeBox"  target="_blank" rel="noreferrer"><strong>RangeBox:</strong></a> stellt Inventor <a href="https://help.autodesk.com/view/INVNTOR/2023/ENU/?guid=GUID-36B1FFB5-5291-4532-8F11-90E912769B34"  target="_blank" rel="noreferrer">seit Version 2023</a> eine <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=ComponentDefinition_PreciseRangeBox"  target="_blank" rel="noreferrer"><strong>PreciseRangeBox</strong></a> zur Ermittlung der Bounding Box bereit.</p>


  

<figure class="centered"><img src="/posts/ilogic-rangebox/rangebox.png"
    alt="RangeBox in der Inventor API">
</figure>


<p>Autodesk definiert sie in ihrer Funktionsweise folgendermaßen:</p>
<dl>
<dt><strong>RangeBox:</strong></dt>
<dd><blockquote><p>Property that returns a Box object which contains the opposing points of a rectangular box that is guaranteed to enclose this object.</p>
</blockquote></dd>
<dt><strong>PreciseRangeBox:</strong></dt>
<dd><blockquote><p>Gets a bounding box that tightly encloses all the solid and surface bodies under the ComponentDefinition.</p>
</blockquote></dd>
</dl>
<p>Der elementare Unterschied besteht darin, dass die <code>PreciseRangeBox</code> auch dann ein korrektes <em>minimal umgebendes Rechteck</em> liefert, wenn Teile <em>schief im Raum</em> und nicht entlang der Achsen modelliert wurden. Nachteil ist, dass die <code>PreciseRangeBox</code> in großen Baugruppen spürbar rechenintensiver ist. Auch <a href="https://forums.autodesk.com/t5/inventor-programming-forum/what-is-more-precise-about-preciserangebox-compared-to-rangebox/m-p/11445994/highlight/true#M32382"  target="_blank" rel="noreferrer">exkludiert</a> sie <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-E0C11929-0C49-4923-84E7-1138C8D8014F"  target="_blank" rel="noreferrer">Work Features</a>, wie Ebenen, Achsen und Punkte.</p>

<h2 class="relative group">Code
    <div id="code" class="anchor"></div>
    
</h2>
<p>Der folgende Code ermittelt die Werte der Bounding Box in Abhängigkeit des Dokumententyps. Siehe dazu: <a href="/posts/ilogic-documenttypes/">iLogic: Dokumententypen</a>.</p>
<p>Für Bauteile wird die <code>PreciseRangeBox</code> verwendet, für Baugruppen die <code>RangeBox</code>. Die ermittelten Werte werden anschließend, wie bereit im Post <a href="/posts/ilogic-uom/">iLogic: Units of Measurement</a> erläutert, von Inventors interner Längeneinheit Zentimeter in Millimeter konvertiert und mit der <code>Ceil()</code>-Methode auf den nächst höheren Ganzzahlwert gerundet. Der ermittelte Wert wird anschließend in ein Custom Property namens <code>Dimensionen</code> geschrieben.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">UOM</span> <span class="ow">As</span> <span class="n">UnitsOfMeasure</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oRangeBox</span> <span class="ow">As</span> <span class="n">Box</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Select</span> <span class="k">Case</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span>
</span></span><span class="line"><span class="cl">        <span class="k">Case</span> <span class="n">DocumentTypeEnum</span><span class="p">.</span><span class="n">kPartDocumentObject</span>
</span></span><span class="line"><span class="cl">            <span class="n">oRangeBox</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">PreciseRangeBox</span>
</span></span><span class="line"><span class="cl">        <span class="k">Case</span> <span class="n">DocumentTypeEnum</span><span class="p">.</span><span class="n">kAssemblyDocumentObject</span>
</span></span><span class="line"><span class="cl">            <span class="n">oRangeBox</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">RangeBox</span>
</span></span><span class="line"><span class="cl">        <span class="k">Case</span> <span class="k">Else</span>
</span></span><span class="line"><span class="cl">            <span class="k">Exit</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">    <span class="nf">End</span> <span class="k">Select</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Max Werte
</span></span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Xmax</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oRangeBox</span><span class="p">.</span><span class="n">MaxPoint</span><span class="p">.</span><span class="n">X</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Ymax</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oRangeBox</span><span class="p">.</span><span class="n">MaxPoint</span><span class="p">.</span><span class="n">Y</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Zmax</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oRangeBox</span><span class="p">.</span><span class="n">MaxPoint</span><span class="p">.</span><span class="n">Z</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Min Werte
</span></span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Xmin</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oRangeBox</span><span class="p">.</span><span class="n">MinPoint</span><span class="p">.</span><span class="n">X</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Ymin</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oRangeBox</span><span class="p">.</span><span class="n">MinPoint</span><span class="p">.</span><span class="n">Y</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Zmin</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oRangeBox</span><span class="p">.</span><span class="n">MinPoint</span><span class="p">.</span><span class="n">Z</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Konvertiere in &#39;mm&#39; und runde auf nächsten Ganzzahlwert
</span></span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">X</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">Ceil</span><span class="p">(</span><span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">Xmax</span> <span class="o">-</span> <span class="n">Xmin</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Y</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">Ceil</span><span class="p">(</span><span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">Ymax</span> <span class="o">-</span> <span class="n">Ymin</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Z</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">Ceil</span><span class="p">(</span><span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">Zmax</span> <span class="o">-</span> <span class="n">Zmin</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Erzeuge String im Format &#39;X x Y x Z [mm]&#39;
</span></span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">Dimensions</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="kt">String</span><span class="p">.</span><span class="n">Format</span><span class="p">(</span><span class="s">&#34;{0} x {1} x {2} [mm]&#34;</span><span class="p">,</span> <span class="n">X</span><span class="p">,</span> <span class="n">Y</span><span class="p">,</span> <span class="n">Z</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Schreibe Abmaße in Property und Logger
</span></span></span><span class="line"><span class="cl">    <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Custom&#34;</span><span class="p">,</span> <span class="s">&#34;Dimensionen&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="n">Dimensions</span>
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Dimensionen: {0}&#34;</span><span class="p">,</span> <span class="n">Dimensions</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>

<h2 class="relative group">Ergebnis
    <div id="ergebnis" class="anchor"></div>
    
</h2>


  

<figure class="centered"><img src="/posts/ilogic-rangebox/result.png">
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Material über InputListBox zuweisen</title>
      <link>https://jbetzen.net/posts/ilogic-material-inputlistbox/</link>
      <pubDate>Fri, 31 Oct 2025 16:44:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-material-inputlistbox/</guid>
      <description>Dieser Post behandelt einen Lösungsvorschlag für ein Realproblem von Felix Rodermund, der in einem Youtube-Video versucht über iLogic und ein InputListBox-Dialogfenster eine Materialzuweisung per Listenauswahl zu realisieren.</description>
      <content:encoded><![CDATA[ <p>Der empfehlenswerte <a href="https://www.youtube.com/@r-kon"  target="_blank" rel="noreferrer">Youtube Kanal</a> von <a href="https://r-kon.de/"  target="_blank" rel="noreferrer">Felix Rodermund</a> wurde auf diesem Blog bereits im Post <a href="/posts/inventor-top-down-design/">Inventor: Top-Down-Konstruktion</a> empfohlen, da er eine schöne Serie zur Konstruktionsmethodik mit der Top-Down-Methode veröffentlicht hat.</p>
<p>Durch Zufall bin ich heute erneut über ein Video von ihm gestoßen, das ein augenscheinlich einfaches alltägliches Problem mit iLogic zu lösen versucht:</p>
<lite-youtube videoid="y739jWsr_fg" playlabel="y739jWsr_fg" params=""></lite-youtube>

<p><strong>Die Problemstellung:</strong> Wie kann man mit einem <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=b8c1d959-a08d-09fa-cbc3-3f05d828bf71"  target="_blank" rel="noreferrer">InputListBox</a>-Dialogfenster die verfügbaren Materialien der Standard Inventor Materialbibliothek auflisten und diese durch eine Listenauswahl zuweisen? Das InputListBox-Dialogfenster ist dem aufmerksamen Leser dieser Serie bereit bekannt aus dem Post <a href="/posts/ilogic-user-interaction/">iLogic: User Interaktion</a>.</p>
<p>Felix versucht in seinem initialen Code alle verfügbaren Materialien über das <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-MaterialAsset"  target="_blank" rel="noreferrer">MaterialAssets Object</a> als Liste anzusprechen und diese an eine InputListBox zu übergeben. Das Problem über das er stolpert stellt sich in der Form dar, dass Materialien in Inventor auf programmatischer Ebene <strong>Objekte</strong> sind. Folgerichtig bekommt er auch eine Liste an Objekten in der InputListBox angezeigt, die jedoch dem Endanwender nichtssagende Informationen enthalten:</p>


  

<figure class="centered"><img src="/posts/ilogic-material-inputlistbox/inputlistbox-objects.png"
    alt="Screenshot InputListBox">
</figure>


<p>Wie bereits angeschnitten, ist ein <code>MaterialAsset</code> in Inventor nicht nur lediglich ein Materialname, sondern besitzt zusätzlich viele Eigenschaften, bspw. einen Namen, einen Anzeigenamen, Texturangaben oder aber auch physische Eigenschaften, wie die Dichte:</p>


  

<figure class="centered"><img src="/posts/ilogic-material-inputlistbox/material-asset.png"
    alt="Screenshot InputListBox">
</figure>


<p>Felix umgeht das Problem mit einem gesunden Maß an <em>Overengineering</em> und nutzt letzten Endes eine flexible <em>Wrapper Class</em>, die er auf seiner Webseite <a href="https://r-kon.de/cad-auswahldialog.php"  target="_blank" rel="noreferrer">dokumentiert</a> hat.</p>

<h2 class="relative group">Meine Lösung
    <div id="meine-lösung" class="anchor"></div>
    
</h2>
<p>Im Kern kann das Problem auch relativ einfach in iLogic gelöst werden. Man muss lediglich:</p>
<ol>
<li>eine leere Liste <code>listMaterials</code> deklarieren</li>
<li>über alle <code>MaterialAssets</code> iterieren und jeden Anzeigenamen des jeweiligen Materials in die Liste einfügen</li>
<li>diese Liste an die InputListBox übergeben und die Auswahl als <em>Return Value</em> in eine Variable speichern</li>
<li>im finalen Schritt den iLogic Shortcut <code>iProperties.Material</code> benutzen. Dieser akzeptiert für eine Materialzuweisung den Anzeigenamen eines Materials.</li>
</ol>
<p><strong>Der Code:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oMaterialAssets</span> <span class="ow">As</span> <span class="n">AssetsEnumerator</span> <span class="o">=</span> <span class="n">ThisApplication</span><span class="p">.</span><span class="n">AssetLibraries</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="n">3</span><span class="p">).</span><span class="n">MaterialAssets</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">listMaterials</span> <span class="ow">As</span> <span class="k">New</span> <span class="n">List</span><span class="p">(</span><span class="k">Of</span> <span class="kt">String</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oMaterialAsset</span> <span class="ow">As</span> <span class="n">MaterialAsset</span> <span class="ow">In</span> <span class="n">oMaterialAssets</span>
</span></span><span class="line"><span class="cl">        <span class="n">listMaterials</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">oMaterialAsset</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">selection</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">InputListBox</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Welches Material soll zugewiesen werden?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">listMaterials</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">listMaterials</span><span class="p">(</span><span class="n">0</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Materialauswahl&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Material&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">480</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">0</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="k">Not</span> <span class="kt">String</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">selection</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">iProperties</span><span class="p">.</span><span class="n">Material</span> <span class="o">=</span> <span class="n">selection</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p><strong>Das Ergebnis:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-material-inputlistbox/assign-material.gif"
    alt="Materialzuweisung über InputListBox">
</figure>



<h2 class="relative group">Optimierte Lösung
    <div id="optimierte-lösung" class="anchor"></div>
    
</h2>
<p>Idealerweise prüft man vorangestellt, ob es sich beim aktiven Dokument um ein Bauteil (<code>PartDocument</code>) handelt, denn Baugruppen kann kein Material (abseits <a href="https://www.autodesk.com/support/technical/article/caas/sfdcarticles/sfdcarticles/Changing-default-weld-material.html"  target="_blank" rel="noreferrer">Material für Schweißwerkstoffe</a>) zugewiesen werden. Wie dies funktioniert wurde bereits im Post <a href="/posts/ilogic-documenttypes/">iLogic: Dokumententypen</a> erläutert:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">&lt;&gt;</span> <span class="n">DocumentTypeEnum</span><span class="p">.</span><span class="n">kPartDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">MessageBox</span><span class="p">.</span><span class="n">Show</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s">&#34;Dieser Code funktioniert nur in Bauteilen.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s">&#34;Falscher Dokumententyp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">MessageBoxButtons</span><span class="p">.</span><span class="n">OK</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">MessageBoxIcon</span><span class="p">.</span><span class="n">Error</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">MessageBoxDefaultButton</span><span class="p">.</span><span class="n">Button1</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">Exit</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">    <span class="nf">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oMaterialAssets</span> <span class="ow">As</span> <span class="n">AssetsEnumerator</span> <span class="o">=</span> <span class="n">ThisApplication</span><span class="p">.</span><span class="n">AssetLibraries</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="n">3</span><span class="p">).</span><span class="n">MaterialAssets</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">listMaterials</span> <span class="ow">As</span> <span class="k">New</span> <span class="n">List</span><span class="p">(</span><span class="k">Of</span> <span class="kt">String</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oMaterialAsset</span> <span class="ow">As</span> <span class="n">MaterialAsset</span> <span class="ow">In</span> <span class="n">oMaterialAssets</span>
</span></span><span class="line"><span class="cl">        <span class="n">listMaterials</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">oMaterialAsset</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">selection</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">InputListBox</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Welches Material soll zugewiesen werden?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">listMaterials</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">listMaterials</span><span class="p">(</span><span class="n">0</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Materialauswahl&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Material&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">480</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">0</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="k">Not</span> <span class="kt">String</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">selection</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">iProperties</span><span class="p">.</span><span class="n">Material</span> <span class="o">=</span> <span class="n">selection</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p>Wird der Code nun in einer Baugruppe oder Zeichnung ausgeführt, erscheint eine Warnmeldung:</p>


  

<figure class="centered"><img src="/posts/ilogic-material-inputlistbox/warning.png"
    alt="Warnmeldung für einen falschen Dokumententyp">
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Import BOM Settings</title>
      <link>https://jbetzen.net/posts/ilogic-import-bom-settings/</link>
      <pubDate>Thu, 30 Oct 2025 21:44:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-import-bom-settings/</guid>
      <description>Baugruppen in Inventor erlauben die Stückliste anzuzeigen. Die dargestellten Properties in der Stücklistenansicht sind am Dokument gespeichert und können daher variieren. Sie können aber mittels iLogic manipuliert und harmonisiert werden.</description>
      <content:encoded><![CDATA[ <p>Wie im Post <a href="/posts/ilogic-bom/">iLogic: Stücklisten</a> beschrieben, erlauben Baugruppen in Inventor über <code>Assemble &gt; Bill of Materials</code> auf die Stückliste zuzugreifen. In der Stücklistenansicht werden definierte Properties angezeigt. Welche Properties angezeigt werden, wird am Dokument und <strong>nicht</strong> global gespeichert. Daher können die angezeigten Properties von Dokument zu Dokument zu variieren.</p>
<p>Inventor erlaubt eine vordefinierte Config im XML-Format über den Button <kbd>Import&hellip;</kbd> zu importieren:</p>


  

<figure class="centered"><img src="/posts/ilogic-import-bom-settings/btn-import-settings.png">
</figure>


<p>Eine solche Config für darzustellende Properties muss zuvor in einem Dokument definiert und anschließen über den Button <kbd>Export&hellip;</kbd> exportiert und kann anschließend maßgebend in anderen Baugruppen genutzt werden.</p>
<p>Da das händische Importieren einer solchen Datei einen Akt zeitraubener Klickarbeit darstellt, kann der Import auch mit iLogic automatisiert werden.</p>

<h2 class="relative group">Code zum Import der BOM Settings
    <div id="code-zum-import-der-bom-settings" class="anchor"></div>
    
</h2>
<p>Die BOM Settings können über das <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-BOM"  target="_blank" rel="noreferrer">BOM Object </a> manipuliert werden. Für den Import einer Settings-Datei stellt die Inventor API explizit die Methode <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=BOM_ImportBOMCustomization"  target="_blank" rel="noreferrer">BOM.ImportBOMCustomization</a> bereit. Diese erwartet als einzigen Parameter einen absoluten Dateipfad zur Settings-Datei als String:</p>


  

<figure class="centered"><img src="/posts/ilogic-import-bom-settings/ImportBOMCustomization.png"
    alt="ImportBOMCustomization Method zum Import einer BOM Settings Datei">
</figure>


<p>Im realen Anwendungsfall kann ales Beispiel eine BOM-Setting-Datei mit dem Namen <code>BOM-Settings.xml</code> herhalten. Der absolute Pfad dieser beispielhaften Datei soll lauten: <code>C:\foo\BOM-Settings.xml</code></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Sicherstellen, dass Import nur in Baugruppen erfolgt
</span></span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kAssemblyDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oBOM</span> <span class="ow">As</span> <span class="n">BOM</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">BOM</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Absoluter Pfad zur BOM-Settings-Datei
</span></span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sBOMSettingsFile</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">&#34;C:\foo\BOM-Settings.xml&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Import der Datei im aktiven Baugruppendokument
</span></span></span><span class="line"><span class="cl">    <span class="n">oBOM</span><span class="p">.</span><span class="n">ImportBOMCustomization</span><span class="p">(</span><span class="n">sBOMSettingsFile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span></span></span></code></pre></div></div>

<h2 class="relative group">BOM nach Import öffnen
    <div id="bom-nach-import-öffnen" class="anchor"></div>
    
</h2>
<p>Möchte man die BOM nach dem Import einer Setting-Datei öffnen, kann auch dies mit iLogic erfolgen. Dabei muss die BOM über das <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-ControlDefinition"  target="_blank" rel="noreferrer">ControlDefinition Object</a> angesprochen und geöffnet werden. Um dies zu bewerkstelligen muss der zuvor gezeigte Code um lediglich 2 Zeilen erweitert werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oBOMControlDef</span> <span class="ow">As</span> <span class="n">ControlDefinition</span> <span class="o">=</span> <span class="n">ThisApplication</span><span class="p">.</span><span class="n">CommandManager</span><span class="p">.</span><span class="n">ControlDefinitions</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="s">&#34;AssemblyBillOfMaterialsCmd&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oBOMControlDef</span><span class="p">.</span><span class="n">Execute2</span><span class="p">(</span><span class="k">True</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Wichtig an der Stelle ist, dass die Methode <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=ControlDefinition_Execute2"  target="_blank" rel="noreferrer">ControlDefinition.Execute2</a> und nicht <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=ControlDefinition_Execute"  target="_blank" rel="noreferrer">ControlDefinition.Execute</a> verwendet wird, da eine synchrone Ausführung des Befehls zur Laufzeit des iLogic Codes erfolgen muss.</p>
<p>Der Finale Code könnte dergestalt sein:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Sicherstellen, dass Import nur in Baugruppen erfolgt
</span></span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kAssemblyDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oBOMControlDef</span> <span class="ow">As</span> <span class="n">ControlDefinition</span> <span class="o">=</span> <span class="n">ThisApplication</span><span class="p">.</span><span class="n">CommandManager</span><span class="p">.</span><span class="n">ControlDefinitions</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="s">&#34;AssemblyBillOfMaterialsCmd&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oBOM</span> <span class="ow">As</span> <span class="n">BOM</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">BOM</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sBOMSettingsFile</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">&#34;C:\foo\BOM-Settings.xml&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Import der Datei im aktiven Baugruppendokument
</span></span></span><span class="line"><span class="cl">    <span class="n">oBOM</span><span class="p">.</span><span class="n">ImportBOMCustomization</span><span class="p">(</span><span class="n">sBOMSettingsFile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#39; BOM Aufrufen
</span></span></span><span class="line"><span class="cl">    <span class="n">oBOMControlDef</span><span class="p">.</span><span class="n">Execute2</span><span class="p">(</span><span class="k">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Schreibschutz von Dateien prüfen</title>
      <link>https://jbetzen.net/posts/ilogic-schreibschutz/</link>
      <pubDate>Thu, 23 Oct 2025 21:43:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-schreibschutz/</guid>
      <description>Vault nutzt den Dateischreibschutz im lokalen Workspace, um anzuzeigen, ob eine Datei schreibbar (checked out) ist. Diese Information kann mit iLogic ausgelesen und sichergestellt werden, dass Code nur in schreibbaren Dateien ausgeführt wird.</description>
      <content:encoded><![CDATA[ <p>Autodesk Vault verfolgt das geläufige PDM Konzept eines <a href="https://help.autodesk.com/view/VAULT/2024/ENU/?guid=GUID-4723870E-9E95-4141-8682-8332B3FD4A6F"  target="_blank" rel="noreferrer">lokalen Workspace</a>, in den Dateien heruntergeladen werden, wenn sie aus Vault geöffnet werden. Checkt man eine Datei nach dem Öffnungsbefehl nicht aus, sondern betrachtet sie lediglich, setzt Vault für diese Datei einen Schreibschutz im Dateisystem. Folgerichtig wird die Schreibbarkeit einer Datei nach einem Check-Out darüber geregelt, dass der Schreibschutz auf Dateisystemebene entfernt wird:</p>


  

<figure class="centered"><img src="/posts/ilogic-schreibschutz/file-read-only.png"
    alt="Read Only Dateistatus"><figcaption>
      <p>Eingecheckte Datei mit Read Only Attribute</p>
    </figcaption>
</figure>


<p>Man kann auf zweierlei Weise mittels iLogic prüfen, ob eine Datei ausgechecked ist:</p>
<dl>
<dt><strong>Methode 1 | Abfrage über die Vault API</strong></dt>
<dd>Seit Inventor 2024 stellt Autodesk die Möglichkeit bereit über iLogic Abfragen an die Vault API zu senden. Dafür steht die <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=33786554-75a9-06db-a868-b42f2883a355"  target="_blank" rel="noreferrer">iLogicVaultClass Class</a> bereit und besitzt auch eine Methode <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=476739ae-ab9a-ba83-8aab-b605ea0d74ff"  target="_blank" rel="noreferrer">iLogicVaultClass.GetVaultFileStatus</a>, um Informationen zum Dateistatus zu erhalten. Autodesk stellt Beispiel-Code in Form einer Funktion bereit, die als Return Value ein Dictionary liefert, in denen alle Informationen zum Dateistatus enthalten sind:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Public</span> <span class="k">Function</span> <span class="nf">GetVaultFileStatus</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">LocalFullFileName</span> <span class="ow">As</span> <span class="kt">String</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="ow">As</span> <span class="n">Dictionary</span><span class="p">(</span><span class="k">Of</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">String</span><span class="p">)</span></span></span></code></pre></div></div>
<p><strong>Problem:</strong> Ich habe nicht länger Zugriff auf eine laufende Vault Instanz und kann über die Funktionsweise keinerlei Auskunft geben. ¯\<em>(ツ)</em>/¯</p>
</dd>
<dt><strong>Methode 2 | Abfrage des Schreibschutzes der Datei</strong></dt>
<dd>Über iLogic kann dem aktiven Dokument der volle Pfad über das Property <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=Document_FullFileName"  target="_blank" rel="noreferrer">Document.FullFileName</a> entnommen werden. Dieser Pfad kann mittels der <a href="https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo?view=net-9.0"  target="_blank" rel="noreferrer">FileInfo Class</a> genutzt werden, um über das Property <a href="https://learn.microsoft.com/en-us/dotnet/api/system.io.fileinfo.isreadonly?view=net-9.0#system-io-fileinfo-isreadonly"  target="_blank" rel="noreferrer">FileInfo.IsReadOnly</a> einen bool&rsquo;schen Wert über den Status des Schreibschutzes zu erhalten.</dd>
</dl>
<p>Die zweite Methode wird in diesem Post näher erklärt.</p>

<h2 class="relative group">Funktion mit Boolean Return Value
    <div id="funktion-mit-boolean-return-value" class="anchor"></div>
    
</h2>
<p>Der einfachste Weg zum Erfragen des Schreibschutzstatus ist die Auslagerung der gesamten Logik in eine eigene Funktion, die am Ende einen bool&rsquo;schen Wert als Return Value liefert. Als Funktionsparameter wird der <code>FullFileName</code> übergeben. Ist die Datei schreibgeschützt, wird der Wert <code>True</code> zurückgegeben.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsWriteProtected</span><span class="p">(</span><span class="n">FullFileName</span> <span class="ow">As</span> <span class="kt">String</span><span class="p">)</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oFileInfo</span> <span class="ow">As</span> <span class="k">New</span> <span class="n">IO</span><span class="p">.</span><span class="n">FileInfo</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oFileInfo</span><span class="p">.</span><span class="n">IsReadOnly</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span></span></span></code></pre></div></div>
<p>Zum Thema Funktionen ist in dieser Serie bereits ein Post erschienen: <a href="/posts/ilogic-struktur/">iLogic: Struktureller Aufbau</a></p>

<h2 class="relative group">Vollständiges Beispiel
    <div id="vollständiges-beispiel" class="anchor"></div>
    
</h2>
<p>Diese Funktion kann nun in einer Hauptroutine genutzt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">IsWriteProtected</span><span class="p">(</span><span class="n">oDoc</span><span class="p">.</span><span class="n">FullFileName</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">&#34;Datei ist schreibgeschützt.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">Exit</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">    <span class="nf">Else</span>
</span></span><span class="line"><span class="cl">        <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Datei ist nicht schreibgeschützt.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">&#39; Hier kann Code platziert werden, der nur
</span></span></span><span class="line"><span class="cl">        <span class="c">&#39; in ausgecheckten Dateien ausgeführt wird.
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsWriteProtected</span><span class="p">(</span><span class="n">sFullFileName</span> <span class="ow">As</span> <span class="kt">String</span><span class="p">)</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oFileInfo</span> <span class="ow">As</span> <span class="k">New</span> <span class="n">IO</span><span class="p">.</span><span class="n">FileInfo</span><span class="p">(</span><span class="n">sFullFileName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oFileInfo</span><span class="p">.</span><span class="n">IsReadOnly</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span></span></span></code></pre></div></div>

<h2 class="relative group">Zu Beachten
    <div id="zu-beachten" class="anchor"></div>
    
</h2>
<p>Der Schreibschutz auf Dateiebene ist kein 100% sicherer Marker, ob eine Datei wirklich ausgechecked ist. Am Ende gibt es immer noch den wild-klickenden unbedarften User, der es am Ende schafft eine eingecheckte Datei <a href="https://help.autodesk.com/view/VAULT/2024/ENU/?guid=GUID-3621EE8C-4CC6-44F8-8111-314ADE6D2AB3"  target="_blank" rel="noreferrer">lokal zu modifizieren</a>:</p>


  

<figure class="centered"><img src="/posts/ilogic-schreibschutz/modified-file.png">
</figure>


<p>Im Falle einer lokalen Modifikation würde eine iLogic beim Ausführen der Funktion <code>IsWriteProtected()</code> melden, dass die Datei schreibbar ist, obwohl sie nicht auf den aktuellen User ausgechecked ist. Der iLogic Code würde laufen. Gleichzeitig würden aber die Änderungen, die eine iLogic am Dokument vorgenommen hat, nicht in Vault vorhanden sein.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Displayname anpassen</title>
      <link>https://jbetzen.net/posts/ilogic-displayname/</link>
      <pubDate>Thu, 23 Oct 2025 10:43:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-displayname/</guid>
      <description>Inventor erlaubt das Setzen eines Anzeigenamen für CAD-Dokumente. Dieser Displayname taucht im Model Browser von Inventor auf, kann mittels iLogic angepasst werden und dazu beitragen verbaute Dokumente leichter zu identifizieren.</description>
      <content:encoded><![CDATA[ <p>Inventor nutzt als Default den Dateinamen als Displayname (Anzeigename) im Model Browser:</p>


  

<figure class="centered"><img src="/posts/ilogic-displayname/displayname-filename.png"
    alt="Filename als Displayname">
</figure>


<p>Dateinamen und insbesondere lange kryptische Dateinamen, bspw. aus importierten Dateien, können den Model Browser schnell überfrachten und zur generellen Unübersichtlichkeit beitragen. Es kann daher Sinn ergeben ein einheitliches Format zu erzwingen und mittels iLogic beim Speichervorgang zu setzen.</p>
<p>Der Displayname ist dem <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-Document"  target="_blank" rel="noreferrer">Document Object</a> zugehörig und steht als eigenes Property <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=Document_DisplayName"  target="_blank" rel="noreferrer">Document.DisplayName</a> zur Verfügung. An dieses Property muss lediglich ein String übergeben werden, um den Displaynamen anzupassen:</p>


  

<figure class="centered"><img src="/posts/ilogic-displayname/displayname-property.png"
    alt="DisplayName Property">
</figure>



<h2 class="relative group">Beispielszenario
    <div id="beispielszenario" class="anchor"></div>
    
</h2>
<p>Im Post <a href="/posts/ilogic-propertysets/">iLogic: Propertysets</a> wurden bereits alle Inventor <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-PropertySets"  target="_blank" rel="noreferrer">Property Sets</a> beschrieben. Für das folgende Beispiel wird die Datei <code>LST_Bender.ipt</code> verwendet. Dieses Bauteil hat fiktiv gepflegte Werte in den Project Properties:</p>


  

<figure class="centered"><img src="/posts/ilogic-displayname/example-project-properties.png"
    alt="Beispiel anhand von Project Properties">
</figure>


<p>Die Werte für die Properties <code>Part Number</code>, <code>Description</code> und <code>Revision Number</code> sollen nun in einem kombinierten String in den Displaynamen geschrieben werden. Als Beispiel wird das folgende Format gewählt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{Description} - {Part Number} - Rev.{Revision Number}</span></span></code></pre></div></div>
<p>Am konkreten Beispiel sähe das Resultat folgerichtig folgendermaßen aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">LST Bender - 25785 - Rev.A</span></span></code></pre></div></div>

<h2 class="relative group">Displayname anpassen
    <div id="displayname-anpassen" class="anchor"></div>
    
</h2>
<p>Wie bereits erwähnt, wurde im Post <a href="/posts/ilogic-propertysets/">iLogic: Propertysets</a> die Inventor Property Sets beschrieben. Zusätzlich wurde dort auch der iLogic Shortcut vorgestellt, mit dem einfach Property Werte ausgelesen und geschrieben werden können. Der Befehlt lautet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;$PropertySet&#34;</span><span class="p">,</span> <span class="s">&#34;$PropertyName&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Im konkreten Fall muss also das Project Property Set ausgelesen und die darin enthaltenen Werte in Variablen gespeichert und als String kombiniert werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDescription</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Project&#34;</span><span class="p">,</span> <span class="s">&#34;Description&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sPartnumber</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Project&#34;</span><span class="p">,</span> <span class="s">&#34;Part Number&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sRevision</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Project&#34;</span><span class="p">,</span> <span class="s">&#34;Revision Number&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Erzeuge String für kombinierten Displayname
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDisplayname</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="kt">String</span><span class="p">.</span><span class="n">Format</span><span class="p">(</span><span class="s">&#34;{0} - {1} - Rev.{2}&#34;</span><span class="p">,</span> <span class="n">sDescription</span><span class="p">,</span> <span class="n">sPartnumber</span><span class="p">,</span> <span class="n">sRevision</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Setze Displayname
</span></span></span><span class="line"><span class="cl"><span class="n">oDoc</span><span class="p">.</span><span class="n">DisplayName</span> <span class="o">=</span> <span class="n">sDisplayname</span></span></span></code></pre></div></div>
<p><strong>Das Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-displayname/result-displayname.png"
    alt="Mit iLogic angepasster Displayname"><figcaption>
      <p>Displayname: Vorher (links) und nachher (rechts)</p>
    </figcaption>
</figure>



<h2 class="relative group">Displayname zurücksetzen
    <div id="displayname-zurücksetzen" class="anchor"></div>
    
</h2>
<p>Ein bereits gesetzter Displayname kann einfach gelöscht werden, indem man das Bauteil im Model Browser selektiert und mittels Umbenennen <kbd>F2</kbd> den gesetzten Namenstext löscht:</p>


  

<figure class="centered"><img src="/posts/ilogic-displayname/reset-displayname.gif"
    alt="Displayname zurücksetzen"><figcaption>
      <p>Reset Displayname</p>
    </figcaption>
</figure>


<p>Ein Reset des Displayname kann auch auf einfachste Weise mit iLogic geschehen, indem schlichtweg ein leerer String übergeben wird:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">DisplayName</span> <span class="o">=</span> <span class="s">&#34;&#34;</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Trigger Before Check-In</title>
      <link>https://jbetzen.net/posts/ilogic-before-check-in/</link>
      <pubDate>Wed, 22 Oct 2025 09:43:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-before-check-in/</guid>
      <description>Autodesk Vault erlaubt seit Version 2025 iLogics vor dem dem Check-In auszuführen. Der neue Trigger bietet Vorteile, hat aber auch seine Tücken und erzeugt im schlimmsten Fall ein Dokument, das sich nicht mehr in Vault einchecken lässt.</description>
      <content:encoded><![CDATA[ <p>Autodesk Vault 2025 hat einen <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-AB407A72-1C02-4C89-B2D0-72A7E962FB90"  target="_blank" rel="noreferrer">neuen Event-Trigger</a> <code>Before Vault Check-In</code> eingeführt, der es erlaubt iLogics vor dem Check-In-Vorgang auszuführen. Siehe: <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-A5262025-BB42-4707-8BA0-C0ED830FF3BD"  target="_blank" rel="noreferrer">Event Trigger Reference</a></p>
<p>Die <a href="https://forums.autodesk.com/t5/inventor-ideas/ilogic-events-trigger-before-check-in/idi-p/5701514"  target="_blank" rel="noreferrer">ursprüngliche Bitte</a> an Autodesk einen solchen Trigger einzuführen, stammt bereits aus dem Jahr 2015. Die Gründe sind nachvollziehbar, da vor diesem Trigger lediglich ein wirklich brauchbarer Event vorhanden war, um effektiv iLogics laufen zu lassen und zwar <code>Before Save Document</code>. iLogics bei jedem Speichervorgang laufen zu lassen ist und war nie wirklich zielführend. Insbesondere, wenn man eine große Menge an iLogics aktiv an die User ausgerollt hat, können diese die Dauer des Speichervorgangs spürbar beeinträchtigen. Viele iLogics kontrollieren, korrigieren oder setzen Property-Werte und oftmals ist es ausreichend dies <strong>nicht</strong> bei jedem Speichern erfolgen zu lassen, denn ein einmaliger Lauf vor dem Check-In in die Vault Datenbank führt zu selbigem Ergebnis.</p>

<h2 class="relative group">Tücken des Triggers
    <div id="tücken-des-triggers" class="anchor"></div>
    
</h2>
<p>Kurz nach den ersten Testvorgängen, stellte sich schnell Ernüchterung auf meiner Seite ein. Es ist relativ schnell und einfach möglich mit diesem neuen Trigger ein Dokument zu erzeugen, dass sich nicht mehr in Vault einchecken lässt. Ich hatte bspw. einen Fall in der alten iLogic Codebase, der nicht länger benötigte Custom Properties bereinigt hat, wie in diesem Post <a href="/posts/ilogic-delete-properties/">iLogic: Alte Properties automatisiert löschen</a> beschrieben. Zeitgleich hat eine andere iLogic aber ein eben bereinigtes Property erneut in das Dokument geschrieben. Ersteres geschah beim Speichervorgang. Zweiteres beim Einchecken.</p>
<p><strong>Das Resultat:</strong> Zirkellogik und ein Dokument, das nie aktuell ist und sich daher auch nicht sauber in Vault einchecken lässt:</p>


  

<figure class="centered"><img src="/posts/ilogic-before-check-in/file-was-modified-by-a-rule.png"
    alt="Error Message: File was modified by a rule"><figcaption>
      <p>Error: The file was modified by a rule that ran on check-in</p>
    </figcaption>
</figure>


<p>Da scheinbar mehrere User über dieses Problem gestolpert sind, hat Autodesk bereits ein eigenes Support-Dokument veröffentlicht: <a href="https://www.autodesk.com/support/technical/article/caas/sfdcarticles/sfdcarticles/The-file-was-modified-by-a-rule-that-ran-on-check-in-when-check-in-file-from-Inventor.html"  target="_blank" rel="noreferrer">The file was modified by a rule that ran on check-in</a></p>
<p>Dort wird die Ursache des Problems wie folgt beschrieben:</p>
<blockquote><p>When using trigger &ldquo;Before Vault Check-In&rdquo; or &ldquo;After Save Document&rdquo;, it is not allowed to change (the already saved) Inventor file. Writing properties or any other information in the current file is not allowed for these triggers as it causes that the file needs a save operation again.</p>
</blockquote><p>Das Kernproblem, welches an dieser Stelle entsteht, ist mangelnde <em>Verbosity</em>, denn die Aussage, dass die Datei während des Check-Ins modifiziert wurde, ist schlichtweg unpräzise und liefert keinerlei Hilfestellungen zur Lösung des Problems. Welche Regel das Dokument modifiziert hat oder was überhaupt modifiziert wurde ist nicht klar ersichtlich. Wer einen guten Überblick über seinen iLogic Code hat und diesen bis ins Detail kennt, mag darin kein Problem sehen. Wer aber eine komplett undokumentierte und unstrukturierte Codebase von 2800 Zeilen Code übernimmt, muss man dann erst mal Haare raufen und sich auf die Suche der Ursache begeben.</p>

<h2 class="relative group">Anwendungsfälle für den Trigger
    <div id="anwendungsfälle-für-den-trigger" class="anchor"></div>
    
</h2>
<p>Wie bereits erwähnt, kann der Trigger bei Dokumentenmodifikation zu Problemen führen. Daraus folgt aber auch, dass alle iLogic Tasks, die das Dokument <strong>nicht</strong> modifizieren, als idealer Anwendungsfall dienen. An dieser Stelle möchte ich zwei reale Anwendungsfälle aufzeigen, die nicht mit dem aktiven Dokument arbeiten, sondern lediglich die Inventor Anwendung per iLogic ansprechen:</p>
<dl>
<dt><a href="/posts/ilogic-before-check-in/#isometrische-ansicht-f%c3%bcr-3d-bauteile" ><strong>FALL 1 - 3D Bauteile in Iso-Ansicht schwenken</strong></a></dt>
<dd>Für Bauteile und Baugruppen kann über den Trigger sichergestellt werden, dass das Modell vor dem Einchecken immer in die isometrische Ansicht gedreht wird. Somit findet jeder, der das Dokument aus Vault öffnet dieses einheitlich in identischer Ansicht vor.</dd>
<dt><a href="/posts/ilogic-before-check-in/#seite-1-aktivieren-f%c3%bcr-zeichnungen" ><strong>FALL 2 - Erste Seite einer Zeichnung aktivieren</strong></a></dt>
<dd>Für Zeichnungen kann der Trigger genutzt werden, um vor dem Einchecken die erste Seite der Zeichnung zu aktivieren. Dies sorgt ebenfalls für ein einheitliches Öffnungsverhalten beim Laden aus Vault und sorgt zusätzlich dafür, dass die erste Seite als Thumbnail in Vault hinterlegt wird.</dd>
</dl>
<p>Beide Fälle werden im Folgetext mit Code-Beispielen erläutert.</p>

<h3 class="relative group">Isometrische Ansicht für 3D-Bauteile
    <div id="isometrische-ansicht-für-3d-bauteile" class="anchor"></div>
    
</h3>
<p>Um die Inventor Anwendung mittels iLogic zu überreden die aktive Ansicht in die Iso-Ansicht zu schwenken, muss als erstes auf das <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-Camera"  target="_blank" rel="noreferrer">Camera Object</a> zurückgegriffen werden. Dieses definiert die Ansicht des Modells in der Inventor Application. Das <code>Camera Object</code> hat ein Property namens <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=Camera_ViewOrientationType"  target="_blank" rel="noreferrer">Camera.ViewOrientationType</a>, mit dem eine spezifische Ansicht gewählt werden kann. Für letzteres ist dann der <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=ViewOrientationTypeEnum"  target="_blank" rel="noreferrer">ViewOrientationTypeEnum</a> wichtig. Der <code>ViewOrientationTypeEnum</code> stellt Standard-Asichten, wie die Vorderansicht, Seitenansicht etc., zur Verfügung, die dem Inventor-Anwender auch vom <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-B266056E-5E88-42A0-B29C-7BB25C377BD3"  target="_blank" rel="noreferrer">ViewCube</a> bekannt sein sollten. Er stellt aber auch spezifischere Ansichten bereit, bspw. Blechansichten oder aber die gewünschten Iso-Ansichten:</p>


  

<figure class="centered"><img src="/posts/ilogic-before-check-in/ViewOrientationTypeEnum.png">
</figure>


<p>Im folgenden Beispiel wird der <code>ViewOrientationTypeEnum</code> mit dem Wert <code>kIsoTopRightViewOrientation</code> genutzt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">SetIsoView3d</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oApp</span> <span class="ow">As</span> <span class="n">Application</span> <span class="o">=</span> <span class="n">ThisApplication</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Select</span> <span class="k">Case</span> <span class="n">oApp</span><span class="p">.</span><span class="n">ActiveDocument</span><span class="p">.</span><span class="n">DocumentType</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">&#39; Nur in Bauteilen und Baugruppen ausführen
</span></span></span><span class="line"><span class="cl">        <span class="k">Case</span> <span class="n">DocumentTypeEnum</span><span class="p">.</span><span class="n">kPartDocumentObject</span><span class="p">,</span> <span class="n">DocumentTypeEnum</span><span class="p">.</span><span class="n">kAssemblyDocumentObject</span>
</span></span><span class="line"><span class="cl">            <span class="k">Dim</span> <span class="n">oCamera</span> <span class="ow">As</span> <span class="n">Camera</span> <span class="o">=</span> <span class="n">oApp</span><span class="p">.</span><span class="n">ActiveView</span><span class="p">.</span><span class="n">Camera</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c">&#39; Schwenke in Iso Ansicht (Top Right)
</span></span></span><span class="line"><span class="cl">            <span class="n">oCamera</span><span class="p">.</span><span class="n">ViewOrientationType</span> <span class="o">=</span> <span class="n">ViewOrientationTypeEnum</span><span class="p">.</span><span class="n">kIsoTopRightViewOrientation</span>
</span></span><span class="line"><span class="cl">            <span class="n">oCamera</span><span class="p">.</span><span class="n">Apply</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="c">&#39; View auf aktuelle Fenstergröße anpassen
</span></span></span><span class="line"><span class="cl">            <span class="n">oApp</span><span class="p">.</span><span class="n">ActiveView</span><span class="p">.</span><span class="n">Fit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">Select</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p><strong>Das Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-before-check-in/result-iso-view-fit.gif">
</figure>



<h3 class="relative group">Seite 1 aktivieren für Zeichnungen
    <div id="seite-1-aktivieren-für-zeichnungen" class="anchor"></div>
    
</h3>
<p>Für Zeichnungen ist das aktivieren des ersten Zeichnungsblattes leicht möglich. Man muss lediglich über das <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-Sheets"  target="_blank" rel="noreferrer">Sheets Object</a> das erste <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=GUID-Sheet"  target="_blank" rel="noreferrer">Sheet Object</a> (Blatt 1) adressieren und über die Methode <a href="https://help.autodesk.com/view/INVNTOR/2025/ENU/?guid=Sheet_Activate"  target="_blank" rel="noreferrer"><code>Sheet.Activate()</code></a> aktivieren:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">ActivateDrawingSheet1</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oApp</span> <span class="ow">As</span> <span class="n">Application</span> <span class="o">=</span> <span class="n">ThisApplication</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Try</span>
</span></span><span class="line"><span class="cl">        <span class="c">&#39; Sheet 1 aktivieren
</span></span></span><span class="line"><span class="cl">        <span class="n">oApp</span><span class="p">.</span><span class="n">ActiveDocument</span><span class="p">.</span><span class="n">Sheets</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="n">Activate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="c">&#39; View auf aktuelle Fenstergröße anpassen
</span></span></span><span class="line"><span class="cl">        <span class="n">oApp</span><span class="p">.</span><span class="n">ActiveView</span><span class="p">.</span><span class="n">Fit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Catch</span>
</span></span><span class="line"><span class="cl">        <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">&#34;Could not activate Sheet 1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">Try</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p><strong>Das Resultat:</strong></p>


  

<figure class="centered"><img src="/posts/ilogic-before-check-in/result-drawing-sheet1.gif">
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Stile bereinigen</title>
      <link>https://jbetzen.net/posts/ilogic-purge-styles/</link>
      <pubDate>Wed, 15 Oct 2025 09:43:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-purge-styles/</guid>
      <description>An Bauteilen und Zeichnungen kann eine Historie alter Stile gespeichert sein, die nicht länger benötigt werden oder den Vorgaben der entsprechenden Bibliotheken entsprechen. Zur Bereinigung stellt Inventor die Funktion Purge Styles bereit.</description>
      <content:encoded><![CDATA[ <p>Inventor hält an Bauteilen und Zeichnungen eine Historie zugewiesener Stile vor. Das können bspw. Materialien sein, die einem Bauteil zugewiesen und anschließend geändert, oder auch Zeichnungsstile, die im Verlauf der Konstruktion gewechselt wurden.</p>
<p>Es gehört zum <em>guten Stil</em> diese Altlasten vor Freigaben zu entfernen. Inventor stellt dafür unter dem Tab <code>Manage</code> eine Funktion namens <code>Purge</code> bereit:</p>


  

<figure class="centered"><img src="/posts/ilogic-purge-styles/button-purge-styles.png"
    alt="Purge Styles Button">
</figure>


<p>Sind an einem Dokument alte Stile vorhanden, werden diese in einem neuen Fenster gelistet und können mit einem Klick auf <kbd>OK</kbd> gelöscht werden:</p>


  

<figure class="centered"><img src="/posts/ilogic-purge-styles/purge-styles-list.png"
    alt="Purge Styles Button">
</figure>


<p>Was in Inventors grafischer Oberfläche augenscheinlich einfach erscheint, stellte sich programmatisch mittels iLogic als nicht so einfach heraus. Es gab zu diesem Anwendungsfall bereits ein Post <a href="https://forums.autodesk.com/t5/inventor-programming-forum/ilogic-to-purge-styles/m-p/11712945"  target="_blank" rel="noreferrer">iLogic to purge styles</a> im Inventor Forum, der bereits mehrere Jahre alt war, aber zu keiner gescheiten Lösung geführt hat. Vier Jahre nach Erstellung des Posts stand ich vor selbigem Problem und konnte es <a href="https://forums.autodesk.com/t5/inventor-programming-forum/ilogic-to-purge-styles/m-p/11712945/highlight/true#M68730"  target="_blank" rel="noreferrer">lösen</a>.</p>
<p>Alte Zeichnungsstile und Stile an 3D Bauteilen müssen grundsätzlich anders programmatisch mit iLogic bereinigt werden:</p>

<h2 class="relative group">Zeichnungsstile bereinigen
    <div id="zeichnungsstile-bereinigen" class="anchor"></div>
    
</h2>
<p>Für Zeichnungsstile stellt die Inventor API den <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-StylesManager"  target="_blank" rel="noreferrer"><code>Styles Manager</code></a> bereit. Dieser Verwaltet jeden Zeichnungsstil als <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-Style"  target="_blank" rel="noreferrer"><code>Style Object</code></a> und bietet auch die Methode <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=Style_Delete"  target="_blank" rel="noreferrer"><code>Delete</code></a> an, um Stile zu löschen.</p>
<p>Die Styles einer Zeichnung sind erreichbar unter <code>oDoc.StylesManager.Styles</code>:</p>


  

<figure class="centered"><img src="/posts/ilogic-purge-styles/drawing-styles.png"
    alt="Drawing Styles">
</figure>


<p>Um alte Stile eindeutig erkennen und auslesen zu können, müssen zwei Bedingungen erfüllt sein:</p>
<ol>
<li>
<p>Das Property <code>InUse</code> muss den Wert <code>False</code> besitzen. Damit ist sichergestellt, dass der Stil nicht aktiv im Zeichnungsdokument verwendet wird.</p>
</li>
<li>
<p>Die <code>StyleLocation</code>, darf nicht der aktiven Stilbibliothek  entsprechen. Auskunft darüber gibt der sogenannte <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=StyleLocationEnum"  target="_blank" rel="noreferrer"><code>StyleLocationEnum</code></a>, der generell drei mögliche Werte kennt:</p>
<ul>
<li><code>kLocalStyleLocation</code>: Der Stil ist nur lokal vorhanden und nicht in der aktiven Bibliotheken enthalten.</li>
<li><code>kLibraryStyleLocation</code>: Der Stil stammt aus einer aktiven Bibliothek.</li>
<li><code>kBothStyleLocation</code>: Der Stil ist lokal am Dokument vorhanden, entspricht aber den Vorgaben einer aktiven Bibliothek.</li>
</ul>
</li>
</ol>
<p>In Code geschrieben sehen diese Bedingungen so aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">PurgeDrawingStylesHistory</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oStyles</span> <span class="ow">As</span> <span class="n">Styles</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">StylesManager</span><span class="p">.</span><span class="n">Styles</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oStyle</span> <span class="ow">As</span> <span class="n">Style</span> <span class="ow">In</span> <span class="n">oStyles</span>
</span></span><span class="line"><span class="cl">        <span class="k">If</span> <span class="p">(</span><span class="n">oStyle</span><span class="p">.</span><span class="n">InUse</span> <span class="o">=</span> <span class="k">False</span> <span class="ow">And</span> <span class="n">oStyle</span><span class="p">.</span><span class="n">StyleLocation</span> <span class="o">&lt;&gt;</span> <span class="n">StyleLocationEnum</span><span class="p">.</span><span class="n">kLibraryStyleLocation</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">            <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Removed unused style: &#34;</span> <span class="o">&amp;</span> <span class="n">oStyle</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">oStyle</span><span class="p">.</span><span class="n">Delete</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>

<h2 class="relative group">Bauteilstile bereinigen
    <div id="bauteilstile-bereinigen" class="anchor"></div>
    
</h2>
<p>Für Bauteile und Baugruppen ist der Vorgang ein Anderer. Um in diesem Fall die Historie an Stilen zu löschen, muss auf das <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-Assets"  target="_blank" rel="noreferrer">Assets Object</a> zurückgegriffen werden. Dieses steht unter <code>oDoc.Assets</code> zur Verfügung. Jedes Asset (Material, Appearance, etc.) wird dort als <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=Asset_AssetType"  target="_blank" rel="noreferrer"><code>AssetType</code></a> gelistet. Die Art des Assets wird über den <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=AssetTypeEnum"  target="_blank" rel="noreferrer"><code>AssetTypeEnum</code></a> ermittelt. Grundsätzlich stehen vier Auswahlmöglichkeiten an AssetTypes zur Auswahl:</p>
<ol>
<li><code>kAssetTypeAppearance</code>: Darstellungseinstellungen, wie bspw. ein Material gerendert werden soll.</li>
<li><code>kAssetTypeMaterial</code>: Ein Material, das zuweisbar ist.</li>
<li><code>kAssetTypePhysicalProperties</code>: Die physischen Eigenschaften des Materials, bspw. Dichte.</li>
<li><code>kAssetTypeUnknown</code>: Ein nicht näher definierbares Asset.</li>
</ol>


  

<figure class="centered"><img src="/posts/ilogic-purge-styles/assets-types.png"
    alt="Purge Styles Button">
</figure>


<p>Relevant sind lediglich <code>kAssetTypeAppearance</code>, <code>kAssetTypeMaterial</code> und die beiden Properties <code>IsReadOnly</code> und <code>IsUsed</code>. Nur ein schreibbares Asset (<code>IsReadOnly = False</code>) kann auch gelöscht werden und, wie auch bei den <a href="/posts/ilogic-purge-styles/#zeichnungsstile-bereinigen" >Zeichnungsstilen</a>, kann nur ein Asset gelöscht werden, das nicht aktive in Verwendung (<code>IsUsed = False</code>) ist.</p>
<p>Zusätzlich gibt es ein Verhalten in Inventor, das eine strikte Einhaltung bei der Löschung der Stile erforderlich macht: Material Assets müssen <strong>vor</strong> den Appearance Assets gelöscht werden.</p>
<p>Mit diesen Erkenntnissen kann eine Sub-Routine bspw. folgendermaßen aussehen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">PurgePartStylesHistory</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oAssets</span> <span class="ow">As</span> <span class="n">Assets</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">Assets</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Clean Material Assets first
</span></span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oAsset</span> <span class="ow">As</span> <span class="n">Asset</span> <span class="ow">In</span> <span class="n">oAssets</span>
</span></span><span class="line"><span class="cl">        <span class="k">Try</span>
</span></span><span class="line"><span class="cl">            <span class="k">If</span> <span class="p">(</span><span class="n">oAsset</span><span class="p">.</span><span class="n">AssetType</span> <span class="o">=</span> <span class="n">AssetTypeEnum</span><span class="p">.</span><span class="n">kAssetTypeMaterial</span> <span class="ow">And</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">IsReadOnly</span> <span class="o">=</span> <span class="k">False</span> <span class="ow">And</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">IsUsed</span> <span class="o">=</span> <span class="k">False</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">                <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Removed unused Material Asset: &#34;</span> <span class="o">&amp;</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">oAsset</span><span class="p">.</span><span class="n">Delete</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">        <span class="k">Catch</span>
</span></span><span class="line"><span class="cl">            <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Error when removing unused Material Asset.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">End</span> <span class="k">Try</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Clean Appearance Assets second
</span></span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oAsset</span> <span class="ow">As</span> <span class="n">Asset</span> <span class="ow">In</span> <span class="n">oAssets</span>
</span></span><span class="line"><span class="cl">        <span class="k">Try</span>
</span></span><span class="line"><span class="cl">            <span class="k">If</span> <span class="p">(</span><span class="n">oAsset</span><span class="p">.</span><span class="n">AssetType</span> <span class="o">=</span> <span class="n">AssetTypeEnum</span><span class="p">.</span><span class="n">kAssetTypeAppearance</span> <span class="ow">And</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">IsReadOnly</span> <span class="o">=</span> <span class="k">False</span> <span class="ow">And</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">IsUsed</span> <span class="o">=</span> <span class="k">False</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">                <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Removed unused Appearance Asset: &#34;</span> <span class="o">&amp;</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">oAsset</span><span class="p">.</span><span class="n">Delete</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">        <span class="k">Catch</span>
</span></span><span class="line"><span class="cl">            <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Error when removing unused Appearance Asset.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">End</span> <span class="k">Try</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p>Das wäre aber zu einfach! Bei Bauteilen und Baugruppen kommt noch eine Besonderheit zum Tragen und zwar die <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-8E771DBE-1107-4AE8-BE3E-AF3A7977F3C6"  target="_blank" rel="noreferrer">Model States</a>, die seit Version 2022 in Inventor zur Verfügung stehen. Diese können zur Variantenkonstruktion genutzt werden, sprich in einem Bauteil können unterschiedliche Materialien einem Model State zugewiesen werden. Es gilt daher zu beachten, dass lediglich der aktive Model State des Dokuments bereinigt wird.</p>

<h2 class="relative group">iLogic für Zeichnungen und Bauteile
    <div id="ilogic-für-zeichnungen-und-bauteile" class="anchor"></div>
    
</h2>
<p>Im Post <a href="/posts/ilogic-documenttypes/">iLogic: Dokumententypen</a> wurde bereits erläutert, wie Dokumententypen eindeutig identifiziert wurde. Möchte man nun den gezeigten Code in einer iLogic laufen lassen und zweitgleich Bauteile und Zeichnungen adressieren, kann dies folgendermaßen aussehen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kPartDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">PurgePartStylesHistory</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span> <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kDrawingsDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">PurgeDrawingStylesHistory</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">PurgePartStylesHistory</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oAssets</span> <span class="ow">As</span> <span class="n">Assets</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">Assets</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Clean Material Assets first
</span></span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oAsset</span> <span class="ow">As</span> <span class="n">Asset</span> <span class="ow">In</span> <span class="n">oAssets</span>
</span></span><span class="line"><span class="cl">        <span class="k">Try</span>
</span></span><span class="line"><span class="cl">            <span class="k">If</span> <span class="p">(</span><span class="n">oAsset</span><span class="p">.</span><span class="n">AssetType</span> <span class="o">=</span> <span class="n">AssetTypeEnum</span><span class="p">.</span><span class="n">kAssetTypeMaterial</span> <span class="ow">And</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">IsReadOnly</span> <span class="o">=</span> <span class="k">False</span> <span class="ow">And</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">IsUsed</span> <span class="o">=</span> <span class="k">False</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">                <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Removed unused Material Asset: &#34;</span> <span class="o">&amp;</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">oAsset</span><span class="p">.</span><span class="n">Delete</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">        <span class="k">Catch</span>
</span></span><span class="line"><span class="cl">            <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Fil</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Error when removing unused Material Asset.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">End</span> <span class="k">Try</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Clean Appearance Assets second
</span></span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oAsset</span> <span class="ow">As</span> <span class="n">Asset</span> <span class="ow">In</span> <span class="n">oAssets</span>
</span></span><span class="line"><span class="cl">        <span class="k">Try</span>
</span></span><span class="line"><span class="cl">            <span class="k">If</span> <span class="p">(</span><span class="n">oAsset</span><span class="p">.</span><span class="n">AssetType</span> <span class="o">=</span> <span class="n">AssetTypeEnum</span><span class="p">.</span><span class="n">kAssetTypeAppearance</span> <span class="ow">And</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">IsReadOnly</span> <span class="o">=</span> <span class="k">False</span> <span class="ow">And</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">IsUsed</span> <span class="o">=</span> <span class="k">False</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">                <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Removed unused Appearance Asset: &#34;</span> <span class="o">&amp;</span> <span class="n">oAsset</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">oAsset</span><span class="p">.</span><span class="n">Delete</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">        <span class="k">Catch</span>
</span></span><span class="line"><span class="cl">            <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Error when removing unused Appearance Asset.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">End</span> <span class="k">Try</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">PurgeDrawingStylesHistory</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oStyles</span> <span class="ow">As</span> <span class="n">Styles</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">StylesManager</span><span class="p">.</span><span class="n">Styles</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oStyle</span> <span class="ow">As</span> <span class="n">Style</span> <span class="ow">In</span> <span class="n">oStyles</span>
</span></span><span class="line"><span class="cl">        <span class="k">If</span> <span class="p">(</span><span class="n">oStyle</span><span class="p">.</span><span class="n">InUse</span> <span class="o">=</span> <span class="k">False</span> <span class="ow">And</span> <span class="n">oStyle</span><span class="p">.</span><span class="n">StyleLocation</span> <span class="o">&lt;&gt;</span> <span class="n">StyleLocationEnum</span><span class="p">.</span><span class="n">kLibraryStyleLocation</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">            <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Removed unused style: &#34;</span> <span class="o">&amp;</span> <span class="n">oStyle</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">oStyle</span><span class="p">.</span><span class="n">Delete</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p>Dieser Code könnte somit auf einen <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-B14A47F4-81D2-4627-A973-2BC7F790DF89"  target="_blank" rel="noreferrer">Event Trigger</a>, bspw. <code>Before Save Document</code>, für alle Dokumente gelegt werden und würde bei jedem Speichern die Stilhistorie bereinigen.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Alte Properties automatisiert löschen</title>
      <link>https://jbetzen.net/posts/ilogic-delete-properties/</link>
      <pubDate>Sun, 05 Oct 2025 11:43:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-delete-properties/</guid>
      <description>Im Laufe eines Projekts sammeln sich häufig Properties an, die im Verlauf keinerlei Verwendung mehr finden. Diese können mittels einer Liste und iLogic automatisiert gelöscht werden.</description>
      <content:encoded><![CDATA[ <p>Der Post <a href="/posts/ilogic-propertysets/">iLogic: Propertysets</a> hat bereits die <strong>4 Property Sets</strong> aus Inventor vorgestellt. Im Konstruktionsalltag spielen hauptsächlich die <strong>Custom Properties</strong>, auch <em>User Defined Properties</em> genannt, eine Rolle, da sie dem User erlauben dort Werte zu hinterlegen, die später auf Stücklisten oder Zeichnungen sichtbar werden.</p>

<h2 class="relative group">Anwendungsfall
    <div id="anwendungsfall" class="anchor"></div>
    
</h2>
<p>Oftmals zeigt sich der Fall auf, dass Properties im Laufe eine Projekts angelegt, später aber nicht länger benötigt werden. Ursachen für diesen Zustand gibt es viele, bspw. das Überarbeiten bestehender Prozesse, konzeptfrei arbeitende Vault Admins oder aber der Wechsel eines Vault Admins in der Firma. Das händische Löschen von Properties ist in einem solchen Fall ein zeitraubender Task, den iLogic geflissentlich übernehmen kann.</p>

<h2 class="relative group">Code zum Löschen der Properties
    <div id="code-zum-löschen-der-properties" class="anchor"></div>
    
</h2>
<p>Das folgende Beispiel definiert eine Liste <code>listOutdatedPropertyNames</code> mit alten Propertynamen, die nicht länger benötigt werden. Diese Liste ist beispielhaft und kann mit Propertynamen aus realen Anwendungsfällen gefüllt werden. Wichtig und zu beachten dabei ist lediglich, dass der letzte Propertyname <strong>nicht</strong> mit dem Zeichen <code>,</code> beendet wird.</p>
<p>Der Code iteriert über alle Custom Properties, die im aktiven Dokument vorhanden sind und prüft jedes einzeln, ob es in der definierten Liste enthalten ist und löscht es, sofern dies der Fall ist:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Liste mit alten Propertynamen definieren, die
</span></span></span><span class="line"><span class="cl"><span class="c">&#39; durch die iLogic entfernt werden sollen
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">listOutdatedPropertyNames</span> <span class="o">=</span> <span class="k">New</span> <span class="kt">String</span><span class="p">(){</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Adhesive Assembly&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Adhesived Assembly&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Approved By&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Area&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Author&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Batch Number Relevant&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Blechabmessung&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Fläche&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Hurz&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Adressiere das Custom Property Set
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oPropSet</span> <span class="ow">As</span> <span class="n">PropertySet</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">PropertySets</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="s">&#34;Inventor User Defined Properties&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="k">Each</span> <span class="n">oProperty</span> <span class="ow">In</span> <span class="n">oPropSet</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">listOutdatedPropertyNames</span><span class="p">.</span><span class="n">Contains</span><span class="p">(</span><span class="n">oProperty</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">oProperty</span><span class="p">.</span><span class="n">Delete</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Browser Add-Ons für ein schöneres Internet</title>
      <link>https://jbetzen.net/posts/essential-browser-addons/</link>
      <pubDate>Sun, 17 Aug 2025 18:08:58 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/essential-browser-addons/</guid>
      <description>Die schönen Zeiten des Internets sind vorbei. Dieser Post listet all meine präferierten Browser-Addons, die das tägliche Surfen wieder genießbar machen.</description>
      <content:encoded><![CDATA[ <p>Was war es für eine schöne Zeit, als das Internet sich noch in guter <em>Anarchomanier</em> präsentierte. Viele Dienste, dezentral und fernab der Fänge großer Konzerne, dessen primäres Ziel es ist über Nutzerdaten und Surfgewohnheiten Umsatz zu generieren. Das Weltnetz ist heute geprägt von Tracking, Werbung, <em>Sponsored Videos</em> und verseuchten URLs, die das Surf-Erlebnis sichtlich einschränken.</p>
<p>Im folgenden möchte ich meine Lieblings-Addons präsentieren, die ich in Kombination mit meinem Browser der Wahl Firefox nutze:</p>

<h2 class="relative group">uBlock
    <div id="ublock" class="anchor"></div>
    
</h2>
<p><a href="https://addons.mozilla.org/de/android/addon/ublock-origin/"  target="_blank" rel="noreferrer">uBlock Origin</a> ist ein Ad-Blocker und absolute Pflicht in jedem Browser. Nicht nur blockiert er ressourcenschonend und effizient Werbung und unterbindet Tracking, auch ist er <a href="https://github.com/gorhill/uBlock"  target="_blank" rel="noreferrer">quelloffen</a> und wird durch eine lebhafte Community täglich mit aktuellen Blocklists versorgt.</p>
<p>uBlock Origin bringt eine sinnvolles Set an vordefinierten Blocklists mit und erlaubt auch das Aktivieren zusätzlicher, sowie das Anfertigen eigener Listen. Für Nutzer im deutschsprachigen Raum empfiehlt es sich die Blockliste <code>DEU: EasyList Germany</code> zu aktivieren.</p>
<p><a href="https://apps.apple.com/us/app/ublock-origin-lite/id6745342698"  target="_blank" rel="noreferrer">uBlock ist auch für iOS verfügbar</a> und löst somit kostenpflichtige AdBlocking Solutions wie AdGuard ab.</p>

<h2 class="relative group">SponsorBlock
    <div id="sponsorblock" class="anchor"></div>
    
</h2>
<p><a href="https://addons.mozilla.org/de/firefox/addon/sponsorblock/"  target="_blank" rel="noreferrer">SponsorBlock</a> für Youtube ermöglicht es gesponserte Segmente in Youtube Videos zu überspringen. Es beschränkt sich aber nicht nur auf solche Segmente, sondern kann auch sinnfreie Intros skippen oder direkt zu den Highlights der Videos springen und den selbstdarstellerischen Teil heutiger Creators vom User fernhalten. Das Addon ist ebenfalls <a href="https://github.com/ajayyy/SponsorBlock"  target="_blank" rel="noreferrer">quelloffen</a>. Die Timestamps zum Überspringen der Segmente sind gespeist durch eine große nutzergenerierte Datenbank. Neu erscheinende Videos gängiger Kanäle sind oftmals bereits nach kurzer Zeit in der SponsorBlock-Datenbank.</p>
<p>Dieses Addon macht Youtube im Kern erst wieder benutzbar. Vorbei sind die Zeiten von <em>press like, subscribe and hit the bell</em> und wenn man sich einmal an diese Erweiterung gewöhnt hat, möchte man Youtube nie wieder ohne nutzen.</p>
<p>Der Entwickler hat eine dedizierte <a href="https://sponsor.ajay.app/"  target="_blank" rel="noreferrer">Website</a> für diese Erweiterung am Netz und stellt es dort ausführlich vor.</p>

<h2 class="relative group">DeArrow
    <div id="dearrow" class="anchor"></div>
    
</h2>
<p><a href="https://dearrow.ajay.app/"  target="_blank" rel="noreferrer">DeArrow</a> ist ebenfalls von dem selben Entwickler wie SponsorBlock und verbessert gleichso die Youtube-Experience. Dieses Mal wird jedoch nicht Werbung und lästig Intros adressiert, sondern bereits die Darbietung der Video-Thumbnails und Beschreibungen auf Youtube. Wer kennt sie nicht, diese schrecklichen Clickbait-Titel, diese grässlich verzogenen Fratzen und den übermäßigen Einsatz von Ausrufezeichen und Emojis?</p>
<p>DeArrow ist angetreten, diesem Treiben ein Ende zu bereiten und tut das erstaunlich gut.</p>
<blockquote><p>DeArrow is an open source browser extension for crowdsourcing better titles and thumbnails on YouTube. The goal is to make titles accurate and reduce sensationalism. No more arrows, ridiculous faces, and no more clickbait.</p>
</blockquote>
<h2 class="relative group">Consent-O-Matic
    <div id="consent-o-matic" class="anchor"></div>
    
</h2>
<p><a href="https://addons.mozilla.org/de/firefox/addon/consent-o-matic/"  target="_blank" rel="noreferrer">Consent-O-Matic</a> ist ein Addon, das sich einem wichtigen Thema annimmt, nämlich <em>GDPR Consent Pop-Ups</em>. Wer eine Webseite besucht wird allzu oft von einem lästigen Cookie-Banner begrüßt, der zur Zustimmung aller Tracking-Optionen auffordert. Ist man damit nicht einverstanden landet man oft in einem Wust von Untermenüs an <em>Dark Patterns</em>, die oftmals die eigene Geduld so weit ausreizen, bis man genervt auf <em>Accept All</em> klickt. Consent-O-Matic füllt diese Dark Patterns automatisiert aus, so dass der User nur noch die Auswahl bestätigen muss.</p>
<p>Consent-O-Matic ist ein Projekt der Aarhuus University in Dänemark, ist ebenfalls <a href="https://github.com/cavi-au/Consent-O-Matic"  target="_blank" rel="noreferrer">quelloffen</a> und wird auf einer eigenen <a href="https://consentomatic.au.dk/"  target="_blank" rel="noreferrer">Website</a> vorgestellt.</p>
<p>Das Addon gibt es auch als <a href="https://apps.apple.com/gb/app/consent-o-matic/id1606897889"  target="_blank" rel="noreferrer">Safari-Extension</a> für iOS.</p>

<h2 class="relative group">ClearURLs
    <div id="clearurls" class="anchor"></div>
    
</h2>
<p><a href="https://addons.mozilla.org/de/firefox/addon/clearurls/"  target="_blank" rel="noreferrer">ClearURLs</a> bereinigt Links von URL-Tracking-Parametern. Webseiten nutzen oftmals bei Benutzung der Sharing-Funktion von Inhalten spezifische URL-Parameter, die Rückschlüsse auf den User, seine Endgeräte oder das eigene Soziotop ermöglichen. ClearURLs ist <a href="https://gitlab.com/ClearURLs/ClearUrls"  target="_blank" rel="noreferrer">quelloffen</a> und entfernt diese Tracking-Parameter.</p>

<h2 class="relative group">redditUntranslate
    <div id="reddituntranslate" class="anchor"></div>
    
</h2>
<p>Das quelloffene Add-On <a href="https://github.com/SeidSmatti/redditUntranslate"  target="_blank" rel="noreferrer">redditUntranslate</a> löst ein Problem, das eigentlich gar nicht existieren sollte. Google ist in ihrem dauerhaften Bestreben das Internet weiterhin kaputt zu machen mit einer neuen Marotte aufgewartet: Sucht man bei Google eine Information, die zu Reddit führt, und klickt auf diesen Link, wird automatisch ein URL-Parameter <code>?tl=de</code> eingefügt, der eine automatische Übersetzung des Inhalts erzwingt. Dieses zweifelhafte &ldquo;Feature&rdquo; lässt sich nicht abschalten und sorgt für <a href="https://www.reddit.com/r/bugs/comments/1dfnsub/how_do_i_stop_the_automatic_translation_of_all/"  target="_blank" rel="noreferrer">Unmut</a>.</p>
<p>redditUntranslate entfernt diesen URL-Parameter beim Aufruf der Seite und verhindert somit eine automatische Übersetzung.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Ein Krawallant im Haus</title>
      <link>https://jbetzen.net/posts/krawallant-im-haus/</link>
      <pubDate>Sun, 03 Aug 2025 14:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/krawallant-im-haus/</guid>
      <description>Ein Artikel darüber, wie sich das Verhalten eines psychisch gestörten Bewohners auf eine intakte Hausgemeinschaft auswirkt und welche zweifelhaften Freuden das Zusammenwohnen Tür an Tür mit sich führt.</description>
      <content:encoded><![CDATA[ 
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Dieser Artikel handelt von einem Menschen mit psychischer Störung.</span>
</div>

<p>Nach einer zweijährigen Odyssee schloss sich vor Kurzem, per richterlicher Anordnung, ein unschönes Kapitel in dem Mehrparteienhaus, in dem ich lebe. Eine Person mit offenkundig psychischen Problemen hielt über viele Monate hinweg die gesamte Hausgemeinschaft auf Trab. Was unscheinbar begann, endete in Wahnvorstellungen, Sachbeschädigung und Fremdgefährdung.</p>
<p>Anregung für diesen Artikel war ein Beitrag aus der Zeit mit dem Titel <a href="https://archive.ph/SDpiy"  target="_blank" rel="noreferrer">Gefahr in der Hausgemeinschaft: Als die Angst einzog</a>, der erschreckend viele Parallelen zu dem Fall in meinem Haus aufwies. Der Artikel der Zeit beschreibt eindrücklich den Fall einer wahnhaften Frau, ihrem Treiben und den Folgen für eine intakte Hausgemeinschaft. Gleichzeitig schildert er eindrücklich das Ohnmachtsgefühl nichts gegen den Störenfried tun zu können und das Verhalten einer Polizei, deren Hände in solchen Fällen gebunden sind. Die Kommentarspalte unter dem Artikel gibt Einblick in anderer Menschen Leben, die ebenfalls von einem solchen Fall betroffen sind.</p>
<p>Ich möchte in diesem Beitrag konkret aufzeigen, wie sich die Situation im Falle unserer Hausgemeinschaft abgespielt hat.</p>

<h2 class="relative group">Phase 1: Stiller Einzug
    <div id="phase-1-stiller-einzug" class="anchor"></div>
    
</h2>
<p>Nachdem im Jahr 2023 eine Wohneinheit in unserem Mehrparteienhaus frei wurde, zogen zeitnah im ersten Stock eine Mutter und ihr Sohn, letzterer im folgenden Text nur noch &ldquo;S&rdquo; genannt, in die Wohnung ein. Die Mutter war geschätzt Mitte 50 und ihr Sohn höchstens in den frühen 20ern. Persönlich habe ich von dem Einzug nichts mitbekommen und laut Erzählungen eines Hausbewohners zogen beide ohne nennenswerten Hausstand ein. Es gab keinen gebuchten Möbeltransporter, kaum Kartons oder großes Schleppen.</p>
<p>Die Mutter sah man nur früh, wenn sie zu Fuß den Weg zur Arbeit antrat oder nachmittags davon zurück kehrte. S bekam niemand zu Gesicht. Auch nahm man ihn nicht auffallend wahr. Hätte man es mir nicht erzählt, hätte ich nie vermutet, dass neben der Mutter noch eine weitere Person in dem Haushalt leben würde.</p>
<p>In diesem Modus verlief das Zusammenleben nahezu ein Dreivierteljahr bis sich der Sommer 2024 anbahnte und wir als Hausgemeinschaft vermehrt den Garten vor dem Haus für gemeinsames Grillen, Feierabendbierchen und Plausch in der Sonne nutzten. Das Zimmer von S hatte Blick auf den Garten und das Treiben schien ihm zutiefst zuwider.</p>

<h2 class="relative group">Phase 2: Zettelbotschaften
    <div id="phase-2-zettelbotschaften" class="anchor"></div>
    
</h2>
<p>Es war besagter Sommeranfang als eines Tages ein vereinzelter Zettel mit einem Bibelvers urplötzlich im Garten auftauchte. Er wurde offenkundig aus dem Fenster im ersten Stock unmittelbar in den darunter gelegenen Garten geworfen. Die Sammlung der Zettel wuchs beständig von Tag zu Tag. Mal waren sie zu Papierfliegern gefaltet und andere male waren sie an Geldmünzen geklebt, um gezielter abgeworfen werden zu können. Einmal hing aus dem Nichts ein Pamphlet an der Pinnwand im Hausflur.</p>
<p>Es waren keine lebensbejahenden Bibelstellen, die auf den Zetteln vermerkt waren. Auf einigen Zetteln waren lediglich Songs mit zugehörigem Youtube-Link mit Titeln wie <a href="https://www.youtube.com/watch?v=tOq4S2G-H1s"  target="_blank" rel="noreferrer">Kapelle des Todes</a> oder <a href="https://www.youtube.com/watch?v=vEZzH-dWDOU"  target="_blank" rel="noreferrer">Is He Worthy</a> geschrieben. Andere enthielten persönliche Drohgebärden, wie <em>&ldquo;Euer Los. [&hellip;] Entscheidet weise, Mädels&rdquo;</em>. Heraus stach auch eine kontinuierlich propagierte Referenz auf die Webseite <a href="http://trompete-gottes.com"  target="_blank" rel="noreferrer">trompete-gottes.com</a>, die wiederum einen URL-Redirect auf eine Webseite namens <a href="https://awakeningforreality.com/"  target="_blank" rel="noreferrer">Trumpet Call of God</a> besitzt.</p>
<p>Über die Trompetenfreunde ist relativ wenig im Weltnetz zu finden. Es scheint sich um einen <a href="https://www.reddit.com/r/atheism/comments/2ln8ag/has_anyone_heard_of_these_trumpet_call_of_god/"  target="_blank" rel="noreferrer">sektenhaften Kult</a> zu handeln, der einem Anführer namens <a href="https://ttruthalwaysprevails.blogspot.com/"  target="_blank" rel="noreferrer">Timothy</a> folgt und der im echten Leben <em>Speed T. Rathbun</em> heißt. Timothy hatte, laut eigenen Aussagen, 2010 mal einen direkten Plausch mit Gott, hat alles schriftlich festgehalten und bietet diesen zweifelhaften Content in diversen PDFs, verstreut über mehrere Webseiten, kostenfrei im Internet zum Download feil.</p>


  
  
  
  
  
  
  
  
  
  <div class="width-patch"></div>
<div id="gallery-43e856c45109025dd80f222a17c75e6f" class="gallery">
  
  <img src="/posts/krawallant-im-haus/00.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/01.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/02.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/03.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/04.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/05.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/06.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/07.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/08.jpg" class="grid-w50" />
  <img src="/posts/krawallant-im-haus/09.jpg" class="grid-w50" />
</div>

<p>Bis zu diesem Punkt und bis auf die Zettel, war sonst nichts von S zu vernehmen. Eine persönliche Ansprache oder Kontaktaufnahme seinerseits blieb aus. Der Sommer ging in den Herbst über und die sonst gewohnte Ruhe von S gehörte der Vergangenheit an.</p>

<h2 class="relative group">Phase 3: Fluchen &amp; Ruhestörung
    <div id="phase-3-fluchen--ruhestörung" class="anchor"></div>
    
</h2>
<p>Im Herbst konnte man mit steigender Tendenz lautes Fluchen und Beleidigungen außerhalb von S Wohnungstür im Treppenhaus vernehmen. Es waren Kraftausdrücke aller <em>Couleur</em>, die er augenscheinlich gegen seine eigene Mutter richtete. <em>&ldquo;Du Schlampe. Du Hure. Ich bring dich um!&rdquo;</em></p>
<p>Mit der Zeit wurde ersichtlich, dass er auch fluchte, wenn seine Mutter gar nicht zu Hause anwesend war. Zu den Flüchen gesellte sich rasch auch laut vernehmbarer Krach, der scheinbar davon ausging, dass S mit physischer Gewalt auf Gegenstände und die Wände der Wohnung einwirkte. Die nun fast täglich auftretenden Ausraster zur Tageszeit wurden zur Normalität und verlagerten sich alsbald in unchristliche nächtliche Zeitfenster zwischen 0 und 3 Uhr.</p>
<p>Insgeheim herrschte initial die Hoffnung, dass seine Mutter intervenieren und dem Treiben ein Ende bereiten würde. Sprach man sie auf das Verhalten ihres Sohnes an, stellte sich schnell heraus, dass sie die Schuld nicht bei ihm sah, sondern dass die Hausgemeinschaft sein Verhalten <em>triggern</em> würde. Ihren Sohn träfe da gewiss keine Schuld. Verständnis ihrerseits für vorgetragene Beschwerden war nie vernehmbar. Auch schien sie das Verhalten ihres Sohnes gänzlich für sich normalisiert zu haben.</p>
<p>Zeitnah lernte ich S das erste mal persönlich kennen. Ich saß im Garten in der herbstlichen Abendsonne, als er unerwartet aus dem Haus trat, um den Müll in den Mülltonnen zu entsorgen. Ein kleiner, zierlicher junger Mann in Adiletten und kurzer Hose stand mir gegenüber. Er wirkte auf den ersten Blick völlig normal und blickte mich doch mit stechendem Blick an, ohne ein Wort zu verlieren. Ich begrüßte ihn und stellte mich vor. Er tat es ebenfalls und wirkte dabei sichtlich nervös und kurz angebunden, als wolle er schnell wieder ins Haus. Ob alles in Ordnung wäre, fragte ich, was mit einem knappen <em>&ldquo;Ja&rdquo;</em> begegnet wurde. Er guckte mich weiterhin nur an. Was er denn so machen würde, fragte ich, worauf er antwortete <em>&ldquo;Er mache sein eigenes Ding&rdquo;</em>. Noch immer stand er einfach da und guckte mich an. Wo er denn herkäme und aufgewachsen sei, fragte ich, woraufhin er erwiderte er wäre in der Vergangenheit sehr oft mit seiner Mutter umgezogen. Dann wünschte er mir einen schönen Abend und ging zurück ins Haus.</p>
<p>Bis zu diesem Punkt spielte sich das Verhalten von S in Gänze innerhalb seiner eigenen vier Wände ab. Doch auch das sollte ab nun der Vergangenheit angehören.</p>

<h2 class="relative group">Phase 4: Psychoterror
    <div id="phase-4-psychoterror" class="anchor"></div>
    
</h2>
<p>Eines Tages klingelte es zum späten Abend bei einem Bewohner des Hauses an der Tür. S hatte sich aus der Wohnung gewagt, um ein persönliches Anliegen vorzutragen. Als die Tür geöffnet wurde sagte er den Satz: <em>&ldquo;Wir haben ein Problem&rdquo;</em>.</p>
<p>Das Gespräch verlief wirr. Im Kern warf S der Person vor mittels Gedankenkraft seine Wassertherme zu steuern und somit Lärm zu verursachen, um willentlich &ldquo;Psychoterror&rdquo; an ihm zu verüben. Beschwichtigungen, dass dies nicht der Fall sei, erreichten S nicht und langsam wurde allen im Haus ersichtlich, dass eine Stufe erreicht war, die Vorsicht und Umsicht von allen Hausbewohnern erforderte.</p>
<p>Die Lärmbelästigung nahm in den folgenden Wochen mehr und mehr zu. Die Polizei wurde in regelmäßigen Abständen eingeschaltet, blieb in der Regel aber nur kurz, da keine Fremdgefährdung vorlag. Sie empfahl ein Lärmprotokoll zu führen und mit der Vermietung in Kontakt zu treten. Mehr könne sie an der Stelle nicht tun.</p>
<p>Ein Nachbar aus dem Nebengebäude beschwerte sich in den Folgetagen, weil S am Fenster gestanden und ihn auf der Straße mit Kraftausdrücken beleidigt hätte. Als ich S eines Tages am Fenster stehen sah, streckte er mir energisch den Mittelfinger entgegen, obwohl ich ihm stets freundlich gesonnen war.</p>
<p>Eines weiteren Tages traf ich S im Hausflur an. Er stand dort einfach ziellos auf halbem Stock herum. Auf die Frage, ob es ihm gut ginge folgte erneut nur ein knappes <em>&ldquo;Ja&rdquo;</em>. Auf meine Nachfrage, ob es ihm <em>wirklich</em> gut ginge, geriet er unerwartet in einen Redeschwall. Er hätte ein gewaltiges Problem mit dem Bewohner, bei dem er auch bereits geklingelt hätte. Der besagte Mitbewohner würde <em>&ldquo;Psychoterror&rdquo;</em> und <em>&ldquo;psychische Folter&rdquo;</em> an ihm ausüben und er müsse sich da konsequenterweise etwas <em>finales</em> überlegen, um diesem Treiben ein Ende zu bereiten. Auf meinen Einwand, dass gerade sein Verhalten das gesamte Haus gegen ihn aufbrächte, entgegnete er nur mit einem abschätzigen Lachen. Zugänglich war er ab dem Punkt nicht mehr und unsere Wege trennten sich.</p>
<p>Einmal öffnete S die Haustür als ich gerade Heim kam und sprach mich direkt mit meinem Vornamen an, den er sich scheinbar aus der Vorstellung im Garten gemerkt hatte. Er hätte ein Problem. Sein Handy hätte urplötzlich keinen Empfang mehr und er hätte den Verdacht abgehört zu werden oder gehackt worden zu sein. Er bat um eine technische Einschätzung des Sachverhalts und wirkte freundlich gesonnen und kommunikativ.</p>
<p>Ein weiteres mal kam ich spät abends nach Hause, schloss die Haustür auf und S stand direkt dahinter im Dunkeln - wer weiß wie lange schon. Ich erschrak mich zu Tode und sagte ihm in freundlichem Ton, dass er mir einen guten Schrecken eingejagt hätte. Er sagte nichts. Stattdessen ging er langsam seitwärts die Treppe hinauf bis zu seiner Haustür und schaute mich dabei durchweg an.</p>
<p>Im folgenden ein exemplarisches kombiniertes Lärmprotokoll aller Hausbewohner aus dieser Zeit:</p>
<table>
  <thead>
      <tr>
          <th>TAG</th>
          <th>UHRZEIT</th>
          <th>VORFALL</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>01.12</td>
          <td>19:15</td>
          <td>Dumpfe Schläge gegen Wände</td>
      </tr>
      <tr>
          <td>04.12</td>
          <td>07:00</td>
          <td>Hämmern gegen Wände</td>
      </tr>
      <tr>
          <td>05.12</td>
          <td>22:15</td>
          <td>Mehrfach lautes physisches Knallen, als falle etwas massives um.</td>
      </tr>
      <tr>
          <td>05.12</td>
          <td>00:15</td>
          <td>Schreien</td>
      </tr>
      <tr>
          <td>06.12</td>
          <td>22:00</td>
          <td>Poltern / Schlagen gegen Haustür / lautstarkes Fluchen &amp; Beleidigen</td>
      </tr>
      <tr>
          <td>09.12</td>
          <td>03:00</td>
          <td>Geräusche (undefinierbar)</td>
      </tr>
      <tr>
          <td>10.12</td>
          <td>02:10</td>
          <td>Lautes Fluchen am Hinterhoffenster</td>
      </tr>
      <tr>
          <td>10.12</td>
          <td>14:30</td>
          <td>Hämmern gegen Wände</td>
      </tr>
      <tr>
          <td>12.12</td>
          <td>17:00</td>
          <td>Eskalation mit Polizeieinsatz. Offene Drohung seinerseits an Hausbewohner, als diese sich über Lärmbelästigung beschweren.</td>
      </tr>
      <tr>
          <td>13.12</td>
          <td>15:15</td>
          <td>Lautes Gerumpel</td>
      </tr>
      <tr>
          <td>14.12</td>
          <td>02:00</td>
          <td>Lautes Gerumpel. Erneut Polizeieinsatz.</td>
      </tr>
      <tr>
          <td>17.12</td>
          <td>00:30</td>
          <td>Lauter hasserfüllter Monolog. Vernehmbar im Treppenhaus.</td>
      </tr>
      <tr>
          <td>18.12</td>
          <td>05:15</td>
          <td>Hämmern gegen Wände</td>
      </tr>
      <tr>
          <td>20.12</td>
          <td>02:20</td>
          <td>Lautes physisch vernehmbare dumpfe Geräusche. Wände wackeln merklich.</td>
      </tr>
      <tr>
          <td>26.12</td>
          <td>23:00</td>
          <td>Lautes Geschrei. Deutlich vernehmbar im Treppenhaus.</td>
      </tr>
      <tr>
          <td>28.12</td>
          <td>01:05</td>
          <td>Hämmern gegen Wände</td>
      </tr>
      <tr>
          <td>28.12</td>
          <td>14:15</td>
          <td>Zeigt Mittelfinger durchs Fenster zur Straße und beleidigt Leute. Später erneut Polizei im Haus wegen laut vernehmbarer Hasstiraden. Polizei blieb gute zwei Stunden.</td>
      </tr>
  </tbody>
</table>
<p>Zu aller Überraschung war bereits seit einiger Zeit urplötzlich seine Mutter verschwunden. Sie war einfach seit Wochen nicht mehr da und S war auf sich allein gestellt und ohne familiäre Bezugsperson. Eines Abends klingelte er bei einem Nachbarn und bat um Geld, da er schlichtweg nichts mehr zu essen hätte. Er konnte einem Leid tun&hellip;</p>
<p>Seine Mutter sollte nur noch einmal auftauchen und eine neue Phase der Eskalation einleiten.</p>

<h2 class="relative group">Phase 5: Sachbeschädigung
    <div id="phase-5-sachbeschädigung" class="anchor"></div>
    
</h2>
<p>Die nächtlichen Exzesse und Ruhestörungen gingen unvermittelt weiter. Die Polizei war in der Regel einmal die Woche im Haus, sprach mit S und ging. Der fortlaufend geäußerten Bitte, einen Amtsarzt hinzuzuziehen und eine Einweisung in die Psychiatrie aus höherer Instanz zu erwirken, wurde beharrlich nicht stattgegeben, da weder Fremd-, noch Eigengefährdung vorläge.</p>
<p>Eines Tages war seine Mutter urplötzlich auf einmal wieder da. Es waren lautstarke Beschimpfungen seinerseits im Treppenhaus zu vernehmen. Sie schien ihm irgendwas gesagt zu haben, was ihm offenkundig nicht gefiel. Durch das Fenster sah ich die Mutter fluchtartig die Wohnung verlassen und Minuten später hörte man das laute Klirren berstender Scheiben durch den Hausflur. S hatte in einem Wutanfall die Glasscheibe in seiner Haustür eingeschlagen. Seine Mutter ward ab diesem Zeitpunkt nie wieder gesehen.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/krawallant-im-haus/10.jpg"
          alt="Zerschlagene Scheibe in Haustür"
        />
  
  
  </figure>
<p>Ein paar Tage später wurde ich morgens um 6 Uhr unsanft aus dem Schlaf gerissen, weil ein Gegenstand mit Wucht und aus Höhe im Hinterhof auf den Boden prallte. S hatte die gesamte Beistellware, die auf der Fensterbank platziert war, inkl. zweier großer Pflanzen und deren massiven Tontöpfen, in den Hinterhof des Nachbarhauses geworfen. In dem angrenzenden Nachbarhaus leben Kinder, die genau in diesem Hinterhof ihre Fahrräder abstellen mit denen sie morgens zur Schule und zum Kindergarten fahren. Der Punkt der Fremdgefährdung war an dieser Stelle erreicht. Eine rote Linie wurde von S überschritten.</p>
<p>Minuten später war die Polizei und ein Rettungswagen vor Ort. S antwortete nicht auf ihr energisches Klingeln, Klopfen und Rufen. Er schwieg und tat, als wäre er nicht zu Hause. Die Feuerwehr wurde hinzugezogen, um die Haustür gewaltsam zu öffnen. Als S dann doch nach 1,5 Stunden kleinbei gab und die Tür öffnete, hörte man sein ewiges Klagelied darüber, dass <em>er</em> von den Hausbewohnern terrorisiert werden würde, dass <em>er</em> nicht derjenige sei, zu dem die Polizei gehen solle, dass sich alle gegen <em>ihn</em> verschworen hätten.</p>
<p>Dieses mal nahm die Polizei S nach langen Verhandlungen mit. Wohin erfuhr niemand. Wie lange S wegbleiben würde, wusste ebenfalls keiner - aber er blieb weg.</p>
<p>Nach ca. 10 Tagen des Friedens tauchte er aus der Versenkung wieder auf. Danach war er nicht mehr wiederzuerkennen. Er ging joggen und man traf ihn oftmals außer Haus an. Einmal kreuzten sich unsere Wege in der Stadt und er erzählte stolz, dass er jetzt eine Freundin hätte. Man konnte sich gut mit ihm unterhalten und er wirkte froh, dem Leben zugewandt und lachte viel beim Reden. Oft war er tagelang nicht zu Hause. Alles schien gut und in geordneten Bahnen, als hätte irgendjemand in der Zeit seiner Abwesenheit einen Schalter umgelegt. Auch die nächtlichen Ruhestörungen blieben aus. Es war wieder Frieden und Normalität im Haus.</p>
<p>Zwei Monate später waren erneut Scherben im Flur. Er hatte einen Bilderrahmen mit Glasscheibe zerschlagen&hellip;</p>

<h2 class="relative group">Phase 6: Stiller Auszug
    <div id="phase-6-stiller-auszug" class="anchor"></div>
    
</h2>
<p>Über die vergangenen Monate wurde in einem langen zähen Prozess vor dem Amtsgericht eine außerordentliche Kündigung des Mietvertrags inkl. Zwangsräumung erwirkt, der nun - zur Erleichterung aller Hausbewohner - stattgegeben und auch von S akzeptiert wurde.</p>
<p>So still wie S einzog, zog er auch wieder aus. Er nahm, laut Erzählung eines Hausbewohners, lediglich drei Gegenstände mit: Eine Matratze, einen Bluetooth-Lautsprecher und einen Spiegel.</p>
<p>Unweigerlich musste ich an seine Worte unserer Begegnung in der herbstlichen Abendsonne denken: <em>&quot;[&hellip;] ich bin oft mit meiner Mutter umgezogen&quot;</em>. An der Stelle schließt sich vermutlich der Kreis. Er wird erneut mit spärlichen Hausstand umziehen, ohne Vorwarnung an seine neuen Nachbarn und das Treiben beginnt schleichend von Neuem.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Inventor: Zeichnungsrevisionen vergleichen</title>
      <link>https://jbetzen.net/posts/inventor-pdf-diff/</link>
      <pubDate>Tue, 03 Jun 2025 20:26:58 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/inventor-pdf-diff/</guid>
      <description>pdf-diff ist ein quelloffenes und kostenfreies Kommandozeilentool, das es erlaubt den Inhalt zweier PDFs zu vergleichen. Das ist nützlich, wenn man die Änderungen zweier Revisionen von Zeichnungen auswerten möchte.</description>
      <content:encoded><![CDATA[ <p><a href="https://vslavik.github.io/diff-pdf/"  target="_blank" rel="noreferrer">diff-pdf</a> ist ein quelloffenes und kostenfreies Kommandozeilentool, das es erlaubt den Inhalt zweier PDFs als grafisches Overlay zu vergleichen. Das ist nützlich, wenn man die Änderungen zweier Revisionen von Zeichnungen auswerten möchte oder die Korrektheit vorliegender Änderungsmitteilungen für eine Zeichnungsrevision prüfen möchte.</p>


  

<figure class="centered"><img src="/posts/inventor-pdf-diff/diff.png"
    alt="Beispiel Diff zweier PDFs erstellt mit diff-pdf"><figcaption>
      <p>Beispiel Diff zweier PDFs erstellt mit diff-pdf</p>
    </figcaption>
</figure>



<h2 class="relative group">diff-pdf herunterladen
    <div id="diff-pdf-herunterladen" class="anchor"></div>
    
</h2>
<p>Das Tool liegt als kompilierte <code>exe</code> bei <a href="https://github.com/vslavik/diff-pdf"  target="_blank" rel="noreferrer">Github</a> vor und kann über <a href="https://github.com/vslavik/diff-pdf/releases/download/v0.5.2/diff-pdf-win-0.5.2.zip"  target="_blank" rel="noreferrer">diesen Link</a> direkt heruntergeladen werden. Anschließend muss der Ordner entpackt und in <code>diff-pdf</code> umbenannt werden.</p>
<p>Da das Tool keinen Installer besitzt, macht es Sinn es an einen Ort im Benutzerverzeichnis abzulegen und die Pfadvariablen von Windows so zu setzen, dass es für den User im Terminal verfügbar ist. Dazu den Windows Explorer öffnen und in die Pfadzeile <code>%USERPROFILE%</code> eintragen. Dort einen Ordner namens <code>Apps</code> anlegen und den Ordner <code>diff-pdf</code> hinein kopieren.</p>


  

<figure class="centered"><img src="/posts/inventor-pdf-diff/folder-copy.png"
    alt="Ordner in Nutzerverzeichnis kopieren">
</figure>



<h2 class="relative group">Pfadvariablen setzen
    <div id="pfadvariablen-setzen" class="anchor"></div>
    
</h2>
<p>Damit im Terminal nicht immer der volle Pfad zur <code>exe</code> eingegeben werden muss, wird eine neue Pfadvariable gesetzt:</p>
<ol>
<li>In die Windows Suche <code>environment variables for</code> eingeben und den Eintrag <code>Edit environment variables for your account</code> wählen:

    
      
    
    <figure class="img-list"><img src="/posts/inventor-pdf-diff/env-vars-search.png">
    </figure>
    
</li>
<li>Einen Doppelklick auf die Zeile <code>Path</code> machen:

    
      
    
    <figure class="img-list"><img src="/posts/inventor-pdf-diff/env-vars-path.png">
    </figure>
    
</li>
<li>Ein Klick auf <code>New</code> und mittels <code>Browse...</code> den Ordner <code>diff-pdf</code> im Benutzerverzeichnis auswählen:

    
      
    
    <figure class="img-list"><img src="/posts/inventor-pdf-diff/env-vars-add.png">
    </figure>
    
</li>
</ol>
<p>Von nun an steht das Kommando <code>diff-pdf.exe</code> im Terminal zur Verfügung.</p>

<h2 class="relative group">PDFs vergleichen
    <div id="pdfs-vergleichen" class="anchor"></div>
    
</h2>
<ol>
<li>Einen Ordner erstellen, bspw. auf dem Desktop und dort zwei PDFs hinein kopieren, die verglichen werden sollen. In meinem Beispiel heißen die Dateien <code>LST_Bender_RevA.pdf</code> und <code>LST_Bender_RevB.pdf</code>:

  
    
  
  <figure class="img-list"><img src="/posts/inventor-pdf-diff/folder-pdfs.png">
  </figure>
  
</li>
<li>Einen Rechtsklick auf eine leere Fläche des Ordners machen und <code>Open in Terminal</code> auswählen:

  
    
  
  <figure class="img-list"><img src="/posts/inventor-pdf-diff/folder-open-in-terminal.png">
  </figure>
  
</li>
</ol>
<p>Das Terminal öffnet sich und erwartet einen Befehl. Im Umgang mit <code>diff-pdf</code> stehen nun folgende Auswahlmöglichkeiten über Parameter und Argumente zur Verfügung:</p>

<h3 class="relative group">Grafischer Viewer
    <div id="grafischer-viewer" class="anchor"></div>
    
</h3>
<p>Ein einfacher grafischen Viewer kann mit dem Argument <code>--view</code> geöffnet werden. Dieser zeigt grafisch die detektierten Änderungen und erlaubt das Zoomen innerhalb der Anzeige:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="nb">diff-pdf</span><span class="p">.</span><span class="py">exe</span> <span class="p">-</span><span class="n">-view</span> <span class="p">.\</span><span class="n">LST_Bender_RevB</span><span class="p">.</span><span class="py">pdf</span> <span class="p">.\</span><span class="n">LST_Bender_RevA</span><span class="p">.</span><span class="n">pdf</span></span></span></code></pre></div></div>


  

<figure class="centered"><img src="/posts/inventor-pdf-diff/diff-pdf-gui-differences.png">
</figure>


<p>Das farbliche Overlay kann mit gedrückter <kbd>CTRL</kbd> und den Pfeiltasten verschoben werden.</p>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Es empfiehlt sich die neuere Revision einer Zeichnung als ersten Parameter an <code>diff-pdf.exe</code> zu übergeben. Im Falle des aufgezeigten Beispiels zuerst die Revision B <code>LST_Bender_RevB.pdf</code>. Das erste Document wird mit roter Farbe im Diff-View gerendert, das zweite mit einer (nicht immer gut erkennbaren) türkisen Farbe.</span>
</div>


<h3 class="relative group">PDF der Änderungen erzeugen
    <div id="pdf-der-änderungen-erzeugen" class="anchor"></div>
    
</h3>
<p>Möchte man den grafischen Output als PDF abspeichern, kann das Argument <code>--output-diff=diff.pdf</code> verwendet werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="nb">diff-pdf</span><span class="p">.</span><span class="py">exe</span> <span class="p">-</span><span class="n">-output-diff</span><span class="p">=</span><span class="n">diff</span><span class="p">.</span><span class="py">pdf</span> <span class="p">.\</span><span class="n">LST_Bender_RevB</span><span class="p">.</span><span class="py">pdf</span> <span class="p">.\</span><span class="n">LST_Bender_RevA</span><span class="p">.</span><span class="n">pdf</span></span></span></code></pre></div></div>
<p>Das Kommando erzeugt dann eine Datei mit dem Namen <code>diff.pdf</code>:


  

<figure class="centered"><img src="/posts/inventor-pdf-diff/folder-pdfs-output.png">
</figure>

</p>

<h2 class="relative group">Anmerkungen
    <div id="anmerkungen" class="anchor"></div>
    
</h2>
<p>Arbeitet man im Ökosystem von Autodesk und nutzt Vault als PDM-Datenbank, kann <code>diff-pdf</code> auch auf dem Jobserver eingesetzt werden. Wechselt eine Zeichnung bspw. in einen Status zur Aufforderung der Zeichnungsprüfung werden in der Regel bereits PDF-Exporte vom Job Server erstellt und liegen somit in der Vault Datenbank vor. Ein vorhandener Job kann somit erweitert werden, zusätzlich das PDF der vorherigen Revision abrufen und direkt gegen den neuen Stand der aktuellen Freigabe ein Diff erzeugen.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Plain Text Exporte</title>
      <link>https://jbetzen.net/posts/ilogic-text-export/</link>
      <pubDate>Sun, 01 Jun 2025 11:43:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-text-export/</guid>
      <description>Mittels des StreamWriters können Textdateien in iLogic erzeugt, geschrieben und direkt geöffnet werden. Die eignet sich hervorragend für Plain Text Exporte, bspw. um CSV-Dateien zu erzeugen oder Einträge der Inventor Bibliotheken auszuleiten.</description>
      <content:encoded><![CDATA[ <p>Da in iLogic auch .NET-Klassen aus dem <a href="https://learn.microsoft.com/en-us/dotnet/api/system.io?view=net-9.0"  target="_blank" rel="noreferrer">Namespace</a> <code>System.IO</code> zur Verfügung stehen, können diese genutzt werden, um <a href="https://en.wikipedia.org/wiki/Plain_text"  target="_blank" rel="noreferrer">Plain-Text-Dateien</a> mittels <a href="https://learn.microsoft.com/en-us/dotnet/api/system.io.streamwriter?view=net-9.0"  target="_blank" rel="noreferrer">StreamWriter</a> oder <a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.fileio.filesystem.writealltext?view=net-8.0"  target="_blank" rel="noreferrer">WriteAllText</a> zu erzeugen und zu schreiben.</p>
<p>Die Methoden unterscheiden sich grundsätzlich in ihrer Funktion und Anwendungsfall:</p>
<dl>
<dt><strong><code>StreamWriter</code></strong></dt>
<dd>Öffnet einen Stream, der es erlaubt kontinuierlich Daten Zeile-für-Zeile wegzuschreiben. Dies ist nützlich in iterativen Vorgängen.</dd>
<dt><strong><code>WriteAllText</code></strong></dt>
<dd>Schreibt direkt einen Text in eine Datei.</dd>
</dl>

<h2 class="relative group">Minimalbeispiel StreamWriter
    <div id="minimalbeispiel-streamwriter" class="anchor"></div>
    
</h2>
<p>Im folgenden ein Minimalbeispiel, dass eine Textdatei <code>hallo.txt</code> im Pfad <code>C:/iLogicOutput/</code> erzeugt, die Zeichenfolge <code>Hallo</code> schreibt und nach dem Schreibvorgang das Dokument automatisch öffnet. Vorangestellt ist eine Prüfung die sicherstellt, dass der Ausgabepfad bereits existiert und legt diesen ggf. an, bevor der Schreibvorgang startet.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oFile</span> <span class="ow">As</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">File</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oWrite</span> <span class="ow">As</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">StreamWriter</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sFilePath</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">&#34;C:/iLogicOutput/hallo.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDirectory</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetDirectoryName</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="k">Not</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">Exists</span><span class="p">(</span><span class="n">sDirectory</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">CreateDirectory</span><span class="p">(</span><span class="n">sDirectory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span> <span class="o">=</span> <span class="n">oFile</span><span class="p">.</span><span class="n">CreateText</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;Hallo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Launch</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Die letzte Zeile <code>ThisDoc.Launch()</code> öffnet final die erzeugte Textdatei.</p>

<h2 class="relative group">Minimalbeispiel WriteAllText
    <div id="minimalbeispiel-writealltext" class="anchor"></div>
    
</h2>
<p>Das folgende Minimalbeispiel schreibt das heutige Datum in eine Textdatei:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sFilePath</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">&#34;C:/iLogicOutput/hallo.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDirectory</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetDirectoryName</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">currentDate</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">Now</span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="s">&#34;dd.MM.yyyy&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="k">Not</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">Exists</span><span class="p">(</span><span class="n">sDirectory</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">CreateDirectory</span><span class="p">(</span><span class="n">sDirectory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">IO</span><span class="p">.</span><span class="n">File</span><span class="p">.</span><span class="n">WriteAllText</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">,</span> <span class="n">currentDate</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Launch</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span></span></span></code></pre></div></div>

<h2 class="relative group">Anwendungsbeispiele
    <div id="anwendungsbeispiele" class="anchor"></div>
    
</h2>

<h3 class="relative group">Blechregeln als CSV-Export
    <div id="blechregeln-als-csv-export" class="anchor"></div>
    
</h3>
<p>Dieses Beispiel exportiert sämtliche Blechregeln als <a href="https://en.wikipedia.org/wiki/Comma-separated_values"  target="_blank" rel="noreferrer">CSV-Datei</a>. Als Trennzeichen (<a href="https://de.wikipedia.org/wiki/Trennzeichen"  target="_blank" rel="noreferrer"><em>Delimiter</em></a>) wird in diesem Fall ein Semikolon <code>;</code> verwendet und kann ggf. in den Zeilen <code>oWrite.WriteLine()</code> angepasst werden, sofern ein Komma als Trennzeichen erwünscht ist. Das Beispiel ist ein <em>Quick-and-Dirty-Lösung</em>, die den CSV-Header und die Wertezeilen rausschreibt. Die CSV-Datei kann dann theoretisch auch in Excel importiert werden - <em>wenn</em> man das denn will.</p>
<p>Das Blechteil wird, wie bereits im Post <a href="/posts/ilogic-documenttypes/">iLogic: Dokumententypen</a> erläutert, über die <code>DocumentSubTypeID</code> ermittelt.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Check if Sheet Metal Part is currently open in Inventor
</span></span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentSubType</span><span class="p">.</span><span class="n">DocumentSubTypeID</span> <span class="o">&lt;&gt;</span> <span class="s">&#34;{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}&#34;</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">MessageBox</span><span class="p">.</span><span class="n">Show</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Please open a Sheet Metal Part and try again&#34;</span><span class="p">,</span> _
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Not a Sheet Metal Part&#34;</span><span class="p">,</span> _
</span></span><span class="line"><span class="cl">        <span class="n">MessageBoxButtons</span><span class="p">.</span><span class="n">OK</span><span class="p">,</span> _
</span></span><span class="line"><span class="cl">        <span class="n">MessageBoxIcon</span><span class="p">.</span><span class="n">Error</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Exit</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl"><span class="nf">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Get SheetMetalStyles object
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oSheetMetalStyles</span> <span class="ow">As</span> <span class="n">SheetMetalStyles</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">SheetMetalStyles</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Streamwriter
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oFile</span> <span class="ow">As</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">File</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oWrite</span> <span class="ow">As</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">StreamWriter</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sFilePath</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">&#34;C:/iLogicOutput/SheetMetalRules.csv&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDirectory</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetDirectoryName</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="k">Not</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">Exists</span><span class="p">(</span><span class="n">sDirectory</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">CreateDirectory</span><span class="p">(</span><span class="n">sDirectory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span> <span class="o">=</span> <span class="n">oFile</span><span class="p">.</span><span class="n">CreateText</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; CSV Header
</span></span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;&#34;&#34;Sheet Metal Rule&#34;&#34;;&#34;&#34;Material&#34;&#34;;&#34;&#34;Thickness&#34;&#34;;&#34;&#34;Bend Radius&#34;&#34;;&#34;&#34;Unfold Rule&#34;&#34;;&#34;&#34;kFactor&#34;&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; CSV Row Data
</span></span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="k">Each</span> <span class="n">oSheetMetalStyle</span> <span class="ow">As</span> <span class="n">SheetMetalStyle</span> <span class="ow">In</span> <span class="n">oSheetMetalStyles</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sRuleName</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oSheetMetalStyle</span><span class="p">.</span><span class="n">Name</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sMaterial</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oSheetMetalStyle</span><span class="p">.</span><span class="n">Material</span><span class="p">.</span><span class="n">Name</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sThickness</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oSheetMetalStyle</span><span class="p">.</span><span class="n">Thickness</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sBendRadius</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oSheetMetalStyle</span><span class="p">.</span><span class="n">BendRadius</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sUnfoldRule</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oSheetMetalStyle</span><span class="p">.</span><span class="n">UnfoldMethod</span><span class="p">.</span><span class="n">Name</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sKFactor</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oSheetMetalStyle</span><span class="p">.</span><span class="n">UnfoldMethod</span><span class="p">.</span><span class="n">kFactor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;&#34;&#34;{0}&#34;&#34;;&#34;&#34;{1}&#34;&#34;;&#34;&#34;{2}&#34;&#34;;&#34;&#34;{3}&#34;&#34;;&#34;&#34;{4}&#34;&#34;;&#34;&#34;{5}&#34;&#34;&#34;</span><span class="p">,</span> _
</span></span><span class="line"><span class="cl">                     <span class="n">sRuleName</span><span class="p">,</span> <span class="n">sMaterial</span><span class="p">,</span> <span class="n">sThickness</span><span class="p">,</span> <span class="n">sBendRadius</span><span class="p">,</span> <span class="n">sUnfoldRule</span><span class="p">,</span> <span class="n">sKFactor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Launch</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span></span></span></code></pre></div></div>

<h3 class="relative group">Materialbibliothek als Markdown-Tabelle
    <div id="materialbibliothek-als-markdown-tabelle" class="anchor"></div>
    
</h3>
<p>Dieses Beispiel exportiert die Materialbibliothek in eine <a href="https://www.markdownguide.org/extended-syntax/#tables"  target="_blank" rel="noreferrer">Markdown Tabelle</a>. Dazu wird nicht das aktive Dokument genutzt, sondern die Inventor Application, die sämtliche Bibliotheken (<code>AssetLibraries</code>) verwaltet:</p>


  

<figure class="centered"><img src="/posts/ilogic-text-export/api-materialassets.png"
    alt="Eine gerenderte Markdown Tabelle"><figcaption>
      <p>MaterialAssets in AssetLibraries</p>
    </figcaption>
</figure>


<p>Zusätzlich wird der Zeitpunkt des Exports im Dateinamen im Format <code>Materials_2025-06-01.txt</code> festgehalten.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vbnet" data-lang="vbnet"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oApp</span> <span class="ow">As</span> <span class="n">Application</span> <span class="o">=</span> <span class="n">ThisApplication</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oMaterialLibrary</span> <span class="ow">As</span> <span class="n">AssetsEnumerator</span> <span class="o">=</span> <span class="n">oApp</span><span class="p">.</span><span class="n">AssetLibraries</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="s">&#34;Autodesk Material Library&#34;</span><span class="p">).</span><span class="n">MaterialAssets</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">formattedDate</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">Now</span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="s">&#34;yyyy-MM-dd&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sFilePath</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">&#34;C:/iLogicOutput/Materials_&#34;</span> <span class="o">&amp;</span> <span class="n">formattedDate</span> <span class="o">&amp;</span> <span class="s">&#34;.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">sDirectory</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Path</span><span class="p">.</span><span class="n">GetDirectoryName</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oFile</span> <span class="ow">As</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">File</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oWrite</span> <span class="ow">As</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">StreamWriter</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="k">Not</span> <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">Exists</span><span class="p">(</span><span class="n">sDirectory</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">System</span><span class="p">.</span><span class="n">IO</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">CreateDirectory</span><span class="p">(</span><span class="n">sDirectory</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span> <span class="o">=</span> <span class="n">oFile</span><span class="p">.</span><span class="n">CreateText</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;Export date: **&#34;</span> <span class="o">&amp;</span> <span class="n">formattedDate</span> <span class="o">&amp;</span> <span class="s">&#34;**&#34;</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;This table is a Markdown Table. Paste it into your Markdown renderer of choice like https://dillinger.io or vscode.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;| Material Name | Material Category | Density [kg/m^3^]|&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;| ------------- | ----------------- | ---------------: |&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="k">Each</span> <span class="n">oMaterialAsset</span> <span class="ow">As</span> <span class="n">MaterialAsset</span> <span class="ow">in</span> <span class="n">oMaterialLibrary</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sLineData</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">&#34;| &#34;</span> <span class="o">&amp;</span> <span class="n">oMaterialAsset</span><span class="p">.</span><span class="n">DisplayName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">oMaterialAsset</span><span class="p">.</span><span class="n">CategoryName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">oMaterialAsset</span><span class="p">.</span><span class="n">PhysicalPropertiesAsset</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="s">&#34;structural_Density&#34;</span><span class="p">).</span><span class="n">Value</span> <span class="o">&amp;</span>  <span class="s">&#34; |&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">oWrite</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">sLineData</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oWrite</span><span class="p">.</span><span class="n">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Launch</span><span class="p">(</span><span class="n">sFilePath</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Der Output ist als <a href="https://www.markdownguide.org/extended-syntax/#tables"  target="_blank" rel="noreferrer">Markdown Tabelle</a> formatiert und kann mit einem Markdown Renderer wie <a href="https://code.visualstudio.com/docs/languages/markdown#_markdown-preview"  target="_blank" rel="noreferrer">Visual Studio Code</a> oder Onlinediensten wie <a href="https://dillinger.io/"  target="_blank" rel="noreferrer">dillinger.io</a> gerendert werden:</p>


  

<figure class="centered"><img src="/posts/ilogic-text-export/table-render-dillinger.png"
    alt="Eine gerenderte Markdown Tabelle"><figcaption>
      <p>Gerenderte Markdown Tabelle mit <a href="https://dillinger.io/"  target="_blank" rel="noreferrer">dillinger.io</a></p>
    </figcaption>
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>Neue Theme für den Blog</title>
      <link>https://jbetzen.net/posts/neue-theme/</link>
      <pubDate>Thu, 22 May 2025 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/neue-theme/</guid>
      <description>Der Blog ist auf eine neue Hugo Theme mit dem Namen Blowfish umgezogen. Für Abonnenten des RSS-Feeds wird dies ein wenig Chaos mit sich bringen, da alle alten Beiträge überarbeitet wurden und neu im Feed erscheinen.</description>
      <content:encoded><![CDATA[ <p>Der Blog ist auf eine neue Hugo Theme namens <a href="https://blowfish.page/"  target="_blank" rel="noreferrer">Blowfish</a> umgezogen. Die alte Theme namens <a href="https://github.com/rhazdon/hugo-theme-hello-friend-ng"  target="_blank" rel="noreferrer">hello-friend-ng</a> war zwar gut, aber mit viel Frickelei und stagnierender Entwicklung behaftet.</p>
<p>Da ich alle alten Beiträge überarbeitet habe und an die neue Theme anpassen musste, wird dies naturgemäß zu Chaos im RSS-Feed führen und sämtliche Posts werden als neue Beiträge angezeigt. Dies lässt sich technisch leider nicht unterbinden.</p>
<p>Die URL des <a href="/posts/index.xml" >RSS-Feed</a> bleibt bestehen und erfordert keinerlei Anpassungen im <a href="https://readkit.app/"  target="_blank" rel="noreferrer">Feedreader des Vertrauens</a>.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Inventor Team Web mit MkDocs</title>
      <link>https://jbetzen.net/posts/inventor-teamweb/</link>
      <pubDate>Wed, 13 Dec 2023 20:26:58 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/inventor-teamweb/</guid>
      <description>Inventor bietet die Möglichkeit eine begleitende Webseite im Team Web direkt in der Application darzustellen. Dieser Post erläutert, wie man eine Webseite mit dem Static Site Generator MkDocs erstellen und einen zentrale Ort des Wissen für konstruktionsrelevante Themen schaffen kann.</description>
      <content:encoded><![CDATA[ <details
      class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm group"
      data-type="warning"
      open>
      <summary class="flex items-center gap-2 font-semibold text-inherit cursor-pointer">
        <div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span></div>
        <div class="grow">
          Das Projekt &ldquo;Material for MkDocs&rdquo; ist Geschichte.
        </div>
        <div
          class="ms-auto flex h-5 w-5 items-center justify-center transition-transform ease-in-out -rotate-90 group-open:rotate-0 print:hidden"><span class="relative block icon"><svg
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 20 20"
  fill="currentColor"
  aria-hidden="true"
>
  <path
    fill-rule="evenodd"
    d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
    clip-rule="evenodd"
  />
</svg>
</span></div>
      </summary><div class="admonition-content mt-3 text-base leading-relaxed text-inherit"><p>In diesem Post wird das Setup einer begleitenden Webseite für Autodesk Inventor mit dem Tool <a href="https://squidfunk.github.io/mkdocs-material/"  target="_blank" rel="noreferrer">Material for MkDocs</a> beschrieben. Dieses Projekt ist nicht länger aktiv, da es <a href="https://fpgmaas.com/blog/collapse-of-mkdocs/"  target="_blank" rel="noreferrer">Verwerfungen</a> unter den Maintainern und divergente Ansichten über die <a href="https://squidfunk.github.io/mkdocs-material/blog/2026/02/18/mkdocs-2.0/#we-raised-concerns-early"  target="_blank" rel="noreferrer">zukünftige Ausrichtung</a> der Software gab. Der spirituelle Nachfolger heißt <a href="https://squidfunk.github.io/mkdocs-material/blog/2025/11/05/zensical/"  target="_blank" rel="noreferrer">Zensical</a> und strebt an vollständige <a href="https://squidfunk.github.io/mkdocs-material/blog/2025/11/05/zensical/#maximum-compatibility"  target="_blank" rel="noreferrer">Kompatibilität</a> zu MkDocs for Material herzustellen.</p>
<p><strong>Was heißt das im Detail:</strong> Die beschriebene <a href="/posts/inventor-teamweb/#mkdocs-starter-template" >Starter Template</a> funktioniert auch mit Zensical.</p></div></details>
<h2 class="relative group">Was ist Team Web?
    <div id="was-ist-team-web" class="anchor"></div>
    
</h2>
<p><a href="https://help.autodesk.com/view/INVNTOR/2024/DEU/?guid=GUID-278F58BB-D6F2-42BC-B9C8-279323F081B2"  target="_blank" rel="noreferrer">Team Web</a> ist ein nützliches Feature um Inventor-spezifische Themen, Anleitungen oder Hilfestellungen direkt in der Inventor Applikation darzustellen. Inventor wird somit zu einem kleinen Webbrowser, der HTML rendern und als zentrale Anlaufstelle für Konstruktionsthemen oder als News-Feed dienen kann. Oftmals anderweitig vorhandene fragmentiere Anleitungen und Handbücher finden im Team Web eine potentielle neue Heimat und sind dort indexiert, einfach suchbar und schnell zur Hand, ohne die Inventor Anwendung verlassen zu müssen.</p>
<p>Grundlage des dargestellten Inhalts ist eine HTML-Webseite, die entweder lokal vom Dateisystem oder über eine 


<abbr title="Fully Qualified Domain Name">FQDN</abbr> serviert wird. Da in Firmennetzwerken das Beantragen eines Domain-Namens, sowie die Bereitstellung eines Webservers gut und gerne auch mal etwas länger dauern kann, fokussiert dieser Post das ausliefern des HTML über einen Dateiordner in einem Netzwerkpfad, der in den Inventor Applikationseinstellungen gesetzt werden muss.</p>

<h2 class="relative group">Wie baut man als Laie Webseiten?
    <div id="wie-baut-man-als-laie-webseiten" class="anchor"></div>
    
</h2>
<p>Die einfachste Wahl um schnell und dynamisch statisches HTML zu generieren sind sogenannte <a href="https://en.wikipedia.org/wiki/Static_site_generator"  target="_blank" rel="noreferrer">Static Site Generators</a>, wie bspw. <a href="https://gohugo.io/"  target="_blank" rel="noreferrer">Hugo</a>, der diesen Blog generiert, oder <a href="https://www.mkdocs.org/"  target="_blank" rel="noreferrer">MkDocs</a>.</p>
<p>In diesem Beispiel wird <strong>MkDocs</strong> verwendet. Da Webseiten, die mit MkDocs generiert sind ein wenig <em>altbacken</em> daher kommen, wird zusätzlich das feature-reiche <a href="https://squidfunk.github.io/mkdocs-material/"  target="_blank" rel="noreferrer">MkDocs Material</a> als Theme benutzt. Dieses Theme erzeugt zeitgemäßes responsives HTML im <a href="https://m2.material.io/"  target="_blank" rel="noreferrer">Material Design Look</a> und bietet zusätzlich nützliche Funktionen, wie eine <a href="https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/"  target="_blank" rel="noreferrer">globale Suche</a>, vielseitige <a href="https://squidfunk.github.io/mkdocs-material/reference/formatting/"  target="_blank" rel="noreferrer">Formatierungsoptionen</a>, <a href="https://squidfunk.github.io/mkdocs-material/reference/diagrams/"  target="_blank" rel="noreferrer">Diagramme</a> oder <a href="https://squidfunk.github.io/mkdocs-material/reference/icons-emojis/"  target="_blank" rel="noreferrer">Emoji-Support</a>.</p>
<p>Was alle <em>Static Site Generators</em> eint ist die Tatsache, dass es nicht erforderlich ist HTML beherrschen zu müssen. Stattdessen wird auf eine vereinfachte <a href="https://de.wikipedia.org/wiki/Auszeichnungssprache"  target="_blank" rel="noreferrer">Auszeichnungssprache</a> (Markup Language) - in der Regel <a href="https://www.markdownguide.org/cheat-sheet/"  target="_blank" rel="noreferrer">Markdown</a> - zurückgegriffen, die von MkDocs in HTML übersetzt wird. Markdown benötigt nur eine <a href="https://www.markdownguide.org/basic-syntax/"  target="_blank" rel="noreferrer">handvoll leicht zu erlernender Anweisung</a> zur Formatierung und Strukturierung von Textinhalten und wird einfach in Plain-Text-Dateien geschrieben und gespeichert.</p>

<h2 class="relative group">Software Requirements
    <div id="software-requirements" class="anchor"></div>
    
</h2>
<p>Um eine Webseite mit MkDocs zu erstellen, muss auf der Nutzermaschine folgende Software installiert sein. Für Python und Visual Studio Code sollten die <a href="https://de.wikipedia.org/wiki/Umgebungsvariable"  target="_blank" rel="noreferrer">Umgebungsvariablen</a> (<code>PATH</code>) gesetzt werden. Dafür benötigt man in der Regel die Firmen IT mit Administratorrechten.</p>
<ul>
<li>
<p><a href="https://www.python.org/downloads/"  target="_blank" rel="noreferrer">Python</a>, samt dem eigenen Paketmanager <code>pip</code>, muss installiert sein. <code>pip</code> ist in dem Python-Installer für Windows enthalten.</p>
</li>
<li>
<p><a href="https://code.visualstudio.com/"  target="_blank" rel="noreferrer">Visual Studio Code</a> als Text-Editor.</p>
</li>
<li>
<p>Die offizielle <a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python"  target="_blank" rel="noreferrer">Python Extension</a> für Visual Studio Code.

  <figure>
          <img
            class="my-0 rounded-md img-list"
            src="/posts/inventor-teamweb/extension-python.png"
            alt="Python Extension"
          />
    
    
    </figure></p>
</li>
<li>
<p><strong>Optional</strong>, aber mit klarer Empfehlung: Die VSCode Extension <a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one"  target="_blank" rel="noreferrer">Markdown All in One</a>, die das Arbeiten mit Markdown durch Shortcuts erheblich erleichtert.

  <figure>
          <img
            class="my-0 rounded-md img-list"
            src="/posts/inventor-teamweb/extension-markdown-aio.png"
            alt="Markdown All-in-One Extension"
          />
    
    
    </figure></p>
<p>Weitere empfohlene Extensions sind dem Projekt in der Datei <code>.vscode/extensions.json</code> beigelegt und können in VSCode über den Filter <kbd>Recommended</kbd> angezeigt und installiert werden:

    <figure>
            <img
              class="my-0 rounded-md centered"
              src="/posts/inventor-teamweb/extensions-recommendations.png"
              alt="VSCode recommended extensions"
            />
      
      
      </figure></p>
</li>
</ul>

<h2 class="relative group">MkDocs Starter Template
    <div id="mkdocs-starter-template" class="anchor"></div>
    
</h2>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Die Template ist auf die Benutzung über ein <a href="https://squidfunk.github.io/mkdocs-material/plugins/offline"  target="_blank" rel="noreferrer">Dateisystem</a> optimiert - sprich ohne Domain und Webserver - und kann einfach in einem Netzwerkordner abgelegt und in Inventor eingebunden werden. Dieses Vorgehen erlaubt einem Poweruser maximale Autonomie zum Ändern der dargestellten Inhalte.</span>
</div>

<p>Um das Vorhaben zur Erstellung einer Team Web Webseite zu vereinfachen, habe ich eine minimale Starter-Template erstellt, die als Grundlage für alle folgenden Schritte gilt und unbedarften Usern als niederschwellige Einstiegshürde in die magische Welt der <em>Static Site Generators</em> dient.</p>
<p>Die Starter Template ist bei Github verfügbar und kann dort <a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo"  target="_blank" rel="noreferrer">geforked</a> oder als <code>zip</code> Archiv <a href="https://github.com/schneekluth/teamweb-inventor-mkdocs/archive/refs/heads/main.zip"  target="_blank" rel="noreferrer">heruntergeladen</a> werden:</p>
<div class="github-card-wrapper">
    <a id="github-7c6e930275238e3e568a135157122948" target="_blank" href="https://github.com/schneekluth/teamweb-inventor-mkdocs" class="cursor-pointer">
      <div
        class="w-full md:w-auto p-0 m-0 border border-neutral-200 dark:border-neutral-700 border rounded-md shadow-2xl"><div class="w-full nozoom">
            <img
              src="https://opengraph.githubassets.com/0/schneekluth/teamweb-inventor-mkdocs"
              alt="GitHub Repository Thumbnail"
              class="nozoom mt-0 mb-0 w-full h-full object-cover">
          </div><div class="w-full md:w-auto pt-3 p-5">
          <div class="flex items-center">
            <span class="text-2xl text-neutral-800 dark:text-neutral me-2">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
</span>
            </span>
            <div
              id="github-7c6e930275238e3e568a135157122948-full_name"
              class="m-0 font-bold text-xl text-neutral-800 decoration-primary-500 hover:underline hover:underline-offset-2 dark:text-neutral">
              schneekluth/teamweb-inventor-mkdocs
            </div>
          </div>

          <p id="github-7c6e930275238e3e568a135157122948-description" class="m-0 mt-2 text-md text-neutral-800 dark:text-neutral">
            Das begleitende Repository zum Blogpost über die Erstellung eines Inventor Team Web mit MKDocs Material.
          </p>

          <div class="m-0 mt-2 flex items-center">
            <span class="mr-1 inline-block h-3 w-3 rounded-full language-dot" data-language="default"></span>
            <div class="m-0 mr-5 text-md text-neutral-800 dark:text-neutral">
              null
            </div>

            <span class="text-md mr-1 text-neutral-800 dark:text-neutral">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z"/></svg></span>
            </span>
            <div id="github-7c6e930275238e3e568a135157122948-stargazers" class="m-0 mr-5 text-md text-neutral-800 dark:text-neutral">
              0
            </div>

            <span class="text-md mr-1 text-neutral-800 dark:text-neutral">
              <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M80 104c13.3 0 24-10.7 24-24s-10.7-24-24-24S56 66.7 56 80s10.7 24 24 24zm80-24c0 32.8-19.7 61-48 73.3V192c0 17.7 14.3 32 32 32H304c17.7 0 32-14.3 32-32V153.3C307.7 141 288 112.8 288 80c0-44.2 35.8-80 80-80s80 35.8 80 80c0 32.8-19.7 61-48 73.3V192c0 53-43 96-96 96H256v70.7c28.3 12.3 48 40.5 48 73.3c0 44.2-35.8 80-80 80s-80-35.8-80-80c0-32.8 19.7-61 48-73.3V288H144c-53 0-96-43-96-96V153.3C19.7 141 0 112.8 0 80C0 35.8 35.8 0 80 0s80 35.8 80 80zm208 24c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24zM248 432c0-13.3-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24s24-10.7 24-24z"/></svg></span>
            </span>
            <div id="github-7c6e930275238e3e568a135157122948-forks" class="m-0 mr-5 text-md text-neutral-800 dark:text-neutral">
              0
            </div>
          </div>
        </div>
      </div>
      
      
      <script
        async
        type="text/javascript"
        src="/js/fetch-repo.min.dc5533c50cefd50405344b235937142271f26229fe39cbee27fd4960e8bb897a0beebfad77a1091ca91cd0d1fb14e70fc37cc114dd9674fb2c32e0ab512ec8a4.js"
        integrity="sha512-3FUzxQzv1QQFNEsjWTcUInHyYin&#43;OcvuJ/1JYOi7iXoL7r&#43;td6EJHKkc0NH7FOcPw3zBFN2WdPssMuCrUS7IpA=="
        data-repo-url="https://api.github.com/repos/schneekluth/teamweb-inventor-mkdocs"
        data-repo-id="github-7c6e930275238e3e568a135157122948"></script>
    </a>
  </div>
<p>Die Ordnerstruktur der Starter Template sieht <a href="https://tree.nathanfriend.com/?s=%28%27options%21%28%27fancy%21true%7EfullPath%21false%7EtrailingSlash%21true%7ErootDot%21false%29%7EB%28%27B%27C-inventor-mkA4AEblog6postF*2023-05-23-8md6.authors.yml69*assetFcsF*extra.csFbanner76favicon.ico6firmenlogo.svgEimg6hund76katze76maus7E9includesEabbreviations.mdE8xlsx4mkA.yml4requirements.txt4C.code-workspace%27%29%7Eversion%21%271%27%29*&#43;&#43;4%5Cn*6E*7.jpg8beispiel.9index.md4AdocsBsource%21CteamwebE4*Fs6%01FECBA98764*"  target="_blank" rel="noreferrer">wie folgt</a> aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">teamweb-inventor-mkdocs/
</span></span><span class="line"><span class="cl">├── docs/
</span></span><span class="line"><span class="cl">│   ├── blog/
</span></span><span class="line"><span class="cl">│   │   ├── posts/
</span></span><span class="line"><span class="cl">│   │   │   └── 2023-05-23-beispiel.md
</span></span><span class="line"><span class="cl">│   │   ├── .authors.yml
</span></span><span class="line"><span class="cl">│   │   └── index.md
</span></span><span class="line"><span class="cl">│   ├── assets/
</span></span><span class="line"><span class="cl">│   │   ├── css/
</span></span><span class="line"><span class="cl">│   │   │   └── extra.css
</span></span><span class="line"><span class="cl">│   │   ├── banner.jpg
</span></span><span class="line"><span class="cl">│   │   ├── favicon.ico
</span></span><span class="line"><span class="cl">│   │   └── firmenlogo.svg
</span></span><span class="line"><span class="cl">│   ├── img/
</span></span><span class="line"><span class="cl">│   │   ├── hund.jpg
</span></span><span class="line"><span class="cl">│   │   ├── katze.jpg
</span></span><span class="line"><span class="cl">│   │   └── maus.jpg
</span></span><span class="line"><span class="cl">│   └── index.md
</span></span><span class="line"><span class="cl">├── includes/
</span></span><span class="line"><span class="cl">│   ├── abbreviations.md
</span></span><span class="line"><span class="cl">│   └── beispiel.xlsx
</span></span><span class="line"><span class="cl">├── mkdocs.yml
</span></span><span class="line"><span class="cl">├── requirements.txt
</span></span><span class="line"><span class="cl">└── teamweb.code-workspace</span></span></code></pre></div></div>
<p>Alle Dateien und Ordner und ihre Funktion:</p>
<table>
  <thead>
      <tr>
          <th>Datei / Ordner</th>
          <th>Erläuterung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>docs/</code></td>
          <td>Der Hauptordner in dem alle Markdown-Dokumente, Bilder und Assets für die Webseite liegen.</td>
      </tr>
      <tr>
          <td><code>blog/</code></td>
          <td>Beinhaltet alle Blogposts und zusätzlich eine Datei namens <code>.authors.yml</code> in der ein Autorenverzeichnis für die Blog-Beiträge angelegt werden kann. Die Datei <code>index.md</code> erzeugt automatisch eine Übersicht aller vorhandenen Blog-Beiträge.</td>
      </tr>
      <tr>
          <td><code>assets/</code></td>
          <td>Enthält Firmenlogo, ein Bannerbild für die Startseite und ein Browser Icon (Favicon). Zusätzlich können CSS Stylings in der Datei <code>extra.css</code> vorgenommen werden. Ein Beispiel zum zentrieren des Footers und für Tabellen ist in der Template vorhanden.</td>
      </tr>
      <tr>
          <td><code>img/</code></td>
          <td>In diesem Ordner werden alle Bilder abgelegt, die in der Webseite verwendet werden.</td>
      </tr>
      <tr>
          <td><code>includes/</code></td>
          <td>In der Datei <code>abbreviations.md</code> können Abkürzungen hinterlegt werden, deren Volltext automatisch erscheint, wenn mit der Maus über den 


<abbr title="Beispieltext">Text</abbr> gefahren wird. Zusätzlich befindet sich eine Excel-Tabelle <code>beispiel.xlsx</code> in dem Verzeichnis, dessen Inhalt mit MkDocs automatisch eingelesen und dargestellt werden kann. Ein Beispiel dafür ist in der Template vorhanden.</td>
      </tr>
      <tr>
          <td><code>mkdocs.yml</code></td>
          <td>Über diese Datei wird MkDocs konfiguriert und der strukturelle Aufbau der Webseite definiert. Dazu wird eine einfache Sprache namens <a href="https://de.wikipedia.org/wiki/YAML"  target="_blank" rel="noreferrer">YAML</a> verwendet.</td>
      </tr>
      <tr>
          <td><code>requirements.txt</code></td>
          <td>Beinhaltet eine Liste an Python-Paketen, die für das generieren der HTML für das Team Web benötigt werden.</td>
      </tr>
      <tr>
          <td><code>teamweb.code-workspace</code></td>
          <td>Diese Datei beinhaltet nützliche Einstellungen für den Text-Editor Visual Studio Code.</td>
      </tr>
  </tbody>
</table>

<h2 class="relative group">Python Virtual Environment erzeugen
    <div id="python-virtual-environment-erzeugen" class="anchor"></div>
    
</h2>
<p>Python bietet eine Funktion namens <a href="https://virtualenv.pypa.io/en/latest/"  target="_blank" rel="noreferrer">Virtual Environments</a> (<code>venv</code>), die es erlaubt Python-Pakete isoliert und projektspezifisch in bestimmten Versionen zu installieren. Somit wird vermieden, dass systemweit installierte Pakete mit denen von dem lokalen Team Web Projekt in Konflikt geraten.</p>
<p>Der einfachste Weg ein solches <em>Virtual Environment</em> anzulegen und die benötigten Pakete zu installieren ist über Visual Studio Code. Dazu einen Rechtsklick auf den Ordner <code>teamweb-inventor-mkdocs/</code> machen und im Kontextmenü den Eintrag <code>Mit Code öffnen</code> auswählen:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/oeffnen-mit-code.png"
          alt="Öffnen mit VSCode"
        />
  
  
  </figure>
<p>Beim erstmaligen Öffnen wird Visual Studio Code einmalig Fragen, ob dem Autoren des Ordnerinhaltes vertraut werden soll. Diese Meldung muss mit einem Klick auf den Button <code>Yes, I trust the authors</code> bestätigt werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/trust-author.png"
          alt="Trust Author"
        />
  
  
  </figure>
<p>In VSCode muss nun die Tastenkombination <kbd>STRG</kbd>+<kbd>SHIFT</kbd>+<kbd>P</kbd> gedrückt werden. Es öffnet sich ein Eingabefeld, dass mit dem Zeichen <code>&gt;</code> beginnt. In dieses Feld wird folgendes eingetippt: <code>Python: Create Environment</code>. Anschließend mit <kbd>Enter</kbd> bestätigen:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/venv-create.png"
          alt="Create venv"
        />
  
  
  </figure>
<p>Anschließend fragt VSCode, welcher Typ von Environment erstellt werden soll. Es muss <code>.venv</code> ausgewählt und mit <kbd>Enter</kbd> bestätigt werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/venv-type.png"
          alt="venv type"
        />
  
  
  </figure>
<p>Im folgenden Schritt muss definiert werden, welcher Python Interpreter benutzt werden soll. An dieser Stelle muss der global installierte Python Interpreter ausgewählt werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/venv-interpreter.png"
          alt="venv interpreter"
        />
  
  
  </figure>
<p>Im letzten Schritt bietet Visual Studio Code die Möglichkeit an alle erforderlichen Python-Pakete und Abhängigkeiten zu installieren. Diese sind in der mitgelieferten Datei <code>requirements.txt</code> definiert. Es werden die folgenden Pakete installiert:</p>
<table>
  <thead>
      <tr>
          <th>Paket</th>
          <th>Erläuterung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://squidfunk.github.io/mkdocs-material/"  target="_blank" rel="noreferrer">mkdocs-material</a></td>
          <td>Die Theme, die die Team Web Webseite ansehnlich macht.</td>
      </tr>
      <tr>
          <td><a href="https://timvink.github.io/mkdocs-table-reader-plugin/"  target="_blank" rel="noreferrer">mkdocs-table-reader-plugin</a></td>
          <td>Zum importieren von Excel Tabellenwerten.</td>
      </tr>
      <tr>
          <td><a href="https://blueswen.github.io/mkdocs-glightbox/"  target="_blank" rel="noreferrer">mkdocs-glightbox</a></td>
          <td>Zum zoomen eingebundener Grafiken</td>
      </tr>
      <tr>
          <td><a href="https://openpyxl.readthedocs.io/en/stable/"  target="_blank" rel="noreferrer">openpyxl</a></td>
          <td>Zum importieren von Excel Tabellenwerten im <code>xlsx</code> Format.</td>
      </tr>
  </tbody>
</table>
<p>Die Datei <code>requirements.txt</code> muss explizit in der Checkbox aktiviert und anschließen mit <kbd>OK</kbd> bestätigt werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/venv-dependencies.png"
          alt="venv dependencies"
        />
  
  
  </figure>
<p>VSCode erstellt nun ein Virtual Environment samt aller benötigten Pakete und legt dieses im Ordner <code>teamweb-inventor-mkdocs</code> im Unterordner <code>.venv/</code> an:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/venv-creating.png"
          alt="Creating venv"
        />
  
  
  </figure>
<p>Nach der Installation sollte Visual Studio Code einmal geschlossen und anschließend über einen Doppelklick auf die Datei <code>teamweb.code-workspace</code> neu geöffnet werden. Der Grund ist, dass diese Datei Anweisungen für VSCode für die Arbeit mit Virtual Environments enthält, die aber gerade erst erzeugt wurden.</p>

<h2 class="relative group">Das Terminal
    <div id="das-terminal" class="anchor"></div>
    
</h2>
<p>Niemand sollte sich von der Arbeit mit dem Terminal abschrecken lassen. Ja, es wirkt in erster Linie immer einschüchternd und ja, es birgt gefahren und man kann sehr schnell Dummheiten anrichten. Unachtsamkeiten werden nicht verziehen und einen <em>Rückgängig</em>-Knopf wird man vergebens suchen. Das sollte aber kein Grund sein sich mit diesem mächtigen Werkzeug vertraut zu machen, mit dem die ersten Jahrzehnte Computer bedient wurden.</p>
<p>In heutiger Zeit sind Nutzer, insbesondere <a href="https://www.quora.com/Why-is-Windows-considered-more-idiot-proof-than-Linux-despite-the-fact-that-Linux-is-usually-a-much-more-stable-operating-system"  target="_blank" rel="noreferrer">Windows-Nutzer</a>, an grafische <em>Klicki-Bunti-Oberflächen</em> mit Knöpfen gewöhnt. Oberflächen, die keine Neugierde mehr wecken, was da eigentlich auf dem Computer gerade passiert. Für Linux-Nutzer ist das Terminal ein Werkzeug, das in den Alltag gehört wie Sand zwischen den Zehen an einem ausgiebigen Tag am Strand. Seid neugierig und wagt euch Dinge anders zu tun. Ein modernes Team Web baut man nun einmal <strong>nicht</strong> in Excel, Word oder PowerPoint.</p>
<p>Für die Arbeit mit MkDocs sollte ein PowerShell Terminal gewählt werden und nicht ein klassisches Command Prompt, da PowerShell mehr Annehmlichkeiten mit sich bringt, bspw. farblichen Terminal output. Visual Studio Code hat praktischerweise ein <a href="https://code.visualstudio.com/docs/terminal/basics"  target="_blank" rel="noreferrer">integriertes Terminal</a>. Somit kann die Pflege des Team Web Contents und die Erzeugung des HTMLs direkt in einer Anwendung stattfinden.</p>
<p>Über die Menüleiste in VSCode kann mittels <code>Terminal &gt; New Terminal</code> oder die Tastenkombination <kbd>STRG</kbd>+<kbd>SHIFT</kbd>+<kbd>Ö</kbd> eine neue Terminal Session geöffnet werden. Im unteren Bereich der Applikation öffnet sich nun ein integriertes Terminal-Fenster. An dieser Stelle kann geprüft werden, ob es sich um ein PowerShell-Terminal handelt:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/terminal-new.png"
          alt="New Terminal"
        />
  
  
  </figure>
<p>Handelt es sich bei dem neuen Terminal Fenster nicht um ein Powershell Terminal, kann dies im Handumdrehen geändert werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/terminal-change.png"
          alt="Change Terminal"
        />
  
  
  </figure>

<h2 class="relative group">Virtual Environment aktivieren
    <div id="virtual-environment-aktivieren" class="anchor"></div>
    
</h2>
<p><strong>Wichtig:</strong> Python Virtual Environments müssen explizit pro Terminal-Session aktiviert werden, ansonsten wird nicht mit einer isolierten Umgebung für Pakete und Abhängigkeiten gearbeitet, sondern mit den global installierten Paketen.</p>
<p><strong>Die gute Nachricht:</strong> Die Team Web Starter Template enthält in der Datei <code>teamweb.code-workspace</code> eine Anweisung, die eine automatische Aktivierung des Environments in jedem aktiven Terminal Fenster in Visual Studio Code vornimmt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&#34;python.terminal.activateEnvInCurrentTerminal&#34;: true</span></span></code></pre></div></div>
<p>Wer sichergehen möchte ob das Virtual Environment aktiv ist, muss nur einen Blick ins Terminal werfen. Dies wird angedeutet durch einen grünen Text, der den Ausdruck <code>.venv</code> voranstellt:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/venv-activated.png"
          alt="venv active"
        />
  
  
  </figure>
<p>Ist dies nicht der Fall kann einfach in Visual Studio Code die Datei <code>teamweb.code-workspace</code> angeklickt werden und auf den Button <code>Open Workspace</code> geklickt werden</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/open-workspace.png"
          alt="Open Workspace"
        />
  
  
  </figure>
<p>oder man gibt folgendes Kommando in das Terminal ein:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Powershell" data-lang="Powershell"><span class="line"><span class="cl"><span class="p">.\.</span><span class="n">venv</span><span class="p">\</span><span class="n">Scripts</span><span class="p">\</span><span class="n">Activate</span><span class="p">.</span><span class="n">ps1</span></span></span></code></pre></div></div>

<h2 class="relative group">Webseitenstruktur
    <div id="webseitenstruktur" class="anchor"></div>
    
</h2>
<p>Die Webseitenstruktur wird über die zentrale Datei <code>mkdocs.yml</code> in der Sektion <code>nav:</code> mittel einer Sprache namens <a href="https://de.wikipedia.org/wiki/YAML"  target="_blank" rel="noreferrer">YAML</a> definiert:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/webseite-struktur.png"
          alt="Webseite Struktur"
        />
  
  
  </figure>
<p>Dieses Kapitel in der MkDocs Material Dokumentation ist eine genauere Einsicht wert. Wer Beispielsweise oben keine horizontal angeordneten Tabs haben möchte, sollte sich die Sektion <a href="https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/"  target="_blank" rel="noreferrer">Setting Up Navigation</a> genauer anschauen.</p>

<h2 class="relative group">Workflow für die Arbeit am Teamweb
    <div id="workflow-für-die-arbeit-am-teamweb" class="anchor"></div>
    
</h2>
<p>Ein normaler Workflow gestaltet sich so, dass man im ersten Schritt eine Änderung des Content einbringen möchte. Man startet dazu den <a href="/posts/inventor-teamweb/#entwicklungsserver-starten" >lokalen Entwicklungsserver</a> von MkDocs und kann prüfen, ob die eingebrachten Änderungen den eigenen Ansprüchen genügen. Ist letzteres der Fall wird der Entwicklungsserver beendet und MkDocs angewiesen das statische HTML zu generieren.</p>

<h3 class="relative group">Entwicklungsserver Starten
    <div id="entwicklungsserver-starten" class="anchor"></div>
    
</h3>
<p>MkDocs beinhaltet ein nützliches Feature um Änderungen an der Webseite direkt im Browser live zu begutachten. MkDocs nutzt dazu einen lokalen Server, der die Webseite über <code>127.0.0.1:8000</code> ausliefert. Um den Dev Server zu starten muss im Terminal folgendes Kommando ausgeführt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="n">mkdocs</span> <span class="n">serve</span></span></span></code></pre></div></div>
<p>Öffnet man nun den Webbrowser seines vertrauen und tippt als URL <a href="http://127.0.0.1:8000/"  target="_blank" rel="noreferrer"><code>127.0.0.1:8000</code></a> ein, erscheint ein Preview der Team Web Webseite. Ändert man eine Datei und speichert diese, lädt der Dev Server die Änderungen sofort neu durch und man kann das Ergebnis direkt im Browser begutachten:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/template-preview.png"
          alt="Preview der Template"
        />
  
  
  </figure>
<p>Stoppen kann man den Dev Server mit der Tastenkombination <kbd>STRG</kbd>+<kbd>C</kbd> im aktiven Terminal-Fenster.</p>

<h3 class="relative group">Log Messages beachten
    <div id="log-messages-beachten" class="anchor"></div>
    
</h3>
<p>Der Entwicklungsserver gibt kontinuierlich Rückmeldung in Form von Log Messages. Entsteht bspw. ein Fehler, der es nicht ermöglicht die Webseite zu bauen, erscheinen diese mit dem Präfix <code>ERROR</code> und müssen behoben werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/log-error.png"
          alt="Beispiel: Log Error"
        />
  
  
  </figure>
<p>Eine erfolgreiche Konfiguration hingegen wird über die Ausgabe <code>INFO  Documentation built in X seconds</code> angezeigt:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/log-success.png"
          alt="Beispiel: Log Success"
        />
  
  
  </figure>

<h3 class="relative group">Webseiten HTML erzeugen
    <div id="webseiten-html-erzeugen" class="anchor"></div>
    
</h3>
<p>Sind alle Änderungen eingebracht, ist es an der Zeit das statische HTML zu erzeugen. MkDocs stellt dafür das Kommando <code>mkdocs build</code> bereit. Diese erzeugt standardmäßig das HTML in einem neuen Unterordner namens <code>site/</code> direkt im <code>teamweb-inventor-mkdocs/</code> Ordner. Damit läge die Webseite aber nur lokal auf der eigenen Nutzermaschine und kann nicht global verfügbar an die User ausgerollt werden.</p>
<p>Es macht daher Sinn die IT auf einen Kaffee einzuladen und Schreibrechte auf einem öffentlich zugängigen Netzwerkpfad zu besorgen. Dieser Pfad muss dann für das Team Web in den Inventor-Einstellungen gesetzt werden.</p>
<p>Ist ein allgemein zugänglicher Netzwerkpfad vorhanden, kann MkDocs direkt angewiesen werden das HTML an dieser Stelle zu generieren und abzulegen. Dazu wird dem Kommando <code>build</code> die <a href="https://www.mkdocs.org/user-guide/cli/#mkdocs-build"  target="_blank" rel="noreferrer">Option</a> <code>-d</code> (Kurznotation für <code>--site-dir</code>) angestellt und der Netzwerkpfad angegeben:</p>
<p>Ein Beispiel für einen fiktiven Netzwerkpfad auf einer Maschine namens <code>SERVER-X</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">mkdocs</span> <span class="n">build</span> <span class="n">-d</span> <span class="s2">&#34;\\SERVER-X\Teamweb&#34;</span></span></span></code></pre></div></div>

<h2 class="relative group">Inventor Settings für Team Web
    <div id="inventor-settings-für-team-web" class="anchor"></div>
    
</h2>
<p>In den Inventor Application Settings findet sich im Reiter <code>File</code> eine Option den Pfad für das Team Web anzugeben. Dort wird nun einfach zu dem Netzwerkpfad navigiert und die Datei <code>index.html</code> angegeben.</p>
<p>Es ist durchaus sinnvoll den Haken bei der Checkbox <code>Show Team Web on Startup</code> zu setzen. Damit ist sichergestellt, dass jeder User einmal beim Neustart der Applikation das Team Web Fenster in Inventor sieht.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/inventor-settings.png"
          alt="Inventor Team Web Settings"
        />
  
  
  </figure>
<p><strong>Tip:</strong> Wer direkt auf die Blog Sektion im Team Web verlinken möchte, um die User mit Neuigkeiten zu versorgen, kann dies auch einfach tun. Dazu den Eintrag in den Inventor-Einstellungen folgendermaßen abändern:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">\\SERVER-X\Teamweb\blog\index.html</span></span></code></pre></div></div>
<p><strong>Et voilà!</strong> Schon befindet sich das erzeugte HTML direkt im Inventor:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/inventor-teamweb/teamweb-in-inventor.png"
          alt="Inventor Team Web"
        />
  
  
  </figure>

<h2 class="relative group">Updates installieren
    <div id="updates-installieren" class="anchor"></div>
    
</h2>
<p>Sowohl <a href="https://github.com/mkdocs/mkdocs/releases"  target="_blank" rel="noreferrer">MkDocs</a>, also auch die verwendete Theme <a href="https://github.com/squidfunk/mkdocs-material/releases"  target="_blank" rel="noreferrer">MkDocs Material</a> erhalten fortlaufend updates. Updates können einfach über das Kommando <a href="https://pip.pypa.io/en/stable/cli/pip_install"  target="_blank" rel="noreferrer"><code>pip install</code></a> <a href="https://squidfunk.github.io/mkdocs-material/upgrade/"  target="_blank" rel="noreferrer">eingespielt</a> werden. Dazu wieder in VSCode ein PowerShell Terminal aufrufen, sicherstellen dass das Virtual Environment aktiv ist und folgendes Kommando ausführen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ps1" data-lang="ps1"><span class="line"><span class="cl"><span class="n">pip</span> <span class="n">install</span> <span class="n">-U</span> <span class="p">-</span><span class="n">-force-reinstall</span> <span class="n">-r</span> <span class="p">.\</span><span class="n">requirements</span><span class="p">.</span><span class="n">txt</span></span></span></code></pre></div></div>
<p>Die Optionen für das Kommando <code>pip install</code> im Detail:</p>
<table>
  <thead>
      <tr>
          <th>Option</th>
          <th>Erläuterung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>-U</code></td>
          <td>Kurznotation für <code>--upgrade</code>, also dem installieren von neueren Paketversionen.</td>
      </tr>
      <tr>
          <td><code class="nowrap">--force-reinstall</code></td>
          <td>Erzwinge kein bloßes Update der Pakete, sondern eine komplette Neuinstallation. Dies wird von MkDocs Material <a href="https://squidfunk.github.io/mkdocs-material/upgrade/"  target="_blank" rel="noreferrer">empfohlen</a>.</td>
      </tr>
      <tr>
          <td><code>-r</code></td>
          <td>Kurznotation für <code>--requirements</code>. In diesem Fall wird die <code>requirements.txt</code> eingelesen, die alle benötigten Pakete enthält.</td>
      </tr>
  </tbody>
</table>

<h2 class="relative group">Anregungen
    <div id="anregungen" class="anchor"></div>
    
</h2>
<p>Ich habe dieses Jahr bei meinem derzeitigen Arbeitgeber ein solches Team Web in Inventor realisiert und etabliert. Die Vorteile lagen für mich mit der zentralen Bündelung und Durchsuchbarkeit von Informationen auf der Hand.</p>
<p>Sicherlich ersetzt ein Team Web kein Konstruktionshandbuch - erstrecht nicht in einem strukturell rückständigen Land wie Deutschland, in dem lineare Papierstrukturen auf Totholzerzeugnissen immer noch die Norm sind. Aber es ist ein einfach zu realisierender Gegenvorschlag, der einfach zu Pflegen ist und schnell und effizient fragmentierte Informationen bündeln kann.</p>

<h3 class="relative group">Anwendungsfälle für Team Web
    <div id="anwendungsfälle-für-team-web" class="anchor"></div>
    
</h3>
<p>Da sich Anwendungsfälle immer schwer generalisieren lassen, sind im folgenden einige Anwendungsfälle aus meinem Berufsalltag gelistet:</p>
<ul>
<li>
<p><strong>Troubleshooting:</strong> Ich sammle darin häufige Fehlermeldung aus Vault und Inventor, deren Ursachen und Lösungswege. Durch die eingebaute Suche können die User den Fehlermeldungstext direkt in das Suchfeld tippen und erhalten sofort den Link, der sie zur Lösung führt.</p>
</li>
<li>
<p><strong>FAQ / Anwenderfragen:</strong> Ich verwende eine eigene Sektion für Fragen von PDM-Benutzern. Oftmals traut sich der Anwender im Direktgespräche nicht zu Fragen und macht dann lieber irgendwelchen Quatsch. Ich habe in der Sektion einen Button, der direkt eine Mail-Template startet in denen die User Fragen stellen können. Sind diese von Relevanz oder tragen zum allgemeinen Verständnis bei, nehme ich sie in dieser Sektion auf.</p>
</li>
<li>
<p><strong>Vault Listeneinträge:</strong> Wer mit großen Listen in Autodesk Vault Properties arbeitet, kann ein Lied davon singen, dass diese nur <a href="https://forums.autodesk.com/t5/vault-customization/get-list-of-values-for-vault-property/td-p/10464516"  target="_blank" rel="noreferrer">schwer zu durchsuchen</a> sind. Wir nutzen ein selbstgehacktes Export-Tool, dass mittels der Vault API alle Listeneinträge ausliest und in das CSV-Format konvertiert. Die CSV-Datei lassen wir automatisch von MkDocs <a href="https://timvink.github.io/mkdocs-table-reader-plugin/readers/#read_csv"  target="_blank" rel="noreferrer">einlesen und darstellen</a>.</p>
</li>
<li>
<p><strong>News:</strong> Das <a href="https://squidfunk.github.io/mkdocs-material/setup/setting-up-a-blog/"  target="_blank" rel="noreferrer">Blog-Feature</a> von MkDocs Material eignet sich hervorragend dafür User über anstehende Änderungen, Neuigkeiten oder einfach nur sinnvolle Tipps oder konstruktionsrelevante Youtube Tutorial zu informieren. Youtube Videos können direkt ohne Aufwand in der Webseite eingebunden werden. Ein Beispiel zur Einbindung ist in der Starter Template enthalten.</p>
</li>
</ul>

<h3 class="relative group">Weiterführende Gedanken
    <div id="weiterführende-gedanken" class="anchor"></div>
    
</h3>
<ul>
<li>
<p>Das von mir erstellte Template ist nur ein minimales Showcase, was die Theme MkDocs Material alles zu bieten hat. Die Dokumentation hat eine wunderbar anschauliche Sektion namens <a href="https://squidfunk.github.io/mkdocs-material/reference/"  target="_blank" rel="noreferrer">Reference</a>, in der alle Funktionalität direkt an Beispielen erläutert werden. Ich kann diese Sektion jedem empfehlen, der meine Template erweitern möchte.</p>
</li>
<li>
<p>Für die Arbeit in einem internationalen Unternehmen kann MkDocs Material auch eine <a href="https://squidfunk.github.io/mkdocs-material/setup/changing-the-language/#site-language-selector"  target="_blank" rel="noreferrer">mehrsprachige Dokumentation</a> erstellen. Die Sprache kann dann - pro angezeigter Seite - über einen <em>Language Selector</em> ausgewählt werden. Bitte immer im Hinterkopf behalten, dass sich der Schreibaufwand und die Pflege der Inhalte pro zusätzlicher Sprache proportional erhöht.</p>
</li>
<li>
<p>Das schöne an der Arbeit mit Markdown-Dateien ist, dass es schlichtweg schnörkellose ehrliche Plain-Text-Dateien sind und nicht verkopfte und überladene proprietäre Textdarstellungsformate wie Word-Dokumente. Die Natur einer Plain-Text-Datei erlaubt bspw. eine einfache Kollaboration mehrerer Bearbeiter mittel <a href="https://git-scm.com/"  target="_blank" rel="noreferrer"><code>Git</code></a>. So kann man effizient zusammen an einem Team Web arbeiten, mit dem zusätzlichen Obolus eine ISO-9001 konforme Änderungshistorie zur Hand zu haben, die durch den Git Server automatisch und ohne Mehraufwand erzeugt wird.</p>
<p>MkDocs Material biete darüber hinaus sogar die Möglichkeit zur Anzeige älterer Versionsstände des Team Webs. Das sogenannte <a href="https://squidfunk.github.io/mkdocs-material/setup/setting-up-versioning"  target="_blank" rel="noreferrer">Versioning</a> wird in diesem Fall mittels einer Software namens <a href="https://github.com/jimporter/mike"  target="_blank" rel="noreferrer"><code>mike</code></a> vollzogen.</p>
</li>
<li>
<p><strong>Kosten:</strong> Alle verwendeten Tools sind Open-Source und kostenfrei verfügbar. Ein Webserver sollte in jedem Unternehmen im 21. Jahrhundert bereits zur Verfügung stehen. Selbst wenn dies nicht der Fall ist kann man sich mit dem eingebauten <a href="https://learn.microsoft.com/en-us/iis/get-started/introduction-to-iis/iis-web-server-overview"  target="_blank" rel="noreferrer">IIS-Server</a> von Windows behelfen und darüber das HTML bereitstellen. Während Microsoft gerade ihren Dienst Sharepoint für die nutzung als Intranet pusht, ist man mit einem lokal laufenden und selbstverwalteten Team Web mit MkDocs losgelöst von kontinuierlicher Kostensteigerung bei Microsoft und zusätzlich Herr über die bereitgestellten Daten.</p>
</li>
<li>
<p>Das Erstellen eines Team Webs mit MkDocs bietet viele thematische Facetten und eine einfache Lernkurve, die sich auf zukünftige Projekte übertragen lassen, z.B.:</p>
<ul>
<li>Das Arbeiten mit dem Terminal.</li>
<li>Der Umgang mit Python, Virtual Environments, sowie dem Paketmanager <code>pip</code>.</li>
<li>Das Arbeiten mit Visual Studio Code.</li>
<li>Das Arbeiten mit einem lokalen Entwicklungs-Server.</li>
<li>Das Erlernen von Markdown, das weit verbreitet Anwendung im Weltnetz findet.</li>
<li>Grundsätzliche Funktionsweisen von Static Site Generators.</li>
<li>Das Erstellen von Dokumentationen jenseits von gruseligen Bloatware-Anwendungen wie Microsoft Word.</li>
</ul>
</li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Pixelclock: Custom App Collection</title>
      <link>https://jbetzen.net/posts/pixelclock-custom-app-collection/</link>
      <pubDate>Sat, 29 Apr 2023 11:29:27 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/pixelclock-custom-app-collection/</guid>
      <description>Da die Entwicklung der Custom Firmware AWTRIX3 schleunig voranschreitet und fortlaufend neue Features eingebaut werden, soll dieser Post als Sammler für derzeitige und zukünftige Custom Apps dienen.</description>
      <content:encoded><![CDATA[ 
<h2 class="relative group">Grundsätzliches
    <div id="grundsätzliches" class="anchor"></div>
    
</h2>
<p>Bei Custom Apps ist es, je nach Anwendungsfall, sinnvoll das MQTT-Flag <code>retain: true</code> zu setzen. Nur so ist gewährleistet, dass eine Custom App den Neustart der Pixelclock übersteht und persistent angezeigt wird. Custom Apps <em>leben</em> ansonsten nur im RAM des ESP32 und werden bei einem Neustart gelöscht. Das Retain-Flag bewirkt, dass der MQTT-Broker bei einer Neuverbindung der Pixelclock erneut, die zuletzt gesendete Message an diese übermittelt.</p>
<p><strong>Wann macht es Sinn das Retain-Flag zu nutzen:</strong><br>
Wenn Sensor Daten sich nicht häufig aktualisieren und somit der Trigger in Homeassistant nicht häufig auslöst. Startet man in diesem Fall die Pixelclock neu, kann es unter Umständen sehr lange dauern, bis ein neuer Wert gesendet wird. Die Custom App wäre bis zu diesem Zeitpunkt nicht aktiv.</p>
<p><strong>Wann macht es keinen Sinn das Retain-Flag zu setzen:</strong><br>
Wenn Custom Apps automatisch bei einem Trigger X entfernt werden, siehe Beispiel der Spotify App. Es macht keinen Sinn die letzten Wiedergabeinformationen eines Songs am Broker vorzuhalten, da diese Info <em>flüchtig</em> ist und sich häufig ändert und der Song ggf. gar nicht mehr abgespielt wird, wenn die Pixelclock neugestartet wird.</p>

<h2 class="relative group">Custom Apps
    <div id="custom-apps" class="anchor"></div>
    
</h2>
<p>Folgend eine List an Custom App, die im Zusammenspiel mit Homeassistant an die Pixelclock gesendet werden können. Alle Daten werden direkt aus Homeassistant generiert und benötigen keinerlei externe Add-Ons, wie bspw. Node Red.</p>
<p>Zu jeder Custom App ist ein Icon von der <a href="https://developer.lametric.com/icons"  target="_blank" rel="noreferrer">LaMetric Developer Website</a> verlinkt. Der nachfolgende Dateiname entspricht der Benennung im Filesystem der Pixelclock.</p>

<h3 class="relative group">Spotify
    <div id="spotify" class="anchor"></div>
    
</h3>
<p>Dargestellt wird der aktuell abgespielte Künstler und Songname, sowie ein Fortschrittsanzeige (<em>Progressbar</em>) für den aktuell abgespielten Song. Die Progressbar wird aus zwei Attributen des Media-Players errechnet. Der Name des Media-Players muss entsprechend an den Namen der eigenen <em>Entity</em> in Homeassistant angepasst werden.</p>
<p>Die App wird automatisch entfernt, wenn der State des Media-Player auf <code>idle</code> oder <code>paused</code> wechselt und keine Wiedergabe mehr erfolgt.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock-custom-app-collection/spotify.gif"
          alt="Animated GIF of currently played song on Spotify shown on Pixelclock"
        />
  
  
  </figure>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>18207 / spotify.jpg</td>
      </tr>
      <tr>
          <td>Integration</td>
          <td><a href="https://www.home-assistant.io/integrations/spotify/"  target="_blank" rel="noreferrer">Spotify</a></td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>media_player.spotify</td>
      </tr>
  </tbody>
</table>
</div>
<p><strong>Automation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_spotify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.spotify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">choose</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.spotify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;playing&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                  {
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;text&#34;: &#34;{{ state_attr(&#39;media_player.spotify&#39;, &#39;media_artist&#39;) }} - {{ state_attr(&#39;media_player.spotify&#39;, &#39;media_title&#39;) }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;icon&#34;: &#34;spotify&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;rainbow&#34;: false,
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;duration&#34;: 10,
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;pushIcon&#34;: 0,
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;textCase&#34;: 2,
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;progress&#34;: {{ ( states.media_player.spotify.attributes.media_position / states.media_player.spotify.attributes.media_duration * 100 ) | round() }},
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;progressC&#34;: &#34;18B04C&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/spotify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ not states.media_player.spotify.state == &#39;playing&#39; }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/spotify</span></span></span></code></pre></div></div>

<h3 class="relative group">pr0gramm
    <div id="pr0gramm" class="anchor"></div>
    
</h3>
<p>Zeigt den <em>Benis</em> eines beliebigen Accounts auf einem großen deutschen Imageboard, über das niemand spricht. Der Username, beispielhaft <code>horst</code>, muss entsprechend im API-Call angepasst werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock-custom-app-collection/pr0.jpg"
          alt="Pixelclock showing Benis status of pr0gramm"
        />
  
  
  </figure>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>26807 / pr0.jpg</td>
      </tr>
      <tr>
          <td>Integration</td>
          <td><a href="https://www.home-assistant.io/integrations/sensor.rest/"  target="_blank" rel="noreferrer">RESTful Sensor</a></td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>sensor.pr0</td>
      </tr>
  </tbody>
</table>
</div>
<p><strong>Sensor:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">rest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">scan_interval</span><span class="p">:</span><span class="w"> </span><span class="m">3600</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pr0 Benis&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ value_json[&#34;user&#34;][&#34;score&#34;] | int }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resource</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;https://pr0gramm.com/api/profile/info?name=horst&#39;</span></span></span></code></pre></div></div>
<p><strong>Automation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_pr0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.pr0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34;{{ states.sensor.pr0.state }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;pr0&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/pr0</span></span></span></code></pre></div></div>

<h3 class="relative group">Wetter
    <div id="wetter" class="anchor"></div>
    
</h3>
<p>Für diese Custom App sind diverse Icons erforderlich. Das exakte Vorgehen wurde bereits im vorhergehenden Post <a href="/posts/pixelclock/">Ulanzi Smart Pixel Clock TC001</a> im Detail erläutert.</p>
<p>Die Temperatur wird ganzzahlig gerundet dargestellt.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock-custom-app-collection/weather.gif"
          alt="Pixelclock showing current weather"
        />
  
  
  </figure>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>Zu viele</td>
      </tr>
      <tr>
          <td>Integration</td>
          <td><a href="https://www.home-assistant.io/integrations/weather/"  target="_blank" rel="noreferrer">Weather</a></td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>weather.openweathermap</td>
      </tr>
  </tbody>
</table>
</div>
<p><strong>Automation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_weather</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">weather.openweathermap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ states.weather.openweathermap.attributes.temperature }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34;{{ states.weather.openweathermap.attributes.temperature | round(0) }} {{ states.weather.openweathermap.attributes.temperature_unit }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;{{ states(&#39;weather.openweathermap&#39;) }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;rainbow&#34;: false
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/weather</span></span></span></code></pre></div></div>

<h3 class="relative group">Bitcoin
    <div id="bitcoin" class="anchor"></div>
    
</h3>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Im Post <a href="/posts/automation-templates/">Automation Templates</a> ist eine erweiterte Variante dieser Custom App zu finden, die, je nach steigendem oder fallenden Kurs, den aktuellen Kurswert farblich ausgibt.</span>
</div>

<p>Der aktuelle Kursstand des Bitcoins. Der Name des Sensors muss entsprechend angepasst werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock-custom-app-collection/bitcoin.jpg"
          alt="Pixelclock showing Bitcoin price"
        />
  
  
  </figure>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>10814 / bitcoin.jpg</td>
      </tr>
      <tr>
          <td>Integration</td>
          <td><a href="https://www.home-assistant.io/integrations/bitcoin/"  target="_blank" rel="noreferrer">Bitcoin</a></td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>sensor.exchange_rate_1_btc</td>
      </tr>
  </tbody>
</table>
</div>
<p><strong>Automation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_bitcoin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.exchange_rate_1_btc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34;{{ states.sensor.exchange_rate_1_btc.state | round(0) }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;bitcoin&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/bitcoin</span></span></span></code></pre></div></div>

<h3 class="relative group">Hochwasserportal
    <div id="hochwasserportal" class="anchor"></div>
    
</h3>
<p>Als echte Nordblume ist der Pegelstand der Ostsee natürlich essentiell. Diese Custom App nutzt eine Custom Integration namens <a href="https://github.com/stephan192/hochwasserportal"  target="_blank" rel="noreferrer">Hochwasserportal</a>, die über HACS installiert werden kann. Drei variable Icons kommen für die Pegelstände zum Einsatz. Bei Hochwasser wird der Text in rot ausgegeben, bei Niedrigwasser in gelb.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock-custom-app-collection/hochwasser.gif"
          alt="Pixelclock showing water level"
        />
  
  
  </figure>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>24114 / water-level-low.gif</td>
      </tr>
      <tr>
          <td>LaMetric Icon</td>
          <td>24117 / water-level-normal.gif</td>
      </tr>
      <tr>
          <td>LaMetric Icon</td>
          <td>24120 / water-level-high.gif</td>
      </tr>
      <tr>
          <td>Custom Integration</td>
          <td><a href="https://github.com/stephan192/hochwasserportal"  target="_blank" rel="noreferrer">Hochwasserportal</a></td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>sensor.ostsee_level</td>
      </tr>
  </tbody>
</table>
</div>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_hochwasser</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.ostsee_level</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/hochwasser</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;text&#34;: &#34;{{ states.sensor.ostsee_level.state }} cm&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;textCase&#34;: 2,
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;icon&#34;: {% if (states.sensor.ostsee_level.state | int &lt;= 379) %} &#34;water-level-low&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  {% elif (states.sensor.ostsee_level.state | int &gt;= 624) %} &#34;water-level-high&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  {% else %}&#34;water-level-normal&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  {% endif %},
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;color&#34;:  {% if (states.sensor.ostsee_level.state | int &lt;= 379) %} &#34;#FFFF00&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    {% elif (states.sensor.ostsee_level.state | int &gt;= 624) %} &#34;#FF0000&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    {% else %} &#34;#FFFFFF&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    {% endif %}
</span></span></span><span class="line"><span class="cl"><span class="sd">        }</span></span></span></code></pre></div></div>

<h3 class="relative group">Instagram Follower
    <div id="instagram-follower" class="anchor"></div>
    
</h3>
<p>Angepasst werden muss der Name des Accounts in dem Parameter <code>ressource</code>. Verwendet wird eine Custom Component, die das <em>Scrapen</em> von Webseiten ermöglicht und weitaus mehr Funktionsumfang bietet als die native <a href="https://www.home-assistant.io/integrations/scrape/"  target="_blank" rel="noreferrer">Scrape Integration</a> von Homeassistant. Die Custom Component kann aus dem HACS Store installiert werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock-custom-app-collection/instagram.jpg"
          alt="Pixelclock showing Instagram follower count"
        />
  
  
  </figure>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>3741 / instagram.jpg</td>
      </tr>
      <tr>
          <td>Custom Integration</td>
          <td><a href="https://github.com/danieldotnl/ha-multiscrape"  target="_blank" rel="noreferrer">ha-multiscrape</a></td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>sensor.ig_followers_davidhasselhoff</td>
      </tr>
  </tbody>
</table>
</div>
<p><strong>Sensor:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">multiscrape</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">intagram_followers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resource</span><span class="p">:</span><span class="w"> </span><span class="l">https://www.picnob.com/profile/davidhasselhoff/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">scan_interval</span><span class="p">:</span><span class="w"> </span><span class="m">1800</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sensor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">unique_id</span><span class="p">:</span><span class="w"> </span><span class="l">ig_followers_davidhasselhoff</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;D.Hasselhoff Instagram Followers&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">select</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;.item_followers &gt; div:nth-child(2)&#34;</span></span></span></code></pre></div></div>
<p><strong>Automation</strong>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_instagram</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.ig_followers_davidhasselhoff</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34;{{ states.sensor.ig_followers_davidhasselhoff.state | int() }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;instagram&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/thehoff</span></span></span></code></pre></div></div>

<h3 class="relative group">Octoprint
    <div id="octoprint" class="anchor"></div>
    
</h3>
<p>Die Custom App zeigt den aktuellen Druckfortschritt in Prozent an. Wenn Octoprint während des Druckvorgangs in einen Fehler läuft, wird eine Nachricht dauerhaft auf der Pixelclock angezeigt, die aktiv durch einen Klick auf den mittleren Button entfernt werden muss. Ist der Druckvorgang beendet, wir die Custom App automatisch entfernt.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock-custom-app-collection/octoprint.gif"
          alt="Pixelclock showing Octoprint progress"
        />
  
  
  </figure>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>51841 / 3dprinter.jpg</td>
      </tr>
      <tr>
          <td>Integration</td>
          <td><a href="https://www.home-assistant.io/integrations/octoprint/"  target="_blank" rel="noreferrer">Octoprint</a></td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>sensor.octoprint_current_state</td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>sensor.octoprint_job_percentage</td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>binary_sensor.octoprint_printing_error</td>
      </tr>
  </tbody>
</table>
</div>
<p><strong>Automation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_octoprint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ not state(&#39;sensor.octoprint_current_state&#39;) == &#39;Printing&#39; }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;idle&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.octoprint_job_percentage</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;printing&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.octoprint_printing_error</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;error&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">choose</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">trigger</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;printing&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.octoprint_current_state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Printing&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                {
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &#34;text&#34;: &#34;{{ (states(&#39;sensor.octoprint_job_percentage&#39;) | round(1) ) }}{{ state_attr(&#39;sensor.octoprint_job_percentage&#39;, &#39;unit_of_measurement&#39;) }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &#34;icon&#34;: &#34;3dprinter&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &#34;duration&#34;: 10
</span></span></span><span class="line"><span class="cl"><span class="sd">                }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/octoprint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">trigger</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;idle&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/octoprint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">trigger</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;error&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/octoprint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                {
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &#34;text&#34;: &#34;Error&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &#34;icon&#34;: &#34;3dprinter&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &#34;textCase&#34;: 2,
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &#34;hold&#34;: true
</span></span></span><span class="line"><span class="cl"><span class="sd">                }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/octoprint</span></span></span></code></pre></div></div>

<h3 class="relative group">NINA Warnungen
    <div id="nina-warnungen" class="anchor"></div>
    
</h3>
<p>Das <a href="https://www.bbk.bund.de/"  target="_blank" rel="noreferrer">Bundesamt für Bevölkerungsschutz und Katastrophenhilfe</a> stellt über die <a href="https://www.home-assistant.io/integrations/nina/"  target="_blank" rel="noreferrer">NINA Integration</a> ortsbezogene Warnmeldungen für die Bevölkerung bereit. Diese Custom App prüft, ob eine Warnung vorliegt und nutzt das <a href="https://blueforcer.github.io/awtrix-light/#/api?id=colored-indicators"  target="_blank" rel="noreferrer">Indicator Feature</a> von AWTRIX Light. Liegt eine Warnung vor, leuchtet der Indicator so lange über allen anderen Custom Apps rot, bis die Warnung aufgehoben wird. Bei Aufhebung der Warnung wird die Custom App automatisch entfernt.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock-custom-app-collection/nina.gif"
          alt="Pixelclock showing warnings from NINA warn app"
        />
  
  
  </figure>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>9335 / warning.gif</td>
      </tr>
      <tr>
          <td>Integration</td>
          <td><a href="https://www.home-assistant.io/integrations/nina/"  target="_blank" rel="noreferrer">NINA</a></td>
      </tr>
      <tr>
          <td>Sensor</td>
          <td>binary_sensor.warning_kiel</td>
      </tr>
  </tbody>
</table>
</div>
<p><strong>Automation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_warning</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.warning_kiel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">choose</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.warning_kiel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;on&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                  {
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;text&#34;: &#34;{{ state_attr(&#34;binary_sensor.warning_kiel&#34;, &#34;headline&#34;) }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;icon&#34;: &#34;warning&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;pushIcon&#34;: 2
</span></span></span><span class="line"><span class="cl"><span class="sd">                  }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/nina</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">                  {
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &#34;color&#34;: [255,0,0]
</span></span></span><span class="line"><span class="cl"><span class="sd">                  }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/indicator1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.warning_kiel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;off&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/nina</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/indicator1</span></span></span></code></pre></div></div>

<h3 class="relative group">Homeassistant Timer
    <div id="homeassistant-timer" class="anchor"></div>
    
</h3>
<p>Homeassistant hat eine <a href="https://www.home-assistant.io/integrations/timer/"  target="_blank" rel="noreferrer">Timer Integration</a>, die auf dem Eventbus feuert, wenn ein Timer abgelaufen ist. Wie bereits im Beitrag <a href="/posts/controllerx/">ControllerX und Home Assistant</a> beschrieben, nutze ich diese für meine Waschmaschine.</p>
<p>Verwendet wird ein <a href="https://www.ikea.com/de/de/p/tradfri-shortcut-button-weiss-smart-40356381/"  target="_blank" rel="noreferrer">Tradfri Shortcut Button</a>. Wenn der Button einfach gedrückt wird startet ein Timer für eine 40° Wäsche, wird er gedrückt gehalten startet ein Timer für die 60° Wäsche.</p>
<p>Ist einer der beiden Timer abgelaufen lauscht eine Automation auf dem Eventbus</p>
<p><strong>Timer Configuration:</strong></p>
<p>Die Werte müssen entsprechend der Waschdauer des eigenen Geräts angepasst werden.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">laundry_40</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">duration</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;01:58:00&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">laundry_60</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">duration</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;02:09:00&#39;</span></span></span></code></pre></div></div>
<p><strong>Automation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_laundy_finished</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">event</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_type</span><span class="p">:</span><span class="w"> </span><span class="l">timer.finished</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">timer.laundry_40</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">event</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_type</span><span class="p">:</span><span class="w"> </span><span class="l">timer.finished</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">timer.laundry_60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34;Fertig&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;laundry&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;hold&#34;: true
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/notify</span></span></span></code></pre></div></div>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>26671 / laundry.gif</td>
      </tr>
      <tr>
          <td>Integration</td>
          <td><a href="https://www.home-assistant.io/integrations/timer/"  target="_blank" rel="noreferrer">Timer</a></td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Rauchmelder Warnung
    <div id="rauchmelder-warnung" class="anchor"></div>
    
</h3>
<p>Im Post <a href="/posts/smoke-detector/">Zigbee Rauchmelder</a> ist eine weitere Custom App <a href="/posts/smoke-detector/#nachricht-auf-ulanzi-pixelclock" >beschrieben</a>, die auf der Pixelclock eine Warnung ausgibt, wenn ein Zigbee Rauchmelder einen Brand/Rauch detektiert.</p>

<h3 class="relative group">Spritpreise
    <div id="spritpreise" class="anchor"></div>
    
</h3>
<p>Mit der <a href="https://www.home-assistant.io/integrations/tankerkoenig/"  target="_blank" rel="noreferrer">Tankerkönig Integration</a>, können die Spritpreise nahegelegener Tankstellen angezeigt werden. Dafür muss zuvor ein <a href="https://creativecommons.tankerkoenig.de/"  target="_blank" rel="noreferrer">API-Key</a> erstellt werden.</p>


  

<figure class="centered"><img src="/posts/pixelclock-custom-app-collection/spritpreis.png">
</figure>


<p>Der nachfolgende Automationscode nutzt beispielhaft den Sensor mit dem Namen <code>sensor.shell_pansenstrasse_15_super</code> und entfernt den Tankstellenpfennig/Zehntelpfennig <code>9</code>. Aus <code>2.059 €</code> wird <code>2.05 €</code>.</p>
<p><strong>Automation:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_gasprice</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Spritpreis ohne Tankstellencent 9&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.shell_adelheidstr_15_super</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34;{{ &#34;%.2f&#34; | format((states(&#39;sensor.shell_pansenstrasse_15_super&#39;) | float * 100) | int / 100) }} €&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;fuel&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/gasprice</span></span></span></code></pre></div></div>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LaMetric Icon</td>
          <td>55274 / fuel.gif</td>
      </tr>
      <tr>
          <td>Integration</td>
          <td><a href="https://www.home-assistant.io/integrations/tankerkoenig/"  target="_blank" rel="noreferrer">Tankerkönig</a></td>
      </tr>
  </tbody>
</table>
</div>

<h2 class="relative group">Links
    <div id="links" class="anchor"></div>
    
</h2>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>hivemq.com</td>
          <td><a href="https://www.hivemq.com/blog/mqtt-essentials-part-8-retained-messages/"  target="_blank" rel="noreferrer">MQTT Retained Messages</a></td>
      </tr>
      <tr>
          <td>developer.lametric.com</td>
          <td><a href="https://developer.lametric.com/icons"  target="_blank" rel="noreferrer">LaMetric Icons</a></td>
      </tr>
  </tbody>
</table>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Blechbauteile</title>
      <link>https://jbetzen.net/posts/ilogic-sheetmetal/</link>
      <pubDate>Wed, 26 Apr 2023 21:43:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-sheetmetal/</guid>
      <description>Blechbauteile haben in Inventor eine eigene Konstruktionsumgebung die blechspezifische Funktionen und Features bereitstellt. Dazu gehören Abwicklungen und die Abwicklungsabmaße, die mit iLogic erzeugt und ausgelesen werden können.</description>
      <content:encoded><![CDATA[ <p>Wie bereits im Post <a href="/posts/ilogic-documenttypes/">iLogic: Dokumententypen</a> erwähnt, stellen Blechteile eine Sonderform des <code>PartDocument</code> dar.</p>
<p>Blechteile besitzen im Inventor eine eigene Modellierungsumgebung, Features und Konzepte. Für nachfolgende ERP-Systeme sind oftmals die Maße der Abwicklung und die eingesetzte Stärke des Blechs relevant. Diese können leicht unter der Hilfenahme von iLogic und der Inventor API ermittelt und in ein Custom iProperty geschrieben werden.</p>
<p><strong>Im folgenden zeige ich wie man:</strong></p>
<ul>
<li>Blechbauteile eindeutig identifiziert</li>
<li>prüft, ob im Dokument bereits eine Abwicklung vorhanden ist und falls nicht</li>
<li>eine Abwicklung mit iLogic erstellt</li>
<li>die Abmaße der Abwicklung (<em>Flat Pattern Extents</em>) ermittelt</li>
<li>die Stärke des Blechs ausliest</li>
<li>aus der Stärke und den Abmessungen einen kombinierten String erstellt und in ein Custom Property schreibt</li>
</ul>
<p>Als Beispielbauteil dient ein einfach gelaschtes Blechteil mit Laschenlängen von <code>120mm</code> und <code>220mm</code>, die beidseitig über eine Länge von <code>480mm</code> ausgetragen wurde. Die Blechstärke wurde mit <code>5mm</code> angesetzt.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-sheetmetal/sheetmetal-example.png"
          alt="Screenshot of example sheet metal part"
        />
  
  
  </figure>

<h2 class="relative group">Blechteile identifizieren
    <div id="blechteile-identifizieren" class="anchor"></div>
    
</h2>
<p>Blechteile können nicht direkt über den <code>DocumentType</code> ermittelt, sondern lediglich über die <code>DocumentSubTypeID</code> eindeutig identifiziert werden. Für Blechbauteile lautet diese ID <code>{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}</code> und kann in der API unter <code>oDoc.DocumentSubType.DocumentSubTypeID</code> ausgelesen werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-sheetmetal/sheetmetal-subtypeid.png"
          alt="API Overview Location for DocumentSubType"
        />
  
  
  </figure>
<p>Mit dieser Information können über eine simple If-Schleife ausschließlich Blechbauteile im Code adressiert werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentSubType</span><span class="p">.</span><span class="n">DocumentSubTypeID</span> <span class="o">=</span> <span class="s">&#34;{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}&#34;</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">   <span class="c">&#39;  Hier kommt der Code hin, der im Blechteil laufen soll
</span></span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span></span></span></code></pre></div></div>

<h2 class="relative group">Prüfen, ob Abwicklung bereits vorhanden
    <div id="prüfen-ob-abwicklung-bereits-vorhanden" class="anchor"></div>
    
</h2>
<p>Ob bereits eine Abwicklung in einem Dokument vorhanden ist, lässt sich optisch leicht dem Modellbrowser entnehmen. Ist dort der Eintrag <code>Flat Pattern</code> vorhanden, wurde bereits im Blechteil eine Abwicklung erstellt. Diese kann beispielsweise direkt in einer Zeichnungsansicht verwendet werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-sheetmetal/sheetmetal-flatpattern-modelbrowser.png"
          alt="Screenshot of sheet metal part example"
        />
  
  
  </figure>
<p>Auch die API bietet ein bool&rsquo;sches Property an, mit dem das Vorhandensein einer Abwicklung ermittelt werden kann. Die ist zu finden unter <code>oDoc.ComponentDefinition.HasFlatPattern</code>. Der Wert des Properties ist ein Boolean und kann demnach nur die Werte <code>True</code> und <code>False</code> annehmen. Ist der Wert <code>True</code>, ist eine Abwicklung im Dokument vorhanden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-sheetmetal/sheetmetal-hasflatpattern.png"
          alt="API Overview Location HasFlatPattern Property"
        />
  
  
  </figure>

<h2 class="relative group">Abwicklung mit iLogic erstellen
    <div id="abwicklung-mit-ilogic-erstellen" class="anchor"></div>
    
</h2>
<p>Die Information, ob bereits eine Abwicklung vorhanden ist, kann nun genutzt werden um diese automatisch zu erstellen, sofern sie noch nicht vorhanden ist. Dazu kann eine Subroutine geschrieben werden, die folgendermaßen aussieht und folgendes tut:</p>
<ol>
<li>Prüfung, ob Abwicklung vorhanden ist und wenn nicht..</li>
<li>&hellip;erstelle die Abwicklung mittels der Methode <code>Unfold()</code></li>
<li>wechsle aus der Abwicklungsansicht zurück in das &ldquo;gefaltete&rdquo; Modell mittels der Methode <code>FlatPattern.ExitEdit()</code></li>
<li>Update das Part, um alle Änderungen in das Blechbauteil zu übertragen mit dem iLogic Shortcut <code>InventorVb.DocumentUpdate()</code></li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">ErstelleAbwicklung</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oSheetMetalCompDef</span> <span class="ow">As</span> <span class="n">SheetMetalComponentDefinition</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">ComponentDefinition</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="n">HasFlatPattern</span><span class="p">()</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Eine Abwicklung ist bereits vorhanden.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Eine Abwicklung ist nicht vorhanden und wird erstellt.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="n">Unfold</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="n">FlatPattern</span><span class="p">.</span><span class="n">ExitEdit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">&#39; Aktualisiere das Blechteil nach Abwicklungserstellung
</span></span></span><span class="line"><span class="cl">        <span class="n">InventorVb</span><span class="p">.</span><span class="n">DocumentUpdate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p>Näheres zum strukturellen Aufbau einer iLogic, Funktionen und Subroutinen wurde bereits im Post <a href="/posts/ilogic-struktur/">iLogic: Struktureller Aufbau</a> ausführlich beschrieben.</p>

<h2 class="relative group">Abwicklungsdimensionen ermitteln
    <div id="abwicklungsdimensionen-ermitteln" class="anchor"></div>
    
</h2>
<p>Zur Ermittlung der abgewickelten Dimensionen ist es <strong>zwingend</strong> erforderlich, dass im Bauteil bereits eine Abwicklung vorhanden ist.</p>
<p>Zusätzlich gilt es immer zu bedenken, dass die Inventor API intern mit dem Längenmaß <code>cm</code> als Einheit arbeitet, die im Engineering eher untypisch ist und in <code>mm</code> konvertiert werden muss. Dies wurde bereits im Post <a href="/posts/ilogic-uom/">iLogic: Units of Measurement</a> erläutert. Zur Umrechnung kommt auch an dieser Stelle die Methode <code>ConvertUnits()</code> zum Einsatz.</p>

<h3 class="relative group">Abgewickelte Länge und Breite
    <div id="abgewickelte-länge-und-breite" class="anchor"></div>
    
</h3>
<p>Zur Verfügung stehen zwei Properties mit den Namen <code>Width</code>und <code>Length</code>. Beide sind vom Datentyp <code>Double</code> und in der API unter <code>oDoc.ComponentDefinition.FlatPattern</code> zu finden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-sheetmetal/sheetmetal-api-flatpatternextents.png"
          alt="API Overview Location for Length and Width Properties"
        />
  
  
  </figure>
<p>Wie man im Screenshot im Wert <code>Width</code> gut erkennen kann, haben <em>Double Precision Values</em> potentiell viele Nachkommastellen. Bei Blechteilen ist es in der Regel so, dass auf den nächst höheren Ganzzahlwert gerundet wird, um sicherzustellen, dass nicht zu wenig Ausgangsmaterial des Bleches bestellt wird. Daher schleichen sich bei vielen Nachkommastellen potentiell Fehler bei Rundungen ein, wenn bspw. ein Breitenwert von <code>0.00000000001</code> vorhanden ist auf den nächst höheren Ganzzahlwert <code>1</code> gerundet wird. Dies gilt es zu berücksichtigen und abzufangen.</p>

<h3 class="relative group">Blechstärke ermitteln
    <div id="blechstärke-ermitteln" class="anchor"></div>
    
</h3>
<p>Die Blechtstärke besitzt in der API ein eigenes Property <code>Thickness</code>, ebenfalls vom Typ <code>Double</code>, das unter <code>oDoc.ComponentDefinition.Thickness</code> zur Verfügung steht.</p>
<p>Dieses Property ist <strong>ausschließlich</strong> bei Blech- und nicht bei Standardbauteilen verfügbar und Teil der <code>SheetMetalComponentDefinition</code>.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-sheetmetal/sheetmetal-thickness.png"
          alt="API Overview Location for Thickness Property"
        />
  
  
  </figure>

<h3 class="relative group">Dimensionen in Property schreiben
    <div id="dimensionen-in-property-schreiben" class="anchor"></div>
    
</h3>
<p>Wenn Blechstärke, Länge und Breite bekannt sind können diese leicht in die Einheit <code>mm</code> konvertiert, zu einem kombinierten String zusammengefügt und in ein Property geschrieben werden. Auch hierfür kommt eine eigene Subroutine zum Einsatz, die folgendes tut:</p>
<ol>
<li>Blechstärke, Länge und Breite der Abwicklung ermitteln</li>
<li>Die ermittelten Werte von <code>cm</code> in <code>mm</code> konvertiert und auf eine Nachkommastelle gerundet.</li>
<li>Länge und Breite werden auf ganzzahlige Millimeter aufgerundet.</li>
<li>Es wird ein kombinierter String erstellt mit dem Schema <code>Blech Stärke x Länge x Breite</code>.</li>
<li>Der kombinierte String wird in ein Custom Property mit dem Namen <code>Blechabmessungen</code> geschrieben.</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">ErmittleAbwicklungsdimensionen</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oSheetMetalCompDef</span> <span class="ow">As</span> <span class="n">SheetMetalComponentDefinition</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">ComponentDefinition</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">UOM</span> <span class="ow">As</span> <span class="n">UnitsOfMeasure</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">UnitsOfMeasure</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oFlatPattern</span> <span class="ow">As</span> <span class="n">FlatPattern</span> <span class="o">=</span> <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="o">**</span><span class="n">FlatPattern</span><span class="o">**</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">dThickness</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="n">Thickness</span><span class="p">.</span><span class="n">Value</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">dFlatPatternLength</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oFlatPattern</span><span class="p">.</span><span class="n">Length</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">dFlatPatternWidth</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oFlatPattern</span><span class="p">.</span><span class="n">Width</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">dThickness</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">dThickness</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Konvertiere cm in mm und runde Millimeterangaben auf eine Nachkommastelle
</span></span></span><span class="line"><span class="cl">    <span class="n">dFlatPatternLength</span> <span class="o">=</span> <span class="n">Round</span><span class="p">(</span><span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">oFlatPattern</span><span class="p">.</span><span class="n">Length</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">),</span> <span class="n">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">dFlatPatternWidth</span> <span class="o">=</span> <span class="n">Round</span><span class="p">(</span><span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">oFlatPattern</span><span class="p">.</span><span class="n">Width</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">),</span> <span class="n">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Rund auf nächst höheren Ganzzahlwert
</span></span></span><span class="line"><span class="cl">    <span class="n">dFlatPatternWidth</span> <span class="o">=</span> <span class="n">Ceil</span><span class="p">(</span><span class="n">dFlatPatternWidth</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">dFlatPatternLength</span> <span class="o">=</span> <span class="n">Ceil</span><span class="p">(</span><span class="n">dFlatPatternLength</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Erstelle kombinierten String mit dem Schema: Stärke x Länge x Breite
</span></span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sCombinedString</span> <span class="ow">As</span> <span class="kt">String</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sCombinedString</span> <span class="o">=</span> <span class="s">&#34;Blech &#34;</span> <span class="o">&amp;</span> <span class="n">dThickness</span> <span class="o">&amp;</span> <span class="s">&#34; x &#34;</span> <span class="o">&amp;</span> <span class="n">dFlatPatternLength</span> <span class="o">&amp;</span> <span class="s">&#34; x &#34;</span> <span class="o">&amp;</span> <span class="n">dFlatPatternWidth</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Schreibe kombinierten String in ein Custom Property mit dem Namen &#34;Blechabmessungen&#34;
</span></span></span><span class="line"><span class="cl">    <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Custom&#34;</span><span class="p">,</span> <span class="s">&#34;Blechabmessungen&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="n">sCombinedString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Blechabmessungen: &#34;</span> <span class="o">&amp;</span> <span class="n">sCombinedString</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>

<h2 class="relative group">Vollständiges Codebeispiel
    <div id="vollständiges-codebeispiel" class="anchor"></div>
    
</h2>
<p>Es kann nun in einer iLogic eine Hauptroutine erstellt werden, die im ersten Schritt prüft, ob es sich um ein Blechbauteil handelt und anschließend beide Subroutinen aufruft:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentSubType</span><span class="p">.</span><span class="n">DocumentSubTypeID</span> <span class="o">=</span> <span class="s">&#34;{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}&#34;</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">ErstelleAbwicklung</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">ErmittleAbwicklungsdimensionen</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">ErstelleAbwicklung</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oSheetMetalCompDef</span> <span class="ow">As</span> <span class="n">SheetMetalComponentDefinition</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">ComponentDefinition</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="n">HasFlatPattern</span><span class="p">()</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Eine Abwicklung ist bereits vorhanden.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="n">Logger</span><span class="p">.</span><span class="n">Warn</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Eine Abwicklung ist nicht vorhanden und wird erstellt.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="n">Unfold</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="n">FlatPattern</span><span class="p">.</span><span class="n">ExitEdit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">&#39; Update Document, nach Erzeugung der Blechabwicklung
</span></span></span><span class="line"><span class="cl">        <span class="n">InventorVb</span><span class="p">.</span><span class="n">DocumentUpdate</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">ErmittleAbwicklungsdimensionen</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oSheetMetalCompDef</span> <span class="ow">As</span> <span class="n">SheetMetalComponentDefinition</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">ComponentDefinition</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">UOM</span> <span class="ow">As</span> <span class="n">UnitsOfMeasure</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">UnitsOfMeasure</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oFlatPattern</span> <span class="ow">As</span> <span class="n">FlatPattern</span> <span class="o">=</span> <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="o">**</span><span class="n">FlatPattern</span><span class="o">**</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">dThickness</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oSheetMetalCompDef</span><span class="p">.</span><span class="n">Thickness</span><span class="p">.</span><span class="n">Value</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">dFlatPatternLength</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oFlatPattern</span><span class="p">.</span><span class="n">Length</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">dFlatPatternWidth</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oFlatPattern</span><span class="p">.</span><span class="n">Width</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">dThickness</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">dThickness</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Konvertiere cm in mm und runde Millimeterangaben auf eine Nachkommastelle
</span></span></span><span class="line"><span class="cl">    <span class="n">dFlatPatternLength</span> <span class="o">=</span> <span class="n">Round</span><span class="p">(</span><span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">oFlatPattern</span><span class="p">.</span><span class="n">Length</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">),</span> <span class="n">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">dFlatPatternWidth</span> <span class="o">=</span> <span class="n">Round</span><span class="p">(</span><span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">oFlatPattern</span><span class="p">.</span><span class="n">Width</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">),</span> <span class="n">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Rund auf nächst höheren Ganzzahlwert
</span></span></span><span class="line"><span class="cl">    <span class="n">dFlatPatternWidth</span> <span class="o">=</span> <span class="n">Ceil</span><span class="p">(</span><span class="n">dFlatPatternWidth</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">dFlatPatternLength</span> <span class="o">=</span> <span class="n">Ceil</span><span class="p">(</span><span class="n">dFlatPatternLength</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Erstelle kombinierten String mit dem Schema: Stärke x Länge x Breite
</span></span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">sCombinedString</span> <span class="ow">As</span> <span class="kt">String</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sCombinedString</span> <span class="o">=</span> <span class="s">&#34;Blech &#34;</span> <span class="o">&amp;</span> <span class="n">dThickness</span> <span class="o">&amp;</span> <span class="s">&#34; x &#34;</span> <span class="o">&amp;</span> <span class="n">dFlatPatternLength</span> <span class="o">&amp;</span> <span class="s">&#34; x &#34;</span> <span class="o">&amp;</span> <span class="n">dFlatPatternWidth</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Schreibe kombinierten String in ein Custom Property mit dem Namen &#34;Blechabmessungen&#34;
</span></span></span><span class="line"><span class="cl">    <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Custom&#34;</span><span class="p">,</span> <span class="s">&#34;Blechabmessungen&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="n">sCombinedString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34; | &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | Blechabmessungen: &#34;</span> <span class="o">&amp;</span> <span class="n">sCombinedString</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p>Der iLogic Log zeigt bei Ausführung des Codes die ermittelten Werte an:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-sheetmetal/sheetmetal-logger.png"
          alt="Screenshot of iLogic Log Output"
        />
  
  
  </figure>
<p>Wer mehr über die Logging Funktionalität von iLogic erfahren möchte kann den vorangegangenen Post <a href="/posts/ilogic-logger/">iLogic: Logging</a> lesen.</p>

<h2 class="relative group">Links
    <div id="links" class="anchor"></div>
    
</h2>
<ul>
<li><a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-SheetMetalComponentDefinition"  target="_blank" rel="noreferrer">Inventor API: SheetMetalComponentDefinition</a></li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Ulanzi Smart Pixel Clock TC001</title>
      <link>https://jbetzen.net/posts/pixelclock/</link>
      <pubDate>Thu, 30 Mar 2023 20:33:58 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/pixelclock/</guid>
      <description>Die Ulanzi Pixelclock ist eine kleine Pixelmatrix, die über die Installation der Custom Firmware AWTRIX3 und deren Integration als Notifier in Homeassistant genutzt werden kann. Die Clock eignet sich hervorragend als stationäres Display für Statusupdates.</description>
      <content:encoded><![CDATA[ <p>Die <a href="https://www.ulanzi.de/products/ulanzi-pixel-smart-uhr-2882"  target="_blank" rel="noreferrer">Ulanzi Pixel Clock TC001</a> ist für ca. 40€ zu erwerben und besitzt eine 8x32 RGB-Pixelmatrix. Verbaut ist in ihr ein ESP32 Mikrokontroller, der wahlweise mit ESPHome oder mit einer Custom-Firmware geflasht werden kann. Somit kann die Pixel Clock als universeller Notifier für Homeassistant eingesetzt werden. Neben der Möglichkeit animierte GIFs und statische Icons anzuzeigen, besitzt die Pixel Clock eine Reihe weiterer verbauter Sensoren für Helligkeit, Luftfeuchte und Temperatur und kann darüber hinaus auch (furchtbar klingende) 


<abbr title="Ring Tones Text Transfer Language">RTTTL</abbr> Sounds abspielen. Ein fest verbauter Akku mit 4400mAh erlaubt den kurzzeitigen Betrieb ohne Stromversorgung.</p>

<h2 class="relative group">Custom Firmware: AWTRIX3
    <div id="custom-firmware-awtrix3" class="anchor"></div>
    
</h2>
<p>Obwohl für die Pixel Clock eine <a href="https://blakadder.com/esphome-pixel-clock/"  target="_blank" rel="noreferrer">ausführliche Anleitung</a> zur Verwendung mit <a href="/categories/esphome/" >ESPHome</a> existiert, habe ich mich für die Verwendung mit der Custom Firmware <a href="https://github.com/Blueforcer/awtrix3"  target="_blank" rel="noreferrer">AWTRIX3</a> entschieden.</p>
<p>Die Firmware ist quelloffen und unterstützt <em>Homeassistant Discovery</em>, das eine unkomplizierte Integration mittels MQTT ermöglicht. Vorrausetzung ist, dass ein MQTT-Broker betrieben wird - bspw. mittels des Homeassistant Add-On <a href="https://github.com/home-assistant/addons/tree/master/mosquitto"  target="_blank" rel="noreferrer">Mosquitto Broker</a>.</p>

<h3 class="relative group">Firmware flashen
    <div id="firmware-flashen" class="anchor"></div>
    
</h3>
<p>AWTRIX3 kann sehr einfach mittels ESP Web Tools direkt aus dem Chrome/Edge Browser auf das Gerät geflasht werden. Dazu muss die Pixel Clock lediglich mittels USB-C Kabel mit einem Rechner verbunden werden und der zugehörige <a href="https://blueforcer.github.io/awtrix3/#/flasher"  target="_blank" rel="noreferrer">Online Flasher</a> im Browser aufgerufen werden. Nach einem erfolgreichen Flashen wechselt die Pixel Clock in den AP-Modus und spannt ein WiFi mit dem Namen <code>awtrix_XXXXX</code> auf, das mit dem Passwort <code>12345678</code> verbunden werden kann. Nachdem die Verbindungsdaten zum heimischen WiFi hinterlegt wurden, startet die Pixel Clock neu und ist fortan im eigenen Netzwerk erreichbar. Die vergebene IP wird praktischerweise auf dem Display dargestellt.</p>

<h3 class="relative group">Ersteinrichtung
    <div id="ersteinrichtung" class="anchor"></div>
    
</h3>
<p>Ruft man nun das Webinterface der Pixel Clock im Browser auf erscheint eine Oberfläche zur Konfiguration:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock/awtrix-light-menu.png"
          alt=""
        />
  
  
  </figure>
<p>Die Menupunkte und ihre Einstellungsmöglichkeiten im Detail:</p>
<table>
  <thead>
      <tr>
          <th>Menüpunkt</th>
          <th>Einstellungsmöglichkeiten</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>WiFi Setup</td>
          <td>Hier können bei Bedarf neue WiFi Credentials hinterlegt werden.</td>
      </tr>
      <tr>
          <td>Network</td>
          <td>Netzwerkeinstellungen, wie DNS Server oder Vergabe einer statischen IP.</td>
      </tr>
      <tr>
          <td>MQTT</td>
          <td>Hier müssen die MQTT-Broker Einstellungen hinterlegt und Homeassistant Discovery aktiviert werden. Auch kann hier der Prefix für MQTT festgelegt werden, über den die Pixel Clock angesprochen werden kann.</td>
      </tr>
      <tr>
          <td>Time</td>
          <td>NTP Server zur zeitsynchronisation und Zeitzoneneinstellungen.</td>
      </tr>
      <tr>
          <td>Icons</td>
          <td>Hierüber können Icons und animierte Gifs auf das lokale Dateisystem des ESP32 geladen werden, die in den dargstellten Nachrichten angezeigt werden können.</td>
      </tr>
      <tr>
          <td>General</td>
          <td>Hier kann festgelegt werden, ob Text nur in Großbuchstaben (<em>Uppercase</em>) dargestellt werden soll.</td>
      </tr>
      <tr>
          <td>Files</td>
          <td>Öffnet ein weiteres Browserfenster zur einfachen Verwaltung von Icons, RTTTL-Melodien, Alarmen oder der globalen Konfigurationsdatei von AWTRIX3.</td>
      </tr>
      <tr>
          <td>Update</td>
          <td>Erlaubt das Flashen von Firmware Updates direkt aus dem Webbrowser.</td>
      </tr>
  </tbody>
</table>
<p>Sind alle Einstellungen getätigt und die MQTT-Konfigurationswerte korrekt eingegeben worden, erkennt Homeassistant automatisch alle Sensoren und Funktionen der Pixel Clock:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock/hass-discovery.png"
          alt=""
        />
  
  
  </figure>

<h3 class="relative group">Bedienung
    <div id="bedienung" class="anchor"></div>
    
</h3>
<p>Im Gegensatz zur Standardfirmware von Ulanzi ermöglicht AWTRIX3 die Standardfunktionen, bspw. welche Infos dargestellt werden sollen, wie lange eine Info sichtbar sein soll oder wie schnell die Transition beim Bildschirmwechsel erfolgen soll, direkt über die 3 Buttons auf der Oberseite der Pixel Clock einzustellen. Dazu muss der mittlere Knopf 2 Sekunden gedrückt werden. Anschließend kann mit dem linken und rechten Button navigiert werden. Möchte man ein Untermenüpunkt aktivieren erfolgt dies über einen einfachen Klick auf den mittleren Button. Möchte man zurück ins Menü kehren erfolgt dies wieder durch ein zweisekündiges Gedrückthalten des mittleren Buttons.</p>

<h3 class="relative group">Icons herunterladen
    <div id="icons-herunterladen" class="anchor"></div>
    
</h3>
<p>Icons können von der Konkurrenz von <a href="https://lametric.com/en-US"  target="_blank" rel="noreferrer">LaMetric</a> genutzt und heruntergeladen werden. Neben vielen existierenden Icons und Gifs können auf der <a href="https://developer.lametric.com/icons"  target="_blank" rel="noreferrer">Herstellerseite</a> auch eigene Icons generiert werden.</p>
<p>Hat man ein passendes Icon gefunden, muss lediglich die <code>ID</code> des Icons im Webinterface von AWTRIX3 im Menüpunkt <em>Icons</em> eingegeben werden. Es kann ein Preview generiert und die Datei direkt ins Dateisystem heruntergeladen werden. Die Icons liegen im Ordner <code>/ICONS/</code>und haben standardmäßig das Namensschema <code>$ID.jpg</code> oder <code>$ID.gif</code>. Die Dateinamen können direkt im Webinterface mit einem Rechtsklick und der Funktion <code>Rename/Move</code> in sprechende Namen umbenannt werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock/awtrix-light-filesystem.png"
          alt=""
        />
  
  
  </figure>

<h3 class="relative group">RTTTL Soundfiles speichern
    <div id="rtttl-soundfiles-speichern" class="anchor"></div>
    
</h3>
<p><em>Ring Tone Text Transfer Language</em>, kurz RTTTL, Soundfiles können im Ordner <code>/MELODIES/</code> im <code>txt</code>-Format abgelegt werden. Ein kleiner Fundus an RTTTL-Soundfiles kann <a href="https://www.laub-home.de/wiki/RTTTL_Songs"  target="_blank" rel="noreferrer">hier</a> entdeckt und verwendet werden. Möchte man beispielsweise die Titelmelodie der TV Serie <em>Die Simpsons</em> erklingen lassen, legt man die Datei <code>/MELODIES/simpsons.txt</code> an und fügt dort den folgenden Inhalt ein:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">The Simpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c6,8c6,c6</span></span></code></pre></div></div>

<h2 class="relative group">Nutzen als Notifier in Homeassistant
    <div id="nutzen-als-notifier-in-homeassistant" class="anchor"></div>
    
</h2>
<p>Über MQTT können nun Notifications direkt aus Homeassistant an die Pixel Clock gesendet werden. Entscheidend ist hierfür das vom Benutzer vergebene MQTT Präfix, welches in der Ersteinrichtung gewählt wurde. Ich habe an dieser Stelle den Präfix <code>pixelclock</code> gewählt. Möchte man grundsätzlich erst einmal testen wie Notifications auf der Clock aussehen, kann dies sehr einfach in den <em>Developer Tools</em> von Homeassistant erfolgen. Dazu den Menüpunkt <code>Services</code> auswählen und dann den Service <code>mqtt.publish</code>:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock/hass-devtools.png"
          alt=""
        />
  
  
  </figure>
<p>Es stehen grundsätzlich <strong>zwei Arten</strong> der Notification mit diversen <em>Payload Parametern</em> zur Verfügung, die in der Sektion <a href="https://blueforcer.github.io/awtrix3/#/api?id=custom-apps-and-notifications"  target="_blank" rel="noreferrer">Custom Apps and Notifications</a> beschrieben werden:</p>
<ol>
<li><strong>Notification:</strong> Eine Textnachricht, ggf. mit Icon, die für eine definierbare Dauer dargestellt wird. Es besteht auch die Möglichkeit eine Notification solange darzustellen, bis sie durch einen Klick auf den mittleren Button entfernt wird.</li>
<li><strong>Custom Page:</strong> Standardmäßig wechselt die Pixel Clock durch ein definierte Liste an Infobildschirmen. Ist das Ende der Liste erreicht, beginnt sie von vorne. Diese Liste kann mit einer (oder mehreren) Custom Page erweitert werden.</li>
</ol>

<h3 class="relative group">Fall 1: Notification
    <div id="fall-1-notification" class="anchor"></div>
    
</h3>
<p><strong>Fiktiver Fall:</strong> Ein Rauchmelder in der Wohnung meldet einen Brand. Hierzu habe ich das Feuer-Icon mit der LaMetric-ID <code>24873</code> über das Webinterface von AWTRIX3 heruntergeladen und anschließend in <code>feuer.gif</code> umbenannt. Um nun dieses Icon und den Text <code>Feuer!</code> auf dem Display anzuzeigen wird der folgende Service Call gesendet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    {
</span></span></span><span class="line"><span class="cl"><span class="sd">      &#34;icon&#34;: &#34;fire&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">      &#34;text&#34;: &#34;Feuer!&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">      &#34;hold&#34;: true
</span></span></span><span class="line"><span class="cl"><span class="sd">    }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/notify</span></span></span></code></pre></div></div>
<p>Der Parameter <code>hold</code> bewirkt dabei, dass die Darstellung erst beendet wird wenn sie durch einen Klick auf den mittleren Button bestätigt wird.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock/feuer.gif"
          alt=""
        />
  
  
  </figure>

<h3 class="relative group">Fall 2: Custom Page
    <div id="fall-2-custom-page" class="anchor"></div>
    
</h3>
<p><strong>Fiktiver Fall:</strong> Der Wert eines Sensors in Homeassistant soll kontinuierlich auf der Pixel Clock angezeigt werden. Ändert sich der Wert, soll auch die Info auf der Pixelclock aktuell sein. Als Beispiel dient ein REST-Sensor für ein deutsches Bilderbrett, das im <a href="https://community.home-assistant.io/t/pr0gramm-com-benis-sensor/76946"  target="_blank" rel="noreferrer">Homeassistant Forum</a> zu finden ist. Verwendet wird das Icon mit der LaMetric ID <code>26807</code>, das anschließend unter dem Dateinamen <code>pr0.jpg</code> gespeichert wurde. Der Sensor hat den Namen <code>sensor.pr0</code>. Zu beachten ist, dass jede Custom Page einen eindeutigen Namen erhält. In diesem Fall ist es <code>pr0</code>. Die Automation in Homeassistant sieht wie folgt aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_pr0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.pr0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;text&#34;: &#34;{{ states.sensor.pr0.state }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">          &#34;icon&#34;: &#34;pr0&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">        }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/pr0</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock/pr0.gif"
          alt=""
        />
  
  
  </figure>
<p>Custom Pages sollten ggf. mit dem parameter <code>retain: true</code> gesendet werden. Der MQTT-Broker speichert somit das letzte gesendete <code>mqtt.publish</code> Kommando und stellt dieses bei einem Neustart der Pixel Clock wieder zur Verfügung. Nähere Erläuterungen zum Thema <strong>Retained Messages</strong> sind im Folgepost <a href="/posts/pixelclock-custom-app-collection/">Pixelclock: Custom App Collection</a> zu finden.</p>





  
  
    
  
  


  <section class="space-y-10 w-full">
    
    











  
  
  








  
  
    

    
    
      
      
        
      
        
      
        
      
    

    
    

    
    
      
        
        
      
    
  



<article class="article-link--shortcode flex flex-col md:flex-row relative">
  
    <div class="flex-none relative overflow-hidden  thumbnail-shadow md:mr-7 thumbnail">
      <img
        src="/posts/pixelclock-custom-app-collection/featured.png"
        role="presentation"
        loading="lazy"
        decoding="async"
        class="not-prose absolute inset-0 w-full h-full object-cover">
    </div>
  
  <div class=" mt-3 md:mt-0">
    <header class="items-center text-start text-xl font-semibold">
      <a
        
          href="/posts/pixelclock-custom-app-collection/"
        
        class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
        <h2>
          Pixelclock: Custom App Collection
          
        </h2>
      </a>
      
      
    </header>
    <div class="text-sm text-neutral-500 dark:text-neutral-400">
      







  

  
  
  
    
  

  

  
    
  

  
    
  

  
    
  

  
    
  

  

  

  

  

  


  <div class="flex flex-row flex-wrap items-center">
    
    
      <time datetime="2023-04-29T11:29:27&#43;02:00">29.04.2023</time><span class="px-2 text-primary-500">&middot;</span><time datetime="2026-03-05T00:00:00&#43;00:00">Aktualisiert: 05.03.2026</time><span class="px-2 text-primary-500">&middot;</span><span>1556 Wörter</span><span class="px-2 text-primary-500">&middot;</span><span title="Lesezeit">8 min</span>
    

    
    
  </div>

  

  
  
    <div class="flex flex-row flex-wrap items-center">
      
        
      
        
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/mqtt/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    MQTT
  </span>
</span>

                </a>
              
            
            
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/microcontroller/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    Microcontroller
  </span>
</span>

                </a>
              
            
            
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/media-player/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    media-player
  </span>
</span>

                </a>
              
            
            
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/notification/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    Notification
  </span>
</span>

                </a>
              
            
            
          
        
      
        
      
        
          
            
              
            
            
          
            
              
            
            
          
            
              
            
            
          
        
      
        
          
            
              
            
            
          
        
      
    </div>
  


    </div>
    
      
      <div
        class="article-link__summary prose dark:prose-invert max-w-fit mt-1 ">
        Da die Entwicklung der Custom Firmware AWTRIX3 schleunig voranschreitet und fortlaufend neue Features eingebaut werden, soll dieser Post als Sammler für derzeitige und zukünftige Custom Apps dienen.
      </div>
    
  </div>
</article>

  </section>


<p>Möchte man die Custom Page wieder entfernen, erfolgt dies über das erneute adressieren des App-Topics, jedoch mit einer leeren <em>Payload</em>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/pr0</span></span></span></code></pre></div></div>

<h2 class="relative group">Sounds Wiedergeben
    <div id="sounds-wiedergeben" class="anchor"></div>
    
</h2>
<p>Generell kann jeder Art von Notification auch eine <a href="https://blueforcer.github.io/awtrix3/#/api?id=sound-playback"  target="_blank" rel="noreferrer">Soundwiedergabe</a> zugewiesen werden, wovon man nur abraten kann. Verwendet wird exemplarisch die bereits erwähnte <code>simpsons.txt</code> RTTTL-File:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    {
</span></span></span><span class="line"><span class="cl"><span class="sd">      &#34;sound&#34;: &#34;simpsons&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/sound</span></span></span></code></pre></div></div>
<p>Alternativ kann auch direkt ein RTTTL-String über die API-Endpoint <code>/RTTTL</code> abgespielt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    {
</span></span></span><span class="line"><span class="cl"><span class="sd">      &#34;sound&#34;: &#34;d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c6,8c6,c6&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">    }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/rtttl</span></span></span></code></pre></div></div>
<p>Der Timer ist nach 3 Sekunden abgelaufen und spielt anschließend die Melodie so lange, bis der mittlere Button auf der Clock gedrückt wird.</p>

<h2 class="relative group">Automation: Spotify Media Player
    <div id="automation-spotify-media-player" class="anchor"></div>
    
</h2>
<p>Die Pixel Clock eignet sich hervorragend Informationen zum aktuell wiedergegebenen Interpreten und Song darzustellen. Genutzt wird in diesem Beispiel die <a href="https://www.home-assistant.io/integrations/spotify/"  target="_blank" rel="noreferrer">Spotify Integration</a>. Analog wird erneut ein passendes Icon gesucht und als <code>spotify.gif</code> oder <code>spotify.jpg</code> auf der Clock gespeichert.</p>
<p>Als <strong>Trigger</strong> wird der <em>State</em> des Spotify Media Player verwendet. Erfolgt eine aktive Wiedergabe wechselt dessen State auf <code>playing</code> und stellt zusätzlich die Attribute <code>media_artist</code> (Interpret) und <code>media-title</code> (Songname) bereit. Wird die Wiedergabe pausiert wechselt der State zu <code>idle</code> und die Attribute stehen nicht länger zur Verfügung, daher muss dieser Fall als Ausnahme über eine <em>Condition</em> ausgeschlossen werden.</p>
<p>Da die dargestellte Textinformation unter Umständen sehr lang ausfallen, macht es Sinn einen höheren Wert für die <code>duration</code> (Anzeigedauer) zu wählen. Auch ist in diesem Fall die Option <code>pushIcon</code> mit dem Wert <code>2</code> gesetzt. Dies bewirkt, dass das Icon mit dem Scrollen des Textes <em>mitfährt</em> und anschließend beim nächsten Durchlauf erneut erscheint.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_spotify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.spotify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">not</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.spotify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;idle&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34; {{ state_attr(&#39;media_player.spotify&#39;, &#39;media_artist&#39;) }} - {{ state_attr(&#39;media_player.spotify&#39;, &#39;media_title&#39;) }}&#34;, 
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;spotify&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;duration&#34;: 10,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;pushIcon&#34;: 2
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/notify</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/pixelclock/spotify.gif"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">Wetteranzeige
    <div id="wetteranzeige" class="anchor"></div>
    
</h2>
<p>Die Pixelclock lässt sich hervorragend als Wetteranzeiger nutzen. Voraussetzung ist eine eingerichtete <a href="https://www.home-assistant.io/integrations/weather/"  target="_blank" rel="noreferrer">Weather Integration</a> bspw. <a href="https://www.home-assistant.io/integrations/openweathermap/"  target="_blank" rel="noreferrer">OpenWeatherMap</a>. Die <em>Weather Integration</em> hat, ungeachtet des gewählten Wetterdatenproviders, ein festes <a href="https://www.home-assistant.io/integrations/openweathermap/"  target="_blank" rel="noreferrer">Condition Mapping</a>:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>State</th>
          <th>Erläuterung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>clear-night</code></td>
          <td>Klare Nacht ohne Wolken</td>
      </tr>
      <tr>
          <td><code>cloudy</code></td>
          <td>Bewölkt</td>
      </tr>
      <tr>
          <td><code>fog</code></td>
          <td>Nebel</td>
      </tr>
      <tr>
          <td><code>hail</code></td>
          <td>Hagel</td>
      </tr>
      <tr>
          <td><code>lightning</code></td>
          <td>Gewitter</td>
      </tr>
      <tr>
          <td><code>lightning-rainy</code></td>
          <td>Gewitter mit Regen</td>
      </tr>
      <tr>
          <td><code>partlycloudy</code></td>
          <td>Teilweise bewölkt</td>
      </tr>
      <tr>
          <td><code>pouring</code></td>
          <td>Starkregen</td>
      </tr>
      <tr>
          <td><code>rainy</code></td>
          <td>Regen</td>
      </tr>
      <tr>
          <td><code>snowy</code></td>
          <td>Schneefall</td>
      </tr>
      <tr>
          <td><code>snowy-rainy</code></td>
          <td>Schneeregen</td>
      </tr>
      <tr>
          <td><code>sunny</code></td>
          <td>Sonnig</td>
      </tr>
      <tr>
          <td><code>windy</code></td>
          <td>Windig</td>
      </tr>
      <tr>
          <td><code>windy-variant</code></td>
          <td>Windig mit Wolken</td>
      </tr>
      <tr>
          <td><code>exceptional</code></td>
          <td>Keine exakt zuordbare Wetterkondition</td>
      </tr>
  </tbody>
</table>
</div>
<p>Damit eine eindeutige Zuordnung der dargestellten Icons zu der aktuellen Wetterlage möglich ist, sollte für jeden State ein passendes Icon auf die Pixel Clock geladen werden, dass genau den Namen des <em>States</em> trägt, bspw. <code>fog.jpg</code> oder <code>lightning-rainy.gif</code>.</p>
<p>Anschließend kann unter Hilfenahme von Templates eine Automation für eine Custom Page zur Anzeige des Wetters erstellt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">notify_pixelclock_weather</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">weather.openweathermap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt.publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">qos</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">retain</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;text&#34;: &#34;{{ states.weather.openweathermap.attributes.temperature | round(0) }} {{ states.weather.openweathermap.attributes.temperature_unit }}&#34;,
</span></span></span><span class="line"><span class="cl"><span class="sd">            &#34;icon&#34;: &#34;{{ states(&#39;weather.openweathermap&#39;) }}&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">          }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">pixelclock/custom/weather</span></span></span></code></pre></div></div>
<p>Die Temperaturwerte werden ganzzahlig gerundet und die Temperatureinheit, bspw. <code>°C</code>, an den Wert angehangen.</p>

<h2 class="relative group">Resources
    <div id="resources" class="anchor"></div>
    
</h2>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>AWTRIX3 Firmware</td>
          <td><a href="https://github.com/Blueforcer/awtrix3/releases"  target="_blank" rel="noreferrer">Github Releases</a></td>
      </tr>
      <tr>
          <td>AWTRIX3 Doku</td>
          <td><a href="https://blueforcer.github.io/awtrix3/#/README"  target="_blank" rel="noreferrer">blueforcer.github.io</a></td>
      </tr>
      <tr>
          <td>LaMetric Icon Gallery</td>
          <td><a href="https://developer.lametric.com/icons"  target="_blank" rel="noreferrer">lametric.com</a></td>
      </tr>
      <tr>
          <td>Weather Condition Mapping</td>
          <td><a href="https://developers.home-assistant.io/docs/core/entity/weather/#recommended-values-for-state-and-condition"  target="_blank" rel="noreferrer">developers.homeassistant.com</a></td>
      </tr>
  </tbody>
</table>
<lite-youtube videoid="N0NKPJzGHuA" playlabel="N0NKPJzGHuA" params=""></lite-youtube>

 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Propertysets</title>
      <link>https://jbetzen.net/posts/ilogic-propertysets/</link>
      <pubDate>Mon, 07 Nov 2022 19:50:27 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-propertysets/</guid>
      <description>Jedes Konstruktionsdokument in Inventor enthält vier Propertysets, die Konstruktionsdaten oder benutzerdefinierte Daten enthalten und auf die Mittels iLogic zugegriffen werden kann. iLogic bietet die Möglichkeit zur Manipulation der Werte.</description>
      <content:encoded><![CDATA[ <p>iProperties sind wie Key-Value-Stores zu betrachten. Ein iProperty hat eine Bezeichnung und einen zugehörigen Wert. Der Wert kann dabei vom Typ String, Boolean, Date oder ein Zahlenwert sein. Grundsätzlich wird zwischen 4 Propertysets unterschieden:</p>
<ol>
<li>Inventor Summary Information</li>
<li>Inventor Document Summary Information</li>
<li>Design Tracking Properties</li>
<li>Inventor User Defined Properties</li>
</ol>
<p>Die ersten 3 Sets sind von Autodesk Definiert. Im konstruktiven Alltag spielt hauptsächlich das 4. Set, die <strong>Inventor User Defined Properties</strong> - auch <strong>Custom Properties</strong> genannt - eine Rolle. Diese sind frei zuweisbar, hängen stets direkt am Konstruktionsdokument und sind essentiell für den Übertrag in Folgesysteme wie ERP.</p>

<h2 class="relative group">Propertysets
    <div id="propertysets" class="anchor"></div>
    
</h2>
<p>Das jeweilige Propertyset kann in einer Datei im Inventor aufgerufen werden, indem man einen Sekundärklick auf das geöffnete Konstruktionsdokument ausführt und den Eintrag <code>iProperties</code> auswählt.</p>
<p>Im Folgenden die einzelnen Propertysets und deren Bezeichnungen:</p>

<h3 class="relative group">Inventor Summary Information
    <div id="inventor-summary-information" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th style="text-align: left">Property Name</th>
          <th style="text-align: left">Type</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Author</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Comments</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Keywords</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Last Saved By</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Thumbnail</td>
          <td style="text-align: left">IPictureDisp</td>
      </tr>
      <tr>
          <td style="text-align: left">Revision Number</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Subject</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Title</td>
          <td style="text-align: left">String</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Inventor Document Summary Information
    <div id="inventor-document-summary-information" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th style="text-align: left">Property Name</th>
          <th style="text-align: left">Type</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Category</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Company</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Manager</td>
          <td style="text-align: left">String</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Design Tracking Properties
    <div id="design-tracking-properties" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th style="text-align: left">Property Name</th>
          <th style="text-align: left">Type</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Authority</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Catalog Web Link</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Categories</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Checked By</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Cost</td>
          <td style="text-align: left">Currency</td>
      </tr>
      <tr>
          <td style="text-align: left">Cost Center</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Creation Time</td>
          <td style="text-align: left">Date</td>
      </tr>
      <tr>
          <td style="text-align: left">Date Checked</td>
          <td style="text-align: left">Date</td>
      </tr>
      <tr>
          <td style="text-align: left">Defer Updates</td>
          <td style="text-align: left">Boolean</td>
      </tr>
      <tr>
          <td style="text-align: left">Description</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Design Status</td>
          <td style="text-align: left">Long</td>
      </tr>
      <tr>
          <td style="text-align: left">Designer</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Document SubType</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Document SubType Name</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Engineer</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Engr Approved By</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Engr Date Approved</td>
          <td style="text-align: left">Date</td>
      </tr>
      <tr>
          <td style="text-align: left">External Property Revision Id</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Language</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Manufacturer</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Material</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Mfg Approved By</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Mfg Date Approved</td>
          <td style="text-align: left">Date</td>
      </tr>
      <tr>
          <td style="text-align: left">Parameterized Template</td>
          <td style="text-align: left">Boolean</td>
      </tr>
      <tr>
          <td style="text-align: left">Part Icon</td>
          <td style="text-align: left">IPictureDisp</td>
      </tr>
      <tr>
          <td style="text-align: left">Part Number</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Part Property Revision Id</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Project</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Proxy Refresh Date</td>
          <td style="text-align: left">Date</td>
      </tr>
      <tr>
          <td style="text-align: left">Size Designation</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Standard</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Standard Revision</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Standards Organization</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Stock Number</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Template Row</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">User Status</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Vendor</td>
          <td style="text-align: left">String</td>
      </tr>
      <tr>
          <td style="text-align: left">Weld Material</td>
          <td style="text-align: left">String</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Inventor User Defined Properties
    <div id="inventor-user-defined-properties" class="anchor"></div>
    
</h3>
<p>Dieses Set an Properties kann frei zugewiesen werden. Ein paar Beispielhafte Einträge:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-propertysets/propset-custom.png"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">iLogic Shortcuts
    <div id="ilogic-shortcuts" class="anchor"></div>
    
</h2>
<p>Grundsätzlich kann über iLogic sehr komfortabel auf iProperties zugegriffen werden. Das Grundschema sieht so aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Set Name in iLogic&#34;</span><span class="p">,</span> <span class="s">&#34;Property Name&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Zu beachten ist, dass auch die Bezeichnungen der Sets eigene Kurzformen haben. Diese lauten:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Set Name</th>
          <th>Set Name in iLogic</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Inventor Summary Information</td>
          <td>Summary</td>
      </tr>
      <tr>
          <td>Inventor Document Summary Information</td>
          <td>Project</td>
      </tr>
      <tr>
          <td>Design Tracking Properties</td>
          <td>Design</td>
      </tr>
      <tr>
          <td>Inventor User Defined Properties</td>
          <td>Custom</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Beispiel
    <div id="beispiel" class="anchor"></div>
    
</h3>
<p>Folgende Properties sollen anhand des Beispiels im folgenden Screenshot ausgelesen und in eine Variable gespeichert werden. Anschließend wird der Wert der Variable über den Logger, der im vorangegangenen Post <a href="/posts/ilogic-logger/">iLogic: Logging</a> vorgestellt wurde, ausgegeben:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Property Name</th>
          <th>Set</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Author</td>
          <td>Summary</td>
          <td>D.Hasselhoff</td>
      </tr>
      <tr>
          <td>Description</td>
          <td>Project</td>
          <td>Ein Schalter, der kippt.</td>
      </tr>
      <tr>
          <td>Partnumber</td>
          <td>Design</td>
          <td>420</td>
      </tr>
      <tr>
          <td>LED</td>
          <td>Custom</td>
          <td>12 Volt</td>
      </tr>
  </tbody>
</table>
</div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-propertysets/propset-example.png"
          alt=""
        />
  
  
  </figure>
<p>Zu sehen ist, dass direkt über den iLogic Shortcut und dem entsprechenden Reiternamen auf das gewünschte Property und dessen Wer zugegriffen werden kann:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">Author</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Summary&#34;</span><span class="p">,</span> <span class="s">&#34;Author&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">Description</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Project&#34;</span><span class="p">,</span> <span class="s">&#34;Description&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">Partnumber</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Status&#34;</span><span class="p">,</span> <span class="s">&#34;Part Number&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">LED</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Custom&#34;</span><span class="p">,</span> <span class="s">&#34;LED&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Author: &#34;</span> <span class="o">&amp;</span> <span class="n">Author</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Description: &#34;</span> <span class="o">&amp;</span> <span class="n">Description</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;Part Number: &#34;</span> <span class="o">&amp;</span> <span class="n">Partnumber</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;LED Version: &#34;</span> <span class="o">&amp;</span> <span class="n">LED</span><span class="p">)</span></span></span></code></pre></div></div>
<p><strong>Ergebnis:</strong></p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-propertysets/propset-example-result.png"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">Links
    <div id="links" class="anchor"></div>
    
</h2>
<ul>
<li><a href="https://modthemachine.typepad.com/my_weblog/2010/02/accessing-iproperties.html"  target="_blank" rel="noreferrer">Mod the machine: Accessing iProperties</a></li>
<li><a href="https://knowledge.autodesk.com/de/support/inventor-products/learn-explore/caas/CloudHelp/cloudhelp/2015/DEU/Inventor-Help/files/GUID-8CAEA21E-A604-4D0A-8F06-3FA5B693CE6B-htm.html"  target="_blank" rel="noreferrer">iProperties-Funktionen in iLogic</a></li>
<li><a href="https://adndevblog.typepad.com/manufacturing/2018/04/accessing-iproperties-through-ilogic-code.html"  target="_blank" rel="noreferrer">Accessing iProperties through iLogic code</a></li>
<li><a href="https://modthemachine.typepad.com/files/ipropertiesandparameters.pdf"  target="_blank" rel="noreferrer">Inventor API: Exploring iProperties and Parameters</a></li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: iLogic, VBA, VB.NET</title>
      <link>https://jbetzen.net/posts/ilogic-010-ilogic-vba-vbnet/</link>
      <pubDate>Thu, 22 Sep 2022 15:44:18 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-010-ilogic-vba-vbnet/</guid>
      <description>Auf der Suche nach iLogic Code findet häufig die Begriffe VBA, VB.NET oder auch einfach iLogic. Doch was ist damit genau gemeint? Was hat iLogic mit VB.NET zu tun und wie unterscheidet es sich von VBA? Wie trennt man diese Begriffe von einander?</description>
      <content:encoded><![CDATA[ 
<h2 class="relative group">VBA - Das Urgestein
    <div id="vba---das-urgestein" class="anchor"></div>
    
</h2>
<p>Im ersten Post dieser Serie <a href="/posts/ilogic-basics/">iLogic: Erste Schritte mit der Inventor API</a> wurde bereits ein VBA-Code vorgestellt. VBA steht für <em>Visual Basic for Applicationss</em> und ist eine Programmiersprache, die aus dem Ökosystem der Microsoft Office Anwendungen entsprungen ist. VBA-Code wird in einem eigens dafür kreierten Editor geschrieben, dem VBE-Editor (Visual Basic Environment). Im ersten Post wurde auch bereits erläutert, dass man mit dem Shortcut <kbd>ALT</kbd> + <kbd>F11</kbd> diesen Editor starten kann. Dies Funktioniert in allen Office Anwendungen aus dem Hause Microsoft.</p>
<p>Mit VBA haben viele Konstrukteure im Studium Berührung. Am Häufigsten wird es im Zusammenspiel mit Microsoft Excel gelehrt und in der Regel sind es freudenlose Aufgabenstellungen für Probleme, die niemand im Arbeitsleben wiederfindet. VBA ist eine alte Programmiersprache aus dem Jahre 1993 und gerade <em>in Puncto</em> Error Handling eine nicht hinnehmbare Zumutung. Das hat auch Microsoft erkannt und die Sprache mit dem letzten Release <em>VBA 6</em> den Tabellenknechten des Strafplaneten Excel überlassen und sich entschlossen fortwährend VB.NET zuzuwenden.</p>

<h2 class="relative group">VB.NET - Die Weiterentwicklung
    <div id="vbnet---die-weiterentwicklung" class="anchor"></div>
    
</h2>
<p>Während VBA auszog, um eine einheitliche Programmierumgebung innerhalb der Office-Anwendungen zu bieten, ist VB.net eine mächtigere Programmiersprache. Sie kann auch außerhalb vom VBA-Editor verwendet und in <em>standalone EXE</em> Files kompiliert werden. VB.net kann VBA gänzlich ersetzen und bietet viele Vorteile und erweiterten Funktionsumfang, bspw. in der Gestaltung von Steuerelementen oder der Handhabung von Fehlern. Ein anschauliches Beispiel aus dem <a href="/posts/ilogic-user-interactions/" >Post über Benutzerinteraktionen</a> sind Messageboxes. VBA bietet lediglich die Möglichkeit einen Titel und einen Messagebody zu senden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Titel&#34;</span><span class="p">,</span> <span class="s">&#34;Messagebody&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>VB.net hingegen erlaubt zusätzlich noch die Definition von Icons, Buttons mit Default-Belegung, etc., wie im vorangegangenen Post <a href="/posts/ilogic-user-interaction/">iLogic: User Interaktion</a> vorgestellt wurde. Der iLogic Editor, der ebenfalls bereits im Post <a href="/posts/ilogic-editor/">iLogic: Der Editor</a> vorgestellt wurde, kann ebenfalls VB.net ausführen. Auch kann beliebig VB.net und iLogic Code in diesem Editor kombiniert werden.</p>

<h2 class="relative group">iLogic - Ein VB.NET Subset
    <div id="ilogic---ein-vbnet-subset" class="anchor"></div>
    
</h2>
<p>Was genau ist also iLogic? iLogic ist ein sogenanntes <em>Subset</em> von VB.net. Eine Sammlung nützlicher spezifischer <em>Shortcuts</em>, die explizit die Handhabung und Funktionalität von Inventor betreffen. Diese Shortcuts bieten erleichtert Zugang zu den Funktionalitäten der Inventor API.</p>
<p>Ein paar Beispiele, die iLogic und die API-Variante gegenüberstellen:</p>

<h3 class="relative group">iProperties
    <div id="iproperties" class="anchor"></div>
    
</h3>
<p>In iLogic kann ein <em>Custom</em> iPoperty mit dem beispielhaften Namen <code>Titel</code> sehr einfach ausgelesen werden.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="c">&#39; iLogic
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">Titel</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">iProperties</span><span class="p">.</span><span class="n">Value</span><span class="p">(</span><span class="s">&#34;Custom&#34;</span><span class="p">,</span> <span class="s">&#34;Titel&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; API Variante
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">Titel</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">PropertySets</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="s">&#34;Inventor User Defined Properties).Item(&#34;</span><span class="n">Blechdicke</span><span class="s">&#34;).Value</span></span></span></code></pre></div></div>

<h3 class="relative group">Zugriff auf Zeichnungsmodelle
    <div id="zugriff-auf-zeichnungsmodelle" class="anchor"></div>
    
</h3>
<p>In iLogic kann einfach auf das Modelldokument einer Zeichnung zugegriffen werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="c">&#39; iLogic
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oModelDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDrawing</span><span class="p">.</span><span class="n">ModelDocument</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; API Variante
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oModelDoc</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ReferencedFiles</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="n">1</span><span class="p">)</span></span></span></code></pre></div></div>

<h2 class="relative group">Links
    <div id="links" class="anchor"></div>
    
</h2>
<ul>
<li><a href="https://software-solutions-online.com/vba-vs-vb-net/"  target="_blank" rel="noreferrer">VBA vs VB.NET</a></li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: User Interaktion</title>
      <link>https://jbetzen.net/posts/ilogic-user-interaction/</link>
      <pubDate>Sun, 18 Sep 2022 10:43:23 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-user-interaction/</guid>
      <description>iLogic bietet die Nutzung grafischer Elemente, die dem User präsentiert werden und abhängig von einer User-Auswahl die Logik des Codes beeinflussen können. In diesem Post werden MsgBox, InputBox, etc. erläutert.</description>
      <content:encoded><![CDATA[ <p>In den vorherigen Posts <a href="/posts/ilogic-logger/">iLogic: Logging</a> und <a href="/posts/ilogic-progressbar/">iLogic: Progressbar</a> wurden zwei Methoden vorgestellt, um dem Anwender Ausgaben zu präsentieren. Mit dem User in Interaktion treten kann man über <em>Logger</em> und <em>Progressbars</em> jedoch nicht. Für interaktive Abfragen stehen weitere grafische Elemente zur Verfügung. Basierend auf einer User-Auswahl, kann somit auch Einfluss auf die Ausführung des Codes einer iLogic genommen werden.</p>

<h2 class="relative group">MessageBox
    <div id="messagebox" class="anchor"></div>
    
</h2>
<p>Eine Messagebox kann mit dem Befehl <code>MessageBox.Show()</code> aufgerufen werden. Die Methode erwartet nur einen Pflichtparameter und zwar den <em>Message Body</em>. Alle anderen Parameter sind optional.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">MessageBox</span><span class="p">.</span><span class="n">Show</span><span class="p">(</span><span class="s">&#34;Message Body&#34;</span><span class="p">,</span> <span class="s">&#34;Title&#34;</span><span class="p">,</span> <span class="n">MessageBoxButtons</span><span class="p">.</span><span class="n">OKCancel</span><span class="p">,</span> <span class="n">MessageBoxIcon</span><span class="p">.</span><span class="n">Information</span><span class="p">,</span> <span class="n">MessageBoxDefaultButton</span><span class="p">.</span><span class="n">Button2</span><span class="p">)</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-user-interaction/messagebox-basic.png"
          alt="Beispiel einer Messagebox"
        />
  
  
  </figure>
<p>Die einzelnen Parameter, in Reihenfolge der Klammeranweisung, erläutert:</p>
<table>
  <thead>
      <tr>
          <th>Parameter</th>
          <th>Erläuterung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Message Body</td>
          <td>Der Text, der in der Messagebox ausgegeben werden soll.</td>
      </tr>
      <tr>
          <td>Title</td>
          <td>Der Titel, der in der Statusbar der Messagebox dargestellt wird.</td>
      </tr>
      <tr>
          <td>MessageBoxButtons</td>
          <td>Die Auswahl, welche Buttons in der Messagebox angezeigt werden sollen.</td>
      </tr>
      <tr>
          <td>MessageBoxIcon</td>
          <td>Die Auswahl, welches Icon in der Messagebox angezeigt werden soll.</td>
      </tr>
      <tr>
          <td>MessageBoxDefaultButton</td>
          <td><p>Die Auswahl, welcher Button als <em>Default</em> aktiviert sein soll, abhängig von der Auswahl <code>MessageBoxButtons</code>.</p><p>Button <kbd>OK</kbd> im obigen Beispiel entspräche <code>Button1</code>. <kbd>Abbrechen</kbd> entspräche <code>Button2</code>.</p></td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Icons
    <div id="icons" class="anchor"></div>
    
</h3>
<p>Es stehen folgende vier Icons zur Auswahl. Einige haben doppelte und dreifache Bezeichnungen. Die fettgedruckten Werte werden für alle Beispiele in folgenden Posts verwendet:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th style="text-align: center">Icon Grafik</th>
          <th>Bezeichnung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-icons/messagebox-asterisk.png"
          alt=""
        />
  
  
  </figure></td>
          <td>Asterisk, <strong>Information</strong></td>
      </tr>
      <tr>
          <td style="text-align: center">
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-icons/messagebox-error.png"
          alt=""
        />
  
  
  </figure></td>
          <td><strong>Error</strong>, Hand, Stop</td>
      </tr>
      <tr>
          <td style="text-align: center">
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-icons/messagebox-exclamation.png"
          alt=""
        />
  
  
  </figure></td>
          <td>Exclamation, <strong>Warning</strong></td>
      </tr>
      <tr>
          <td style="text-align: center">
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-icons/messagebox-question.png"
          alt=""
        />
  
  
  </figure></td>
          <td><strong>Question</strong></td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Buttons
    <div id="buttons" class="anchor"></div>
    
</h3>
<p>Es stehen folgende sechs Button-Darstellungen zur Verfügung:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Button Darstellung</th>
          <th>Bezeichnung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-buttons/btn-abortretryignore.png"
          alt=""
        />
  
  
  </figure></td>
          <td>AbortRetryIgnore</td>
      </tr>
      <tr>
          <td>
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-buttons/btn-ok.png"
          alt=""
        />
  
  
  </figure></td>
          <td>OK</td>
      </tr>
      <tr>
          <td>
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-buttons/btn-okcancel.png"
          alt=""
        />
  
  
  </figure></td>
          <td>OKCancel</td>
      </tr>
      <tr>
          <td>
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-buttons/btn-retrycancel.png"
          alt=""
        />
  
  
  </figure></td>
          <td>RetryCancel</td>
      </tr>
      <tr>
          <td>
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-buttons/btn-yesno.png"
          alt=""
        />
  
  
  </figure></td>
          <td>YesNo</td>
      </tr>
      <tr>
          <td>
<figure>
        <img
          class="my-0 rounded-md"
          src="/posts/ilogic-user-interaction/msgbox-buttons/btn-yesnocancel.png"
          alt=""
        />
  
  
  </figure></td>
          <td>YesNoCancel</td>
      </tr>
  </tbody>
</table>
</div>
<p>Es stehen folgende <a href="https://learn.microsoft.com/de-de/office/vba/language/reference/user-interface-help/msgbox-constants#msgbox-return-values"  target="_blank" rel="noreferrer">Rückgabewerte</a> für das Klicken auf einen Button zur Verfügung. Diese Rückgabewerte können genutzt werden um Entscheidungslogiken im iLogic Code zu steuern:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Rückgabe</th>
          <th>Beschreibung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>vbOK</td>
          <td><kbd>OK</kbd> wurde geklickt</td>
      </tr>
      <tr>
          <td>vbCancel</td>
          <td><kbd>Abbrechen</kbd> wurde geklickt</td>
      </tr>
      <tr>
          <td>vbAbort</td>
          <td><kbd>Beenden</kbd> wurde geklickt</td>
      </tr>
      <tr>
          <td>vbRetry</td>
          <td><kbd>Wiederholen</kbd> wurde geklickt</td>
      </tr>
      <tr>
          <td>vbIgnore</td>
          <td><kbd>Ignorieren</kbd> wurde geklickt</td>
      </tr>
      <tr>
          <td>vbYes</td>
          <td><kbd>Ja</kbd> wurde geklickt</td>
      </tr>
      <tr>
          <td>vbNo</td>
          <td><kbd>Nein</kbd> wurde geklickt</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Interaktion bei Button-Klick
    <div id="interaktion-bei-button-klick" class="anchor"></div>
    
</h3>
<p>Erzeugt man eine Messagebox und fordert den User damit zu einer interaktiven Eingabe auf, kann in iLogic ausgelesen welcher Button geklickt wurde (Rückgabewerte) und daraufhin Code ausführen.</p>
<p>Im folgenden fiktiven Beispiel wird eine Situation aus dem Post <a href="/posts/ilogic-bom/">iLogic: Stücklisten</a> aufgegriffen:</p>
<ol>
<li>Der User bearbeitet eine Baugruppe und vergisst die Strukturstückliste zu aktivieren.</li>
<li>Die ilogic prüft, ob die Strukturstückliste aktiviert ist. Wenn dies nicht der Fall ist, wird eine Messagebox erzeugt.</li>
<li>Klickt der User auf den Button <kbd>OK</kbd> wird die Strukturstückliste aktiviert.</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">bStrucutredViewEnabled</span> <span class="ow">As</span> <span class="kt">Boolean</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">BOM</span><span class="p">.</span><span class="n">StructuredViewEnabled</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">bStrucutredViewEnabled</span> <span class="o">=</span> <span class="k">False</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">question</span> <span class="o">=</span> <span class="n">MessageBox</span><span class="p">.</span><span class="n">Show</span><span class="p">(</span><span class="s">&#34;Die Strukturstuecklistenansicht ist nicht aktiviert. Soll sie aktiviert werden?&#34;</span><span class="p">,</span> <span class="s">&#34;StructuredView aktivieren?&#34;</span><span class="p">,</span> <span class="n">MessageBoxButtons</span><span class="p">.</span><span class="n">OKCancel</span><span class="p">,</span> <span class="n">MessageBoxIcon</span><span class="p">.</span><span class="n">Warning</span><span class="p">,</span> <span class="n">MessageBoxDefaultButton</span><span class="p">.</span><span class="n">Button1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">question</span> <span class="o">=</span> <span class="n">vbOK</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">BOM</span><span class="p">.</span><span class="n">StructuredViewEnabled</span> <span class="o">=</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-user-interaction/messagebox-example-bom.gif"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">InputBox
    <div id="inputbox" class="anchor"></div>
    
</h2>
<p>Eine <em>Inputbox</em> erwarten, wie auch die <em>Messagebox</em>, Eingangsparameter. Diese sind analog ein <em>Message Body</em>, ein Titel und ein Parameter für einen <em>Default</em>-Wert:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">InputBox</span><span class="p">(</span><span class="s">&#34;Message Body&#34;</span><span class="p">,</span> <span class="s">&#34;Title&#34;</span><span class="p">,</span> <span class="s">&#34;Default Entry&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p><strong>Beispiel für die Verwendung einer InputBox:</strong></p>
<ol>
<li>Als Basismodell dient ein einfacher Zylinder mit einer modellierten Fase. Die Fase hat eine Größe von <code>5mm</code> und den Modellparameternamen <code>d3</code>.</li>
<li>Eine InputBox soll erscheinen und den User interaktiv nach der Größe der Fase fragen.</li>
<li>Nach der Eingabe eines Größenwerts, wird die Eingabe an den Parameter <code>d3</code> übergeben.</li>
<li>Das Modell wird anschließend neu aufgebaut, um die Änderung der Fasengröße darzustellen.</li>
</ol>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-user-interaction/inputbox-chamfer-example.png"
          alt=""
        />
  
  
  </figure>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">Parameter</span><span class="p">(</span><span class="s">&#34;d3&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="n">InputBox</span><span class="p">(</span><span class="s">&#34;Wähle die Dimension der Fase.&#34;</span><span class="p">,</span> <span class="s">&#34;Fase&#34;</span><span class="p">,</span> <span class="n">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">Rebuild</span><span class="p">()</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-user-interaction/inputbox-chamfer-example.gif"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">InputListBox
    <div id="inputlistbox" class="anchor"></div>
    
</h2>
<p><em>InputListBox</em> ist quasi gleichzusetzen mit der <em>InputBox</em>, jedoch bietet sie eine vordefinierte Liste aus auswählbaren Einträgen an.</p>
<p><strong>Beispiel für die Verwendung einer <em>InputListBox</em>:</strong><br>
Ein liebevoll modelliertes Toastbrot erhält ein Prägungs-Feature, das stellvertretend für den Aufschnitt steht. Eine Prägung (<em>Emboss</em>) erfordert eine Skizze - in diesem Fall der Textblock - und eine Fläche auf dem diese platziert wird. Der Textwert des Aufschnitts ist in der API unter <code>ComponentDefinition.Sketches</code> zu finden. In meinem Fall war der Endpunkt unter <code>ComponentDefinition.Sketches.Item(2).TextBoxes.Item(1).Text</code> zu erreichen. Es wird eine Liste mit verfügbaren Aufschnitten erzeugt. Anschließend wird die <em>InputListBox</em> aufgerufen und die Auswahl in einen String gewandelt, da sonst nur ein Index zurückgegeben würde. Die Auswahl wird anschließend an die Prägungsskizze übergeben. Da es sich, wie schon im Beispiel zuvor, um eine Änderung der Modellgeometrie handelt muss als letzter Befehl <code>ThisDoc.Document.Rebuild()</code> gesendet werden, um das Modell neu aufzubauen.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oSketches</span> <span class="ow">As</span> <span class="n">PlanarSketches</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">Sketches</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">listAufschnitt</span> <span class="o">=</span> <span class="k">New</span> <span class="kt">String</span><span class="p">(){</span><span class="s">&#34;Wurst&#34;</span><span class="p">,</span> <span class="s">&#34;Käse&#34;</span><span class="p">,</span> <span class="s">&#34;Mett&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">aufschnitt</span> <span class="o">=</span> <span class="n">InputListBox</span><span class="p">(</span><span class="s">&#34;Wählen Sie!&#34;</span><span class="p">,</span> <span class="n">listAufschnitt</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oSketches</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="n">TextBoxes</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="n">Text</span> <span class="o">=</span> <span class="n">aufschnitt</span><span class="p">.</span><span class="n">ToString</span>
</span></span><span class="line"><span class="cl"><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">Rebuild</span><span class="p">()</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-user-interaction/inputlistbox-example-toast.gif"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">InputRadioBox
    <div id="inputradiobox" class="anchor"></div>
    
</h2>
<p>Eine <em>InputRadioBox</em> bietet die Möglichkeit aus zwei gelisteten Werten zu entscheiden. Die Grundstruktur ist einfach beschrieben. Geliefert wird ein <em>bool&rsquo;scher</em> Rückgabewert. Der Parameter <code>True</code> besagt, ob gegen die erste (<code>True</code>) oder zweite (<code>False</code>) Auswahlmöglichkeit geprüft wird.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">bResult</span> <span class="o">=</span> <span class="n">InputRadioBox</span><span class="p">(</span><span class="s">&#34;Prompt&#34;</span><span class="p">,</span> <span class="s">&#34;Button1 Label&#34;</span><span class="p">,</span> <span class="s">&#34;Button2 Label&#34;</span><span class="p">,</span> <span class="k">True</span><span class="p">,</span> <span class="n">Title</span> <span class="p">:</span><span class="o">=</span><span class="s">&#34;Title&#34;</span><span class="p">)</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-user-interaction/inputradiobox-basic.png"
          alt="Beispiel InputRadioBox"
        />
  
  
  </figure>
<p><strong>Ein Beispiel für die Verwendung einer InputRadioBox:</strong><br>
Ein modellierter Donut hat zwei Modellparameter:  <strong>Donutgrösse:</strong>, also der Durchmesser des Donuts und <strong>Teigquerschnitt:</strong>, also Durchmesser der Teigfläche.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-user-interaction/inputradiobox-example-donut-params.png"
          alt=""
        />
  
  
  </figure>
<p>Es wird eine <em>InputRadioBox</em> ausgegeben, die erfragt wie groß der Hunger ist. Es besteht die Auswahl zwischen einem kleinen und einem großen Donut. Basierend auf der Wahl werden die Parameter <em>Donutgröße</em> und <em>Teigquerschnitt</em> angepasst und das Modell neu aufgebaut:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">bResult</span> <span class="o">=</span> <span class="n">InputRadioBox</span><span class="p">(</span><span class="s">&#34;Wie groß ist der Hunger?&#34;</span><span class="p">,</span> <span class="s">&#34;Kleiner Donut!&#34;</span><span class="p">,</span> <span class="s">&#34;Großer Donut!&#34;</span><span class="p">,</span> <span class="k">True</span><span class="p">,</span> <span class="n">Title</span> <span class="p">:</span><span class="o">=</span><span class="s">&#34;Wähle einen Donut&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">bResult</span> <span class="o">=</span> <span class="k">True</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">Parameter</span><span class="p">(</span><span class="s">&#34;Donutgrösse&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="s">&#34;50mm&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">Parameter</span><span class="p">(</span><span class="s">&#34;Teigquerschnitt&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="s">&#34;30mm&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">Else</span>
</span></span><span class="line"><span class="cl">    <span class="n">Parameter</span><span class="p">(</span><span class="s">&#34;Donutgrösse&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="s">&#34;80mm&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">Parameter</span><span class="p">(</span><span class="s">&#34;Teigquerschnitt&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="s">&#34;50mm&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">Rebuild</span><span class="p">()</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-user-interaction/inputradiobox-example-donut.gif"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">Links
    <div id="links" class="anchor"></div>
    
</h2>
<ul>
<li><a href="https://knowledge.autodesk.com/support/inventor-products/learn-explore/caas/CloudHelp/cloudhelp/2014/ENU/Inventor/files/GUID-1A217E8E-FE3A-4E84-9926-8E11C81D60D9-htm.html"  target="_blank" rel="noreferrer">Message Box functions in iLogic reference</a></li>
<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.messageboxicon?redirectedfrom=MSDN&amp;view=windowsdesktop-6.0#remarks"  target="_blank" rel="noreferrer">Messagebox Icons</a></li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Progressbar</title>
      <link>https://jbetzen.net/posts/ilogic-progressbar/</link>
      <pubDate>Sat, 17 Sep 2022 10:42:49 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-progressbar/</guid>
      <description>Fortschrittsanzeigen (Progressbars) können als eigenständige Fenster oder in der Statusleiste von Inventors ausgegeben werden und den User darüber informieren, wie viel Zeit oder Schritte eine Operation in Anspruch nimmt.</description>
      <content:encoded><![CDATA[ <p>Im vorherigen Post <a href="/posts/ilogic-logger/">iLogic: Logging</a> wurde bereits die <em>iLogic Log</em>-Funktion vorgestellt, die <em>Log Statements</em> in einem eigenen Tab ausgeben kann. Mit der <em>Progressbar</em> steht eine weitere Möglichkeit zur Verfügung. <em>Progressbars</em> nutzen kein Dokumentenobjekt, sondern direkt Inventor und werden entweder unten Links im Hauptfenster angezeigt oder als eigenständiges Fenster ausgegeben.</p>

<h2 class="relative group">Anatomie einer Progressbar
    <div id="anatomie-einer-progressbar" class="anchor"></div>
    
</h2>
<p>Progressbars müssen eingangs deklariert werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oProgressBar</span> <span class="ow">As</span> <span class="n">Inventor</span><span class="p">.</span><span class="n">ProgressBar</span> <span class="o">=</span> <span class="n">ThisApplication</span><span class="p">.</span><span class="n">CreateProgressBar</span><span class="p">(</span><span class="k">True</span><span class="p">,</span> <span class="n">10</span><span class="p">,</span> <span class="s">&#34;Titel&#34;</span><span class="p">,)</span></span></span></code></pre></div></div>
<p>Es werden 3 Pflicht-Parameter erwartet. Zusätzlich stehen 2 weitere optionale Anweisungen zur Verfügung. Die optionalen angaben sind in der Tabelle mit einem <code>*</code> gekennzeichnet. In ihrer Reihenfolge in der Klammerangabe lauten diese:</p>
<table>
  <thead>
      <tr>
          <th>Parameter</th>
          <th>Typ</th>
          <th>Erläuterung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>Boolean</td>
          <td>Dieser Wert gibt an, ob die Ausgabe in der Statusbar (<code>True</code>) oder in einem eigenen dedizierten Fenster (<code>False</code>) erfolgen soll.</td>
      </tr>
      <tr>
          <td>2</td>
          <td>Integer</td>
          <td>Anzahl der Schritte, die durchlaufen werden sollen.</td>
      </tr>
      <tr>
          <td>3</td>
          <td>String</td>
          <td>Der Titel der Progressbar</td>
      </tr>
      <tr>
          <td>4*</td>
          <td>Boolean</td>
          <td>Optionale Angabe, ob ein <em>Cancel</em>-Button dargestellt werden soll (<code>True</code>) oder ob dieser entfallen soll (<code>True</code>).</td>
      </tr>
      <tr>
          <td>5*</td>
          <td>Integer</td>
          <td>Eine Referenz auf das Dialogfeld, die die Progressbar ausgelöst hat. Funktioniert nur, wenn Parameter 1 auf <code>False</code> gesetzt wird.</td>
      </tr>
  </tbody>
</table>

<h2 class="relative group">Progressbar als eigenes Fenster
    <div id="progressbar-als-eigenes-fenster" class="anchor"></div>
    
</h2>
<p>Ein gutes Beispiel für eine <em>Progressbar</em> in dediziertem Fenster liefert Autodesk auf der eigenen <a href="https://knowledge.autodesk.com/support/inventor/learn-explore/caas/simplecontent/content/create-use-progressbar-within-ilogic-rule.html"  target="_blank" rel="noreferrer">Homepage</a>. Das Beispiel von Autodesk nutzt <em>Threading</em> um eine Pause von einer Sekunde zwischen den einzelnen Fortschrittsschritten zu erzielen. Es werden 10 Schritte für einen vollen Fortschritt definiert und diese, beginnend beim Wert 1, durchiteriert:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">iSteps</span> <span class="ow">As</span> <span class="kt">Integer</span> <span class="o">=</span> <span class="n">10</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oProgressBar</span> <span class="ow">As</span> <span class="n">Inventor</span><span class="p">.</span><span class="n">ProgressBar</span> <span class="o">=</span> <span class="n">ThisApplication</span><span class="p">.</span><span class="n">CreateProgressBar</span><span class="p">(</span><span class="k">False</span><span class="p">,</span> <span class="n">iSteps</span><span class="p">,</span> <span class="s">&#34;Beispiel: Progressbar&#34;</span><span class="p">,</span> <span class="k">True</span><span class="p">,)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Startmeldung definieren
</span></span></span><span class="line"><span class="cl"><span class="n">oProgressBar</span><span class="p">.</span><span class="n">Message</span> <span class="o">=</span> <span class="s">&#34;Starting a process...&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="n">iStep</span> <span class="ow">As</span> <span class="kt">Integer</span> <span class="o">=</span> <span class="n">1</span> <span class="k">To</span> <span class="n">iSteps</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Beschreibung oder Update der Beschreibung des aktuellen Schritts
</span></span></span><span class="line"><span class="cl">    <span class="n">oProgressBar</span><span class="p">.</span><span class="n">Message</span> <span class="o">=</span> <span class="s">&#34;Processing Step &#34;</span> <span class="o">&amp;</span> <span class="n">iStep</span> <span class="o">&amp;</span> <span class="s">&#34; of &#34;</span> <span class="o">&amp;</span> <span class="n">iSteps</span>
</span></span><span class="line"><span class="cl">    <span class="n">System</span><span class="p">.</span><span class="n">Threading</span><span class="p">.</span><span class="n">Thread</span><span class="p">.</span><span class="n">Sleep</span><span class="p">(</span><span class="k">New</span> <span class="n">TimeSpan</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="n">0</span><span class="p">,</span><span class="n">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Progressbar-Fortschritt updaten
</span></span></span><span class="line"><span class="cl">    <span class="n">oProgressBar</span><span class="p">.</span><span class="n">UpdateProgress</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oProgressBar</span><span class="p">.</span><span class="n">Message</span> <span class="o">=</span> <span class="s">&#34;Processing Finished&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Nachdem alle Schritte erfolgt sind, wird die Progress Bar geschlossen
</span></span></span><span class="line"><span class="cl"><span class="n">oProgressBar</span><span class="p">.</span><span class="n">Close</span></span></span></code></pre></div></div>
<p><strong>Das Ergebnis:</strong></p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-progressbar/progressbar.gif"
          alt="Beispiel Progressbar"
        />
  
  
  </figure>

<h2 class="relative group">Progressbar in Statusleiste
    <div id="progressbar-in-statusleiste" class="anchor"></div>
    
</h2>
<p>Das gleiche Beispiel kann auch verwendet werden, um eine <em>Progressbar</em> unten am Inventor-Fenster anzuzeigen. Es muss lediglich die Code-Zeile angepasst werden, die die Progressbar initialisiert.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">iSteps</span> <span class="ow">As</span> <span class="kt">Integer</span> <span class="o">=</span> <span class="n">10</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oProgressBar</span> <span class="ow">As</span> <span class="n">Inventor</span><span class="p">.</span><span class="n">ProgressBar</span> <span class="o">=</span> <span class="n">ThisApplication</span><span class="p">.</span><span class="n">CreateProgressBar</span><span class="p">(</span><span class="k">True</span><span class="p">,</span> <span class="n">iSteps</span><span class="p">,</span> <span class="s">&#34;Beispiel: Progressbar&#34;</span><span class="p">,</span> <span class="k">True</span><span class="p">,)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Startmeldung definieren
</span></span></span><span class="line"><span class="cl"><span class="n">oProgressBar</span><span class="p">.</span><span class="n">Message</span> <span class="o">=</span> <span class="s">&#34;Starting a process...&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">For</span> <span class="n">iStep</span> <span class="ow">As</span> <span class="kt">Integer</span> <span class="o">=</span> <span class="n">1</span> <span class="k">To</span> <span class="n">iSteps</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Beschreibung oder Update der Beschreibung des aktuellen Schritts
</span></span></span><span class="line"><span class="cl">    <span class="n">oProgressBar</span><span class="p">.</span><span class="n">Message</span> <span class="o">=</span> <span class="s">&#34;Processing Step &#34;</span> <span class="o">&amp;</span> <span class="n">iStep</span> <span class="o">&amp;</span> <span class="s">&#34; of &#34;</span> <span class="o">&amp;</span> <span class="n">iSteps</span>
</span></span><span class="line"><span class="cl">    <span class="n">System</span><span class="p">.</span><span class="n">Threading</span><span class="p">.</span><span class="n">Thread</span><span class="p">.</span><span class="n">Sleep</span><span class="p">(</span><span class="k">New</span> <span class="n">TimeSpan</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="n">0</span><span class="p">,</span><span class="n">1</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="c">&#39; Progressbar-Fortschritt updaten
</span></span></span><span class="line"><span class="cl">    <span class="n">oProgressBar</span><span class="p">.</span><span class="n">UpdateProgress</span>
</span></span><span class="line"><span class="cl"><span class="k">Next</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oProgressBar</span><span class="p">.</span><span class="n">Message</span> <span class="o">=</span> <span class="s">&#34;Processing Finished&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Nachdem alle Schritte erfolgt sind, wird die Progress Bar geschlossen
</span></span></span><span class="line"><span class="cl"><span class="n">oProgressBar</span><span class="p">.</span><span class="n">Close</span></span></span></code></pre></div></div>
<p><strong>Das Ergebnis:</strong></p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-progressbar/progressbar-statusbar.gif"
          alt="Beispiel Progressbar Statusbar"
        />
  
  
  </figure>

<h2 class="relative group">Links
    <div id="links" class="anchor"></div>
    
</h2>
<ul>
<li><a href="https://clintbrown.co.uk/2019/02/22/progressbar/"  target="_blank" rel="noreferrer">iLogic: Hacking the Progress Bar</a></li>
<li><a href="https://knowledge.autodesk.com/support/inventor/learn-explore/caas/simplecontent/content/create-use-progressbar-within-ilogic-rule.html"  target="_blank" rel="noreferrer">Create &amp; Use A ProgressBar Within An iLogic Rule</a></li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Logging</title>
      <link>https://jbetzen.net/posts/ilogic-logger/</link>
      <pubDate>Fri, 16 Sep 2022 23:57:35 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-logger/</guid>
      <description>Inventor stellt mit dem iLogic Log ein nützliches Werkzeug bereit, das bei Code Ausführung Log-Messages ausgeben und das Debuggen von iLogic Code erleichtern kann. Dieser Post zeigt Beispiele, wie man den Log und die verschiedenen Log Level verwenden kann.</description>
      <content:encoded><![CDATA[ <p>Neben dem Modellbrowser stellt Inventor einen eigenen Tab zur Einsicht des <em>iLogic Log</em> bereit. In dieser Umgebung können <em>Log Statements</em> während der Ausführung von einer iLogic eingesehen werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-logger/ilogiclog.png"
          alt="iLogic Log Fenster aktivieren"
        />
  
  
  </figure>

<h2 class="relative group">Logger Settings
    <div id="logger-settings" class="anchor"></div>
    
</h2>
<p>Logging Funktionen unterstützen naturgemäß verschiedene Level. Es gibt Logstatements, die lediglich zur Information dienen, aber auch kritischere Statements, die ggf, die Aufmerksam und das Eingreifen des Users erfordern. Unter <code>Tools &gt; Options &gt; iLogic Configuration</code> kann der <em>Log Level</em> definiert werden. Die Einträge sind hierarchisch zu betrachten und loggen stets nur alle <em>Level</em>, die <strong>unter</strong> dem gewählten Levels verortet sind.</p>
<p><strong>Die verfügbaren Log Level lauten:</strong></p>
<ul>
<li><code>Trace</code></li>
<li><code>Debug</code></li>
<li><code>Info</code></li>
<li><code>Warn</code></li>
<li><code>Error</code></li>
<li><code>Fatal</code></li>
<li><code>None</code></li>
</ul>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-logger/ilogiclog-settings.png"
          alt="Einstellungen für den iLogic Log"
        />
  
  
  </figure>
<dl>
<dt><strong>Beispiel:</strong></dt>
<dd>Wählt man den <em>Log Level</em> <code>Info</code> aus, erscheinen <strong>keine</strong> Meldungen mit dem <em>Level</em> <code>Debug</code>und <code>Trace</code>. Es erscheinen lediglich Logs zu den Leveln <code>Info</code>, <code>Warn</code>, <code>Error</code>, <code>Fatal</code>und <code>None</code>.</dd>
</dl>

<h2 class="relative group">Log Statement erzeugen
    <div id="log-statement-erzeugen" class="anchor"></div>
    
</h2>
<p>Welchen <em>Log Level</em> einem <em>Log Statement</em> zugewiesen wird, legt der Nutzer in seinem iLogic Code selber fest. Um ein einfaches Log Statement zu erzeugen reicht folgendes Kommando:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="s">&#34;This is an info message shown in the logger.&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Natürlich können auch Variablen in Logstatements integriert werden. Auch bietet iLogic ein nützliches <em>Subset</em> an Funktionen, die ein Debuggen erleichtern können. Zwei dieser Funktionen sollen hiermit vorgestellt werden:</p>
<dl>
<dt><code>ThisDoc.Filename()</code></dt>
<dd>Gibt den Dateinamen aus, in dem der iLogic Code ausgeführt wird. Setzt man in den Klammern den Wert <code>True</code>, wird der gesamte Dateiname inkl. Dateiendung ausgegeben. Setzt man den Wert <code>False</code> wird auf die Dateiendung verzichtet.</dd>
<dt><code>iLogicVb.RuleName</code></dt>
<dd>Gibt den Namen der iLogic aus, die ausgeführt wird.</dd>
</dl>
<p>Kombiniert man diese beiden Funktionen und setzt diese direkt als erste Zeile in eine iLogic, kann man dem Logger entnehmen, ob die iLogic ausgeführt wurde und in welchem Dokument sie ausgeführt wurde. Für das folgende Beispiel wurde eine iLogic mit dem Dateinamen <code>00_DebugAndTesting.vb</code> erstellt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="n">Logger</span><span class="p">.</span><span class="n">Info</span><span class="p">(</span><span class="n">iLogicVb</span><span class="p">.</span><span class="n">RuleName</span> <span class="o">&amp;</span> <span class="s">&#34;| &#34;</span> <span class="o">&amp;</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">FileName</span><span class="p">(</span><span class="k">True</span><span class="p">)</span> <span class="o">&amp;</span> <span class="s">&#34; | iLOGIC TRIGGERED&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p><strong>Das Ergebnis:</strong>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-logger/log-example.png"
          alt="Beispiel Log Output"
        />
  
  
  </figure></p>

<h2 class="relative group">Links
    <div id="links" class="anchor"></div>
    
</h2>
<ul>
<li><a href="https://knowledge.autodesk.com/support/inventor-products/learn-explore/caas/CloudHelp/cloudhelp/2014/ENU/Inventor/files/GUID-1A217E8E-FE3A-4E84-9926-8E11C81D60D9-htm.html"  target="_blank" rel="noreferrer">Message Box functions in iLogic reference</a></li>
<li><a href="https://inventorfaq.blogspot.com/2018/09/inventor-20191-logging-in-ilogic-ilogic.html"  target="_blank" rel="noreferrer">Inventor 2019.1: Logging in iLogic (iLogic-Protokoll)</a></li>
<li><a href="https://knowledge.autodesk.com/support/inventor/learn-explore/caas/CloudHelp/cloudhelp/2019/ENU/Inventor-iLogic/files/GUID-BE996AA3-DE49-4765-86A7-129DD7B42A4A-htm.html"  target="_blank" rel="noreferrer">To Create Log Statements</a></li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Stücklisten</title>
      <link>https://jbetzen.net/posts/ilogic-bom/</link>
      <pubDate>Wed, 14 Sep 2022 14:02:41 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-bom/</guid>
      <description>Stücklistenstrukturen spielen sowohl in Bauteilen, als auch Baugruppen eine elementare Rolle. Baugruppen haben als übergeordnete Bauteilsammler zusätzlich noch Stücklistenansichten, die essentiell für nachfolgende ERP-Systeme sind. Dieser Post behandelt, wie Stücklistenstrukturen und Stücklistenansichten mittels der API definiert, aktiviert oder deaktiviert werden können.</description>
      <content:encoded><![CDATA[ <p>Im vorherigen Post <a href="/posts/ilogic-uom/">iLogic: Units of Measurement</a> wurde bereits auf die Dokumenteneinstellungen zu Maßeinheiten innerhalb von Dokumenten eingegangen. In diesem Post geht es um die Stücklisteneinstellungen, die ebenfalls Teil der Dokumenteneinstellungen sind.</p>

<h2 class="relative group">Bauteile und Baugruppen
    <div id="bauteile-und-baugruppen" class="anchor"></div>
    
</h2>
<p>Sowohl in Bauteilen, als auch Baugruppen, kann ein <a href="https://knowledge.autodesk.com/support/inventor/learn-explore/caas/CloudHelp/cloudhelp/2021/ENU/Inventor-Help/files/GUID-A0A95805-CBF6-4524-8314-4AC8B53C19CF-htm.html"  target="_blank" rel="noreferrer">Stücklistenstruktur</a> vorgegeben werden, die Einfluss darauf hat wie diese Bauteile in übergeordneten Baugruppen aufgelöst werden. Baugruppen selber können in dieser Einstellung ihre eigene Stücklistenauflösung definieren.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-bom/bom-doc-settings.gif"
          alt=""
        />
  
  
  </figure>

<h3 class="relative group">Aktuelle Stücklistenstruktur auslesen
    <div id="aktuelle-stücklistenstruktur-auslesen" class="anchor"></div>
    
</h3>
<p>In der API steht die aktuell zugewiesene Stücklistenstruktur unter <code>ComponentDefinition.BOMStructure</code> zur Verfügung:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-bom/compdef-bom-structure.png"
          alt=""
        />
  
  
  </figure>
<p>Umn die aktuell zugewiesene Stücklistenstruktur auszulesen muss zuerst verstanden werden, wie die API und im speziellen der sogenannte <a href="https://help.autodesk.com/view/INVNTOR/2022/ENU/?guid=BOMStructureEnum"  target="_blank" rel="noreferrer"><code>BOMStructureEnum</code></a> funktioniert. Er liefert standardmäßig keinen sprechenden Namen zurück, wie bspw. <em>Phantom</em> order <em>Purchased</em>, sondern eine repräsentative 5-stellige Ziffernfolge:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Name GUI</th>
          <th>BOMStructureEnum</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Normal</td>
          <td>kNormalBOMStructure</td>
          <td>51970</td>
      </tr>
      <tr>
          <td>Phantom</td>
          <td>kPhantomBOMStructure</td>
          <td>51971</td>
      </tr>
      <tr>
          <td>Reference</td>
          <td>kReferenceBOMStructure</td>
          <td>51972</td>
      </tr>
      <tr>
          <td>Purchased</td>
          <td>kPurchasedBOMStructure</td>
          <td>51973</td>
      </tr>
      <tr>
          <td>Inseparable</td>
          <td>kInseparableBOMStructure</td>
          <td>51974</td>
      </tr>
      <tr>
          <td>-</td>
          <td>kDefaultBOMStructure</td>
          <td>51969</td>
      </tr>
      <tr>
          <td>-</td>
          <td>kVariesBOMStructure</td>
          <td>51975</td>
      </tr>
  </tbody>
</table>
</div>
<p>Diese Ziffernfolge kann in einer weiteren Variablen und der Methode <code>ToString</code> in ihren sprechenden Namen, bspw. <code>kNormalBOMStructure</code> umgewandelt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">currentBOMStructure</span> <span class="ow">As</span> <span class="n">BOMStructureEnum</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">BOMStructure</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">currentBOMStructureAsString</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">currentBOMStructure</span><span class="p">.</span><span class="n">ToString</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;BOM Structure Value is: &#34;</span> <span class="o">&amp;</span> <span class="n">currentBOMStructure</span> _
</span></span><span class="line"><span class="cl">        <span class="o">&amp;</span> <span class="n">vbLf</span> <span class="o">&amp;</span> _
</span></span><span class="line"><span class="cl">        <span class="s">&#34;BOM Structure String is: &#34;</span> <span class="o">&amp;</span> <span class="n">currentBOMStructureAsString</span><span class="p">,</span> _
</span></span><span class="line"><span class="cl">        <span class="n">MessageBoxButtons</span><span class="p">.</span><span class="n">OK</span><span class="p">,</span> _
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Current BOM Structure&#34;</span><span class="p">)</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-bom/msgbox-current-bom-structure.png"
          alt=""
        />
  
  
  </figure>
<p><strong>Tipp:</strong> Lange Code-Befehle (<em>Spaghetti Code</em>), wie bspw. die <code>MsgBox()</code>-Anweisung, können mit einem <em>Underscore</em> (<code>_</code>) in eine neue Zeile umgebrochen werden und so zur Lesbarkeit beitragen. Weiterführendes zum Thema <em>Messageboxes</em> ist im Post <a href="/posts/ilogic-user-interaction/">iLogic: User Interaktion</a> der Serie zu finden.</p>

<h3 class="relative group">Stücklistenstruktur im Dokument ändern
    <div id="stücklistenstruktur-im-dokument-ändern" class="anchor"></div>
    
</h3>
<p>Auch ein manipulieren und überschreiben der aktuellen Stücklistenstruktur ist mit dem <em>BOMStructureEnum</em> leicht möglich. Möchte man die Stückliste bspw. auf <em>Purchased</em> setzen, kann dies so erfolgen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">BOMStructure</span> <span class="o">=</span> <span class="n">BOMStructureEnum</span><span class="p">.</span><span class="n">kPurchasedBOMStructure</span></span></span></code></pre></div></div>

<h2 class="relative group">Stücklistenansichten in Baugruppen
    <div id="stücklistenansichten-in-baugruppen" class="anchor"></div>
    
</h2>
<p>Neben den Dokumenteneinstellungen zur Stückliste haben Baugruppen noch eine weitere Stücklistenfunktion: Stücklistenansichten. Es gibt 3 an der Zahl:</p>
<dl>
<dt><strong>Model Data:</strong></dt>
<dd>Ist immer aktiv und kann nicht beeinflusst werden.</dd>
<dt><strong>Structured:</strong></dt>
<dd>Die Strukturstückliste, die sämtliche Stücklisteneinstellungen der Unterkomponenten aus ihren den Dokumenteneigenschaften widerspiegelt. Sie ist besonders wichtig für nachfolgende Systeme, wie bspw. <a href="https://www.autodesk.de/products/fusion-360-manage-with-upchain/overview?term=1-YEAR&amp;tab=subscription"  target="_blank" rel="noreferrer">Fusion 360 Manage</a>. Ist sie nicht aktiv, werden <em>keine</em> Stücklistenstrukturen ins PLM übertragen.</dd>
<dt><strong>Parts Only:</strong></dt>
<dd>Die reine Bauteilstückliste. Im Kern ist sie gleichzusetzen mit der Strukturstückliste, jedoch listet sie ausschließlich die stücklistenrelevanten Bauteile und das ohne Baugruppenebenen.</dd>
</dl>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-bom/bom-assembly.png"
          alt=""
        />
  
  
  </figure>

<h3 class="relative group">Stücklistenansichten aktivieren
    <div id="stücklistenansichten-aktivieren" class="anchor"></div>
    
</h3>
<p>Die API stellt Informationen zu den Stücklistenansichten unter <code>ComponentDefinitions.BOM</code> bereit. Darin befinden sich auch zwei Endpunkte vom Typ <code>Boolean</code> mit den Namen <code>PartsOnlyViewEnabled</code> und <code>StructuredViewEnabled</code>, die Auskunft darüber geben ob diese aktiviert oder deaktiviert sind:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-bom/bom-views-api.png"
          alt=""
        />
  
  
  </figure>
<p>Um nun die Stücklistenansichten zu aktivieren, muss lediglich der Wert <code>True</code> an diesen Endpunkte gesetzt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">BOM</span><span class="p">.</span><span class="n">PartsOnlyViewEnabled</span> <span class="o">=</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl"><span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">BOM</span><span class="p">.</span><span class="n">StructuredViewEnabled</span> <span class="o">=</span> <span class="k">True</span></span></span></code></pre></div></div>
<p>Zu beachten ist, dass eine Baugruppe in der Detailgenauigkeit <code>Master</code>, bzw. im Model State <code>Primary</code> geladen sein muss, um die Stückliste(n) zu aktivieren:</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Units of Measurement</title>
      <link>https://jbetzen.net/posts/ilogic-uom/</link>
      <pubDate>Fri, 09 Sep 2022 23:29:58 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-uom/</guid>
      <description>Ungeachtet der in Dokumenten festgelegten Maßeinheiten, rechnet Inventor intern mit einem vordefinierten Satz an Basiseinheiten, die Default Database Units genannt werden. Wie man diese auslesen und konvertieren kann, wird in diesem Post erläutert.</description>
      <content:encoded><![CDATA[ <p>Jedes Dokument hat in Autodesk Inventor in seinen Dokumenteneinstellungen ein benutzerdefiniertes Set an Maßeinheiten (<em>Units of Measure</em>, kurz <em>UOM</em>), sowie Angaben zu deren Präzision. Werden bspw. <em>Inch</em> oder <em>Millimeter</em> für Längenangaben genutzt, werden der <em>Radiant</em> oder das <em>Gradmaß</em> verwendet oder Gewichtsangaben in <em>Gramm</em> oder <em>Kilogramm</em> angegeben. Diese Angaben dienen aber nur dem Nutzer, der die grafische Oberfläche von Inventor verwendet. Nutzt man bspw. die API um eine Länge von einem Bauteil zu berechnen, rechnet Inventor immer mit der internen Maßeinheit <em>Zentimeter</em>. Dies gilt es zu berücksichtigen, wenn man diese Werte ausliest und ggf. weiterverarbeitet. Die internet Bezeichnung für den Satz an Maßeinheiten der API lautet <em>DatabaseUnits</em>.</p>

<h2 class="relative group">Internal DatabaseUnits
    <div id="internal-databaseunits" class="anchor"></div>
    
</h2>
<p>Inventor nutzt zur internen Berechnung die folgenden Maßeinheiten. Sie werden <em>Internal Database Units (IDU)</em> genannt:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Messtyp</th>
          <th>IDU</th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Masse</td>
          <td>kg</td>
          <td>Kilogramm</td>
      </tr>
      <tr>
          <td>Länge / Distanz</td>
          <td>cm</td>
          <td>Zentimeter</td>
      </tr>
      <tr>
          <td>Zeit</td>
          <td>s</td>
          <td>Sekunden</td>
      </tr>
      <tr>
          <td>Temperatur</td>
          <td>K</td>
          <td>Kelvin</td>
      </tr>
      <tr>
          <td>Winkel</td>
          <td>rad</td>
          <td>Radiant</td>
      </tr>
      <tr>
          <td>Kraft</td>
          <td>N</td>
          <td>Newton</td>
      </tr>
      <tr>
          <td>Moment / Drehmoment</td>
          <td>Ncm</td>
          <td>Newton-Zentimeter</td>
      </tr>
      <tr>
          <td>Druck / Spannung</td>
          <td>N/cm²</td>
          <td>Newton pro Quadratzentimeter</td>
      </tr>
      <tr>
          <td>Energie</td>
          <td>J</td>
          <td>Joule</td>
      </tr>
      <tr>
          <td>Leistung</td>
          <td>W</td>
          <td>Watt</td>
      </tr>
      <tr>
          <td>Dichte</td>
          <td>g/cm³</td>
          <td>Gramm pro Kubikzentimeter</td>
      </tr>
      <tr>
          <td>Volumen</td>
          <td>cm³</td>
          <td>Kubikzentimeter</td>
      </tr>
      <tr>
          <td>Fläche</td>
          <td>cm²</td>
          <td>Quadratzentimeter</td>
      </tr>
      <tr>
          <td>Geschwindigkeit</td>
          <td>cm/s</td>
          <td>Zentimeter pro Sekunde</td>
      </tr>
      <tr>
          <td>Beschleunigung</td>
          <td>cm/s²</td>
          <td>Zentimeter pro Quadratsekunde</td>
      </tr>
  </tbody>
</table>
</div>
<p>Problematisch im Konstruktionswesen sind an dieser Stelle die Längenangaben in <em>Zentimeter</em>, da der <em>Millimeter</em> für Konstruktionsdaten als Goldstandard gilt, sowie das Winkelmaß als <em>Radiant</em>, das für den menschlichen Verstand nicht so gut greifbar ist, wie das Winkelmaß.</p>

<h2 class="relative group">Maßeinheiten und die API
    <div id="maßeinheiten-und-die-api" class="anchor"></div>
    
</h2>
<p>Die API stellt für das Arbeiten mit Maßeinheiten zwei wichtige Werkzeuge bereit:</p>
<ol>
<li>
<p>Das Objekt <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=GUID-UnitsOfMeasure"  target="_blank" rel="noreferrer"><strong>UnitsOfMeasure:</strong></a></p>
<p>Dieses Objekt hat die zugehöriger Methode zur Konvertierung von Einheiten und gleichzeitig die in den Dokumenteneinstellungen gesetzten Einheiten zum Auslesen und Manipulieren verfügbar.</p>
</li>
<li>
<p>Den Enum <a href="https://help.autodesk.com/view/INVNTOR/2024/ENU/?guid=UnitsTypeEnum"  target="_blank" rel="noreferrer"><strong>UnitTypeEnum:</strong></a></p>
<p>Mit diesem Enum kann bestimmt werden, um was für eine Einheitenangabe es sich handelt, bspw. um eine generelle Längenangabe, aber auch präziser um eine Längenangabe mit der Einheit <em>Millimeter</em>.</p>
</li>
</ol>

<h2 class="relative group">Beispiel zur Veranschaulichung
    <div id="beispiel-zur-veranschaulichung" class="anchor"></div>
    
</h2>
<p>Als praktisches Beispiel dient ein Zylinder mit einem Durchmesser <code>Ø25mm</code> und einer extrudierten Länge von <code>80mm</code>:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-uom/cylinder-dimensions-ui.png"
          alt=""
        />
  
  
  </figure>
<p>Das Zylinderbauteil hat in den Dokumenteneigenschaften die Maßeinheit <code>mm</code> für Längenangaben gesetzt:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-uom/doc-units-settings.png"
          alt=""
        />
  
  
  </figure>
<p>Eine Extrusion ist ein sogenanntes <em>Feature</em>. Features sind in der API unter dem Objekt <code>ComponentDefinition.Features</code> zu finden. Dort gibt es den Endpunkt <code>ExtrudeFeatures</code>, in dem alle Dimensionsparameter zu finden sind, die den Beispielzylinder beschreiben:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-uom/compdef-feat.png"
          alt=""
        />
  
  
  </figure>
<p>Unterhalb der <code>ExtrudeFeatures</code> gibt es den Abschnitt <code>Parameters</code>, der eine Liste mit allen geometriebeschreibenden Parametern vorhält. Der Durchmesser des Zylinder ist in dieser Liste <code>Item 1</code> und die ausgetragene Länge <code>Item 3</code>.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-uom/cylinder-dimensions-api.png"
          alt=""
        />
  
  
  </figure>
<p>Als <code>Value</code> erkennt man auf dem Screenshot, dass die Werte <code>2,5</code> und <code>8</code> angegeben werden, obwohl <code>25mm</code> und <code>80mm</code> definiert wurden. <strong>Dies muss immer berücksichtigt werden, wenn ein solcher Wert ausgelesen und bspw. in einer Variable gespeichert und weiterverwendet wird.</strong></p>

<h2 class="relative group">Konvertierung von Maßeinheiten
    <div id="konvertierung-von-maßeinheiten" class="anchor"></div>
    
</h2>
<p>Als Beispiel für eine Maßeinheitenkonvertierung wird der Durchmesser des Zylinders gewählt. Dieser hat, wie bereits angemerkt, die Nummer <code>Item 1</code> in der Parameterliste der <em>ExtrudeFeatures</em>. Um den internen Wert <code>cm</code> nun in <code>mm</code> zu konvertieren wird zum einen  der <code>UnitsTypeEnum</code> benötigt und das <code>UnitsOfMeasure</code>-Objekt des Dokuments, dass die Methode <a href="https://help.autodesk.com/view/INVNTOR/2022/ENU/?guid=UnitsOfMeasure_ConvertUnits"  target="_blank" rel="noreferrer"><code>ConvertUnits()</code></a> bereitstellt.</p>
<p>Die Methode <code>ConvertUnits()</code> benötigt 3 Parameter:</p>
<ol>
<li>Einen Eingabewert, der konvertiert werden soll.</li>
<li>Ein Ausgangsmaßeinheit, von dem aus konvertiert werden soll. In diesem Fall ist es die interne <em>DatabaseUnit</em> für Längenangaben.</li>
<li>Ein Maßeinheit, in die konvertiert werden soll, also <code>mm</code>.</li>
</ol>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">UOM</span> <span class="ow">As</span> <span class="n">UnitsOfMeasure</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Auslesen des Zylinderdurchmessers
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">DiameterCylinder</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ComponentDefinition</span><span class="p">.</span><span class="n">Features</span><span class="p">.</span><span class="n">ExtrudeFeatures</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="n">Parameters</span><span class="p">.</span><span class="n">Item</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="n">Value</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&#39; Konvertierung in Millimeter
</span></span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">DiameterCylinderMillimeters</span> <span class="ow">As</span> <span class="kt">Double</span> <span class="o">=</span> <span class="n">UOM</span><span class="p">.</span><span class="n">ConvertUnits</span><span class="p">(</span><span class="n">DiameterCylinder</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDatabaseLengthUnits</span><span class="p">,</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Der Durchmesser beträgt intern: &#34;</span> <span class="o">&amp;</span> <span class="n">DiameterCylinder</span> <span class="o">&amp;</span> <span class="n">vbLf</span> <span class="o">&amp;</span> <span class="s">&#34;Der Durchmesser beträgt konvertiert: &#34;</span> <span class="o">&amp;</span> <span class="n">DiameterCylinderMillimeters</span><span class="p">,</span> <span class="n">MessageBoxButtons</span><span class="p">.</span><span class="n">OK</span><span class="p">,</span> <span class="s">&#34;Konvertierung&#34;</span><span class="p">)</span></span></span></code></pre></div></div>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-uom/msgbox-convert.PNG"
          alt=""
        />
  
  
  </figure>
<p><strong>Tipp:</strong> In einem späterem Post <a href="/posts/ilogic-user-interaction/">iLogic: User Interaktion</a> wird ausführlich auf die Verwendung von <em>MessageBoxes</em> eingegangen.</p>

<h2 class="relative group">Praxisbeispiel: Importierte CAD-Daten
    <div id="praxisbeispiel-importierte-cad-daten" class="anchor"></div>
    
</h2>
<p>Vor kurzem gab es in meinem Unternehmen folgende Situation:</p>
<ul>
<li>Ein externer Zulieferer importierte eine größere native Inventor Baugruppe.</li>
<li>Sämtliche Bauteile hatten in den Dokumenteneigenschaften einen abweichenden Satz an Maßeinheiten, konkret wurde <em>Meter</em>, statt <em>Millimeter</em> als Längeneinheit definiert.</li>
</ul>

<h3 class="relative group">Korrekturoption 1: Dokumentenebene
    <div id="korrekturoption-1-dokumentenebene" class="anchor"></div>
    
</h3>
<p>Diese Subroutine kann einfach als Trigger vor dem Speichern gesetzt werden und kontrolliert, ob die gewünschten Einheiten und Präzisionswerte vorhanden sind. Ist dies nicht der Fall werden sie korrigiert. Sie prüft und korrigiert nur in Bauteilen und Baugruppen, da Zeichnungen auf die Werte der dargestellten Dokumente zugreifen.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kPartDocumentObject</span> <span class="ow">Or</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kAssemblyDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">CheckAndSetUnits</span><span class="p">(</span><span class="n">oDoc</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">CheckAndSetUnits</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">LengthUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">LengthDisplayPrecision</span> <span class="o">=</span> <span class="n">3</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">AngleUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDegreeAngleUnits</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">AngleDisplayPrecision</span> <span class="o">=</span> <span class="n">2</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">MassUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kKilogramMassUnits</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">TimeUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kSecondTimeUnits</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">&#39; Subroutine beenden, da Einheiten korrekt sind
</span></span></span><span class="line"><span class="cl">        <span class="k">Exit</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">    <span class="nf">Else</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">LengthUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">LengthDisplayPrecision</span> <span class="o">=</span> <span class="n">3</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">AngleUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDegreeAngleUnits</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">AngleDisplayPrecision</span> <span class="o">=</span> <span class="n">2</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">MassUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kKilogramMassUnits</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">TimeUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kSecondTimeUnits</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>

<h3 class="relative group">Korrekturoption 2: Alle Unterkomponenten
    <div id="korrekturoption-2-alle-unterkomponenten" class="anchor"></div>
    
</h3>
<p>Alternativ zu einer <em>Trigger</em>-basierten Korrektur, kann auch die oberste Baugruppe geöffnet und eine iLogic manuell ausgeführt werden, die über die gesamten Unterkomponenten der Bauteile iteriert und jedes Teil dabei prüft und ggf. korrigiert. Dafür muss in der API das Objekt <code>ReferencedDocuments</code> verwendet werden. <em>ReferencedDocuments</em> beinhaltet Verweise auf sämtliche Dokumente, die in einer Baugruppe verbaut sind und auch auf den Basiseinheitensatz jedes verbauten Dokuments:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-uom/referenced-docs.png"
          alt="Screenshot API Referenced Documents"
        />
  
  
  </figure>
<p>Dies ist zugleich eine gute Möglichkeit eine nützliche Funktionalität kennenzulernen: Eine <code>For Each</code>-Schleife.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDocRefs</span> <span class="ow">As</span> <span class="n">DocumentsEnumerator</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">ReferencedDocuments</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">For</span> <span class="k">Each</span> <span class="n">oDocRef</span> <span class="ow">In</span> <span class="n">oDocRefs</span>
</span></span><span class="line"><span class="cl">        <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kPartDocumentObject</span> <span class="ow">Or</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kAssemblyDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">            <span class="n">CheckAndSetUnits</span><span class="p">(</span><span class="n">oDocRef</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">    <span class="k">Next</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">CheckAndSetUnits</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">LengthUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">LengthDisplayPrecision</span> <span class="o">=</span> <span class="n">3</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">AngleUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDegreeAngleUnits</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">AngleDisplayPrecision</span> <span class="o">=</span> <span class="n">2</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">MassUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kKilogramMassUnits</span> _
</span></span><span class="line"><span class="cl">        <span class="ow">And</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">TimeUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kSecondTimeUnits</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">&#39; Subroutine beenden, da Einheiten korrekt sind
</span></span></span><span class="line"><span class="cl">        <span class="k">Exit</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">    <span class="nf">Else</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">LengthUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kMillimeterLengthUnits</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">LengthDisplayPrecision</span> <span class="o">=</span> <span class="n">3</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">AngleUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kDegreeAngleUnits</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">AngleDisplayPrecision</span> <span class="o">=</span> <span class="n">2</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">MassUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kKilogramMassUnits</span>
</span></span><span class="line"><span class="cl">        <span class="n">oDoc</span><span class="p">.</span><span class="n">UnitsOfMeasure</span><span class="p">.</span><span class="n">TimeUnits</span> <span class="o">=</span> <span class="n">UnitsTypeEnum</span><span class="p">.</span><span class="n">kSecondTimeUnits</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Dokumententypen</title>
      <link>https://jbetzen.net/posts/ilogic-documenttypes/</link>
      <pubDate>Thu, 08 Sep 2022 12:44:33 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-documenttypes/</guid>
      <description>Dokument ist nicht gleich Dokument. Eine Baugruppe ist keine Schweißbaugruppe. Ein Bauteil ist nicht zwangsweise ein Blechbauteil. Dieser Post handelt darüber, wie man Dokumententypen eindeutig identifiziert und adressiert.</description>
      <content:encoded><![CDATA[ <p>Im vorherigen Post <a href="/posts/ilogic-struktur/">iLogic: Struktureller Aufbau</a> konnte bereits Bekanntschaft mit zwei unterschiedlichen Dokumententypen gemacht werden: <code>kAssemblyDocumentObject</code> und <code>kDrawingDocumentObject</code>. Doch woher kommen diese Bezeichnungen und wie viele gibt es von ihnen? Dokumentiert sind diese Objekte auf der Autodesk Webseite zur <a href="https://help.autodesk.com/view/INVNTOR/2022/ENU/?guid=DocumentTypeEnum"  target="_blank" rel="noreferrer">Inventor API</a>.</p>

<h2 class="relative group">Documenttypes
    <div id="documenttypes" class="anchor"></div>
    
</h2>
<p>Zur Auswahl und Abfrage stehen folgende <code>DocumentTypes</code>. Relevant für die tägliche Arbeit als Konstrukteur sind allerdings nur die ersten drei, nämlich Bauteil, Baugruppe und technische Zeichnung.</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Value</th>
          <th>Beschreibung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>kPartDocumentObject</td>
          <td>12290</td>
          <td>Part Document</td>
      </tr>
      <tr>
          <td>kAssemblyDocumentObject</td>
          <td>12291</td>
          <td>Assembly Document</td>
      </tr>
      <tr>
          <td>kDrawingDocumentObject</td>
          <td>12292</td>
          <td>Drawing Document</td>
      </tr>
      <tr>
          <td>kDesignElementDocumentObject</td>
          <td>12294</td>
          <td>Design Element Document</td>
      </tr>
      <tr>
          <td>kForeignModelDocumentObject</td>
          <td>12295</td>
          <td>Foreign Model Document</td>
      </tr>
      <tr>
          <td>kNestingDocument</td>
          <td>12298</td>
          <td>Nesting Document</td>
      </tr>
      <tr>
          <td>kNoDocument</td>
          <td>12297</td>
          <td>No Document</td>
      </tr>
      <tr>
          <td>kPresentationDocumentObject</td>
          <td>12293</td>
          <td>Presentation Document</td>
      </tr>
      <tr>
          <td>kSATFileDocumentObject</td>
          <td>12296</td>
          <td>SAT File Document</td>
      </tr>
      <tr>
          <td>kUnknownDocumentObject</td>
          <td>12289</td>
          <td>Unknown Document</td>
      </tr>
  </tbody>
</table>
</div>
<p>Dem geschulten Auge stellen sich sofort zwei Fragen:</p>
<ol>
<li>Wie unterscheide ich ein Standardbauteil von einem Blechbauteil?</li>
<li>Wie unterscheide ich eine Standardbaugruppe von einer Schweißbaugruppe?</li>
</ol>
<p>Um diese Fragen zu beantworten, stellt die Inventor API ein Property namens <a href="https://help.autodesk.com/view/INVNTOR/2022/ENU/?guid=Document_SubType"  target="_blank" rel="noreferrer"><code>DocumentSubType</code></a> zur Verfügung.</p>

<h2 class="relative group">DocumentSubTypes
    <div id="documentsubtypes" class="anchor"></div>
    
</h2>
<p>Sämtliche zur Verfügung stehenden <em>DocumentSubTypes</em> sind auf der <a href="https://knowledge.autodesk.com/search-result/caas/simplecontent/content/documentsubtype-list-common-name-inventors-name-cslid-inv-pro-2021-dev-tools.html"  target="_blank" rel="noreferrer">Autodesk Webseite</a> gelistet. <em>DocumentSubTypes</em> werden mit Hilfe von <a href="https://de.wikipedia.org/wiki/Globally_Unique_Identifier"  target="_blank" rel="noreferrer">GUIDs</a> angegeben. Das steht für <em>Global Unique Identitfier</em>. Diese GUID ist eine Zeichenfolge vom Typ <code>String</code>, die stellvertretend für einen Dokumententyp steht.</p>

<h3 class="relative group">Parts
    <div id="parts" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Common Name</th>
          <th>GUID</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Normal Part</td>
          <td>&ldquo;4D29B490-49B2-11D0-93C3-7E0706000000&rdquo;</td>
      </tr>
      <tr>
          <td>SheetMetal Part</td>
          <td>&ldquo;9C464203-9BAE-11D3-8BAD-0060B0CE6BB4&rdquo;</td>
      </tr>
      <tr>
          <td>Gereric Proxy</td>
          <td>&ldquo;92055419-B3FA-11D3-A479-00C04F6B9531&rdquo;</td>
      </tr>
      <tr>
          <td>Compatibility Proxy</td>
          <td>&ldquo;9C464204-9BAE-11D3-8BAD-0060B0CE6BB4&rdquo;</td>
      </tr>
      <tr>
          <td>Catalog Proxy</td>
          <td>&ldquo;9C88D3AF-C3EB-11D3-B79E-0060B0F159EF&rdquo;</td>
      </tr>
      <tr>
          <td>Molded Part</td>
          <td>&ldquo;4D8D80D4-F5B0-4460-8CEA-4CD222684469&rdquo;</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Assemblies
    <div id="assemblies" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Common Name</th>
          <th>GUID</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Normal Assembly</td>
          <td>&ldquo;E60F81E1-49B3-11D0-93C3-7E0706000000&rdquo;</td>
      </tr>
      <tr>
          <td>Welded Assembly</td>
          <td>&ldquo;28EC8354-9024-440F-A8A2-0E0E55D635B0&rdquo;</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Drawing
    <div id="drawing" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Common Name</th>
          <th>GUID</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Drawing Document</td>
          <td>&ldquo;BBF9FDF1-52DC-11D0-8C04-0800090BE8EC&rdquo;</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Design Element
    <div id="design-element" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Common Name</th>
          <th>GUID</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Design Element</td>
          <td>&ldquo;62FBB030-24C7-11D3-B78D-0060B0F159EF&rdquo;</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Presentations
    <div id="presentations" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Common Name</th>
          <th>GUID</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Normal Presentation</td>
          <td>&ldquo;76283A80-50DD-11D3-A7E3-00C04F79D7BC&rdquo;</td>
      </tr>
      <tr>
          <td>Composite Presentation</td>
          <td>&ldquo;A2B4C17D-F0D2-4C0F-9799-DD5F71DFB291&rdquo;</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Design View Document
    <div id="design-view-document" class="anchor"></div>
    
</h3>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Common Name</th>
          <th>GUID</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Design View Document</td>
          <td>&ldquo;81B95C5D-8E31-4F65-9790-CCF6ECABD141&rdquo;</td>
      </tr>
  </tbody>
</table>
</div>

<h2 class="relative group">Codebeispiele
    <div id="codebeispiele" class="anchor"></div>
    
</h2>
<p>Im folgenden ein paar Beispiele, die sowohl den <em>DocumentType</em> abfragen, als auch die GUID nutzen.</p>

<h3 class="relative group">Abfragen des DocumentTypes
    <div id="abfragen-des-documenttypes" class="anchor"></div>
    
</h3>
<p>Wer generell ermitteln möchte, ob es sich bei einem Dokument bspw. um ein Bauteil handelt, kann dies, wie im vorangegangenen Post <a href="/posts/ilogic-struktur/">iLogic: Struktureller Aufbau</a> beschrieben, folgendermaßen erreichen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kPartDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist ein Bauteil.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Else</span>
</span></span><span class="line"><span class="cl">    <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist kein Bauteil.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span></span></span></code></pre></div></div>
<p>Alternativ kann auch explizit der <em>DocumentTypeEnum</em> bei der Abfrage verwendet werden. Das Ergebnis ist das selbe:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">DocumentTypeEnum</span><span class="p">.</span><span class="n">kPartDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist ein Bauteil.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Else</span>
</span></span><span class="line"><span class="cl">    <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist kein Bauteil.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span></span></span></code></pre></div></div>
<p><strong>Faustformel:</strong> Möchte man lediglich <em>grob</em> Ermitteln, ob es sich bei einem Dokument bspw. um ein Bauteil, eine Baugruppe oder eine Zeichnung handelt, reicht eine Abfrage des <em>DocumentType</em>.</p>

<h3 class="relative group">Abfragen des DocumentSubTypes
    <div id="abfragen-des-documentsubtypes" class="anchor"></div>
    
</h3>
<p>Möchte man eine spezifischere Abfrage tätigen, die nicht nur prüft ob es sich bei einem Dokument bspw. um ein Bauteil, sondern explizit um ein Blechbauteil handelt, muss der <em>DocumentSubType</em> in Verbindung mit der GUID genutzt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">as</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oSubType</span> <span class="ow">as</span> <span class="kt">String</span> <span class="o">=</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentSubType</span><span class="p">.</span><span class="n">DocumentSubTypeID</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">oSubType</span> <span class="o">=</span> <span class="s">&#34;{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}&#34;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="n">MessageBox</span> <span class="p">(</span><span class="s">&#34;Das Bauteil ist ein Blechbauteil.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Else</span>
</span></span><span class="line"><span class="cl">    <span class="n">MessageBox</span> <span class="p">(</span><span class="s">&#34;Das Bauteil ist kein Blechbauteil.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">if</span></span></span></code></pre></div></div>

<h2 class="relative group">Weitergedacht
    <div id="weitergedacht" class="anchor"></div>
    
</h2>
<p>Im vorherigen Post <a href="/posts/ilogic-struktur/">iLogic: Struktureller Aufbau</a> wurden Funktionen und Routinen vorgestellt. Wer häufig eine Unterscheidung zwischen Standard-Bauteilen und Blechbauteilen oder zwischen Standard-Baugruppen und Schweißbaugruppen benötigt, kann eine solche Abfrage auch in einer Funktion abbilden und wiederverwenden. Diese kann dann wiederum mit der Abfrage des <em>DocumentType</em> kombiniert werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kPartDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">If</span> <span class="n">IsSheetMetal</span><span class="p">()</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">            <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das Bauteil ist ein Blechbauteil&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">Else</span>
</span></span><span class="line"><span class="cl">            <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das Bauteil ist zwar ein Bauteil, aber kein Blechbauteil.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsSheetMetal</span><span class="p">()</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span><span class="p">.</span><span class="n">DocumentSubType</span><span class="p">.</span><span class="n">DocumentSubTypeID</span> <span class="o">=</span> <span class="s">&#34;{9C464203-9BAE-11D3-8BAD-0060B0CE6BB4}&#34;</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">if</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Struktureller Aufbau</title>
      <link>https://jbetzen.net/posts/ilogic-struktur/</link>
      <pubDate>Wed, 07 Sep 2022 11:21:41 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-struktur/</guid>
      <description>In diesem Post geht es um den strukturellen Aufbau einer iLogic. iLogics müssen nicht zwangsweise sequenziell als Skript von oben nach unten laufen sondern bieten bspw. die Möglichkeit der Verwendung von Routinen, Subroutinen und Funktionen.</description>
      <content:encoded><![CDATA[ <p>Im vorherigen Post <a href="/posts/ilogic-editor/">iLogic: Der Editor</a> wurde der grundsätzliche Aufbau des Editors erläutert. Nun gilt es den ersten Code zu schreiben. iLogic Code kann prinzipiell ohne eine explizite Hauptroutine <code>Sub Main()</code> laufen. Aller Code wird dann einfach im Editor <em>Line by Line</em> ausgeführt. Während dies bei einfachen Programmen kein Problem darstellt, kann dies bei komplexer aufgebauten iLogics zur Unübersichtlichkeit und Problemen beim Lesen des Codes führen.</p>

<h2 class="relative group">iLogic ohne Main- und Subroutines
    <div id="ilogic-ohne-main--und-subroutines" class="anchor"></div>
    
</h2>
<p>Ein einfaches Beispiel für eine iLogic ohne Unterroutinen könnte so aussehen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kAssemblyDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">    <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist eine Baugruppe.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">Else</span>
</span></span><span class="line"><span class="cl">    <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist keine Baugruppe.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">If</span></span></span></code></pre></div></div>
<p><strong>Der Code im Detail:</strong></p>
<ol>
<li>Zuerst wird ein Objekt <code>oDoc</code> deklariert und direkt in der gleichen Zeile wird diesem das Dokument zugewiesen, in dem der Code ausgeführt werden soll.</li>
<li>Es folgt eine If-Bedingung, die prüft ob es sich bei dem geöffneten Dokument um eine Baugruppe handelt. Ist dies der Fall, wird eine Messagebox auf dem Bildschirm erscheinen und den Text <em>Das geöffnete Dokument ist eine Baugruppe.</em> ausgeben.</li>
<li>Handelt es sich bei dem geöffneten Dokument um keine Baugruppe, erscheint ebenfalls eine Messagebox, die den Text <em>&ldquo;Das geöffnete Dokument ist keine Baugruppe.&rdquo;</em> ausgibt.</li>
</ol>

<h2 class="relative group">Funktionen und Subroutinen
    <div id="funktionen-und-subroutinen" class="anchor"></div>
    
</h2>
<p>Wie bereits zuvor erwähnt, können Funktionen und Unterroutinen die Lesbarkeit des Codes stark erhöhen. Dies ist besonders hilfreich, wenn man eine iLogic studiert, die nicht aus eigener Feder stammt. Funktionen und Routinen sollten daher immer kurze und prägnante Benennungen erhalten, die eindeutig aufzeigen welchen Zweck sie erfüllen.</p>
<p>Unterscheiden tun sich Funktionen von Subroutinen in einem Punkt: Sie liefern einen Wert zurück und brauchen auch korrekterweise direkt einen Datentyp bei der Deklaration, der beschreibt <em>was</em> da zurückgeliefert wird, bspw. ob es sich um einen <code>String</code>, einen Ganzzahlwert <code>int</code> oder einen <em>bool&rsquo;schen</em> Wert <code>true</code> oder <code>false</code> handelt.</p>

<h3 class="relative group">Funktionen
    <div id="funktionen" class="anchor"></div>
    
</h3>
<p>Übertragen auf das erste Beispiel einer iLogic, kann die Prüfung ob es sich beim geöffneten Dokument um eine Baugruppe handelt, auch einfach durch eine Funktion übernommen werden, die für diesen Fall den Wert <code>true</code> zurückliefert. Dazu muss der eigentliche Programmcode und der Code der Funktion voneinander getrennt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">IsDocumentAssembly</span><span class="p">()</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist eine Baugruppe.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist keine Baugruppe.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsDocumentAssembly</span><span class="p">()</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kAssemblyDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span></span></span></code></pre></div></div>
<p><strong>Der Code im Detail:</strong></p>
<ol>
<li>Der eigentliche Programmcode läuft nun in einer Hauptroutine <code>Main()</code></li>
<li>Die Prüfung, ob es sich um eine Baugruppe handelt ist in eine eigene Funktion ausgelagert. Die Funktion ist vom Typ <code>Private</code>, steht damit nur in der iLogic selber zur Verfügung und liefert einen <em>bool&rsquo;schen Wert</em> zurück.</li>
<li>Das zu prüfende Dokument <code>oDoc</code> wird direkt in der Funktion zugewiesen.</li>
</ol>
<p>Speziell <strong>Punkt 3</strong> sollte genauer betrachtet werden: Angenommen es gäbe noch eine weitere Funktion, die zusätzlich prüft ob es sich bei dem geöffneten Dokument um eine Zeichnung handelt, müsste man bei gleicher Programmierung zweimal ein Objekt <code>oDoc</code> deklarieren und abfragen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsDocumentDrawing</span><span class="p">()</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kDrawingDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span></span></span></code></pre></div></div>
<p>Diese Redundanz ist ineffizient und sollte vermieden werden. Die Lösung dazu sind Funktionsparameter. Derzeit arbeiten die beiden Funktionen <code>IsDocumentAssembly()</code> und <code>IsDocumentDrawing()</code> ohne einen einzigen Parameter, der an sie übergeben wird. Die Klammern <code>()</code> sind leer. Innerhalb dieser Klammern kann also Vorgegeben werden was für einen Parameter oder Datentyp an die Funktion übergeben wird:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">IsDocumentAssembly</span><span class="p">(</span><span class="n">oDoc</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist eine Baugruppe.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span> <span class="k">If</span> <span class="n">IsDocumentDrawing</span><span class="p">(</span><span class="n">oDoc</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist eine Zeichnung.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist weder eine Baugruppe, noch eine Zeichnung.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsDocumentAssembly</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kAssemblyDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsDocumentDrawing</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kDrawingDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span></span></span></code></pre></div></div>
<p><strong>Der Code im Detail:</strong></p>
<ol>
<li>Das Objekt <code>oDoc</code>, das stellvertretend für das geöffnete Dokument steht, wir in der Hauptroutine <code>Main()</code> einmalig definiert und an die beiden Funktionen als Parameter übergeben.</li>
<li>Die If-Bedingung wurde um einen weiteren Prüfentscheid <code>Else If</code> erweitert der zusätzlich prüft, ob das geöffnete Dokument eine Zeichnung ist.</li>
<li>Die Funktionen liefern einen <em>bool&rsquo;schen</em> Wert zurück, der in der Hauptroutine für Entscheidungen genutzt werden kann.</li>
</ol>

<h3 class="relative group">Subroutines
    <div id="subroutines" class="anchor"></div>
    
</h3>
<p>Wenn kein expliziter Rückgabewert gefordert ist, kann anstelle eine Funktion auch eine Subroutine genutzt werden. Sie sollten wie kleine Unterprogramme betrachtet werden, die spezifische Tasks ausführen, bspw. eine Stücklistenstruktur setzen, einen Anzeigenamen anpassen oder Bauteile unterdrücken.</p>
<p><strong>Ein fiktiver Fall für eine Subroutine:</strong></p>
<p>Basierend auf dem bisherigen Code soll für den Fall, dass das aktive Dokument eine Baugruppe ist, der Anzeigename im Modellbrowser mit der Bezeichnung <code>Klappspaten</code> überschrieben werden. Die API von Inventor stellt diese Information bereit, man muss nur die richtige Methodik finden an diesen Wert zu kommen. Zur Hilfe bei der Suche kommt wieder das VBA-Makro aus dem ersten Teil dieser Serie <a href="/posts/ilogic-basics/">iLogic: Erste Schritte mit der Inventor API</a> zum Einsatz.</p>
<p>Der Anzeigename ist Teil des Objekts <code>oDoc</code> und kann unter dem API-Endpunkt <code>oDoc.DisplayName</code> geschrieben werden. Des Weiteren kann der Typ entnommen werden, nämlich <code>String</code> - also eine Textfolge -, ebenso wie der aktuelle Wert (<em>Value</em>) des Anzeigenamens, nämlich <code>&quot;Assembly.iam&quot;</code>.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-struktur/displayname.png"
          alt="Displayname in Inventor"
        />
  
  
  </figure>
<p>Die Subroutine, um den Anzeigenamen zu überschreiben, könnte so aussehen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">SetDisplayName</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">newName</span> <span class="ow">As</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">&#34;Klappspaten&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">oDoc</span><span class="p">.</span><span class="n">DisplayName</span> <span class="o">=</span> <span class="n">newName</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p>Der gesamte Code, inkl. der neuen Subroutine:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span> <span class="o">=</span> <span class="n">ThisDoc</span><span class="p">.</span><span class="n">Document</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">IsDocumentAssembly</span><span class="p">(</span><span class="n">oDoc</span><span class="p">)</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="n">SetDisplayName</span><span class="p">(</span><span class="n">oDoc</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist eine Baugruppe.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span> <span class="k">If</span> <span class="n">IsDocumentDrawing</span><span class="p">(</span><span class="n">oDoc</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist eine Zeichnung.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="n">MsgBox</span><span class="p">(</span><span class="s">&#34;Das geöffnete Dokument ist weder eine Baugruppe, noch eine Zeichnung.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsDocumentAssembly</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kAssemblyDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Function</span> <span class="nf">IsDocumentDrawing</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span> <span class="ow">As</span> <span class="kt">Boolean</span>
</span></span><span class="line"><span class="cl">    <span class="k">If</span> <span class="n">oDoc</span><span class="p">.</span><span class="n">DocumentType</span> <span class="o">=</span> <span class="n">kDrawingDocumentObject</span> <span class="k">Then</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">Else</span>
</span></span><span class="line"><span class="cl">        <span class="k">Return</span> <span class="k">False</span>
</span></span><span class="line"><span class="cl">    <span class="k">End</span> <span class="k">If</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Function</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">SetDisplayName</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">oDoc</span><span class="p">.</span><span class="n">DisplayName</span> <span class="o">=</span> <span class="s">&#34;Klappspaten&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p>Und so sieht das Ergebnis nach dem Ausführen der iLogic im Modellbaum in Inventor aus:</p>


  

<figure class="centered"><img src="/posts/ilogic-struktur/displayname-klappspaten.png"
    alt="Beispiel Displayname">
</figure>


<p>Wenn man den Anzeigenamen wieder auf den Default-Wert setzen möchte, kann man dies erreichen indem man ihn einfach erneut mit einem leeren String überschreibt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Private</span> <span class="k">Sub</span> <span class="nf">ResetDisplayName</span><span class="p">(</span><span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">oDoc</span><span class="p">.</span><span class="n">DisplayName</span> <span class="o">=</span> <span class="s">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p>Im Serienteil <a href="/posts/ilogic-displayname/">iLogic: Displayname anpassen</a> gibt es weitere Beispiele zur Manipulation des Displaynamens.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Stromkosten mit Tasmota errechnen</title>
      <link>https://jbetzen.net/posts/tasmota-stromkosten/</link>
      <pubDate>Sat, 03 Sep 2022 21:11:51 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/tasmota-stromkosten/</guid>
      <description>Mit Tasmota geflashte Steckdosen stellen Statistiken für den täglichen Stromverbrauch bereit. In Kombination mit einer Variable, die den Strompreis beinhaltet, kann somit Verbrauch in monetäre Beträge gewandelt werden.</description>
      <content:encoded><![CDATA[ <p>Mit großer Freude erreichte mich die frohe Kunde, dass mein lokaler Stromanbieter eine Anhebung der Strompreise vorsieht. Die vormalig geltenden 23,21c/kWh werden auf den Betrag von 36,15c/kWh erhöht.</p>
<p>Um den täglichen Verbrauch aller Tasmota-Steckdosen in den real anfallenden Geldbetrag zu wandeln, wird die Custom Component <a href="https://github.com/rogro82/hass-variables"  target="_blank" rel="noreferrer">hass-variables</a> benötigt. Diese kann aus dem HACS-Store installiert werden und kam unter anderem bereits in diesen Posts zum einsatz:</p>

  
      <ul>
        
          <li><a href="/posts/room-based-presence/">Room Based Presence mit Motion Sensoren</a></li>
        
          <li><a href="/posts/fritzbox-callmon/">Homeassistant, FritzBox und Anrufbenachrichtigung per Push-Nachricht</a></li>
        
      </ul>
  



<h2 class="relative group">Zur Theorie des Vorhabens
    <div id="zur-theorie-des-vorhabens" class="anchor"></div>
    
</h2>
<p>Die Tasmota-Steckdosen liefern einen Wert zum täglichen Stromverbrauch (<code>Energy Today</code>), der um Mitternacht auf <code>0</code> gesetzt wird. Tagsüber kann also dieser kumulierende Wert mit dem aktuellen Strompreis multipliziert werden und man erhält den anfallenden Geldbetrag.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/tasmota-stromkosten/tasmota-stats.png"
          alt=""
        />
  
  
  </figure>
<p>Den Strompreis möchte ich zentral in einer Variable festhalten. So kann bei Verwendung mehrerer Tasmota-Steckdosen zentral auf diesen Wert zugegriffen und bei der nächsten anstehenden Erhöhung muss nur an einer Stelle der Strompreis geändert werden.</p>

<h2 class="relative group">Strompreisvariable anlegen
    <div id="strompreisvariable-anlegen" class="anchor"></div>
    
</h2>
<p>Nachdem <em>hass-variable</em> installiert wurde, muss folgender Eintrag in der <code>configuration.yaml</code> erfolgen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">var</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">strompreis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restore</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">initial_value</span><span class="p">:</span><span class="w"> </span><span class="m">0.3615</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:currency-eur</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="l">Strompreis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;€/kwh&#39;</span></span></span></code></pre></div></div>
<p>Anschließend steht die Variable mit der Entity ID <code>var.strompreis</code> in Homeassistant zur Verfügung.</p>

<h2 class="relative group">Template Sensor
    <div id="template-sensor" class="anchor"></div>
    
</h2>
<p>Nun muss nur noch ein Sensor erstellt werden, der den Tagesverbrauch von einer beliebigen Tasmota Steckdose ausließt und mit dem Strompreis multipliziert. In diesem Beispiel dient mein Kühlschrank mit der Entity ID <code>sensor.kuehlschrank_energy_today</code> als Beispiel parat. Der Sensor muss ebenfalls in der <code>configuration.yaml</code> angelegt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">sensor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">kuehlschrank_kosten_heute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ &#34;%.2f&#34; | format(states.sensor.kuehlschrank_energy_today.state | float | multiply(states.var.strompreis.state | float)) }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="l">€</span></span></span></code></pre></div></div>
<p>Der Sensor liefert einen zweistelligen Nachkommawert à la <code>0,07€</code>.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/tasmota-stromkosten/sensor-stromkosten.png"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">Kumulierte Kosten mehrerer Steckdosen
    <div id="kumulierte-kosten-mehrerer-steckdosen" class="anchor"></div>
    
</h2>
<p>Wer mehrere Tasmota-Steckdosen im Eigenheim betreibt, kann den obigen Sensor für alle weiteren Steckdosen einrichten und am Ende die Gesamtsummer errechnen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">sensor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">kosten_gesamt_elektrische_verbraucher</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ &#39;%.2f&#39; | format( (states(&#39;sensor.kuehlschrank_kosten_heute&#39;) | float) + (states(&#39;sensor.heimserver_kosten_heute&#39;) | float) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="l">€</span></span></span></code></pre></div></div>

<h2 class="relative group">Monats- &amp; Jahreskosten
    <div id="monats---jahreskosten" class="anchor"></div>
    
</h2>
<p>Da Tasmota nur einen Wert für den Tagesverbrauch liefert, muss zur Ermittlung von monatlichen oder jährlichen Kosten auf bordeigene Mittel in Homeassistant zugegriffen werden. Dafür steht die <a href="https://www.home-assistant.io/integrations/utility_meter/"  target="_blank" rel="noreferrer">Utitlity Meter</a> Integration zur Verfügung, die auch schon im <a href="/posts/fitness-tracker/" >Post zum Homeassistant Fitness Tracker</a> zum Einsatz kam:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">utility_meter</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kuehlschrank_kosten_monatlich</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.kuehlschrank_kosten_heute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cycle</span><span class="p">:</span><span class="w"> </span><span class="l">monthly</span></span></span></code></pre></div></div>





  
  
    
  
  


  <section class="space-y-10 w-full">
    
    











  
  
  








  
  
    

    
    
      
      
        
      
        
      
        
      
    

    
    

    
    
      
        
        
      
    
  



<article class="article-link--shortcode flex flex-col md:flex-row relative">
  
    <div class="flex-none relative overflow-hidden  thumbnail-shadow md:mr-7 thumbnail">
      <img
        src="/posts/fitness-tracker/featured.png"
        role="presentation"
        loading="lazy"
        decoding="async"
        class="not-prose absolute inset-0 w-full h-full object-cover">
    </div>
  
  <div class=" mt-3 md:mt-0">
    <header class="items-center text-start text-xl font-semibold">
      <a
        
          href="/posts/fitness-tracker/"
        
        class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
        <h2>
          Homeassistant Fitness Tracker
          
        </h2>
      </a>
      
      
    </header>
    <div class="text-sm text-neutral-500 dark:text-neutral-400">
      







  

  
  
  
    
  

  

  
    
  

  

  
    
  

  
    
  

  

  

  

  

  


  <div class="flex flex-row flex-wrap items-center">
    
    
      <time datetime="2021-01-23T23:42:13&#43;02:00">23.01.2021</time><span class="px-2 text-primary-500">&middot;</span><span>531 Wörter</span><span class="px-2 text-primary-500">&middot;</span><span title="Lesezeit">3 min</span>
    

    
    
  </div>

  

  
  
    <div class="flex flex-row flex-wrap items-center">
      
        
      
        
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/utility-meter/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    Utility Meter
  </span>
</span>

                </a>
              
            
            
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/companion-app/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    Companion App
  </span>
</span>

                </a>
              
            
            
          
        
      
        
      
        
      
        
          
            
              
            
            
          
        
      
    </div>
  


    </div>
    
      
      <div
        class="article-link__summary prose dark:prose-invert max-w-fit mt-1 ">
        Die Homeassistant Companion App liefert Fitnessdaten, die direkt vom Smartphone OS generiert werden. Diese können in Langzeitstatistiken mittels dem Utility Meter in Homeassistant getrackt und ausgewertet werden.
      </div>
    
  </div>
</article>

  </section>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Der Editor</title>
      <link>https://jbetzen.net/posts/ilogic-editor/</link>
      <pubDate>Fri, 02 Sep 2022 18:29:16 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-editor/</guid>
      <description>Inventor liefert einen eingebauten iLogic Editor mit. Dieser Post zeigt wie man ihn aufruft, wie seine grafische Oberfläche aufgebaut ist und welche Vor- und Nachteile er gegenüber gängigen Text-Editoren hat.</description>
      <content:encoded><![CDATA[ <p>Im ersten Teil dieser Serie <a href="/posts/ilogic-basics/">iLogic: Erste Schritte mit der Inventor API</a> wurde gezeigt, wie man ein eigenes Verzeichnis mit Schreibrechten einbinden und den <strong>iLogic Browser</strong> aufrufen kann. In diesem Verzeichnis kann nun eine Datei abgelegt werden, bspw. <code>test.vb</code>. Mit einem Rechtsklick auf diese Datei bietet das Kontextmenü den Eintrag <em>Edit</em> an. Wird dieser ausgewählt öffnet sich der iLogic Editor.</p>

<h2 class="relative group">Dateiendung
    <div id="dateiendung" class="anchor"></div>
    
</h2>
<p>Inventor akzeptiert für iLogics folgende Dateiendungen:</p>
<table>
  <thead>
      <tr>
          <th>Dateiendung</th>
          <th>Anmerkungen</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>.txt</code></td>
          <td>Sollte nicht gewählt werden, da allgemeine Textdateien alles beinhalten könnten, was sich in Text abbilden lässt. Es wird jedoch mit Code gearbeitet und es muss nicht zwangsweise im iLogic Editor gearbeitet werden. Ich nutzte bspw. lieber <a href="https://code.visualstudio.com/"  target="_blank" rel="noreferrer">Visual Studio Code</a>. Dieser Editor ermöglicht auch automatisches Syntax Highlighting. Das kann jedoch nicht funktionieren, wenn aus der Dateiendung nicht hervorgeht, dass es sich um Basic-Code handelt.</td>
      </tr>
      <tr>
          <td><code>.ilogicvb</code></td>
          <td>Eine Autodesk-Kreation, die alle Nachteile von <code>.txt</code> mit sich und, gleichzeitig gegenüber <code>.vb</code>, <strong>keinen</strong> Vorteil bietet. Syntax Highlighting ist auch mit dieser Dateiendung (standardmäßig) in externen Code-Editoren nicht möglich.</td>
      </tr>
      <tr>
          <td><code>.vb</code></td>
          <td>Meine präferierte Dateiendung, die automatisches Syntax Highlighting in externen Code-Editoren ermöglicht. Die Dateiendung <code>.vb</code> verweist klar und eindeutig darauf hin, dass in der Datei Basic-Code verwendet wird.</td>
      </tr>
  </tbody>
</table>
<p>Für alle folgenden Beispiele werde ich die Dateiendung <code>.vb</code> verwenden.</p>

<h2 class="relative group">Der iLogic Editor
    <div id="der-ilogic-editor" class="anchor"></div>
    
</h2>
<p>Ruft man den iLogic Editor auf, wird man von folgender Oberfläche begrüßt:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-editor/ilogic-editor.png"
          alt="iLogic Editor"
        />
  
  
  </figure>

<h3 class="relative group">Aufbau der Grafischen Oberfläche
    <div id="aufbau-der-grafischen-oberfläche" class="anchor"></div>
    
</h3>
<p>Das aktiv geladene Part, in diesem Beispiel <code>Part_Meter.ipt</code> wird mittig im Editor angezeigt. Alle Features des Bauteils werden gelistet, bspw. <code>Extrusion1</code>. Klickt man auf dieses Feature erscheinen alle Parameter, bspw. wie viele Millimeter die Extrusion ausgetragen wurde, in der Parameterübersicht.</p>
<p>Im unterem Teil des Editors wird der Code geschrieben. In der Headerleiste befinden sich diverse Dropdown-Menüs, die vordefinierte Code-Blöcke, wie <code>If-Else</code>-Schleifen oder <code>Select Case</code>-Bedingungen bereitstellen. Was diese Funktionen im einzelnen machen, wird in dieser Serie nachfolgend anhand anschaulicher Beispiele erläutert.</p>
<p>Im Linken Teil des Editors stehen allgemeingültige API-Endpunkte zur Verfügung, die nicht zwangsweise etwas mit dem geöffneten Dokument zu tun haben. Es sind bspw. auch Funktionen für <em>iParts</em> oder <em>Sheet Metal Parts</em> verfügbar, auch wenn es sich bei dem Beispielbauteil nicht um solche Bauteile handelt.</p>

<h3 class="relative group">Auto Completion
    <div id="auto-completion" class="anchor"></div>
    
</h3>
<p>Eine sinnvolle Funktion des eingebauten iLogic Editors ist das Autocomplete Feature. Während getippt wird macht der Editor Vorschläge zu API-Endpunkten oder bereits vergebenen Variablen. Wenn ein solcher Eintrag in der Auswahlliste gewünscht ist, kann er mit einem Tippen auf die <kbd>TAB</kbd> Taste übernommen werden. Wichtig ist bei der Kontextauswahl nicht die Taste <kbd>ENTER</kbd> zur Bestätigung zu nutzen, da der Editor sonst nicht den Vorschlag übernimmt und stattdessen direkt in die nächste Zeile spring.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-editor/ilogic-editor-autocompletion.png"
          alt="iLogic Editor Autocompletion"
        />
  
  
  </figure>

<h3 class="relative group">Nachteile des iLogic Editors
    <div id="nachteile-des-ilogic-editors" class="anchor"></div>
    
</h3>
<p>VBA-Editoren arbeiten naturgemäß mit Tabstopps und nicht mit Leerzeichen um Code einzurücken (<em>Indentation</em>). Das führt zu wunderlichen Situationen, wenn der Code dann kopiert und in irgendwelchen Foren gepostet wird. Kopiert man dann diesen Code aus Foren zurück in den lokalen Editor sieht das in der Regel aus wie <em>Kraut und Rüben</em>. Auch obliegen Tabstopps in ihrer Einrückungsdarstellund der Interpretation des jeweiligen Editors, mit dem sie angezeigt werden. In dieser <a href="https://alexkondov.com/indentation-warfare-tabs-vs-spaces/"  target="_blank" rel="noreferrer">endlosen Debatte</a> gehöre ich dem <strong>Team Spaces</strong> an und möchte daher auch keine Tabstopps in meinem Code vorfinden.</p>
<p>Ich handhabe es so, dass ich den eingebauten iLogic Editor zur Programmierung verwende und schließend den Code noch einmal in Visual Studio Code aufrufe und formatiere. vscode hat eine Funktion namens <code>Convert Indentation to Spaces</code>, die ich anschließend immer über alle iLogics laufen lasse, die mit dem eingebauter iLogic Editor erstellt wurden. Die Funktion kann in vscode mit der Tastenkombination <kbd>STRG</kbd>+<kbd>SHIFT</kbd>+<kbd>P</kbd> aufgerufen werden. In das sich öffnende Kommandofeld muss anschließend nur der Name der Funktion <code>Convert Indentation to Spaces</code> eingetippt und mit <kbd>ENTER</kbd> bestätigt werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-editor/vscode-convert-indentation.png"
          alt="vscode convert Indentation"
        />
  
  
  </figure>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iLogic: Erste Schritte mit der Inventor API</title>
      <link>https://jbetzen.net/posts/ilogic-basics/</link>
      <pubDate>Thu, 01 Sep 2022 17:00:22 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ilogic-basics/</guid>
      <description>Autodesk Inventor bietet mittels iLogic ein mächtiges Werkzeug wiederkehrende Aufgaben zu automatisieren. Da der Einstieg in den Umgang mit iLogic und der Inventor API für viele Konstrukteure eine Hürde darstellt, werden in diesem Post die ersten Schritte erläutert.</description>
      <content:encoded><![CDATA[ <p>Viele Konstrukteure verkennen bis heute den Sachverhalt, dass sie oftmals repetitiven Aufgaben gegenüber stehen die Zeit fressen und sich leicht automatisieren lassen. Sie verschwenden damit nicht nur ihre wertvolle Zeit, Arbeitskraft und Motivation, auch verkennen Sie dass im 21 Jahrhundert die Schnittstelle zwischen klassischer mechanischer Konstruktion und Programmierung mehr und mehr verschmelzen.</p>
<p>Diesem Post wird eine Serie zum Thema iLogics und der Inventor API folgen, die praxisnahe Beispiele aus dem Konstruktionsalltag und deren Automatisierung beinhaltet.</p>

<h2 class="relative group">iLogic - Was ist das eigentlich?
    <div id="ilogic---was-ist-das-eigentlich" class="anchor"></div>
    
</h2>
<p>Autodesk bezeichnet iLogic folgendermaßen:</p>
<blockquote><p><a href="https://help.autodesk.com/view/INVNTOR/2026/ENU/?guid=GUID-AB9EE660-299E-408F-BBE1-AFE44C723F59"  target="_blank" rel="noreferrer">iLogic</a> ermöglicht eine regelgesteuerte Konstruktion und bietet eine einfache Möglichkeit zum Erfassen und Wiederverwenden Ihrer Arbeit. Verwenden Sie iLogic zur Standardisierung und Automatisierung von Konstruktionsprozessen und zur Konfiguration Ihrer virtuellen Produkte.</p>
<p>In iLogic werden Regeln als Objekte direkt in Bauteil-, Baugruppen- und Zeichnungsdokumente integriert. Mithilfe der Regeln werden die Parameter und Attributwerte für Ihren Entwurf festgelegt. Über diese Werte definieren Sie das Verhalten der Attribute, Elemente und Komponenten eines Modells. Die Informationen werden wie geometrische Konstruktionselemente direkt in den Dokumenten gespeichert.</p>
</blockquote><p>iLogics können direkt aus Inventor heraus in einem bereitgestellten Editor erstellt und editiert werden. Als Programmiersprache kann VBA und <a href="https://de.wikipedia.org/wiki/Visual_Basic_.NET"  target="_blank" rel="noreferrer">VB.net</a> verwendet werden. Letztere ist in jedem Fall zu bevorzugen, da deutlich bessere Werkzeuge, bspw. zum Error-Handling zu Verfügung stehen. VBA-Erfahrung sammeln Konstrukteure in der Regel bereits dadurch, dass sie im Studium auf dem Strafplaneten Excel den Dienstgrad <em>Tabellenknecht</em> erwerben müssen. Wer also bereits Excel-Macros schreiben oder ein Grundverständnis der Thematik besitzt, wird mit iLogic zügig zurechtkommen.</p>
<p>Neben den Standardfunktionalitäten von Basic bringt iLogic zusätzlich nützliche <em>Shortcuts</em> mit, die das Arbeiten mit Bauteilen und Zeichnungen vereinfachen.</p>

<h3 class="relative group">iLogic Administration
    <div id="ilogic-administration" class="anchor"></div>
    
</h3>
<p>iLogics werden im Idealfall im Zusammenspiel mit Autodesk Vault administriert und zentral durch einen kompetenten Administrator verwaltet und den Nutzern zur Verfügung gestellt. So können sie mittels Berechtigungskonzepten gegen manipulation gesichert werden. Gleichzeitig ist so garantiert, dass der User stets den aktuellen Code in Verwendung hat.</p>
<p>Dieses Konzept bringt für kreative Anwender auch Nachteile mit sich. Die ausgerollten iLogics und das übergeordnete Verzeichnis sind schreibgeschützt. Ein schnelles Testen und Ausprobieren ist somit nicht möglich. Unmöglich ist es aber nicht. Man kann einfach einen weiteren Pfad auf dem Nutzerrechner erstellen, die iLogics dort ablegen und dieses Verzeichnis zusätzlich zum Standardpfad hinzuladen. Zur Einbindung eines iLogic-Verzeichnisses muss im Inventor der Menüpunkt <kbd>Tools &gt; Options &gt; iLogic Configuration</kbd> geklickt werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-basics/ilogic-external-dir-menu.png"
          alt="Externer Pfad in dem iLogics gespeichert sind"
        />
  
  
  </figure>
<p>Anschließend öffnet sich folgendes Fenster, in dem ein weiteres Verzeichnis hinzugefügt werden kann:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-basics/ilogic-external-dir.png"
          alt="Advanced iLogic Configuration Window"
        />
  
  
  </figure>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Inventor benötigt für iLogics stets eindeutige Dateinamen. Wenn im lokalen iLogic-Verzeichnis und im Standardverzeichnis eine iLogic mit gleichem Dateinamen liegt, führt dies zu Problemen.</span>
</div>


<h3 class="relative group">iLogic Browser
    <div id="ilogic-browser" class="anchor"></div>
    
</h3>
<p>Inventor bietet - neben dem Model Browser - die Möglichkeit den iLogic Browser einzublenden. Dazu muss auf das <strong>+</strong> Symbol neben dem Modellbrowser geklickt und der Eintrag <em>iLogic Browser</em> selektiert werden. Anschließend werden alle iLogics gelistet, auch die aus dem zuvor vom Nutzer manuell hinzugefügten Verzeichnis:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-basics/ilogic-browser.gif"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">Die Inventor API
    <div id="die-inventor-api" class="anchor"></div>
    
</h2>
<p>API steht für <em>Application Programming Interface</em>. Über diese API können alle Tasks, die sonst ein Konstrukteur mit Hilfe der grafischen Oberfläche ausführt, programmatisch erledigt werden. Autodesk stellt eine Übersicht aller Objekte zur Verfügung, die über die API genutzt werden können. Es gilt in erster Linie nicht zu erschrecken, da diese Übersicht selbst auf A0 ausgedruckt immer noch sehr unübersichtlich ist. Die API von Inventor unterliegt konstantem Wandel und wird mit jedem Major Release erweitert.</p>

<figure><a href="https://damassets.autodesk.net/content/dam/autodesk/www/pdfs/Inventor2022ObjectModel.pdf" target="_blank" class="inline-block">
        <img
          class="my-0 rounded-md nozoom"
          src="/posts/ilogic-basics/api-object-model.png"
          alt="Ausschnitt der Lautsprecher"
        />
  </a>
  
  </figure>
<p>Auf der <a href="https://help.autodesk.com/view/INVNTOR/2022/ENU/?guid=UserManualIndex"  target="_blank" rel="noreferrer">Autodesk Webseite</a> sind diverse Erläuterungen und Leitfäden zur API einsehbar.</p>

<h3 class="relative group">Objekte
    <div id="objekte" class="anchor"></div>
    
</h3>
<p>Betrachtet man das verlinkte PDF des <em>Inventor Object Model</em> genauer, erkennt man dass es unzählige Objekte gibt, auf die zugegriffen werden kann, bspw:</p>
<ul>
<li><strong>Dokumententyp:</strong> Bauteil, Baugruppe oder Zeichnung.</li>
<li><strong>Units of Measurement:</strong> Welche Einheiten werden im Bauteil verwendet, z.B. Inch oder Zentimeter.</li>
<li><strong>Materialien:</strong> Welches Material ist dem Bauteil zugewiesen.</li>
<li><strong>Application:</strong> Welche Version von Inventor in Verwendung ist.</li>
<li>&hellip;</li>
</ul>

<h3 class="relative group">Enums
    <div id="enums" class="anchor"></div>
    
</h3>
<p>Neben Objekten können sogenannte <strong>Enums</strong> genutzt werden. Enums bieten hilfreiche Methoden, um mit Objekten und ihren Werten zu interagieren. In diesem Teil der Serie sollen sie erst einmal keine Rolle spielen. In dem späteren Post <a href="/posts/ilogic-uom/">iLogic: Units of Measurement</a> werden sie anhand eines Beispiels mit Maßeinheiten (<em>Units of Measure</em>) erläutert.</p>

<h2 class="relative group">Debugging der Inventor API
    <div id="debugging-der-inventor-api" class="anchor"></div>
    
</h2>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>Beginnend mit Inventor 2021, liefert Autodesk mit dem Inventor-Installer kein VBA-Modul für Inventor mehr aus. Das Modul kann und muss für die folgenden Schritte manuell für die entsprechende Inventor-Version installiert werden.</p>
<p>Siehe: <a href="https://www.autodesk.com/support/technical/article/caas/tsarticles/ts/580m5V9igpBgk3WNek5Ydf.html"  target="_blank" rel="noreferrer">Download the Microsoft VBA module for Inventor</a></p>
</span>
</div>

<p>Der VBA-Editor kann mit der Tastenkombination <kbd>ALT</kbd> + <kbd>F11</kbd> aufgerufen werden. In diesem Editor werden, neben dem aktuell geöffneten Dokument, das Standard Anwendungsprojekt gelistet. Dieses kann (und wird auch sicherlich) sogenannte VBA-Module enthalten. Jedes Modul hat einen Anwendungszweck und Code der diesen repräsentiert. Möchte man nun einen Blick in die Inventor API werfen gibt es 2 Möglichkeiten:</p>
<ol>
<li><strong>Der nette Admin:</strong> Der Admin ist euch wohlgesonnen und integriert den anschließend folgenden VBA-Code direkt in einem Standard-Modul. Somit steht es global zur Verfügung und kann unabhängig des geöffneten Dokuments ausgeführt werden.</li>
<li><strong>Der uneinsichtige Admin:</strong> Der Admin sieht nicht ein, dem User eine Spielwiese zum Optimieren seiner Arbeitsabläufe zu bieten und legt kein globales Modul an. In diesem Fall kann man ein Alibi-Dokument erstellen und den folgenden Code dort im <em>DocumentProject</em> unter <code>ThisDocument</code> einfügen. Dazu einen Doppelklick auf <code>ThisDocument</code> ausführen.</li>
</ol>
<p><strong>Der benötigte Code:</strong></p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vb" data-lang="vb"><span class="line"><span class="cl"><span class="k">Sub</span> <span class="nf">HappyDebug</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oApp</span> <span class="ow">As</span> <span class="n">Application</span>
</span></span><span class="line"><span class="cl">    <span class="k">Set</span> <span class="n">oApp</span> <span class="o">=</span> <span class="n">ThisApplication</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">Dim</span> <span class="n">oDoc</span> <span class="ow">As</span> <span class="n">Document</span>
</span></span><span class="line"><span class="cl">    <span class="k">Set</span> <span class="n">oDoc</span> <span class="o">=</span> <span class="n">ThisApplication</span><span class="p">.</span><span class="n">ActiveDocument</span>
</span></span><span class="line"><span class="cl"><span class="k">End</span> <span class="k">Sub</span></span></span></code></pre></div></div>
<p><strong>Dieser Code macht folgendes:</strong></p>
<ol>
<li>Er deklaritert ein Objekt <code>oApp</code> vom Typ <code>Application</code> und weist diesem Objekt die Inventor Anwendung zu.</li>
<li>Er deklariert ein Objekt <code>oDoc</code> vom Typ <code>Document</code> und weist das aktiv offene Dokument im Inventor zu.</li>
</ol>
<p>Der Ausdruck <code>Dim</code> steht für <em>Dimension</em>, also den Platz den dieses Objekt im Speicher belegen wird. Dieser Code ermöglicht es also zum einen die Anwendung Inventor, wie auch das aktiv geöffnete Dokument zu bespielen.</p>
<p><strong>Nun müssen drei finale Schritte erfolgen:</strong></p>
<ol>
<li>Es muss jeweils ein Rechtsklick die Einträge <code>oApp</code> und <code>oDoc</code> ausgeführt und im Kontextmenü der Eintrag <code>Add Watch</code> gewählt werden. Damit wird es, während des Ausführens des VBA-Codes, ermöglicht live in diese zwei Objekt zu schauen.</li>
<li>Es muss ein Stopppunkt in Zeile 4 gesetzt werden. Dazu muss in der linken grauen Leiste vor dem Eintrag <code>End Sub</code> mit der linken Maustaste geklickt werden. Mit dem Setzen des Stopppunkts wird erreicht, dass der Code nur bis zu dieser Zeile ausgeführt wird und somit vor der Terminierung in das Objekt <code>oDoc</code> geschaut werden kann.</li>
<li>Nun muss lediglich noch der Code durch einen klick auf das Play Icon (<em>run</em>) ausgeführt werden.</li>
</ol>
<p>Hat alles funktioniert erscheint nun unterhalb des Codefensters ein Menüfenster mit der Bezeichnung <em>Watches</em>, in dem die Objekte <code>oApp</code> und <code>oDoc</code> auftauchen und ausgeklappt werden können. Dieses Fenster ist ggf. nicht sofort sichtbar. Oftmals ist lediglich der Codebereich sichtbar und man muss den Objektbrowser erst weiter nach unten ziehen, da er soweit unten am Bildschirmrand platziert ist, dass man seinen Inhalt nicht sehen kann.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ilogic-basics/happydebug.gif"
          alt="Animation zum Debuggin mittels VBA Editor"
        />
  
  
  </figure>
<p>Diese Methode eignet sich hervorragend um das aktive Dokument oder die Anwendung zu inspizieren. Es werden anschaulich Endpunkte, Werte und Datentyp aufgelistet, die anschließend in einer iLogic verwendet werden können. Somit ist die Basis für eine gut geschriebene iLogic gesetzt.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Inventor: Top-Down-Konstruktion</title>
      <link>https://jbetzen.net/posts/inventor-top-down-design/</link>
      <pubDate>Thu, 01 Sep 2022 01:41:00 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/inventor-top-down-design/</guid>
      <description>Das Thema Konstruieren mit Master Skizzen, auch &lt;strong&gt;Top-Down-Methode&lt;/strong&gt; genannt, ist erfahrungsgemäß nicht sonderlich gut im Weltnetz dokumentiert. Umso schöner ist es, dass Felix Rodermund sich auf seinem Youtube-Kanal die Zeit genommen hat um diesem Thema eine ganze Playlist zu widmen.</description>
      <content:encoded><![CDATA[ <p>Das Thema Konstruieren mit Master Skizzen, auch <strong>Top-Down-Methode</strong> genannt, ist erfahrungsgemäß nicht sonderlich gut im Weltnetz dokumentiert. Umso schöner ist es, dass <a href="https://r-kon.de/unternehmen_gruender.php"  target="_blank" rel="noreferrer">Felix Rodermund</a> sich auf seinem Youtube-Kanal <a href="https://www.youtube.com/channel/UCxexZqzlgc1E-YuETYdFiUw"  target="_blank" rel="noreferrer">R-Kon - 3D-CAD und Maschinenbau</a> die Zeit genommen hat um eine ganze Playlist diesem Thema zu widmen. Er zeigt anschaulich über mehrere Videos hinweg, welche Sonderfälle und methodischen Schritte im Gegensatz zur klassischen <em>Bottom-Up-Methode</em> eingehalten werden müssen.</p>

<h2 class="relative group">Bottom-Up vs Top-Down
    <div id="bottom-up-vs-top-down" class="anchor"></div>
    
</h2>
<p>Die beiden Bezeichnungen beschreiben die Konstruktionsweise in Baugruppen. Die klassische <strong>Bottom-Up-Methode</strong> beginnt mit der Konstruktion der Einzelteile, die am Ende zu einer Baugruppe zusammengeführt werden. Sämtliche Bemaßungen finden in den Einzelteilen statt - demnach erfolgen auch konstruktive Anpassungen direkt in den jeweiligen Bauteilen und oftmals direkt in mehreren Bauteilen. Erfahrungsgemäß ist diese Methode nach wie vor im Arbeitsalltag des Konstrukteurs vorherrschend.</p>
<p>Die <strong>Top-Down-Methode</strong> stellt diese Konzept auf den Kopf. Wie der Name bereits erahnen lässt werden Baugruppen in ihrer logischen und definierten Struktur nicht in den Einzelteilen bestimmt, sondern <em>von Oben herab</em>. Dies geschieht unter Hilfenahme einer einzigen steuernden Skizze, der Master-Skizze oder auch <em>Master Sketch</em> genannt. In dieser Skizze werden sämtlichen Maße der Einzelteile und ihrer Zusammenhänge in der übergeordneten Baugruppe definiert. Teile dieser Skizzen können dann wiederum in Bauteile <em>projiziert</em> werden. Autodesk Inventor hat für das Arbeiten mit steuernden Skizzen eine eigene Funktion namens <strong>Ableitung</strong> (<a href="https://help.autodesk.com/view/INVNTOR/2023/ENU/?guid=GUID-04363641-CCF9-4B2C-A3C4-AF676D6F7B1A"  target="_blank" rel="noreferrer">derived components</a>), die ausgiebig in der Videoreihe erläutert wird.<br>
Der Vorteil der Top-Down-Methode liegt auf der Hand: Ändert man bspw. den Durchmesser eines Stiftes, der durch drei Bauteile geführt wird, müssen bei der klassischen <em>Bottom-Up-Methode</em> auch alle drei Bauteile in ihren Maßen angepasst werden. Bei der <em>Top-Down-Methode</em> ändert man lediglich ein Maß in der Master-Skizze, das dann in beide Teile automatisch nachgezogen wird.</p>

<h2 class="relative group">Youtube Playlist
    <div id="youtube-playlist" class="anchor"></div>
    
</h2>


  

<figure class="nozoom"><a href="https://www.youtube.com/playlist?list=PLZnehly9NcJg6FHfbGESUXabst67oPS-G" class="inline-block"><img src="https://img.youtube.com/vi/ObaCM5vhklA/maxresdefault.jpg"></a>
</figure>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>Adafruit Macropad RP2040</title>
      <link>https://jbetzen.net/posts/macropad/</link>
      <pubDate>Wed, 20 Apr 2022 11:50:47 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/macropad/</guid>
      <description>Das Adafruit Macropad RP2040 kann mittels Circuitpython programmiert werden und vordefinierte Macros ausführen. Dieser Post behandelt, wie man das Macropad startklar macht, ihm ein deutsches Keyboard-Layout zuweist und ein funktionales Gehäuse druckt.</description>
      <content:encoded><![CDATA[ <p>Das <a href="https://learn.adafruit.com/adafruit-macropad-rp2040"  target="_blank" rel="noreferrer">Adafruit Macropad RP2040</a> basiert, wie der Name bereits andeutet, auf dem <a href="https://www.raspberrypi.com/products/rp2040/"  target="_blank" rel="noreferrer">RP2040</a> der <a href="/categories/raspberrypi/" >Raspberry Pi</a> Foundation. Es ist ein kleiner Mikrocontroller, der kinderleicht mit <a href="https://micropython.org/"  target="_blank" rel="noreferrer">Micropython</a> oder <a href="https://circuitpython.org/"  target="_blank" rel="noreferrer">Circuitpython</a> programmiert werden kann.</p>
<p>Dieser Post erläutert den Anwendungsfall eines klassischen Macropads, basierend auf diesem <a href="https://learn.adafruit.com/macropad-hotkeys"  target="_blank" rel="noreferrer">Guide von Adafruit</a> mit dem wesentlichen Unterschied, dass statt dem standardmäßigen US-Tastatur-Layout das Deutsche Layout verwendet wird. Somit können mit vordefinierten Macros Tastenkombinationen und Strings mit einem einfachen Klick auf einen der 12 Buttons an den angeschlossenen Computer gesendet werden. Ein Treiber ist nicht notwendig, da sich das Macropad als HID-Device ausweist.</p>

<h2 class="relative group">Setup
    <div id="setup" class="anchor"></div>
    
</h2>
<p>Auch wenn Adafruit <a href="https://learn.adafruit.com/adafruit-macropad-rp2040"  target="_blank" rel="noreferrer">viele</a> <a href="https://learn.adafruit.com/minecraft-turbopad"  target="_blank" rel="noreferrer">gute</a> <a href="https://learn.adafruit.com/macropad-2fa-totp-authentication-friend"  target="_blank" rel="noreferrer">Tutorials</a> für das Macropad auf ihrer Webseite anbietet, zeichnete sich schnell ab, dass insbesondere die Zuweisung eines Tastatur Layouts fernab des amerikanischen US-Layouts eine Hürde für Beginner darstellt. Um diesen Showstopper zu überwinden sind im Folgenden alle Schritte erläutert, die zu einem funktionalem Macropad mit deutschem Tastaturlayout und den entsprechenden Keycodes führen.</p>

<h3 class="relative group">Circuitpython
    <div id="circuitpython" class="anchor"></div>
    
</h3>
<p>Der einfachste Weg das Macropad lauffähig zu machen erfolgt mittels Circuitpython. Sämtliche Adafruit Tutorials greifen auf diese Programmierumgebung zurück und können daher hervorragen als Basis für eigene Vorhaben dienen.</p>
<p>Für das Macropad stehen auf der Webseite von Circuitpython eine <a href="https://circuitpython.org/board/adafruit_macropad_rp2040/"  target="_blank" rel="noreferrer">eigene Firmware</a> zur Verfügung. Diese muss heruntergeladen werden. Anschließend muss das Macropad mit dem heimischen Computer per USB verbunden und in den <a href="https://learn.adafruit.com/adafruit-macropad-rp2040/circuitpython"  target="_blank" rel="noreferrer">Bootloader</a> gebootet werden. Dies erfolgt durch ein gedrückt halten des Rotary Encoders und einem Druck auf die seitliche Reset-Taste am Macropad. Es erscheint anschließend ein Laufwerk im Dateiexplorer mit dem Namen <code>RPI-RP2</code>. Die heruntergeladene Firmwaredatei muss nun auf dieses Laufwerk kopiert werden. Das Macropad startet anschließend neu und es erscheint ein weiteres Laufwerk mit dem Namen <code>CIRCUITPYTHON</code>. Für alle folgenden Schritte muss diese Laufwerk sichtbar und das Macropad mit dem Computer verbunden sein.</p>

<h3 class="relative group">Circup
    <div id="circup" class="anchor"></div>
    
</h3>
<p><a href="https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup"  target="_blank" rel="noreferrer">Circup</a> ist ein nützliches Kommandozeilentool, das unkompliziert alle benötigten Circuitpython-Bibliotheken installieren kann. Es benötigt ein installiertes Python3 und den Paketmanager Pip. Nach der Installation können die notwendigen Bibliotheken installiert werden.</p>

<h3 class="relative group">Libraries installieren
    <div id="libraries-installieren" class="anchor"></div>
    
</h3>
<p>Die benötigten Bibliotheken werden direkt auf dem angeschlossenen Macropad installiert. Die Downloads sind allesamt vorkompilierter Python-Code im <code>.mpy</code>-Format, sprich sie können anschließend nicht als Textdateien bearbeitet werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">circup install adafruit_bitmap_font adafruit_display_shapes adafruit_display_text adafruit_hid adafruit_debouncer adafruit_pixelbuf adafruit_simple_text_display adafruit_ticks adafruit_neopixel</span></span></code></pre></div></div>
<p>Zusätzlich wird noch die eigentliche Bibliothek für das Macropad benötigt. Da in dieser Datei das Tastaturlayout definiert wird, muss in diesem Fall eine nicht kompilierte Pythondatei heruntergeladen werden. Die kann über den Parameter <code>--py</code> erreicht werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">circup install --py adafruit_macropad</span></span></code></pre></div></div>

<h2 class="relative group">Deutsches Tastatur-Layout
    <div id="deutsches-tastatur-layout" class="anchor"></div>
    
</h2>
<p>Im finalen Schritt fehlen nur noch die Dateien, die das deutsche Tastaturlayout definieren. Diese können als Bundle neben vielen anderen Layouts installiert werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">circup bundle-add Neradoc/Circuitpython_Keyboard_Layouts</span></span></code></pre></div></div>
<p>gefolgt von dem Befehl zur eigentlichen Installation des deutschen Layouts:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">circup install keyboard_layout_win_de keycode_win_de</span></span></code></pre></div></div>
<p>Die heruntergeladenen Layouts bestehen aus zwei Dateien:</p>
<ol>
<li>Der Layoutdefinition, also der Tastenanordnung auf der Tastatur. In diesem Beispiel ist es die Datei <code>keyboard_layout_win_de.py</code>.</li>
<li>Den Keycodes, also welcher Buchstabe ausgegeben werden soll, wenn die Taste auf dem vordefinierten Layout gedrückt wird. In diesem Beispiel ist es die Datei <code>keycode_win_de.py</code>.</li>
</ol>

<h2 class="relative group">Macropad Code
    <div id="macropad-code" class="anchor"></div>
    
</h2>
<p>Das deutsche Tastaturlayout funktioniert nur durch die Anpassung der Datei <code>lib/adafruit_macropad.py</code>. Die <a href="https://github.com/adafruit/Adafruit_CircuitPython_MacroPad/blob/8ef52f0999513a4991e90c9679c3f6461cb3ca7d/adafruit_macropad.py"  target="_blank" rel="noreferrer">Originaldatei von Adafruit</a> kann hier bezogen werden und muss wie in <a href="https://github.com/Neradoc/Circuitpython_Keyboard_Layouts/blob/3b4003ea71b4a7b2997652baf9c1d66528888d5c/ADAFRUIT-MACROPAD.md"  target="_blank" rel="noreferrer">diesem Pull Request beschrieben</a> angepasst werden.</p>
<p>Weiterhin benötigt wird die Datei <code>code.py</code>, die den eigentlichen Programmcode enthält. Sie steht bei Github zum <a href="https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/b29b64e5236688bafdd3fe9be04da6117f14aea3/Macropad_Hotkeys/code.py"  target="_blank" rel="noreferrer">Download</a> bereit.</p>
<p>Damit ist das Macropad mit deutschem Tastaturlayout und den zugehörigen Keycodes einsatzbereit. Es fehlen lediglich noch die Makros. Diese müssen in einem Ordner namens <code>macros/</code> auf dem Macropad abgelegt werden. Sie werden alphabetisch sortiert geladen und können mit dem Drehschalter auf dem Pad gewechselt werden. Zur Festlegung der Reihenfolge empfiehlt es sich also einen numerischen Prefix im Dateinamen zu vergeben.</p>
<p>Die Ordnerstruktur auf dem Macropad sollte anschließend so aussehen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CIRCUITPYTHON/
</span></span><span class="line"><span class="cl">  │
</span></span><span class="line"><span class="cl">  ├── code.py
</span></span><span class="line"><span class="cl">  ├── lib/
</span></span><span class="line"><span class="cl">  │   ├── adafruit_bitmap_font/
</span></span><span class="line"><span class="cl">  │   ├── adafruit_display_shapes/
</span></span><span class="line"><span class="cl">  │   ├── adafruit_display_text/
</span></span><span class="line"><span class="cl">  │   ├── adafruit_hid/
</span></span><span class="line"><span class="cl">  │   ├── adafruit_midi/
</span></span><span class="line"><span class="cl">  │   ├── adafruit_debouncer.mpy
</span></span><span class="line"><span class="cl">  │   ├── adafruit_macropad.py
</span></span><span class="line"><span class="cl">  │   ├── adafruit_pixelbuf.mpy
</span></span><span class="line"><span class="cl">  │   ├── adafruit_simple_text_display.mpy
</span></span><span class="line"><span class="cl">  │   ├── adafruit_ticks.mpy
</span></span><span class="line"><span class="cl">  │   ├── keyboard_layout_win_de.mpy
</span></span><span class="line"><span class="cl">  │   ├── keycode_win_de.mpy
</span></span><span class="line"><span class="cl">  │   └── neopixel.mpy
</span></span><span class="line"><span class="cl">  └── macros/
</span></span><span class="line"><span class="cl">      ├── 0_windows.py
</span></span><span class="line"><span class="cl">      └── 1_firefox.py</span></span></code></pre></div></div>

<h2 class="relative group">Beispielmakros
    <div id="beispielmakros" class="anchor"></div>
    
</h2>
<p>Im folgenden sind zwei Beispielmakros für Windowsfunktionen und den Firefox Browser erläutert. Die Bezeichnung im Feld <code>'name':</code> wird oben auf dem Display des Macropads angezeigt. Die Benennung der einzelnen Makrofunktionen ebenfalls. Die Namen der Funktionen sollte <em>nicht</em> länger als 6 Buchstaben sein.</p>

<h3 class="relative group">Windows Funktionen
    <div id="windows-funktionen" class="anchor"></div>
    
</h3>
<p>Die Datei <code>0_windows.py</code>könnte beispielsweise so aussehen. Sie verwendet sowohl Tastenkombinationen, als auch sogenannte <em>Consumer Control Codes</em>. Letztere sind spezifische Eingabekommandos, wie bspw. das erhöhen oder reduzieren der Lautstärke oder der Bildschirmhelligkeit oder die Musiksteuerung. Alle implementierten <em>Consumer Control Codes</em> sind <a href="https://learn.adafruit.com/macropad-hotkeys/mouse-and-media-controls"  target="_blank" rel="noreferrer">hier</a> dokumentiert.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># MACRO ..... :  Windows</span>
</span></span><span class="line"><span class="cl"><span class="c1"># DESCRIPTION :  Basic Windows Functions</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">keycode_win_de</span> <span class="kn">import</span> <span class="n">Keycode</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">adafruit_hid.consumer_control_code</span> <span class="kn">import</span> <span class="n">ConsumerControlCode</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;name&#39;</span> <span class="p">:</span> <span class="s1">&#39;WINDOWS&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;macros&#39;</span> <span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># COLOR    LABEL    KEY SEQUENCE</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 1st row ----------</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x000020</span><span class="p">,</span> <span class="s1">&#39;Vol-&#39;</span><span class="p">,</span> <span class="p">[[</span><span class="n">ConsumerControlCode</span><span class="o">.</span><span class="n">VOLUME_DECREMENT</span><span class="p">]]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x000020</span><span class="p">,</span> <span class="s1">&#39;Mute&#39;</span><span class="p">,</span> <span class="p">[[</span><span class="n">ConsumerControlCode</span><span class="o">.</span><span class="n">MUTE</span><span class="p">]]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x000020</span><span class="p">,</span> <span class="s1">&#39;Vol+&#39;</span><span class="p">,</span> <span class="p">[[</span><span class="n">ConsumerControlCode</span><span class="o">.</span><span class="n">VOLUME_INCREMENT</span><span class="p">]]),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 2nd row ----------</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x663300</span><span class="p">,</span> <span class="s1">&#39;TSKMGR&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">SHIFT</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">ESCAPE</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x663300</span><span class="p">,</span> <span class="s1">&#39;DSKTP&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">WINDOWS</span><span class="p">,</span> <span class="s1">&#39;d&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x663300</span><span class="p">,</span> <span class="s1">&#39;EXPL&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">WINDOWS</span><span class="p">,</span> <span class="s1">&#39;e&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 3rd row ----------</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x380e0e</span><span class="p">,</span> <span class="s1">&#39;SelAll&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="s1">&#39;a&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x02180B</span><span class="p">,</span> <span class="s1">&#39;CBHist&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">WINDOWS</span><span class="p">,</span> <span class="s1">&#39;v&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x663300</span><span class="p">,</span> <span class="s1">&#39;SNIPPT&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">WINDOWS</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="s1">&#39;%windir%\system32\SnippingTool.exe&#39;</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">RETURN</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 4th row ----------</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x380e0e</span><span class="p">,</span> <span class="s1">&#39;Cut&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="s1">&#39;x&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x380e0e</span><span class="p">,</span> <span class="s1">&#39;Copy&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="s1">&#39;c&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x380e0e</span><span class="p">,</span> <span class="s1">&#39;Paste&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="s1">&#39;v&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Encoder button ---</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x000000</span><span class="p">,</span> <span class="s1">&#39;NoSkype&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">WINDOWS</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">ESCAPE</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Erläuterung der einzelnen Funktionen:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Funktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Vol-</td>
          <td>Verringert die Lautstärke</td>
      </tr>
      <tr>
          <td>Mute</td>
          <td>Toggle für das Stummschalten der Lautstärke</td>
      </tr>
      <tr>
          <td>Vol+</td>
          <td>Erhöht die Lautstärke</td>
      </tr>
      <tr>
          <td>TSKMGR</td>
          <td>Startet den Taskmanager</td>
      </tr>
      <tr>
          <td>DSKTP</td>
          <td>Toggle um alle Fenster auszublenden und den Desktop anzuzeigen</td>
      </tr>
      <tr>
          <td>EXPL</td>
          <td>Öffnet den Windows Explorer</td>
      </tr>
      <tr>
          <td>SelAll</td>
          <td>Markiert den gesamten Text</td>
      </tr>
      <tr>
          <td>CBHist</td>
          <td>Zeigt den Verlauf der Einträge in der Zwischenablage</td>
      </tr>
      <tr>
          <td>SNIPPT</td>
          <td>Startet das Snipping Tool</td>
      </tr>
      <tr>
          <td>Cut</td>
          <td>Markierten Text ausschneiden</td>
      </tr>
      <tr>
          <td>Copy</td>
          <td>Kopiert den markierten Text in die Zwischenablage</td>
      </tr>
      <tr>
          <td>Paste</td>
          <td>Fügt den Text aus der Zwischenablage ein</td>
      </tr>
      <tr>
          <td>Quit</td>
          <td>Schließt das aktive Programm</td>
      </tr>
  </tbody>
</table>
</div>

<h3 class="relative group">Firefox Browser
    <div id="firefox-browser" class="anchor"></div>
    
</h3>
<p>Die Datei <code>1_firefox.py</code>könnte beispielsweise so aussehen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># MACRO ..... : Firefox</span>
</span></span><span class="line"><span class="cl"><span class="c1"># DESCRIPTION : Firefox Hotkeys incl. Bitwarden Add-On</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">keycode_win_de</span> <span class="kn">import</span> <span class="n">Keycode</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;name&#39;</span> <span class="p">:</span> <span class="s1">&#39;FIREFOX&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;macros&#39;</span> <span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># COLOR    LABEL    KEY SEQUENCE</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 1st row ----------</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x02180B</span><span class="p">,</span> <span class="s1">&#39;+ TAB&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="s1">&#39;t&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x380e0e</span><span class="p">,</span> <span class="s1">&#39;+ WDW&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="s1">&#39;n&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x000020</span><span class="p">,</span> <span class="s1">&#39;BW PW&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">SHIFT</span><span class="p">,</span> <span class="s1">&#39;l&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 2nd row ----------</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x02180B</span><span class="p">,</span> <span class="s1">&#39;- TAB&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x380e0e</span><span class="p">,</span> <span class="s1">&#39;- WDW&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">SHIFT</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x000020</span><span class="p">,</span> <span class="s1">&#39;BW 2F&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">SHIFT</span><span class="p">,</span> <span class="s1">&#39;l&#39;</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="o">-</span><span class="n">Keycode</span><span class="o">.</span><span class="n">SHIFT</span><span class="p">,</span> <span class="s1">&#39;v&#39;</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">ENTER</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 3rd row ----------</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x404000</span><span class="p">,</span> <span class="s1">&#39;&lt; Back&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">ALT</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">LEFT_ARROW</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x404000</span><span class="p">,</span> <span class="s1">&#39;Reload&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">F5</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x404000</span><span class="p">,</span> <span class="s1">&#39;Forw &gt;&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">ALT</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">RIGHT_ARROW</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 4th row ----------</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x121212</span><span class="p">,</span> <span class="s1">&#39;&lt; Tab&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">PAGE_UP</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x121212</span><span class="p">,</span> <span class="s1">&#39;Tab &gt;&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">PAGE_DOWN</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x002000</span><span class="p">,</span> <span class="s1">&#39;DL&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">CONTROL</span><span class="p">,</span> <span class="s1">&#39;j&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Encoder button ---</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="mh">0x000000</span><span class="p">,</span> <span class="s1">&#39;Quit&#39;</span><span class="p">,</span> <span class="p">[</span><span class="n">Keycode</span><span class="o">.</span><span class="n">ALT</span><span class="p">,</span> <span class="n">Keycode</span><span class="o">.</span><span class="n">F4</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Erläuterung der einzelnen Funktionen:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Funktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>+ TAB</td>
          <td>Öffnet einen neuen Tab</td>
      </tr>
      <tr>
          <td>+ WDW</td>
          <td>Öffnet ein neues Fenster</td>
      </tr>
      <tr>
          <td>BW PW</td>
          <td>Setzt Passwörter aus dem Bitwarden Add-On ein</td>
      </tr>
      <tr>
          <td>- TAB</td>
          <td>Schließt den aktiven Tab</td>
      </tr>
      <tr>
          <td>- WDW</td>
          <td>Schließt das aktive Browser-Fenster</td>
      </tr>
      <tr>
          <td>BW 2F</td>
          <td>Setzt Passwörter aus dem Bitwarden Add-On inkl. 2Fa ein</td>
      </tr>
      <tr>
          <td>&lt; Back</td>
          <td>Navigiert zurück in der Browserhistory</td>
      </tr>
      <tr>
          <td>Forw &gt;</td>
          <td>Navigiert vor in der Browserhistory</td>
      </tr>
      <tr>
          <td>Reload</td>
          <td>Lädt den aktiven Tab neu</td>
      </tr>
      <tr>
          <td>&lt; Tab</td>
          <td>Wechselt zum vorherigen geöffneten Tab</td>
      </tr>
      <tr>
          <td>Tab &gt;</td>
          <td>Wechselt zum nächsten geöffneten Tab</td>
      </tr>
      <tr>
          <td>DL</td>
          <td>Wechselt zu den Downloads</td>
      </tr>
      <tr>
          <td>Quit</td>
          <td>Schließt den Firefox Browser</td>
      </tr>
  </tbody>
</table>
</div>

<h2 class="relative group">Beispielcode als Download
    <div id="beispielcode-als-download" class="anchor"></div>
    
</h2>
<p>Der oben dargestellte Beispielcode mit deutschem Tastaturlayout und samt der zwei beschriebenen Makros kann <a href="https://github.com/schneekluth/adafruit_macropad_de/archive/refs/heads/main.zip"  target="_blank" rel="noreferrer">auf Github</a> als Zip-Archiv heruntergeladen werden. Der Inhalt des Archivs kann direkt auf das Makropad kopiert werden und wird automatisch ausgeführt.</p>

<h3 class="relative group">RGB Hex Farbcodes
    <div id="rgb-hex-farbcodes" class="anchor"></div>
    
</h3>
<p>Als kleines Helferlein gibt es noch eine Tabelle mit einer kleinen Auswahl an Farbcodes für die Zuweisung der RGB-LEDs unterhalb der einzelnen Switches. Diese Farbcodes wurden von mir verwendet:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Farbe</th>
          <th>Dunkel</th>
          <th>Hell</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Blau</td>
          <td>0x000020</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Grün</td>
          <td>0x002000</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Rot</td>
          <td>0x200000</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Gelb</td>
          <td>0x404000</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Orange</td>
          <td>0x663300</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Cyan</td>
          <td>0x02180B</td>
          <td>0x0de05e</td>
      </tr>
      <tr>
          <td>Rosa</td>
          <td>0x380e0e</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Lila</td>
          <td>0x1d0320</td>
          <td>0x770de0</td>
      </tr>
      <tr>
          <td>Grau</td>
          <td>0x121212</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Dunkel</td>
          <td>0x202000</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Schwarz</td>
          <td>0x000000</td>
          <td>-</td>
      </tr>
  </tbody>
</table>
</div>
<p>Es gibt als eine weitere gute Referenz <a href="https://forum.turris.cz/t/led-color-code-list/1045"  target="_blank" rel="noreferrer">diesen Blogpost</a> für zusätzliche Farben.</p>

<h2 class="relative group">Gehäuse drucken
    <div id="gehäuse-drucken" class="anchor"></div>
    
</h2>
<p>Ich habe in Autodesk Inventor ein <a href="https://www.thingiverse.com/thing:5361974"  target="_blank" rel="noreferrer">eigenes Gehäuse</a> entworfen. Dieses enthält Ausschnitte für sämtliche Anschlüsse und die seitliche Reset-Taste. Es empfiehlt sich jedoch einen Raft um die Grundfläche zu drucken, da sonst Warping-Effekte auftraten. Die Oberseite ist aus Gründen der Ergonomie um 9° geneigt.</p>
<p>Mit den folgenden Druckoptionen dauert der Druck ca. 4 Stunden:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th style="text-align: center">Infill</th>
          <th style="text-align: center">Resolution</th>
          <th style="text-align: center">Support</th>
          <th style="text-align: center">Raft</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">10%</td>
          <td style="text-align: center">0.2mm</td>
          <td style="text-align: center">Nein</td>
          <td style="text-align: center">Ja</td>
      </tr>
  </tbody>
</table>
</div>


  
  <div class="width-patch"></div>
<div id="gallery-0d527578898b095fe669252033b9a43a" class="gallery">
  
  <img src="/posts/macropad/macropad_stand.jpg" class="grid-w50" />
  <img src="/posts/macropad/macropad_stand.png" class="grid-w50" />s
</div>

<p>Viel Spaß mit dem Macropad!</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>ESP32-CAM mit ESPHome</title>
      <link>https://jbetzen.net/posts/esp32-cam/</link>
      <pubDate>Fri, 18 Mar 2022 14:21:34 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/esp32-cam/</guid>
      <description>Wer auf der Suche nach einer kostengünstigen experimentellen Webcam ist, wird mit der ESP32-CAM fündig. Diese basiert auf dem Microcontroller ESP32 und lässt sich kinderleicht mittels ESPHome in Homeassistant einbinden.</description>
      <content:encoded><![CDATA[ <p>Mit der ESP32-CAM ist eine günstige internetfähige Webcam auf dem Markt positioniert. Sie basiert auf dem <a href="/tags/esp32/" >ESP32</a> und lässt sich daher einfach mit <a href="/categories/esphome/" >ESPHome</a> in Homeassistant integrieren. Die kosten für die kleine Kamera belaufen sich bei <a href="https://de.aliexpress.com/wholesale?catId=0&amp;initiative_id=AS_20220318053930&amp;SearchText=esp32&#43;cam"  target="_blank" rel="noreferrer">Aliexpress</a> lediglich auf 5-6€.</p>
<p>Das Vorgehen ist einfach erklärt. Mit dem <a href="https://www.home-assistant.io/integrations/esphome/"  target="_blank" rel="noreferrer">ESPHome Add-On</a> wird eine Firmware für den ESP32 generiert und diese mittels eines <a href="https://www.amazon.de/s?k=usb&#43;ttl"  target="_blank" rel="noreferrer">USB-TTL-Adapters</a> oder alternativ einem <a href="https://www.amazon.de/LYEAA-ESP32-CAM-MB-Seriell-Adapter-Spannungsregler-Schwarz/dp/B0CVTHZFJP"  target="_blank" rel="noreferrer">USB-Adapter-Board</a> auf den Microcontroller geflasht. Das Flashen funktioniert seit kurzem reibungslos direkt über einen Webkit-basierten Browser, also Chrome oder Safari und kann daher auch von unbedarften Users einfach vorgenommen werden. Genaueres dazu kann <a href="https://esphome.github.io/esp-web-tools/"  target="_blank" rel="noreferrer">hier</a> nachgelesen werden.</p>

<h2 class="relative group">ESPHome: Firmware generieren
    <div id="esphome-firmware-generieren" class="anchor"></div>
    
</h2>
<p>Über das ESPHome Add-On muss eine neue Firmware generiert werden. Das Add-On bietet dazu einen Wizard, der den Microcontrollertypen, Ausführungen, Gerätenamen und WLan-Credentials abfragt.</p>
<p>Der Platzhalter <code>YOUR_GENERATED_ID</code> muss durch eine selbstgenerierte ID ersetzt werden. Diese kann direkt auf der <a href="https://esphome.io/components/api.html#configuration-variables"  target="_blank" rel="noreferrer">ESPHome Webseite</a> generiert und kopiert werden.</p>
<p>Die Config sollte anschließend wie folgt aussehen. Neben der Camera wird noch die eingebaute LED als eigene Entity in Homeassistant bereitgestellt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">esphome</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">gockelcam</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">ESP32</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">board</span><span class="p">:</span><span class="w"> </span><span class="l">esp32cam</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">wifi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ssid</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret wifi_ssid</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret wifi_passwd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Enable fallback hotspot (captive portal) in case wifi connection fails</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ap</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ssid</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Gockelcam Fallback Hotspot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret ap_passwd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">captive_portal</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Enable logging</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">logger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Enable Home Assistant API</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">encryption</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;YOUR_GENERATED_ID&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ota</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret ota_passwd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">esp32_camera</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">external_clock</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pin</span><span class="p">:</span><span class="w"> </span><span class="l">GPIO0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">frequency</span><span class="p">:</span><span class="w"> </span><span class="l">20MHz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">i2c_pins</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sda</span><span class="p">:</span><span class="w"> </span><span class="l">GPIO26</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">scl</span><span class="p">:</span><span class="w"> </span><span class="l">GPIO27</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">data_pins</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vsync_pin</span><span class="p">:</span><span class="w"> </span><span class="l">GPIO25</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">href_pin</span><span class="p">:</span><span class="w"> </span><span class="l">GPIO23</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pixel_clock_pin</span><span class="p">:</span><span class="w"> </span><span class="l">GPIO22</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">power_down_pin</span><span class="p">:</span><span class="w"> </span><span class="l">GPIO32</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">horizontal_mirror</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vertical_flip</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">gockelcam</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">output</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">gpio</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pin</span><span class="p">:</span><span class="w"> </span><span class="l">GPIO4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">gpio_4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">light</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">binary</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">output</span><span class="p">:</span><span class="w"> </span><span class="l">gpio_4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">gockelcam light</span></span></span></code></pre></div></div>
<p>Zur Verwendung einer <code>secrets.yaml</code>-Datei mit ESPHome kann dieser Post erläuternd zu Rate gezogen werden:</p>





  
  
    
  
  


  <section class="space-y-10 w-full">
    
    











  
  
  








  
  
    

    
    
      
      
        
      
        
      
        
      
    

    
    

    
    
      
        
        
      
    
  



<article class="article-link--shortcode flex flex-col md:flex-row relative">
  
    <div class="flex-none relative overflow-hidden  thumbnail-shadow md:mr-7 thumbnail">
      <img
        src="/posts/esphome-btle-hub/featured.png"
        role="presentation"
        loading="lazy"
        decoding="async"
        class="not-prose absolute inset-0 w-full h-full object-cover">
    </div>
  
  <div class=" mt-3 md:mt-0">
    <header class="items-center text-start text-xl font-semibold">
      <a
        
          href="/posts/esphome-btle-hub/"
        
        class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
        <h2>
          ESPHome Bluetooth Low Energy Hub
          
        </h2>
      </a>
      
      
    </header>
    <div class="text-sm text-neutral-500 dark:text-neutral-400">
      







  

  
  
  
    
  

  

  
    
  

  
    
  

  
    
  

  
    
  

  

  

  

  

  


  <div class="flex flex-row flex-wrap items-center">
    
    
      <time datetime="2022-01-13T23:11:40&#43;01:00">13.01.2022</time><span class="px-2 text-primary-500">&middot;</span><time datetime="2025-08-07T00:00:00&#43;00:00">Aktualisiert: 07.08.2025</time><span class="px-2 text-primary-500">&middot;</span><span>1121 Wörter</span><span class="px-2 text-primary-500">&middot;</span><span title="Lesezeit">6 min</span>
    

    
    
  </div>

  

  
  
    <div class="flex flex-row flex-wrap items-center">
      
        
      
        
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/esphome/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    ESPHome
  </span>
</span>

                </a>
              
            
            
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/bluetooth/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    Bluetooth
  </span>
</span>

                </a>
              
            
            
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/xiaomi/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    Xiaomi
  </span>
</span>

                </a>
              
            
            
          
        
      
        
      
        
          
            
              
            
            
          
            
              
            
            
          
            
              
            
            
          
            
              
            
            
          
        
      
        
          
            
              
            
            
          
        
      
    </div>
  


    </div>
    
      
      <div
        class="article-link__summary prose dark:prose-invert max-w-fit mt-1 ">
        Dieser Post beschreibt, wie man mittels ESPHome und einem ESP32 Microcontroller ein Bluetooth Low Energy Hub aufsetzt, das alle Bluetooth IoT-Geräte ausliest und als Broker an Homeassistant sendet.
      </div>
    
  </div>
</article>

  </section>


<p>Das initiale <em>Flashen</em> der Firmware muss einmalig über eine Kabelverbindung erfolgen. Anschließend kann die Konfiguration einfach und unkompliziert <em>Over the air</em> angepasst werden.</p>
<p>Wurden alle Schritte erfolgreich durchlaufen erkennt Homeassistant automatisch die neue ESPHome-Node und bietet an diese einzubinden. Anschließend stehen zwei neue <em>Entities</em> zur Verfügung:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/esp32-cam/esp32-cam-entities.png"
          alt=""
        />
  
  
  </figure>

<h2 class="relative group">USB-Stromversorgung
    <div id="usb-stromversorgung" class="anchor"></div>
    
</h2>
<p>Da das ESP32-CAM-Modul keinen eigenen Micro-USB-Anschluss auf der Platine enthält, empfiehlt es sich einen altes USB-Ladegerät am Steckerende abzutrennen, die einzelnen Kabel freizulegen und direkt mit der Platinenanschlüssen zu verlöten oder auf ein <em>USB-Breakout-Board</em> zurückzugreifen, wie bspw. in diesem <a href="https://www.instructables.com/Wireless-Security-Camera-in-a-Matchbox/"  target="_blank" rel="noreferrer">Post</a> beschrieben.</p>

<h2 class="relative group">Gehäuse drucken
    <div id="gehäuse-drucken" class="anchor"></div>
    
</h2>
<p>Da eine nackte Platine nicht sonderlich ansehnlich erscheint, kann der heimische <a href="/categories/3d-drucker/" >3D-Drucker</a> angeworfen werden und bei Thingiverse ein <a href="https://www.thingiverse.com/search?q=esp32-cam&amp;type=things&amp;sort=relevant"  target="_blank" rel="noreferrer">passendes Gehäuse</a> gewählt werden. Die vorhandene Auswahl ist groß.</p>

<h2 class="relative group">ESP32-CAM in Lovelace UI
    <div id="esp32-cam-in-lovelace-ui" class="anchor"></div>
    
</h2>
<p>Ich habe meine ESP32-CAM mit einer <a href="https://www.home-assistant.io/dashboards/picture-glance/"  target="_blank" rel="noreferrer">Picture Glance Card</a> in die UI eingebunden. Diese erlaubt neben dem direkten betrachten des Videostream auch das einfach Ein- und Ausschalten der LED auf der Platine des ESP32. Der Code dafür:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">picture-glance</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Gockelcam</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">camera_view</span><span class="p">:</span><span class="w"> </span><span class="l">auto</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">camera_image</span><span class="p">:</span><span class="w"> </span><span class="l">camera.gockelcam</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">light.gockelcam_light</span></span></span></code></pre></div></div>
<p>In der Lovelace UI sieht dies dann folgendermaßen aus. Die LED kann mit einem Klick auf das Lampen-Icon einfach geschaltet werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/esp32-cam/esp32-cam-lovelace.png"
          alt=""
        />
  
  
  </figure>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Morgendlicher Newsflash</title>
      <link>https://jbetzen.net/posts/morgendlicher-newsflash/</link>
      <pubDate>Sun, 13 Mar 2022 11:04:11 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/morgendlicher-newsflash/</guid>
      <description>Wer werktags morgens in die Küche läuft, kann sich im Handumdrehen einen morgendlichen Newsflash, bspw. Tagesschau in 100 Sekunden über einen Media Player anspielen lassen.</description>
      <content:encoded><![CDATA[ <p>Wer in seinem trauten Heim in jedem Raum einen Bewegungssensor hat, kann diese nicht nur zum automatischen Einschalten von Beleuchtung verwenden, wie bspw. in <a href="/categories/motion-sensor/" >diesen Posts</a> beschrieben.</p>
<p>In diesem Post erkläre ich, wie man einen Bewegungssensor nutzen kann, um automatisiert die <em>Tagesschau in 100 Sekunden</em> beim morgendlichen Betreten der Küche abzuspielen.</p>

<h2 class="relative group">Hardware
    <div id="hardware" class="anchor"></div>
    
</h2>
<p>Ich verwende bei mir die Motion-Sensoren von Philips Hue in meiner Küche und dort steht auch ein <a href="/tags/bose-soundtouch/" >Bose Soundtouch 10</a> zur räumlichen Beschallung. Für das folgende Beispiel werden diese <em>Entity IDs</em> verwendet:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Device</th>
          <th>Entity ID</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bose Soundtouch 10</td>
          <td>media_player.soundtouch_kueche</td>
      </tr>
      <tr>
          <td>Hue Motion Sensor</td>
          <td>binary_sensor.hue_motion_kueche</td>
      </tr>
  </tbody>
</table>
</div>

<h2 class="relative group">Setup Bose Mediaplayer
    <div id="setup-bose-mediaplayer" class="anchor"></div>
    
</h2>
<p>Die Bose Soundtouch App erlaubt das Belegen von sechs Shortcuts, die schnell und unkompliziert über die physischen Buttons auf dem Lautsprecher und der zugehörigen Fernbedienung aktiviert werden können. Gleichzeitig erlaubt die <a href="https://www.home-assistant.io/integrations/soundtouch/"  target="_blank" rel="noreferrer">Bose Soundtouch Integration</a> für Homeassistant die Shortcuts automatisiert abzuspielen. In Kombination mit der <a href="https://www.home-assistant.io/integrations/spotify/"  target="_blank" rel="noreferrer">Spotify Integration</a> lassen sich so leicht vordefinierte Songs, Playlisten und Podcasts automatisiert wiedergeben.</p>
<p>Ich habe den täglichen &ldquo;Podcast&rdquo; der <em>Tagesschau in 100 Sekunden</em> auf den <code>Shortcut 4</code> gelegt.</p>

<h2 class="relative group">Die Automation
    <div id="die-automation" class="anchor"></div>
    
</h2>
<p>Wenn morgens die Küche betreten wird, soll automatisch die Wiedergabe der <em>Tagesschau in 100 Sekunden</em> erfolgen, jedoch nur unter bestimmten Bedingungen (<em>Conditions</em>):</p>
<ul>
<li>Die Wiedergabe soll ausschließlich <em>nach</em> einer vordefinierten Uhrzeit erfolgen. In diesem Beispiel wird der Zeitpunkt nach <code>05:30h</code> gewählt.
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">after</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;05:30:00&#39;</span></span></span></code></pre></div></div>
</li>
<li>Die Wiedergabe soll nur einmal am Tag erfolgen und nicht jedes mal, wenn die Küche betreten wird. Dafür wird auf das Attribute <code>last_triggered</code> der Automation zurückgegriffen, wie im <a href="https://community.home-assistant.io/t/only-allow-automation-to-trigger-once-a-day/390097/6"  target="_blank" rel="noreferrer">Homeassistant Forum</a> beschrieben:
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="s2">&#34;{{ state_attr(&#39;automation.wecker_tagesschau_auto_play&#39;, &#39;last_triggered&#39;) | default(today_at(), true) &lt; today_at(&#39;05:30&#39;) }}&#34;</span></span></span></code></pre></div></div>
</li>
</ul>

<h3 class="relative group">Code für Automation
    <div id="code-für-automation" class="anchor"></div>
    
</h3>
<p>Der Code für die beschriebenen Bedingungen sieht folgender Maßen aus. Er stellt zusätzlich noch eine annehmbare Lautstärke für den Bose Soundtouch ein, damit man in aller Früh nicht zu laut beschallt wird:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Wecker Tagesschau auto play</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.hue_motion_kueche</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">after</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;05:30:00&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;{{ state_attr(&#39;automation.wecker_tagesschau_auto_play&#39;, &#39;last_triggered&#39;) | default(today_at(), true) &lt; today_at(&#39;05:30&#39;) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.play_media</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.soundtouch_kueche</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">media_content_id</span><span class="p">:</span><span class="w"> </span><span class="m">4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">media_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">PLAYLIST</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.volume_set</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.soundtouch_kueche</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volume_level</span><span class="p">:</span><span class="w"> </span><span class="m">0.4</span></span></span></code></pre></div></div>

<h3 class="relative group">Optimierungen
    <div id="optimierungen" class="anchor"></div>
    
</h3>
<p>Dieser Code funktioniert eigenständig, kann natürlich aber noch optimiert und den eigenen Bedürfnissen angepasst werden. Wer beispielsweise nur unter der Woche am Morgen mit einem Newsflash begrüßt werden will und am Wochenende seine Ruhe haben möchte, kann auf die <a href="https://www.home-assistant.io/integrations/workday/"  target="_blank" rel="noreferrer">Workday Integration</a> zurückgreifen und diese als eine weitere Bedingung hinzufügen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.workday_sensor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Phoniebox - DIY NFC Audioplayer</title>
      <link>https://jbetzen.net/posts/phoniebox/</link>
      <pubDate>Sun, 20 Feb 2022 16:51:44 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/phoniebox/</guid>
      <description>Dieser Post behandelt den Bau einer sogenannten Phoniebox - einem freien und offenen NFC-Audioplayer mit kinderleichter Bedienung ähnlich der Tonie Box. Mein persönlicher Build lebt in einer hölzernen Zigarrenkiste und verwendet Arcade Buttons.</description>
      <content:encoded><![CDATA[ 
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg></span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Mein Eigenbau hat es in den <a href="https://raw.githubusercontent.com/wiki/MiczFlor/RPi-Jukebox-RFID/img/gallery/calendar/latest-Phoniebox-Calendar.jpg"  target="_blank" rel="noreferrer"><strong>Phoniebox Kalender 2024</strong></a> geschafft.</span>
</div>

<p>Jüngst ereignete mich der schwesterliche Auftrag <em>eine Art Tonie-Box</em> für meinen kleinen Neffen zu bauen. Da ich lange kein Hardware-Projekt mehr vor der Brust hatte, stürzte ich mich umgehend in die Recherche und Planung. Tonie-Boxen sind derzeit die Thermomixe der Kinderzimmer. Das Konzept ist einfach und bestechend. Man kauft überteuerte Figuren, die einen 


<abbr title="Near Field Communication">NFC</abbr>-Chip enthalten und auf die Boxen den relatierten Audio-Content laden. Die Figuren werden auf die Box gestellt, das NFC-Tag ausgelesen und die Wiedergabe erfolgt - <em>Kinderleicht</em>! Da sich die Firma Tonie darüber hinaus noch in ihren AGBs das Recht herausnimmt, <a href="https://tarnkappe.info/artikel/datenschutz/toniebox-eine-musikbox-fuer-kinder-unter-der-lupe-62219.html#h-agb-und-datenschutz"  target="_blank" rel="noreferrer">das Hörverhalten der Kinder und SSIDs des Heimnetzwerks in Echtzeit auszuspionieren</a>, musste zwangsweise etwas der <em>Marke Eigenbau</em> her.</p>
<p>Ich stieß auf das <a href="http://phoniebox.de/"  target="_blank" rel="noreferrer">Phoniebox Projekt</a>, das genau die Funktion der Tonie-Boxen sauber in Open Source umsetzt. Man brauchte lediglich NFC-Tags, einen 


<abbr title="Radio Frequency Identification">RFID</abbr>-Leser, ein paar Lautsprecher und einen kleinen Raspberry Pi und schon war der Weg zum Eigenbau geebnet.</p>

<h2 class="relative group">Hardware
    <div id="hardware" class="anchor"></div>
    
</h2>
<p>Diese Hardware wurde von mir verwendet. Preislich ist da definitiv Optimierungspotential vorhanden.</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Bauteil</th>
          <th style="text-align: right">Preis</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://buyzero.de/collections/boards-kits/products/raspberry-pi-zero-wh-mit-bestucktem-header"  target="_blank" rel="noreferrer">Raspberry Pi Zero WH</a></td>
          <td style="text-align: right">14,99€</td>
      </tr>
      <tr>
          <td><a href="https://de.aliexpress.com/item/2026446641.html"  target="_blank" rel="noreferrer">RC522 RFID Reader</a></td>
          <td style="text-align: right">2,42€</td>
      </tr>
      <tr>
          <td><a href="https://de.aliexpress.com/item/1005001812489613.html"  target="_blank" rel="noreferrer">100x NFC Sticker Tags</a></td>
          <td style="text-align: right">12,00€</td>
      </tr>
      <tr>
          <td><a href="https://www.amazon.de/gp/product/B00544XKK4"  target="_blank" rel="noreferrer">USB-Lautsprecher</a></td>
          <td style="text-align: right">14,72€</td>
      </tr>
      <tr>
          <td><a href="https://www.amazon.de/gp/product/B006DYNU3G"  target="_blank" rel="noreferrer">USB-Hub</a></td>
          <td style="text-align: right">17,99€</td>
      </tr>
      <tr>
          <td><a href="https://www.amazon.de/gp/product/B08GLMPD1V"  target="_blank" rel="noreferrer">USB-Adapterkabel</a></td>
          <td style="text-align: right">8,99€</td>
      </tr>
      <tr>
          <td><a href="https://arcadeforge.net/Buttons/Sanwa-Buttons:::52_62.html"  target="_blank" rel="noreferrer">3x Sanwa Arcade Buttons 24mm</a></td>
          <td style="text-align: right">7,50€</td>
      </tr>
      <tr>
          <td><a href="https://www.adafruit.com/product/481"  target="_blank" rel="noreferrer">Rugged Metal Push Button</a></td>
          <td style="text-align: right">5,00€</td>
      </tr>
  </tbody>
</table>
</div>
<p>Darüber hinaus werden <em>Female-Female</em> und <em>Male-Female</em>-Steckverbinderkabel benötigt.</p>

<h2 class="relative group">Zusammenbau
    <div id="zusammenbau" class="anchor"></div>
    
</h2>

<h3 class="relative group">Gehäuse
    <div id="gehäuse" class="anchor"></div>
    
</h3>
<p>Grundlage zur Elektronikbehausung war eine Zigarrenbox, die ich von einem entfernten Verwandten erstanden habe. Sie hatte die ideale Größe, um alle nötigen Komponenten in sich unterbringen zu können. Wer es genau wissen will und zufällig <strong>386€</strong> auf der hohen Kante hat, kann sich exakt <a href="https://www.noblego.de/drew-estate-liga-privada-no-9-toro-oscuro/"  target="_blank" rel="noreferrer">diese Box samt 24 Zigarren</a> kaufen.</p>

<h4 class="relative group">Bedienelemente + Lautsprecher
    <div id="bedienelemente--lautsprecher" class="anchor"></div>
    
</h4>
<p>Im ersten Schritt wurde das Paar USB-Lautsprecher aus seinem Ursprungsgehäuse befreit und dessen Lochbild und Passung links und rechts in die Seiten der Zigarrenbox übertragen und eingebracht. Das Loch unten links dient der späteren Stromversorgung des Gesamtsystems:</p>

<figure>
        <img
          class="my-0 rounded-md nozoom"
          src="/posts/phoniebox/01.jpg"
          alt="Ausschnitt der Lautsprecher"
        />
  
  
  </figure>
<p>Auf der Oberseite mussten zwei weitere Bohrungen eingebracht werden. Das hintere Loch dienst der Lautstärkeregelung, für die das Steuerelement der USB-Lautsprecher übernommen wurde. Der Drehknopf ließ sich einfach abziehen und anschließend von außen wieder draufstecken. Das größere Loch ist für den Adafruit <em>Rugged Metal Push Button</em> mit dem eingebauten Status-LED-Ring:</p>

<figure>
        <img
          class="my-0 rounded-md nozoom"
          src="/posts/phoniebox/03.jpg"
          alt="Steuerelemente auf Oberseite"
        />
  
  
  </figure>
<p>Im letzten Schritt mussten die drei Arcade-Buttons in die Front integriert werden. Ich habe dazu einen 25er <a href="https://de.wikipedia.org/wiki/Forstnerbohrer"  target="_blank" rel="noreferrer">Forstner</a>-Bohrer verwendet:</p>

<figure>
        <img
          class="my-0 rounded-md nozoom"
          src="/posts/phoniebox/02.jpg"
          alt="Pushbuttons auf der Front"
        />
  
  
  </figure>
<p>Somit war alle äußerliche Steuerelektronik bereits verbaut. Das Ergebnis sah so aus:</p>

<figure>
        <img
          class="my-0 rounded-md nozoom"
          src="/posts/phoniebox/07.jpg"
          alt="Phoniebox ohne Innenleben"
        />
  
  
  </figure>

<h4 class="relative group">Innenleben
    <div id="innenleben" class="anchor"></div>
    
</h4>
<p>Nun galt es die inneren Komponenten gezielt unterzubringen. Das USB-Hub nahm ich als Ausgangspunkt und positioniert es unten rechts in der Zigarrenbox. So ließ sich das Stromversorgungskabel sauber zuführen und hinter der Bohrung noch einen Knoten vor dem Steckeranschluss platzieren, der ein Ruckartiges ziehen am Kabel dämpfen würde. Die Soundkarte wurde mit den Boxen verbunden und das Hub mittels <em>Micro-USB-zu-USB-A-Adapter</em> an den Pi angeschlossen. Der Pi selber bezieht seinen Versorgungsstrom direkt aus dem USB-Hub:</p>

<figure>
        <img
          class="my-0 rounded-md nozoom"
          src="/posts/phoniebox/04.jpg"
          alt="Verdrahtung"
        />
  
  
  </figure>
<p>Da einige Kabel relativ biegesteif waren, musste eine zielführende Anordnung für das RFID-Lesegerät und den Pi selber gefunden werden. Ich habe den Pi daher schräg versetzt montiert, um das Schließverhalten des Holzgehäuses zu begünstigen. Die Zigarrenbox ließ sich anschließend einwandfrei schließen. Hardwareseitig war alles erledigt.</p>

<h3 class="relative group">Verdrahtung
    <div id="verdrahtung" class="anchor"></div>
    
</h3>

<figure>
        <img
          class="my-0 rounded-md nozoom"
          src="/posts/phoniebox/05.jpg"
          alt="Übersicht Verdrahtung innerhalb der Box"
        />
  
  
  </figure>
<p>Nun folgte der akribische und spannende Teil der Verdrahtung. Den folgenden Verdrahtungplan habe ich dem Wiki der Phoniebox entnommen. Die Steckplatzanordnung funktioniert, ohne viel zusätzliche Frickelei in den Konfigurationseinstellungen, <em>out-of-the-box</em>. Lediglich der <em>Rugged Metal Pushbutton</em>, der zum herunterfahren des Pi dient, muss explizit aktiviert werden. Dazu später mehr.</p>
<p>Wichtig an dieser Stelle ist, dass das ewig verwirrende Thema der Pin-Nummern beim Raspberry Pi verstanden wird. Da es unterschiedliche Modelle des Raspberry Pis gibt und diese unterschiedlich viele Pins besitzen, wird in der Regel die exakte <a href="https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/Using-GPIO-hardware-buttons"  target="_blank" rel="noreferrer">GPIO-Bezeichnung</a> bei Kabelverbindungen angegeben. Dieses Schema ist über alle Raspberry Pi Modelle anwendbar. Die hier zusötzlich aufgeführten BCM-Nummern gelten explizit für den <strong>Pi Zero W(H)</strong>, nicht aber zwangsweise für andere Raspberry Pi Modelle.</p>

<h4 class="relative group">RFID Reader
    <div id="rfid-reader" class="anchor"></div>
    
</h4>
<p>Das RFID-RC522 Modul basiert auf dem Philips MF522-AN Board und ist kompatibel mit Tags, die mittels <code>13,56 MHz</code> gelesen und geschrieben werden.</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>RC522</th>
          <th>GPIO</th>
          <th style="text-align: center">BCM</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SDA</td>
          <td>GPIO 8</td>
          <td style="text-align: center">24</td>
      </tr>
      <tr>
          <td>SCK</td>
          <td>GPIO 11</td>
          <td style="text-align: center">23</td>
      </tr>
      <tr>
          <td>MOSI</td>
          <td>GPIO 10</td>
          <td style="text-align: center">19</td>
      </tr>
      <tr>
          <td>MISO</td>
          <td>GPIO 9</td>
          <td style="text-align: center">21</td>
      </tr>
      <tr>
          <td>IRQ</td>
          <td>GPIO 24</td>
          <td style="text-align: center">18</td>
      </tr>
      <tr>
          <td>RST</td>
          <td>GPIO 25</td>
          <td style="text-align: center">22</td>
      </tr>
      <tr>
          <td>GND</td>
          <td>GPIO 25</td>
          <td style="text-align: center">22</td>
      </tr>
      <tr>
          <td>RST</td>
          <td></td>
          <td style="text-align: center">25</td>
      </tr>
      <tr>
          <td>3.3V</td>
          <td></td>
          <td style="text-align: center">17</td>
      </tr>
  </tbody>
</table>
</div>

<h4 class="relative group">Rugged Metal Push Button
    <div id="rugged-metal-push-button" class="anchor"></div>
    
</h4>
<p>Ich habe diesen Button gewählt, da er unverwüstlich ist und neben seiner eigentlichen Funktion als Taster auch noch eine Statusring-LED enthält. Diese LED soll später als Indikator dienen, dass die Box eingeschaltet und betriebsbereit ist.</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Pin</th>
          <th>GPIO</th>
          <th>BCM</th>
          <th>Funktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>+</td>
          <td>GPIO 12</td>
          <td>32</td>
          <td>LED Ring für Status LED</td>
      </tr>
      <tr>
          <td>-</td>
          <td></td>
          <td>39</td>
          <td>LED Ring für Status LED</td>
      </tr>
      <tr>
          <td>NO</td>
          <td>GPIO 3</td>
          <td>5</td>
          <td>Shutdown Button</td>
      </tr>
      <tr>
          <td>C</td>
          <td></td>
          <td>6</td>
          <td>Shutdown Button</td>
      </tr>
  </tbody>
</table>
</div>
<p>Die Bezeichnungen <code>NC</code> und <code>NO</code> auf den Button Pins stehen für <em>Normally Open</em> (schließender Taster) und <em>Normally Closed</em> (öffnender Taster).</p>

<h4 class="relative group">Arcade Buttons
    <div id="arcade-buttons" class="anchor"></div>
    
</h4>
<p>Die Arcade-Buttons teilen sich einen <a href="https://www.youtube.com/watch?v=qPXN5FaorYQ"  target="_blank" rel="noreferrer">gemeinsamen Ground</a> (<code>GND</code>) und sind mit ihrem angedachten Einsatzzweck in Spielautomaten bestens gerüstet für Kinderhände..</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Button</th>
          <th>GPIO</th>
          <th style="text-align: center">BCM</th>
          <th>Funktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Blau</td>
          <td>GPIO 22</td>
          <td style="text-align: center">15</td>
          <td>Vorh. Titel</td>
      </tr>
      <tr>
          <td>Weiß</td>
          <td>GPIO 27</td>
          <td style="text-align: center">13</td>
          <td>Play / Pause</td>
      </tr>
      <tr>
          <td>Rot</td>
          <td>GPIO 23</td>
          <td style="text-align: center">16</td>
          <td>Nächst. Titel</td>
      </tr>
      <tr>
          <td>GND</td>
          <td></td>
          <td style="text-align: center">14</td>
          <td></td>
      </tr>
  </tbody>
</table>
</div>

<h2 class="relative group">Raspberry Pi einrichten
    <div id="raspberry-pi-einrichten" class="anchor"></div>
    
</h2>
<p><strong>Wichtig:</strong> Die Phoniebox läuft in <em>Version 2</em> nur stabil und fehlerfrei unter <em>Debian Buster</em> (Legacy). Der einfachste Weg diese Version zu installieren ist der <a href="https://www.raspberrypi.com/news/raspberry-pi-imager-imaging-utility/"  target="_blank" rel="noreferrer">Raspberry Pi Image Flasher</a>. Standardmäßig bietet dieser aber eine neuere Version als <em>Buster</em> an, bietet zugleich aber auch die Möglichkeit per Mausklick <code>Debian Buster Lite</code> zu installieren.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/phoniebox/buster-install.gif"
          alt="Gif Debian Buster Install"
        />
  
  
  </figure>
<p>Ein weiterer Klick auf das Zahnradsymbol unten rechts ermöglicht das Vorkonfigurieren diverser Einstellungen, wie W-Lan Zugangsdaten, Systemsprache und Zeitzonen. Dies sollte wahrgenommen werden, da hier Zeit gespart werden kann.</p>

<h3 class="relative group">SPI-Schnittstelle aktivieren
    <div id="spi-schnittstelle-aktivieren" class="anchor"></div>
    
</h3>
<p>Mittel <code>sudo raspi-config</code> kann die Konfiguration des Rasperry Pi Zero aufgerufen werden. Dort befindet sich im Menüpunkt <code>5 Interface Options</code>. Anschließend den Eintrag <code>SPI</code> selektieren und <code>Enable</code> wählen.</p>

<h3 class="relative group">Externe Soundkarte einrichten
    <div id="externe-soundkarte-einrichten" class="anchor"></div>
    
</h3>
<p>Die externe Soundkarte wurde bei mir <em>nicht</em> als primäres Ausgabegerät erkannt. Dies lässt sich aber relativ leicht einstellen. Dazu muss eine 


<abbr title="Advanced Linux Sound Architecture">ALSA</abbr>-Konfiguration angelegt werden. Dieser Schritt sollte <em>vor</em> der Installation der Phoniebox-Software erfolgen, da der Installer nach dem primären Ausgabegerät fragt und die Soundkarte sonst nicht in der Auswahlliste erscheint. Zur Anlage der Konfiguration wird der Befehl <code>sudo nano /etc/asound.conf</code> aufgerufen. Es öffnet sich der Nano Text Editor. In die Datei muss folgender Inhalt geschrieben werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">pcm.!default {
</span></span><span class="line"><span class="cl">    type hw
</span></span><span class="line"><span class="cl">    card 1
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ctl.!default {
</span></span><span class="line"><span class="cl">    type hw
</span></span><span class="line"><span class="cl">    card 1
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div>
<p>Mit <kbd>STRG</kbd>+<kbd>O</kbd> und einem anschließend <kbd>ENTER</kbd> kann der neue Eintrag gespeichert und der Editor mit <kbd>STRG</kbd>+<kbd>X</kbd> verlassen werden. Ein Neustart ist erforderlich.</p>

<h3 class="relative group">Installation Phoniebox
    <div id="installation-phoniebox" class="anchor"></div>
    
</h3>
<p>Wenn alle Verkabelung angeschlossen ist, die Soundkarte konfiguriert ist und der Pi neu gestartet wurde, kann mit der Installation der Phoniebox Software begonnen werden. Die Macher des Projekts stellen dafür einen <a href="https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/INSTALL-stretch#a-default-install-script-for-buster-recommended"  target="_blank" rel="noreferrer"><em>Oneliner</em></a>, eine einfach kopierbare Zeile für das Terminal. bereit.</p>
<p>Der <em>Onliner</em> lautet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">cd<span class="p">;</span> rm buster-install-*<span class="p">;</span> wget https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/master/scripts/installscripts/buster-install-default.sh<span class="p">;</span> chmod +x buster-install-default.sh<span class="p">;</span> ./buster-install-default.sh</span></span></code></pre></div></div>
<p>Diese Codezeile wird einfach ins Terminal kopiert und mit <kbd>ENTER</kbd> ausgeführt. Es folgen einige systemseitige abfragen. Die erste wichtige Abfrage, unter der Verwendung einer externen Soundkarte, ist, dass nicht der Standardausgang <code>Headphone</code> verwendet wird. Bei mir hieß die externe Soundkarte <code>Speaker</code> und tauchte in der Auswahlliste auf. Die Bezeichnung muss manuell ins Terminal eingegeben werden. Die nächste wichtige Abfrage ist die Konfiguration des RFID-Lesers. Hier wird aus der Auswahlliste der angeschlossene RC522 ausgewählt, woraufhin explizit gerätespezifische Bibliotheken installiert werden.</p>
<p>Insgesamt dauert die Installation ca. 15 Minuten und erfordert anschließend einen finalen Neustart.</p>

<h2 class="relative group">Phoniebox Konfiguration
    <div id="phoniebox-konfiguration" class="anchor"></div>
    
</h2>
<p>Mit der Beschriebenen Verdrahtung funktioniert die Phoniebox ohne weiteres Zutun. Einzig der Shutdown-Button muss noch konfiguriert werden. Wird dieser zwei Sekunden lang gedrückt gehalten, fährt der Raspberry Pi sauber herunter und kann anschließend vom Strom getrennt werden. Die Einleitung des Shutdowns wird über die Lautsprecher mit einem Systemton quittiert.</p>

<h3 class="relative group">Shutdown Button
    <div id="shutdown-button" class="anchor"></div>
    
</h3>
<p>Dazu <code>nano ~/RPi-Jukebox-RFID/settings/gpio_settings.ini</code> ausführen und dort den Wert des Shutdown Buttons so abändern:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Shutdown]</span>
</span></span><span class="line"><span class="cl"><span class="na">enabled : True</span>
</span></span><span class="line"><span class="cl"><span class="na">type : Button</span>
</span></span><span class="line"><span class="cl"><span class="na">pin : 3</span>
</span></span><span class="line"><span class="cl"><span class="na">hold_time : 2</span>
</span></span><span class="line"><span class="cl"><span class="na">functioncall : functionCallShutdown</span>
</span></span><span class="line"><span class="cl"><span class="na">pull_up_down : pull_up</span></span></span></code></pre></div></div>
<p>Die neue Button-Konfiguration muss jetzt final neu mit dem Befehl <code>sudo systemctl restart phoniebox-gpio-control.service</code> durchgeladen werden.</p>
<p>Das wars! Die Phoniebox ist einsatzbereit und möchte nun nur noch mit Abspielbaren Audiodateien bestückt werden. Wie dies erfolgen kann, steht im <a href="https://github.com/MiczFlor/RPi-Jukebox-RFID/wiki/MANUAL#connect"  target="_blank" rel="noreferrer">Phoniebox Wiki</a>.</p>

<h2 class="relative group">Demo
    <div id="demo" class="anchor"></div>
    
</h2>
<lite-youtube videoid="fD93uQqcCgk" playlabel="fD93uQqcCgk" params=""></lite-youtube>

<p>Viel Spaß beim Eigenbau! Als Quelle zur Inspiration sei abschließend diese <a href="https://github.com/MiczFlor/RPi-Jukebox-RFID/issues/639"  target="_blank" rel="noreferrer">wirklich tolle Sammlung</a> an gebauten Phonieboxen zu empfehlen.</p>
<hr>
<p><strong>Nachtrag 27.12.2023:</strong><br>
Auf dem <a href="https://en.wikipedia.org/wiki/Chaos_Communication_Congress"  target="_blank" rel="noreferrer">37c3</a> wurde ein großartiger Talk gehalten, der das <em>Reverse Engineering</em> der original Tonie Box erläutert, inkl. eigener Cloud, dem Bespielen mit eigenen Audio-Dateien und Anbindung an <a href="/topics/home-assistant/" >Home Assistant</a> per <a href="/categories/mqtt/" >MQTT</a>:</p>
<lite-youtube videoid="DNufX-tss5M" playlabel="DNufX-tss5M" params=""></lite-youtube>

 ]]></content:encoded>
    </item>
    
    <item>
      <title>ESPHome Bluetooth Low Energy Hub</title>
      <link>https://jbetzen.net/posts/esphome-btle-hub/</link>
      <pubDate>Thu, 13 Jan 2022 23:11:40 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/esphome-btle-hub/</guid>
      <description>Dieser Post beschreibt, wie man mittels ESPHome und einem ESP32 Microcontroller ein Bluetooth Low Energy Hub aufsetzt, das alle Bluetooth IoT-Geräte ausliest und als Broker an Homeassistant sendet.</description>
      <content:encoded><![CDATA[ 
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>Dieser Post enthält Themen, die bereits in zwei vorherigen Posts beschrieben wurden. In den vorherigen Anleitungen wurden die Daten der Geräte über eine Software namens BT-MQTT-Gateway ermittelt:</p>
<ul>
<li><a href="/posts/xiaomi-scale/">Xiaomi MiScale Waage in Homeassistant integrieren</a></li>
<li><a href="/posts/xiaomi-mithermometer/">Xiaomi Thermometer in Homeassistant einbinden</a></li>
</ul>
<p>Die Einbindung der Xiaomi MiScale und dem Mithermometer über ESPHome ist den Methoden der alten Posts vorzuziehen.</p>
</span>
</div>

<hr>
<p>Viele IoT-Geräte funken ihre Daten über <a href="https://de.wikipedia.org/wiki/Bluetooth_Low_Energy"  target="_blank" rel="noreferrer">Bluetooth LE</a>. Um diese ins Smarthome integrieren zu können benötigt man ein Gerät mit Bluetooth-Schnittstelle, das als Broker fungiert. Ich nutze dafür seit Jahren zuverlässig einen <a href="https://www.raspberrypi.com/products/raspberry-pi-zero/"  target="_blank" rel="noreferrer">Raspberry Pi Zero</a>, der in Anbetracht des schlichten Abfangens und Weiterleitens von Paketdaten, unterfordert und überdimensioniert ist. Ein solches Bluetooth Hub lässt sich mittels eines <a href="https://de.wikipedia.org/wiki/ESP32"  target="_blank" rel="noreferrer">ESP32</a> und <a href="https://esphome.io/"  target="_blank" rel="noreferrer">ESPHome</a> mit deutlich geringerem Stromverbrauch und Konfigurationsaufwand realisieren.</p>
<p>Umgezogen werden bei mir die folgenden Bluetooth Sensoren, die nun vom ESP32 ausgelesen und an Homeassistant weitergereicht werden:</p>
<ul>
<li><a href="https://esphome.io/components/sensor/xiaomi_ble.html#lywsd03mmc"  target="_blank" rel="noreferrer">Xiaomi Mithermometer</a></li>
<li><a href="https://esphome.io/components/sensor/xiaomi_miscale.html?highlight=miscale"  target="_blank" rel="noreferrer">Xiaomi Miscale v1</a></li>
<li><a href="https://esphome.io/components/sensor/xiaomi_ble.html#hhccjcy01"  target="_blank" rel="noreferrer">Xiaomi Miflora Pflanzensensoren</a></li>
</ul>

<h2 class="relative group">Vorraussetzungen
    <div id="vorraussetzungen" class="anchor"></div>
    
</h2>
<p>Benötigt werden:</p>
<ul>
<li>ESP32 bspw. die Variante <em>ESP32 D1 Mini</em>, die ich für dieses Projekt verwendet habe</li>
<li><a href="https://esphome.io/guides/getting_started_hassio.html"  target="_blank" rel="noreferrer">ESPHome Add-On</a> für Hass.io</li>
<li>Chrome bzw. Edge Browser oder <a href="https://github.com/esphome/esphome-flasher/releases"  target="_blank" rel="noreferrer">ESPHome Flasher</a> zum einmaligen flashen der Firmware</li>
</ul>

<h2 class="relative group">Konfiguration
    <div id="konfiguration" class="anchor"></div>
    
</h2>

<h3 class="relative group">ESPHome Add-On
    <div id="esphome-add-on" class="anchor"></div>
    
</h3>
<p>Im ersten Schritt muss das ESPHome Add-On installiert, aktiviert und gestartet werden. Anschließend kann über SMB oder das VS-Code-Add-On eine Datei namens <code>secrets.yaml</code> angelegt werden, in der alle sensitiven Daten, wie WiFi-Passwörter oder Atomraketenstartcodes hinterlegt werden können. Diese Einträge können mittels Variablen bei der Konfiguration verwendet werden.</p>
<p>Die Datei muss im Pfad <code>./config/esphome/secrets.yaml</code> erstellt und kann anschließend diesem Schema und Beispieleinträgen gefüllt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">wifi_ssid</span><span class="p">:</span><span class="w"> </span><span class="l">LanSolo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">wifi_passwd</span><span class="p">:</span><span class="w"> </span><span class="l">Wurstjunge12</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ota_passwd</span><span class="p">:</span><span class="w"> </span><span class="l">S4mtpf0te</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ap_passwd</span><span class="p">:</span><span class="w"> </span><span class="l">GoBBerw4rz3</span></span></span></code></pre></div></div>
<p>Nach der Anlage der Datei, kann diese auch direkt aus dem ESPHome Dashboard in Homeassistant editiert werden. Dafür steht das Schlosssymbol neben dem Button <code>SECRETS</code> zur verfügung:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/esphome-btle-hub/esphome-dashboard.png"
          alt="ESPHome Dashboard Screenshot"
        />
  
  
  </figure>
<p>Damit ist die Basis geschaffen, um die Konfiguration des Microcontrollers anzulegen. Im ESPHome Dashboard wird mit einem Klick auf das (+)-Icon eine neue <em>Node</em> erzeugt. So heißen die einzelnen eingebundenen Microcontroller, auf denen ESPHome läuft.</p>

<h3 class="relative group">MiFlora Pflanzensensoren
    <div id="miflora-pflanzensensoren" class="anchor"></div>
    
</h3>
<p>Benötigt wird lediglich die MAC-Adresse des Sensors, um die Daten zu empfangen. In Firmwareversionen <a href="https://github.com/esphome/esphome/pull/1288#issuecomment-695809481"  target="_blank" rel="noreferrer">vor 2.3.1</a> sendeten die Pflanzensensoren noch ihren Batteriestand. Dies ist mit neueren Firmwares leider nicht mehr möglich.</p>
<p>Eine Beispielkonfiguration:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">xiaomi_hhccjcy01</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;C4:7C:23:69:63:08&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">temperature</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Vietnamchili Temperature&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">moisture</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Vietnamchili Moisture&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">illuminance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Vietnamchili Illuminance&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">conductivity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Vietnamchili Soil Conductivity&#34;</span></span></span></code></pre></div></div>
<p><strong>Ein kleiner Tipp noch zum Troubleshooting:</strong><br>
Meine Sensoren liefen auf der Firmware <code>2.7.0</code>. Damit konnten nicht zuverlässig Werte empfangen werden. Man kann die Firmware der Sensoren leicht mit der App <strong>Flower Care</strong> (<a href="https://play.google.com/store/apps/details?id=com.huahuacaocao.flowercare"  target="_blank" rel="noreferrer">Android </a>/<a href="https://apps.apple.com/de/app/flower-care/id1095274672"  target="_blank" rel="noreferrer"> iOS</a>) updaten. Die App setzt jedoch einen Account bei Xiaomi voraus.</p>

<h3 class="relative group">MiScale v1
    <div id="miscale-v1" class="anchor"></div>
    
</h3>
<p>Für die Waage von Xiaomi sollte der ESP32 das aktuelle Gewicht auslesen und zeitgleich noch einen weiteren Sensor für den BMI berechnen und an Homeassistant Senden. Die Berechnung des BMI hatte ich zuvor on Homeassitant mit einem Template Sensor gelöst. Diese Aufgabe kann nun der Mikrocontroller übernehmen. Auch für die Waage muss die MAC-Adresse bekannt sein:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">xiaomi_miscale</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;C4:3F:13:E0:AC:82&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">weight</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;miscale_weight&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Miscale Weight&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:scale-bathroom</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">accuracy_decimals</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">on_value</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">then</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">lambda</span><span class="p">:</span><span class="w"> </span><span class="p">|-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            const float bmi_factor = 0.29860551;
</span></span></span><span class="line"><span class="cl"><span class="sd">            return id(miscale_bmi).publish_state(x * bmi_factor);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    
</span></span></span><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;miscale_bmi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Miscale BMI&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:human-pregnant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">accuracy_decimals</span><span class="p">:</span><span class="w"> </span><span class="m">1</span></span></span></code></pre></div></div>
<p>Diese Konfiguration enthält zwei Sensoren für Gewicht und BMI. Wird das Gewicht ausgelesen erfolgt automatisch die Berechnung des BMI. Der Faktor <code>0.29860551</code> muss gemäß der eigenen Körperwerte angepasst werden und setzt sich aus dem Kehrwert des Quadrats der Körperhöhe [m] zusammen:</p>
<!-- 
<figure>
      <img class="my-0 rounded-md centered" src="/posts/xiaomi-scale/bmi-formula.svg" alt="BMI-Formel" />
  
  
  </figure> -->
<p>

$$BMI = \frac{m}{h^{2}} = m \times \frac{1}{h^{2}}$$</p>
<p><strong>Tipp zum Abschluss:</strong><br>
Für die Xiaomi Waage gibt es eine <a href="https://github.com/dckiller51/bodymiscale"  target="_blank" rel="noreferrer">Custom Component</a>, die es erlaubt mehrere Benutzer zu tracken und einen erweiterten Fundus an generierten Fitnessdaten liefert.</p>

<h3 class="relative group">Mithermometer
    <div id="mithermometer" class="anchor"></div>
    
</h3>
<p>Auch die Mithermometer lassen sich einfach über die MAC-Adresse einbinden. Am einfachsten lassen sie sich auslesen, wenn man den <a href="https://github.com/pvvx/ATC_MiThermometer#bluetooth-advertising-formats"  target="_blank" rel="noreferrer">Advertising Type</a> auf den Wert <code>Custom</code> setzt. Wie das geht, habe ich in dem ursprünglichen <a href="/posts/xiaomi-mithermometer#methode-2-bt-mqtt-gateway" >Post zu dem Thermometer</a> bereits beschrieben. Dort wurde jedoch der Funkstandard (<em>Advertising Type</em>) <code>ATC1441</code> genutzt. Im gleichen Menü kann der Standard gewechselt werden.</p>
<p>Ein Konfiguration für ESPHome sieht dann wie folgt aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">pvvx_mithermometer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;A6:C3:23:E0:A8:42&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">temperature</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Temperature&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">humidity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Humidity&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">battery_level</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Battery Level&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">battery_voltage</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Battery Voltage&#34;</span></span></span></code></pre></div></div>

<h2 class="relative group">Vollständige ESP32 Konfiguration
    <div id="vollständige-esp32-konfiguration" class="anchor"></div>
    
</h2>
<p>Nun müssen nur noch alle drei Teilkonfigurationen zusammengeführt, kompiliert und auf den Mikrocontroller geflasht werden. In dieser Konfiguration können die Variablen aus der <code>secrets.yaml</code> verwendet werden. Eine vollständige Konfiguration könnte wie folgend aussehen. Wichtig ist, dass ggf. der Eintrag im Feld <code>board:</code> angepasst wird, wenn eine abweichende Variante vom ESP32 verwendet wird.</p>
<p>Der Platzhalter <code>YOUR_GENERATED_ID</code> muss durch eine selbstgenerierte ID ersetzt werden. Diese kann direkt auf der <a href="https://esphome.io/components/api.html#configuration-variables"  target="_blank" rel="noreferrer">ESP Home Webseite</a> erzeugt und kopiert werden.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">esphome</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">esp32-btle-hub</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">comment</span><span class="p">:</span><span class="w"> </span><span class="l">ESP32 Bluetooth LE Hub</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">esp32</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">board</span><span class="p">:</span><span class="w"> </span><span class="l">esp32dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">framework</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">esp-idf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Enable logging</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">logger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Enable Home Assistant API</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">encryption</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;YOUR_GENERATED_ID&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ota</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret ota_passwd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">wifi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ssid</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret wifi_ssid</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret wifi_passwd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fast_connect</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Enable fallback hotspot (captive portal)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># in case wifi connection fails</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ap</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ssid</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Btle-Hub Fallback Hotspot&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret ap_passwd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">captive_portal</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">esp32_ble_tracker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">sensor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># XIAOMI MISCALE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">xiaomi_miscale</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;C4:3F:13:E0:AC:82&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">weight</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;miscale_weight&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Miscale Weight&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:scale-bathroom</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">accuracy_decimals</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">on_value</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">then</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">lambda</span><span class="p">:</span><span class="w"> </span><span class="p">|-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">              const float bmi_factor = 0.29860551;
</span></span></span><span class="line"><span class="cl"><span class="sd">              return id(miscale_bmi).publish_state(x * bmi_factor);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;miscale_bmi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Miscale BMI&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:human-pregnant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">accuracy_decimals</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># XIAOMI THERMOMETER</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">pvvx_mithermometer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;A6:C3:23:E0:A8:42&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">temperature</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Temperature&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">humidity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Humidity&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">battery_level</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Battery Level&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">battery_voltage</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Battery Voltage&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># XIAOMI MIFLORA PLANT SENSOR</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">xiaomi_hhccjcy01</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;C4:7C:23:69:63:08&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">temperature</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Vietnamchili Temperature&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">moisture</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Vietnamchili Moisture&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">illuminance</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Vietnamchili Illuminance&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conductivity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Vietnamchili Soil Conductivity&#34;</span></span></span></code></pre></div></div>
<p>Dieser Code muss im ESPHome Add-On im Editor zur Node eingegeben, gespeichert und kompiliert werden.</p>

<h2 class="relative group">Firmware flashen
    <div id="firmware-flashen" class="anchor"></div>
    
</h2>
<p>Im ESPHome Add-On gibt es mehrere Möglichkeiten eine Firmware auf den ESP32 zu installieren:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/esphome-btle-hub/esphome-flashen.png"
          alt="ESPHome auf Mikrocontroller flashen"
        />
  
  
  </figure>
<p>Der einfachste Weg ist die erste Option den Mikrocontroller direkt aus dem Browser heraus zu flashen. Voraussetzung dafür ist der Browser Chrome oder Edge. Mit Firefox klappt es nicht.</p>
<p>Ich nutze immer die letzte Option des manuellen Downloads und lade die Firmware herunter und flashe sie anschließend mit dem <a href="https://github.com/esphome/esphome-flasher/releases"  target="_blank" rel="noreferrer">ESPHome Flasher</a>. Dieser Vorgang muss nur einmal erfolgen, da Nodes später <em>Over-The-Air</em> geupdated werden können.</p>
<p>Ist der Flashvorgang erfolgreich durchlaufen wird der ESP32 automatisch neu starten.</p>

<h1 class="relative group">ESP32 Node in Hass einbinden
    <div id="esp32-node-in-hass-einbinden" class="anchor"></div>
    
</h1>
<p>Homeassistant erkennt nach dem erfolgreichen Flashvorgang den ESP32 automatisch und bietet an diesen zu integrieren. Beim initialen Verbinden mit dem Controller muss dann das definierte Passwort, welches in der Variable <code>!secret api_passwd</code> hinterlegt ist, eingegeben werden. Anschließend stehen alle Sensoren in Homeassistant zur Verfügung.</p>
<p>Sollte die automatischer Erkennung der Node nicht klappen, kann diese auch manuell eingebunden werden indem man im Menü <code>Configuration &gt; Devices &amp; Services</code> den Button <code>Add Integration</code> klickt und dort den Eintrag <code>ESPHome</code> auswählt.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>iOS Actions, Shortcuts und die Apple Watch</title>
      <link>https://jbetzen.net/posts/ios-actions/</link>
      <pubDate>Tue, 21 Dec 2021 15:22:26 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/ios-actions/</guid>
      <description>iOS Actions sind Aktionen, die per Push-Nachrichten getriggert werden können. Diese können sowohl auf dem iPhone als auch mit der Apple Watch genutzt werden. Dieser Post legt den Fokus auf die Nutzung mit der Apple Watch.</description>
      <content:encoded><![CDATA[ 
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><strong>iOS Actions</strong> sind mittlerweile im Status Deprecated/Legacy und sollen nicht länger verwendet werden. Siehe: <a href="https://companion.home-assistant.io/docs/apple-watch/watch-actions"  target="_blank" rel="noreferrer">(Legacy) iOS Actions</a></span>
</div>

<p><a href="https://companion.home-assistant.io/docs/core/actions"  target="_blank" rel="noreferrer">iOS Actions</a> sind eine Funktion von Homeassistant, die speziell für das iOS-Ökosystem genutzt werden kann. Im Kern ähneln sie den <a href="https://companion.home-assistant.io/docs/notifications/actionable-notifications/"  target="_blank" rel="noreferrer">Actionable Notification</a>, wobei letztere - unabhängig davon, ob man die Homeassistant <a href="/categories/companion-app/" >Companion App</a> auf dem Smartphone installiert hat - genutzt werden können, wie in dem Post <a href="/posts/notificaton-appdaemon/">Homeassistant, Actionable Notifications und Appdaemon</a> <a href="/posts/notificaton-appdaemon/" >über Appaemon Notifications</a> bereits beschrieben wurde.</p>
<p>iOS Actions können nach initialem Setup in der iOS Shortcuts App und auch auf der Apple Watch genutzt werden. Wie dies einfach möglich ist, möchte ich in diesem Post zeigen.</p>

<h2 class="relative group">Das Vorgehen
    <div id="das-vorgehen" class="anchor"></div>
    
</h2>
<p>Für mein Setup definiere ich alle iOS Actions direkt in Homeassistant. Wird eine dieser Actions ausgeführt, bspw. über einen Shortcut oder einen Tap auf der Apple Watch, wird auf dem <em>Eventbus</em> von Homeassistant ein Event mit dem Namen <code>ios.action_fired</code> gefeuert. Diesen fange ich mit einer <a href="https://community.home-assistant.io/t/home-assistant-community-add-on-appdaemon-4/163259"  target="_blank" rel="noreferrer">Appdaemon</a> App ab und weise in der App an, welche Action erfolgen soll.</p>

<h2 class="relative group">Konfiguration Homeassistant
    <div id="konfiguration-homeassistant" class="anchor"></div>
    
</h2>
<p>In der <code>configuration.yaml</code> muss zuerst die <a href="https://companion.home-assistant.io/docs/core/actions#creating-actions-in-home-assistant"  target="_blank" rel="noreferrer">iOS-Integration</a> aktiviert werden. Da ich ungern meine <code>configuration.yaml</code> zumülle habe ich im ersten Schritt im Ordner <code>./config/</code> eine Datei mit dem Namen <code>ios.yaml</code> angelegt und diese anschließend so in der <code>configuration.yaml</code> eingebunden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">ios</span><span class="p">:</span><span class="w"> </span>!<span class="l">include ios.yaml</span></span></span></code></pre></div></div>
<p>In der Datei <code>ios.yaml</code> habe ich vier Einträge eingefügt, die für diesen Post als Beispiel herhalten sollen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">actions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">blind_toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">background_color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#000000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">label</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">text</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Rollo&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;4BA7EE&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">blinds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#ffffff&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">sleepmode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">background_color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#000000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">label</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">text</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Schlafmodus&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;4BA7EE&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">bed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#ffffff&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">christmastree</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">background_color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#000000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">label</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">text</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Weihnachtsbaum&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;4BA7EE&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">pine-tree</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#ffffff&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cast_toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">background_color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#000000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">label</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">text</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Chromecast&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;4BA7EE&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">cast</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#ffffff&#34;</span></span></span></code></pre></div></div>
<p>Entscheidend sind an dieser Stelle die Namen der Actions in den Einträgen <code>name:</code>, da diese auf dem Eventbus abgefangen werden müssen.</p>

<h3 class="relative group">Erläuterung Beispiele
    <div id="erläuterung-beispiele" class="anchor"></div>
    
</h3>
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Action</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>blind_toggle</td>
          <td>Fährt mein Ikea Fyrtur Rollo entweder hoch oder herunter. Siehe: <a href="../ikea-rollo" >Post</a></td>
      </tr>
      <tr>
          <td>sleepmode</td>
          <td>Ein Script, das den Schlafmodus einschaltet.</td>
      </tr>
      <tr>
          <td>christmastree</td>
          <td>Schaltet meinen Weihnachtsbaum ein oder aus. Siehe <a href="../wled-weihnachtsbaum" >Post</a></td>
      </tr>
      <tr>
          <td>cast_toggle</td>
          <td>Schaltet eine Steckdose ein oder aus, an der mein Chromecast samt Monitor und Boxen angeschlossen ist.</td>
      </tr>
  </tbody>
</table>
<p>Ist diese Einrichtung erfolgt, muss Homeassistant neugestartet werden. Anschließend stehen diese Actions bereits in der Companion App, der Watch App und der iOS Shortcuts App zur Verfügung, können aber zu dem jetzigen Zeitpunkt noch keine Actions ausführen.</p>

<h3 class="relative group">Screenshots
    <div id="screenshots" class="anchor"></div>
    
</h3>

<h4 class="relative group">Apple Watch Companion App
    <div id="apple-watch-companion-app" class="anchor"></div>
    
</h4>


  

<figure class="centered"><img src="/posts/ios-actions/iosactions-screenshot-watch.png"
    alt="Screenshot Apple Watch">
</figure>



<h4 class="relative group">iOS Shortcut App
    <div id="ios-shortcut-app" class="anchor"></div>
    
</h4>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/ios-actions/iosactions-screenshot-app.png"
          alt="Screenshot Shortcuts App"
        />
  
  
  </figure>

<h2 class="relative group">Konfiguration Appdaemon
    <div id="konfiguration-appdaemon" class="anchor"></div>
    
</h2>
<p>Damit die vier Beispiele auch Aktionen ausführen können, bspw. das Rollo oder den Weihnachtsbaum schalten, nutze ich Appdaemon. Dazu muss im ersten Schritt die Datei <code>./config/appdaemon/apps/iosactions.py</code> angelegt werden und folgendes in diese geschrieben werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">appdaemon.plugins.hass.hassapi</span> <span class="k">as</span> <span class="nn">hass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">iOSActions</span><span class="p">(</span><span class="n">hass</span><span class="o">.</span><span class="n">Hass</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">listen_event</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">handle_event</span><span class="p">,</span> <span class="s2">&#34;ios.action_fired&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">handle_event</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event_name</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;actionName&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;blind_toggle&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s2">&#34;cover/toggle&#34;</span><span class="p">,</span> <span class="n">entity_id</span> <span class="o">=</span> <span class="s2">&#34;cover.rollo_schlafzimmer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;actionName&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;sleepmode&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s2">&#34;script/turn_on&#34;</span><span class="p">,</span> <span class="n">entity_id</span> <span class="o">=</span> <span class="s2">&#34;script.schlafzimmer_readytosleep&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;actionName&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;christmastree&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s2">&#34;light/toggle&#34;</span><span class="p">,</span> <span class="n">entity_id</span> <span class="o">=</span> <span class="s2">&#34;light.wled&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;actionName&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;cast_toggle&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s2">&#34;switch/toggle&#34;</span><span class="p">,</span> <span class="n">entity_id</span> <span class="o">=</span> <span class="s2">&#34;switch.schlafzimmer_steckdose_cast&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Diese Appdaemon App lauscht kontinuierlich nach dem Event <code>ios.action_fired</code>. Ist ein solcher Event erkannt, wird anschließend nach dem <code>actionName</code> gesucht und die zugewiesene Action ausgeführt. Die App muss im letzten Schritt noch für Appdaemon zugänglich gemacht werden. Dazu muss in der Datei <code>./config/appdaemon/apps/apps.yaml</code> der folgende Eintrag hinzugefügt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Handling iOS Actions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">iOSActions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">module</span><span class="p">:</span><span class="w"> </span><span class="l">iosactions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">iOSActions</span></span></span></code></pre></div></div>
<p>Nach dem Speichern registriert Appdaemon automatisch die neue Konfiguration. Anschließend können die iOS Actions genutzt werden.</p>

<h2 class="relative group">Tipp zum Abschluss
    <div id="tipp-zum-abschluss" class="anchor"></div>
    
</h2>
<p>iOS erlaubt auf dem iPhone unter <em>Einstellungen &gt; Bedienungshilfen &gt; Tippen &gt; Auf Rückseite tippen</em> die Möglichkeit einen Shortcut direkt mit einem Doppel- oder Dreifachtipp auf die Rückseite des iPhones auszuführen. Da die iOS Shortcut App auch auf die iOS Actions zugreifen kann, kann man also auch mit einem Doppeltipp auf die Rückseite seinen Weihnachtsbaum ein- oder ausschalten.</p>
<p>Viel Spaß damit!</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>WLED-Weihnachtsbaum in Homeassistant</title>
      <link>https://jbetzen.net/posts/wled-weihnachtsbaum/</link>
      <pubDate>Sat, 18 Dec 2021 18:59:06 +0100</pubDate>
      
      <guid>https://jbetzen.net/posts/wled-weihnachtsbaum/</guid>
      <description>Die Custom Firmware WLED ermöglicht das Steuern von LEDs und kann somit hervorragend zur Automatisierung des Weihnachtsbaums eingesetzt werden. WLED bietet eine Integration für Homeassistant und kann somit im Handumdrehen eingebunden werden.</description>
      <content:encoded><![CDATA[ <p>Die Integration von <a href="https://www.home-assistant.io/integrations/wled/"  target="_blank" rel="noreferrer">WLED</a> in Homeassistant wurde im vergangenen Jahr kontinuierlich verbessert. Während man vorher noch auf umständliche Weise mittels Service Calls gewünschte Presets aufrufen musst, bringt die Integration nun eine handvoll nützlicher Entities mit, die auch unbedarften Usern eine einfache Einbindung in eine Lovelace Card ermöglichen. Die Auswahl von Presets, Effekt-Intensität und Effekt-Geschwindigkeit stehen nun als vordefinierte Entities automatisch in Homeassistant zur Verfügung.</p>
<p>Da sich das jährliche Weihnachtsfest nähert möchte ich in diesem Post zeigen, wie ich WLED für meinen Weihnachtsbaum nutze und in Homeassistant einbinde.</p>

<h2 class="relative group">Verwendete Hardware
    <div id="verwendete-hardware" class="anchor"></div>
    
</h2>
<p>Herzstück meines Setups ist ein <em>QuinLED Dig Uno</em>, der mit einem ESP8266 bestückt ist und die angeschlossenen LED-Strips steuert. Auf den ESP8266 wurde widerum <a href="https://kno.wled.ge/"  target="_blank" rel="noreferrer">WLED</a> geflasht. Ich habe dabei auch 5V-LED-Strips des Typs WS2811 gesetzt. Es gibt diese auch in 12V-Varianten, was dann aber in der Wahl des Netzteils berücksichtigt werden muss. Der QuinLED Dig Uno kann sowohl 5V, als auch 12V LEDs steuern.</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Bauteil</th>
          <th>Preis</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://quinled.info/pre-assembled-quinled-dig-uno/"  target="_blank" rel="noreferrer">QuinLED Dig Uno</a></td>
          <td>35,00€</td>
      </tr>
      <tr>
          <td><a href="https://de.aliexpress.com/item/32625011824.html"  target="_blank" rel="noreferrer">2x WS2811 LED Strip</a></td>
          <td>20,00€</td>
      </tr>
      <tr>
          <td><a href="https://de.aliexpress.com/item/32810906485.html"  target="_blank" rel="noreferrer">5V/10A-Netzteil</a></td>
          <td>18,50€</td>
      </tr>
      <tr>
          <td><a href="https://de.aliexpress.com/item/33004595253.html"  target="_blank" rel="noreferrer">LED Adapter</a></td>
          <td>1,50€</td>
      </tr>
  </tbody>
</table>
</div>

<h2 class="relative group">Setup
    <div id="setup" class="anchor"></div>
    
</h2>

<h3 class="relative group">QuinLED Dig Uno
    <div id="quinled-dig-uno" class="anchor"></div>
    
</h3>
<p>Nachdem der QuinLED Dig Uno korrekt mit den LED-Strips, dem Adapter und dem Netzteil verdrahtet ist, liefert er automatisch die nötige Betriebsspannung für den aufgesetzten ESP8266. Dieser muss zunächst mit dem WLan verbunden werden. Die vorgefertigten Bausätze haben bereits WLED installiert und müssen nur mit dem Netzteil verbunden werden. Anschließend erzeugt WLED einen <em>Access Point</em> mit dem man sich verbinden und die Zugangsdaten für das Heimnetz eintragen kann. Es sollte eine statische IP-Adresse zugewiesen werden.</p>

<h3 class="relative group">WLED in Homeassistant Integrieren
    <div id="wled-in-homeassistant-integrieren" class="anchor"></div>
    
</h3>
<p>In Homeassistant kann unter <code>Configuration &gt; Devices &amp; Services</code> über den Button <code>Add Integration</code> WLED hinzugefügt werden. Homeassistant erkennt im Idealfall direkt automatisch das neue Gerät im Netzwerk. Alternativ kann manuell die zuvor vergebene statische IP angegeben werden.</p>
<p>In meiner Kofiguration wurde folgender Entity-Name für WLED eingerichtet: <code>light.wled</code>. Dieses wird für die anschließenden Beispiele verwendet.</p>

<h2 class="relative group">Lovelace Custom Card
    <div id="lovelace-custom-card" class="anchor"></div>
    
</h2>
<p>Über HACS sollte die <a href="https://github.com/ofekashery/vertical-stack-in-card"  target="_blank" rel="noreferrer">custom-vertical-stack-in-card</a> installiert werden. Sie sorgt dafür, dass man mehrere Cards ohne visuellen Abstand zueinander Stapeln kann. Unterschiedliche Lovelace-Cards erscheinen in der UI dann als eine zusammengehörige Card.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/wled-weihnachtsbaum/wled-lovelacecard.png"
          alt="WLED Lovelace Card"
        />
  
  
  </figure>
<p>Ich verwende für meinen Weihnachtsbaum eine <a href="https://www.home-assistant.io/lovelace/picture-glance/"  target="_blank" rel="noreferrer">Picture Glance Card</a>. Diese hat den Vorteil, dass ich direkt auf das Bild klicken kann, um den Baum mittels <code>light.toggle</code> ein und auszuschalten. Zusätzlich kann im Footer der Karte mit einem Klick der <code>more-info</code>-Dialog für WLED aufgerufen werden, der eine detailliertere Auswahl der Farben und Effekte erlaubt</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/wled-weihnachtsbaum/wled-toggle.gif"
          alt="WLED toggle LED Gif"
        />
  
  
  </figure>
<p>Die Card hat des Weiteren noch eine Listenauswahl für Effekte, die in WLED als Preset festgelegt wurden und zwei weitere Slider für die Intensität und die Geschwindigkeit des gewählten Effekts.</p>

<h3 class="relative group">Hintergrundbild
    <div id="hintergrundbild" class="anchor"></div>
    
</h3>
<p>Als Hintergrundbild für die Picture Glance Card muss das folgende Bild über <code>Speichern unter</code> im Homeassistant Config-Pfad gespeichert werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/wled-weihnachtsbaum/wled-background.jpeg"
          alt="Background Image for Lovelace Card"
        />
  
  
  </figure>
<p>Im folgenden Beispiel nutze ich dafür den Pfad <code>./config/www/entitypictures/weihnachtsbaum.jpeg</code></p>

<h3 class="relative group">Code für die Card
    <div id="code-für-die-card" class="anchor"></div>
    
</h3>
<p>Der folgende Code erzeugt die oben dargestellte Card:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">custom:vertical-stack-in-card</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cards</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">picture-glance</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">/local/entitypictures/weihnachtsbaum.jpeg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">light.wled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">more-info</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">light.wled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Weihnachtsbaum</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">entities</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">select.wled_preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">number.wled_intensity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">number.wled_speed</span></span></span></code></pre></div></div>

<h2 class="relative group">Advanced Lovelace Card
    <div id="advanced-lovelace-card" class="anchor"></div>
    
</h2>
<p>Alternativ kann der Footer direkt mit weiteren Presets versehen werden, die sich mit einem Klick direkt aktivieren lassen ohne vorher in das Dropdown Menü wechseln zu müssen. Die Sache hat einen Haken und erfordert ein wenig kreatives Umdenken. Als Entities können nur bereits existierende Entities eingebunden werden. Die Picture Glance Card biete leider keine Möglichkeit ein Platzhalter zu platzieren.</p>
<p><strong>Ein Umweg:</strong><br>
Man kann auf schnellem Wege ein paar Scenes unter <code>Configuration &gt; Automations &amp; Scenes</code> anlegen und ein Icon zuweisen, dass dem Effekt entspricht. Als Gerät wird dann einfach <code>light.wled</code> angegeben. Diese Scenes dienen nur als Icons im Footer. Bei einem Klick werden sie nicht aktiviert, sondern stattdessen ein dedizierter Service Call ausgeführt. Die Karte sieht dann so aus:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/wled-weihnachtsbaum/wled-lovelacecard-advanced.png"
          alt="Advanced Lovelace Card"
        />
  
  
  </figure>

<h3 class="relative group">Code
    <div id="code" class="anchor"></div>
    
</h3>
<p><strong>Wichtig:</strong><br>
Im Service Call muss als <code>option</code> der exakte Name des Presets aus der WLED-UI gewählt werden. Die Namen können unter den <em>Developer Tools</em> eingesehen werden, wenn man nach der Entity <code>select.wled_preset</code> sucht und diese anklickt.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">custom:vertical-stack-in-card</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cards</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">picture-glance</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">/local/entitypictures/weihnachtsbaum.jpeg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">light.wled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">X-Mas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">scene.wled_running</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">call-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">select.select_option</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">select.wled_preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">option</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="l">Running XMas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">scene.wled_fireflicker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">call-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">select.select_option</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">select.wled_preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">option</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="l">Fireflicker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">scene.wled_flow</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">call-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">select.select_option</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">select.wled_preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">option</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w"> </span><span class="l">Flow</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">scene.wled_lake</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">call-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">select.select_option</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">select.wled_preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">option</span><span class="p">:</span><span class="w"> </span><span class="m">4</span><span class="w"> </span><span class="l">Lake</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">scene.wled_colorwinkles</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">call-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">select.select_option</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">select.wled_preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">option</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w"> </span><span class="l">Colorwinkles</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">scene.wled_saw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">call-service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">select.select_option</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">service_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">select.wled_preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">option</span><span class="p">:</span><span class="w"> </span><span class="m">6</span><span class="w"> </span><span class="l">Saw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">light.wled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">more-info</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">entities</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">select.wled_preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Preset</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">number.wled_intensity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">number.wled_speed</span></span></span></code></pre></div></div>
<p>Viel Spaß beim automatisieren des Weihnachtsbaums.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Automatisch pausierender Media Player</title>
      <link>https://jbetzen.net/posts/autopause-mediaplayer/</link>
      <pubDate>Sun, 08 Aug 2021 16:13:05 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/autopause-mediaplayer/</guid>
      <description>Wer zu Hause Bewegungssensoren installiert hat, kann automatisiert einen Media Player pausieren und wieder starten sobald man den Raum verlässt und wieder betritt.</description>
      <content:encoded><![CDATA[ 
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Der neue Post <a href="/posts/last-motion-sensor/">Last Motion Sensor</a> enthält eine verbesserte Methode für den Last Motion Sensor, der ohne die Custom Component <a href="https://github.com/snarky-snark/home-assistant-variables"  target="_blank" rel="noreferrer">home-assistant-variables</a> funktioniert.</span>
</div>

<p>Lebt man in seiner Wohnung überwiegend allein, kann über Bewegungssensoren verlässlich ermittelt werden, in welchem Raum man sich aufhält. Wie das geht wurde bereits im <a href="/posts/room-based-presence/" >Post über Room Based Presence</a> erläutert. Schaut man einen Film oder hört ein Hörbuch, geschieht das über einen Media Player, der klar einem Raum zugeordnet ist. Verlässt man den Raum, in dem gerade eine Wiedergabe erfolgt, bspw. um eine Tüte Chips zu holen, kann der Media Player automatisch pausiert und beim Wiedereintreten in den Raum automatisch fortgesetzt werden.</p>

<h2 class="relative group">Voraussetzungen
    <div id="voraussetzungen" class="anchor"></div>
    
</h2>
<p>Für diese Automation wird der <code>sensor.last_motion</code> benötigt, der in dem <a href="/posts/room-based-presence/" >Post über Room Based Presence</a> ausgiebig erläutert wurde. Durch diesen Sensor weiß Homeassistant in welchem Raum man sich derzeit aufhält.</p>





  
  
    
  
  


  <section class="space-y-10 w-full">
    
    











  
  
  








  
  
    

    
    
      
      
        
      
        
      
        
      
    

    
    

    
    
      
        
        
      
    
  



<article class="article-link--shortcode flex flex-col md:flex-row relative">
  
    <div class="flex-none relative overflow-hidden  thumbnail-shadow md:mr-7 thumbnail">
      <img
        src="/posts/room-based-presence/featured.png"
        role="presentation"
        loading="lazy"
        decoding="async"
        class="not-prose absolute inset-0 w-full h-full object-cover">
    </div>
  
  <div class=" mt-3 md:mt-0">
    <header class="items-center text-start text-xl font-semibold">
      <a
        
          href="/posts/room-based-presence/"
        
        class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
        <h2>
          Room Based Presence mit Motion Sensoren
          
        </h2>
      </a>
      
      
    </header>
    <div class="text-sm text-neutral-500 dark:text-neutral-400">
      







  

  
  
  
    
  

  

  
    
  

  
    
  

  
    
  

  
    
  

  

  

  

  

  


  <div class="flex flex-row flex-wrap items-center">
    
    
      <time datetime="2020-03-30T23:42:13&#43;02:00">30.03.2020</time><span class="px-2 text-primary-500">&middot;</span><time datetime="2025-12-04T00:00:00&#43;00:00">Aktualisiert: 04.12.2025</time><span class="px-2 text-primary-500">&middot;</span><span>637 Wörter</span><span class="px-2 text-primary-500">&middot;</span><span title="Lesezeit">3 min</span>
    

    
    
  </div>

  

  
  
    <div class="flex flex-row flex-wrap items-center">
      
        
      
        
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/zigbee/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    Zigbee
  </span>
</span>

                </a>
              
            
            
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/motion-sensor/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    Motion Sensor
  </span>
</span>

                </a>
              
            
            
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/hass-variables/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    hass-variables
  </span>
</span>

                </a>
              
            
            
          
        
      
        
      
        
          
            
              
            
            
          
        
      
        
          
            
              
            
            
          
        
      
    </div>
  


    </div>
    
      
      <div
        class="article-link__summary prose dark:prose-invert max-w-fit mt-1 ">
        Wer im Eigenheim Bewegungssensoren installiert hat, kann schnell und einfach einen Sensor erstellen, der aufzeigt in welchem Raum die letzte Bewegung registriert wurde. Wie man diese Info sinnvoll nutzen kann, wird in diesem Post erläutert.
      </div>
    
  </div>
</article>

  </section>


<p>Es gelten die erläuterten Grundsätze, die im verlinkten Post erläutert wurden:</p>
<ul>
<li>Jeder Raum des Hauses benötigt einen Bewegungssensor</li>
<li>Diese Automation funktioniert nur, wenn sich nur eine Person im Haus aufhält</li>
<li>Es sollte ein <code>input_boolean</code> als <em>Condition</em> in der Automation verwendet werden für den Fall, dass mehrere Personen im Haus anwesend sind</li>
</ul>

<h2 class="relative group">Die Automation
    <div id="die-automation" class="anchor"></div>
    
</h2>
<p>Benötigt wird ein Media Player. In diesem Beispiel verwendete ich meinen Beelink GT-King Pro, der mit CoreElec geflasht wurde und das kostenlose Media Center Kodi ausführt. Wie man diesen Media Player mit CoreElec flasht, habe ich in <a href="/posts/gtkingprop-coreelec/" >diesem Post</a> ausgeführt.</p>





  
  
    
  
  


  <section class="space-y-10 w-full">
    
    











  
  
  








  
  
    

    
    
      
      
        
      
        
      
        
      
    

    
    

    
    
      
        
        
      
    
  



<article class="article-link--shortcode flex flex-col md:flex-row relative">
  
    <div class="flex-none relative overflow-hidden  thumbnail-shadow md:mr-7 thumbnail">
      <img
        src="/posts/gtkingprop-coreelec/featured.png"
        role="presentation"
        loading="lazy"
        decoding="async"
        class="not-prose absolute inset-0 w-full h-full object-cover">
    </div>
  
  <div class=" mt-3 md:mt-0">
    <header class="items-center text-start text-xl font-semibold">
      <a
        
          href="/posts/gtkingprop-coreelec/"
        
        class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
        <h2>
          Beelink GT-King Pro und CoreELEC
          
        </h2>
      </a>
      
      
    </header>
    <div class="text-sm text-neutral-500 dark:text-neutral-400">
      







  

  
  
  
    
  

  

  
    
  

  

  
    
  

  
    
  

  

  

  

  

  


  <div class="flex flex-row flex-wrap items-center">
    
    
      <time datetime="2020-02-01T23:42:13&#43;02:00">01.02.2020</time><span class="px-2 text-primary-500">&middot;</span><span>193 Wörter</span><span class="px-2 text-primary-500">&middot;</span><span title="Lesezeit">1 min</span>
    

    
    
  </div>

  

  
  
    <div class="flex flex-row flex-wrap items-center">
      
        
      
        
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/media-player/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    media-player
  </span>
</span>

                </a>
              
            
            
          
        
      
        
      
        
          
            
              
            
            
          
            
              
            
            
          
            
              
            
            
          
        
      
        
      
    </div>
  


    </div>
    
      
      <div
        class="article-link__summary prose dark:prose-invert max-w-fit mt-1 ">
        Der Hersteller Beelink bietet mit dem GT-King Pro eine Streaming Box an, die sowohl mit Android als auch mit dem freie Media Center Kodi betrieben werden kann. Dieser Post beschreibt, wie man Kodi mittels dem CoreELEC Projekt installiert.
      </div>
    
  </div>
</article>

  </section>


<p>Der Media Player für dieses Beispiel hat den Namen <code>media_player.beelink</code> und befindet sich in meinem Wohnzimmer.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Kodi Auto Pause</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Automatically pauses and resumes playback when living room is left or entered&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.last_motion</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">and</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">input_boolean.besuchermodus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.current_av_receiver_source</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Kodi&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% if not is_state(&#34;sensor.last_motion&#34;, &#34;Wohnzimmer&#34;) and is_state(&#34;media_player.beelink&#34;, &#34;playing&#34;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          media_player.media_pause
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% elif is_state(&#34;sensor.last_motion&#34;, &#34;Wohnzimmer&#34;) and is_state(&#34;media_player.beelink&#34;, &#34;paused&#34;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          media_player.media_play
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% endif %}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.beelink</span></span></span></code></pre></div></div>

<h2 class="relative group">Erläuterung der Automation
    <div id="erläuterung-der-automation" class="anchor"></div>
    
</h2>

<h3 class="relative group">Trigger
    <div id="trigger" class="anchor"></div>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.last_motion</span></span></span></code></pre></div></div>
<p>Trigger ist der Sensor <code>sensor.last_motion</code>, der den Aufenthalt in Haus angibt. Ändert sich der Aufenthalt, werden zwei <code>conditions</code> geprüft.</p>

<h3 class="relative group">Conditions
    <div id="conditions" class="anchor"></div>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">input_boolean.besuchermodus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span></span></span></code></pre></div></div>
<p>Diese <em>Condition</em> prüft den <em>State</em> eines <code>input_boolean</code>, der von mir gesetzt wird, wenn Besuch anwesend ist. Für diesen Fall soll die Automation nicht ausgeführt werden.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.current_av_receiver_source</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Kodi&#39;</span></span></span></code></pre></div></div>
<p>Diese <em>Condition</em> prüft, ob mein AV-Receiver auf den Eingang des Media Players geschaltet ist, damit die Automation nicht <em>triggert</em>, wenn ich bspw. über das Gerät Musik höre.</p>

<h3 class="relative group">Action
    <div id="action" class="anchor"></div>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">    {% if not is_state(&#34;sensor.last_motion&#34;, &#34;Wohnzimmer&#34;) and is_state(&#34;media_player.beelink&#34;, &#34;playing&#34;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">      media_player.media_pause
</span></span></span><span class="line"><span class="cl"><span class="sd">    {% elif is_state(&#34;sensor.last_motion&#34;, &#34;Wohnzimmer&#34;) and is_state(&#34;media_player.beelink&#34;, &#34;paused&#34;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">      media_player.media_play
</span></span></span><span class="line"><span class="cl"><span class="sd">    {% endif %}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.beelink</span></span></span></code></pre></div></div>
<p>Für die <em>Action</em> kommt eine <em>service_template</em> zum Einsatz, die basierend auf dem Zustand des <code>sensor.last_motion</code> und des Media Players <code>media_player.beelink</code> zwei Unterschiedliche Aktionen ausführt:</p>
<ul>
<li><strong>Pause:</strong> Wenn eine Bewegung <em>außerhalb</em> des Wohnzimmers detektiert wird UND der Media Player gerade etwas abspielt</li>
<li><strong>Resume:</strong> Wenn eine Bewegung <em>im</em> Wohnzimmer detektiert wird UND der Media Player pausiert ist.</li>
</ul>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>ControllerX und Home Assistant</title>
      <link>https://jbetzen.net/posts/controllerx/</link>
      <pubDate>Thu, 08 Jul 2021 14:19:40 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/controllerx/</guid>
      <description>ControllerX ist eine mächtige AppDaemon App um Zigbee-Eingabegeräte, wie bspw. Aqara- und IKEA-Buttons, Lichtschalter- oder Rollosteuerungen zu verwalten und in Homeassistant zu nutzen. Anhand ein paar Beispielen zeige ich, wie ControllerX bei mir in Verwendung ist.</description>
      <content:encoded><![CDATA[ 
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>Das Themenfeld ControllerX und AppDaemon ist aktuell ein Minenfeld und die <a href="https://github.com/xaviml/controllerx/discussions/874"  target="_blank" rel="noreferrer">zukünftige Kompatibilität</a> ist derzeit nicht gesichert. Der Post wurde aktualisiert und gilt nun für die folgenden Minimalversionen:</p>
<ul>
<li>ControllerX: <code>5.2.2</code></li>
<li>AppDaemon Add-On: <code>0.17.13</code></li>
</ul></span>
</div>

<p><a href="https://github.com/xaviml/controllerx"  target="_blank" rel="noreferrer">ControllerX</a> ist ein mächtiges und <a href="https://xaviml.github.io/controllerx/"  target="_blank" rel="noreferrer">sehr gut dokumentiertes</a> Tool, um <a href="https://de.wikipedia.org/wiki/ZigBee"  target="_blank" rel="noreferrer">Zigbee</a>-Eingabegeräte in Homeassistant zu verwenden. ControllerX greift dabei auf die Funktionalitäten von <a href="https://appdaemon.readthedocs.io/en/latest/"  target="_blank" rel="noreferrer">AppDaemon</a> zurück und erlaubt es <a href="https://xaviml.github.io/controllerx/controllers/"  target="_blank" rel="noreferrer">nahezu jedes auf dem Markt verfügbare Eingabegerät</a> nahtlos mit Homeassistant zu verwenden.</p>

<h2 class="relative group">Vorraussetzungen zur Nutzung
    <div id="vorraussetzungen-zur-nutzung" class="anchor"></div>
    
</h2>

<h3 class="relative group">AppDaemon
    <div id="appdaemon" class="anchor"></div>
    
</h3>
<p><a href="https://appdaemon.readthedocs.io"  target="_blank" rel="noreferrer">AppDaemon</a> ist essentiell für die Nutzung von ControllerX und steht als eigenes <a href="https://www.home-assistant.io/addons/"  target="_blank" rel="noreferrer">Add-On</a> im <a href="https://www.home-assistant.io/integrations/hassio/"  target="_blank" rel="noreferrer">Supervisor</a> von Homeassistant zur Installation bereit. Ist die Installation erfolgt, muss das Add-On manuell gestartet werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/controllerx/controllerx-appdaemon-addon.png"
          alt="Screenshot vom AppDaemon Add-on"
        />
  
  
  </figure>
<p>Damit ControllerX und AppDaemon im Zusammenspiel funktionieren, muss zusätzlich die Datei <code>appdaemon.yaml</code> angepasst werden. Siehe dazu die <a href="https://xaviml.github.io/controllerx/start/installation/#appdaemon-addon"  target="_blank" rel="noreferrer">ControllerX Dokumentation</a>. Wichtig ist der Eintrag für <code>app_dir</code>, der Anweist in welchem Pfadd AppDaemon nach Anwendungen sucht.</p>

<h3 class="relative group">HACS
    <div id="hacs" class="anchor"></div>
    
</h3>
<p><a href="https://hacs.xyz/"  target="_blank" rel="noreferrer">HACS</a>, kurz für <em>Home Assistant Community Store</em>, ist ein Appstore zur Erweiterung von Homeassistants Basisfunktionen.</p>

<h3 class="relative group">ControllerX
    <div id="controllerx" class="anchor"></div>
    
</h3>
<p>ControllerX kann über HACS installiert und aktualisiert werden. Es wird unter dem Menüpunkt <code>Automation</code> gelistet und mit einem Klick auf <kbd>Install</kbd> installiert werden.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/controllerx/controllerx-hacs-install.png"
          alt="Screenshot von ControllerX in HACS"
        />
  
  
  </figure>

<h3 class="relative group">Zigbee-Hub
    <div id="zigbee-hub" class="anchor"></div>
    
</h3>
<p>Ein <em>Zigbee-Hub</em> dient als Router für alle Zigbee-Geräte. Über diese Hardware wird jedes Gerät eingebunden und in Homeassistant als <em>Entity</em> zur Verfügung gestellt. Als Universalhub für alle Hersteller spreche ich eine klare Empfehlung für den <a href="https://phoscon.de/de/conbee2"  target="_blank" rel="noreferrer">Conbee 2</a> von Dresden Elektronik aus. Der Conbee 2 kann zusammen mit dem offiziellen <a href="https://www.home-assistant.io/integrations/deconz/"  target="_blank" rel="noreferrer">deCONZ Add-On</a> genutzt werden und stellt zum <em>Pairing</em> der Geräte eine <em>Web UI</em> namens <a href="https://phoscon.de/de/app/doc"  target="_blank" rel="noreferrer">Phoscon</a> zur Verfügung. Über diese kann im Browser ein neues Gerät eingebunden, benannt und gruppiert werden.</p>

<h2 class="relative group">Zigbee Events und der Event Bus
    <div id="zigbee-events-und-der-event-bus" class="anchor"></div>
    
</h2>
<p>Im Herzen von Homeassistant schlägt der <em>Event Bus</em>. Jede Änderung eines <em>States</em> wird dort in einem fortlaufenden Stream dargestellt und im <a href="https://www.home-assistant.io/integrations/recorder/"  target="_blank" rel="noreferrer">Recorder</a> festgehalten. Nutzt man die deCONZ-Integration, kann explizit nach Events von Zigbee-Geräten gefiltert werden. Dazu muss unter <code>DEVELOPER TOOLS &gt; EVENTS</code> in der Sektion <code>Listen to Events</code> im Eingabefeld <code>deconz_event</code> eingegeben und auf den Button <kbd>Start listening</kbd> geklickt werden. Für andere Integrationen als deCONZ sei auf die <a href="https://xaviml.github.io/controllerx/others/extract-controller-id"  target="_blank" rel="noreferrer">Docs</a> von ControllerX verwiesen.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/controllerx/controllerx-deconzevent.png"
          alt="Screenshot des Event Bus"
        />
  
  
  </figure>
<p>Drückt man nun einen Knopf an einem Zigbee-Eingabegerät, wie bspw. einem Aqara Button, erscheint - exemplarisch - eine Ausgabe im JSON-Format:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;event_type&#34;</span><span class="p">:</span> <span class="s2">&#34;deconz_event&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;button_wohnzimmer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;unique_id&#34;</span><span class="p">:</span> <span class="s2">&#34;00:15:8d:00:01:e8:7f:26&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;event&#34;</span><span class="p">:</span> <span class="mi">1002</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;device_id&#34;</span><span class="p">:</span> <span class="s2">&#34;6f7383674eed4fb3b0aabca03e9a32e1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;origin&#34;</span><span class="p">:</span> <span class="s2">&#34;LOCAL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;time_fired&#34;</span><span class="p">:</span> <span class="s2">&#34;2021-07-08T19:23:01.451187+00:00&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;context&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;89f14979b86490040adef6588e0789d8&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;parent_id&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;user_id&#34;</span><span class="p">:</span> <span class="kc">null</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Interessant für die Automationen sind die Daten im Objekt <code>data: {}</code>. Zur Erläuterung:</p>
<table>
  <thead>
      <tr>
          <th>Key</th>
          <th>Funktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>id</code></td>
          <td>Ist der Name des Zigbee-Geräts, den die Deconz-Integration an Homeassistant durchreicht.</td>
      </tr>
      <tr>
          <td><code>unique_id</code></td>
          <td>Ist die eindeutige Hardware-Adresse des Zigbee-Geräts.</td>
      </tr>
      <tr>
          <td><code>event</code></td>
          <td>Der Event-Code entspricht einer bestimmten Aktion des Geräts, bspw. einem einfach Klick oder dem Gedrückthalten eines Tasters.</td>
      </tr>
      <tr>
          <td><code>device_id</code></td>
          <td>Eine interne ID, die Homeassistant führt und automatisch dem Gerät zuweist. Im Gegensatz zur <code>unique_id</code> ist sie nicht persistent und kann wechseln, wenn das Gerät nach einem <em>Unpairing</em> neu verbunden wird.</td>
      </tr>
  </tbody>
</table>
<p>Die <code>id</code> und <code>unique_id</code> sind essentiell für die Nutzung des Geräts mit ControllerX. Mit einem der beiden Parameter kann jedes Gerät eindeutig ermittelt und der gesendete <code>event_code</code> zugeordnet werden. Dieser kann dann als <em>Trigger</em> für Aktionen benutzt werden. Per <em>default</em> verwendet ControllerX die <code>id</code>.</p>

<h2 class="relative group">Controller einbinden
    <div id="controller-einbinden" class="anchor"></div>
    
</h2>
<p>Nach der Installation von AppDaemon ist unter <code>./config/appdaemon/apps/</code> eine Datei namens <code>apps.yaml</code> verfügbar, in der alle Eingabegeräte konfiguriert werden.</p>
<p>Als einfaches Beispiel sei hier die Konfiguration für die <a href="https://xaviml.github.io/controllerx/controllers/E1766"  target="_blank" rel="noreferrer">Fernbedienung E1766</a> meines IKEA Fyrtur Rollos dargestellt, dem zuvor schon ein <a href="../ikea-rollo" >Post</a> gewidmet wurde:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">rollo_schlafzimmer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">module</span><span class="p">:</span><span class="w"> </span><span class="l">controllerx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">E1766CoverController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">controller</span><span class="p">:</span><span class="w"> </span><span class="l">tradfri_open_close_remote</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">integration</span><span class="p">:</span><span class="w"> </span><span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cover</span><span class="p">:</span><span class="w"> </span><span class="l">cover.rollo_schlafzimmer</span></span></span></code></pre></div></div>
<p>Die einzelnen Konfigurationsparameter im Detail:</p>
<table>
  <thead>
      <tr>
          <th>Parameter</th>
          <th>Funktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>rollo_schlafzimmer</code></td>
          <td>Ein eindeutiger Name, der vergeben werden muss.</td>
      </tr>
      <tr>
          <td><code>module</code></td>
          <td>Weist AppDaemon an, dass ControllerX verwendet werden soll.</td>
      </tr>
      <tr>
          <td><code>class</code></td>
          <td>ControllerX hat unterschiedliche Standardanweisungen für jeden Controller parat. Der E1766-Controller kann bspw. auch zur Steuerung eines Lichts oder eines Schalters verwendet werden.</td>
      </tr>
      <tr>
          <td><code>controller</code></td>
          <td>Der <em>Entity Name</em> der Fernbedienung in Homeassistant.</td>
      </tr>
      <tr>
          <td><code>integration</code></td>
          <td>Neben der deCONZ-Integration beherrscht ControllerX noch weitere Integrationen, wie <a href="https://www.home-assistant.io/integrations/zha/"  target="_blank" rel="noreferrer">Zigbee Home Automation</a> (<code>zha</code>) oder <a href="https://www.zigbee2mqtt.io/"  target="_blank" rel="noreferrer">Zigbee2MQTT</a> (<code>z2m</code>)</td>
      </tr>
      <tr>
          <td><code>cover</code></td>
          <td>Der <code>Entity Name</code> des Rollos in Homeassistant.</td>
      </tr>
  </tbody>
</table>
<p>Eine große Stärke von ControllerX ist, dass jede vordefinierte Aktion überschrieben werden kann. Dabei wird unterschieden zwischen:</p>
<ul>
<li><code>mapping</code>: Überschreibt die Grundfunktionen des Controllers komplett. Nicht definierte Events werden ignoriert.</li>
<li><code>merge_mapping</code>: Behält die Grundfunktionen und überschreibt nur bestimmte Events.</li>
</ul>

<h2 class="relative group">Beispielkonfigurationen
    <div id="beispielkonfigurationen" class="anchor"></div>
    
</h2>
<p>Da es dem Menschen zu gerne an Phantasie mangelt, möchte ich ein paar Beispiele präsentieren, wie ich Zigbee-Eingabegeräte in meinem Alltag nutze. Alle Beispiele zur Nutzung von ControllerX erfolgen unter der Verwendung der deCONZ-Integration und müssen ggf. angepasst werden, wenn ZHA oder Z2M verwendet werden.</p>

<h3 class="relative group">Hue Dimmer Switch
    <div id="hue-dimmer-switch" class="anchor"></div>
    
</h3>
<p>In jedem meiner Räume hängt eine <a href="https://xaviml.github.io/controllerx/controllers/324131092621"  target="_blank" rel="noreferrer">Hue Dimmer Switch</a>. Sämtliche Lichter eines Raumes sind über die Phoscon App gruppiert und erscheinen als eine einzige steuerbare <em>Entity</em> in Homeassistant. Ich nutze die Dimmschalter zum Einschalten und Dimmen der Lampen und habe jeweils den Einschalt- und den Ausschaltknopf mit einem <code>merge_mapping</code> angepasst. Geändert wurden die folgenden <em>Event Codes</em>:</p>
<table>
  <thead>
      <tr>
          <th>Event</th>
          <th>Aktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>1000</code></td>
          <td>Ein einfacher Click auf die I-Taste startet eine Szene, in der alle Leuchten mit voller Leuchtstärke erstrahlen.</td>
      </tr>
      <tr>
          <td><code>1001</code></td>
          <td>Das Gedrückthalten der I-Taste startet eine Szene, in der alle Leuchten gedimmt erstrahlen.</td>
      </tr>
      <tr>
          <td><code>4001</code></td>
          <td>Das Gedrückthalten der O-Taste startet ein Script, das ausgeführt wird wenn ich das Haus verlasse und alle Lichter und Media Player ausschaltet.</td>
      </tr>
  </tbody>
</table>
<p>Der entsprechende Eintrag in der <code>.config/appdaemon/apps/apps.yaml</code> sieht dann folgendermaßen aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">hue_dimmer_flur</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">module</span><span class="p">:</span><span class="w"> </span><span class="l">controllerx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">HueDimmerController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">controller</span><span class="p">:</span><span class="w"> </span><span class="l">flur_dimmschalter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">integration</span><span class="p">:</span><span class="w"> </span><span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">light</span><span class="p">:</span><span class="w"> </span><span class="l">light.deconz_licht_flur</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">merge_mapping</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1000</span><span class="p">:</span><span class="w"> </span><span class="c"># Click I</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">scene</span><span class="p">:</span><span class="w"> </span><span class="l">scene.flur_hell</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1001</span><span class="p">:</span><span class="w"> </span><span class="c"># Hold I</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">scene</span><span class="p">:</span><span class="w"> </span><span class="l">scene.flur_gedimmt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">4001</span><span class="p">:</span><span class="w"> </span><span class="c"># Hold O</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">script.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">script.leaving_home</span></span></span></code></pre></div></div>

<h3 class="relative group">Aqara Buttons
    <div id="aqara-buttons" class="anchor"></div>
    
</h3>
<p>Für kleines Geld können simple Buttons von Aqara erstanden werden. Diese gibt es jedoch in zwei unterschiedlichen Varianten:</p>
<table>
  <thead>
      <tr>
          <th>Variante</th>
          <th>Eigenschaften</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>WXKG11LM <a href="https://xaviml.github.io/controllerx/controllers/WXKG11LM-sensor_switch"  target="_blank" rel="noreferrer">lumi.sensor_switch.aq2</a></td>
          <td>4 unterschiedliche Aktionen verfügbar</td>
      </tr>
      <tr>
          <td>WXKG11LM <a href="https://xaviml.github.io/controllerx/controllers/WXKG11LM-remote"  target="_blank" rel="noreferrer">lumi.remote.b1acn01</a></td>
          <td>3 Unterschiedliche Aktionen verfügbar</td>
      </tr>
  </tbody>
</table>
<p>In diesem Beispiel kommt die erste Variante mit 4 möglichen Aktionen zum Tragen und es werden alle Standardfunktion des Buttons mittels <code>mapping</code> überschrieben. Das zugewiesene Licht <code>light.schlafzimmer_decke</code> ist also nur ein Alibi-Eintrag, denn ein Licht wird in diesem Fall nicht direkt gesteuert.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">button_schlafzimmer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">module</span><span class="p">:</span><span class="w"> </span><span class="l">controllerx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">WXKG11LMSensorSwitchLightController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">controller</span><span class="p">:</span><span class="w"> </span><span class="l">button_schlafzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">integration</span><span class="p">:</span><span class="w"> </span><span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">light</span><span class="p">:</span><span class="w"> </span><span class="l">light.schlafzimmer_decke</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mapping</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1002</span><span class="p">:</span><span class="w"> </span><span class="c"># 1 Click</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.scene_schlafzimmer_nachtlicht</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1004</span><span class="p">:</span><span class="w"> </span><span class="c"># 2 Clicks</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.schlafzimmer_steckdose_cast</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1005</span><span class="p">:</span><span class="w"> </span><span class="c"># 3 Clicks</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">script.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">script.schlafzimmer_readytosleep</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1006</span><span class="p">:</span><span class="w"> </span><span class="c"># 4 Clicks</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">script.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">script.sz_soundtouch_aus_1h</span></span></span></code></pre></div></div>
<p>Das neu festgelegte Mapping der Events führt folgende Aktionen aus:</p>
<table>
  <thead>
      <tr>
          <th>Event</th>
          <th>Aktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>1002</code></td>
          <td>Ein einfacher Klick wechselt über einen <a href="https://www.home-assistant.io/integrations/switch.template/"  target="_blank" rel="noreferrer">Template Switch</a> zwischen meiner Nachtbeleuchtung oder ganz ausgeschaltetem Licht im Schlafzimmer.</td>
      </tr>
      <tr>
          <td><code>1004</code></td>
          <td>Ein doppelter Klick schaltet ebenfalls einen Template Switch für eine Steckdose, an der ein Monitor, ein Chromecast und ein paar Boxen hängen.</td>
      </tr>
      <tr>
          <td><code>1005</code></td>
          <td>Ein dreifacher Klick startet ein Script, das den Schlafmodus einleitet. Das Rollo wird heruntergefahen, alle Mediaplayer ausgeschaltet und das Licht geht nicht mehr automatisch über den Bewegungssensor an.</td>
      </tr>
      <tr>
          <td><code>1006</code></td>
          <td>Ein vierfacher Klick startet einen Timer von 1h für meine Schlafzimmeranlage. Ist der Timer abgelaufen schaltet sich die Box automatisch aus.</td>
      </tr>
  </tbody>
</table>

<h3 class="relative group">Aqara Magic Cube
    <div id="aqara-magic-cube" class="anchor"></div>
    
</h3>
<p>Der <a href="https://xaviml.github.io/controllerx/controllers/MFKZQ01LM"  target="_blank" rel="noreferrer">Magic Cube</a> ist ein merkwürdiges Gerät. Es hat so viel Funktionen, dass ich jedes mal nach fünf Minuten vergesse, womit ich sie belegt habe. Trotzdem lässt er sich wunderbar mit ControllerX verwenden. In diesem Fall wird aber nicht ein Event ausgelesen, sondern eine bereits interpretierte Geste (<code>gesture</code>), wie bspw. das Schütteln, Drehen oder das Schieben des Würfels verwendet. Den Magic Cube nutze ich für meine Lichter im Wohnzimmer und zur Steuerung meines Fernsehers und AV-Receivers.</p>
<p>Die <em>Gestures</em> und gewählten Aktionen im Detail:</p>
<table>
  <thead>
      <tr>
          <th>Event</th>
          <th>Gesture</th>
          <th>Aktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>1</code></td>
          <td>Schütteln</td>
          <td>Schaltet den Fernseher ein</td>
      </tr>
      <tr>
          <td><code>3</code></td>
          <td>90° Flip</td>
          <td>Aktiviert Szene</td>
      </tr>
      <tr>
          <td><code>4</code></td>
          <td>180° Flip</td>
          <td>Aktiviert weitere Szene</td>
      </tr>
      <tr>
          <td><code>6</code></td>
          <td>Doppelt tippen auf Oberfläche</td>
          <td>Schaltet den Eingang des AV-Receivers auf <code>HDMI2</code></td>
      </tr>
      <tr>
          <td><code>7</code></td>
          <td>Rotation Rechts</td>
          <td>Erhöht die Lautstärke des AV-Receivers</td>
      </tr>
      <tr>
          <td><code>8</code></td>
          <td>Rotation Links</td>
          <td>Reduziert die Lautstärke des AV-Receivers</td>
      </tr>
  </tbody>
</table>
<p>Der zugehörige Eintrag in der <code>.config/appdaemon/apps/apps.yaml</code> lautet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">magiccube_wohnzimmer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">module</span><span class="p">:</span><span class="w"> </span><span class="l">controllerx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">MFKZQ01LMLightController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">controller</span><span class="p">:</span><span class="w"> </span><span class="l">zauberwurfel_wohnzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">gesture</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">light</span><span class="p">:</span><span class="w"> </span><span class="l">light.wohzimmer_esstisch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mapping</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1</span><span class="p">:</span><span class="w"> </span><span class="c"># Shake</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.sony_bravia</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">3</span><span class="p">:</span><span class="w"> </span><span class="c"># Flip 90°</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">scene</span><span class="p">:</span><span class="w"> </span><span class="l">scene.wohnzimmer_fernsehen</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">4</span><span class="p">:</span><span class="w"> </span><span class="c"># Flip 180°</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">scene</span><span class="p">:</span><span class="w"> </span><span class="l">scene.wohnzimmer_chill</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">6</span><span class="p">:</span><span class="w"> </span><span class="c"># Double Tap on Surface</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.select_source</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.av_receiver</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;HDMI2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">7</span><span class="p">:</span><span class="w"> </span><span class="c"># Rotate Right</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.volume_up</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.av_receiver</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">8</span><span class="p">:</span><span class="w"> </span><span class="c"># Rotate Left</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.volume_down</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.av_receiver</span></span></span></code></pre></div></div>

<h3 class="relative group">Tradfri Shortcut Button
    <div id="tradfri-shortcut-button" class="anchor"></div>
    
</h3>
<p>IKEA hat vor kurzem sein Sortiment an Zigbee-Geräten um einen simplen <a href="https://www.ikea.com/de/de/p/tradfri-shortcut-button-weiss-40356381/"  target="_blank" rel="noreferrer">Shortcut Button</a> (E1812) erweitert. Dieser sendet folgende <em>Event Codes</em> auf Homeassistants <a href="https://www.home-assistant.io/docs/configuration/events/"  target="_blank" rel="noreferrer">Event Bus</a>:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Event</th>
          <th>Aktion</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>1001</code></td>
          <td>Button wird gedrückt und gehalten</td>
      </tr>
      <tr>
          <td><code>1002</code></td>
          <td>Button wird einmal gedrückt</td>
      </tr>
      <tr>
          <td><code>1003</code></td>
          <td>Button wird nach dem gedrückt halten wieder losgelassen</td>
      </tr>
  </tbody>
</table>
</div>
<p>Ich nutze den Button für meine Waschmaschine. Meine Waschmaschine hat gefühlt 32 Millionen unterschiedliche Waschprogramme. Da ich aber ein einfacher Mann bin, verwende ich immer nur 2 davon: 40°-Wäsche und 60°-Wäsche. Beide Waschvorgänge dauern unterschiedlich lange.</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Waschvorgang</th>
          <th>Dauer</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>40°-Wäsche</td>
          <td>01:58:00</td>
      </tr>
      <tr>
          <td>60°-Wäsche</td>
          <td>01:48:00</td>
      </tr>
  </tbody>
</table>
</div>
<p>Ich habe meinen Button direkt neben der Waschmaschine an die Wand geklebt und kann mit einem einfachen <em>Click</em> einen Timer für die 40°-Wäsche in Homeassistant starten. Wird der Button gedrückt gehalten, startet der Timer für die 60°-Wäsche. Ist einer der beiden Timer abgelaufen, bekomme ich eine Pushnachricht auf mein iPhone.</p>
<p>Dafür werden in Homeassistant zwei <a href="https://www.home-assistant.io/integrations/timer/"  target="_blank" rel="noreferrer">Timer</a> benötigt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">timer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">laundry_40</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">duration</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;01:48:00&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">laundry_60</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">duration</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;01:58:00&#39;</span></span></span></code></pre></div></div>
<p>Des weiteren die Automation für die Benachrichtigung per Push-Nachricht:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Notify Waschmaschine fertig</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">event</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_type</span><span class="p">:</span><span class="w"> </span><span class="l">timer.finished</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">timer.laundry_40</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">event</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_type</span><span class="p">:</span><span class="w"> </span><span class="l">timer.finished</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">timer.laundry_60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_mizar</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Waschmaschine 🧺&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Die Waschmaschine ist fertig&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">channel</span><span class="p">:</span><span class="w"> </span><span class="l">Info</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;PUSH_WASCHMASCHINE_FERTIG&#34;</span></span></span></code></pre></div></div>
<p>Und im finalen Schritt muss eine Konfiguration in <code>.config/appdaemon/apps/apps.yaml</code> eingetragen werden. Der Tradfri Shortcut Button ist unter den Zigbee-Geräten in Verbindung mit der Phoscon-App ein Sonderfall. Er wird in der App nicht aufgeführt. Daher besteht keine Möglichkeit den Namen und somit seine <code>id</code> zu ändern. Besitzt man mehrere Shortcut Buttons, senden sie also alle unter der gleichen ID <code>tradfri_shortcut_button</code> und können somit von ControllerX nicht auseinandergehalten werden. Ich habe daraufhin den Entwickler von ControllerX <a href="https://github.com/xaviml"  target="_blank" rel="noreferrer">xaviml</a> angeschrieben und er hat mit dem <a href="https://github.com/xaviml/controllerx/releases/tag/v4.14.0"  target="_blank" rel="noreferrer">Release 4.14.0</a> eine Möglichkeit geschaffen die unterschiedlichen Shortcut Buttons über einen weiteren <em>Identifier</em> zu unterscheiden. Dieser Identifier ist die hardware-basierte <code>unique_id</code>, die einmalig für jeden Button ist:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tradfri_button_laundry</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">module</span><span class="p">:</span><span class="w"> </span><span class="l">controllerx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">E1812LightController</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">controller</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;58:8e:81:ff:fe:3f:05:fc&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">listen_to</span><span class="p">:</span><span class="w"> </span><span class="l">unique_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">light</span><span class="p">:</span><span class="w"> </span><span class="l">light.wohnzimmer_couch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mapping</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1002</span><span class="p">:</span><span class="w"> </span><span class="c"># Click</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">timer.start</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">timer.laundry_40</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">timer.cancel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">timer.laundry_60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">1001</span><span class="p">:</span><span class="w"> </span><span class="c"># Hold</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">timer.start</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">timer.laundry_60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">timer.cancel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">timer.laundry_40</span></span></span></code></pre></div></div>
<p>Da ich manchmal schusselig bin und dazu tendiere den falschen Timer zu starten, habe ich für jeden Timer noch die Funktion <code>timer.cancel</code> eingebaut, der bei einer Korrekturangabe den falschen Timer wieder beendet und den richtigen startet.</p>
<p>Viel Spaß beim experimentieren mit ControllerX.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Wechsel von Android zu iOS</title>
      <link>https://jbetzen.net/posts/android-zu-ios/</link>
      <pubDate>Wed, 23 Jun 2021 20:23:42 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/android-zu-ios/</guid>
      <description>Ich habe nach 11 langen Jahren mein Mobile OS gewechselt und bin als treuer Nexus/Oneplus Kunde auf ein iPhone 12 umgesattelt. Wie sich der Wechsel von Android zu iOS gestaltet hat, was ich vermisse und was mir gefällt, soll Thema dieses Posts sein.</description>
      <content:encoded><![CDATA[ <p>Es gibt Momente im Leben, die wollen wohl überlegt sein. Wenn man nach 11 Jahren sein <em>Mobile OS</em> wechselt, zähle ich das dazu. Nur war mein Wechsel von Android auf iOS in keinster Weise überlegt, sondern eher ein aus der Not geborener Impuls. Nach 11 Jahren auf Android schleppt man eine immense <em>Baggage</em> an Apps mit sich, hat seine Workflows im <em>Muscle Memory</em> und hat für jeden Task seine App der Wahl. Das gesamte digitale Leben und sämtliche Gewohnheiten müssen an einen neuen Platz gerückt werden.</p>

<h2 class="relative group">Zur Historie
    <div id="zur-historie" class="anchor"></div>
    
</h2>
<p>Android war für mich immer das Betriebssystem der Wahl auf mobilen Endgeräten. Es war frei, es war offen. Man konnte Apps an Google vorbei installieren und es erlaubte alle Möglichkeiten zur <em>Customization</em>, die man sich nur wünschte. Wer ein gescheites Smartphone mit großer Entwickler-Community gekauft hatte, konnte aus einer Vielzahl von <a href="https://www.droidwiki.org/wiki/ROM"  target="_blank" rel="noreferrer">Custom ROMs</a> auswählen und sich die <em>User Experience</em> schaffen, die man sich wünschte. Diese Zeiten sind <em>passé</em>. Android ist nicht mehr das, was es mal war.</p>

<h2 class="relative group">Device History
    <div id="device-history" class="anchor"></div>
    
</h2>
<p>Seit Anbeginn von Android hatte ich ausschließlich <a href="https://de.wikipedia.org/wiki/Nexus_%28Google%29"  target="_blank" rel="noreferrer">Googles Nexus Geräte</a>. Sie waren Vorzeigetelefone zur Demonstration von Androids Fähigkeiten, erschwinglich und boten eine einfache Möglichkeit <em>ab Werk</em> <a href="https://www.droidwiki.org/wiki/Root"  target="_blank" rel="noreferrer">Root Rechte</a> zu erlangen. Der <em>Root</em> war immer essentiell für mich. Nur mit höchsten Rechten und Kontrolle über alle Hardware-Schnittstellen, kann man sein Gerät auch wirklich <em>seins</em> nennen.</p>
<p>Ich erinnere mich noch gut daran, wie der kleine <a href="https://git.susa.pw/Tim/"  target="_blank" rel="noreferrer">Tim</a> mir erstmals Stolz sein <a href="https://www.droidwiki.org/wiki/Sony/Xperia_mini_pro"  target="_blank" rel="noreferrer">Sony Xperia Mini</a> mit ausfahrbarem Hardware-Keyboard zeigte. Damals hatte ich noch ein altbewährtes <a href="https://de.wikipedia.org/wiki/Nokia_6230"  target="_blank" rel="noreferrer">Nokia 6230i</a>. Er zeigte mir, mit welchem Aufwand er <a href="https://de.wikipedia.org/wiki/CyanogenMod"  target="_blank" rel="noreferrer">Cyanogenmod</a> auf das Gerät geflasht hatte und wie er manuell die Tastenbelegung des Keyboards definieren musste. Es war eine neue Welt der Möglichkeiten. Eine Welt voller <em>Frickelei</em>. Sie war spannend und ich wollte daran teilhaben.</p>

<h3 class="relative group">Nexus S
    <div id="nexus-s" class="anchor"></div>
    
</h3>
<p>Mein erstes Smartphone überhaupt war das Wegweisende <a href="https://de.wikipedia.org/wiki/Nexus_S"  target="_blank" rel="noreferrer">Nexus S</a>, gefertigt von Samsung und vertrieben von Google. Es war - wie alle Geräte der Nexus-Reihe - ein <em>Developer Phone</em>. Mit diesem Gerät wollte Google zeigen, was Android kann und das gelang ihnen. Der Formfaktor war grandios. Das Display seinerzeit herausragend und sogar <em>curved</em>. Ich rootete es noch in der ersten Woche mit <a href="https://supersuroot.org/"  target="_blank" rel="noreferrer">Super SU</a>, flashte <a href="https://www.droidwiki.org/wiki/ClockWorkMod"  target="_blank" rel="noreferrer">Clockwork Mod</a> als <a href="https://www.droidwiki.org/wiki/Recovery#Custom_Recovery"  target="_blank" rel="noreferrer">Custom Recovery</a> und installierte die Custom ROM <a href="https://de.wikipedia.org/wiki/CyanogenMod"  target="_blank" rel="noreferrer">Cyanogenmod</a>. Das Nexus S war ab <em>Minute 1</em> mein <em>Daily Driver</em>. Vorinstalliert kam damals Android 2 und rückblickend lief es furchtbar. Die drei Reboots zur Wiederherstellung aller Gerätefunktionalitäten gehörten zur Tagesroutine. Das Gerät erhielt Support für lediglich ein <em>Major Release</em> auf Android 4. Die älteren Leser werden sich entsinnen, dass <a href="https://de.wikipedia.org/wiki/Liste_von_Android-Versionen#Version_3.x"  target="_blank" rel="noreferrer">Android 3</a> niemals für Smartphones erschien und nur den aufkommenden Tablets vergönnt war. Daher erfolgte der direkte Sprung von Android 2 auf Android 4.</p>

<h3 class="relative group">Nexus 4
    <div id="nexus-4" class="anchor"></div>
    
</h3>
<p>Das Nexus S stieß schnell an seine Grenzen. Die Hardware war der schnellen Entwicklung auf dem mobilen Sektor nicht gewachsen. Smartphones steckten noch in den Kinderschuhen und konnten mit Apples <em>Pace</em> des iPhones nur mühsam mithalten. Die logische Konsequenz für mich war die Anschaffung des <a href="https://de.wikipedia.org/wiki/Nexus_4"  target="_blank" rel="noreferrer">Nexus 4</a>. Ebenfalls ein formschönes Gerät mit Glasrücken, einer besseren Kamera und einem sichtlich performanterem Prozessor. Eindrücklich in Erinnerung geblieben ist mir das immense Gewicht des Geräts gegenüber dem Nexus S und die deutlich verbesserte Verarbeitungsqualität - dieses Mal gefertigt durch den Hersteller LG. Das Gerät kam standardmäßig mit Android 4 und war, wie das Nexus S, das Vorzeigegerät von Google für eben diese Android Version. Auch dieses Gerät wurde umgehend <em>gerooted</em> und mit Cyanogenmod geflasht und integrierte sich nahtlos in meinen Alltag. Es war eins der ersten Geräte mit <a href="https://www.droidwiki.org/wiki/Android_Beam"  target="_blank" rel="noreferrer">Android Beam</a>, einer Technik zum Austausch von Informationen und Dateien, die auf <a href="https://de.wikipedia.org/wiki/Near_Field_Communication"  target="_blank" rel="noreferrer">NFC</a> basierte. Einen realen Anwendungsfall, wie das heutige <em>Mobile Payment</em>, gab es damals jedoch nicht. Das Gerät erhielt von Google Support bis Android 5.</p>

<h3 class="relative group">Nexus 5
    <div id="nexus-5" class="anchor"></div>
    
</h3>
<p>Das <a href="https://de.wikipedia.org/wiki/Nexus_5"  target="_blank" rel="noreferrer">Nexus 5</a> war anders, ebenfalls von LG gefertigt und gleichzeitig eine willkommene Abkehr von dem schweren Glasrücken des Nexus 4. Rückwirkend war es vom Formfaktor, Preis und Funktionalität eines der schönsten Smartphones, das ich je besessen habe. Das Nexus 5 war deutlich leichter als das Nexus 4 und hatte eine gummierte Rückseite, die sich gut in die Hand schmiegte. Es war ein Gerät, das man <em>gerne</em> in die Hand nahm. Erstmals installierte ich <a href="https://lineageos.org/"  target="_blank" rel="noreferrer">LineageOS</a> als Custom Rom, da Cyanogenmod <a href="https://de.wikipedia.org/wiki/CyanogenMod#Ende_des_Projektes"  target="_blank" rel="noreferrer">eingestampft</a> wurde. Ab Android 6 dachte sich Google, dass ein weiterer Support nicht notwendig sei.</p>

<h3 class="relative group">Oneplus 3
    <div id="oneplus-3" class="anchor"></div>
    
</h3>
<p>Zwar wurde im Auftrag von Google noch ein <a href="https://de.wikipedia.org/wiki/Nexus_6"  target="_blank" rel="noreferrer">Nexus 6 und ein Nexus 6P</a> gefertigt, jedoch zeichnete sich das Ende der Nexus-Reihe bereits ab. Ich stand vor der Entscheidung noch einmal in bewährte Fußstapfen zu treten oder mich nach etwas Neuem umzuschauen. Auf dem Markt der Entwicklertelefone trat ein neuer <em>Player</em> namens <a href="https://de.wikipedia.org/wiki/OnePlus"  target="_blank" rel="noreferrer">Oneplus</a> auf. Dieser hatte mit dem <a href="https://de.wikipedia.org/wiki/OnePlus_One"  target="_blank" rel="noreferrer">Oneplus One</a> in die Kerbe der Nexus-Geräte geschlagen. Die Firmenphilosophie erlaubte ein einfaches Erlangen von Root-Rechten und ein simples Entsperren des <a href="https://www.droidwiki.org/wiki/Bootloader"  target="_blank" rel="noreferrer">Bootloaders</a>, daher fiel die Wahl auf das <a href="https://de.wikipedia.org/wiki/OnePlus_3"  target="_blank" rel="noreferrer">Oneplus 3</a>. Es war ds erste Gerät, auf das ich keine <em>Custom ROM</em> flashte. Das hauseigene <em>OxygenOS</em> brachte einen mannigfaltigen Fundus an Funktionen aus der Custom-ROM-Welt mit sich, mit der ich erstmals zufrieden war. Oneplus hatte eine handvoll Entwickler aus dem <a href="https://paranoidandroid.co/"  target="_blank" rel="noreferrer">Paranoid Android Projekt</a> abgeworben und eine simple UI geschaffen, die LineageOS und Cyanogenmod sehr nahe kam und mannigfaltige Möglichkeiten zu Anpassung bot. Das Gerät war grandios, schnell aber auch deutlich größer als das Nexus 5. Ich erinnere mich gut, wie sperrig es in meiner Hosentasche wirkte. Das Zeitalter der <em>Phablets</em> hatte begonnen und der Markt verlangte nicht länger nach kleinen Telefonen. Es war das erste Smartphone in meinem Besitz, das eine Zweihandbedienung erforderte und ich haderte lange mit dem Gedanken, ob das wirklich Zukunft sein sollte. Man gewöhnte sich jedoch sehr schnell an den neuen Formfaktor. Das Oneplus 3 war ein grandioses Device. Der Bildschirm war, gegenüber dem Nexus 5, herausragend. Das Telegon war <em>snappy</em> und erfüllte all seine Aufgaben mit verlässlichem Eifer und es war das erste Gerät in meinem Besitz, das mit einem Fingerabdruckscanner ausgestattet war. Oneplus versorgte es bis Android 9 (OxygenOS 9) mit einem aktuellen Betriebssystem. Ein herausragendes Feature war der <em>Dual-Sim-Slot</em>, der es ermöglichte Zeitgleich zwei SIM-Karten zu betreiben, was mir während meiner <a href="/series/vietnam/" >Reise durch Vietnam</a> sehr gelegen kam.</p>

<h3 class="relative group">Oneplus 7T
    <div id="oneplus-7t" class="anchor"></div>
    
</h3>
<p>Mit dem finalen Upgrade auf Android 9, stellte sich mir erneut die Frage nach einem gescheiten Nachfolger von dem Oneplus 3 zu suchen. Die Zeit der Custom ROMs war für mich bereits verblichen. Ich brauchte ein Gerät, das auf der Höhe der Zeit war. Gleichzeitig störte mich, dass Oneplus - wie auch Google - lediglich 2 Major Releases an Updates für ihre Geräte versprach. Ich empfand den Gedanken, alle zwei Jahre ein neues Smartphone kaufen zu müssen, als lästig und nicht nachhaltig. Ich kaufte das <a href="https://de.wikipedia.org/wiki/OnePlus_7T"  target="_blank" rel="noreferrer">Oneplus 7T</a> und hatte das erste mal das Gefühl, damit nicht die richtige Entscheidung getroffen zu haben. Es gab 3 Faktoren, die Ursache des Zweifelns waren:</p>
<ul>
<li>Oneplus versprach mehr, als sie als Hersteller liefern konnten. Die Updates wurden immer unregelmäßiger, die <em>Security Patches</em> waren über lange Zeiträume heillos veraltet und die Softwarequalität von OxygenOS ließ an vielen Stellen zu wünschen übrig. Oneplus fokussierte sich zunehmend darauf neue Geräte auf dem Markt zu platzieren und ihre bereits erhältlichen Geräte zu vernachlässigen. Es traten unschöne Ereignisse auf, wie der <a href="https://www.computerbase.de/2018-01/oneplus-shop-kreditkarte-hack/"  target="_blank" rel="noreferrer">Verlust von 40.000 Kreditkartendaten im Oneplus Store</a> und auch deckte man auf, dass Oneplus <a href="https://de.wikipedia.org/wiki/OnePlus#Nutzerdatensammlung"  target="_blank" rel="noreferrer">heimlich und unverschlüsselt private Daten vom Telefon nach China sendete</a>.</li>
<li>Die Entwicklerszene um das Telefon war kaum vorhanden. Oneplus hatte einst den Ruf in die Fußstapfen der Nexus-Reihe von Google zu stapfen. Stümperhafte Implementierungen in ihren Geräten erforderten aber mehr und mehr Aufwand sie zu betreiben, bspw. hat es nie ein lauffähiges <em>Custom Recovery</em> für das Phone gegeben und man musste sein <em>Bootimage</em> zum Rooten <em>von Hand</em> patchen.</li>
<li>Google legte dem <em>Rooten</em> auf Android die Daumenschrauben an. Mit dem <a href="/posts/android-zu-ios/#google-safety-net" >Safety Net Framework</a> wurde das Rooten mehr und mehr zur Qual, da fortlaufend die <em>Device Integrity</em> gecheckt wurde. Google prüfte konstant, ob der Bootloader entsperrt war, ob ein <em>Root</em> detektiert wurde und ob das Telefon in Einklang mit den, von Google gnadenlos aufoktroyierten Bedingungen, im Betrieb war.</li>
</ul>

<h2 class="relative group">Gründe des Wechsels zu iOS
    <div id="gründe-des-wechsels-zu-ios" class="anchor"></div>
    
</h2>

<h3 class="relative group">Google Safety Net
    <div id="google-safety-net" class="anchor"></div>
    
</h3>
<p>Was genau ist das genau - mag man sich fragen - und wofür ist es gut?</p>
<blockquote><p>SafetyNet ist ein Service von Google, welcher im Rahmen der Google Play Services auf einem Android-Gerät installiert ist und vordergründig für App-Entwickler zur Verfügung steht. Entwickler können mit Hilfe der von SafetyNet bereitgestellten Informationen zum einen ihre App im Google Play Store verstecken, wenn das Gerät die sog. SafetyNet-Überprüfung (siehe weiter unten für mehr Informationen) nicht besteht. Zum anderen kann eine auf einem Gerät bereits installierte App den SafetyNet-Status selbst überprüfen und so bspw. App-Funktionen deaktivieren oder die Arbeit auch komplett verweigern.</p>
<p>Ziel von SafetyNet ist es, kritischen Apps, wie bspw. Banking- oder Bezahl-Apps, den Status des Gerätes vor deren Verwendung zu überprüfen um potentiellen Missbrauch zu minimieren. - [Quelle: <a href="https://www.droidwiki.org/wiki/SafetyNet"  target="_blank" rel="noreferrer">Droid Wiki</a>]</p>
</blockquote><p><em>Safety Net</em> setzt zur Prüfung der Konformität bei neueren Geräten auf ein Feature namens <a href="https://developer.android.com/training/articles/security-key-attestation"  target="_blank" rel="noreferrer">Hardware Key Attestation</a>. Dieses Feature wurde von Oneplus beim Modell 7T glücklicherweise stümperhaft implementiert. Dadurch war eine hardwarebasierte Überprüfung der Gerätekonformität nicht möglich und es wurde gegen ein niedrigeres Konformitätsniveau namens <em>Basic Integrity</em> geprüft, das eigentlich älteren Smartphone vorbehalten war, die keine Möglichkeit einer hardwarebasierter Verifizierung boten. Eine Zeit lang schien es, als ob Google und die Hersteller von Apps dem nichts entgegenzustellen wusste und ein <em>Root</em> keinerlei Einschränkungen in der Funktionalität des Geräts mit sich brachte. Mein verwendeter <em>Systemless Root Manager</em> <a href="https://github.com/topjohnwu/Magisk"  target="_blank" rel="noreferrer">Magisk</a> bot Google die Stirn und gaukelte zuverlässig eine Konformität des Geräts vor, die durch meinen Root und entsperrten Bootloader nicht gegeben war. Es gab trotzdem einiges Apps, die es schafften den entsperrten Bootloader zu detektieren, bspw. Netflix. Netflix war auf meinem Gerät nicht ausführbar, auch nicht unter Anwendung der <em>Magisk Hide</em> Funktion, die zusätzliche Verschleierungstaktiken und -mechanismen für besonders hartnäckige <em>Root Detection Frameworks</em> anwendete. Netflix wurde im Playstore nicht einmal zum Download angeboten. Für die Zukunft ließ das bereits nichts Gutes erahnen&hellip;</p>

<h3 class="relative group">MacBook Air M1
    <div id="macbook-air-m1" class="anchor"></div>
    
</h3>
<p>Anfang des Jahres 2021 stellte Apple ein neues <a href="https://www.apple.com/de/macbook-air/"  target="_blank" rel="noreferrer">MacBook Air</a> mit einem hauseigenen M1-Prozessor auf <em>ARM</em>-Basis vor. Zu dem Zeitpunkt hatte ich ein sechs Jahre altes <em>Dell XPS13 (9343)</em>, auf dem Ubuntu lief. Da auch 2021 noch nicht <em>das Jahr von Linux auf dem Desktop</em> angebrochen war und mir die durchweg vorhandene Frickelei am OS stark auf die Nerven ging, klang das Angebot von Apple verlockend. Ich hatte schon während meines Ingenieurstudiums ein MacBook (Modell: Late 2008) und mochte das Gerät, auch wenn es für die Windows-dominierte Welt des <em>Engineerings</em> nicht immer gut geeignet war. Nichts desto trotz war es ein tolles Gerät. Schön verarbeitet, tolles Trackpad und ein augenschmeichelndes OS namens <em>Snow Leopard</em>. Ich wollte mich mal wieder auf OSX einlassen und schauen, wie es nach 10 Jahren daher kam und sich entwickelt hat. Kurzum, ich kaufte das neue MacBook Air und daher war auch die nahtlose Integration des iPhones ein verlockendes Argument nicht länger auf Android zu setzen.</p>

<h3 class="relative group">Smartwatch
    <div id="smartwatch" class="anchor"></div>
    
</h3>
<p>Ich bin seit frühen Kickstarter-Tagen ein Besitzer der <a href="https://de.wikipedia.org/wiki/Pebble_%28Smartwatch%29#Pebble_Time_und_Pebble_Time_Steel"  target="_blank" rel="noreferrer">Pebble Time Steel</a>. Einer wirklich tollen kleinen Smartwatch mit 10 Tagen Akku-Laufzeit und <em>Always-On Display</em>. Pebble hat leider seit Jahren das zeitliche gesegnet. Erst kam der Aufkauf durch <em>Fitbit</em> und rund ein Jahr später wurden die <a href="https://www.golem.de/news/fitbit-pebble-uhren-werden-ab-juli-2018-nicht-mehr-unterstuetzt-1801-132376.html"  target="_blank" rel="noreferrer">Server abgeschaltet</a> und die Uhr war nur noch rudimentär zu benutzen. Zwar gab es mit <a href="https://gadgetbridge.org/"  target="_blank" rel="noreferrer">Gadget Bridge</a> und dem Projekt <a href="https://rebble.io/"  target="_blank" rel="noreferrer">Rebble.io</a> die Möglichkeit die Uhr weiterhin zu betreiben und auch <em>Pebble</em> selber veröffentliche eine letzte Version ihrer Begleitapp ohne Cloud-Zwang, jedoch stand die Entwicklung für die Uhr komplett still. Mit Android 11 lief dann auch die entkernte Version der Pebble-App nicht mehr rund und die Uhr verlor ihren Reiz sie zu tragen. Ich wollte auf lange Sicht wieder eine Smartwatch haben, da mit dieser Geräteklasse nicht jedes Mal das Handy aus der Tasche gezogen werden muss, wenn man eine Nachricht erhält. In 80% aller Fälle reicht ein Blick auf die Uhr.<br>
Die <em>Pebble Time</em> war zudem integraler Bestandteil meines mobilen Setups. Ich habe mein Smartphone stets so konfiguriert, dass es absolut lautlos läuft - sprich weder Anrufen, noch Benachrichtigungen war es gestattet auf sich aufmerksam zu machen. Diesen Teil hat immer die Uhr mit ihrer Vibrationsfunktion übernommen und wenn ich meine Ruhe haben wollte, musste ich nur die Uhr stummschalten. Eine neue Watch musste her und die Apple Watch schien konkurrenzlos am Firmament der <em>Wearables</em>.</p>

<h3 class="relative group">Aussperrung meines Bankkontos
    <div id="aussperrung-meines-bankkontos" class="anchor"></div>
    
</h3>
<p>Der <em>Tag X</em>, an dem ich mir spontan ein <em>iPhone 12</em> kaufte war erreicht, als meine werte Hausbank, bei der ich seit 15 Jahren Kunde bin, sich entschloss ihre TAN-App zu updaten. Diese erkannte plötzlich mein gerootetes Gerät und verweigerte prompt die eigene Ausführung und das Generieren neuer TANs. Ich konnte mein Bankkonto zwar noch einsehen, jedoch keine Online-Überweisungen machen. Schlimmer noch: Das bereits bei der Bank registrierte Oneplus 7T ließ sich als TAN-Empfänger nur löschen, wenn man mit ihm eine neue TAN als Bestätigung eingab. Ein Gerät, dass das Starten der TAN-App verweigerte, sollte eine TAN generieren, um sich selber zu deregistrieren. Ein klassischer <em>Worst Case</em> fehlgeleiteter Zirkellogik. Der Support meiner Bank verstand das Problem nicht auf Anhieb. Es traten Fragen auf, warum ich überhaupt <em>&ldquo;ein Telefon in einem solchen Zustand&rdquo;</em> betreiben würde. Schönen Dank auch, werte Sparda-Bank! Auch der Hersteller der TAN-App weigerte sich mir die vorherige lauffähige Version als APK zuzusenden. Ein Online-Banking war schlichtweg nicht länger möglich.</p>

<h2 class="relative group">Der Wechsel
    <div id="der-wechsel" class="anchor"></div>
    
</h2>
<p>Ich kaufte noch am selben Tag das iPhone 12. Es war der Moment, an dem ich wusste, dass Android für mich kein mobiles OS mit Zukunft darstellte. In dem Moment, in dem ich die Hoheit über mein Gerät an Google abtreten würde, hätte ich keine Möglichkeit mehr den systemweiten Adblocker <a href="https://adaway.org/"  target="_blank" rel="noreferrer">AdAway</a> oder den wunderbaren und von Werbung befreiten Youtube-Mod namens <a href="https://vancedapp.com/"  target="_blank" rel="noreferrer">Youtube-Vanced</a> zu nutzen. Android war damit für mich gestorben. Die Frage, ein Android-Gerät mit gesperrten Bootloader in dem Zustand zu nutzen, den der Hersteller oder Google vorgab, stellte sich mir nicht. <em>It&rsquo;s dead, Jim.</em></p>
<p>Der Wechsel auf iOS war begleitet von einer freudig-gestimmten Neugierde und dem Gefühl eines langen Wiedersehens. Ich hatte zuvor nur einmal ein iOS-Device besessen und das war ein iPod Touch und es war, wie auch der Mac, über 10 Jahre her. Der iPod Touch war damals ein tolles Gerät. Es gab diverse Jailbreaks, eine komplette - nicht immer ganz legale - Parallelwelt an Appstores, die abseits von Apples Appstore existierte und Freude bereitete.</p>

<h3 class="relative group">iPhone 12: The eagle has landed
    <div id="iphone-12-the-eagle-has-landed" class="anchor"></div>
    
</h3>
<p>Nun also war der Tag gekommen und mein neues iPhone 12 lag jungfräulich vor mir. Der erste Akt war das Aufbringen einer <a href="https://www.amazon.de/Entspiegelte-Schutzfolien-kompatibel-h%C3%BCllenfreundliche-Displayschutz-Folie-Matt/dp/B08L7SVKHS"  target="_blank" rel="noreferrer">matten Folie</a>, wie es mir <a href="https://git.susa.pw/Tim/"  target="_blank" rel="noreferrer">Tim</a> empfohlen hatte. Ein guter Tipp!</p>
<p>Mein erster Eindruck war geprägt von der Überzeugung, die richtige Entscheidung getroffen zu haben. Das Gerät war kleiner, leichter und angenehmer zu Halten als das Oneplus 7T. Die Einrichtung war eine <em>seamless experience</em> und mein MacBook und das Phone sagten sich das erste Mal <em>Hallo</em> im Heimnetzwerk.</p>
<p>Ich bin kein Freund von klobigen Panzerglasfolien, jedoch konnte ich den Glasrücken des iPhones nicht ohne gutes Gewissen ungeschützt lassen. Als Schutzhülle kaufte ich mir das offizielle <a href="https://www.apple.com/de/shop/product/MHKH3ZM/A/iphone-12-pro-max-leder-case-mit-magsafe-california-poppy?fnode=23f249ac8fba48c79ec0c716297e8264b235a891c60e2a180aa94852c5d739c93a9fed49abdd7fd90e1e13a2b4164abf436f2574da9cfb71d43b4d99f8b52714e7b0fbda666e8fc18b3cf94337cfed74f92b9179867bf45c2edbd2688667715e"  target="_blank" rel="noreferrer">Leder-Case von Apple</a> mit <em>MagSafe</em>-Kompatibilität. Die Hülle ergänzt das Telefon perfekt und die Entscheidung war goldrichtig.</p>

<h3 class="relative group">Apps und ihre Alternativen auf iOS
    <div id="apps-und-ihre-alternativen-auf-ios" class="anchor"></div>
    
</h3>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    ><p>Diese Sektion is nur noch in Teilen gültig, da einige Apps dem allgemein vorherrschenden Trend der <a href="https://en.wikipedia.org/wiki/Enshittification"  target="_blank" rel="noreferrer">Enshittification</a> zum Opfer fielen:</p>
<ol>
<li><strong>Apollo</strong> steht als Reddit-Alternative leider nicht mehr zu Verfügung, da Reddit sich entschlossen hat <em>Third Party Apps</em> in den finanziellen Ruin zu treiben. Siehe: <a href="https://www.reddit.com/r/apolloapp/comments/144f6xm/apollo_will_close_down_on_june_30th_reddits/"  target="_blank" rel="noreferrer">Apollo will close down on June 30th.</a></li>
<li><strong>Tweetbot</strong> steht als Twitter-Alternative nicht mehr zur Verfügung, da <a href="https://en.wiktionary.org/wiki/Space_Karen"  target="_blank" rel="noreferrer">Space Karen</a> beschlossen hat <em>Third Party Apps</em> gänzlich zu verbieten. Siehe: <a href="https://tapbots.com/tweetbot/"  target="_blank" rel="noreferrer">In Memory Of Tweetbot</a></li>
</ol></span>
</div>

<p>Neu kaufen musste ich erstaunlicherweise nur eine einzige App: <a href="https://threema.ch/de"  target="_blank" rel="noreferrer">Threema</a>. Threema ist, seit Anbeginn seines Bestehens, mein Hauptmessenger auf dem Smartphone und ich kann diese App uneingeschränkt empfehlen. Sie erlaubt komplett anonyme Kommunikation, ohne Angaben von Handynummern. Der Umzug meiner <em>Threema ID</em> war simpel und ging mit <a href="/posts/threema-safe-nextcloud/" >meinem selbstverwalteten Backup in der Nextcloud</a> ohne <em>Murren und Knurren</em>. Was nicht transferiert werden konnte, war der Chatverlauf und gesendete Mediendateien.</p>
<p>Einige Apps boten jedoch kein eigenes <em>Pendant</em> auf iOS. Meine gewählten Alternativen wurde wie folgt gewählt:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Android-App</th>
          <th>iOS Alternative</th>
          <th>Aufgabe</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://antennapod.org/"  target="_blank" rel="noreferrer">Antennapod</a></td>
          <td><a href="https://apps.apple.com/de/app/overcast/id888422857"  target="_blank" rel="noreferrer">Overcast</a></td>
          <td>Podcasts</td>
      </tr>
      <tr>
          <td><a href="https://f-droid.org/de/packages/de.ph1b.audiobook/"  target="_blank" rel="noreferrer">Voice</a></td>
          <td><a href="https://apps.apple.com/de/app/bookplayer/id1138219998"  target="_blank" rel="noreferrer">BookPlayer</a></td>
          <td>Hörbücher</td>
      </tr>
      <tr>
          <td><a href="https://f-droid.org/de/packages/de.dennisguse.opentracks/"  target="_blank" rel="noreferrer">OpenTracks</a></td>
          <td><a href="https://apps.apple.com/de/app/open-gpx-tracker/id984503772"  target="_blank" rel="noreferrer">Open GPX Tracker</a></td>
          <td>GPX Tracker</td>
      </tr>
      <tr>
          <td><a href="https://f-droid.org/de/packages/com.health.openscale/"  target="_blank" rel="noreferrer">OpenScale</a></td>
          <td><a href="https://apps.apple.com/lu/app/airweight-for-bluetooth-scales/id931232011?l=de"  target="_blank" rel="noreferrer">Airweight</a></td>
          <td>Xiaomi <a href="/tags/xiaomi-miscale/" >MiScale Waage</a> auslesen</td>
      </tr>
      <tr>
          <td><a href="https://f-droid.org/de/packages/org.adaway/"  target="_blank" rel="noreferrer">AdAway</a></td>
          <td><a href="https://adguard.com/en/blog/adguard-for-ios.html"  target="_blank" rel="noreferrer">AdGuard</a></td>
          <td>Werbeblocker</td>
      </tr>
      <tr>
          <td><a href="https://play.google.com/store/apps/details?id=allen.town.focus.reader"  target="_blank" rel="noreferrer">FocusReader</a></td>
          <td><a href="https://apps.apple.com/us/app/readkit-reading-hub/id1615798039"  target="_blank" rel="noreferrer">ReadKit</a></td>
          <td>RSS-Reader</td>
      </tr>
      <tr>
          <td><a href="https://play.google.com/store/apps/details?id=com.reddit.frontpage"  target="_blank" rel="noreferrer">Reddit</a></td>
          <td><del><a href="https://apps.apple.com/de/app/apollo-for-reddit/id979274575"  target="_blank" rel="noreferrer">Apollo</a></del></td>
          <td>Ersatz für Standardapp</td>
      </tr>
      <tr>
          <td><a href="https://play.google.com/store/apps/details?id=com.twitter.android"  target="_blank" rel="noreferrer">Twitter</a></td>
          <td><del><a href="https://apps.apple.com/de/app/tweetbot-6-for-twitter/id1527500834"  target="_blank" rel="noreferrer">Tweetbot</a></del></td>
          <td>Ersatz für Standardapp</td>
      </tr>
  </tbody>
</table>
</div>
<p>Twitter und Reddit waren zunehmend so überflutet von Werbung und <em>sponsored content</em>, dass ich mich komplett gegen die hauseigenen Apps entschied. Mit <em>Apollo</em> und <em>Tweetbot</em> standen uneingeschränkt zu empfehlende Alternativen zur Installation bereit.</p>
<p>Am schwierigsten habe ich mich mit der Suche nach einem <a href="https://en.wikipedia.org/wiki/List_of_podcatchers"  target="_blank" rel="noreferrer">Podcatcher</a> getan. Podcasts sind ein essentieller Bestandteil meines Alltags. Ich lese keine Bücher, ich schaue keine Nachrichten im Fernsehen - ich bin ein reiner <em>Audiomensch</em>.<br>
Android hat eine sehr gute, feature-reiche und freie App zum Konsumieren von Podcasts namens <a href="https://antennapod.org/"  target="_blank" rel="noreferrer">AntennaPod</a>. Auf iOS habe ich vorerst <a href="https://castro.fm/"  target="_blank" rel="noreferrer">Castro</a> ausprobiert, jedoch bin ich kein Freund von Apps mit Abomodellen und 18,99€ pro Jahr, <em>nur</em> um Podcasts hören zu können, sind eine bodenlose Frechheit. Gelandet bin ich schlussendlich bei <a href="https://overcast.fm/"  target="_blank" rel="noreferrer">Overcast</a>. Auch dieser Podcatcher erfordert ein Abo zum Unkostenpreis von 10€/a, wenn man die App werbefrei genießen möchte.</p>
<p>Ebenfalls vermisse ich die App <a href="http://mixplorer.com/"  target="_blank" rel="noreferrer">MiXplorer</a>, den grandiosesten <em>File Explorer</em> der Android Welt mit Unterstützung für sämtliche Cloud-Dienste und Protokolle, wie <a href="https://de.wikipedia.org/wiki/WebDAV"  target="_blank" rel="noreferrer">WebDAV</a> und <a href="https://de.wikipedia.org/wiki/SSH_File_Transfer_Protocol"  target="_blank" rel="noreferrer">SFTP</a>. Die Zeit eines offenen Dateisystems ist mit iOS nicht länger vorhanden. Fortan steht nur Apples eigene <a href="https://en.wikipedia.org/wiki/Files_%28Apple%29"  target="_blank" rel="noreferrer">Files App</a> zur Verfügung, die nicht Ansatzweise an den Funktionsumfang von MiXplorer heranreicht.</p>

<h3 class="relative group">Was macht iOS besser, als Android
    <div id="was-macht-ios-besser-als-android" class="anchor"></div>
    
</h3>
<p>Auffallend besser an iOS, aus der Sicht eines ehemaligen Android-Users, empfand ich folgende Punkte. Dabei muss berücksichtigt werden, dass es nicht <em>das Eine</em> Android gibt und ich nur die Sicht eines Nutzers der Oneplus und Nexus Geräte beschreiben kann:</p>

<h4 class="relative group">Die zentrale Verwaltung von Einstellungen
    <div id="die-zentrale-verwaltung-von-einstellungen" class="anchor"></div>
    
</h4>
<p>Was Android über die Jahre <em>in Puncto</em> Unübersichtlichkeit perfektioniert hat war, dass jede App ihre eigenen Einstellungen verwaltete. Untermenü über Untermenü und Einstellungen, die oftmals mit globalen Systemeinstellungen des Telefons in Konflikt gerieten. Damit ist unter iOS Schluss und es gefällt mir sehr gut. Es gibt einen zentralen Ort um die Apps und ihre Berechtigungen zu verwalten.</p>

<h4 class="relative group">Die nahtlose Integration weiterer Apple Geräte
    <div id="die-nahtlose-integration-weiterer-apple-geräte" class="anchor"></div>
    
</h4>
<p>Unter meinem Linux-Android-Setup war es nicht immer einfach die Geräte sinnvoll zu vernetzen. Zwar war mit <a href="https://wiki.ubuntuusers.de/KDE_Connect/"  target="_blank" rel="noreferrer">KDE Connect</a> eine gute Möglichkeit geschaffen worden, um bspw. vom Laptop aus eine geteilte Zwischenablage zu nutzen und Dateien auf das Gerät zu senden, jedoch lief die Implementation nicht immer rund. Das funktioniert mit OSX und iOS deutlich komfortabler und das ohne nennenswerte Konfiguration. It just works.
Hervorzuheben ist hier klar die Möglichkeit Telefonate am Mac anzunehmen und <em>on-the-fly</em> zurück auf das iPhone zu wechseln oder aber der Moment, wenn man das MacBook im Freien aufklappt und automatisch angeboten wird das iPhone als Hotspot zu verwenden. Eine komplett neue Erfahrung für mich, die den Alltag bereichert.</p>

<h4 class="relative group">Native CalDAV/CardDAV Integration
    <div id="native-caldavcarddav-integration" class="anchor"></div>
    
</h4>
<p>Android fehlt seit Tag 1 eine native Integration zur Synchronisation von Kontakten, Tasks und Kalendern über CalDAV und CardDAV. Immer schon war es notwendig dafür Adapter-Apps zu installieren. Meine App der Wahl war immer <a href="https://www.davx5.com/"  target="_blank" rel="noreferrer">DAVx5</a>, die auch immer wunderbar funktionierte und nicht hoch genug gelobt werden kann. Google sah es nie ein die Möglichkeit zu integrieren, da ihre eigenen Dienste und der Datenhunger des Unternehmens damit in einem Interessenskonflikt standen. Google möchte alle Kontakte in ihrem GMail sehen und Kalender betrifft das ebenso. Google möchte wissen, wen der eigene <em>Socialgraph</em> umfasst und was man an <em>Tag X</em> wann und wo zu erledigen hat.</p>
<p>Mit iOS gehört dieser Zustand der Vergangenheit an und ich konnte Problemlos meine Kalender, Tasks und Kontakte aus der Nextcloud importieren und nativ syncen.</p>

<h4 class="relative group">Eine konsistente UI
    <div id="eine-konsistente-ui" class="anchor"></div>
    
</h4>
<p>Eine Absolute Katastrophe unter Android ist die inkonsistente <em>UI</em> und <em>UX</em>, da jeder Smartphone-Hersteller Anpassungen des <em>Vanilla-Android</em> vornimmt. Resultat ist letzten Endes, dass die Optik und Einstellungsmöglichkeiten sehr variieren und ein vorhersehbares Verhalten der UI nicht gewährleistet ist. Samsung hat bspw. bis heute den <em>Back-Button</em> und den den Button zum Anzeigen der zuletzt geöffneten Apps in ihrer Position vertauscht. Xiaomi und Realme platzieren eigene Werbung in Fremdapps oder gar in die Systemeinstellungen. Dieser Wust an herstellerspezifischen Android-Varianten ist kaum noch zu Überblicken:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Hersteller</th>
          <th>Android UI</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Samsung</td>
          <td><a href="https://www.samsung.com/de/apps/one-ui/"  target="_blank" rel="noreferrer">One UI</a></td>
      </tr>
      <tr>
          <td>Oneplus</td>
          <td><a href="https://www.oneplus.com/de/oxygenos"  target="_blank" rel="noreferrer">OxygenOS</a></td>
      </tr>
      <tr>
          <td>Huawei</td>
          <td><a href="https://consumer.huawei.com/de/emui-11/"  target="_blank" rel="noreferrer">EMUI</a></td>
      </tr>
      <tr>
          <td>Xiaomi</td>
          <td><a href="https://en.miui.com/"  target="_blank" rel="noreferrer">MIUI</a></td>
      </tr>
      <tr>
          <td>Oppo</td>
          <td><a href="https://www.oppo.com/de/coloros6/"  target="_blank" rel="noreferrer">ColorOS</a></td>
      </tr>
      <tr>
          <td>LG</td>
          <td><a href="https://en.wikipedia.org/wiki/LG_UX"  target="_blank" rel="noreferrer">LG UX</a></td>
      </tr>
      <tr>
          <td>Realme</td>
          <td><a href="https://www.realme.com/in/realme-ui"  target="_blank" rel="noreferrer">Realme UI</a></td>
      </tr>
      <tr>
          <td>Vivo</td>
          <td><a href="https://www.vivoglobal.ph/vivo-origin-os/"  target="_blank" rel="noreferrer">Origin OS</a></td>
      </tr>
  </tbody>
</table>
</div>
<p>Hinzu kommt, dass alle Hersteller bestrebt sind ein eigenes Ökosystem um ihre Geräte aufzubauen. Das ist nachvollziehbar, aber nicht immer Gut und erschwert die Fehlersuche, wenn etwas mal nicht ordnungsgemäß funktioniert. Die Ursache kann dann entweder an Android, der Android Version oder den Anpassungen der Hersteller liegen, was schnell in einem mühseligen Prozess zur Lösung des Problems endet.</p>
<p><strong>Ein Beispiel:</strong><br>
Nach dem Update von <em>OxygenOs 9</em> auf <em>OxygenOS 10</em> konnte ich mit meinem Oneplus 7T nicht mehr die Bluetooth-Lautstärke von meinem <a href="/tags/bose-soundtouch/" >Bose Soundtouch</a> Audio System steuern. Mit anderen Android 10 Geräten funktionierte es. Oneplus schob es auf Bose, Bose schob es auf Oneplus. Damit hat dann keiner Schuld, aber es ist auch keinem Geholfen.</p>

<h4 class="relative group">Verlässliche Ausführung von Apps
    <div id="verlässliche-ausführung-von-apps" class="anchor"></div>
    
</h4>
<p>Jeder Hersteller von Android Smartphones trifft seine hauseigene Abwägung zwischen Funktionalität der Apps und der Batterielaufzeit des Telefons. Auch hier herrscht keine Konsistenz im Android-Universum. <a href="https://dontkillmyapp.com/"  target="_blank" rel="noreferrer">Oneplus ist berüchtigt für sein radikales Beenden von Apps</a>, die essentiell im Hintergrund laufen, bspw. der Mail-Client. Zwar bietet der Hersteller die Option zur Einrichtung einer Ausnahme an, die das Ausführen als Background-Prozess explizit erlaubt, jedoch wird diese manuell gesetzte Ausnahme regelmäßig wieder entzogen und muss neu gesetzt werden. Da Ergebnis: Mal erhält man eine Benachrichtigung über den Eingang einer neuen Mail und das andere Mal erhält man diese erst, wenn man die App startet.</p>

<h4 class="relative group">Apples Privacy Ansatz
    <div id="apples-privacy-ansatz" class="anchor"></div>
    
</h4>
<p>Das erste erfreulich Update für iOS, das ich auf dem iPhone erhielt hatte ein sinnvolles Feature namens <a href="https://developer.apple.com/documentation/apptrackingtransparency"  target="_blank" rel="noreferrer">ATT</a>. <em>App Tracking Transparency</em> verhindert das Tracken des Nutzerverhaltens in Apps. Darauf wurde mit einem Dialogfenster aufmerksam gemacht mit dem man das Tracking direkt mit einem Klick unterbinden kann.</p>
<p>Auch gefällt mir die klare Trennung zwischen Gerät und <em>iCloud</em>-Diensten von Apple. Natürlich möchte auch Apple seine Cloud anpreisen und sicherlich hat dies auch vorteile, aber ich nutze so etwas grundsätzlich nicht. Die iCloud ist zudem die Achillesferse in Apples Ökosystem. Wird sie genutzt, muss Apple zwangsweise die Möglichkeit gegeben werden die Inhalte zu entschlüsseln. Lässt man die Daten auf dem Gerät und übernimmt den Sync selber, bspw. mit einer Nextcloud, umgeht man dieses Manko. Bei Android war die Trennung zwischen Betriebssystem und Google oftmals nicht so einfach zu erkennen. Google liefert bspw. als vorinstallierte <a href="https://de.wikipedia.org/wiki/Bloatware"  target="_blank" rel="noreferrer">Bloatware</a> eine App namens <em>Fotos</em> aus, die Anbindung an Googles <em>GDrive</em> hat. So verschwimmen schnell lokaler und fremdverwalteter Speicher und so landet ebenso schnell der lokale Speicher in Googles Cloud, die dann schnell zum <a href="https://www.theguardian.com/technology/2022/aug/22/google-csam-account-blocked"  target="_blank" rel="noreferrer">eigenen Verhängnis</a> werden können.</p>
<p>Es sind auch kleine unscheinbare Features, die mir aus Datenschutzsicht gefallen, wie der <em>Copy-Paste-Indicator</em>, der transparent - beim Einfügen von Texten - darüber Auskunft gibt, welche App zuvor in die aktuelle Zwischenablage geschrieben hat.</p>

<h3 class="relative group">Androids Stärken gegenüber iOS
    <div id="androids-stärken-gegenüber-ios" class="anchor"></div>
    
</h3>
<p>Wie eingangs bereits erwähnt, sind Androids ruhmreiche Zeiten für mich nicht länger existent. Zu groß ist Googles Einfluss auf das einst freie System. Wer mit gerootetem Telefon auf Online Banking und mobile Payment verzichten kann, wer damit Leben kann, dass er kein Netflix oder andere Apps aus dem Playstore installieren kann, für den ist Android sicherlich noch heute eine gute Wahl. Auf mich traf das nicht länger zu.</p>
<p>Als eindeutige Stärke Androids hervorzuheben ist aber die Freiheit Apps an Googles Playstore vorbei zu installieren. Allzu oft wurden funktionale Apps wie <a href="https://f-droid.org/de/packages/org.adaway/"  target="_blank" rel="noreferrer">AdAway</a> aus dem Playstore entfernt, weil sie bspw. systemweit auf dem Telefon Werbung herausfilterten und somit geschäftsschädigend für Google waren. Unter Android ist eine solche Zensur aber kein Beinbruch. Man kann auf den tollen Appstore <a href="https://www.f-droid.org/"  target="_blank" rel="noreferrer">F-Droid</a> zurückgreifen, der diesen Apps eine neue Heimat bietet. Würde Apple eine solche App verbieten, ist der Handlungsspielraum quasi nicht vorhanden.</p>
<p>Ebenso lobenswert ist der <em>Open Source</em>-Gedanke der Android Community. Für nahezu jeden Anwendungsfall findet man eine freie, quelloffene App. Auffallend für einen iOS-Neueinsteiger war die hohe Anzahl an Apps mit <em>In-App-Purchases</em>, Abomodellen und generell der deutlich erhöhte Preis für Apps in Apples Appstore.</p>

<h2 class="relative group">Fazit
    <div id="fazit" class="anchor"></div>
    
</h2>
<p>Der Umstieg von Android auf iOS fiel deutlich leichter und angenehmer aus, als ich ursprünglich befürchtet hatte. Das Zusammenspiel mit meinem MacBook Air macht Spaß und ich ertappe mich recht häufig dabei ein Telefonat auch einmal an meinem Laptop anzunehmen, was ich zuvor schlichtweg nie konnte. Inzwischen hat es auch die <a href="https://www.apple.com/de/shop/buy-watch/apple-watch-se"  target="_blank" rel="noreferrer">Apple Watch SE</a> an mein Handgelenk geschafft und auch dieses Gerät fügt sich nahtlos in Apples Ökosystem ein. Das erste Telefonat über die Uhr am Handgelenk war für mich ein kleiner <em>Michael Knight Moment</em>, der mir ein ausgeprägtes Schmunzeln ins Gesicht gezaubert hat. Auch die Entsperrung des iPhones mittels der Uhr, während des Tragens einer Corona-Maske ist nur eine kleine Funktion, die den Tag aber bereichert. Die Watch nutze ich erneut genau so, wie damals meine geliebte Pebble Smartwatch. Das iPhone ist konstant stumm geschaltet und die Watch vibriert bei Benachrichtigungen und Anrufen und wenn ich meine Ruhe haben will, stelle ich die Watch stumm.</p>
<p>Mittlerweile fällt es mir schwer mein altes Oneplus 7T noch in die Hand zu nehmen. Es ist erstaunlich, wie schnell einem Altvertrautes in kürzester Zeit fremd erscheint.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Sony PS-LX310BT: Plattenspieler mit Bose Soundtouch und Homeassistant nutzen</title>
      <link>https://jbetzen.net/posts/sony-pslx310bt/</link>
      <pubDate>Fri, 26 Mar 2021 09:19:12 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/sony-pslx310bt/</guid>
      <description>Sony bietet mit dem PS-LX310BT einen Bluetooth fähigen Plattenspieler an, der einfach in ein bestehendes Audio Setup mit Bose Soundtouch Lautsprechern eingebunden und automatisiert werden kann.</description>
      <content:encoded><![CDATA[ <p>Seit kurzem befindet sich ein recht drolliger Plattenspieler aus dem Hause Sony in meinem Besitz. Er schimpft sich <a href="https://www.sony.de/electronics/audio-komponenten/ps-lx310bt"  target="_blank" rel="noreferrer">PS-LX310BT</a> und hat eine Eigenschaft, die jedem Audiophilen vermutlich die Zehennägel hochstehen lässt: Der Plattenspieler hat eingebaute Bluetooth-Konnektivität.</p>
<p>Da ich in meinem trauten Heim ein Bose Soundtouch Multi Room Audio System nutze, das ebenfalls Bluetooth als Eingangsquelle unterstützt, habe ich mich daran gemacht und den Plattenspieler in Homeassistant eingebunden.</p>

<h2 class="relative group">Das Audio-Setup
    <div id="das-audio-setup" class="anchor"></div>
    
</h2>
<p>Kern meiner heimischen Beschallung ist ein <a href="https://de.yamaha.com/de/products/audio_visual/av_receivers_amps/rx-v577/features.html"  target="_blank" rel="noreferrer">Yamaha RX-V577 AV-Receiver</a>. An diesem ist über koaxiales Cinch ein <a href="https://www.bose.de/de_de/support/products/bose_speakers_support/bose_smarthome_speakers_support/soundtouch-adapter.html"  target="_blank" rel="noreferrer">Bose Soundtouch Wireless Adapter</a> angeschlossen, der zusammen mit 4 weiteren <a href="https://www.bose.de/de_de/support/products/bose_speakers_support/bose_smarthome_speakers_support/soundtouch-10-wireless-system.html"  target="_blank" rel="noreferrer">Bose Soundtouch 10</a> im Verbund arbeitet und die gesamte Wohnung mit Klang bespielt. Sämtliche Bose Soundtouch-Geräte haben Bluetooth eingebaut und können daher das Signal des Plattenspielers entgegen nehmen.</p>

<h2 class="relative group">Integration des Plattenspielers
    <div id="integration-des-plattenspielers" class="anchor"></div>
    
</h2>
<p>Mein Ziel war es den Plattenspieler so simpel wie möglich in Homeassistant zu integrieren. Grundproblem ist, dass der Soundtouch Adapter von Bose über analoges Cinch verbunden ist und der AV-Receiver daher, aufgrund der analogen Natur der Steckerverbindung, keinen sauberen State zugespielt bekommt, der aussagt ob gerade etwas auf dem AnalGen Eingang abgespielt wird oder nicht. Dieses Problem lässt sich sehr leicht lösen, da der Soundlink Adapter von Bose gut in Homeassistant integriert ist und zuverlässig mitteilt, ob gerade eine Wiedergabe erfolgt und welche Eingangsquelle aktuell benutzt wird, z.B. Spotify, Bluetooth oder lokale Musikwiedergabe über LAN.</p>

<h3 class="relative group">Ziel der Integration
    <div id="ziel-der-integration" class="anchor"></div>
    
</h3>
<ol>
<li>Sobald der Plattenspieler eingeschaltet wird, soll er sich mit dem Wireless Link Adapter von Bose verbinden</li>
<li>Sobald die Verbindung hergestellt ist, soll der AV-Receiver automatisch auf den korrekten Eingang umschalten und die Lautstärke auf ein brauchbares Maß einstellen</li>
</ol>

<h2 class="relative group">Sensoren und Automation in Homeassistant
    <div id="sensoren-und-automation-in-homeassistant" class="anchor"></div>
    
</h2>
<p>Es wird in Homeassistant ein Sensor benötigt, der registriert ob der Plattenspieler eine Bluetooth-Verbindung zum Bose Wireless Link Adapter aufgenommen hat. Anschließend wird eine Automation erstellt, die dies dem AV-Receiver mitteilt.</p>

<h3 class="relative group">Binary Sensor für den Plattenspieler
    <div id="binary-sensor-für-den-plattenspieler" class="anchor"></div>
    
</h3>
<p>Nachdem der Plattenspieler initial mit dem Soundlink Adapter gekoppelt und eine LP erstmals abgespielt wurde, zeichnete sich folgender State für den Soundlink Adapter in Homeassistant ab:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/sony-pslx310bt/Sony-PSLX310BT-attributes.png"
          alt="Attribute des Plattenspieler in den Hass Dev Tools"
        />
  
  
  </figure>
<p>Wie man sehen kann, gibt sich der Plattenspieler über das Attribut <code>media_title</code> mit seinem Namen <code>PS-LX310BT</code> zu erkennen. Dieses Attribut kann mit einem <a href="https://www.home-assistant.io/integrations/binary_sensor.template/"  target="_blank" rel="noreferrer">Template Binary Sensor</a> ausgelesen werden und somit Auskunft darüber geben, ob der Plattenspieler verbunden ist und Musik abspielt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">binary_sensor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sensors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">plattenspieler_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Plattenspieler spielt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ is_state_attr(&#34;media_player.soundtouch_wohnzimmer&#34;, &#34;media_title&#34;, &#34;PS-LX310BT&#34;) }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">icon_template</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% if is_state(&#34;plattenspieler_on&#34;, &#34;on&#34;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            mdi:record-player
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            mdi:volume-off
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% endif %}</span></span></span></code></pre></div></div>

<h3 class="relative group">Automation für AV-Receiver
    <div id="automation-für-av-receiver" class="anchor"></div>
    
</h3>
<p>Wenn der neu erstellte Binary Sensor nun auf <code>On/True</code> schaltet, muss der AV-Receiver auf den entsprechenden analogen Eingang schalten, an dem der Bose Soundlink Adapter angestöpselt ist. In meinem Fall ist das der <code>AV5</code>. Zusätzlich soll der Bose Adapter und der AV-Receiver eine vordefinierte Lautstärke voreinstellen.</p>
<p>Die passende Automation sieht so aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Set AV5 for Soundtouch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.plattenspieler_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.av_receiver</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.select_source</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.av_receiver</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l">AV5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.volume_set</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.av_receiver</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volume_level</span><span class="p">:</span><span class="w"> </span><span class="m">0.8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.volume_set</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">media_player.soundtouch_wohnzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">volume_level</span><span class="p">:</span><span class="w"> </span><span class="m">0.84</span></span></span></code></pre></div></div>
<p>Wenn nun der Plattenspieler eingeschaltet wird, baut er automatisch eine Bluetooth-Verbindung zum Bose Adapter auf. Der AV-Receiver schaltet sich darauf ein und regelt die Lautstärke.</p>

<h2 class="relative group">Weiterführendes
    <div id="weiterführendes" class="anchor"></div>
    
</h2>
<p>Die Bluetooth-Funktionalität des Plattenspielers lässt sich wunderbar mit Multi-Room-Audio-Feature der <a href="/tags/bose-soundtouch/" >Bose Soundtouch</a> Boxen kombinieren. Somit kann man sein Vinyl synchron im gesamten Haus erklingen lassen:</p>





  
  
    
  
  


  <section class="space-y-10 w-full">
    
    











  
  
  








  
  
    

    
    
      
      
        
      
        
      
        
      
    

    
    

    
    
      
        
        
      
    
  



<article class="article-link--shortcode flex flex-col md:flex-row relative">
  
    <div class="flex-none relative overflow-hidden  thumbnail-shadow md:mr-7 thumbnail">
      <img
        src="/posts/soundtouch-multi-room-audio/featured.png"
        role="presentation"
        loading="lazy"
        decoding="async"
        class="not-prose absolute inset-0 w-full h-full object-cover">
    </div>
  
  <div class=" mt-3 md:mt-0">
    <header class="items-center text-start text-xl font-semibold">
      <a
        
          href="/posts/soundtouch-multi-room-audio/"
        
        class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
        <h2>
          Bose Soundtouch Multi-Room Audio
          
        </h2>
      </a>
      
      
    </header>
    <div class="text-sm text-neutral-500 dark:text-neutral-400">
      







  

  
  
  
    
  

  

  
    
  

  

  
    
  

  
    
  

  

  

  

  

  


  <div class="flex flex-row flex-wrap items-center">
    
    
      <time datetime="2020-04-11T23:42:13&#43;02:00">11.04.2020</time><span class="px-2 text-primary-500">&middot;</span><span>404 Wörter</span><span class="px-2 text-primary-500">&middot;</span><span title="Lesezeit">2 min</span>
    

    
    
  </div>

  

  
  
    <div class="flex flex-row flex-wrap items-center">
      
        
      
        
          
            
              
                <a class="relative mt-[0.5rem] me-2" href="/categories/media-player/">
                  
                  <span class="flex cursor-pointer">
  
  
  
    
    
  
  
    <span
      class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
  
    media-player
  </span>
</span>

                </a>
              
            
            
          
        
      
        
      
        
          
            
              
            
            
          
        
      
        
          
            
              
            
            
          
        
      
    </div>
  


    </div>
    
      
      <div
        class="article-link__summary prose dark:prose-invert max-w-fit mt-1 ">
        Seit Homeassistant 0.107 erhält die Bose Soundtouch Integration die Fähigkeit für Multi-Room-Audio und das Zusammenschließen einzelner Lautsprecher zu eine Zone. Wie man eine praktische Lovelace Card mit dem Custom Mini Media Player erstellt wird in diesem Post gezeigt.
      </div>
    
  </div>
</article>

  </section>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>Homeassistant Fitness Tracker</title>
      <link>https://jbetzen.net/posts/fitness-tracker/</link>
      <pubDate>Sat, 23 Jan 2021 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/fitness-tracker/</guid>
      <description>Die Homeassistant Companion App liefert Fitnessdaten, die direkt vom Smartphone OS generiert werden. Diese können in Langzeitstatistiken mittels dem Utility Meter in Homeassistant getrackt und ausgewertet werden.</description>
      <content:encoded><![CDATA[ <p>Die <a href="https://companion.home-assistant.io/"  target="_blank" rel="noreferrer">Homeassistant Companion App</a> für <a href="https://play.google.com/store/apps/details?id=io.homeassistant.companion.android"  target="_blank" rel="noreferrer">Android</a> kann diverse Sensoren bereitstellen, bspw. die Anzahl der registrierten Schritte oder die derzeitige Aktivität, wie bspw. Radfahren. Diese Daten können mit der <a href="https://github.com/custom-cards/circle-sensor-card"  target="_blank" rel="noreferrer">Circle-Sensor-Card</a> anschaulich in der Lovelace UI dargestellt werden.</p>

<h2 class="relative group">Sensoren in der App aktivieren
    <div id="sensoren-in-der-app-aktivieren" class="anchor"></div>
    
</h2>
<p>In der App muss unter dem Menüpunkt App <code>Configuration &gt; Verwalte Sensoren</code> die folgenden Einträge aktiviert werden:</p>
<ul>
<li><code>Erkannte Aktivität</code></li>
<li><code>Schrittzähler</code></li>
</ul>
<p>Diese Sensoren erscheinen anschließend in Homeassistant als <em>Entities</em>.</p>

<h2 class="relative group">Homeassistant Konfiguration
    <div id="homeassistant-konfiguration" class="anchor"></div>
    
</h2>

<h3 class="relative group">Schrittzähler
    <div id="schrittzähler" class="anchor"></div>
    
</h3>
<p>Im folgenden Beispiel hat der Sensor meine Oneplus 7T die bezeichnung <code>sensor.op7t_steps_sensor</code>.</p>
<p>Der Schrittzähler-Sensor, den die App bereitstellt, hat leider ein inhärentes Problem. Er zählt sämtliche Schritte, die das Smartphone registriert hat, seit dem letzten Neustart des Geräts. Wird also während eines Spaziergangs das Gerät neugestartet, beginnt der Zähler erneut bei <code>0</code>. Dies ist natürlich nicht förderlich, wenn man die Anzahl der täglichen Schritte ermitteln will. Es gibt jedoch eine einfache Lösung für dieses Problem, mittels der <a href="https://www.home-assistant.io/integrations/utility_meter/"  target="_blank" rel="noreferrer">Utility Meter Integration</a>. Diese ist eigentlich zur Aufsummierung von Verbrauchswerten über längere Zeiträume gedacht, kann aber für die Ermittlung der Schrittanzahl zweckentfremdet werden.
Dazu muss die <code>configuration.yaml</code> um folgenden Eintrag erweitert werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">utility_meter</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fitness_schritte_heute</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">source</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.op7t_steps_sensor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cycle</span><span class="p">:</span><span class="w"> </span><span class="l">daily</span></span></span></code></pre></div></div>
<p>Diese Konfiguration summiert sämtliche Schritte im täglichen Intervall auf, ungeachtet davon ob der Sensor zeitweilig wieder auf <code>0</code> springt. Durch diesen Eintrag wird ein neuer Sensor erzeugt, der die Bezeichnung <code>sensor.fitness_schritte_heute</code> trägt.</p>
<p><strong>ACHTUNG:</strong> Der Sensor steht erst nach einem vollen Messintervall zur Verfügung, also nach einem Tag.</p>

<h3 class="relative group">Aktivitätssensor
    <div id="aktivitätssensor" class="anchor"></div>
    
</h3>
<p>Der Aktivitätssensor in diesem Beispiel trägt die Bezeichnung <code>sensor.op7t_erkannte_aktivitat</code>. Dieser kann unterschiedliche States besitzen, bspw. <code>on_bicyle</code> (Radfahren) oder <code>walking</code> (Spazieren).</p>
<p>Wenn ermittelt werden soll, wie lange eine der Aktivitäten ausgeführt wurde, kann man die <a href="https://www.home-assistant.io/integrations/history_stats/"  target="_blank" rel="noreferrer">History Stats Integration</a> benutzen, die einen definierten State über einen festgelegten Zeitraum analysiert:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">history_stats</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Fitness gehen heute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.op7t_erkannte_aktivitat</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;walking&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">start</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">end</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ now() }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">history_stats</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Fitness fahrradfahren heute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.op7t_erkannte_aktivitat</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on_bicycle&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">start</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ now().replace(hour=0).replace(minute=0).replace(second=0) }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">end</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ now() }}&#39;</span></span></span></code></pre></div></div>
<p>Diese zwei Einträge erzeigen zwei neue Sensoren:</p>
<ul>
<li><code>sensor.fitness_gehen_heute</code>, der anzeigt wie viele Stunden am aktuellen Tag spazieren gegangen wurde</li>
<li><code>sensor.fitness_fahrradfahren_heute</code>, der anzeigt wie viele Stunden am aktuellen Tag Rad gefahren wurde</li>
</ul>

<h3 class="relative group">Visualisieren der Daten
    <div id="visualisieren-der-daten" class="anchor"></div>
    
</h3>
<p>Die eingangs erwähnte <a href="https://github.com/custom-cards/circle-sensor-card"  target="_blank" rel="noreferrer">Circle-Sensor-Card</a> eignet sich hervorragend zur Darstellung der neuen Sensoren. Der Sensorwert steht zentral in der Mitte und außen herum befindet sich ein radiale farbliche Kontour, die bei festgelegten Werten bestimmte Farben annimmt. So lassen sich Tagesziele definieren und anschaulich darstellen.</p>
<p>Im ersten Schritt sollte die Circle-Sensor-Card aus <a href="https://hacs.xyz/"  target="_blank" rel="noreferrer">HACS</a> installiert werden. Anschließend kann diese in einem <a href="https://www.home-assistant.io/lovelace/horizontal-stack/"  target="_blank" rel="noreferrer">Horizontal-Stack</a> verwendet werden.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">horizontal-stack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Fitness</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cards</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;custom:circle-sensor-card&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.fitness_schritte_heute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Schritte</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">max</span><span class="p">:</span><span class="w"> </span><span class="m">10000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">min</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">show_card</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">stroke_width</span><span class="p">:</span><span class="w"> </span><span class="m">14</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">gradient</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">units</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39; &#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">font_style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">font-size</span><span class="p">:</span><span class="w"> </span><span class="m">1.</span><span class="l">2em</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">color_stops</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;0&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#f08080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;6000&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#ff9966&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;8000&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;custom:circle-sensor-card&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.fitness_gehen_heute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Spazieren</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">max</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">min</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">show_card</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">stroke_width</span><span class="p">:</span><span class="w"> </span><span class="m">14</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">gradient</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">units</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">font_style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">font-size</span><span class="p">:</span><span class="w"> </span><span class="m">1.</span><span class="l">2em</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">color_stops</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;0&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#f08080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;1&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;0.5&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#ff9966&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;custom:circle-sensor-card&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.fitness_fahrradfahren_heute</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Rad</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">max</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">min</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">show_card</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">stroke_width</span><span class="p">:</span><span class="w"> </span><span class="m">14</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">gradient</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">units</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">font_style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">font-size</span><span class="p">:</span><span class="w"> </span><span class="m">1.</span><span class="l">2em</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">color_stops</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;0&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#f08080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;0.8&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">&#39;0.4&#39;</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#ff9966&#39;</span></span></span></code></pre></div></div>
<p>Das Ergebnis sieht anschließend folgendermaßen aus:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/fitness-tracker/fitness-lovelacecard.png"
          alt="Lovelace Card mit Fitness Werten"
        />
  
  
  </figure>
<p>Viel Spaß beim Tracken der eigenen Werte.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Xiaomi Thermometer in Homeassistant einbinden</title>
      <link>https://jbetzen.net/posts/xiaomi-mithermometer/</link>
      <pubDate>Sun, 17 Jan 2021 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/xiaomi-mithermometer/</guid>
      <description>Xiaomi bietet für kleines Geld ein Bluetooth LE fähiges Thermometer namens LYWSD03MMC an. Dieses kann einfach mit einer Custom Firmware geflasht und über ESPHome oder die Software BT-MQTT-Gateway ausgelesen und in Homeassistant eingebunden werden.</description>
      <content:encoded><![CDATA[ 
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Zu diesem Artikel ist ein Update verfügbar, in dem gezeigt wird wie das Thermometer mittels ESPHome ausgelesen werden kann: <a href="/posts/esphome-btle-hub/">ESPHome Bluetooth Low Energy Hub</a></span>
</div>

<p>Xiaomi bietet seit längerem ein kleines aber feines Thermometer an, das für unter 5€ bei <a href="https://de.aliexpress.com/item/4001042009489.html?spm=a2g0o.productlist.0.0.134255a6e8cD4B&amp;algo_pvid=573f573c-d1ae-4b96-b7f3-92178d477e1b&amp;algo_expid=573f573c-d1ae-4b96-b7f3-92178d477e1b-1&amp;btsid=2100bdde16108977175744368ebd56&amp;ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_"  target="_blank" rel="noreferrer">AliExpress</a> erstanden werden kann. Die genaue Modellbezeichnung lautet <code>LYWSD03MMC</code>. Neben der Temperatur kann dieses zusätzlich noch die Luftfeuchtigkeit messen und seinen Batteriestand mitteilen.</p>
<p>Die Daten des Geräts werden über Bluetooth LE (<em>Low Energy</em>) gesendet und mit der Standard-Firmware von Xiaomi leider nur verschlüsselt übertragen. Zur Dekodierung wird ein sogenannter <code>bind_key</code> benötigt, der sich aber mit kleinem Aufwand auslesen lässt. Alternativ lässt sich eine Custom-Firmware über eine Bluetooth-Verbindung auf die Thermometer flashen, die zusätzlich zur bind_key-Methode einen unverschlüsselten Empfang der Daten ermöglicht.</p>

<h2 class="relative group">Einbindung in Homeassistant
    <div id="einbindung-in-homeassistant" class="anchor"></div>
    
</h2>
<p>Zur Einbindung in Homeassistant stehen prinzipiell 2 Wege zur Verfügung, die ich kurz erläutern möchte:</p>
<ol>
<li>Wer direkten Zugriff auf die Bluetooth-Schnittstelle der Hardware hat, die Homeassistant ausführt - etwa auf einem Raspberry Pi mit Hass.io -, kann direkt die gesendeten Daten mit der nativen Integration <a href="https://www.home-assistant.io/integrations/mitemp_bt/"  target="_blank" rel="noreferrer">mitemp_bt</a> abgreifen oder auf die Custom Component <a href="https://github.com/custom-components/ble_monitor"  target="_blank" rel="noreferrer">ble_monitor</a> zurückgreifen.</li>
<li>Wer keine direkten Zugriff auf die Bluetooth-Schnittstelle besitzt, muss einen Umweg gehen und ein weiteres Gerät nutzen, das diese Daten abgreifen kann, bspw. ein ESP32-Mikrocontroller oder ein Raspberry Pi Zero.</li>
</ol>
<p>In diesem Post möchte ich auf den <strong>Fall 2</strong> eingehen und zeigen, wie ein ESP32 mit ESPHome oder ein Raspberry Pi mit einem Bluetooth2MQTT-Gateway genutzt werden kann.</p>

<h2 class="relative group">Was benötigt wird
    <div id="was-benötigt-wird" class="anchor"></div>
    
</h2>
<ol>
<li>Ein Mobiltelefon mit aktivierten Bluetooth und installierten Chrome-Browser, zum flashen der Custom Firmware</li>
<li>Die aktuellste <a href="https://github.com/pvvx/ATC_MiThermometer/releases/"  target="_blank" rel="noreferrer">Custom-Firmware</a> für das Thermometer. Diese Datei muss auf das Smartphone heruntergeladen werden.</li>
<li>Eine App, um die MAC-Adresse des Thermometers auszulesen, z.B. <a href="https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp"  target="_blank" rel="noreferrer">NRF Connect</a> (Android)</li>
</ol>

<h2 class="relative group">Flashen der Custom-Firmware
    <div id="flashen-der-custom-firmware" class="anchor"></div>
    
</h2>
<p>Mit dem Smartphone muss die Webseite des <a href="https://pvvx.github.io/ATC_MiThermometer/TelinkMiFlasher.html"  target="_blank" rel="noreferrer">Telink Flashers</a> aufgerufen werden. Ist dies erfolgt, muss der Button <code>Connect</code> geklickt werden. Es erscheint eine Liste an Bluetooth-Geräten, die in Empfangsreichweite sind. An dieser Stelle wird das Thermometer ausgewählt. Unter dem Punkt <code>Select Firmware</code> wird nun die zuvor heruntergeladene Custom Firmware Datei ausgewählt und mit einem Klick auf <code>Start Flashing</code> auf das Thermometer übertragen.</p>
<p>Anschließend trennt sich die Bluetooth-Verbindung und das Thermometer erhält einen neuen Bluetooth-Gerätenamen. Erneut muss sich mittels Teelink Flasher mit dem Thermometer verbunden werden. Nun erscheint ein weitaus umfangreicheres Menü zum Einstellen des Thermometers.</p>
<p>Wer das kleine Gesicht unten links im Display vermisst, kann dieses unter dem Menüpunkt <code>Smiley:</code> durch aktivieren der Option <code>Comfort</code> wieder auf dem Display sichtbar machen. Wurde die Einstellung gesetzt, muss diese mit <code>Send Config</code> an das Gerät gesendet werden.</p>

<h2 class="relative group">Bind-Key auslesen
    <div id="bind-key-auslesen" class="anchor"></div>
    
</h2>
<p>Im Menü des Teelink Flashers gibt es den Button <code>Show all mi keys</code>. Klickt man auf diesen, erscheint dort unter dem Punkt <code>Mi Bind Key</code> die benötigte Zahlen- und Ziffernfolge. Diese wird für die Konfiguration von ESHhome benötigt und muss kopiert werden.</p>

<h2 class="relative group">MAC-Adresse des Thermometers
    <div id="mac-adresse-des-thermometers" class="anchor"></div>
    
</h2>
<p>Die MAC-Adresse kann mit der NRF Connect App ausgelesen werden. Alternativ kann dies auch unter Linux mit dem Terminal erfolgen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo hcitool lescan
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">LE Scan ...
</span></span><span class="line"><span class="cl">A4:C1:38:B1:CD:7F ATC_838293</span></span></code></pre></div></div>
<p>Mit der MAC-Adresse und dem Bind-Key sind alle Informationen vorhanden, um mit ESPHome fortzufahren.</p>

<h2 class="relative group">Integration in Homeassistant
    <div id="integration-in-homeassistant" class="anchor"></div>
    
</h2>

<h3 class="relative group">Methode 1: ESPHome
    <div id="methode-1-esphome" class="anchor"></div>
    
</h3>
<p>Esphome hat sich zu einem wahren schweizer Taschenmesser im Umgang mit Homeassistant entwickelt. Seit kurzem ist auch das zugehörige Add-On offiziell im Hass.io Repository vorhanden. Dieses Add-On übernimmt mit einem Wizard die initiale Konfiguration des ESP32 und muss dann lediglich um die <a href="https://esphome.io/components/sensor/xiaomi_ble.html#lywsd03mmc"  target="_blank" rel="noreferrer">folgenden Zeilen</a> ergänzt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">sensor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">xiaomi_lywsd03mmc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;A4:C1:38:B1:CD:7F&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">bindkey</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;eef418daf699a0c188f3bfd17e4565d9&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">temperature</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;LYWSD03MMC Temperature&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">humidity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;LYWSD03MMC Humidity&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">battery_level</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;LYWSD03MMC Battery Level&#34;</span></span></span></code></pre></div></div>
<p>Diese Werte dürfen natürlich nicht 1:1 übernommen werden und die MAC-Adresse, sowie der Bind-Key müssen durch die zuvor ermittelten Angaben ausgetauscht werden.</p>
<p>ESPHome exportiert anschließend eine vorkompilierte Binärdatei, die nun auf den ESP32 geflasht werden muss, bspw. mit dem bewährten <a href="https://github.com/esphome/esphome-flasher/releases"  target="_blank" rel="noreferrer">ESPHome Flasher</a>.</p>
<p>Anschließend muss der geflashte Mikrokontroller im ESPHome Add-On als Node hinzugefügt werden und die Werte für Temperatur, Luftfeuchtigkeit und den Batteriestand stehen in Homeassistant zur Verfügung.</p>

<h3 class="relative group">Methode 2: BT-MQTT-Gateway
    <div id="methode-2-bt-mqtt-gateway" class="anchor"></div>
    
</h3>

  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Die Methode ist nicht länger zu empfehlen und eine Integration über ESPHome ist klar zu bevorzugen: <a href="/posts/esphome-btle-hub/">ESPHome Bluetooth Low Energy Hub</a></span>
</div>

<p>Wer diesen Blog schon länger verfolgt, erinnert sich evtl. an meinen <a href="/posts/xiaomi-scale/" >Post zur Xiaomi Miscale</a>. Dort kam ein Bluetooth Gateway zum Einsatz, das die gelesen Werte mittels MQTT an Homeassistant sendet. Dieses Gateway läuft stabil und zuverlässig auf einem Raspberry Pi Zero und unterstützt auch das Auslesen des Thermometers. Bevor dieses jedoch eingebunden werden kann, muss noch eine Einstellung in der Custom Firmware des Thermometers vorgenommen werden.</p>

<h4 class="relative group">Advertising Type ändern
    <div id="advertising-type-ändern" class="anchor"></div>
    
</h4>
<p>Das BT-MQTT-Gateway erwartet die zugesandten Daten ausschließlich im <code>ATC1441</code>-Format (Stand: 17.01.2021) . Dieses Format muss im Teelink Flasher unter dem Menüpunkt <code>Advertising type:</code> ausgewählt werden und anschließend mit <code>Send Config</code> an das Gerät gesendet werden.</p>

<h4 class="relative group">Gateway Worker konfigurieren
    <div id="gateway-worker-konfigurieren" class="anchor"></div>
    
</h4>
<p>In der <code>config.yaml</code> von BT-MQTT-Gateway muss ein sogenannter <em>Worker</em> angelegt werden. Dieser funktioniert im Kern exakt wie bei der Xiaomi Personenwaage, hat aber eine ausführlichere Konfiguration:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">lywsd03mmc</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">args</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">kueche</span><span class="p">:</span><span class="w"> </span><span class="l">a4:c1:38:b1:cd:7f</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">topic_prefix</span><span class="p">:</span><span class="w"> </span><span class="l">mithermometer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">passive</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c">#command_timeout: 30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">scan_timeout</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">update_interval</span><span class="p">:</span><span class="w"> </span><span class="m">120</span></span></span></code></pre></div></div>
<p><strong>ACHTUNG:</strong> Im Gegensatz zu ESPHome muss die MAC-Adresse <em>zwingend</em> in Kleinbuchstaben eingegeben werden. Ansonsten werden keine Werte ausgelesen und übermittelt.</p>

<h4 class="relative group">MQTT-Sensoren anlegen
    <div id="mqtt-sensoren-anlegen" class="anchor"></div>
    
</h4>
<p>Das Gateway funkt nun die Werte unter dem MQTT-Topic <code>mithermometer/kueche</code> über MQTT. Damit Homeassistant diese verwenden kann, müssen 3 Sensoren für Temperatur, Luftfeuchtigkeit und Batteriestand angelegt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Temperature&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state_topic</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;mithermometer/kueche&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;°C&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ value_json.temperature }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:thermometer</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Humidity&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state_topic</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;mithermometer/kueche&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;%&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ value_json.humidity }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:water-percent</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Mithermometer Kueche Battery Level&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state_topic</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;mithermometer/kueche&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;%&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ value_json.battery }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:battery-80</span></span></span></code></pre></div></div>
<p>Voilà! Nun muss nur noch einmal Homeassistant neu gestartet werden und die Werte können fortan in der Lovelace-UI dargestellt werden.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Aqara Fenster- und Türsensoren</title>
      <link>https://jbetzen.net/posts/aqara-windowsensor/</link>
      <pubDate>Fri, 28 Aug 2020 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/aqara-windowsensor/</guid>
      <description>Aqara bietet in ihrem Smarthome Sortiment preisgünstige Fenster- und Türensensoren an, die über Zigbee funken und den Conbee II in Homeassistant eingebunden werden können. Dieser Post enthält ein paar Beispiele, wie man die Sensordaten nutzen kann.</description>
      <content:encoded><![CDATA[ <p>Die Fenster- und Türkontaksensoren von Aqara lassen sich für kleines Geld erwerben. Sie funktionieren, wie die meisten Zigbee-Geräte, einwandfrei im Zusammenspiel mit der <a href="https://www.home-assistant.io/integrations/deconz/"  target="_blank" rel="noreferrer">Deconz-Integration</a>. Nach erfolgreicher Einbindung tauchen die Kontaktsensoren als Binärsensoren auf und überliefern zusätzlich noch den Wert des Batteriestands, sowie der Umgebungstemperatur. Wie man diese Sensoren sinnvoll einsetzen kann, zeige ich anhand von 2 Beispielen.</p>

<h2 class="relative group">Nutzungsbeispiele
    <div id="nutzungsbeispiele" class="anchor"></div>
    
</h2>

<h3 class="relative group">Beispiel 1: Benachrichtigung bei Regen wenn Fenster offen
    <div id="beispiel-1-benachrichtigung-bei-regen-wenn-fenster-offen" class="anchor"></div>
    
</h3>
<p>Möchte man eine Nachricht erhalten, wenn es zu regnen beginnt und noch Außenfenster geöffnet sind, kann man das folgendermaßen erreichen:</p>

<h4 class="relative group">Gruppe für Fenster anlegen
    <div id="gruppe-für-fenster-anlegen" class="anchor"></div>
    
</h4>
<p>Ist auch nur ein Fenster geöffnet ändert sich der Status der Gruppe auf <code>on</code>. Somit muss nicht jedes Fenster abgefragt werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">group</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fenster</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Außenfenster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">binary_sensor.fenster_kueche</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">binary_sensor.fenster_ballsaal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">binary_sensor.fenster_schlafzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">binary_sensor.fenster_wohnzimmer</span></span></span></code></pre></div></div>

<h4 class="relative group">Automation zum Senden der Pushnachricht
    <div id="automation-zum-senden-der-pushnachricht" class="anchor"></div>
    
</h4>
<p>Diese Automation setzt vorraus, dass ein Wettersensor vorhanden ist. Ich verwende in dem Beispiel <a href="https://www.home-assistant.io/integrations/openweathermap/"  target="_blank" rel="noreferrer">Openweathermaps</a>. Ändert sich der Sensor auf den state <code>rainy</code> wird geprüft ob noch ein Außenfenster geöffnet ist. Sollte dies der Fall sein, wird eine Pushnachricht auf das Smartphone gesendet, in der die noch geöffneten Fenster aufgelistet werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Warnung Regen Fenster offen</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">weather.owm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w">  </span><span class="s1">&#39;rainy&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">group.fenster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">state</span><span class="p">:</span><span class="w">  </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_op7t</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Es regnet und es sind Fenster offen&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ states | selectattr(&#39;entity_id&#39;,&#39;in&#39;, state_attr(&#39;group.fenster&#39;,&#39;entity_id&#39;)) | selectattr(&#39;state&#39;,&#39;eq&#39;,&#39;on&#39;) | map(attribute=&#39;name&#39;) | join(&#39;, &#39;) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;push_regenwarnung_fenster&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">channel</span><span class="p">:</span><span class="w"> </span><span class="l">Alarm</span></span></span></code></pre></div></div>

<h4 class="relative group">Pushnachricht automatisch entfernen, wenn Fenster geschlossen
    <div id="pushnachricht-automatisch-entfernen-wenn-fenster-geschlossen" class="anchor"></div>
    
</h4>
<p>Wer gerne faul ist und die Push-Nachricht nicht manuell vom Smartphone wischen möchte, kann das über eine Automation erledigen. Werden alle Fenster geschlossen während es regnet, verschwindet die Push-Nachricht automatisch vom Smartphone:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Warnung Regen Fenster offen dismiss</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">group.fenster</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">weather.owm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">state</span><span class="p">:</span><span class="w">  </span><span class="l">rainy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_op7t</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">clear_notification</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;push_regenwarnung_fenster&#34;</span></span></span></code></pre></div></div>

<h3 class="relative group">Beispiel: 2: Benachrichtigung Lüften vor dem Schlafen gehen
    <div id="beispiel-2-benachrichtigung-lüften-vor-dem-schlafen-gehen" class="anchor"></div>
    
</h3>
<p>Ich vergesse oftmals vor dem Schlafen gehen mein Schlafzimmer zu lüften und habe mir dafür eine Automation angelegt. Diese triggert abends um 20:00h und auch nur, wenn ich wirklich zu Hause bin und das Schlafzimmerfenster geschlossen ist:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Schlafzimmer lueften</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">at</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;20:00:00&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">and</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.fenster_schlafzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">person.ingeborg</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;home&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_op7t</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Lüften?&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Das Fenster im Schlafzimmer ist geschlossen. Nochmal lüften vor dem luschern?&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;push_sz_lueften&#34;</span></span></span></code></pre></div></div>
<p>Auch in diesem zweiten Beispiel gilt als optionaler Obolus: Wer Faulheit liebt, kann noch eine zweite Automation anlegen, die die gesendete Push-Nachricht vom Smartphone entfernt, sobald das Fenster geöffnet wird:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Schlafzimmer lueften dismiss</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.fenster_schlafzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_op7t</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">clear_notification</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;push_sz_lueften&#34;</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Homeassistant und Octoprint</title>
      <link>https://jbetzen.net/posts/octoprint/</link>
      <pubDate>Wed, 15 Jul 2020 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/octoprint/</guid>
      <description>Die freie Software Octoprint erlaubt es 3D-Drucker zu steuern und kann zeitgleich über eine Integration in Homeassistant eingebunden werden. Wie man den aktuellen Druckstatus in einer Lovelace Card darstellen kann, wird in diesem Post erläutert.</description>
      <content:encoded><![CDATA[ <p><a href="https://octoprint.org/"  target="_blank" rel="noreferrer">Octoprint</a> ist eine freie open-source Software, die es Laien ermöglicht in einer webbasierten Oberfläche 3D-Drucker anzusteuern und zu überwachen. Sie stellt eine vielseitige API zum monitoren des 3D-Druckers bereit, die einfach und unkompliziert in Homeassistant mit der offiziellen <a href="https://www.home-assistant.io/integrations/octoprint/"  target="_blank" rel="noreferrer">Octoprint Integration</a> genutzt werden kann.</p>

<h2 class="relative group">Heimisches Setup
    <div id="heimisches-setup" class="anchor"></div>
    
</h2>
<ul>
<li>Mein Raspberry Pi mit Octoprint hängt an einer Gosund SP111 Steckdose, die mit Tasmota geflasht wurde und in Homeassistant eingebunden ist. Diese hat den Namen <code>switch.strom_octoprint</code>. Ist diese Steckdose ausgeschaltet ist logischerweise auch Octoprint nicht verfügbar. Ziel war es Octoprint sauber auf dem Pi herunterzufahren, bevor er vom Strom getrennt wird. Dafür wird ein REST-Command und ein Template-Switch benötigt.</li>
<li>Mein Anycubic 3D-Drucker bezieht ebenfalls über eine Gosund SP111 seinen Strom und ist in Homeassistant über den Switch <code>switch.strom_anycubic</code> erreichbar.</li>
</ul>

<h2 class="relative group">Octoprint API-Key generieren
    <div id="octoprint-api-key-generieren" class="anchor"></div>
    
</h2>
<p>Zur erfolgreichen Einbindung der Octoprint Integration muss ein Eintrag in der <code>configuration.yaml</code> erfolgen, die anschließend Sensoren zum Drucker und weitere Binary Sensoren bereitstellt.
Im ersten Schritt muss jedoch ein <em>API-Key</em> in der Octoprint Weboberfläche erstellt werden. Dazu muss im Browser zum Eintrag <code>Einstellungen &gt; API</code> navigiert werden und der API-Key in die Zwischenablage kopiert werden.</p>

<h2 class="relative group">Homeassistant konfigurieren
    <div id="homeassistant-konfigurieren" class="anchor"></div>
    
</h2>
<p>In der <code>configuration.yaml</code> muss zwingend die IP-Adresse des Octoprint Servers angegeben werden. Diese sollte idealerweise statisch sein und sich nicht bei jedem Neustart ändern:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">octoprint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">IP-ADDRESSE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">api_key</span><span class="p">:</span><span class="w"> </span><span class="l">API-KEY</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Anycubic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">number_of_tools</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">bed</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div></div>
<p>Je nach Druckermodell können mehrere Druckdüsen (Nozzles) vorhanden sein. Die Anzahl der Düsen sollte in der Konfiguration generell gesetzt werden, da sonst kein Sensor zur Überwachung der Temperatur bereitgestellt wird. Ebenso verhält es sich mit dem Eintrag <code>bed: true</code>. Wenn ein beheiztes Druckbett vorhanden ist, sollte also der boolsche Wert auf <code>true</code> gesetzt werden.</p>

<h3 class="relative group">Rest Command erstellen
    <div id="rest-command-erstellen" class="anchor"></div>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">octopi_shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;http://OCTOPRINT-IP/api/system/commands/core/shutdown&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">method</span><span class="p">:</span><span class="w"> </span><span class="l">POST</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">headers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">content-type</span><span class="p">:</span><span class="w"> </span><span class="l">application/json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">X-Api-Key</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret octoprint_api_key</span></span></span></code></pre></div></div>
<p>In der <code>secrets.yaml</code> sollte ein Eintrag für den Octoprint-API-Key angelegt werden. Die IP von der Octoprint Instanz muss in dem API-Aufruf entsprechend angepasst werden.</p>

<h3 class="relative group">Zwei Template-Switches anlegen
    <div id="zwei-template-switches-anlegen" class="anchor"></div>
    
</h3>
<p>Der erste Template-Switch, schaut ob die Steckdose des Raspberry Pis mit Octoprint bereits eingeschaltet ist. Wird der Switch auf <code>off</code> geschaltet, wird der Pi über die Octoprint API heruntergefahren. Nach weiteren 45 Sekunden schaltet sich die Steckdose auf <code>aus</code>. Der Switch erscheint in Homeassistant unter dem Name <code>switch.octopi_shutdown</code>.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">switches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">octopi_shutdown</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="l">Octopi Shutdown</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ is_state(&#39;switch.strom_octoprint&#39;, &#39;on&#39;) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">turn_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_octoprint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">turn_off</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">rest_command.octopi_shutdown</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">delay</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;00:00:45&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.turn_off</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_octoprint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon_template</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% if is_state(&#39;switch.strom_octoprint&#39;, &#39;on&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:power-plug
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:power-plug-off
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% endif %}</span></span></span></code></pre></div></div>
<p>Ein weiterer Template Switch für das Gesamtsystem 3D-Drucker + Raspberry Pi wird zusätzlich angelegt. Dieser kann sowohl 3D-Drucker, als auch den Pi mit einem Klick an und ausschalten:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">switches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">3dprinter_setup</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3D Printer Setup&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ is_state(&#39;switch.strom_octoprint&#39;, &#39;on&#39;) and is_state(&#39;switch.strom_anycubic&#39;, &#39;on&#39;) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">turn_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_octoprint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_anycubic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">turn_off</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.turn_off</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_anycubic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">rest_command.octopi_shutdown</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">delay</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;00:00:45&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.turn_off</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_octoprint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon_template</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% if is_state(&#39;switch.strom_octoprint&#39;, &#39;on&#39;) and is_state(&#39;switch.strom_anycubic&#39;, &#39;on&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:power-plug
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:power-plug-off
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% endif %}</span></span></span></code></pre></div></div>
<p>Dieser Switch erscheint in Homeassistant unter dem Name <code>switch.3dprinter_setup</code>.</p>

<h2 class="relative group">Lovelace Card: Picture-Elements
    <div id="lovelace-card-picture-elements" class="anchor"></div>
    
</h2>
<p>Was nützen einem Sensorwerte, wenn sie nicht auch anschaulich dargestellt werden. Ich nutze dafür eine <a href="https://www.home-assistant.io/lovelace/picture-elements/"  target="_blank" rel="noreferrer">Picture Elements Card</a>, die folgendermaßen aussieht:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/octoprint/octoprint-card.png"
          alt="Lovelace Card für 3D-Drucker-Setup"
        />
  
  
  </figure>

<h3 class="relative group">Bildvorlage für Picture-Elements Card
    <div id="bildvorlage-für-picture-elements-card" class="anchor"></div>
    
</h3>
<p>Entsprechend meines Druckermodells (Anycubic i3 Mega), habe ich eine Bilddatei erstellt und diese in der Lovelace UI eingebunden. Dieses Bild kann mit einem Rechtsklick gespeichert und in Homeassistant <code>/config/www/</code>-Ordner platziert werden. In meinem Beispiel lautet der Pfad zum Bild <code>/config/www/entitypictures/octoprint-ui-template.png</code>.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/octoprint/octoprint-ui-template.png"
          alt="Template File für 3D-Drucker-Setup"
        />
  
  
  </figure>

<h3 class="relative group">Custom Cards aus HACS installieren
    <div id="custom-cards-aus-hacs-installieren" class="anchor"></div>
    
</h3>
<p>Zur Darstellung der Lovelace Card verwende ich drei Custom Cards, die einfach über HACS installiert werden können:</p>
<ul>
<li><a href="https://github.com/custom-cards/bar-card"  target="_blank" rel="noreferrer">custom:bar-card</a>, zur Darstellung der Fortschrittsbalken</li>
<li><a href="https://github.com/custom-cards/button-card"  target="_blank" rel="noreferrer">custom:button-card</a>, zur Darstellung der unteren 4 Icons</li>
<li><a href="https://github.com/ofekashery/vertical-stack-in-card"  target="_blank" rel="noreferrer">custom:vertical-stack-in-card</a>, zur Anordnung der Karten übereinander, ohne sichtbare Abgrenzung zueinander</li>
</ul>

<h3 class="relative group">Code für Lovelace
    <div id="code-für-lovelace" class="anchor"></div>
    
</h3>
<p>Dargestellt werden Informationen zum Druckfortschritt, der Bett- und Düsentemperatur, zum Druckstatus, zur Fehlerindikation und zur verbleibenden Druckzeit. Darüber hinaus gibt es einen Service Button zum Ein- und Ausschalten von Drucker und Pi.</p>
<p>Die gesamte Config der Card sieht wie folgend aus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">/local/entitypictures/octoprint-ui-template.png</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">picture-elements</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">elements</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">conditional</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">switch.3dprinter_setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">elements</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">service-button</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Turn On</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">service_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.3dprinter_setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">86</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">14</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">width</span><span class="p">:</span><span class="w"> </span><span class="m">20</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">conditional</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">switch.3dprinter_setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">elements</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">service-button</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Turn Off</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">switch.turn_off</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">service_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">switch.3dprinter_setup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">86</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">14</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">width</span><span class="p">:</span><span class="w"> </span><span class="m">20</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">state-icon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">switch.octopi_shutdown</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">14</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">68</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">conditional</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_anycubic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">elements</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">state-icon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">binary_sensor.anycubic_printing_error</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">22</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">14</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">state-icon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_anycubic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;mdi:power-plug&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">22</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">31</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tap_action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="l">toggle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">conditional</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">switch.strom_anycubic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">elements</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">state-label</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.anycubic_current_state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">55</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">23</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">animation</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.anycubic_job_percentage</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_row</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">height</span><span class="p">:</span><span class="w"> </span><span class="l">36px</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Progress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">positions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">indicator</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">minmax</span><span class="p">:</span><span class="w"> </span><span class="l">inside</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">outside</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">speed</span><span class="p">:</span><span class="w"> </span><span class="m">1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">72</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">40</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">width</span><span class="p">:</span><span class="w"> </span><span class="m">48</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;custom:bar-card&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">animation</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.anycubic_actual_bed_temp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_row</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">height</span><span class="p">:</span><span class="w"> </span><span class="l">36px</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">max</span><span class="p">:</span><span class="w"> </span><span class="m">60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Bed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">positions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">indicator</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">minmax</span><span class="p">:</span><span class="w"> </span><span class="l">inside</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">outside</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">severity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#f08080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="m">40</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#ff9966&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="m">40</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="m">55</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="m">55</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="m">65</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">72</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">62</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">width</span><span class="p">:</span><span class="w"> </span><span class="m">48</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;custom:bar-card&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">animation</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.anycubic_actual_tool0_temp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_row</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">height</span><span class="p">:</span><span class="w"> </span><span class="l">36px</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">max</span><span class="p">:</span><span class="w"> </span><span class="m">200</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Nozzle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">positions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">indicator</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;off&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">minmax</span><span class="p">:</span><span class="w"> </span><span class="l">inside</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">outside</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">severity</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#f08080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="m">120</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#ff9966&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="m">120</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="m">180</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="m">180</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="m">220</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">style</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">left</span><span class="p">:</span><span class="w"> </span><span class="m">72</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">top</span><span class="p">:</span><span class="w"> </span><span class="m">84</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">width</span><span class="p">:</span><span class="w"> </span><span class="m">48</span><span class="l">%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;custom:bar-card&#39;</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Bose Soundtouch Multi-Room Audio</title>
      <link>https://jbetzen.net/posts/soundtouch-multi-room-audio/</link>
      <pubDate>Sat, 11 Apr 2020 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/soundtouch-multi-room-audio/</guid>
      <description>Seit Homeassistant 0.107 erhält die Bose Soundtouch Integration die Fähigkeit für Multi-Room-Audio und das Zusammenschließen einzelner Lautsprecher zu eine Zone. Wie man eine praktische Lovelace Card mit dem Custom Mini Media Player erstellt wird in diesem Post gezeigt.</description>
      <content:encoded><![CDATA[ <p>Mit dem Release 0.107 erhielt die <a href="https://www.home-assistant.io/integrations/soundtouch/"  target="_blank" rel="noreferrer">Bose Soundtouch Integration</a> nach langer Zeit ein nützliches Feature für die heimliche Beschallung über mehrere Räume. Das Soundtouch System von Bose kann auf einfachem Weg mehrere Lautsprecher zu einem Verbund zusammenschließen und Audio synchron ausgeben. Das funktionierte in Homeassistant auch seit geraumer Zeit, jedoch gab die State Machine keinerlei Infos/Attribute preis, ob aktuell ein Lautsprecherverbund aktiv und welcher Lautsprecher der Master des Verbunds war. Diese fehlenden Attribute wurden im Release 0.107 ergänzt und nach einer anfänglich fehlerhaften Implementierung mit dem Release 0.108 komplettiert. Existiert ein Zonenverbund steht nun unter den Soundtouch Media Playern das Attribut <code>is_master: true/false</code> zur Verfügung.</p>
<p>Zeitgleich zu dem Release erfuhr auch die grandiose Custom Lovelace Card <a href="https://github.com/kalkih/mini-media-player"  target="_blank" rel="noreferrer">Mini-Media-Player</a> ein Update, um einfach einen Verbund (Zone) von Lautsprechern erstellen und auflösen zu können. Diese Custom Card kann einfach über HACS in Homeassistant installiert werden. Für die Nutzung von Multi-Room-Audio in der Lovelace Card gibt es einen <a href="https://github.com/kalkih/mini-media-player#speaker-group-management"  target="_blank" rel="noreferrer">eigenen Abschnitt</a> in der README.</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/soundtouch-multi-room-audio/soundtouch-lovelace-card.png"
          alt="Speaker Group Management in Lovelace Card"
        />
  
  
  </figure>

<h2 class="relative group">Sensor zur Ausgabe des aktuellen Masters
    <div id="sensor-zur-ausgabe-des-aktuellen-masters" class="anchor"></div>
    
</h2>
<p>Wer in seiner Homeassistant UI nun einen Sensor haben möchte, der angibt welcher Lautsprecher gerade der Master ist oder ob überhaupt eine Zone aus mehreren Lautsprechern besteht, kann das auf folgende Weise mit einem Template Sensor und einer zugehörigen Automation, die diesen Sensor updated, lösen. In dem folgenden Beispiel nutze ich meine drei <a href="https://www.bose.de/de_de/products/speakers/smart_home/soundtouch-10-wireless-system.html#v=soundtouch_10_black_eu_gb_ie"  target="_blank" rel="noreferrer">Soundtouch 10</a> Lautsprecher und einen <a href="https://www.bose.de/de_de/products/speakers/smart_home/soundtouch-adapter.html"  target="_blank" rel="noreferrer">Wireless Link Adapter</a>, die mit dem Namensschema <code>media_player.soundtouch_RAUMNAME</code> benannt sind.</p>

<h3 class="relative group">Template Sensor erstellen
    <div id="template-sensor-erstellen" class="anchor"></div>
    
</h3>
<p>Der <a href="https://community.home-assistant.io/t/template-sensor-to-find-master-in-multi-room-audio-setup/179467"  target="_blank" rel="noreferrer">Template Sensor</a> iteriert über alle Lautsprecher, prüft ob eine Zone existiert und sucht anschließend nach dem Attribut <code>is_master</code>. Anschließend gibt er den Namen des Lautsprechers aus. Existiert keine Zone gibt er <code>No Master</code> aus.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sensors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">soundtouch_master</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% set master = states.media_player | selectattr(&#39;attributes.soundtouch_zone&#39;, &#39;defined&#39;) | selectattr(&#39;attributes.soundtouch_zone.is_master&#39;,&#39;eq&#39;,True) | map(attribute=&#39;name&#39;) | list | first %}
</span></span></span><span class="line"><span class="cl"><span class="sd">        {{ master if master else &#34;No Master&#34; }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Multiroom Master&#39;</span></span></span></code></pre></div></div>

<h3 class="relative group">Update des Template Sensors
    <div id="update-des-template-sensors" class="anchor"></div>
    
</h3>
<p>In der nachfolgenden Automation müssen die Namen der Lautsprecher entsprechend den von euch vergebenen angepasst werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Soundtouch update master sensors</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">media_player.soundtouch_kuche</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">media_player.soundtouch_room2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">media_player.soundtouch_schlafzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">media_player.soundtouch_wohnzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">homeassistant.update_entity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.soundtouch_master</span></span></span></code></pre></div></div>

<h2 class="relative group">Ergebnis
    <div id="ergebnis" class="anchor"></div>
    
</h2>
<p>Der erstellte Sensor mit dem Namen <code>sensor.soundtouch_master</code> kann nun in jeder Lovelace Card eingebunden werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/soundtouch-multi-room-audio/soundtouch-result.png"
          alt="Lovelace Card zum Managen mehrerer Speaker"
        />
  
  
  </figure>

<h2 class="relative group">Weiterführendes
    <div id="weiterführendes" class="anchor"></div>
    
</h2>
<p>Das Multi-Room-Audio Feature der Bose Soundtouch Boxen habe ich mit meinem Sony Plattenspieler mit Bluetooth-Konnektivität genutzt, um Vinyl im gesamten Haus abzuspielen. Der Post dazu: <a href="/posts/sony-pslx310bt/">Sony PS-LX310BT: Plattenspieler mit Bose Soundtouch und Homeassistant nutzen</a></p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Room Based Presence mit Motion Sensoren</title>
      <link>https://jbetzen.net/posts/room-based-presence/</link>
      <pubDate>Mon, 30 Mar 2020 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/room-based-presence/</guid>
      <description>Wer im Eigenheim Bewegungssensoren installiert hat, kann schnell und einfach einen Sensor erstellen, der aufzeigt in welchem Raum die letzte Bewegung registriert wurde. Wie man diese Info sinnvoll nutzen kann, wird in diesem Post erläutert.</description>
      <content:encoded><![CDATA[ 
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Der neue Post <a href="/posts/last-motion-sensor/">Last Motion Sensor</a> enthält eine verbesserte Methode für den Last Motion Sensor, der ohne die Custom Component <a href="https://github.com/snarky-snark/home-assistant-variables"  target="_blank" rel="noreferrer">home-assistant-variables</a> funktioniert.</span>
</div>

<p>In jedem meiner Räume befindet sich ein <a href="https://www2.meethue.com/de-de/p/hue-bewegungssensor/8718696743171"  target="_blank" rel="noreferrer">Hue Motion</a> Bewegungsmelder aus dem Hause Philips, die ich in Homeassistant über das Deconz Addon eingebunden habe. Diese triggern - neben dem obligatorischem Einschalten von Lichtern - in meinem Alltag diverse Automationen, z.B:</p>
<ul>
<li>Automatisch die Tagesschau auf dem Bose Lautsprecher anmachen, wenn ich wochentags das Bett verlassen habe und in die Küche laufe</li>
<li>Automatisch die Rollos hochfahren, wenn ich aufgestanden bin</li>
<li>Automatisch den Wecker ausschalten, wenn ich wochentags das Schlafzimmer verlassen habe</li>
</ul>
<p>Da ich alleine in meiner Wohnung lebe, eignen sich die Bewegungsmelder auch hervorragend, um Homeassistant mitzuteilen in welchem Raum man sich gerade befindet. Dies lässt sich hervorragend nutzen, um bspw. Lichter in Räumen auszuschalten in denen man sich gerade nicht aufhält oder automatisch den Fernseher zu pausieren, wenn man den Raum verlässt. Wie man einen solchen <code>Last Motion Sensor</code> erstellt, erkläre ich im Folgenden.</p>

<h2 class="relative group">Die Theorie des Vorgehens
    <div id="die-theorie-des-vorgehens" class="anchor"></div>
    
</h2>
<p>Wird ein Bewegungssensor getriggert (state = on), muss Homeassistant schauen welcher es war und anschließend den Namen des Raumes in eine Variable schreiben. Anschließend kann ein optionaler <em>Template Sensor</em> erstellt werden, der diese Variable ausließt und ein passendes Icon zu jedem Raum zuordnet.</p>

<h2 class="relative group">Praktische Umsetzung
    <div id="praktische-umsetzung" class="anchor"></div>
    
</h2>
<p>Variablen sind kein natürliches Konzept von Homeassistant und müssen über eine <em>Custom Component</em> namens <a href="https://github.com/snarky-snark/home-assistant-variables"  target="_blank" rel="noreferrer">home-assistant-variables</a> installiert werden.</p>

<h3 class="relative group">Variable anlegen
    <div id="variable-anlegen" class="anchor"></div>
    
</h3>
<p>Es wird eine <a href="https://github.com/snarky-snark/home-assistant-variables#configuration"  target="_blank" rel="noreferrer">Variable konfiguriert</a>. In meinem Fall habe ich sie <code>last_motion</code> genannt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">last_motion</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restore</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:map-marker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Last Motion&#39;</span></span></span></code></pre></div></div>

<h3 class="relative group">Automation: Letzter Aufenthaltsraum
    <div id="automation-letzter-aufenthaltsraum" class="anchor"></div>
    
</h3>
<p>Als Trigger dienen die Bewegungssensoren, die in Homeassistant als <em>Binary Sensors</em> erscheinen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Set Last Motion Variable</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">binary_sensor.hue_motion_flur</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">binary_sensor.hue_motion_kueche</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">binary_sensor.hue_motion_room2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">binary_sensor.hue_motion_schlafzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">binary_sensor.hue_motion_wohnzimmer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">binary_sensor.aqara_motion_bad</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">binary_sensor.aqara_motion_klo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">var.set</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">var.last_motion</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% if trigger.entity_id == &#34;binary_sensor.hue_motion_flur&#34; %} Flur
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif trigger.entity_id == &#34;binary_sensor.hue_motion_kueche&#34; %} Küche
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif trigger.entity_id == &#34;binary_sensor.hue_motion_room2&#34; %} Room2
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif trigger.entity_id == &#34;binary_sensor.hue_motion_schlafzimmer&#34; %} Schlafzimmer
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif trigger.entity_id == &#34;binary_sensor.hue_motion_wohnzimmer&#34; %} Wohnzimmer
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif trigger.entity_id == &#34;binary_sensor.aqara_motion_klo&#34; %} Klo
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif trigger.entity_id == &#34;binary_sensor.aqara_motion_bad&#34; %} Bad
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% endif %}</span></span></span></code></pre></div></div>
<p>Anschließend steht eine Variable mit dem Namen <code>var.last_motion</code> zur Verfügung. Der <em>State</em> dieser Variable kann - analog zu einem Sensor - ausgelesen und in Automationen genutzt werden.</p>

<h3 class="relative group">Template Sensor mit Icon für jeden Raum
    <div id="template-sensor-mit-icon-für-jeden-raum" class="anchor"></div>
    
</h3>
<p>Optional kann zusätzlich noch ein Template Sensor erstellt werden, der jedem State/Raum ein passendes Icon zuweist:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sensors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">last_motion</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Wo bin ich?&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ states.var.last_motion.state }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon_template</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% if is_state(&#39;var.last_motion&#39;, &#39;Küche&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:food-variant
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% elif is_state(&#39;var.last_motion&#39;, &#39;Wohnzimmer&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:sofa
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% elif is_state(&#39;var.last_motion&#39;, &#39;Flur&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:cube-unfolded
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% elif is_state(&#39;var.last_motion&#39;, &#39;Room2&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:guitar-electric
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% elif is_state(&#39;var.last_motion&#39;, &#39;Schlafzimmer&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:bed
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% elif is_state(&#39;var.last_motion&#39;, &#39;Bad&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:shower
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% elif is_state(&#39;var.last_motion&#39;, &#39;Klo&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:toilet
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:account-question
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% endif %}</span></span></span></code></pre></div></div>

<h2 class="relative group">Licht im Raum aufblinken, wenn Smartphone geladen
    <div id="licht-im-raum-aufblinken-wenn-smartphone-geladen" class="anchor"></div>
    
</h2>
<p>Nutzt man die offizielle <a href="https://companion.home-assistant.io/"  target="_blank" rel="noreferrer">Homeassistant App</a>, kann diese Auskunft über den Batteriestand des Telefons und auch darüber, ob das Smartphone gerade geladen wird, ausgeben. Die folgende Automation prüft, ob das Telefon sich am Lader befindet und lässt ein Licht im aktuellen Raum kurz aufblinken, wenn der Akkustand über 90% erreicht hat:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Flash Light when Smartphone fully charged</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">numeric_state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.mizar_battery_level</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">above</span><span class="p">:</span><span class="w"> </span><span class="m">90</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">after</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;09:00:00&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">before</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;22:00:00&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">person.jonas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;home&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">light.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% if is_state(&#39;var.last_motion&#39;,&#39;Schlafzimmer&#39;) %} light.schlafzimmer_kommode
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif is_state(&#39;var.last_motion&#39;,&#39;Wohnzimmer&#39;) %} light.wohnzimmer_regal
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif is_state(&#39;var.last_motion&#39;,&#39;Room2&#39;) %} light.room2_decke
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif is_state(&#39;var.last_motion&#39;,&#39;Küche&#39;) %} light.kuche_kuhlschrank
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% endif %}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">flash</span><span class="p">:</span><span class="w"> </span><span class="l">short</span></span></span></code></pre></div></div>
<p>Ein weiteres Beispiel habe ich in dem separaten Post <a href="/posts/autopause-mediaplayer/">Automatisch pausierender Media Player</a> aufgeführt. Dort wird erläutert, wie man einen beliebigen Media Player pausiert, wenn man den Raum verlässt und die Wiedergabe automatisch fortsetzt, sobald man den Raum erneut betritt.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Homeassistant, FritzBox und Anrufbenachrichtigung per Push-Nachricht</title>
      <link>https://jbetzen.net/posts/fritzbox-callmon/</link>
      <pubDate>Fri, 28 Feb 2020 23:42:12 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/fritzbox-callmon/</guid>
      <description>Die Fritzboxen von AVM lassen sich einfach in Homeassistant einbinden und stellen einen Call Monitor (Callmon) bereit, der über ein- und ausgehende Anrufe informiert. Wie man diese Daten bei Abwesenheit vom Heim per Push-Nachricht senden kann wird in diesem Post erläutert.</description>
      <content:encoded><![CDATA[ <p>FritzBox-Router aus dem Hause AVM sind speziell in Deutschland sehr beliebt und aus Erfahrung eine gute Wahl. Neben zahlreichen Funktionen wie einem NAS, Netzwerkstreaming, Anrufbeantworter, etc. stellt eine wichtige Kernfunktion die Telefonie dar. Diese Anrufinformationen können mit der offiziellen <a href="https://www.home-assistant.io/integrations/fritzbox_callmonitor/"  target="_blank" rel="noreferrer">FritzBox Callmonitor</a> Integration in Homeassistant integriert und entsprechend automatisiert werden. Somit ist Homeassistant fähig zu erkennen, ob sich das Telefon gerade im Leerlauf, im Telefonat oder im Zustand des Klingelns oder Wählens befindet.</p>
<p>Im Folgenden möchte ich erläutern, wie man sich automatisch eine Push-Nachricht mit einem beliebigen Notify-Service senden lassen kann, wenn die Fritzbox einen Anruf empfangen hat, der jedoch nicht entgegen genommen wurde, bspw. wenn man außer Haus ist. Hierzu wird vorausgesetzt, dass die Fritzbox bereits erfolgreich in Homeassistant integriert wurde.</p>

<h2 class="relative group">Theorie zum Callmonitor
    <div id="theorie-zum-callmonitor" class="anchor"></div>
    
</h2>
<p>Nach der Integration der Fritzbox steht eine Sensor zur Verfügung, der die folgenden states ausgibt:</p>
<ul>
<li><code>idle</code>: Die Fritzbox befindet sich im Leerlauf. Es wird nicht telefoniert, gewählt oder ein Telefonat geführt.</li>
<li><code>ringing</code>: Die Fritzbox klingelt. Ein Anrufer wartet auf die Abnahme des Hörers.</li>
<li><code>talking</code>: Ein aktives Telefonat wird geführt.</li>
<li><code>dialing</code>: Ein Telefonat wird aufgebaut und die ausgehende Nummer gewählt.</li>
</ul>
<p>Der Punkt <code>dialing</code> kann für das Vorhaben des Erhalts einer Push-Nachricht vernachlässigt werden, da dies nicht der Fall ist, wenn man sich außer Haus befindet.</p>
<p>Wer denkt, dass nun der einfachste Weg wäre eine Automation zu schreiben, die den State des Callmonitors überwacht und einfach die Daten des Anrufers (Name, Nummer, Uhrzeit, Datum) per Push-Nachricht sendet, ist leider auf dem Holzweg. Diese Daten stehen nämlich nur zur Verfügung, wenn der Callmonitor den State <code>ringing</code> hat, <em>nicht</em> aber bei <code>idle</code>.
Es muss also ein Zwischenschritt erfolgen, der die Anruferdaten zwischenspeichert und wieder zur Verfügung stellt, wenn der State des Callmonitors von <code>ringing</code> auf <code>idle</code> wechselt. Hierfür greife ich auf die Custom Component mit dem Namen <a href="https://github.com/snarky-snark/home-assistant-variables"  target="_blank" rel="noreferrer">home-assistant-variables</a> zurück</p>

<h2 class="relative group">Variablen als Zwischenspeicher einrichten
    <div id="variablen-als-zwischenspeicher-einrichten" class="anchor"></div>
    
</h2>
<p>Nachdem die neue Variablen-Component aus HACS installiert wurde, müssen folgende Einträge in der <code>configuration.yaml</code> erfolgen. Diese speichern die relevanten Informationen des Anrufers:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">var</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">callmon_name</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restore</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:phone-classic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Anrufername&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ states.sensor.callmon.attributes.from_name }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">callmon_number</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restore</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:card-account-phone</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Anrufernummer&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ states.sensor.callmon.attributes.from }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">callmon_time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restore</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:clock-time-three-outline</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Anrufzeit&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ now().strftime(&#34;%H:%M&#34;) }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">callmon_date</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restore</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:calendar-today</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Anrufdatum&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ now().strftime(&#34;%d.%m.%Y&#34;) }}&#39;</span></span></span></code></pre></div></div>

<h2 class="relative group">Automation, um Variblen zu füllen
    <div id="automation-um-variblen-zu-füllen" class="anchor"></div>
    
</h2>
<p>Diese Automation aktualisiert die zuvor definierten <code>value_templates</code> der Variablen und speicher die relevanten Informationen über den Verpassten Anruf:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">festnetz_set_caller_info_variable</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.callmon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;ringing&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">var.update</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">var.callmon_name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">var.callmon_number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">var.callmon_time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">var.callmon_date</span></span></span></code></pre></div></div>

<h2 class="relative group">Automation für Push-Benachrichtigung
    <div id="automation-für-push-benachrichtigung" class="anchor"></div>
    
</h2>
<p>Jetzt muss nur noch eine Automation erstellt werden, die schaut wann der Callmonitor von <code>ringing</code> auf <code>idle</code> wechselt, anschließend die Anruferinformationen aus den Variablen ausliest und diese per Push-Nachricht sendet. Als Beispiel nehme ich mein Smartphone auf dem die offizielle Homeassitant Android App läuft und die über die Mobile Integration eingebunden ist. Es kann jedoch jeder beliebige Notification Service, wie beispielweise Slack, Jabber oder Matrix verwendet werden.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Festnetzbenachrichtigung Hass App</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">initial_state</span><span class="p">:</span><span class="w"> </span><span class="kc">True</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.callmon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="l">ringing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">idle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_mizar</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Festnetzanruf 📞&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {{ states.var.callmon_name.state }} ({{ states.var.callmon_number.state }})
</span></span></span><span class="line"><span class="cl"><span class="sd">          hat am {{ states.var.callmon_date.state }} um {{ states.var.callmon_time.state }}h angerufen.</span></span></span></code></pre></div></div>
<p>Diese Automation sendet eine Nachricht mit dem Schema: <code>Horst (0174-123456) hat am 23.05.2020 um 23:42h angerufen.</code></p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Homeassistant 0.106: Neues Feature zum Dimmen von Lichtern</title>
      <link>https://jbetzen.net/posts/dimmen/</link>
      <pubDate>Thu, 27 Feb 2020 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/dimmen/</guid>
      <description>Mit dem Release 0.106 bietet Homeassistant eine neue granulare Möglichkeit zum Dimmen von Lichtern, die das Ein- und Ausschaltverhalten deutlich angenehmer gestaltet. Dieser Post beinhaltet ein Beispiel, wie dieses neue Feature benutzt werden kann.</description>
      <content:encoded><![CDATA[ <p>Homeassistant hat mit dem Release 0.106 ein praktisches <a href="https://www.home-assistant.io/blog/2020/02/26/release-106/#stepping-up-and-down-the-brightness-of-lights"  target="_blank" rel="noreferrer">neues Feature zum Dimmen von Lichtern</a> erhalten. Ursprünglich herrschte stets das Problem vor, dass ein Licht bereits eingeschaltet sein musste, um eine Dimm-Funktion zu automatisieren. Es musste über Templates der aktuelle Helligkeitswert ausgelesen und ein Wert x substrahiert oder addiert werden. Dies ist nun deutlich einfacher geworden und soll im Folgenden anhand eines Tradfri Dimmers aus dem Hause IKEA aufgezeigt werden.</p>

<h2 class="relative group">Beispiel: IKEA Dimmer mit Deconz und Conbee II
    <div id="beispiel-ikea-dimmer-mit-deconz-und-conbee-ii" class="anchor"></div>
    
</h2>
<p>Der Dimmer sendet die Codes <code>2002</code> und <code>3002</code> unter der ID <code>tradfri_dimmer.room_2</code> auf dem Event-Bus. Diese müssen durch eine Automation ausgelesen und anschließend die neue Dimmfunktion als Action zugewiesen werden. Zum Dimmen stehen zwei Methoden zur Auswahl:</p>
<ul>
<li><code>brightness_step</code>: Dieser Parameter akzeptiert Schrittwerte zwischen -255 und 255. Der Wert 255 steht in diesem Zusammenhang für eine Licht mit voller Leuchstärke.</li>
<li><code>brightness_step_pct</code>: Dieser Parameter gibt einen prozentualen Dimmwert als Stufe an. Es können werte zwischen <code>-100</code> und <code>100</code> zugewiesen werden. Der Wert 100 steht hier analog ebenfalls für die höchstmögliche Leuchtkraft einer Leuchte.</li>
</ul>

<h2 class="relative group">Beispielautomation
    <div id="beispielautomation" class="anchor"></div>
    
</h2>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">Tradfri Dimmer Room2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Tradfri Dimmer Room2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">event</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_type</span><span class="p">:</span><span class="w"> </span><span class="l">deconz_event</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">event_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">tradfri_dimmer_room2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">light.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">light.room2_decke</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">brightness_step_pct</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% if trigger.event.data.event == 2002 %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            +10
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% elif trigger.event.data.event == 3002 %}
</span></span></span><span class="line"><span class="cl"><span class="sd">            -10
</span></span></span><span class="line"><span class="cl"><span class="sd">          {% endif %}</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Homeassistant, Actionable Notifications und Appdaemon</title>
      <link>https://jbetzen.net/posts/notificaton-appdaemon/</link>
      <pubDate>Sun, 09 Feb 2020 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/notificaton-appdaemon/</guid>
      <description>Homeassistant bietet die Möglichkeit Push-Nachrichten an die Companion App zu senden und darüber direkt Actions auszulösen. Dieser Post beschreibt, wie man die Push-Nachrichten über Appdaemon ausliest und darüber Aktionen ausführt.</description>
      <content:encoded><![CDATA[ <p>Seit kurzem wird die Entwicklung der offiziellen Android App für Homeassistant eifrig voran getrieben. Seit dem Release 1.6 hat diese ein nützliches Feature hinzugewonnen, das sich <a href="https://companion.home-assistant.io/en/notifications/actionable"  target="_blank" rel="noreferrer">Actionable Notifications</a> nennt. Actionable Notifications ermöglichen es maximal 3 Aktionen an Push-Nachrichten zu binden und diese quasi wie ein Shortcut zu triggern:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/notificaton-appdaemon/notifications-appdaemon-example.jpg"
          alt="Beispiel Actionable Notification"
        />
  
  <figcaption>Beispiel Actionable Notification</figcaption>
  </figure>
<p>Wie man diese Notification aus Homeassistant heraus sendet und den gewählte Aktion in Appdaemon nutzen kann zeige ich in den folgenden Schritten. Voraussetzungen hierfür sind, dass die <a href="https://www.home-assistant.io/integrations/mobile_app/"  target="_blank" rel="noreferrer">Mobile App Integration</a> von Homeassistant in der <code>configuration.yaml</code> eingetragen, die offizielle Android App installiert und erfolgreich mit der Instanz verbunden und diese aus dem Internet erreichbar ist. Sind alle Voraussetzungen erfüllt, erstellt Homeassistant automatisch einen Service zum Senden von Push-Nachrichten. Dieser lautet <code>notify.mobile_app_TELEFONNAME</code>. Wer sich nicht sicher ist, wie der Dienst für sein Smartphone heißt, kann den genauen Namen unter <code>DEVELOPER TOOLS &gt; SERVICES</code> im Dropdown Menü nachschauen.</p>

<h2 class="relative group">Beispielscript für Notification
    <div id="beispielscript-für-notification" class="anchor"></div>
    
</h2>
<p>Als Beispiel wird ein Script erstellt, dass eine Pushnachricht mit drei Aktionen sendet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">test_notification</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.mobile_app_oneplus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Was möchtest du tun?&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Testnachricht&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">actions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span>- <span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;PN_LICHT&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Licht an&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span>- <span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;PN_STECKDOSE&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Steckdose an&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span>- <span class="nt">action</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;PN_SCENE&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Scene Hell&#39;</span></span></span></code></pre></div></div>
<p>Wichtig ist, dass jede angelegte Aktion einen eindeutigen Namen erhält, z.B. <code>PN_LICHT</code>. Nach dem anlegen des Script, müssen in Homeassitant die Scripte neu eingelesen werden oder ein Neustart von Hass erfolgen. Anschließend kann unter den <code>DEVELOPER TOOLS &gt; STATES</code> das Script gesucht werden. Klickt man auf das kleine rund umrandete <code>i</code> vor dem Script, öffnet sich ein Menü mit dem man es auslösen kann. Unmittelbar danach sollte eine Push-Nachricht auf dem Handy erscheinen.</p>

<h2 class="relative group">Appdaemon konfigurieren
    <div id="appdaemon-konfigurieren" class="anchor"></div>
    
</h2>
<p>Da Pushnotifications einen Haufen unterschiedlicher Services triggern können, habe ich mich entschlossen diese über Appdaemon ausführen zu lassen. Im Ordner <code>config/appdaemon/apps/</code> wird nun eine Datei mit dem Namen <code>pushactions.py</code> erzeugt und folgender Inhalt eingefügt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-py" data-lang="py"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">appdaemon.plugins.hass.hassapi</span> <span class="k">as</span> <span class="nn">hass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PushActions</span><span class="p">(</span><span class="n">hass</span><span class="o">.</span><span class="n">Hass</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">listen_event</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">handle_event</span><span class="p">,</span> <span class="s2">&#34;mobile_app_notification_action&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">handle_event</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event_name</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;action&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;PN_LICHT&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s2">&#34;light/turn_on&#34;</span><span class="p">,</span> <span class="n">entity_id</span> <span class="o">=</span> <span class="s2">&#34;light.decke&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;action&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;PN_STECKDOSE&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s2">&#34;switch/turn_on&#34;</span><span class="p">,</span> <span class="n">entity_id</span> <span class="o">=</span> <span class="s2">&#34;switch.lampe&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;action&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;PN_SCENE&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s2">&#34;scene/turn_on&#34;</span><span class="p">,</span> <span class="n">entity_id</span> <span class="o">=</span> <span class="s2">&#34;scene.hell&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Die Namen der Scenen, Switches und Lights sind exemplarisch und müssen selbstverständlich an die eigenen Belange angepasst werden.</p>
<p>Nun muss die neue App noch Appdaemon bekannt gemacht werden. Dazu wird die Datei <code>config/appdaemon/apps.yaml</code> aufgerufen und folgender Eintrag hinzugefügt:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">ActionableNotifications</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">module</span><span class="p">:</span><span class="w"> </span><span class="l">pushactions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">PushActions</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Beelink GT-King Pro und CoreELEC</title>
      <link>https://jbetzen.net/posts/gtkingprop-coreelec/</link>
      <pubDate>Sat, 01 Feb 2020 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/gtkingprop-coreelec/</guid>
      <description>Der Hersteller Beelink bietet mit dem GT-King Pro eine Streaming Box an, die sowohl mit Android als auch mit dem freie Media Center Kodi betrieben werden kann. Dieser Post beschreibt, wie man Kodi mittels dem CoreELEC Projekt installiert.</description>
      <content:encoded><![CDATA[ <p>Da meine treue alte <a href="https://www.solid-run.com/nxp-family/cubox-i/"  target="_blank" rel="noreferrer">Cubox i-4×4</a> für das aktuelle Kodi Leia keinen offiziellen Support mehr erhalten hat, habe ich mir einen neuen Media Player für das freie Mediacenter <a href="https://kodi.tv/"  target="_blank" rel="noreferrer">Kodi</a> zugelegt.</p>
<p>Es handelt sich dabei um die äußerlich eher weniger ansprechende Android TV Box GT-King Pro des Herstellers Beelink. Die TV-Box kann im Handumdrehen mittels <a href="https://coreelec.org"  target="_blank" rel="noreferrer">CoreELEC</a> mit Kodi versehen und anschließend im Dual Boot mit Android betrieben werden.</p>

<h2 class="relative group">Notwendige Schritte
    <div id="notwendige-schritte" class="anchor"></div>
    
</h2>
<ol>
<li>Das passende <a href="https://coreelec.org/#download"  target="_blank" rel="noreferrer">Image</a> von CoreELEC herunterladen</li>
<li>Das Image mittels <a href="https://rufus.ie/"  target="_blank" rel="noreferrer">Rufus</a>, <a href="https://www.balena.io/etcher/"  target="_blank" rel="noreferrer">Etcher</a> oder <code>dd</code> auf eine SD-Karte mit mindestens 8GB flashen.</li>
<li>Die geflashte Karte öffnen und im Ordner <code>/device_trees</code> das passende <code>.dtb-file</code> kopieren und eine Ebene höher im Root-Verzeichnis ablegen. Die Datei muss anschließend in <code>dtb.img</code> umbenannt werden.</li>
<li>Karte in den SD-Slot der TV-Box einsetzen und neustarten.</li>
</ol>
<p>Nun sollte die TV-Box in den Konfigurationsmodus von CoreELEC booten. Bleibt der Bildschirm hingegen schwarz, wurde mit hoher Wahrscheinlichkeit das falsche <code>.dtb-file</code> kopiert.</p>

<h2 class="relative group">Dual-Boot: Android vs. CoreELEC
    <div id="dual-boot-android-vs-coreelec" class="anchor"></div>
    
</h2>
<p>Ist die SD-Karte eingesteckt, bootet die Box stets in CoreElec. Wer also Android starten möchte, muss diese entfernen oder im Neustartmenü von CoreElec den Punkt <code>Reboot from eMMC/NAND</code> auswählen.</p>

<h2 class="relative group">Weiterführendes
    <div id="weiterführendes" class="anchor"></div>
    
</h2>
<p>Kodi lässt sich hervorragend in Homeassistant integrieren:</p>

  
      <ul>
        
          <li><a href="/posts/autopause-mediaplayer/">Automatisch pausierender Media Player</a></li>
        
      </ul>
  

 ]]></content:encoded>
    </item>
    
    <item>
      <title>Ikea Rollo mit dem Conbee 2 in Homeassistant nutzen</title>
      <link>https://jbetzen.net/posts/ikea-rollo/</link>
      <pubDate>Sat, 28 Dec 2019 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/ikea-rollo/</guid>
      <description>Ikea bietet im Rahmen des Tradfri Smarthome Sortiments ein neues Rollo an, dass über Zigbee gesteuert werden kann. Dieser Post beschreibt, wie das Rollo mittels Conbee II in Homeassistant integrieren und gesteuert werden kann.</description>
      <content:encoded><![CDATA[ <p>Seit einigen Monaten bietet Ikea zwei Modelle für Smarte Rollos an, die über ZigBee kommunizieren. Zur Auswahl stehen das lichtundurchlässige Modell <a href="https://www.ikea.com/de/de/p/fyrtur-verdunklungsrollo-kabellos-batteriebetrieben-grau-20408178/"  target="_blank" rel="noreferrer">Fyrtur</a> und das lediglich abdunkelnde <a href="https://www.ikea.com/de/de/p/kadrilj-rollo-kabellos-batteriebetrieben-grau-60408119/"  target="_blank" rel="noreferrer">Kadrilj</a>.
Im Lieferumfang der beiden Modelle sind ein Zigbee Repeater und eine Fernbedienung enthalten. Diese können mittels <a href="https://phoscon.de/de/conbee2"  target="_blank" rel="noreferrer">Conbee 2</a> in Homeassistant eingebunden werden.</p>
<p><strong>Tipp:</strong> Wer bei der Rollomontage keine Lust auf das Setzen von Bohrungen hat, kann die smarten Rollos mittels dem Montageset <a href="https://www.ikea.com/de/de/p/klamby-beschlag-f-rollo-jalousie-weiss-90149761/"  target="_blank" rel="noreferrer">Klamby</a> direkt am Fenster anbringen. Hierfür wird lediglich ein Kreuzschlitz-Schraubendreher benötigt.</p>

<h2 class="relative group">Pairing des Rollos mit Deconz
    <div id="pairing-des-rollos-mit-deconz" class="anchor"></div>
    
</h2>
<p>Das Rollo kann in der Deconz UI als Licht eingebunden werden und erscheint danach in Homeassistant als <a href="https://www.home-assistant.io/integrations/cover/"  target="_blank" rel="noreferrer">Cover</a> mit den entsprechenden Services.
Zum Pairen müssen beide Buttons auf dem Rollo gedrückt gehalten werden. Anschließend wird das Rollo in die Nähe des Conbee 2 geführt und und der Pairing-Prozess in der Deconz UI gestartet.</p>
<lite-youtube videoid="1b3j39tYcgc" playlabel="1b3j39tYcgc" params=""></lite-youtube>


<h2 class="relative group">Ausfahrlänge des Rollos einstellen
    <div id="ausfahrlänge-des-rollos-einstellen" class="anchor"></div>
    
</h2>
<p>Das Rollo kann mittels der zwei darauf befindlichen Buttons auf die gewünscht Ausfahrlänge eingestellt werden. Ist die gewünschte Länge erreicht, muss zweimal der untere Button gedrückt werden. Das Rollo merkt sich fortan die maximale Ausfahrlänge.</p>
<lite-youtube videoid="ru-gxsVjnjE" playlabel="ru-gxsVjnjE" params=""></lite-youtube>


<h2 class="relative group">Pairen der Fernbedienung mit Deconz
    <div id="pairen-der-fernbedienung-mit-deconz" class="anchor"></div>
    
</h2>
<p>Dieser Vorgang erschloss sich mir nicht gänzlich, wurde aber gemeistert. Die Fernbedienung muss dazu aufgeschraubt und die Batterie eingesetzt werden. Im Aufgeschraubten Zustand ist der Pairing Button erreichbar. Da alle Fernbedienung von Ikea im Lieferzustand bereits mit dem Rollo gepaired sind, muss die Fernbedienung zunächst auf Werkszustand gesetzt werden, damit sie in Deconz nutzbar ist. Dazu muss in der Deconz UI das Pairing für einen Sensor aktiviert werden. Anschließend muss der Button der Fernbedienung 4 mal innerhalb von 5 Sekunden gedrückt werden. Die eingebaute LED leuchtet dann pulsierend rot.
<lite-youtube videoid="UYz9ixxZz6U" playlabel="UYz9ixxZz6U" params=""></lite-youtube>
</p>

<h2 class="relative group">Fernbedienung mit ControllerX nutzen
    <div id="fernbedienung-mit-controllerx-nutzen" class="anchor"></div>
    
</h2>
<p>Über HACS lässt sich <a href="https://github.com/xaviml/controllerx"  target="_blank" rel="noreferrer">ControllerX</a> installieren. Es stellt ein wahrhaftiges Schweizer Taschenmesser für alle Zigbee-Geräte dar.</p>
<p>Der mitgelieferte Controller namens E1743 zum Steuern des Rollos hat einen <a href="https://xaviml.github.io/controllerx/controllers/E1743"  target="_blank" rel="noreferrer">eigenen Abschnitt</a> in der Dokumentation.</p>
<p>Eine ausführliche Anleitung zur Einrichtung und Verwendung von ControllerX ist ebenfalls auf diesem Blog vorhanden. Siehe: <a href="/posts/controllerx/">ControllerX und Home Assistant</a></p>

<h2 class="relative group">Template Sensor für Öffnungsgrad
    <div id="template-sensor-für-öffnungsgrad" class="anchor"></div>
    
</h2>
<p>Die aktuelle Position des Rollos kann mit einem Template Sensor ausgelesen werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sensors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">rollo_schlafzimmer_open_percent</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ states.cover.rollo_schlafzimmer.attributes.current_position }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Geöffnet&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;%&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon_template</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;-</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% if states(&#39;sensor.rollo_schlafzimmer_open_percent&#39;) | int &lt; 50 %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:blinds
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">          mdi:blinds-open
</span></span></span><span class="line"><span class="cl"><span class="sd">        {% endif %}</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>TalkiePi - Ein modernes Walkie Talkie</title>
      <link>https://jbetzen.net/posts/talkiepi/</link>
      <pubDate>Sun, 21 Apr 2019 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/talkiepi/</guid>
      <description>TalkiePi ist ein modernes Walkie Talkie geschrieben in der Programmiersprache Go. Es benutzt einen Mumble Server um Sprache in Echtzeit an beliebig viele weitere TalkiePis zu senden und eignet sich als Intercom in häuslichen Gefilden.</description>
      <content:encoded><![CDATA[ 
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Die Entwicklung des TalkiePi-Projekts wurde am 17.06.2020 eingestellt.</span>
</div>

<p>TalkiePi ist ein kleines, aber feines Bastelprojekt, erdacht von <a href="https://github.com/dchote/talkiepi"  target="_blank" rel="noreferrer">Daniel Chote</a> und anschließend weitergesponnen von <a href="https://github.com/CustomMachines/talkiepi"  target="_blank" rel="noreferrer">Ben Lewis</a>. Während das ursprünglich von Daniel entworfene Design noch händische Lötarbeit erforderte, ist das Projekt von Ben gänzlich mit <em>off-the-shelf</em> Komponenten realisierbar, die nur noch zusammengesteckt und in einem passenden Gehäuse aus dem 3D-Drucker untergebracht werden müssen.</p>

<h2 class="relative group">Was ist Talkiepi?
    <div id="was-ist-talkiepi" class="anchor"></div>
    
</h2>
<p>TalkiePi ist ein modernes Walkie Talkie, geschrieben in der Programmiersprache Go. Es benutzt einen Mumble Server um Sprache in Echtzeit an beliebig viele weitere TalkiePis zu senden. Durch Drücken und Halten des Knopfes an der Oberseite des Gehäuses kann eine Sprachnachricht abgesetzt werden. Drei LEDs geben Auskunft über die erfolgreiche Verbindung zum Mumble Server (rot), den Status weiterer verbundener TalkiePis (grün) und ob gerade aktiv Sprache gesendet wird (blau). Mit mehreren TalkiePis kann man beispielsweise lokal ein simples <em>Intercom</em> für große Häuser bauen oder entfernte Verwandte einfach vernetzen.</p>
<p>Die Projektbeschreibung auf Bens Github-Seite lautet:</p>
<blockquote><p>The talkiepi project is for a truly headless mumble client for the Raspberry Pi, utilizing static config and 


<abbr title="General Purpose Input Output">GPIO</abbr> for status LEDs and a button for push to talk.</p>
</blockquote>
<h2 class="relative group">Was wird benötigt
    <div id="was-wird-benötigt" class="anchor"></div>
    
</h2>
<ul>
<li><a href="https://shop.pimoroni.de/products/raspberry-pi-zero-wh-with-pre-soldered-header"  target="_blank" rel="noreferrer">Raspberry Pi Zero WH</a> + SD-Karte + Netzteil</li>
<li><a href="https://www.amazon.de/gp/product/B0738NLFTG/"  target="_blank" rel="noreferrer">Lautsprecher 3 Watt, 8 Ohm mit JST2.0-Anschluss</a></li>
<li><a href="https://shop.pimoroni.de/products/respeaker-2-mics-phat"  target="_blank" rel="noreferrer">Seeedstudio ReSpeaker 2-Mics pHAT</a></li>
<li>3D-Drucker für das <a href="https://github.com/CustomMachines/talkiepi/tree/master/stl/casemod_schneekluth"  target="_blank" rel="noreferrer">Gehäuse</a> oder ggf. einen 3D-Druck-Service</li>
<li>Senkschraube M2,5×12 zum Zusammenhalten der beiden Gehäuseteile</li>
<li><a href="https://de.wikipedia.org/wiki/Mumble"  target="_blank" rel="noreferrer">Mumble-Server</a>, bspw. günstig zu mieten bei <a href="https://www.lan4play.de/kommunikation/mumble-server/"  target="_blank" rel="noreferrer">lan4play</a> (5ct/Benutzer).</li>
</ul>

<h2 class="relative group">Installation
    <div id="installation" class="anchor"></div>
    
</h2>
<p>Als ich auf das Projekt gestoßen bin, war es nicht länger lauffähig. Ben und ich mussten ein wenig tüfteln um den Fehler zu identifizieren. Das Problem lag am neuesten Treiber für den ReSpeaker pHat, daher ist es wichtig die <a href="https://github.com/respeaker/seeed-voicecard/releases/tag/v3.0"  target="_blank" rel="noreferrer">Version 3.0 von SeeedStudios Installationsskripts</a> zu verwenden. Ben hat seine Anleitung zur Installation auf seiner Github-Seite aktualisiert. Sie ist <a href="https://github.com/CustomMachines/talkiepi/blob/master/doc/README.md"  target="_blank" rel="noreferrer">hier</a> zu finden.</p>

<h2 class="relative group">Gehäuse drucken
    <div id="gehäuse-drucken" class="anchor"></div>
    
</h2>
<p>Ursprünglich wurde das Gehäuse für TalkiePi von Paul van Gaans entworfen. Er hat ein anschauliches Video zum Designprozess veröffentlicht, dass zugleich auch die Funktionsweise von TalkiePi demonstriert:</p>
<lite-youtube videoid="tzYGLe2aL6I" playlabel="tzYGLe2aL6I" params=""></lite-youtube>

<p>Problematisch jedoch war, dass das Gehäuse nach dem Druck auf meinem Anycubic i3 Mega falsch skaliert und schlichtweg zu klein gedruckt wurde. Die Komponenten hatten nicht ausreichend Platz. Ich habe daraufhin mit Paul Kontakt aufgenommen und er hat mir die Modelldatei des Gehäuses zukommen lassen. Zu meinem Schreck war diese jedoch ein Flächenmodell, dass mit <a href="https://www.blender.org/"  target="_blank" rel="noreferrer">Blender</a> erstellt wurde. Da ich selber nur parametrisches CAD beherrsche, hat mir Paul freundlicherweise ein Video erstellt, das die Basisfunktionen von Blender für CAD-User vorstellt. Wer von berufswegen mit Autodesk Inventor, Solidworks, Catia, usw. arbeitet und die ersten Schritte in die Flächenmodellierung wagen möchte, dem empfehle ich auch dieses Video von Paul:</p>
<lite-youtube videoid="gf38wDM0oHE" playlabel="gf38wDM0oHE" params=""></lite-youtube>

<p>Meine angepasste Version des Gehäuses wurde anschließend von Ben übernommen und ist ebenfalls bei <a href="https://github.com/CustomMachines/talkiepi/tree/master/stl/casemod_schneekluth"  target="_blank" rel="noreferrer">Github</a> zu finden.</p>
<p>Beim <em>Slicen</em> mit <a href="https://ultimaker.com/en/products/ultimaker-cura-software"  target="_blank" rel="noreferrer">Cura</a> ist darauf zu achten, dass beim Druck des oberen Teils des Gehäuses die Funktion <code>Keep disconnected Faces</code> aktiviert wird, da sonst der Knopf zur Spracheingabe als Vollfläche gedruckt wird und somit nicht gedrückt werden kann.
Als Filament würde ich ein einen dunklen Farbton (schwarz/grau) empfehlen, da die LEDs sehr stark leuchten und bei meinem Druck mit weißem PLA die Statusfarben nicht immer eindeutig zu erkennen sind.</p>

<h2 class="relative group">Wandhalterung
    <div id="wandhalterung" class="anchor"></div>
    
</h2>


  
  <div class="width-patch"></div>
<div id="gallery-47a8a42e3512f1fef7f5bf00357eca65" class="gallery">
  
  <img src="/posts/talkiepi/talkiepi-halterung.jpg" class="grid-w50" />
  <img src="/posts/talkiepi/talkiepi-halterung-kombiniert.jpg" class="grid-w50" />
</div>

<p>Ich habe zusätzlich noch zwei passende Wandhalterung für das TalkiePi entworfen. Somit kann es schnell aus der Halterung genommen werden, wenn man es zum Sprechen in die Hand nehmen möchte. Es ist eine Variante zum <a href="https://github.com/CustomMachines/talkiepi/blob/master/stl/casemod_schneekluth/wallmount/Screwed_Edition/talkiepi_wallmount_screw.stl"  target="_blank" rel="noreferrer">Verschrauben</a> (2 Schrauben) und eine zum <a href="https://github.com/CustomMachines/talkiepi/blob/master/stl/casemod_schneekluth/wallmount/Taped_Edition/talkiepi_wallmount_tape.stl"  target="_blank" rel="noreferrer">Aufkleben</a> mit Klett oder doppelseitigem Klebeband vorhanden. Somit kann man TalkiePi gut an einen Ort im Haus integrieren.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Feinstaubsensor selber bauen</title>
      <link>https://jbetzen.net/posts/feinstaubsensor/</link>
      <pubDate>Fri, 21 Dec 2018 00:08:25 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/feinstaubsensor/</guid>
      <description>Dieser Post handelt davon, wie man einen Feinstaubsensor des luftdaten.info Projekts auf Basis des Microcontrollers ESP8266 und einem SDS011 Sensors bastelt. Die gewonnenen Daten können über eine Custom Integration in Homeassistant eingebunden werden.</description>
      <content:encoded><![CDATA[ <p>Vor Kurzem bin ich über die Webseite <a href="https://luftdaten.info/"  target="_blank" rel="noreferrer">luftdaten.info</a> gestolpert auf der es eine einfache Bauanleitung für einen Feinstaubsensor gibt. Ich habe kurzerhand alle Teile über AliExpress bestellt und einen solchen Sensor zusammengebastelt. Als Gehäuse habe ich jedoch nicht auf die Marley-Silent-Bögen zurückgegriffen, sondern den 3D-Drucker angeworfen und <a href="https://www.thingiverse.com/thing:2503257"  target="_blank" rel="noreferrer">dieses Gehäuse</a> gedruckt.
Das faszinierende am Luftdatenprojekt ist, dass diese Sensoren mit wenigen Handgriffen Teil eines globalen Netzwerks hunderter anderer Sensoren werden können. Die so aggregierten Daten können dann grafisch sehr anschaulich auf einer <a href="http://deutschland.maps.sensor.community/#6/51.165/10.455"  target="_blank" rel="noreferrer">Karte</a> dargestellt werden.</p>

<h2 class="relative group">Teileliste
    <div id="teileliste" class="anchor"></div>
    
</h2>
<p>Die benötigten Hardware-Komponenten können für ca. 25€ erworben werden:</p>
<div style="display:table; margin:auto">
<table>
  <thead>
      <tr>
          <th>Bauteil</th>
          <th>Bauteilkategorie</th>
          <th style="text-align: right">Kosten</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://de.aliexpress.com/w/wholesale-esp8266.html"  target="_blank" rel="noreferrer">ESP8266</a></td>
          <td>Mikrokontroller</td>
          <td style="text-align: right">3 €</td>
      </tr>
      <tr>
          <td><a href="https://de.aliexpress.com/w/wholesale-sds011.html"  target="_blank" rel="noreferrer">Feinstaubsensor SDS011</a></td>
          <td>Sensor</td>
          <td style="text-align: right">20 €</td>
      </tr>
      <tr>
          <td><a href="https://de.aliexpress.com/w/wholesale-dht22-sensor.html"  target="_blank" rel="noreferrer">DHT22 Sensor</a></td>
          <td>Sensor</td>
          <td style="text-align: right">1 €</td>
      </tr>
  </tbody>
</table>
</div>
<p>Der DHT22 Sensor, kann auch durch einen DHT11 ersetzt werden. Dieser liefert Temperatur- und Luftfeuchtigkeitsangaben jedoch mit schlechterer Genauigkeit. Wer es genauer mag, sollte auf einen DHT22 zurückgreifen.</p>

<h2 class="relative group">Feinstaubwerte
    <div id="feinstaubwerte" class="anchor"></div>
    
</h2>
<p>Die gelieferten Feinstaubwerte beschränken sich auf die Werte für <strong>PM10</strong> und <strong>PM2.5</strong>. Diese sind <a href="https://www.umweltbundesamt.de/themen/luft/luftschadstoffe-im-ueberblick/feinstaub"  target="_blank" rel="noreferrer">laut Umweltbundesamt</a> wie folgt definiert:</p>
<blockquote><p><em>PM10 kann beim Menschen in die Nasenhöhle, PM2,5 bis in die Bronchien und Lungenbläschen und ultrafeine Partikel bis in das Lungengewebe und sogar in den Blutkreislauf eindringen. Je nach Größe und  Eindringtiefe der Teilchen sind die gesundheitlichen Wirkungen von Feinstaub verschieden. Sie reichen von Schleimhautreizungen und lokalen Entzündungen in der Luftröhre und den Bronchien oder den Lungenalveolen bis zu verstärkter Plaquebildung in den Blutgefäßen, einer erhöhten Thromboseneigung oder Veränderungen der Regulierungsfunktion des vegetativen Nervensystems (Herzfrequenzvariabilität).</em></p>
</blockquote><p>Zu den geltenden <strong>Grenzwerten</strong> sind auf der Seite des Umweltbundesamtes folgende Aussagen zu finden:</p>
<blockquote><p><em>Zum Schutz der menschlichen Gesundheit gelten seit dem 1. Januar 2005 europaweit Grenzwerte für die Feinstaubfraktion PM10. Der Tagesgrenzwert beträgt 50 µg/m3 und darf nicht öfter als 35mal im Jahr überschritten werden. Der zulässige Jahresmittelwert beträgt 40 µg/m3. Für die noch kleineren Partikel PM2,5 gilt seit 2008 europaweit ein Zielwert von 25 µg/m3 im Jahresmittel, der bereits seit dem 1. Januar 2010 eingehalten werden soll. Seit 1. Januar 2015 ist dieser Wert verbindlich einzuhalten.</em></p>
</blockquote>
<h2 class="relative group">Integration in Homeassistant
    <div id="integration-in-homeassistant" class="anchor"></div>
    
</h2>
<p>Für die Integration der Sensorwerte steht für Homeassistant eine <em>Custom Component</em> namens <a href="https://github.com/lichtteil/local_luftdaten"  target="_blank" rel="noreferrer">Local Luftdaten</a> zur Verfügung. Diese kann über HACS installiert und muss anschließend konfiguriert werden.</p>

<h3 class="relative group">Konfiguration der Custom Component
    <div id="konfiguration-der-custom-component" class="anchor"></div>
    
</h3>
<p>Die Custom Component verlangt eine IP-Adresse des Feinstaubsensors, ein Scan Interval zur Häufigkeit der Datenabfrage und eine Liste der der Werte, die Ausgelesen werden sollen. Die IP-Adresse sollte statisch vergeben werden, damit sie sich nicht ändert:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">sensor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">local_luftdaten</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="m">192.168.0.123</span><span class="w">         </span><span class="c"># IP Adresse hier eintragen</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">scan_interval</span><span class="p">:</span><span class="w"> </span><span class="m">180</span><span class="w">          </span><span class="c"># Scan alle 3 Minuten</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Feinstaubsensor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">monitored_conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">SDS_P1                 </span><span class="w"> </span><span class="c"># Wert für PM10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">SDS_P2                 </span><span class="w"> </span><span class="c"># Wert für PM2.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">temperature</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">humidity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">signal                 </span><span class="w"> </span><span class="c"># Wert für WiFi-Signalstärke</span></span></span></code></pre></div></div>

<h3 class="relative group">Lovelace UI
    <div id="lovelace-ui" class="anchor"></div>
    
</h3>
<p>Zur Integration in die Lovelace UI empfehle ich die vielseitige <a href="https://github.com/kalkih/mini-graph-card"  target="_blank" rel="noreferrer">Mini-Graph-Card</a>. Im Verbund mit einem Vertical-Stack kann folgendes dargestellt werden:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/feinstaubsensor/feinstaubsensor-lovelacecard.png"
          alt="Lovelace Card zur Anzeige der Feinstaubwerte"
        />
  
  
  </figure>
<p>Der Code zur Darstellung der Lovelace Card:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">vertical-stack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cards</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">horizontal-stack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cards</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">custom:mini-graph-card</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">animate</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">color_thresholds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#f08080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">50</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#ff9966&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">40</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.feinstaubsensor_pm10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">index</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">hours_to_show</span><span class="p">:</span><span class="w"> </span><span class="m">6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">PM10 | ◷ 6h</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">points_per_hour</span><span class="p">:</span><span class="w"> </span><span class="m">6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">show</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">fill</span><span class="p">:</span><span class="w"> </span><span class="l">fade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">graph</span><span class="p">:</span><span class="w"> </span><span class="l">bar</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">custom:mini-graph-card</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">animate</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">color_thresholds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#f08080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">25</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#ff9966&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">15</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.feinstaubsensor_pm2_5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">index</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">hours_to_show</span><span class="p">:</span><span class="w"> </span><span class="m">6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">PM2.5 | ◷ 6h</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">points_per_hour</span><span class="p">:</span><span class="w"> </span><span class="m">6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">show</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">fill</span><span class="p">:</span><span class="w"> </span><span class="l">fade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">graph</span><span class="p">:</span><span class="w"> </span><span class="l">bar</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">horizontal-stack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cards</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">custom:mini-graph-card</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">animate</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">color_thresholds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#f08080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">23</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#90ee90&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">20</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#00bfff&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.feinstaubsensor_temperature</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">index</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">hours_to_show</span><span class="p">:</span><span class="w"> </span><span class="m">24</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Temperatur | ◷24h</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">points_per_hour</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">show</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">custom:mini-graph-card</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">animate</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">color_thresholds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#00ffff&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#00bfff&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">40</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">color</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#00ffff&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.feinstaubsensor_humidity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">index</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">hours_to_show</span><span class="p">:</span><span class="w"> </span><span class="m">24</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Luftfeuchte | ◷24h</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">points_per_hour</span><span class="p">:</span><span class="w"> </span><span class="m">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">show</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">fill</span><span class="p">:</span><span class="w"> </span><span class="l">fade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Matomo Besucherstatistik in Homeassistant einbinden</title>
      <link>https://jbetzen.net/posts/matomo/</link>
      <pubDate>Tue, 18 Dec 2018 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/matomo/</guid>
      <description>Matomo ist eine datenschutzfreundliche Software zum Tracken von Besuchern auf Webseiten. Die ermittelten Metriken können über Matomos API in Homeassistant ausgelesen, dargestellt und per TTS vorgelesen werden.</description>
      <content:encoded><![CDATA[ <p>Wer Matomo als Analytics-Tool zur Auswertung der Besucherstatistiken auf seinem Blog oder Webseite nutzt, kann diese Daten über die <a href="https://developer.matomo.org/api-reference/reporting-api"  target="_blank" rel="noreferrer">Matomo API</a>, mittels eines <a href="https://www.home-assistant.io/components/sensor.rest/"  target="_blank" rel="noreferrer">REST-Sensors</a>, in Homeassistant integrieren. Im Folgenden zeige ich wie man die täglichen, wöchentlichen und monatlichen Besucherstatistiken (Unique Visitors) in Homeassistant darstellen kann. Dazu muss wie folgt vorgegangen werden:</p>

<h2 class="relative group">Auth-Token im Matomo Dashboard suchen
    <div id="auth-token-im-matomo-dashboard-suchen" class="anchor"></div>
    
</h2>
<p>Als erster Schritt in das Dashboard von Matomo gewechselt werden. Dort findet man in den Einstellungen unter der Sektion Platform den Menüpunkt <code>API</code>. Hier präsentiert Matomo den benötigten Token der wie folgt aussieht:
<code>&amp;token_auth=c7a98***************************</code></p>
<p>Dieser muss notiert werden, da er für den REST-Sensor in Homeassistant benötigt wird.</p>

<h2 class="relative group">REST-Sensor in Homeassistant erstellen
    <div id="rest-sensor-in-homeassistant-erstellen" class="anchor"></div>
    
</h2>
<p>In Homeassistant müssen folgende drei Sensoren angelegt werden. Der Platzhalter <code>URL_MATOMO</code> muss durch die entsprechende URL ersetzt werden unter der Matomo erreichbar ist. Zu beachten ist der Eintrag <code>idSite</code> in der URL, wenn mehrere Blogs mittels Matomo getrackt werden. An dieser Stelle muss dann die gewünschte <code>ID</code> eingetragen werden.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">rest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resource</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://URL_MATOMO/?module=API&amp;method=VisitsSummary.getUniqueVisitors&amp;idSite=1&amp;period=day&amp;date=today&amp;format=JSON&amp;token_auth=c7a98***************************&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Blog Visitors Today</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ value_json.value | int }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Visitors&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">rest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resource</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://URL_MATOMO/?module=API&amp;method=VisitsSummary.getUniqueVisitors&amp;idSite=1&amp;period=week&amp;date=today&amp;format=JSON&amp;token_authc7a98***************************4&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Blog Visitors Week</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ value_json.value | int }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Visitors&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">rest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">resource</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://URL_MATOMO/?module=API&amp;method=VisitsSummary.getUniqueVisitors&amp;idSite=1&amp;period=month&amp;date=today&amp;format=JSON&amp;token_auth=c7a98***************************&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Blog Visitors Month</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;{{ value_json.value | int }}&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Visitors&#34;</span></span></span></code></pre></div></div>

<h2 class="relative group">Lovelace Card
    <div id="lovelace-card" class="anchor"></div>
    
</h2>
<p>Zu aller Letzt wird jetzt noch eine Card in der Lovelace-UI erstellt:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/matomo/matomo-card.png"
          alt="Lovelace Card mit Matomo-Statistiken"
        />
  
  
  </figure>
<p>Der zugehörige Code lautet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">glance</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Triumvirat.org | Statistik</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entities</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.blog_visitors_today</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;mdi:calendar-today&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Today</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.blog_visitors_week</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Week</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;mdi:calendar-week&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.blog_visitors_month</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Month</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">&#39;mdi:calendar-week-begin</span></span></span></code></pre></div></div>

<h2 class="relative group">Gesprochenene Blogstatistik mittels TTS-Service
    <div id="gesprochenene-blogstatistik-mittels-tts-service" class="anchor"></div>
    
</h2>
<p>Wer die Blogstatistik gerne mittels 


<abbr title="Text to Speech">TTS</abbr> ausgeben lassen möchte, kann dies mittels eines Scripts bewerkstelligen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tts_blogstatistik</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Blogstatistik</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">notify.alexa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">        &#39;Heute haben {{ states.sensor.blog_visitors_today.state }} Menschen den Blog gelesen. Die gesamte Woche waren es {{ states.sensor.blog_visitors_week.state }} und im gesamten Monat {{ states.sensor.blog_visitors_month.state }} Besucher.&#39;</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Threema Safe mit Nextcloud nutzen</title>
      <link>https://jbetzen.net/posts/threema-safe-nextcloud/</link>
      <pubDate>Mon, 17 Dec 2018 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/threema-safe-nextcloud/</guid>
      <description></description>
      <content:encoded><![CDATA[ <p>Seit Threema 3.6 ist es möglich ein automatisches Backup der ID, Kontakte und deren Vertrauensstufen erstellen zu lassen. Die Funktion trägt den Namen <a href="https://threema.ch/de/blog/posts/threema-safe-de"  target="_blank" rel="noreferrer">Threema Safe</a> und kann die Backups auf Threema-eigenen Servern, aber auch auf einem selbstgehosteten Server speichern, der per WebDAV erreichbar ist. Nextcloud bietet diese Funktion und ist daher ein idealer Ablageort für die periodischen Backups. Und so geht es:</p>

<h2 class="relative group">Ordnerstruktur in der Nextcloud anglegen
    <div id="ordnerstruktur-in-der-nextcloud-anglegen" class="anchor"></div>
    
</h2>
<p>Threema Safe erwartet für den reibungslosen Betrieb eine vorgegebene Ordnerstruktur in der Nextcloud. Diese muss händisch angelegt werden. Die Benennung des Hauptordners steht jedoch frei. Exemplarisch wird hier der Ordner mit dem Namen <code>Safe</code> angelegt. Im Ordner <code>Safe</code> muss nun ein weiterer Ordner mit dem Namen <code>backups</code> angelegt werden. Zusätzlich wird noch eine weitere Datei benötigt. Dazu eine Textdatei mit dem Namen config (ohne Dateiendung!) anlegen, diese öffnen und folgende Zeilen einfügen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">   &#34;maxBackupBytes&#34;: 524288,
</span></span><span class="line"><span class="cl">   &#34;retentionDays&#34;: 180
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div>
<p>Wenn alle Dateien und Ordner erstellt sind, sollte die Ordnerstruktur in der Nextcloud so aussehen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">Nextcloud/
</span></span><span class="line"><span class="cl">    └── Safe/
</span></span><span class="line"><span class="cl">        ├── backups/
</span></span><span class="line"><span class="cl">        └── config</span></span></code></pre></div></div>

<h2 class="relative group">Threema Safe mit Nextcloud verbinden
    <div id="threema-safe-mit-nextcloud-verbinden" class="anchor"></div>
    
</h2>
<p>In der Threema App findet man durch Antippen des Optionsmenüs den Punkt <strong>Meine Backups</strong>. Hier muss der Threema Safe aktiviert werden. Nach einem Klick auf die Experteneinstellung öffnet sich ein neues Menü in dem nun die URL zur Nextcloud, sowie die notwendigen Authentifizierungsdaten, eingegeben werden können:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://nextcloudurl/nextcloud/remote.php/dav/Safe</span></span></code></pre></div></div>
<p>Anschließend kann in der Threema App geprüft werden, ob der externe Backup-Pfad erfolgreich benutzt werden kann.</p>
<p>Es gilt zu beachten, dass Threema Safe lediglich die eigene ID, Profilbild Kontakte und Kontaktbilder, Chatgruppen, etc. sichert, nicht jedoch die eigentlichen Nachrichten. Diese sollten weiterhin als Datenbackup regelmäßig gesichert werden.</p>

<h2 class="relative group">Leserrückmeldung
    <div id="leserrückmeldung" class="anchor"></div>
    
</h2>
<p>Auf Twitter gab es zu diesem Post die Anmerkung, dass ggf. das Anlegen eines eigenen Nextcloud-Benutzers für das Threema-Backup sinnvoll wäre. Das liegt natürlich im eigenen Ermessen des Nutzers, soll hier aber trotzdem seine Erwähnung finden:</p>
<blockquote class="twitter-tweet" data-dnt="true"><p lang="de" dir="ltr">Ich würde empfehlen lieber ein einzelnes Passwort für <a href="https://twitter.com/hashtag/Threema?src=hash&amp;ref_src=twsrc%5Etfw">#Threema</a> verwenden, sodass es nicht Zugriff auf alles hat. Siehe meine Anleitung hier: <a href="https://t.co/TdbkTI97K5">https://t.co/TdbkTI97K5</a></p>&mdash; rugk (@rugkme) <a href="https://twitter.com/rugkme/status/1074782777504133121?ref_src=twsrc%5Etfw">December 17, 2018</a></blockquote>


 ]]></content:encoded>
    </item>
    
    <item>
      <title>Vietnam: Ein Reiseführer für Anfänger</title>
      <link>https://jbetzen.net/posts/life-002-vietnam-reisefuehrer/</link>
      <pubDate>Mon, 03 Dec 2018 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/life-002-vietnam-reisefuehrer/</guid>
      <description>Ein Reiseführer für Anfänger, die Vietnam besuchen und erleben wollen. Da die Vorbereitungen und Ratschläge auf Webseiten nicht immer hilfreich und zielführend sind, habe ich meine persönlichen Erfahrungen und Tips zusammengeschrieben.</description>
      <content:encoded><![CDATA[ <p>Wie bereits in <a href="../berufliche-auszeit" >dem vorangegangenen Beitrag</a> beschrieben, habe ich mir eine berufliche Auszeit gegönnt und Vietnam bereist. Ausschlaggebend war für mich ein Land zu durchqueren, das nicht von westlicher Kultur geprägt ist. Startpunkt war <a href="https://de.wikipedia.org/wiki/Hanoi"  target="_blank" rel="noreferrer">Hanoi</a>. Von dort aus ging es in den Nordosten in die <a href="https://de.wikipedia.org/wiki/Halong-Bucht"  target="_blank" rel="noreferrer">Ha Long Bucht</a> und anschließend in den Nordwesten nach <a href="https://de.wikipedia.org/wiki/Sa_Pa"  target="_blank" rel="noreferrer">Sa Pa</a>. Die restliche Route führte mich durchs gesamte Land gen Süden und endete in <a href="https://de.wikipedia.org/wiki/Ho-Chi-Minh-Stadt"  target="_blank" rel="noreferrer">Ho-Chi-Minh-Stadt</a>. Die genaue Route sah so aus:</p>

<figure>
        <img
          class="my-0 rounded-md centered"
          src="/posts/life-002-vietnam-reisefuehrer/vietnam-thailand-route.png"
          alt="Reiseroute als Karte für Vietnam und Thailand"
        />
  
  
  </figure>
<p>Da ich sehr blauäugig an das gesamte Unterfangen herangegangen bin, möchte ich ein paar Erfahrungen teilen und den einen oder anderen Tipp geben, falls ein Leser ebenfalls dieses wunderbare Land bereisen möchte. Ich habe vor Reiseantritt viel über Vietnam und die nötigen Vorbereitungen und Anschaffungen gelesen, aber das erschien mir schnell als ermüdendes Unterfangen. Man findet <em>zu Hauf</em> Seiten, die allerlei Tand empfehlen und natürlich auch brav ihre Affiliate-Links gesetzt haben, über deren Zweckmäßigkeit gestritten werden kann.</p>
<p>Bleiben wir vorerst bei den unerlässlichen Dingen, die zwingend notwendig sind.</p>

<h2 class="relative group">Kreditkarte
    <div id="kreditkarte" class="anchor"></div>
    
</h2>

  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Die Infos sind veraltet. Die Santander Bank hat die 1plus Kreditkarte abgewertet und ein kostenloses Abheben, sowie eine Rückerstattung der Abhebegebühren sind im Angebot nicht länger enthalten.</span>
</div>

<p>Eine Kreditkarte im Ausland sollte ein Jeder zur Hand haben. <del>Ich habe mir die 1plus Visa-Card der Santander Bank angeschafft. Sie ist praktisch, da sie wie eine Prepaid-Karte aufgeladen werden kann. Das größte Schmankerl ist jedoch, dass sämtliche Gebühren für das Abheben von Bargeld im Ausland erstattet werden. Das klappt auch aus eigener Erfahrung wunderbar. Man muss lediglich die Quittungen der Geldautomaten aufbewahren, einscannen und an die Bank schicken und man erhält binnen kürzester Zeit die Transaktionskosten gutgeschrieben.</del></p>
<p>Die Kreditkarte ist zudem zwingend notwendig zur Buchung von Hostels und Hotels und auch zur Personenbeförderung mittels nützlicher Apps wie Grab. Die Abhebegebühren in Vietnam schwanken zwischen 20.000 - 70.000 Dong (0,75-2,70€).</p>

<h2 class="relative group">Rucksack
    <div id="rucksack" class="anchor"></div>
    
</h2>
<p>Hier scheiden sich die Geister, was das nötige Volumen angeht. Ich habe einen <a href="https://www.amazon.de/Deuter-Aircontact-Trekking-Rucksack-Black-Titan/dp/B01CBOADW2"  target="_blank" rel="noreferrer">Deuter Aircontact 55+10</a> genutzt und war rundum zufrieden. Der Rucksack ist bei einer Körpergröße von 1,84m angenehm zu tragen und kann flexibel an die Physiognomie angepasst werden. Das Fassungsvermögen beträgt 55 Liter, die mittels variablem Zurrkragen um weitere 10 Liter ergänzt werden können. Es war genügend Platz für Souvenirs vorhanden und auch mein maßgeschneiderter Anzug aus Hoi An fand noch ausreichend Platz darin. Eine klare Empfehlung: Ein Rucksack sollte nicht blind bestellt, sondern im entsprechenden Kaufhaus anprobiert und auf Tragekomfort getestet werden.</p>

<h2 class="relative group">Schuhwerk
    <div id="schuhwerk" class="anchor"></div>
    
</h2>
<p>Gute Schuhe sind das <em>A und O</em> bei einer Reise. Das Schuhwerk hängt maßgeblich von der Reiseroute ab. Bereist man abgelegenere Regionen, wie Sa Pa, steht man schnell knöcheltief im Schlamm und wäre mit soliden Wanderschuhen gut beraten. Wanderschuhe sind aber auch teuer. Ich hatte lediglich ein paar klassische Adidas Sneaker und ein paar Flip-Flops dabei und bin damit erstaunlich gut zurecht gekommen. Wichtig ist, dass die Füße regelmäßig Luft kriegen, da man in den Schuhen schwitzt wie ein Schwein. Ich bin mit Sneakers auf den höchsten Punkt im Cat Ba Nationalpark geklettert und auch die abgelegensten Wanderwege in Sa Pa gelaufen. Kurzum, es ging! Rückwirkend hätte ich mir anstelle der Flip-Flops aber lieber gute Sandalen gewünscht, da ich das Tragegefühl von Flip-Flops über längere Zeit als unangenehm empfinde.</p>

<h2 class="relative group">Kleidung
    <div id="kleidung" class="anchor"></div>
    
</h2>
<p>Sämtliche Reiseblogs empfehlen teure Funktionskleidung. Diese trocknet zwar schnell frisch geronnenen Schweiß, stinkt dann aber auch wie Hulle. Ich hatte meine normalen Baumwoll-Shirts im Gepäck, eine lange Hose und einen Pullover. Schwitzen tut man bei den lokalen Temperaturen so oder so. Wenn man dabei noch stinken will, kann man auf Funktionskleidung zurückgreifen. Generell empfehle ich als Daumenregel nicht mehr Kleidung als für 7 Tage mitzunehmen. Das ist unnötiger Ballast und viele Hostels haben ein Waschmaschine. Einen Wäscheservice für 2-3€ (30.000VND pro Kg)  gibt es mehrfach in jeder Straße in Vietnam. Öfter waschen, weniger schleppen! Was ich aber ganz klar empfehlen muss, ist eine Kopfbedeckung. Man kann sich vor Ort einen traditionellen Strohhut für ca. 1€ kaufen oder nimmt sich ein Cap mit. Die Sonne Vietnams sollte nicht unterschätzt werden.</p>

<h2 class="relative group">Regenkleidung
    <div id="regenkleidung" class="anchor"></div>
    
</h2>
<p>Vietnam hat wechselnde Monsunzeiten im Norden und im Süden. Wer das Land einmal komplett durchquert, wird notgedrungen mal in einen Schauer geraten. Diese sind meist heftig, aber kurz und treten morgens, mittags und abends auf. Regenkleidung bekommt man in Vietnam sehr günstig. Northface produziert bspw. in dem Land und man bekommt gute B-Ware und allerlei Fakes zu günstigen Preisen. Ich habe mir eine gefakete Northface Jacke für ca. 8€ gekauft, die sich erstaunlich gut bewährt hat.</p>

<h2 class="relative group">Impfungen
    <div id="impfungen" class="anchor"></div>
    
</h2>
<p>Ich habe ein halbes Jahr vor Reiseantritt mit meiner Hausärztin einen Impfplan ausgearbeitet. Geimpft wurde ich gegen:</p>
<ul>
<li>Tollwut</li>
<li>Hepatitis A+B</li>
<li>Japanische Enzephalitis</li>
</ul>
<p><strong>Kostenpunkt:</strong> Sagenhafte 400€! Daher unbedingt rechtzeitig mit der Krankenkasse aushandeln, ob diese einen Teilbetrag übernimmt. Die Zuschüsse der Kassen schwanken immens. Einige übernehmen den kompletten Betrag. Bei mir waren es lediglich 100€. Den Rest musste ich selber stemmen. Beim Aushandeln des Impfplans sollte gleichzeitig die Gelegenheit genutzt werden die Ärztin nach Empfehlungen für eine Reiseapotheke zu fragen.</p>

<h2 class="relative group">Medikamente
    <div id="medikamente" class="anchor"></div>
    
</h2>
<p>Auf Anraten meiner Hausärztin hatte ich folgende Arznei im Gepäck:</p>
<ul>
<li>Elektrolyte gegen Durchfall</li>
<li><a href="https://de.wikipedia.org/wiki/Atovaquon_und_Proguanil"  target="_blank" rel="noreferrer">Malerone</a> (gegen Malariaerscheinungen)</li>
<li>Pflaster</li>
<li>Breitbandantibiotikum (verschreibungspflichtig)</li>
</ul>
<p>Gebraucht habe ich davon nur die Pflaster. Das Thema Durchfall sollte bei zartbesaiteten Mägen sicherlich nicht außer Acht gelassen werden. Meinem Schweinemagen konnte aber so schnell nichts anhaben, sei es nun Faustgroße Bergschnecke in Limonengrassud oder frittiertes Schweinezahnfleisch. Probleme hatte ich damit nie.</p>

<h2 class="relative group">Medizinische Versorgung vor Ort
    <div id="medizinische-versorgung-vor-ort" class="anchor"></div>
    
</h2>
<p>Generell muss sich vor Augen gehalten werden, dass es in Vietnam eine große Errungenschaft nicht gibt - eine gesetzliche Krankenversicherung. Dementsprechend ist ein Arzt- oder Krankenhausbesuch auch relativ teuer. Anstelle der Ärzte übernehmen hier die Apotheken gerne die Diagnosen und geben einem direkt das passende Medikament zu günstigem Preis mit. Ich hatte nach einer Wanderung einmal einen handgroßen roten Ausschlag am Bein. Für 3€ habe ich in der Apotheke dann ein Breitbandmedikament gegen Hautausschläge bekommen und dann war auch alles wieder gut. In solchen Fällen sollte dann einfach direkt die Apotheke aufgesucht werden.</p>

<h2 class="relative group">Mobiles Internet
    <div id="mobiles-internet" class="anchor"></div>
    
</h2>
<p>SIM-Karten mit Datenvolumen gibt es an jeder Ecke in Vietnam. Man bekommt für ca. 6€ einen Berg an Datenvolumen (18GB). Generell bietet jedes Café und jedes Hostel kostenfreies W-Lan an. Die Netzabdeckung im gesamten Land ist unglaublich gut und Konnektivität ist jederzeit gewährleistet. Mancherorts sind bestimmte Webseiten aufgrund der staatlichen Internetzensur nicht erreichbar, daher sollte ggf. ein VPN vor der Reise eingerichtet werden. Nützliche Apps für den Alltag in Vietnam habe ich in einem <a href="../vietnam-apps" >gesonderten Beitrag</a> aufgelistet.</p>





  
  
    
  
  




<h2 class="relative group">Sprachbarriere
    <div id="sprachbarriere" class="anchor"></div>
    
</h2>
<p>Wer Englisch kann, ist klar im Vorteil. Mit Englisch kommt man relativ gut durch das Land und kann sich rudimentär verständigen. Witzigerweise sind es meist die Kinder, die die Sprache besser beherrschen und dann für die Erwachsenen übersetzen. Der Google Translator sollte jedoch immer auf dem Smartphone greifbar sein. Auch wird einem schnell bewusst, was man mit Hand und Fuß alles erklären kann. Eine kleine Empfehlung wäre noch <a href="https://www.amazon.de/Point-Travellers-language-Dieter-Graf/dp/3980880273"  target="_blank" rel="noreferrer">Point It</a>. Das ist ein kleines Büchlein mit allerlei Symbolen auf die man schnell mal zeigen kann, um Dinge genauer zu erfragen.</p>

<h2 class="relative group">Sonnencreme
    <div id="sonnencreme" class="anchor"></div>
    
</h2>
<p>Sonnencreme war überraschenderweise sehr teuer in Vietnam. Die Einwohner selber nutzen keine und kleiden sich eher bedeckt. Für eine Tube zahlt man schnell mal 8€ und meistens bleicht diese dann sogar noch die Haut, aufgrund des dort vorherrschenden Schönheitsideals, weiß. Sonnencreme sollte daher ausreichend eingepackt werden.</p>

<h2 class="relative group">Essen
    <div id="essen" class="anchor"></div>
    
</h2>
<p>Das Essen in Vietnam ist einfach großartig. Simpel, aber gut. Es sollte immer darauf geachtet werden, dass Fleisch gut frittiert oder durchgebraten ist. Bei Gemüse und Obst gilt der Grundsatz <em>Peel it or leave it</em>. Wer in Vietnam ist, sollte unbedingt die <a href="https://de.wikipedia.org/wiki/Ph%E1%BB%9F"  target="_blank" rel="noreferrer">Pho Suppe</a> probieren. Sie variiert durchs gesamte Land und ist spottbillig. Auch der vietnamesische <a href="https://upload.wikimedia.org/wikipedia/commons/b/b7/Viet-coffee.jpg"  target="_blank" rel="noreferrer">Ca Phe Sua</a> mit gesüßter Kondensmilch ist absolut empfehlenswert, insbesondere auf Eis an heißen Tagen. Er ist stark und hat eine ausgeprägte Kakaonote, die ich so nie zuvor geschmeckt hatte.</p>
<p><strong>Empfehlung:</strong> Probiert werden sollte die süße <a href="https://de.wikipedia.org/wiki/Longan"  target="_blank" rel="noreferrer">Longanfrucht</a>. Sie ähnelt der Lychee und ist auf allen Märkten zu erschwinglichen Preisen erwerbbar. Frittierte Hunde habe ich nur in Hanoi gesehen und habe es nicht übers Herz gebracht sie zu probieren. Generell scheint der Mythos der frittierten Hunde ein wenig verklärt zu sein. Fakt ist: Man wird niemals aus Versehen Hund essen, da das Fleisch deutlich teurer ist als Huhn oder Schwein. Das Verzehren von Hunden scheint zudem eine Tradition der älteren Bevölkerung zu sein. Sämtliche junge Familien, insbesondere auf dem Land, lieben ihre Hunde (auch wenn sie ihnen komischerweise nie Namen geben) und würden sie niemals essen.</p>

<h2 class="relative group">Busreisen innerhalb des Landes
    <div id="busreisen-innerhalb-des-landes" class="anchor"></div>
    
</h2>
<p>Ganz klare Empfehlung zum Meistern größerer Strecken ist die <a href="https://futabus.vn/"  target="_blank" rel="noreferrer">Futa Bus Lines</a>. Die Reisen sind mit 8-15€ sehr günstig. Gefahren wird in einem Schlafbus mit Liegen und in der Regel steht einem auch W-Lan zur Verfügung und ein kostenloses Essen ist im Ticketpreis enthalten. Man sollte sich jedoch niemals auf die offiziellen Abfahrts- und Ankunftszeiten verlassen. Man ist gerne mal 3 Stunden früher da und steht dann um 4 Uhr morgens an einem Ort, an dem nichts geöffnet hat. Praktisch ist, dass Futa einen kostenlosen Shuttle Service anbietet. Dieser holt einem am Hostel/Hotel ab und bei Ankunft an der neuen Destination bringt dieser einen auch zur neuen Herberge. Absolut Top!</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Die berufliche Auszeit und die deutsche Bürokratie</title>
      <link>https://jbetzen.net/posts/life-001-berufliche-auszeit/</link>
      <pubDate>Sat, 24 Nov 2018 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/life-001-berufliche-auszeit/</guid>
      <description>Dieser Post beschreibt, wie man eine berufliche Auszeit ohne Kündigung verschaffen kann, sofern der Arbeitgeber mitspielt. Damit verbunden ist ein inhärenter Kampf gegen die deutsche Bürokratie.</description>
      <content:encoded><![CDATA[ <div class="lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl">
  <em>Wir können die Schwerkraft überwinden, aber der Papierkram erdrückt uns.</em>
</div>

<p>Wer wie ich in einem Leiharbeitsverhältnis beschäftigt ist und ständig wechselnde Einsatzorte hat, kennt sicherlich das Gefühl der stetigen Unplanbarkeit des Lebens. Arbeitseinsätze, die nur für 1-3 Monate angesetzt sind, zerrten bisweilen stark an meinem Gemüt und haben mich dazu angestiftet darüber nachzudenken, wie man den zeitlichen Übergang zwischen einem alten und neuen Einsatzort (neudeutsch IC: Intercontract Zeit) in Zukunft sinnvoller nutzen könnte, ohne dass für den Arbeitgeber und einen selbst Nachteile entstehen. Ich habe mich dazu entschlossen zwei Monate an das andere Ende der Welt zu Reisen und Asien als Backpacker zu erkunden. Das klingt nun leicht daher gesagt, denn so einfach ist das in einem bürokratischen Land wie Deutschland nicht, schließlich muss alles wasserdicht schriftlich abgesichert sein, damit eine erfolgreiche Wiederaufnahme des Jobs ohne neu verhandelten Vertrag, eine fortlaufende Krankenversicherung und eine lückenlose Renteneinzahlungen gewährleistet sind.</p>
<p>Im Folgenden möchte ich erläutern, was alles berücksichtigt, geplant und erledigt werden muss, bevor die berufliche Auszeit angetreten werden kann. Möge es anderen eine Hilfe sein dem beruflichen Alltag zu entfliehen, um Zeit für sich selbst zu finden und zu nutzen!</p>

<h2 class="relative group">Arbeitgeber informieren
    <div id="arbeitgeber-informieren" class="anchor"></div>
    
</h2>
<p>Der Arbeitgeber sollte frühestmöglich über die Pläne einer Auszeit informiert werden. Dass dies in unserer schnelllebigen Welt der Leiharbeit nicht immer möglich ist liegt auf der Hand. Bei mir gab es Momente, da wusste ich 3 Tage vor Monatsabschluss nicht, ob ich am nächsten Monat noch am aktuellen Einsatzort eingesetzt werde oder nicht. Auch gab es den Moment, an dem ich nicht wusste, ob ich vor Antritt der Weihnachtsferien anschließend noch meine Arbeitsplatz wiedersehen würde. Wichtig ist, dass der Arbeitgeber genügend Planungszeitraum hat und ein festes Datum der Rückkehr genannt bekommt, um schnellstmöglich eine Anschlussverwendung arrangieren zu können.</p>
<p>Einem modernen Arbeitgeber sollten berufliche Auszeiten nicht fremd sein, so hat sich beispielsweise das <em>Sabbatical</em> auch außerhalb des Beamtentums in den Arbeitsalltag vieler Beschäftigten geschlichen. Erfahrungsgemäß sind nur wenige Arbeitnehmer über diese Möglichkeit der Auszeit informiert. Sabbaticals sind in der Regel aber mit vielen Auflagen verbunden. Oftmals wird gefordert eine hohe Anzahl Überstunden aufzubauen und diese mit dem kompletten Jahresurlaub zu kombinieren. Als Gegenleistung erhält man dann eine fortlaufende Rentenzahlung und behält den Krankenversichertenstatus. Dies kam für mich jedoch nicht in Frage, da ironischerweise an meinem derzeitigen Einsatzort keine Überstunden aufgebaut werden dürfen und mir mein Jahresurlaub heilig ist. Sollte ein Unternehmen einen Betriebsrat haben, ist es sinnvoll sich mit diesem in Verbindung zu setzen und einmal pauschal nachzufragen, was aus dessen Sicht zu beachten ist. Es ist zudem sinnvoll den Betriebsrat früh in die Reiseplanung einzuweihen, damit man eine unbeteiligte Partei an Bord hat, falls der Arbeitgeber auf dumme Ideen bei der Rückkehr kommt.</p>
<p>Ich habe mich mit meinem Arbeitgeber mittels eines Zusatzvertrages, einer sogenannten Ruhezeitvereinbarung, geeinigt, der ihn von der Zahlung von Rentenbeiträgen und Sozialabgaben freispricht und im Gegenzug garantiert, dass der bestehende Arbeitsvertrag nach Rückkehr wieder in Kraft tritt. Ich wiederum muss mich selber Krankenversichern und für die fortlaufenden Rentenzahlungen aufkommen. Kurzum: Finanziell muss aus der eigenen Tasche ein nicht zu vernachlässigender Betrag beigesteuert werden. Ob sich ein Arbeitgeber zu einer solchen Vereinbarung hinreißen lässt, kann pauschal sicherlich nicht beantwortet werden. Oftmals sind Kündigungsprozesse deutlich besser in Unternehmen automatisiert, als Sonderverträge. Da das Gewinnstreben von Unternehmen pervertierte Züge angenommen hat, könnten Arbeitgeber auch die Chance sehen eine Kündigung auszusprechen, um anschließend bei Rückkehr einen Neuvertrag mit schlechteren Bedingungen auszuhandeln. Daher ist Vorsicht geboten, dass kein persönlicher Nachteil entsteht.</p>
<p>Die Konditionen des Zusatzvertrages werden vom Arbeitgeber bestimmt. Die wichtigsten Punkte lauten:</p>
<ul>
<li>Die Ruhe des bestehenden unbefristeten Vertrags für den verhandelten Zeitraum der Auszeit und dessen uneingeschränkter Fortführung nach der Rückkehr</li>
<li>Der Arbeitgeber ist in diesem Zeitraum nicht verpflichtet die sogenannten Hauptleistungspflichten zu erbringen. Das umfasst: Gehaltszahlungen, Urlaubsansprüche, Urlaubsgeld, Bonuszahlungen, etc.</li>
<li>Ich als Arbeitnehmer bin selber verpflichtet meinen sozialversicherungspflichtigen Belangen nachzugehen</li>
</ul>
<p>Somit ist aus Sicht des Arbeitgebers und des Arbeitnehmers rechtlich alles geklärt.</p>

<h2 class="relative group">Krankenkasse informieren
    <div id="krankenkasse-informieren" class="anchor"></div>
    
</h2>
<p>Nachdem sich der Arbeitgeber von der Zahlung von Sozialleistungen freigesprochen hat, ist der nächste Gang der zur Krankenkasse. Hier gilt es sich in Eigenleistung für die berufliche Auszeit zu versichern, sofern man in einem Land verweilt in dem der Versicherungsschutz der Krankenkasse gilt. Meine Krankenversicherung bietet zwar ein kostenloses Servicetelefon und auch ein Online-Postfach für digitalen Schriftverkehr, der Erfahrung gemäß lohnt es sich aber einen Termin vor Ort zu vereinbaren und die Angelegenheit und Fragen auf kurzem Weg zu klären. Der zu entrichtende Betrag hängt maßgeblich davon ab, wann der Arbeitgeber die Zahlungen der Sozialleistungen beendet. In meinem Fall war dies zum Monatsende. Meine Reise begann jedoch erst 5 Tage später. Da in Deutschland eine gesetzliche Versicherungspflicht besteht, musste also auf Basis von einkommensabhängigen Tagessätzen ein Versicherungsverhältnis für 5 Tage ausgehandelt werden. Das sind ca. 60€ Eigenleistung, die auf einen zukommen. In manchen Fällen kommen die Krankenkassen ihren Kunden auch entgegen und übernehmen eine gewisse Überbrückungszeit aus eigener Tasche. Aus Erfahrung lässt sich sagen, an dieser Stelle ein bisschen hartnäckiger nachzufragen. In meinem Fall hat sich die Krankenkasse entschlossen diese Zahlungen zu übernehmen. Als Nachweise des Auslandsaufenthalts müssen der Krankenkasse die Flugtickets vorgelegt werden. Selbiges gilt analog für die Rückkehr! In diesem Fall ist die Krankenkasse dazu verpflichtet ihre Versicherten wieder aufzunehmen. Gibt es auch hier eine Diskrepanz zwischen dem Datum der Rückkehr und dem Beginn der erneuten Zahlung der Sozialleistungen durch den Arbeitgeber, muss man selber wieder zum Geldbeutel greifen.</p>

<h2 class="relative group">Private Auslandskrankenversicherung
    <div id="private-auslandskrankenversicherung" class="anchor"></div>
    
</h2>
<p>Wichtig ist zudem das Reiseziel. Der Leistungsumfang meiner Krankenkasse beinhalten keine Leistungen in Asien. In der Regel haben Krankenkassen Vereinbarungen mit Nachbarländern - für Asien gilt das jedoch nicht. Man ist also tunlichst angeraten eine private Auslandskrankenversicherung abzuschließen. Diese bieten für eine festgelegte Anzahl an Tagen im Jahr Schutz bei Aufenthalt im Ausland. Ich habe dafür eine <a href="https://www.huk.de/gesundheit-vorsorge-vermoegen/krankenversicherung/auslandskrankenversicherung.html"  target="_blank" rel="noreferrer">Auslandskrankenversicherung bei der HUK</a> abgeschlossen, die an 56 Reisetagen pro Jahr haftet. Kostenpunkt: läppische 9€ pro Jahr. Wer hier nicht zugreift, dem ist nicht zu helfen.</p>

<h2 class="relative group">Rentenversicherung informieren
    <div id="rentenversicherung-informieren" class="anchor"></div>
    
</h2>
<p>Da sich der Arbeitgeber ebenfalls von den Einzahlungen in die Rentenkasse freigesprochen hat, sollte man an dieser Stelle selber zur Tat schreiten. Grundsätzlich stellt es einem die Deutsche Rentenversicherung frei, <a href="https://www.deutsche-rentenversicherung.de/Allgemein/de/Navigation/2_Rente_Reha/01_Rente/01_allgemeines/02_freiwillige_versicherung/00_freiwillige_versicherung_node.html"  target="_blank" rel="noreferrer">wie viel man aus Eigenleistung einzahlen möchte</a>. Vorgegeben sind lediglich ein festgelegter Mindestbeitrag von 83,70€ und ein Höchstbetrag von 1209,00€ (Stand: 08/2018). Ich habe mich entschlossen lediglich den Mindestbeitrag einzuzahlen, damit keine Lücke in den Einzahlungen besteht. Hierfür muss das <a href="https://www.deutsche-rentenversicherung.de/SharedDocs/Formulare/DE/_pdf/V0060.html"  target="_blank" rel="noreferrer">Formular V0060: Antrag auf Beitragszahlung für eine freiwillige Versicherung</a> ausgefüllt und eingereicht werden. Hier muss auch der Zeitraum eingetragen werden ab wann freiwillig eingezahlt wird. Sinnloserweise gibt es kein Feld, um die Dauer der Zahlungen anzugeben. Ein Anruf bei der Rentenversicherung löste das Dilemma, indem darauf hingewiesen wurde den Zeitraum händisch daneben zu schreiben und dem Antrag eine schriftliche Erläuterung beizulegen. Die Deutsche Rentenversicherung hat in vielen Städten Anlaufstellen, wo der Antrag eingereicht und die Situation noch einmal näher erläutert und besprochen werden kann.</p>
<p>Zusätzlich macht es Sinn, der Deutschen Rentenversicherung für diesen Zeitraum eine Einzugsermächtigung für das Konto zu erteilen, um sicherzustellen dass alle Zahlen zeitgerecht abgebucht werden. Hierfür wird das <a href="https://www.deutsche-rentenversicherung.de/SharedDocs/Formulare/DE/_pdf/V0005.html"  target="_blank" rel="noreferrer">Formular V0005: SEPA-Basis-Lastschriftmandat für wiederkehrende Lastschriften</a> benötigt.</p>
<p>Somit ist die Papierhölle erfolgreich durchlaufen. Travel Safe!</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Vietnam: Nützliche Apps für den Alltag</title>
      <link>https://jbetzen.net/posts/life-003-vietnam-apps/</link>
      <pubDate>Sat, 24 Nov 2018 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/life-003-vietnam-apps/</guid>
      <description>Das Mobilfunknetz in Vietnam ist hervorragend ausgebaut und bietet viel Datenvolumen für kleines Geld. Welche Apps in Vietnam von Nutzen sind, habe ich in diesem Post zusammengetragen.</description>
      <content:encoded><![CDATA[ <p>Vietnam bietet eine sehr gute Netzabdeckung und günstiges mobiles Internet. An jeder Ecke können Prepaid-Sim-Karten erworben werden. Ich habe mir bspw. ein Datenvolumen von 18GB für ca. 6€ gekauft. Da kann einem, in Anbetracht deutscher Marktpreise, das Herz aufgehen. Vor meiner Reise habe ich mir allelei Apps heruntergeladen, wovon die meisten aber auch nach kürzester Zeit wieder vom Handy verschwunden waren.</p>
<p>Im Folgenden ist eine Liste der Apps für Android, die ich am Meisten in Vietnam genutzt habe. Davon stammen ein paar aus dem Appstore <a href="https://de.wikipedia.org/wiki/F-Droid"  target="_blank" rel="noreferrer">F-Droid</a>, der eine Alternative zu Googles Playstore darstellt und auschließlich datenschutzfreundliche Open-Source Apps zur Verfügung stellt. F-Droid kann <a href="https://f-droid.org/de/"  target="_blank" rel="noreferrer">hier</a> heruntergeladen und auf dem Android Smartphone installiert werden.</p>
<p>Die Apps im Detail:</p>
<table>
  <thead>
      <tr>
          <th>App</th>
          <th>Beschreibung</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Tricky Tripper</td>
          <td>Eine Bilanzsoftware für gemeinsame Ausgaben. Wer in der Gruppe reist, zahlt auch mal etwas für die anderen mit. Diese App listet alle Ausgaben der einzelnen Personen und berechnet das jeweilige Guthaben der Einzelpersonen. Am Ende der Reise kann eine Übersicht in diversen Formaten exportiert werden.</td>
      </tr>
      <tr>
          <td>Grab</td>
          <td>Grab ist das Uber Asiens. Speziell in Großstädten kann hiermit günstig von A nach B fahren. Es sollte eine Kreditkarte hinterlegt und die Zahlung ausschließlich über die App abgewickelt werden.</td>
      </tr>
      <tr>
          <td>Agoda</td>
          <td>Zum Buchen von Hotels/Hostels.</td>
      </tr>
      <tr>
          <td>Booking</td>
          <td>Ebenfalls zum Buchen von Hotels/Hostels.</td>
      </tr>
      <tr>
          <td>AirBnB</td>
          <td>Viele Hostels, Hotels oder Privatleute inserieren auch bei AirBnB was sich in Vietnam hervorragend benutzen lässt.</td>
      </tr>
      <tr>
          <td>Foody</td>
          <td>Hungrig? Foody ist neben Tripadvisor die scheinbar meistgenutzte App, um Restaurants zu finden und deren Bewertungen zu lesen. Oftmals findet man in der App direkt eine abfotografierte Speisekarte.</td>
      </tr>
      <tr>
          <td>Tripadvisor</td>
          <td>Das Urgestein zum Auffinden von Restaurants und Sehenswürdigtkeiten. Isst man in Vietnam in einem Restaurant, wird man oft gebeten eine Bewertung zu hinterlassen.</td>
      </tr>
      <tr>
          <td>OsmAnd</td>
          <td>Die freie und unglaublich detaillierte Alternative zu Google Maps. Die App stellt das Kartenmaterial des Openstreetmap-Projektes dar. Zu dieser Karte kann jeder direkt beitragen und sie um Informationen ergänzen. Kleine Wanderwege, Sehenswürdigkeiten, Restaurants – alles ist hier zu finden und das Beste ist, dass diese App auch offline zur Navigation genutzt werden kann. Alle Karten müssen zuvor heruntergeladen werden und kommen ohne mobiles Internet aus.</td>
      </tr>
      <tr>
          <td>Google Maps</td>
          <td>Zur Navigation und vermutlich bereits auf den meisten Android Geräten vorhanden.</td>
      </tr>
      <tr>
          <td>MAPS.ME</td>
          <td>Ebenfalls eine Karten- und Navigationsapp, die auch Offline genutzt werden kann. Das Kartenmaterial stammt ebenfalls von Openstreetmap. Die App ist einfacher gehalten als OsmAnd, aber in meinen Augen lange nicht so gut.</td>
      </tr>
      <tr>
          <td>Windy</td>
          <td>Windy liefert sehr gute Wetterprognosen. Ich hatte das große Glück dem Taifun Mankhut davonlaufen zu dürfen und Windy hat stets sehr gut aufgezeigt wo das Auge des Sturms zur Zeit wütet und wo er die nächsten Tage hingelangen wird.</td>
      </tr>
      <tr>
          <td>xCurrency</td>
          <td>Die wahrscheinlich meistgenutzte App von mir. xCurrency ist ein Währungsumrechner, der stets aktuelle Umtauschkurse anwendet. Es können auf einem Bildschirm gleich mehrere Währungen umgerechnet werden. In Vietnam findet man häufig neben dem vietnamesischen Dong auch Preisangaben in Dollar und können somit übersichtlich umgerechnet werden.</td>
      </tr>
      <tr>
          <td>Pixelfed</td>
          <td>Pixelfed ist ein freier Instagramklon und an sich bloß eine Webapplikation. Ich habe es verwendet um meine Reise zu dokumentieren.</td>
      </tr>
      <tr>
          <td>OpenVPN</td>
          <td>In Vietnam sind beizeiten manche Webseiten gesperrt. Es herrscht eine faktische staatliche Internetzensur. Seiten wie BBC oder die Tagesschau wurden bei mir diverse male geblockt. Abhilfe schafft da ein VPN. Ich nutze iPredator und verwende den Dienst auf Android mit OpenVPN.</td>
      </tr>
      <tr>
          <td>Google Translator</td>
          <td>Googles Übersetzung hat sich vielerort als hilfreich erwiesen. Man gerät öfter in die Situation, dass Speisekarten nicht übersetzt wurden und sitzt dann etwas ratlos im Restaurant. In abgelegeneren Gegenden ist Englisch als Sprache zudem nicht immer verbreitet und anwendbar. Google schafft hier Abhilfe.</td>
      </tr>
  </tbody>
</table>
<hr>
<p>Die gelisteten Apps gelten noch für Android. Warum ich mittlerweile zu iOS gewechselt bin habe ich in dem Post <a href="/posts/android-zu-ios/">Wechsel von Android zu iOS</a> erläutert.</p>
 ]]></content:encoded>
    </item>
    
    <item>
      <title>Xiaomi MiScale Waage in Homeassistant integrieren</title>
      <link>https://jbetzen.net/posts/xiaomi-scale/</link>
      <pubDate>Sat, 28 Jul 2018 23:42:13 +0200</pubDate>
      
      <guid>https://jbetzen.net/posts/xiaomi-scale/</guid>
      <description>Xiaomi hat eine Personenwaage im Sortiment, die MiScale heißt und über Bluetooth LE die gemessenen Metriken funkt. Die Daten können über eine Software namens BT-MQTT-Gateway in Homeassistant eingelesen und dargestellt werden.</description>
      <content:encoded><![CDATA[ 
  
  
  
  



<div
  
    class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
  
  >
  <span
    
      class="text-primary-400 pe-3 flex items-center"
    
    >
    <span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
</span>
  </span>

  <span
    
      class="dark:text-neutral-300"
    
    >Zu diesem Artikel ist ein Update verfügbar, in dem gezeigt wird wie die Waage mittels ESPHome ausgelesen werden kann: <a href="/posts/esphome-btle-hub/">ESPHome Bluetooth Low Energy Hub</a></span>
</div>

<p>Homeassistant unterstützt von Haus aus mehr als 1100 Komponenten. Leider zählt die Xiaomi MiScale Personenwaage nicht dazu. Das ist fürs Erste ärgerlich, aber gleichzeitig auch Ansporn kreative Wege einzuschlagen. Wer also gerne seinen Gewicht und ggf. dessen Verlauf im Frontend sehen möchte, braucht ein Programm, das Homeassistant die Daten der Waage in verwertbarer Weise zuspielt. Ich nutze dafür <a href="https://github.com/zewelor/bt-mqtt-gateway"  target="_blank" rel="noreferrer">bt-mqtt-gateway</a>. Das Gateway extrahiert die Daten <a href="https://github.com/zewelor/bt-mqtt-gateway#supported-devices"  target="_blank" rel="noreferrer">diverser</a> Bluetooth-Geräte und sendet sie über <a href="/categories/mqtt/" >MQTT</a> an Homeassistant.</p>

<h2 class="relative group">MAC-Adresse der Waage herausfinden
    <div id="mac-adresse-der-waage-herausfinden" class="anchor"></div>
    
</h2>
<p>Das geht am einfachsten mit dem <code>hcitool</code> auf Linux. Einfach sicherstellen, dass Bluetooth am Gerät eingeschaltet ist und dann das Kommando ins Terminal tippen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo hcitool lescan</span></span></code></pre></div></div>
<p>Nun werden alle Bluetooth-Geräte mit MAC-Adresse und Namen gelistet. Die Xiaomi-Waage hat bspw. den Namen <code>MI_SCALE</code>. Die MAC-Adresse wird später benötigt und muss notiert werden.</p>

<h2 class="relative group">BT-MQTT-GATEWAY
    <div id="bt-mqtt-gateway" class="anchor"></div>
    
</h2>

<h3 class="relative group">Installation
    <div id="installation" class="anchor"></div>
    
</h3>
<p>Die Installation wird auf der zugehörigen Github-Seite ausführlich beschrieben. Zuerst müssen ein paar Abhängigkeiten auf Systemebene installiert werden. Anschließend wird ein <code>virtualenv</code> angelegt, um die Installation der Python-Pakete sauber von den Systempaketen zu trennen.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo apt-get install git python3 python3-pip python3-wheel bluetooth bluez libglib2.0-dev
</span></span><span class="line"><span class="cl">sudo pip3 install virtualenv
</span></span><span class="line"><span class="cl">git clone https://github.com/zewelor/bt-mqtt-gateway.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> bt-mqtt-gateway
</span></span><span class="line"><span class="cl">virtualenv -p python3 .venv
</span></span><span class="line"><span class="cl"><span class="nb">source</span> .venv/bin/activate
</span></span><span class="line"><span class="cl">sudo pip3 install -r requirements.txt</span></span></code></pre></div></div>

<h3 class="relative group">Konfiguration
    <div id="konfiguration" class="anchor"></div>
    
</h3>
<p>Das Gateway benötigt eine Konfigurationsdatei. Ein Beispiel wird mit dem Gateway mitgeliefert. Dieses kann kopiert werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">cp config.yaml.example config.yaml
</span></span><span class="line"><span class="cl">nano config.yaml</span></span></code></pre></div></div>
<p>Es müssen die Zugangsdaten zum MQTT-Broker und die MAC-Adresse der Waage eingetragen werden. Auch kann das Polling-Intervall angepasst werden, das bestimmt wie oft die Waage ausgelesen werden soll. Die config.yaml sollte am Ende so aussehen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">mqtt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost/IP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">USERNAME_MQTT_BROKER</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">PASSWORD_MQTT_BROKER</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">manager</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">topic_subscription</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">update_all</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">topic</span><span class="p">:</span><span class="w"> </span><span class="l">homeassistant/status</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">payload</span><span class="p">:</span><span class="w"> </span><span class="l">online</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">workers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">miscale</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">args</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mac</span><span class="p">:</span><span class="w"> </span><span class="l">XX:XX:XX:XX:XX:XX</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">topic_prefix</span><span class="p">:</span><span class="w"> </span><span class="l">miscale</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">update_interval</span><span class="p">:</span><span class="w"> </span><span class="m">3600</span></span></span></code></pre></div></div>
<p>Sind alle Daten eingetragen und gespeichert, kann die erstellte Konfiguration getestet werden um zu schauen, ob die Waage erfolgreich ausgelesen wird:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo ./gateway.py</span></span></code></pre></div></div>

<h3 class="relative group">Systemd Service anlegen
    <div id="systemd-service-anlegen" class="anchor"></div>
    
</h3>
<p>Es wird eine Beispielkonfiguration für einen Systemd Service mitgeliefert, die jedoch angepasst werden muss. Es muss der korrekte Pfad zum bt-mqtt-gateyway angepasst werden:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo cp bt-mqtt-gateway.service /etc/systemd/system/
</span></span><span class="line"><span class="cl">sudo nano /etc/systemd/system/bt-mqtt-gateway.service</span></span></code></pre></div></div>
<p>Anschließend wird der Dienst registriert und aktiviert:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo systemctl daemon-reload
</span></span><span class="line"><span class="cl">sudo systemctl start bt-mqtt-gateway
</span></span><span class="line"><span class="cl">sudo systemctl status bt-mqtt-gateway
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> bt-mqtt-gateway</span></span></code></pre></div></div>

<h2 class="relative group">Homeassistant
    <div id="homeassistant" class="anchor"></div>
    
</h2>

<h3 class="relative group">MQTT-Sensor für Gewicht
    <div id="mqtt-sensor-für-gewicht" class="anchor"></div>
    
</h3>
<p>Homeassistant benötigt lediglich einen <a href="https://www.home-assistant.io/components/sensor.mqtt/"  target="_blank" rel="noreferrer">MQTT-Sensor</a>, um den übermittelten Gewichtswert des Gateways entgegenzunehmen:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">mqtt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Gewicht&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">state_topic</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;miscale/weight/kg&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">unit_of_measurement</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;kg&#34;</span></span></span></code></pre></div></div>

<h3 class="relative group">Template Sensor für BMI
    <div id="template-sensor-für-bmi" class="anchor"></div>
    
</h3>
<p>Der <a href="https://de.wikipedia.org/wiki/Body-Mass-Index"  target="_blank" rel="noreferrer">Body-Mass-Index</a> ergibt sich wie folgt aus dem Quotienten des Gewichts in Kilogramm und der Körpergröße in Metern zum Quadrat:</p>
<p>

$$BMI = \frac{m}{h^{2}} = m \times \frac{1}{h^{2}}$$</p>
<p>Da das Gewicht in Kilogramm bereits in Homeassistant vorhanden ist, muss lediglich der hinten angestellte Faktor \(\frac{1}{h^{2}}\) ausrechnet werden. Bei einer Größe von bspw. <code>1,84m</code> ergibt sich im Kehrwert also <code>0,29536862</code>. Diesen Wert benötigen wird für den BMI-Sensor in Homeassistant. Der Sensor muss so aussehen und gibt den BMI-Wert auf eine Nachkommastelle heraus:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sensors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">bmi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">friendly_name</span><span class="p">:</span><span class="w"> </span><span class="l">BMI</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ &#39;%.1f&#39; | format(states(&#39;sensor.gewicht&#39;) | float * 0.29536862) }}&#34;</span></span></span></code></pre></div></div>
 ]]></content:encoded>
    </item>
    
  </channel>
</rss>
