<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Marc René Frieß &#8211; SmartHomeNG | smarthome knx homematic mqtt hue 1wire home automation</title>
	<atom:link href="https://www.smarthomeng.de/author/psilo/feed" rel="self" type="application/rss+xml" />
	<link>https://www.smarthomeng.de</link>
	<description>Die Device Integrations-Plattform für Dein Smart Home</description>
	<lastBuildDate>Wed, 30 Dec 2020 15:57:33 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.5</generator>

<image>
	<url>https://www.smarthomeng.de/wp-content/uploads/global/logo_small_152x152-150x150.png</url>
	<title>Marc René Frieß &#8211; SmartHomeNG | smarthome knx homematic mqtt hue 1wire home automation</title>
	<link>https://www.smarthomeng.de</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Tablet als SmartHome Zentrale für smartVISU (mit &#8222;Fully Kiosk Browser&#8220; / Motion Detection)</title>
		<link>https://www.smarthomeng.de/tablet-als-smarthome-zentrale-fuer-smartvisu-mit-fully-kiosk-browser-und-bewegungserkennung</link>
					<comments>https://www.smarthomeng.de/tablet-als-smarthome-zentrale-fuer-smartvisu-mit-fully-kiosk-browser-und-bewegungserkennung#comments</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Wed, 30 Dec 2020 15:34:27 +0000</pubDate>
				<category><![CDATA[Tipps & Tricks]]></category>
		<category><![CDATA[Bewegungserkennung]]></category>
		<category><![CDATA[Fully Kiosk Browser]]></category>
		<category><![CDATA[Motion Detection]]></category>
		<category><![CDATA[smartVISU]]></category>
		<category><![CDATA[Tablet]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=2683</guid>

					<description><![CDATA[Schon lange war ich auf der Suche nach einer Lösung für eine Steuerung meines SmartHomes vom Eingangsbereich unseres Hauses aus. Alle meine Versuche in den letzten Jahren, ein Android Tablet mit Bewegungserkennung und dauerhaft laufender smartVISU aufzusetzen, schlugen an der Umsetzung der Bewegungserkennung fehl. Weihnachten 2020 wollte endlich ich einen<a class="moretag" href="https://www.smarthomeng.de/tablet-als-smarthome-zentrale-fuer-smartvisu-mit-fully-kiosk-browser-und-bewegungserkennung"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Schon lange war ich auf der Suche nach einer Lösung für eine Steuerung meines SmartHomes vom Eingangsbereich unseres Hauses aus. Alle meine Versuche in den letzten Jahren, ein Android Tablet mit Bewegungserkennung und dauerhaft laufender smartVISU aufzusetzen, schlugen an der Umsetzung der Bewegungserkennung fehl.<br />
Weihnachten 2020 wollte endlich ich einen neuen Anlauf unternehmen, diesmal auf Basis eines erschwinglichen Samsung Galaxy Tab A7.<br />
Den am Ende erfolgreichen Ansatz beschreibe ich in diesem Artikel.</p>
<p><strong>Ausgangspunkt</strong></p>
<p>Basis meines Setups ist SmartHomeNG in neuster Version, dazu die SmartVISU 2.9. Vorgeschaltet ist NGINX als ReverseProxy wie einst in <a href="https://www.smarthomeng.de/nginx-als-reverseproxy">https://www.smarthomeng.de/nginx-als-reverseproxy</a> beschrieben. Auf dem Tablet selber testete ich mich erneut durch diverseste Anleitungen zum automatischen Einschalten via Bewegungserkennung. Die Anleitungen basierten auf den Apps / Kombinationen der Apps MacroDroid, Tasker und Motion Detector. Ein Teil dieser Apps ist kostenpflichtig. Via Tasker und Motion Detector konnte ich das Ganze zumindest für 24 Stunden lauffähig bekommen. Allerdings stellte ich fest, dass oft eine der Apps wegen CPU Überlastung des Geräts beendet wurde. Auch landete ich regelmäßig in einem Zustand, in dem das Display des Tablets einfach nicht mehr ausgehen wollte, was sogar in leichtem Einbrenneffekt im OLED resultierte. Auch die Einrichtung des Ganzen war alles andere als einfach&#8230;</p>
<p>Frustriert wollte ich schon aufgeben, da bin ich auf die App &#8222;<a href="https://play.google.com/store/apps/details?id=de.ozerov.fully&amp;hl=de&amp;gl=US">Fully Kiosk Browser &amp; App Lockdown</a>&#8220; (bzw. <a href="https://www.fully-kiosk.com/de/">https://www.fully-kiosk.com/de/</a>) gestoßen. Vom Namen her klang das ganze etwas etwas seltsam, stellte sich im Nachhinein aber als praktikable und super einfache Lösung heraus.<br />
In der Basisversion ist die App kostenlos. Für das was ich vorhatte (Authentifizierung via Clientzertifikaten + Bewegungserkennung) musste ich leider die volle Version für stolze 6.90 € (pro Gerät) freischalten.</p>
<p><strong>Setup</strong></p>
<p>Nach dem Download aus dem Playstore und der kostenpflichtigen Freischaltung der vollen Features, geht man in der App erstmal über die linke Randleiste in den Bereich <strong>&#8222;Settings&#8220;</strong>.</p>
<p><img fetchpriority="high" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2020/12/Einstieg-1.png" alt="" width="698" height="419" class="alignnone wp-image-2685" srcset="https://www.smarthomeng.de/wp-content/uploads/2020/12/Einstieg-1.png 2000w, https://www.smarthomeng.de/wp-content/uploads/2020/12/Einstieg-1-300x180.png 300w, https://www.smarthomeng.de/wp-content/uploads/2020/12/Einstieg-1-768x461.png 768w, https://www.smarthomeng.de/wp-content/uploads/2020/12/Einstieg-1-1024x614.png 1024w" sizes="(max-width: 698px) 100vw, 698px" /></p>
<p>Unter <strong>&#8222;Web Content Settings&#8220;</strong> muss als erstes die Start URL auf den DynDNS HTTPS-Einstiegspunkt für die smartVISU gelegt werden. Später habe ich die Start-URL hier noch angepasst, da ich ein 3-spaltiges Layout in die smartVISU implementiert habe, um die volle Breite des Tablets optimal zu nutzen.</p>
<p>Die <em>Authentifizierung</em> wird als nächstes im Bereich<strong> &#8222;Advanced Web Settings&#8220;</strong> eingerichtet.</p>
<p><img decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2020/12/Settings-1.png" alt="" width="690" height="414" class="alignnone wp-image-2687" srcset="https://www.smarthomeng.de/wp-content/uploads/2020/12/Settings-1.png 2000w, https://www.smarthomeng.de/wp-content/uploads/2020/12/Settings-1-300x180.png 300w, https://www.smarthomeng.de/wp-content/uploads/2020/12/Settings-1-768x461.png 768w, https://www.smarthomeng.de/wp-content/uploads/2020/12/Settings-1-1024x614.png 1024w" sizes="(max-width: 690px) 100vw, 690px" /></p>
<p>Relativ weit unten kann man mit der kostenpflichtigen PLUS Version der App das <strong>&#8222;Client Certificate File&#8220;</strong> angeben. Über &#8222;PICK A FILE&#8220; kann hier der volle Pfad auf die <em>p12 </em>Zertifikatsdatei ausgewählt werden, welche auf dem Gerät abliegen muss. Da das Clientzertifikat mit Passwort geschützt ist, muss dieses im darunterliegenden Punkt <strong>&#8222;Client Certificate Password&#8220;</strong> gesetzt werden.</p>
<p><img decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2020/12/certificate.png" alt="" width="687" height="412" class="alignnone wp-image-2689" srcset="https://www.smarthomeng.de/wp-content/uploads/2020/12/certificate.png 2000w, https://www.smarthomeng.de/wp-content/uploads/2020/12/certificate-300x180.png 300w, https://www.smarthomeng.de/wp-content/uploads/2020/12/certificate-768x461.png 768w, https://www.smarthomeng.de/wp-content/uploads/2020/12/certificate-1024x614.png 1024w" sizes="(max-width: 687px) 100vw, 687px" /></p>
<p>Würde man keine Clientzertifikate verwenden oder möchte man nicht für die PLUS Version zahlen, könnte man auch unter &#8222;Web Content Settings&#8220; einfach den User und das Passwort für eine Basic Authentication hinterlegen.</p>
<p>Schließt man die Konfiguration nun, kann man links unter &#8222;Goto Start URL&#8220; bereits den Zugriff testen.</p>
<p><strong>Motion Detection</strong></p>
<p>Der &#8222;Fully Kiosk Browser&#8220; verfügt über eine integrierte Bewegungserkennung, die sich in meinen Tests robust und wenig CPU-lastig erwiesen hat. Auch im Dauerbetrieb gibt es hier keinerlei Probleme und die Bewegungserkennung zum Einschalten des Displays funktioniert einwandfrei, so lange der Browser die aktive Anwendung ist.</p>
<p>Die Einstellungen könnt ihr ebenfalls unter<strong> &#8222;Settings&#8220;</strong> und dann<strong> &#8222;Motion Detection (PLUS)&#8220;</strong> setzen. Die Bewegungserkennung gibt es &#8211; genauso wie die Clientzertifikate &#8211; nur in der Vollversion. Die gesetzten Einstellungen sind unten zu sehen. Sicher kann man je nach räumlicher Situation auch an der <strong>Detector Sensitivity</strong> und der <strong>Detector Frame Rate</strong> noch Feintuning betreiben.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2020/12/motiondetection.jpg" alt="" width="688" height="413" class="alignnone wp-image-2692" srcset="https://www.smarthomeng.de/wp-content/uploads/2020/12/motiondetection.jpg 2000w, https://www.smarthomeng.de/wp-content/uploads/2020/12/motiondetection-300x180.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2020/12/motiondetection-768x461.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2020/12/motiondetection-1024x614.jpg 1024w" sizes="(max-width: 688px) 100vw, 688px" /></p>
<p>Nach diesen Einstellungen konnte ich das Ganz erfolgreich in Betrieb nehmen. Mit einem dreispaltigen Layout kommt die smartVISU hier zudem voll zur Geltung. Das dreispaltige Layout habe ich mit einem einfachen <span>&lt;div </span><span>class</span><span>=&#8220;block&#8220; </span><span>style</span><span>=&#8220;</span><span>width</span>: <span>33</span>%<span>;</span><span>&#8222;</span><span>&gt; in den &#8222;block&#8220; DIVs erreicht.</span></p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2020/12/20201230_162647.png" alt="" width="3110" height="1750" class="alignnone size-full wp-image-2694" srcset="https://www.smarthomeng.de/wp-content/uploads/2020/12/20201230_162647.png 3110w, https://www.smarthomeng.de/wp-content/uploads/2020/12/20201230_162647-300x169.png 300w, https://www.smarthomeng.de/wp-content/uploads/2020/12/20201230_162647-768x432.png 768w, https://www.smarthomeng.de/wp-content/uploads/2020/12/20201230_162647-1024x576.png 1024w" sizes="(max-width: 3110px) 100vw, 3110px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/tablet-als-smarthome-zentrale-fuer-smartvisu-mit-fully-kiosk-browser-und-bewegungserkennung/feed</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>SmartHomeNG v1.6 &#8211; Das neue Admin Interface</title>
		<link>https://www.smarthomeng.de/smarthomeng-v1-6-das-neue-admin-interface</link>
					<comments>https://www.smarthomeng.de/smarthomeng-v1-6-das-neue-admin-interface#respond</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Sat, 11 May 2019 13:42:26 +0000</pubDate>
				<category><![CDATA[Core]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[Admin Interface]]></category>
		<category><![CDATA[Konfiguration]]></category>
		<category><![CDATA[Logikeditor]]></category>
		<category><![CDATA[SmartHomeNG 1.6]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=2325</guid>

					<description><![CDATA[Wie auch schon das Backend Plugin, erlaubt das neue Admin Interface eine grafische Konfiguration von SmartHomeNG. Diese wurde jedoch deutlich erweitert. Eine detaillierte Dokumentation findet ihr unterhttps://www.smarthomeng.de/user/admin/admin.html. Das Admin Interface wird über die /etc/module.yaml konfiguriert. Näheres dazu findet man hier:https://www.smarthomeng.de/user/konfiguration/module_admin.html Da es im Vergleich zum Backend eine rein clientseitige Webanwendung<a class="moretag" href="https://www.smarthomeng.de/smarthomeng-v1-6-das-neue-admin-interface"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Wie auch schon das Backend Plugin, erlaubt das neue Admin Interface eine grafische Konfiguration von SmartHomeNG. Diese wurde jedoch deutlich erweitert.</p>
<hr />
<p>Eine detaillierte Dokumentation findet ihr unter<br /><a href="https://www.smarthomeng.de/user/admin/admin.html" target="_blank" rel="noopener">https://www.smarthomeng.de/user/admin/admin.html</a>.</p>
<p>Das Admin Interface wird über die /etc/module.yaml konfiguriert. Näheres dazu findet man hier:<br /><a href="https://www.smarthomeng.de/user/konfiguration/module_admin.html" target="_blank" rel="noopener">https://www.smarthomeng.de/user/konfiguration/module_admin.html</a></p>
<hr />
<p>Da es im Vergleich zum Backend eine rein clientseitige Webanwendung ist (Angular), erlaubt es einige Funktionen, die mit dem Backend Plugin so bisher nicht möglich waren. Die gesamte Kommunikation mit dem Core findet über REST Webservices statt.</p>
<hr />
<p>Unter dem Reiter &#8222;<strong>System</strong>&#8220; verbergen sich die bekannten Systemeigenschaften, der PyPi Check und die Hinweise auf die verbauten 3rd Party Komponenten.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/system-1.jpg" alt="" width="1443" height="607" class="alignnone size-full wp-image-2328" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/system-1.jpg 1443w, https://www.smarthomeng.de/wp-content/uploads/2019/05/system-1-300x126.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/system-1-768x323.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/system-1-1024x431.jpg 1024w" sizes="(max-width: 1443px) 100vw, 1443px" />Neu ist, dass man hier auch sein SmartHomeNG, das HTTP Modul (bspw. für das Webservices Plugin) und das Admin Interface (&#8222;Admin Modul&#8220;) komplett grafisch konfigurieren kann.</p>
<p>Nach dem Speichern der Einstellungen, kann man hier direkt sein SmartHomeNG neu starten.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/system2.jpg" alt="" width="1449" height="676" class="alignnone size-full wp-image-2329" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/system2.jpg 1449w, https://www.smarthomeng.de/wp-content/uploads/2019/05/system2-300x140.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/system2-768x358.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/system2-1024x478.jpg 1024w" sizes="(max-width: 1449px) 100vw, 1449px" /></p>
<p>Unter dem Reiter &#8222;<strong>Dienste</strong>&#8220; lässt sich beispielsweise SmartHomeNG neu starten. Hier sind über weitere Reiter auch der bekannte eval und yaml Syntax Prüfer, der CONF-YAML Konverter und die Prüfung des Caches zu finden. Des weiteren kann man die Konfiguration von SmartHomeNG hier als Datei herunterladen.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/dienste.jpg" alt="" width="1445" height="735" class="alignnone size-full wp-image-2327" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/dienste.jpg 1445w, https://www.smarthomeng.de/wp-content/uploads/2019/05/dienste-300x153.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/dienste-768x391.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/dienste-1024x521.jpg 1024w" sizes="(max-width: 1445px) 100vw, 1445px" /></p>
<p>Unter &#8222;<strong>Items</strong>&#8220; findet man den bekannten Item-Tree.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/items.jpg" alt="" width="1430" height="1202" class="alignnone size-full wp-image-2330" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/items.jpg 1430w, https://www.smarthomeng.de/wp-content/uploads/2019/05/items-300x252.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/items-768x646.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/items-1024x861.jpg 1024w" sizes="(max-width: 1430px) 100vw, 1430px" />Neu ist hier, dass man hier auch die YAML Dateien der Items bearbeiten und speichern kann. Zudem findet man die neuen &#8222;Item Structs&#8220;, also bspw. vorgegebene Itemstrukturen für bestimmte Plugins.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/items2.jpg" alt="" width="1442" height="1090" class="alignnone size-full wp-image-2331" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/items2.jpg 1442w, https://www.smarthomeng.de/wp-content/uploads/2019/05/items2-300x227.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/items2-768x581.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/items2-1024x774.jpg 1024w" sizes="(max-width: 1442px) 100vw, 1442px" /></p>
<p>Unter &#8222;<strong>Logiken</strong>&#8220; ist die bekannte Liste der Logiken zu finden. Wir haben dabei insbesondere den Logikeditor aufpoliert, der jetzt ein Autocomplete der Items und der Plugin-APIs anbietet.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/logics1.jpg" alt="" width="1433" height="393" class="alignnone size-full wp-image-2332" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/logics1.jpg 1433w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logics1-300x82.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logics1-768x211.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logics1-1024x281.jpg 1024w" sizes="(max-width: 1433px) 100vw, 1433px" />Einfach &#8222;sh.&#8220; eintippen und Vorschläge erscheinen. Am Ende des Vorschlag sieht man, ob es sich um ein Item oder eine Pluginfunktion handelt. Pluginfunktionen werden mit Informationen über die notwendigen Parameter eingefügt.<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_editor.jpg" alt="" width="1444" height="454" class="alignnone size-full wp-image-2333" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_editor.jpg 1444w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_editor-300x94.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_editor-768x241.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_editor-1024x322.jpg 1024w" sizes="(max-width: 1444px) 100vw, 1444px" /></p>
<p>Bei den Parametern einer Logik, wurde das Handling der Watch Items mit einem Autocomplete überarbeitet. Die Eingabe lässt es zu, Watch Items auch mit &#8222;sh.&#8220; Präfix zu suchen. Sie werden dann ohne diesen Präfix hinzugefügt. Weiterhin gibt es Informationen, welche pluginspezifischen Attribute eine Logik haben kann. Diese kann man ebenfalls setzen.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_parameter.jpg" alt="" width="1449" height="1194" class="alignnone size-full wp-image-2334" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_parameter.jpg 1449w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_parameter-300x247.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_parameter-768x633.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logiken_parameter-1024x844.jpg 1024w" sizes="(max-width: 1449px) 100vw, 1449px" /></p>
<p>Im Bereich &#8222;<strong>Scheduler</strong>&#8220; wird die bekannte Übersicht aller Scheduler dargestellt.<br />
<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/scheduler.jpg" alt="" width="1443" height="344" class="alignnone size-full wp-image-2335" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/scheduler.jpg 1443w, https://www.smarthomeng.de/wp-content/uploads/2019/05/scheduler-300x72.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/scheduler-768x183.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/scheduler-1024x244.jpg 1024w" sizes="(max-width: 1443px) 100vw, 1443px" /></p>
<p>Im Bereich &#8222;<strong>Plugins</strong>&#8220; findet sich die funktional etwas erweiterte Liste aller Plugins. Durch Klick auf jede Zeile, sieht man die Detailkonfiguration. Rechts finden sich u.A. Links auf die Webinterfaces des Plugins, ein Link zur Dokumentation und zum Support-Thread.<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins.jpg" alt="" width="1434" height="327" class="alignnone size-full wp-image-2336" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins.jpg 1434w, https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins-300x68.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins-768x175.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins-1024x234.jpg 1024w" sizes="(max-width: 1434px) 100vw, 1434px" /><br />
Über das Dropdown &#8222;Konfiguration&#8220;, kann man die Konfiguration der Plugins anpassen und Plugins neu hinzufügen &#8211; Derzeit ist danach noch ein Neustart von SmartHomeNG notwendig:<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins2.jpg" alt="" width="1450" height="563" class="alignnone size-full wp-image-2337" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins2.jpg 1450w, https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins2-300x116.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins2-768x298.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/plugins2-1024x398.jpg 1024w" sizes="(max-width: 1450px) 100vw, 1450px" /></p>
<p>Unter dem Menüpunkt &#8222;<strong>Szenen</strong>&#8220; wird die Liste aller Szenen angezeigt. Man kann hier auch die YAML Dateien der Szenen bearbeiten.<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/szenen.jpg" alt="" width="1260" height="559" class="alignnone size-full wp-image-2338" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/szenen.jpg 1260w, https://www.smarthomeng.de/wp-content/uploads/2019/05/szenen-300x133.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/szenen-768x341.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/szenen-1024x454.jpg 1024w" sizes="(max-width: 1260px) 100vw, 1260px" /></p>
<p>In &#8222;<strong>Threads</strong>&#8220; findet man die aus dem Backend bekannte Liste aller laufender Threads:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/threads.jpg" alt="" width="1262" height="484" class="alignnone size-full wp-image-2339" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/threads.jpg 1262w, https://www.smarthomeng.de/wp-content/uploads/2019/05/threads-300x115.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/threads-768x295.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/threads-1024x393.jpg 1024w" sizes="(max-width: 1262px) 100vw, 1262px" /></p>
<p>Auch der &#8222;<strong>Logs</strong>&#8220; Bereich wurde deutlich erweitert. Man findet hier primär die Liste aller (!) Logfiles, inklusive historischer Logfiles, die noch vorhanden sind. Insbesondere das Filtern wurde verbessert, da jetzt Exceptions im Block gefiltert werden können.<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/logs1.jpg" alt="" width="1445" height="512" class="alignnone size-full wp-image-2340" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/logs1.jpg 1445w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logs1-300x106.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logs1-768x272.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logs1-1024x363.jpg 1024w" sizes="(max-width: 1445px) 100vw, 1445px" /></p>
<p>Unter dem Dropdown &#8222;Liste der Logger&#8220; kann zur Laufzeit das Loglevel geändert werden.<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2019/05/logs2.jpg" alt="" width="1441" height="402" class="alignnone size-full wp-image-2341" srcset="https://www.smarthomeng.de/wp-content/uploads/2019/05/logs2.jpg 1441w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logs2-300x84.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logs2-768x214.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2019/05/logs2-1024x286.jpg 1024w" sizes="(max-width: 1441px) 100vw, 1441px" />Unter Konfiguration kann die logging.yaml bearbeitet werden (Neustart erforderlich!).</p>
<p>Neben all dem gibt es noch eine Reihe weiterer Neuerungen. Wir wünschen euch viel Spaß beim entdecken!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/smarthomeng-v1-6-das-neue-admin-interface/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS für das Alexa Plugin einrichten</title>
		<link>https://www.smarthomeng.de/aws-fuer-das-alexa-plugin-einrichten</link>
					<comments>https://www.smarthomeng.de/aws-fuer-das-alexa-plugin-einrichten#comments</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Thu, 29 Nov 2018 17:15:15 +0000</pubDate>
				<category><![CDATA[Plugins]]></category>
		<category><![CDATA[Alexa]]></category>
		<category><![CDATA[alexa4p3]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Lambda-Funktion]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=2207</guid>

					<description><![CDATA[Mit dem Alexa Plugin ist es möglich, SmartHomeNG Items über Alexa zu steuern. Um das Plugin einzurichten, müssen allerdings zuerst einige Dinge auf der Plattform Amazon Web Services (AWS) eingerichtet werden. Dieser Artikel ist Work in Progress und wird nach und nach die Alexa Doku im README des Plugins und<a class="moretag" href="https://www.smarthomeng.de/aws-fuer-das-alexa-plugin-einrichten"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Mit dem Alexa Plugin ist es möglich, SmartHomeNG Items über Alexa zu steuern. Um das Plugin einzurichten, müssen allerdings zuerst einige Dinge auf der Plattform Amazon Web Services (AWS) eingerichtet werden.</p>
<p>Dieser Artikel ist Work in Progress und wird nach und nach die Alexa Doku im <a href="https://github.com/smarthomeNG/plugins/tree/master/alexa" target="_blank" rel="noopener">README des Plugins</a> und im KNX-User-Forum (<a href="https://knx-user-forum.de/forum/supportforen/smarthome-py/1021150-amazon-alexa-plugin" target="_blank" rel="noopener">https://knx-user-forum.de/forum/supportforen/smarthome-py/1021150-amazon-alexa-plugin</a>) ablösen. Ein Teil der Originalanleitung war zudem unter <a href="https://developer.amazon.com/de/blogs/post/Tx4WG410EHXIYQ/Five-Steps-Before-Developing-a-Smart-Home-Skill" target="_blank" rel="noopener">https://developer.amazon.com/de/blogs/post/Tx4WG410EHXIYQ/Five-Steps-Before-Developing-a-Smart-Home-Skill</a> dokumentiert.</p>
<p>Zuerst muss unter <a href="https://developer.amazon.com/home.html" target="_blank" rel="noopener">https://developer.amazon.com/home.html</a> ein Entwicklerzugang erstellt werden. Trotz später notwendiger Angabe einer Kreditkarte kann davon ausgegangen werden, dass die anfallenden Request bei normaler Nutzung deutlich unterhalb der Grenze für die &#8222;Free Tier&#8220; bleiben werden. Trotzdem sollte man gelegentlich im Billing nachsehen. Auch ist zu empfehlen, den Zugang als 2-Factor-Authentifizierung einzurichten, anstatt das auch im &#8222;Shopping&#8220; Amazon verwendete Passwort alleine zu verwenden!</p>
<h1>OAuth2 Credentials anlegen</h1>
<p>Über <a href="https://developer.amazon.com/home.html" target="_blank" rel="noopener">https://developer.amazon.com/home.html</a> &#8211;&gt; &#8222;Login with Amazon&#8220; muss zuerst via &#8222;Create a New Security Profile&#8220; ein neues Security Profile erzeugt werden.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/11/lws.jpg" alt="" width="1377" height="315" class="alignnone size-full wp-image-2219" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/11/lws.jpg 1377w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lws-300x69.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lws-768x176.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lws-1024x234.jpg 1024w" sizes="(max-width: 1377px) 100vw, 1377px" /></p>
<p>Hier sind ein Name, eine Beschreibung und ein Link auf eine &#8222;privacy.htm&#8220; Seite (bspw. via Dyndns &#8211; die Seite muss meines Wissens nicht erreichbar sein) anzugeben.</p>
<p>Über den Reiter Web Settings kann man nun noch seine Client ID und seinen Client Secret einsehen, die für den nächsten Schritt notiert werden müssen. Im unten stehenden Screenshot sind die Informationen entfernt worden:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/11/lws_clientdaten.png" alt="" width="1381" height="629" class="alignnone size-full wp-image-2220" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/11/lws_clientdaten.png 1381w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lws_clientdaten-300x137.png 300w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lws_clientdaten-768x350.png 768w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lws_clientdaten-1024x466.png 1024w" sizes="(max-width: 1381px) 100vw, 1381px" /></p>
<h1>Alexa Skill anlegen</h1>
<p>Als Nächstes muss im Alexa Entwicklerportal ein neuer Skill angelegt werden. Hierzu unter <a href="https://developer.amazon.com/alexa/console/ask" target="_blank" rel="noopener">https://developer.amazon.com/alexa/console/ask</a> &#8222;Create Skill&#8220; auswählen.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_developer_overview.jpg" alt="" width="1304" height="601" class="alignnone size-full wp-image-2212" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_developer_overview.jpg 1304w, https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_developer_overview-300x138.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_developer_overview-768x354.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_developer_overview-1024x472.jpg 1024w" sizes="(max-width: 1304px) 100vw, 1304px" /></p>
<p>Hier ist die korrekte geographische Region als Default Endpoint zu wählen (in meinem Fall eu-west bzw. Europe/India). Als Payload ist für die alten Version des Plugins v2 zu wählen, für die aktuell in Entwicklung befindliche neue (derzeit noch nicht im DEV Branch, dafür unter <a href="https://github.com/Andrek01/Alexa4PayloadV3" target="_blank" rel="noopener">https://github.com/Andrek01/Alexa4PayloadV3</a>) v3.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_skill.png" alt="" width="1369" height="1044" class="alignnone size-full wp-image-2213" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_skill.png 1369w, https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_skill-300x229.png 300w, https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_skill-768x586.png 768w, https://www.smarthomeng.de/wp-content/uploads/2018/11/alexa_skill-1024x781.png 1024w" sizes="(max-width: 1369px) 100vw, 1369px" /></p>
<p><strong>Wichtig:</strong> Das was neben &#8222;amzn1.ask.skill.&#8220; bei &#8222;<span>Your Skill ID&#8220; steht, kopieren. Diese UUID wird später für den nächsten Schritt (Lambda-Funktion) benötigt!</span></p>
<p>Unter Account Linking sind nun die OAuth2 Daten zu hinterlegen, die im vorherigen Schritt generiert wurden:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/11/account_linking-1.png" alt="" width="1384" height="1278" class="alignnone size-full wp-image-2216" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/11/account_linking-1.png 1384w, https://www.smarthomeng.de/wp-content/uploads/2018/11/account_linking-1-300x277.png 300w, https://www.smarthomeng.de/wp-content/uploads/2018/11/account_linking-1-768x709.png 768w, https://www.smarthomeng.de/wp-content/uploads/2018/11/account_linking-1-1024x946.png 1024w" sizes="(max-width: 1384px) 100vw, 1384px" /></p>
<h1>Lambda-Funktion anlegen.</h1>
<p>Um Aktionen gegen SmartHomeNG ausführen zu können, muss über <a href="https://aws.amazon.com" target="_blank" rel="noopener">https://aws.amazon.com</a> (und dann in die AWS Management Console gehen) eine sog. Lambda-Funktion angelegt werden (Link Compute -&gt; Lambda).</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda.jpg" alt="" width="1157" height="681" class="alignnone size-full wp-image-2246" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda.jpg 1157w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda-300x177.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda-768x452.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda-1024x603.jpg 1024w" sizes="(max-width: 1157px) 100vw, 1157px" /></p>
<p>Legt man diese über &#8222;Funktion erstellen&#8220; an, müssen ein Name (bspw. SmartHomeNGFunction), eine Laufzeitumgebung (Node.js 8.10) und eine Vorhandene Rollen (lambda_basic_execution) gewählt werden.</p>
<p>Der neu erstellten Lambda-Funktion wird nun ein Alexa Smart Home Skill zugeordnet. Klickt man diesen unter &#8222;Auslöser hinzufügen&#8220; an, so gibt man nun die Anwendungs-ID (die UUID aus dem vorherigen Schritt) ein! Der Auslöser wird als aktiviert gesetzt!</p>
<p>Das Ganze ist in unten stehendem Screenshot zu sehen.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda-1.png" alt="" width="1348" height="3065" class="alignnone size-full wp-image-2251" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda-1.png 1348w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda-1-132x300.png 132w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda-1-768x1746.png 768w, https://www.smarthomeng.de/wp-content/uploads/2018/11/lambda-1-450x1024.png 450w" sizes="(max-width: 1348px) 100vw, 1348px" />Als Code muss folgendes eingetragen werden:</p>
<pre><code class="language-javascript">
/*
You need to specify the following environmental variables in the lambda function:
- SMARTHOME_HOST
		foobar.dyndns.tld
- SMARTHOME_PORT
		443 - endpoint must be https enabled!
- SMARTHOME_PATH
		'/'
- SMARTHOME_AUTH
		'user:password'
*/
exports.handler = function(event, context, callback) {
	var data = JSON.stringify(event)

	var options = {
		hostname: process.env.SMARTHOME_HOST,
		port: process.env.SMARTHOME_PORT,
		path: process.env.SMARTHOME_PATH,
		method: 'POST',
		auth: process.env.SMARTHOME_AUTH,
		headers: {
			'Content-Type': 'application/json',
			'Content-Length': Buffer.byteLength(data)
		}
	};

	var https = require('https');
	var req = https.request(options, (res) =&gt; {
		console.log(`HTTP ${res.statusCode}`);
		res.setEncoding('utf8');

		var responseData = '';
		res.on('data', (dataChunk) =&gt; {
		    responseData += dataChunk
		});
		res.on('end', () =&gt; {
			console.log('raw response:', responseData)

			var response = JSON.parse(responseData);
			if (res.statusCode == 200) {
				console.info('OK', JSON.stringify(response))
				callback(null, response);
			} else {
				console.error('Failed', JSON.stringify(response))
				callback('DependentServiceUnavailableError');
			}
		});
	});
	req.on('error', (e) =&gt; {
		console.error('request failed', e);
		callback(e);
	});

	console.log('requesting', data)
	req.write(data);
	req.end();
}
</code></pre>
<p>Unterhalb des Codeblocks sind nun noch die vier Variablen SMARTHOME_AUTH (im Stil &lt;user&gt;:&lt;passwort&gt;), SMARTHOME_HOST, SMARTHOME_PATH und SMARTHOME_PORT zu definieren. Wird ein ReverseProxy mit HTTPS verwendet (siehe bspw. <a href="https://www.smarthomeng.de/nginx-als-reverseproxy">im Artikel NGINX als ReverseProxy</a>), so sollte der User und das Passwort dort als Basic Authentication gesetzt werden. Der ReverseProxy muss in Folge dann eine Weiterleitung auf das SmartHomeNG Alexa Plugin (SmartHomeNG URL und Alexa Port) definieren. Port wäre in diesem Fall 443 (https) und URL die Dyndns URL über die das Heimnetzwerk erreichbar ist.</p>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/aws-fuer-das-alexa-plugin-einrichten/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Google Maps Widget für SmartVISU 2.9</title>
		<link>https://www.smarthomeng.de/google-maps-widget-fuer-smartvisu-2-9</link>
					<comments>https://www.smarthomeng.de/google-maps-widget-fuer-smartvisu-2-9#respond</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Sat, 18 Aug 2018 14:52:28 +0000</pubDate>
				<category><![CDATA[Beispiel-Implementierungen]]></category>
		<category><![CDATA[Plugins]]></category>
		<category><![CDATA[egigeozone]]></category>
		<category><![CDATA[Google Maps]]></category>
		<category><![CDATA[smartVISU]]></category>
		<category><![CDATA[smartVISU 2.9]]></category>
		<category><![CDATA[Traffic Plugin]]></category>
		<category><![CDATA[Widget]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=1995</guid>

					<description><![CDATA[Auch in der Visualisierung kann es von Nutzen sein, seine aktuelle Position und ggf. eine Fahrtroute auf einer Google Maps Karte zu visualisieren. SmartHomeNG bietet eine Reihe an Plugins und Möglichkeiten, um die jeweiligen Geokoordinaten zu erfassen (EgiGeoZone mit Network oder Webservices Plugin) und die Fahrzeit zu berechnen (Traffic Plugin).<a class="moretag" href="https://www.smarthomeng.de/google-maps-widget-fuer-smartvisu-2-9"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Auch in der Visualisierung kann es von Nutzen sein, seine aktuelle Position und ggf. eine Fahrtroute auf einer Google Maps Karte zu visualisieren. SmartHomeNG bietet eine Reihe an Plugins und Möglichkeiten, um die jeweiligen Geokoordinaten zu erfassen (EgiGeoZone mit Network oder Webservices Plugin) und die Fahrzeit zu berechnen (Traffic Plugin). Die Anzeige dieser Daten muss jedoch in der Visualisierung geschehen. <span id="more-1995"></span><br />
Aus dieser Motivation heraus habe ich ein Google Maps Widget für die SmartVISU geschrieben, welches mir meine aktuelle Position, meine Heimkoordinaten und den Weg zur Arbeit bzw. den Weg von der aktuellen Position nach Hause anzeigt.</p>
<p>In <a href="https://www.smarthomeng.de/geozonen-basierte-services-mit-der-egigeozone-app-und-dem-network-plugin" target="_blank" rel="noopener">https://www.smarthomeng.de/geozonen-basierte-services-mit-der-egigeozone-app-und-dem-network-plugin</a> wurde beschrieben, wie man Positionsdaten in SmartHomeNG übertragen kann. In <a href="https://www.smarthomeng.de/das-traffic-plugin-am-beispiel-eines-staualarms" target="_blank" rel="noopener">https://www.smarthomeng.de/das-traffic-plugin-am-beispiel-eines-staualarms</a> wurde beschrieben, wie das Traffic Plugin (Google Directions) in SmartHomeNG eingebunden werden kann. Items aus beiden Artikeln werden für das Plugin benötigt!</p>
<p><strong>Tipp:</strong> Am Ende des Artikels findet sich noch eine einfachere Version, die nur die aktuelle Position benötigt (und anzeigt)!</p>
<h1>Items</h1>
<p>Basis bilden ein Teil bzw. eine Erweiterung der Items aus dem EgiGeoZone und den Traffic Plugin Artikel. Bitte auf diese Artikel referenzieren, wie die Items belegt werden können.</p>
<pre><code class="language-yaml">
%YAML 1.1
---
location:

	home:
		lat:
			type: str
			value: 48.263530
			cache: 'yes'

		lon:
			type: str
			value: 11.443952
			cache: 'yes'
	
	work:

		lat:
			type: str
			value: 48.143158
			cache: 'yes'

		lon:
			type: str
			value: 11.547755
			cache: 'yes'
			
	lat:
		type: str
		cache: 'yes'

	lon:
		type: str
		cache: 'yes'

travel_info:
       
        calculate_way_home:
                type: bool
                cache: 'yes'

        calculate_way_work:
                type: bool
                cache: 'yes'

        travel_summary:
                type: str
 
</code></pre>
<p>Neben den jeweiligen GPS Koordinaten der aktuellen Position (<code>location.lat, location.lon</code>), der Home-Koordinate (<code>location.home.lat, location.home.lon</code>) und der Koordinate der Arbeitsstelle (<code>location.work.lat, location.work.lon</code>), sind hier auch zwei boolsche Items, die angeben, ob jeweils der Weg nach Hause oder in die Arbeit angezeigt werden soll (<code>travel_info.calculate_way_home</code> und <code>travel_info.calculate_way_work</code>) Für die Anzeige darf jeweils nur eines dieser Items <code>True</code> sein. Näheres ist in <a href="https://www.smarthomeng.de/das-traffic-plugin-am-beispiel-eines-staualarms" target="_blank" rel="noopener">https://www.smarthomeng.de/das-traffic-plugin-am-beispiel-eines-staualarms</a> beschrieben.</p>
<p><strong>Tipp:</strong> Man kann natürlich auch die &#8222;aktuelle&#8220; Koordinate <code>location.lat, location.lon</code> fest im Item hinterlegen, ohne dieses via EgiGeoZone zu bedaten. Diese Koordinate ist, so lange keine Route angezeigt wird, das Zentrum der Karte!</p>
<h1>Google Maps Widget</h1>
<p>Für das Widget sind im Ordner <code>dropins/widgets</code> zwei Dateien zu erstellen. Im Javascript File wird dabei die Karte initialisiert und falls angegeben, die Verkehrsanzeige eingeschaltet. Daneben wird ein Marker auf die Home Koordinate und die aktuelle Position gesetzt. Letzterer ist mit einem Foto hinterlegt.</p>
<p>Im Update-Block werden die jeweiligen Positionen aktualisiert und die aktuelle Route gesetzt (falls eine Route aktuell angezeigt werden soll).</p>
<p>Im JS-File muss bedacht werden, dass der im Rahmen des Traffic Plugins eingerichtete API Key für die Google Directions API angegeben werden muss (<code>&lt;apikey&gt</code>)! Zudem kann als Title noch der jeweilige Name <code>&lt;Mein Name&gt;</code> gesetzt werden.</p>
<p><strong><code>dropins/widgets/gmaps.js</code>:</strong></p>
<pre><code class="language-javascript">
// dropins/widgets/gmaps.js
$.widget("sv.gmaps_map", $.sv.widget, {
    initSelector: 'div[data-widget="gmaps_map"]',

    options: {
        'traffic': null,
    },

    _create: function () {
        this._super();
        this._create_map();
    },

    _create_map: function () {
        try {
            this.map = new google.maps.Map(this.element[0], {
                zoom: 11,
                disableDefaultUI: false,
                clickableIcons: false,
                mapTypeControl: false,
                streetViewControl: false
            });
        }
        catch(e) {
            if(e.name == "ReferenceError") { // google maps script not loaded yet
                var that = this;
                // google maps script is already loading in another widget
                if(window.google_maps_loading) { 
                    window.setTimeout(function() { that._create_map() }, 100)
                    return;
                }
                // google maps script is not loading
                window.google_maps_loading = true;
                $.ajax({
                    url: 'https://maps.googleapis.com/maps/api/js?key=&lt;apikey&gt;',
                    dataType: "script",
                    complete: function() { window.google_maps_loading = false; that._create_map() }
                });
                return;
            }
            else  // other exceptions should be trown
                throw e;
        }

        this.directionsService = new google.maps.DirectionsService;
        this.directionsDisplay = new google.maps.DirectionsRenderer();
        this.directionsDisplay.setMap(this.map);

        if (this.options['traffic']) {
            var trafficLayer = new google.maps.TrafficLayer();
            trafficLayer.setMap(this.map);
        }

        var pinColorGreen = "20d24a";
        var pinImageGreen = new google.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|" + pinColorGreen,
            new google.maps.Size(21, 34),
            new google.maps.Point(0,0),
            new google.maps.Point(10, 34));
        var pinImageMyself = new google.maps.MarkerImage("/smartVISU/pics/phone/myself.jpg" ,
            null, null, null, new google.maps.Size(40, 40));
        var pinShadow = new google.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_shadow",
            new google.maps.Size(40, 37),
            new google.maps.Point(0, 0),
            new google.maps.Point(12, 35));

        this.marker_home = new google.maps.Marker({
            map: this.map,
            icon: pinImageGreen,
            shadow: pinShadow,
            title:' Home '
        });

        this.marker_myself = new google.maps.Marker({
            map: this.map,
            icon: pinImageMyself,
            title:' &lt;Mein Name&gt;',
            zIndex:99999999
        });
    },

    _update: function(response) {

        if(!this.map) {
            var that = this;
            window.setTimeout(function() { that._update(response) }, 100)
            return;
        }

        this.map.setCenter({lat: response[0], lng: response[1]});
        var pos = new google.maps.LatLng(parseFloat(response[0]),parseFloat(response[1]));
        this.marker_myself.setPosition(pos);

        var pos_home = new google.maps.LatLng(parseFloat(response[2]),parseFloat(response[3]));
        this.marker_home.setPosition(pos_home);
        if (response[6] && response[7] && (response[4] == 1 || response[5] == 1)) {
            if (response[4] == 1) {
                var destination = { lat: parseFloat(response[2]), lng: parseFloat(response[3]) };
            } else if (response[5] == 1) {
                var destination = { lat: parseFloat(response[6]), lng: parseFloat(response[7]) };
            };
            var directionsDisplay = this.directionsDisplay;
            this.directionsDisplay.setMap(this.map);
            this.directionsService.route({
                origin: pos,
                //provideRouteAlternatives: true,
                destination: destination,
                travelMode: google.maps.TravelMode.DRIVING
            }, function(result, status) {
                if (status === google.maps.DirectionsStatus.OK) {
                    directionsDisplay.setDirections(result);
                } else {
                    console.log('Directions request failed due to ' + status);
                }
            });
        } else {
            if(this.directionsDisplay != null) {
                this.directionsDisplay.setMap(null);
            }
        }
    }
})
</code></pre>
<p>&nbsp;</p>
<p>und <strong><code>dropins/widgets/gmaps.html</code>:</strong></p>
<pre><code class="language-twig">
// dropins/widgets/gmaps.html - API Key für Google Directions angeben!
/**
 * Displays a google maps map with current, home and work position, as well as routing
 *
 * @param unique id for this widget
 * @param a gad/item for latitude
 * @param a gad/item for longitude
 * @param a gad/item for home latitude
 * @param a gad/item for home longitude
 * @param a gad/item for bool flag, if route to home shall be calculated
 * @param a gad/item for bool flag, if route to work shall be calculated
 * @param a gad/item for work latitude
 * @param a gad/item for work longitude
 * @param traffic information on (=1) or off (=0)
 */
{% macro map(id, gad_lat, gad_lon, gad_home_lat, gad_home_lon, gad_calculate_way_home, gad_calculate_way_work, gad_work_lat, gad_work_lon, traffic) %}
    &lt;div id="{{ uid(page, id) }}" style="width: 100%; height: 400px;" data-traffic="{{ traffic }}" data-widget="gmaps_map" data-item="{{ gad_lat }}, {{ gad_lon}}, {{ gad_home_lat }}, {{ gad_home_lon }}, {{ gad_calculate_way_home }}, {{ gad_calculate_way_work }}, {{ gad_work_lat }}, {{ gad_work_lon }}"&gt;&lt;/div&gt;
{% endmacro %}
</code></pre>
<p>Weiterhin wird ein Bild, welches unter <code>/smartVISU/pics/phone/myself.jpg</code> abgelegt ist, verwendet! Damit das Widget ordentlich funktioniert, sollte dieses Bild auch dort existieren.</p>
<h1>Einbindung in smartVISU 2.9</h1>
<p>Da die Widgets mit smartVISU 2.9 automatisch geladen werden, muss nun nur noch das Widget auf der entsprechenden Seite eingebunden werden:</p>
<pre><code class="language-twig">
&lt;div class="block" style="width: 100%;"&gt;
  &lt;div class="set-2" data-role="collapsible-set" data-theme="c" data-content-theme="a" data-mini="true"&gt;
    &lt;div data-role="collapsible" data-collapsed="false"&gt;
      &lt;h3&gt;Verkehr:
      {{ basic.symbol('travel_info.calculate_way_home', 'travel_info.calculate_way_home', 'Nach Hause via ', '', '1') }}
      {{ basic.symbol('travel_info.calculate_way_work', 'travel_info.calculate_way_work', 'Zur Arbeit via ', '', '1') }}
      {{ basic.print('travel_info.travel_summary', 'travel_info.travel_summary', 'text') }}
      &lt;/h3&gt;
      {{ gmaps.map('trafficmap', 'location.lat', 'location.lon', 'location.home.lat', 'location.home.lon', 'travel_info.calculate_way_home', 'travel_info.calculate_way_work', 'location.work.lat', 'location.work.lon', '1') }}
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;  
</code></pre>
<p>Das Ergebnis mit aktivem Arbeitsweg, sieht dann wie folgt aus:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/08/kartenansicht.jpg" alt="" width="830" height="472" class="alignnone size-full wp-image-2016" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/08/kartenansicht.jpg 830w, https://www.smarthomeng.de/wp-content/uploads/2018/08/kartenansicht-300x171.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2018/08/kartenansicht-768x437.jpg 768w" sizes="(max-width: 830px) 100vw, 830px" /></p>
<h1>Einfachere Version:</h1>
<p>Hier noch eine einfachere Version des Widgets, nur mit Koordinaten der aktuellen Position:</p>
<pre><code class="language-twig">
{{ gmaps.simple_map('trafficmap_simple', 'location.lat', 'location.lon') }}
</code></pre>
<pre><code class="language-twig">
/**
 * Displays a simple google maps map
 *
 * @param unique id for this widget
 * @param a gad/item for latitude
 * @param a gad/item for longitude
 */
{% macro simple_map(id, gad_lat, gad_lon) %}
&lt;div id="{{ uid(page, id) }}" style="width: 100%; height: 400px;" data-widget="gmaps_simplemap" data-item="{{ gad_lat }}, {{ gad_lon}}"&gt;&lt;/div&gt;
{% endmacro %}
</code></pre>
<pre><code class="language-javascript">
$.widget("sv.gmaps_simplemap", $.sv.widget, {
    initSelector: 'div[data-widget="gmaps_simplemap"]',

    _create: function () {
        this._super();
        this._create_map();
    },

    _create_map: function () {
        try {
            this.map = new google.maps.Map(this.element[0], {
                zoom: 11,
                disableDefaultUI: false,
                clickableIcons: false,
                mapTypeControl: false,
                streetViewControl: false
            });
        }
        catch (e) {
            if (e.name == "ReferenceError") { // google maps script not loaded yet
                var that = this;
                // google maps script is already loading in another widget
                if (window.google_maps_loading) {
                    window.setTimeout(function () {
                        that._create_map()
                    }, 100)
                    return;
                }
                // google maps script is not loading
                window.google_maps_loading = true;
                $.ajax({
                    url: 'https://maps.googleapis.com/maps/api/js?key=&lt;apikey&gt;',
                    dataType: "script",
                    complete: function () {
                        window.google_maps_loading = false;
                        that._create_map()
                    }
                });
                return;
            }
            else  // other exceptions should be thrown
                throw e;
        }

        var pinColorGreen = "20d24a";
        var pinImageGreen = new google.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|" + pinColorGreen,
            new google.maps.Size(21, 34),
            new google.maps.Point(0,0),
            new google.maps.Point(10, 34));
        this.marker_myself = new google.maps.Marker({
            map: this.map,
            icon: pinImageGreen,
            title:' &lt;Mein Name&gt;',
            zIndex:99999999
        });
    },

    _update: function(response) {
        if(!this.map) {
            var that = this;
            window.setTimeout(function() { that._update(response) }, 100)
            return;
        }

        this.map.setCenter({lat: response[0], lng: response[1]});
        var pos = new google.maps.LatLng(parseFloat(response[0]),parseFloat(response[1]));
        this.marker_myself.setPosition(pos);
    }
});</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/google-maps-widget-fuer-smartvisu-2-9/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>E-Paper Wetterstation mit Daten aus SmartHomeNG</title>
		<link>https://www.smarthomeng.de/epaper-wetterstation-mit-daten-aus-smarthomeng</link>
					<comments>https://www.smarthomeng.de/epaper-wetterstation-mit-daten-aus-smarthomeng#comments</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Sun, 22 Jul 2018 09:28:47 +0000</pubDate>
				<category><![CDATA[Beispiel-Implementierungen]]></category>
		<category><![CDATA[Plugins]]></category>
		<category><![CDATA[Arduino IDE]]></category>
		<category><![CDATA[EPaper]]></category>
		<category><![CDATA[ESP32]]></category>
		<category><![CDATA[Waveshare]]></category>
		<category><![CDATA[Wetterstation]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=1929</guid>

					<description><![CDATA[E-Paper Displays, wie sie etwa im Amazon Kindle zum Einsatz kommen, stellen eine hervoragende und energiesparende Möglichkeit dar, Daten von SmartHomeNG zu visualisieren. Im folgenden Artikel soll als Beispiel eine Wetterstation auf Basis eines ESP32 und eines Waveshare 4.2 Zoll E-Paper Display mit 400 x 300 Pixel Auflösung erstellt werden.<a class="moretag" href="https://www.smarthomeng.de/epaper-wetterstation-mit-daten-aus-smarthomeng"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>E-Paper Displays, wie sie etwa im Amazon Kindle zum Einsatz kommen, stellen eine hervoragende und energiesparende Möglichkeit dar, Daten von SmartHomeNG zu visualisieren.</p>
<p>Im folgenden Artikel soll als Beispiel eine Wetterstation auf Basis eines ESP32 und eines Waveshare 4.2 Zoll E-Paper Display mit 400 x 300 Pixel Auflösung erstellt werden.</p>
<p>Das Display kann bspw. via <a href="https://www.amazon.de/Module-Resolution-Electronic-Controller-Communicating/dp/B074NR1SW2" target="_blank" rel="noopener">Amazon</a> bezogen werden. Der verwendete ESP32 kann <a href="https://www.amazon.de/Espressif-ESP32-DEVKITC-Genuine-ESP-WROOM-32-Marked/dp/B07DZP27TH/ref=sr_1_5?s=computers&amp;ie=UTF8&amp;qid=1532247493&amp;sr=1-5&amp;keywords=espressif+esp32" target="_blank" rel="noopener">hier</a> gefunden werden. Als Bibliothek zum Rendern des Bildes kommt <a href="https://github.com/ZinggJM/GxEPD2" target="_blank" rel="noopener">GxEPD2</a> zum Einsatz.  Vorab muss allerdings gesagt werden, dass die Fonts der Bibliothek nur Standard ASCII Zeichen könne, also weder ein Grad Zeichen (°) noch deutsche Sonderzeichen. Als Icons wurden Teile aus <a href="http://adamwhitcroft.com/climacons/" target="_blank" rel="noopener">http://adamwhitcroft.com/climacons/</a> und den SmartVISU Icons verwendet, die via <a href="http://www.digole.com/tools/PicturetoC_Hex_converter.php" target="_blank" rel="noopener">http://www.digole.com/tools/PicturetoC_Hex_converter.php</a> von einem PNG in C-Arrays konvertiert wurden.</p>
<p>Der Artikel erhebt keinen Anspruch auf vollständige Korrektheit und Schönheit des C Codes und dient lediglich als Proof of Concept. Auch sind die Icons derzeit noch nicht vollständig. Der Artikel wird dazu sukzessive aktualisiert.</p>
<p>Für die Items wurden sowohl Systemdaten aus SHNG direkt, dem KNX Plugin, dem ETA PU Plugin, dem SMA EM, als auch des DarkSky Plugins verwendet, die via Webservices Plugin (siehe Tutorial <a href="https://www.smarthomeng.de/das-smarthomeng-webservices-plugin" target="_blank" rel="noopener">https://www.smarthomeng.de/das-smarthomeng-webservices-plugin</a>) verfügbar gemacht wurden. Teilweise wurden die Rohdaten der Plugins über Child-Items noch formatiert oder in Strings (bspw. beim UV Wert) umgerechnet.</p>
<h1>Verkabelung</h1>
<p>Dem Display ist ein Kabel beigelegt, das wie folgt an den ESP32 angeschlossen wird:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/07/Kabel_Waveshare.jpg" alt="" width="733" height="638" class="alignnone size-full wp-image-1935" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/07/Kabel_Waveshare.jpg 733w, https://www.smarthomeng.de/wp-content/uploads/2018/07/Kabel_Waveshare-300x261.jpg 300w" sizes="(max-width: 733px) 100vw, 733px" /></p>
<table border="1" style="border-collapse: collapse; width: 100%;">
<tbody>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><strong>Farbe</strong></td>
<td style="width: 50%; height: 29px;"><strong>Anschluß</strong></td>
</tr>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><strong><span style="color: #ff0000;">rot</span></strong></td>
<td style="width: 50%; height: 29px;">3v3</td>
</tr>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><strong>schwarz</strong></td>
<td style="width: 50%; height: 29px;">GND</td>
</tr>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><strong><span style="color: #0000ff;">blau</span></strong></td>
<td style="width: 50%; height: 29px;">IO23</td>
</tr>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><span style="color: #e3eb15;"><strong>gelb</strong></span></td>
<td style="width: 50%; height: 29px;">IO18</td>
</tr>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><span style="color: #ff9900;"><strong>orange</strong></span></td>
<td style="width: 50%; height: 29px;">IO17</td>
</tr>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><span style="color: #339966;"><strong>grün</strong></span></td>
<td style="width: 50%; height: 29px;">IO16</td>
</tr>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><span style="color: #d6d6d6;"><strong>weiß</strong></span></td>
<td style="width: 50%; height: 29px;">IO5</td>
</tr>
<tr style="height: 29px;">
<td style="width: 50%; height: 29px;"><span style="color: #800080;"><strong>lila</strong></span></td>
<td style="width: 50%; height: 29px;">IO19</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<h1>Items (via Webservices Plugin)</h1>
<p>Folgende Items werden als Itemset vom Webservices Plugin als JSON bereitgestellt:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/07/epaper_json.jpg" alt="" width="685" height="786" class="alignnone size-full wp-image-1936" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/07/epaper_json.jpg 685w, https://www.smarthomeng.de/wp-content/uploads/2018/07/epaper_json-261x300.jpg 261w" sizes="(max-width: 685px) 100vw, 685px" /></p>
<h1>Code</h1>
<p>Das Programm wurde mit der Arduino IDE geschrieben und auf den ESP32 übertragen. Wie oben bereits erwähnt, müssen dazu die notwendigen Bibliotheken vorhanden sein. Wie der ESP32 in der Arduino IDE eingerichtet werden kann, kann <a href="https://www.smarthomeng.de/entfernungsmessung-auf-basis-eines-esp32-und-smarthomeng" target="_blank" rel="noopener">https://www.smarthomeng.de/entfernungsmessung-auf-basis-eines-esp32-und-smarthomeng</a> entnommen werden.</p>
<p>In der Methode <code>void setup(void)</code> werden zunächst das Display und die WLAN Verbindung initialisiert.<br />
In <code>void loop()</code> findet dann der Datenzugriff und das Rendering statt. Nach erfolgreicher Verbindung zu SmartHomeNG wird zuerst der HTTP Header herausgefiltert. Sobald in der Response eine geschweifte Klammer erkannt wird, werden die JSON Daten in ein Char Array (<code>inData</code>) eingelesen. Ist dies vollendet, wird die Verbindung getrennt und der JSON Datensatz geparsed (<code>JsonObject&amp; root = jsonBuffer.parseObject(inData);</code>). Bei Erfolg, werden die Daten mit <code>printJsonData(JsonObject&amp; root)</code> auf dem Display ausgegeben. Dafür werden sie aus den JSON Daten extrahiert und ggf. noch formatiert.</p>
<p>Im &#8222;do &#8230; while&#8220; Block findet die konkrete Ausgabe statt. <code>display.setCursor</code> setzt den Textcursor an bestimmte Positionen. Danach wird Text ausgegeben. Die Methode <code>void display_icon(int x, int y, String icon_name)</code> gibt an entsprechenden Positionen das Icon aus.<br />
Die Methoden <code>void dashedRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)</code> und <code>void drawDashedHLine(int16_t x, int16_t y, int16_t w, uint16_t color)</code> wurden von <a href="https://forum.arduino.cc/index.php?topic=487007.225" target="_blank" rel="noopener">https://forum.arduino.cc/index.php?topic=487007.225</a> übernommen und zeichnen Linien oder Rechtecke.</p>
<pre><code class="language-c">
#include &lt;WiFi.h&gt;
#include &lt;WiFiServer.h&gt;
#include &lt;WiFiClient.h&gt;
#include &lt;WiFiUdp.h&gt;
#include &lt;JsonParser.h&gt;
#include &lt;ArduinoJson.h&gt;
#include &lt;GxEPD2_BW.h&gt;
#include &lt;GxEPD2_3C.h&gt;
#define EPD_CS 17 //SS
#include &lt;Fonts/FreeMonoBold9pt7b.h&gt;
#include &lt;Fonts/FreeMonoBold12pt7b.h&gt;
#include &lt;Fonts/FreeMonoBold18pt7b.h&gt;

// Define each of the *icons for display
const unsigned char gate_open[] PROGMEM = {
0x00,0x00,0x1f,0xff,0xff,0xe0,0x00,0x00,0x00,0x3f,0xff,0xff,0xf0,0x00,0x00,0x00,0x70,0x00,0x00,0x38,0x00,0x00,0x00,0x70,0x00,0x00,0x38,0x00
,0x00,0x00,0x60,0x00,0x00,0x18,0x00,0x00,0x00,0x60,0x00,0x00,0x18,0x00,0x00,0x00,0xe0,0x00,0x00,0x1c,0x00,0x00,0x00,0xe0,0x00,0x00,0x1c,0x00
,0x00,0x00,0xe0,0x00,0x00,0x1c,0x00,0x00,0x00,0xff,0xff,0xff,0xfc,0x00,0x00,0x03,0xff,0xff,0xff,0xfe,0x00,0x00,0x03,0x03,0xc0,0x0f,0x03,0x00
,0x00,0x02,0x03,0xff,0xff,0x01,0x00,0x00,0x02,0x03,0xff,0xff,0x01,0x00,0x00,0x03,0x07,0x80,0x07,0x83,0x00,0x00,0x03,0xff,0xff,0xff,0xff,0x00
,0x00,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x01,0xff,0xff,0xff,0xfe,0x00,0x00,0x01,0xf8,0x00,0x00,0x7c,0x00
,0x00,0x01,0xf8,0x00,0x00,0x7c,0x00,0x00,0x01,0xf8,0x00,0x00,0x7c,0x00,0x00,0x00,0xf0,0x00,0x00,0x7c,0x00,0x1c,0x00,0x00,0x00,0x00,0x00,0x00
,0x1c,0x00,0x00,0x00,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xf8,0x00,0x00,0x00,0x00,0x80,0xff,0xf8,0x00,0x00,0x00,0x00,0xc0
,0xff,0x38,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0
,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0
,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0
,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0
,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xdf,0x18,0x00,0x00,0x00,0x00,0xc0,0xff,0xf8,0x00,0x00,0x00,0x00,0xc0,0xff,0xf8,0x00,0x00,0x00,0x00,0xc0
,0xff,0xf8,0x00,0x00,0x00,0x00,0xc0,0x1f,0xff,0xff,0xff,0xff,0xff,0xc0
};

const unsigned char gate_closed[] PROGMEM = {
0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x01,0xff,0xff,0xff,0x80,0x00,0x03,0x80,0x00,0x01,0xc0,0x00,0x03,0x80,0x00,0x01,0xc0
,0x00,0x03,0x00,0x00,0x00,0xc0,0x00,0x03,0x00,0x00,0x00,0xc0,0x00,0x07,0x00,0x00,0x00,0xe0,0x00,0x07,0x00,0x00,0x00,0xe0
,0x00,0x07,0x00,0x00,0x00,0xe0,0x00,0x07,0xff,0xff,0xff,0xe0,0x00,0x0f,0xff,0xff,0xff,0xf0,0x00,0x18,0x1e,0x00,0x78,0x18
,0x00,0x10,0x1f,0xff,0xf8,0x08,0x00,0x10,0x1f,0xff,0xf8,0x08,0x00,0x18,0x3c,0x00,0x3c,0x18,0x00,0x1f,0xff,0xff,0xff,0xf8
,0x00,0x1f,0xff,0xff,0xff,0xf8,0x00,0x1f,0xff,0xff,0xff,0xf8,0x00,0x0f,0xff,0xff,0xff,0xf0,0x00,0x0f,0xc0,0x00,0x03,0xe0
,0x00,0x0f,0xc0,0x00,0x03,0xe0,0x00,0x0f,0xc0,0x00,0x03,0xe0,0x00,0x07,0x80,0x00,0x03,0xe0,0x60,0x00,0x00,0x00,0x00,0x00
,0xe0,0x00,0x00,0x00,0x00,0x00,0xe0,0x00,0x00,0x00,0x00,0x00,0xe7,0xff,0xff,0xff,0xff,0xf4,0xef,0xff,0xff,0xff,0xff,0xf6
,0xee,0x71,0xce,0x71,0xce,0x76,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36
,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36
,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36
,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36
,0xec,0x31,0x8e,0x31,0x8e,0x36,0xec,0x31,0x8e,0x31,0x8e,0x36,0xef,0xff,0xff,0xff,0xff,0xf6,0xef,0xff,0xff,0xff,0xff,0xf6
,0xe7,0xff,0xff,0xff,0xff,0xf6,0x7f,0xff,0xff,0xff,0xff,0xfe
};

const unsigned char light_off[] PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0x00,0x00,0x00
,0x00,0x00,0x7e,0x1f,0x80,0x00,0x00,0x00,0x00,0xf0,0x03,0xc0,0x00,0x00,0x00,0x01,0xe0,0x01,0xe0,0x00,0x00,0x00,0x03,0xc0,0x00,0xf0,0x00,0x00
,0x00,0x03,0x80,0x00,0x70,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00
,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00
,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x03,0x80,0x00,0x70,0x00,0x00,0x00,0x03,0x80,0x00,0x70,0x00,0x00
,0x00,0x01,0xc0,0x00,0xe0,0x00,0x00,0x00,0x01,0xc0,0x00,0xe0,0x00,0x00,0x00,0x00,0xe0,0x01,0xc0,0x00,0x00,0x00,0x00,0xf0,0x03,0xc0,0x00,0x00
,0x00,0x00,0x70,0x03,0x80,0x00,0x00,0x00,0x00,0x38,0x07,0x00,0x00,0x00,0x00,0x00,0x38,0x07,0x00,0x00,0x00,0x00,0x00,0x38,0x07,0x00,0x00,0x00
,0x00,0x00,0x1c,0x0e,0x00,0x00,0x00,0x00,0x00,0x1c,0x0e,0x00,0x00,0x00,0x00,0x00,0x08,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xf0,0x00,0x00,0x00
,0x00,0x00,0x03,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x00,0x00,0x00
};

const unsigned char light_on[] PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x00,0x00,0x00,0x00,0x03,0x00,0xe0,0x30,0x00,0x00
,0x00,0x03,0x80,0xe0,0x70,0x00,0x00,0x00,0x03,0x80,0xe0,0x70,0x00,0x00,0x00,0x01,0xc0,0x00,0xe0,0x00,0x00,0x00,0x01,0xc0,0x00,0xe0,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x20,0x00,0x03,0xc0,0x0f,0xfc,0x00,0xf0,0x00,0x03,0xe0,0x3f,0xff,0x01,0xf0,0x00
,0x01,0xf0,0xff,0x3f,0xc3,0xe0,0x00,0x00,0x61,0xf0,0x03,0xe1,0x80,0x00,0x00,0x01,0xe0,0x01,0xe0,0x00,0x00,0x00,0x03,0xc0,0x00,0xf0,0x00,0x00
,0x00,0x03,0x80,0x00,0x70,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x08,0x87,0x00,0x00,0x38,0x44,0x00
,0x1f,0x87,0x00,0x00,0x38,0x7e,0x00,0x1f,0x87,0x00,0x00,0x38,0x7e,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00
,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x07,0x00,0x00,0x38,0x00,0x00,0x00,0x03,0x80,0x00,0x70,0x00,0x00,0x00,0x03,0x80,0x00,0x70,0x00,0x00
,0x00,0xe1,0xc0,0x00,0xe1,0x80,0x00,0x03,0xe1,0xe0,0x01,0xe1,0xf0,0x00,0x07,0xe0,0xe0,0x01,0xc1,0xf8,0x00,0x03,0x80,0xf0,0x03,0xc0,0x70,0x00
,0x00,0x00,0x70,0x03,0x80,0x00,0x00,0x00,0x00,0x78,0x07,0x80,0x00,0x00,0x00,0x00,0x38,0x07,0x00,0x00,0x00,0x00,0x00,0x38,0x07,0x00,0x00,0x00
,0x00,0x00,0x1c,0x0e,0x00,0x00,0x00,0x00,0x00,0x1c,0x0e,0x00,0x00,0x00,0x00,0x00,0x18,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xf0,0x00,0x00,0x00
,0x00,0x00,0x03,0xf0,0x00,0x00,0x00,0x00,0x00,0x01,0xc0,0x00,0x00,0x00
};

const unsigned char moon_0_8[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0xff,0xf8,0x3f,0xff,0xfc,0x3f,0xff,0xfc,0x7f,0xff,0xfe
,0x7f,0xff,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0xff,0xfe
,0x7f,0xff,0xfe,0x3f,0xff,0xfc,0x3f,0xff,0xfc,0x1f,0xff,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char moon_1[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0xe1,0xf8,0x3f,0xf0,0x7c,0x3f,0xfc,0x3c,0x7f,0xfc,0x1e
,0x7f,0xfe,0x1e,0xff,0xfe,0x0f,0xff,0xff,0x0f,0xff,0xff,0x0f,0xff,0xff,0x0f,0xff,0xff,0x0f,0xff,0xfe,0x0f,0x7f,0xfe,0x1e
,0x7f,0xfc,0x1e,0x3f,0xfc,0x3c,0x3f,0xf0,0x7c,0x1f,0xe1,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char moon_2[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0xe1,0xf8,0x3f,0xf0,0x7c,0x3f,0xfc,0x3c,0x7f,0xfc,0x1e
,0x7f,0xfe,0x1e,0xff,0xfe,0x0f,0xff,0xff,0x0f,0xff,0xff,0x0f,0xff,0xff,0x0f,0xff,0xff,0x0f,0xff,0xfe,0x0f,0x7f,0xfe,0x1e
,0x7f,0xfc,0x1e,0x3f,0xfc,0x3c,0x3f,0xf0,0x7c,0x1f,0xe1,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char moon_3[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0xf1,0xf8,0x3f,0xf0,0x7c,0x3f,0xf0,0x3c,0x7f,0xf0,0x1e
,0x7f,0xf0,0x1e,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0x7f,0xf0,0x1e
,0x7f,0xf0,0x1e,0x3f,0xf0,0x3c,0x3f,0xf0,0x7c,0x1f,0xf1,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char moon_4[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0xc1,0xf8,0x3f,0xc0,0x7c,0x3f,0x80,0x3c,0x7f,0x00,0x1e
,0x7f,0x00,0x1e,0xff,0x00,0x0f,0xff,0x00,0x0f,0xff,0x00,0x0f,0xff,0x00,0x0f,0xff,0x00,0x0f,0xff,0x00,0x0f,0x7f,0x00,0x1e
,0x7f,0x00,0x1e,0x3f,0x80,0x3c,0x3f,0xc0,0x7c,0x1f,0xc1,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char moon_5[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0x81,0xf8,0x3e,0x00,0x7c,0x3c,0x00,0x3c,0x78,0x00,0x1e
,0x78,0x00,0x1e,0xf0,0x00,0x0f,0xf0,0x00,0x0f,0xf0,0x00,0x0f,0xf0,0x00,0x0f,0xf0,0x00,0x0f,0xf0,0x00,0x0f,0x78,0x00,0x1e
,0x78,0x00,0x1e,0x3c,0x00,0x3c,0x3e,0x00,0x7c,0x1f,0x81,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char moon_6[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0x8f,0xf8,0x3e,0x0f,0xfc,0x3c,0x0f,0xfc,0x78,0x0f,0xfe
,0x78,0x0f,0xfe,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0xf0,0x0f,0xff,0x78,0x0f,0xfe
,0x78,0x0f,0xfe,0x3c,0x0f,0xfc,0x3e,0x0f,0xfc,0x1f,0x8f,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char moon_7[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0x87,0xf8,0x3e,0x0f,0xfc,0x3c,0x3f,0xfc,0x78,0x3f,0xfe
,0x78,0x7f,0xfe,0xf0,0x7f,0xff,0xf0,0xff,0xff,0xf0,0xff,0xff,0xf0,0xff,0xff,0xf0,0xff,0xff,0xf0,0x7f,0xff,0x78,0x7f,0xfe
,0x78,0x3f,0xfe,0x3c,0x3f,0xfc,0x3e,0x0f,0xfc,0x1f,0x87,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char moon_8[] PROGMEM = {
0x00,0x3c,0x00,0x01,0xff,0x80,0x07,0xff,0xe0,0x0f,0xff,0xf0,0x1f,0x87,0xf8,0x3e,0x0f,0xfc,0x3c,0x3f,0xfc,0x78,0x3f,0xfe
,0x78,0x7f,0xfe,0xf0,0x7f,0xff,0xf0,0xff,0xff,0xf0,0xff,0xff,0xf0,0xff,0xff,0xf0,0xff,0xff,0xf0,0x7f,0xff,0x78,0x7f,0xfe
,0x78,0x3f,0xfe,0x3c,0x3f,0xfc,0x3e,0x0f,0xfc,0x1f,0x87,0xf8,0x0f,0xff,0xf0,0x07,0xff,0xe0,0x01,0xff,0x80,0x00,0x3c,0x00
};

const unsigned char pressure[] PROGMEM = {
0x00, 0x00, 0x84, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x84, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x4c, 0x88, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x88, 0x88, 0x80, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x40, 0x00, 0x88, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x11, 0x08, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x01, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x30, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xc0, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x30, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x01, 0x80, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x04, 0x18, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x01, 0x00
, 0x00, 0x00, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x18, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x80, 0x00, 0x01, 0x80
, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x03, 0xc0
, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x80
, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00
, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00
, 0x00, 0x08, 0x00, 0x00, 0x03, 0x80, 0x3f, 0x80, 0x00, 0x00, 0x10, 0x00, 0x00, 0x03, 0x80, 0x3f, 0xe0, 0x00
, 0x00, 0x20, 0x00, 0x00, 0x03, 0x80, 0x30, 0xe0, 0x00, 0x3c, 0xc0, 0x00, 0x00, 0x03, 0xfc, 0x30, 0xe7, 0xf0
, 0xff, 0x80, 0x00, 0x00, 0x03, 0xfc, 0x30, 0xe3, 0xf0, 0xe3, 0x80, 0x00, 0x00, 0x03, 0x8e, 0x3b, 0xe0, 0x38
, 0x01, 0x80, 0x00, 0x00, 0x03, 0x8e, 0x3f, 0xc1, 0xf8, 0x01, 0x80, 0x00, 0x00, 0x03, 0x8e, 0x3f, 0x07, 0xf8
, 0x01, 0x80, 0x00, 0x00, 0x03, 0x8e, 0x30, 0x0e, 0x38, 0x01, 0x80, 0x00, 0x00, 0x03, 0x8e, 0x30, 0x0e, 0x38
, 0x03, 0x80, 0x00, 0x00, 0x03, 0x8e, 0x30, 0x07, 0xf8, 0x01, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x30, 0x03, 0xf0
};

const unsigned char sunrise_sunset[] PROGMEM = {
0x00,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x03,0xc0,0x00,0x00,0x00,0x00,0x03,0xc0,0x00,0x00,0x00,0x00,0x03,0xc0,0x00,0x00
,0x00,0x00,0x03,0xc0,0x00,0x00,0x00,0x00,0x03,0xc0,0x00,0x00,0x00,0x00,0x03,0xc0,0x00,0x00,0x01,0xc0,0x01,0x80,0x03,0x80
,0x01,0xe0,0x00,0x00,0x07,0x80,0x01,0xf0,0x00,0x00,0x0f,0x80,0x00,0xf8,0x00,0x00,0x1f,0x00,0x00,0x78,0x00,0x00,0x1e,0x00
,0x00,0x38,0x03,0xc0,0x1c,0x00,0x00,0x00,0x1f,0xf8,0x00,0x00,0x00,0x00,0x7f,0xfe,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00
,0x00,0x01,0xf8,0x1f,0x80,0x00,0x00,0x03,0xe0,0x07,0xc0,0x00,0x00,0x03,0xc0,0x03,0xc0,0x00,0x00,0x07,0x80,0x01,0xe0,0x00
,0x00,0x07,0x80,0x01,0xe0,0x00,0x00,0x0f,0x00,0x00,0xf0,0x00,0x7e,0x0f,0x00,0x00,0xf0,0x7e,0xff,0x0f,0x00,0x00,0xf0,0xff
,0xff,0x0f,0x00,0x00,0xf0,0xff,0x7e,0x0f,0x00,0x00,0xf0,0x7e,0x00,0x07,0x00,0x00,0xe0,0x00,0x00,0x07,0x80,0x01,0xe0,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x07,0xff,0xff,0xe0,0x00,0x00,0x0f,0xff,0xff,0xf0,0x00,0x00,0x0f,0xff,0xff,0xf0,0x00,0x00,0x07,0xff,0xff,0xe0,0x00
};

const unsigned char clear_day[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00
, 0x00, 0x38, 0x00, 0x30, 0x00, 0x70, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0xf0, 0x00
, 0x00, 0x3e, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x03, 0xe0, 0x00
, 0x00, 0x0f, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0x00, 0x78, 0x03, 0x80, 0x00
, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x00
, 0x00, 0x00, 0x1f, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x03, 0xf0, 0x00, 0x00
, 0x00, 0x00, 0x7c, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x78, 0x00, 0x00
, 0x00, 0x00, 0xf0, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x3c, 0x00, 0x00
, 0x00, 0x01, 0xe0, 0x00, 0x1e, 0x00, 0x00, 0x0f, 0xc1, 0xe0, 0x00, 0x1e, 0x0f, 0xc0
, 0x1f, 0xe1, 0xe0, 0x00, 0x1e, 0x1f, 0xe0, 0x1f, 0xe1, 0xe0, 0x00, 0x1e, 0x1f, 0xe0
, 0x0f, 0xc1, 0xe0, 0x00, 0x1e, 0x0f, 0xc0, 0x00, 0x01, 0xe0, 0x00, 0x1e, 0x00, 0x00
, 0x00, 0x00, 0xf0, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x3c, 0x00, 0x00
, 0x00, 0x00, 0x78, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0xf8, 0x00, 0x00
, 0x00, 0x00, 0x3f, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xe0, 0x00, 0x00
, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00
, 0x00, 0x07, 0x00, 0x78, 0x03, 0x80, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x03, 0xc0, 0x00
, 0x00, 0x1f, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x01, 0xf0, 0x00
, 0x00, 0x3c, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x38, 0x00, 0x30, 0x00, 0x70, 0x00
, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

const unsigned char temperature[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xcf, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x87, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x07, 0xb7, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xb7, 0xc0, 0x00, 0x00
, 0x00, 0x00, 0x0f, 0x33, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x79, 0xe0, 0x00, 0x00
, 0x00, 0x00, 0x1e, 0xfd, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0xe0, 0x00, 0x00
, 0x00, 0x00, 0x1e, 0x79, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x33, 0xc0, 0x00, 0x00
, 0x00, 0x00, 0x0f, 0x87, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0x80, 0x00, 0x00
, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

const unsigned char prob_rain[] PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x00,0x00,0x07,0xff,0xc0,0x00,0x00
,0x00,0x00,0x3f,0xff,0xf0,0x00,0x00,0x00,0x00,0x7f,0xff,0xfc,0x00,0x00,0x00,0x01,0xff,0xff,0xfe,0x00,0x00,0x00,0x03,0xff,0xff,0xff,0x00,0x00
,0x00,0x07,0xff,0xff,0xff,0x80,0x00,0x00,0x07,0xff,0xff,0xff,0xc0,0x00,0x00,0x0f,0xff,0xff,0xff,0xe0,0x00,0x00,0x0f,0xff,0xff,0xff,0xe0,0x00
,0x00,0x00,0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00
,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00
,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x40,0x00,0x00,0x00,0x00,0x00,0x38,0xe0,0x00,0x00,0x00,0x00,0x00,0x38,0xe0,0x00,0x00
,0x00,0x00,0x00,0x1f,0xc0,0x00,0x00,0x00,0x00,0x00,0x1f,0x80,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

const unsigned char barometer[] PROGMEM = {
0x00,0x1f,0xe0,0x00,0x00,0x7f,0xf8,0x00,0x01,0xff,0xfe,0x00,0x03,0xf0,0x3f,0x00,0x0f,0x80,0x07,0xc0,0x0f,0x00,0x03,0xc0,0x1e,0x00,0x01,0xe0,0x3c,0x00,0x00,0xf0
,0x38,0x00,0x3c,0x70,0x70,0x00,0x3c,0x38,0x70,0x00,0x7c,0x38,0xf0,0x00,0xfc,0x3c,0xe0,0x07,0xf0,0x1c,0xe0,0x0f,0xe0,0x1c,0xe0,0x0f,0xc0,0x1c,0xe0,0x0f,0xc0,0x1c
,0xe0,0x1f,0x80,0x1c,0xe0,0x3f,0x80,0x1c,0xf3,0xfc,0x00,0x3c,0x71,0xf8,0x00,0x38,0x70,0xf0,0x00,0x38,0x38,0x70,0x00,0x70,0x3c,0x30,0x00,0xf0,0x1e,0x10,0x01,0xe0
,0x0f,0x00,0x03,0xc0,0x0f,0x80,0x07,0xc0,0x03,0xf0,0x3f,0x00,0x01,0xff,0xfe,0x00,0x00,0x7f,0xf8,0x00,0x00,0x1f,0xe0,0x00
};

const unsigned char wind_speed[] PROGMEM = {
0x00,0x00,0x00,0x00,0x01,0xfc,0x00,0x00,0x03,0x3f,0xf0,0x00,0x85,0x3e,0x1f,0x80,0xd9,0x3e,0x1f,0x38,0xf1,0x3e,0x0f,0x0c,0xc1,0x3e,0x0f,0x0c,0xc1,0x3e,0x0f,0x0c
,0xf1,0x3e,0x0f,0x0c,0xd9,0x3e,0x0f,0x18,0xc5,0x3e,0x1f,0xc0,0xc3,0x3f,0xf0,0x00,0xc1,0xfe,0x00,0x00,0xc0,0x80,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00
,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00
,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

const unsigned char rain[] PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00
,0x00,0x01,0xff,0xe0,0x00,0x00,0x00,0x00,0x07,0xff,0xf8,0x00,0x00,0x00,0x00,0x0f,0xff,0xfc,0x00,0x00,0x00,0x00,0x3f,0x80,0x7f,0x00,0x00,0x00
,0x00,0x7e,0x00,0x1f,0x80,0x00,0x00,0x00,0x78,0x00,0x07,0x80,0x00,0x00,0x00,0xf0,0x00,0x03,0xc0,0x00,0x00,0x01,0xe0,0x00,0x01,0xec,0x00,0x00
,0x01,0xe0,0x00,0x01,0xff,0xc0,0x00,0x03,0xc0,0x00,0x00,0xff,0xf0,0x00,0x03,0xc0,0x00,0x00,0xff,0xf8,0x00,0x03,0x80,0x00,0x00,0x40,0x7c,0x00
,0x03,0x80,0x00,0x00,0x00,0x3e,0x00,0x07,0x80,0x00,0x00,0x00,0x1e,0x00,0x07,0x80,0x00,0x00,0x00,0x0f,0x00,0x07,0x80,0x00,0x00,0x00,0x07,0x00
,0x07,0x80,0x00,0x00,0x00,0x07,0x00,0x03,0x80,0x00,0x00,0x00,0x07,0x80,0x03,0x80,0x00,0x00,0x00,0x07,0x80,0x03,0xc0,0x00,0x00,0x00,0x07,0x80
,0x03,0xc0,0x78,0x00,0x78,0x07,0x80,0x01,0xe0,0x78,0x00,0x78,0x07,0x00,0x01,0xe0,0x78,0x00,0x78,0x0f,0x00,0x00,0xf0,0x78,0x00,0x78,0x0f,0x00
,0x00,0x78,0x78,0x78,0x78,0x1e,0x00,0x00,0x78,0x78,0x78,0x78,0x3e,0x00,0x00,0x38,0x30,0x78,0x30,0x7c,0x00,0x00,0x08,0x00,0x78,0x00,0x78,0x00
,0x00,0x00,0x00,0x78,0x00,0x70,0x00,0x00,0x00,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x78,0x00,0x78,0x00,0x00,0x00,0x00,0x78,0x00,0x78,0x00,0x00,0x00,0x00,0x78,0x00,0x78,0x00,0x00,0x00,0x00,0x78,0x00,0x78,0x00,0x00
,0x00,0x00,0x78,0x78,0x78,0x00,0x00,0x00,0x00,0x78,0x78,0x78,0x00,0x00,0x00,0x00,0x30,0x78,0x30,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00
,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

const unsigned char partly_cloudy_day[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x06, 0x00, 0x0e, 0x00
, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x3e, 0x00
, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x78, 0x00
, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x00
, 0x00, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x00
, 0x00, 0x07, 0xe7, 0xe0, 0x7e, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x80, 0x1f, 0x00, 0x00
, 0x00, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x07, 0x80, 0x00
, 0x07, 0xf0, 0x0f, 0xe0, 0x07, 0x80, 0x00, 0x0f, 0xc0, 0x03, 0xf0, 0x03, 0xc0, 0x00
, 0x1f, 0x00, 0x00, 0xf8, 0x03, 0xc1, 0xf8, 0x1e, 0x00, 0x00, 0x78, 0x03, 0xc3, 0xfc
, 0x3c, 0x00, 0x00, 0x3f, 0xc3, 0xc3, 0xfc, 0x3c, 0x00, 0x00, 0x3f, 0xfb, 0xc1, 0xf8
, 0x78, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x78, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x00
, 0x70, 0x00, 0x00, 0x08, 0x1f, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00
, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00
, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00
, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00
, 0x78, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00
, 0x3c, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00
, 0x1e, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00
, 0x0f, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x00
, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00
, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, 0x00, 0x00
};

GxEPD2_BW&lt;GxEPD2_420, GxEPD2_420::HEIGHT&gt; display(GxEPD2_420(/*CS=D8*/ EPD_CS, /*DC=D3*/ 16, /*RST=D4*/ 5, /*BUSY=D2*/ 19));

char ssid[] = "xxxxxx"; // Your network SSID (name)
char password[] = "xxxxxxxx"; // Your network key
char host[] = "192.168.178.100"; // IP of SmartHomeNG
int port = 4321;

WiFiClient client;
JsonParser&lt;32&gt; parser;
String currentLine = "";
String currRates = "";
boolean readingJson = false;
#define BUFFERSIZE 5800
static char inData[BUFFERSIZE + 1]; // string + terminator

void setup(void) {
  Serial.begin(115200);
  initialise_wifi();
  display.init(115200);
}

void loop() {
  connect_to_server();
  Serial.println("new loop");
  if (client.connected())
  {
    Serial.println("[isConnected]");
    int index = 0;
    while (!client.available()) {}
    while (client.available()) {
      char inChar = client.read();
      if (inChar == '{') {
        readingJson = true;
      }
      if (readingJson) {
        if (index &lt; BUFFERSIZE - 1)
        {
          inData[index] = inChar; // Store it
          index++;
        }
        if (inChar == '}') {
          readingJson = false;
          inData[index] = '\0';
        }
      }
    }
    client.stop();
    Serial.println("[Disconnected]");
     
    // JSON Daten parsen
    StaticJsonBuffer&lt;5800&gt; jsonBuffer;
    JsonObject&amp; root = jsonBuffer.parseObject(inData);
    if (!root.success())
    {
      Serial.print("parseObject(");
      Serial.print(inData);
      Serial.println(") failed");
    } else {
      Serial.println("[Parse Success]");
      printJsonData(root);
    }
  }
  delay(1000 * 60 * 10); // 10 Minuten warten
}

void printJsonData(JsonObject&amp; root)
{
  int dateSubstringStart = 11;
  int dateSubstringEnd = 16;
  double ozone = root["weather.darksky.home.currently.ozone"];
  String uvIndex = root["weather.darksky.home.currently.uvIndex"];
  String uvIndexStr = root["weather.darksky.home.currently.uvIndex.string"];
  uvIndexStr.replace("u00e4", "ae");
  uvIndexStr.replace("u00df", "ss");
  String curTime = root["weather.darksky.home.currently.time.timeStr"];
  String curDate = root["weather.darksky.home.currently.time.dateStr"];
  double outsideHumidity = root["weather.darksky.home.currently.humidity"];
  double surplus = root["sma.smaem.surplus.kw"];
  double regard = root["sma.smaem.regard.kw"];
  double precipProb = root["weather.darksky.home.currently.precipProbability"];
  double pressure = root["weather.darksky.home.currently.pressure"];
  String currentIcon = root["weather.darksky.home.currently.icon"];
  String moonLight = root["env.location.moonlight"];
  String knx_light_status = root["knx.light_status"];
  String gate_state = root["knx.outside.gate.driveway.state"];
  String moonRise = root["env.location.moonrise"];
  moonRise = moonRise.substring(dateSubstringStart, dateSubstringEnd);
  String moonSet = root["env.location.moonset"];
  moonSet = moonSet.substring(dateSubstringStart, dateSubstringEnd);
  String moonPhase = root["env.location.moonphase"];
  String sunSet = root["env.location.sunset"];
  sunSet = sunSet.substring(dateSubstringStart, dateSubstringEnd);;
  String sunRise = root["env.location.sunrise"];
  sunRise = sunRise.substring(dateSubstringStart, dateSubstringEnd);
  String temperature = root["eta_unit.temperature_outside.value"];
  String temperatureMin = root["eta_unit.temperature_outside.value.min_24"];
  String temperatureMax = root["eta_unit.temperature_outside.value.max_24"];
  String currentlySummary = root["weather.darksky.home.currently.summary"];
  String dailySummary = root["weather.darksky.home.daily.summary"];
  double windSpeed = root["weather.darksky.home.currently.windSpeed"];
  dailySummary.replace("u00b0C", " Grad");
  dailySummary.replace("u00e4", "ae");
  display.setRotation(2);
  display.setFont(&amp;FreeMonoBold18pt7b);
  display.setTextColor(GxEPD_BLACK);
  display.setFullWindow();
  display.firstPage();

  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setFont(&amp;FreeMonoBold9pt7b);

    display.setCursor(0, 11); display.println(curTime);
    display.setCursor(150, 11); display.println("Mein Ort");
    display.setCursor(290, 11); display.println(curDate);
    drawDashedHLine(0, 20, 420, GxEPD_BLACK);

    display_icon(330, 25, currentIcon);
    display.setCursor(270, 42);
    if (surplus &gt; 0) {
      display.println(surplus);
    } else {
      display.println((-1)*regard);
    }
    display.setCursor(270, 62); display.println("kW");
    display.setCursor(270, 100); display.println("UV: "+uvIndex);
    display.setCursor(270, 115); display.println(uvIndexStr);
    display.setCursor(270, 140); display.print("Ozon: "); display.println((int)ozone, DEC);

    display.setFont(&amp;FreeMonoBold12pt7b);
    dashedRect(0,25,260,125, GxEPD_BLACK);
    display_icon(2, 25, "temperature");
    display.setCursor(50, 47); display.println(temperature+" Grad");
    display.setCursor(50, 67); display.print(outsideHumidity*100,0); display.println("%");
    display_icon(2, 65, "prob_rain");
    display.setCursor(50, 98); display.print(precipProb*100,0); display.println("%");
    display_icon(100, 76, "wind_speed");
    display.setCursor(140, 98); display.print((int)windSpeed, DEC); display.println("km/h");

    //display.setFont(&amp;FreeMonoBold9pt7b);
    display_icon(15, 113, "barometer");
    display.setCursor(50, 135); display.print((int)pressure, DEC); display.println("hPa");

    display.setFont(&amp;FreeMonoBold9pt7b);
    drawDashedHLine(0, 155, 420, GxEPD_BLACK);
    display.setCursor(0, 170); display.println(dailySummary);
    drawDashedHLine(0, 220, 420, GxEPD_BLACK);
    display.setFont(&amp;FreeMonoBold9pt7b);
    display_icon(0, 255, "sunrise_sunset");
    display.setCursor(60, 275); display.println(sunRise);
    display.setCursor(60, 289); display.println(sunSet);
    display.setFont(&amp;FreeMonoBold18pt7b);
    display_icon(137, 265, "moon_"+moonPhase);
    display.setFont(&amp;FreeMonoBold9pt7b);
    display.setCursor(170, 275); display.println(moonRise);
    display.setCursor(170, 289); display.println(moonSet);
    display.setCursor(260, 242); display.println("Tor:");
    
    if (gate_state.equals("false")) {
      display_icon(257, 249, "gate_closed");
    } else {
      display_icon(257, 249, "gate_open");
    }
    display.setCursor(325, 242); display.println("Licht:");
    if (knx_light_status.equals("false")) {
      display_icon(330, 247, "light_off");
    } else {
      display_icon(330, 247, "light_on");
    }
  }
  while (display.nextPage());
}

void drawDashedHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
  display.startWrite();
  writeDashedLine(x, y, x + w - 1, y, color);
  display.endWrite();
}

#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif

void writeDashedLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) {
  int16_t steep = abs(y1 - y0) &gt; abs(x1 - x0);
  bool hole = false; //make it dash by using a hole every second pixel
  if (steep) {
    _swap_int16_t(x0, y0);
    _swap_int16_t(x1, y1);
  }

  if (x0 &gt; x1) {
    _swap_int16_t(x0, x1);
    _swap_int16_t(y0, y1);
  }

  int16_t dx, dy;
  dx = x1 - x0;
  dy = abs(y1 - y0);

  int16_t err = dx / 2;
  int16_t ystep;

  if (y0 &lt; y1) {
    ystep = 1;
  }
  else {
    ystep = -1;
  }

  for (; x0 &lt;= x1; x0++) {
    if (steep) {
      if (!hole) display.writePixel(y0, x0, color);
      hole = !hole;
    } else {
      if (!hole) display.writePixel(x0, y0, color);
      hole = !hole;
    }
    err -= dy;
    if (err &lt; 0) {
      y0 += ystep;
      err += dx;
    }
  }
}

void dashedRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
  display.startWrite();
  writeDashedLine(x, y, x + w - 1, y, color);
  writeDashedLine(x, y, x, y + h - 1, color);
  writeDashedLine(x + w - 1, y, x + w - 1, y + h - 1, color);
  writeDashedLine(x, y + h - 1, x + w - 1, y + h - 1, color);
  display.endWrite();
}

void display_icon(int x, int y, String icon_name) {
  int scale = 10; // Adjust size as necessary
  if (icon_name == "partly-cloudy-day")
    display.drawBitmap(x, y, partly_cloudy_day, 54, 50, GxEPD_BLACK);
  else if (icon_name == "clear-day")
    display.drawBitmap(x, y, clear_day, 54, 50, GxEPD_BLACK);
  else if (icon_name == "rain")
    display.drawBitmap(x, y, rain, 54, 50, GxEPD_BLACK);
  else if (icon_name == "sunrise_sunset")
    display.drawBitmap(x, y, sunrise_sunset, 48, 36, GxEPD_BLACK);
  else if (icon_name == "prob_rain")
    display.drawBitmap(x, y, prob_rain, 54, 50, GxEPD_BLACK);
  else if (icon_name == "temperature")
    display.drawBitmap(x, y, temperature, 54, 50, GxEPD_BLACK);
  else if (icon_name == "pressure")
    display.drawBitmap(x, y, pressure, 69, 64, GxEPD_BLACK);
  else if (icon_name == "moon_0")
    display.drawBitmap(x, y, moon_0_8, 24, 24, GxEPD_BLACK);
  else if (icon_name == "moon_1")
    display.drawBitmap(x, y, moon_2, 24, 24, GxEPD_BLACK);
  else if (icon_name == "moon_2")
    display.drawBitmap(x, y, moon_3, 24, 24, GxEPD_BLACK);
  else if (icon_name == "moon_3")
    display.drawBitmap(x, y, moon_4, 24, 24, GxEPD_BLACK);
  else if (icon_name == "moon_4")
    display.drawBitmap(x, y, moon_5, 24, 24, GxEPD_BLACK);
  else if (icon_name == "moon_5")
    display.drawBitmap(x, y, moon_6, 24, 24, GxEPD_BLACK);
  else if (icon_name == "moon_6")
    display.drawBitmap(x, y, moon_7, 24, 24, GxEPD_BLACK);
  else if (icon_name == "moon_7")
    display.drawBitmap(x, y, moon_8, 24, 24, GxEPD_BLACK);
  else if (icon_name == "moon_8")
    display.drawBitmap(x, y, moon_0_8, 24, 24, GxEPD_BLACK);
  else if (icon_name == "barometer")
    display.drawBitmap(x, y, barometer, 30, 30, GxEPD_BLACK);
  else if (icon_name == "wind_speed")
    display.drawBitmap(x, y, wind_speed, 30, 30, GxEPD_BLACK);
  else if (icon_name == "light_on")
    display.drawBitmap(x, y, light_on, 50, 50, GxEPD_BLACK);
  else if (icon_name == "light_off")
    display.drawBitmap(x, y, light_off, 50, 50, GxEPD_BLACK);
  else if (icon_name == "gate_open")
    display.drawBitmap(x, y, gate_open, 50, 50, GxEPD_BLACK);
  else if (icon_name == "gate_closed")
    display.drawBitmap(x, y, gate_closed, 47, 50, GxEPD_BLACK);
}

// NETWORK STUFF

void connect_to_server() {
  if (client.connect(host, port)) {
    Serial.println("[Connected to server]");
    // HTTP Request gegen SmartHomeNG / das Webservices-Plugin durchführen
    client.println("GET /rest/itemset/weather/ HTTP/1.0");
    //client.println("Host: 192.168.178.100");
    client.println();
  }
  else
  {
    Serial.println("[Connection Failed!]");
    client.stop();
  }
}

static void initialise_wifi(void)
{
  // Versuche ins WiFi Netzwerk zu verbinden
  int connAttempts = 0;
  Serial.print(F("\r\nConnecting to: "));
  Serial.println(String(ssid));
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000); Serial.print(".");
  if (connAttempts &gt; 30) return;
    connAttempts++;
  }
  Serial.println("Connected to wifi");
  print_wifi_status();
}

void print_wifi_status() {
  // SSID des WiFi Netzwerkes ausgeben:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // WiFi IP Adresse des ESP32 ausgeben:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // WiFi Signalstärke ausgeben:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}
</code></pre>
<p>Lädt man das Programm nun auf den verkabelten ESP32 hoch, erscheint nach kurzer Zeit folgende Ausgabe. Nach 10 Minuten erfolgt ein automatischer Refresh:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/07/result_epaper.jpg" alt="" width="1082" height="770" class="alignnone size-full wp-image-1944" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/07/result_epaper.jpg 1082w, https://www.smarthomeng.de/wp-content/uploads/2018/07/result_epaper-300x213.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2018/07/result_epaper-768x547.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2018/07/result_epaper-1024x729.jpg 1024w" sizes="(max-width: 1082px) 100vw, 1082px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/epaper-wetterstation-mit-daten-aus-smarthomeng/feed</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Jetzt wird es bunt! Wie man ein Web-Interface für ein Plugin schreibt (ab SmartHomeNG 1.5)</title>
		<link>https://www.smarthomeng.de/jetzt-wird-es-bunt-wie-man-ein-plugin-webinterface-schreibt-in-kuerze-ab-smarthomeng-1-5</link>
					<comments>https://www.smarthomeng.de/jetzt-wird-es-bunt-wie-man-ein-plugin-webinterface-schreibt-in-kuerze-ab-smarthomeng-1-5#respond</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Tue, 26 Jun 2018 17:00:46 +0000</pubDate>
				<category><![CDATA[Beispiel-Implementierungen]]></category>
		<category><![CDATA[Plugins]]></category>
		<category><![CDATA[Bootstrap]]></category>
		<category><![CDATA[CherryPy]]></category>
		<category><![CDATA[Jinja2]]></category>
		<category><![CDATA[SmartHomeNG 1.5]]></category>
		<category><![CDATA[WebInterface]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=1740</guid>

					<description><![CDATA[Um SmartHomeNG einsteigerfreundlicher zu machen, bieten wir ab Version 1.5 die Möglichkeit, Plugins mit eigenen Web-Interfaces (ähnlich dem Backend Plugin) zu versehen. Web-Interface bezeichnet hierbei eine kleine Webseite, die Infos zum Plugin ausgibt oder die Bedienung und Konfiguration vereinfacht. Existierende Web-Interfaces von Plugins, lassen sich in der Plugin-Liste des Backend-Plugins<a class="moretag" href="https://www.smarthomeng.de/jetzt-wird-es-bunt-wie-man-ein-plugin-webinterface-schreibt-in-kuerze-ab-smarthomeng-1-5"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Um SmartHomeNG einsteigerfreundlicher zu machen, bieten wir ab Version 1.5 die Möglichkeit, Plugins mit eigenen Web-Interfaces (ähnlich dem Backend Plugin) zu versehen. Web-Interface bezeichnet hierbei eine kleine Webseite, die Infos zum Plugin ausgibt oder die Bedienung und Konfiguration vereinfacht.</p>
<p><span id="more-1740"></span></p>
<p>Existierende Web-Interfaces von Plugins, lassen sich in der Plugin-Liste des Backend-Plugins finden (siehe Artikel <a href="https://www.smarthomeng.de/das-backend-plugin" target="_blank" rel="noopener"><em>Das Backend-Plugin</em></a>). Ein Web-Interface ist instanzspezifisch, die URL wird dementsprechend aufgebaut.</p>
<p>Die Web-Interfaces des AVM, KNX, Wundergrund und Webservices Plugins sind nur einige Beispiele:<br />
<a href="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_1.jpg"><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_1-150x150.jpg" alt="" width="150" height="150" class="alignnone wp-image-1834 size-thumbnail" /></a> <a href="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_2.jpg"><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_2-150x150.jpg" alt="" width="150" height="150" class="alignnone wp-image-1835 size-thumbnail" /></a> <a href="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_3.jpg"><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_3-150x150.jpg" alt="" width="150" height="150" class="alignnone wp-image-1836 size-thumbnail" /></a> <a href="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_4.jpg"><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_4-150x150.jpg" alt="" width="150" height="150" class="alignnone wp-image-1837 size-thumbnail" /></a></p>
<p>Basis hierzu stellt die auf <a href="https://cherrypy.org" target="_blank" rel="noopener">CherryPy</a> (und <a href="http://jinja.pocoo.org/docs/2.10/" target="_blank" rel="noopener">Jinja2</a> als Template-Engine) basierende Implementierung im http module dar. Für detaillierte Infos zur Template Syntax wird auf die o.g. Doku von Jinja2 verwiesen. Dieser Artikel setzt Grundkenntnisse darin voraus.</p>
<h1>Frameworks</h1>
<p>SmartHomeNG bringt dabei für alle Plugins nutzbar eine ganze Reihe an Frameworks bereits mit. Diese finden derzeit vor allem im Backend Plugin ihren Einsatz.</p>
<p>Vorhanden sind per Default (Versionen Stand Release 1.5):</p>
<table border="1" style="border-collapse: collapse; width: 81.4293%;" height="457">
<tbody>
<tr style="height: 29px;">
<td style="width: 31.8412%; height: 29px;"><strong>Framework</strong></td>
<td style="width: 65.1885%; height: 29px;"><strong>Beschreibung / Link</strong></td>
</tr>
<tr style="height: 58px;">
<td style="width: 31.8412%; height: 58px;">Bootstrap 4.1.1</td>
<td style="width: 65.1885%; height: 58px;">Web GUI Framework<br />
<a href="http://getbootstrap.com/" target="_blank" rel="noopener">http://getbootstrap.com/</a></td>
</tr>
<tr style="height: 87px;">
<td style="width: 31.8412%; height: 87px;">Bootstrap Datepicker Widget 1.8.0</td>
<td style="width: 65.1885%; height: 87px;"><a href="https://github.com/uxsolutions/bootstrap-datepicker" target="_blank" rel="noopener">https://github.com/uxsolutions/bootstrap-datepicker</a></td>
</tr>
<tr>
<td style="width: 31.8412%;">Bootstrap Tree View<br />(selber angepasst auf Bootstrap 4)</td>
<td style="width: 65.1885%;"><a href="https://github.com/jonmiles/bootstrap-treeview" target="_blank" rel="noopener">https://github.com/jonmiles/bootstrap-treeview</a></td>
</tr>
<tr style="height: 58px;">
<td style="width: 31.8412%; height: 58px;">JQuery 3.3.1</td>
<td style="width: 65.1885%; height: 58px;">Javascript Framework<br />
<a href="https://jquery.org" target="_blank" rel="noopener">https://jquery.org</a></td>
</tr>
<tr style="height: 58px;">
<td style="width: 31.8412%; height: 58px;">Codemirror 5.39.0</td>
<td style="width: 65.1885%; height: 58px;">Online Code Editor<br />
<a href="https://codemirror.net/" target="_blank" rel="noopener">https://codemirror.net/</a></td>
</tr>
<tr style="height: 58px;">
<td style="width: 31.8412%; height: 58px;">Font Awesome 5.1.0</td>
<td style="width: 65.1885%; height: 58px;">Icon-Font für Webseiten<br />
<a href="http://fontawesome.com" target="_blank" rel="noopener">http://fontawesome.com</a></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>Abgelegt sind die Frameworks unter <code>/smarthome/modules/http/webif/gstatic</code> und können direkt in einer Webseite genutzt werden.</p>
<p>Am Beispiel Bootstrap sieht das wie folgt aus:</p>
<pre><code class="language-html">&lt;script src="/gstatic/bootstrap/js/bootstrap.min.js"&gt;&lt;/script&gt;</code></pre>
<h1>Beispielplugin mit Web-Interface</h1>
<p>Das jeweils aktuelle Beispiel für ein Plugin mit Web-Interface findet sich unter <code>/dev/sample_plugin_webif</code>.<br />
In der Methode init_webinterface (als Teil des Plugin-Codes in der Datei <code>__init__.py</code>) wird dabei das Web-Interface initialisiert:</p>
<pre><code class="language-python">
from lib.model.smartplugin import *
[...]
    def init_webinterface(self):
        """"
        Initialize the web interface for this plugin

        This method is only needed if the plugin is implementing a web interface
        """
        try:
            self.mod_http = Modules.get_instance().get_module('http')   # try/except to handle running in a core version that does not support modules
        except:
             self.mod_http = None
        if self.mod_http == None:
            self.logger.error("Plugin '{}': Not initializing the web interface".format(self.get_shortname()))
            return False
        
        # set application configuration for cherrypy
        webif_dir = self.path_join(self.get_plugin_dir(), 'webif')
        config = {
            '/': {
                'tools.staticdir.root': webif_dir,
            },
            '/static': {
                'tools.staticdir.on': True,
                'tools.staticdir.dir': 'static'
            }
        }
        
        # Register the web interface as a cherrypy app
        self.mod_http.register_webif(WebInterface(webif_dir, self), 
                                     self.get_shortname(), 
                                     config, 
                                     self.get_classname(), self.get_instance_name(),
                                     description='')
                                   
        return True
</code></pre>
<p>Diese Methode muss dann natürlich in der <code>def __init__</code> eingebunden werden:</p>
<pre><code class="language-python">
    def __init__(self, sh, *args, **kwargs):
        [...]
        if not self.init_webinterface():
            self._init_complete = False
        [...]</pre>
<p></code>Die eigentliche Implementierung befindet sich in einer eigenen Klasse, die ebenfalls in der <code>__init__.py</code> definiert wird:</p>
<pre><code class="language-python">
import cherrypy
from jinja2 import Environment, FileSystemLoader

class WebInterface(SmartPluginWebIf):


    def __init__(self, webif_dir, plugin):
        """
        Initialization of instance of class WebInterface
        
        :param webif_dir: directory where the webinterface of the plugin resides
        :param plugin: instance of the plugin
        :type webif_dir: str
        :type plugin: object
        """
        self.logger = logging.getLogger(__name__)
        self.webif_dir = webif_dir
        self.plugin = plugin
        self.tplenv = Environment(loader=FileSystemLoader(self.plugin.path_join( self.webif_dir, 'templates' ) ))


    @cherrypy.expose
    def index(self, reload=None):
        """
        Build index.html for cherrypy
        
        Render the template and return the html file to be delivered to the browser
            
        :return: contents of the template after beeing rendered 
        """
        tmpl = self.tplenv.get_template('index.html')
        return tmpl.render(plugin_shortname=self.plugin.get_shortname(), plugin_version=self.plugin.get_version(),
                           plugin_info=self.plugin.get_info(), p=self.plugin)
</code></pre>
<p>Der Ausdruck <code>@cherrypy.exposed</code> legt fest, dass die Methode über http-Requests aufrufbar (also über den Browser zugreifbar) ist und diese beantworten kann. Methoden bei denen dies nicht der Fall sein soll, brauchen diese Angabe nicht.<br />
In <code>self.tplenv.get_template</code> wird das <code>index.html</code> Template geladen, in <code>tmpl.render</code> werden Variablen übergeben und das Template als HTML gerendert.</p>
<p><strong>Tipp:</strong> Für ein minimales Plugin Web-Interface reicht es im Übrigen, nur die notwendigen Parameter der <code>tmpl.render</code> Methode zu übergeben und eine minimale <code>index.html</code> zu haben.</p>
<h1>Das Plugin gestalten</h1>
<p>Wie im nachfolgenden Bild am Beispiel des Web-Interfaces des Database Plugins zu sehen, werden statische Dateien wie etwa Bilder im Ordner <code>static</code> direkt unter dem Pluginordner abgelegt. Die eigentlichen Templates für das Web-Interface des Plugins liegen unter <code>templates</code>.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/plugin_webif.jpg" alt="" width="237" height="330" class="alignnone size-full wp-image-1758" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/06/plugin_webif.jpg 237w, https://www.smarthomeng.de/wp-content/uploads/2018/06/plugin_webif-215x300.jpg 215w" sizes="(max-width: 237px) 100vw, 237px" /></p>
<p>Einstiegs-Template ist die Datei <code>index.html</code> im <code>templates</code> Ordner, die oben in der Methode <code>index</code> definiert wurde. Das Default Template zu dieser Seite ist wie folgt aufgebaut:</p>
<ol>
<li>Mittels <code>{% extends "base_plugin.html" %}</code> wird von einer globalen Vorlage für Plugin-Web-Interfaces abgeleitet. Diese ist unter <code>/smarthome/modules/http/webif/gtemplates/base_plugin.html</code> zu finden. Über dieses Basis-Template werden bereits Bootstrap, JQuery, Font Awesome und CodeMirror eingebunden. Die Verwendung des Templates stellt sicher, dass Web-Interfaces für Plugins ein homogenes Look and Feel haben.</li>
<li>Da wir SmartHomeNG-weit auf Bootstrap 4 setzen, bitten wir, die Stilmittel und CSS Klassen von Bootstrap zu verwenden. Details siehe <a href="http://getbootstrap.com/" target="_blank" rel="noopener">http://getbootstrap.com/</a></li>
<li>Es gibt eine Reihe an Bereichen, die man nun in seiner <code>index.html</code> individuell ausgestalten kann. Diese sog. Blöcke sind <code>headtable</code>, <code>buttons</code> und <code>bodytab1</code> bis <code>bodytab4</code></li>
<li>Ein Block kann im Template wie folgt befüllt werden:
<pre class="language-django"><code>{% block headtable %}
...
{% endblock headtable %}</code></pre>
</li>
<li>Im Fall mehrerer <code>bodytab</code>'s, können auch noch <code>tab1title</code> bis <code>tab4title</code> vergeben werden. Wird dies nicht gemacht, werden Default Titel erzeugt.</li>
</ol>
<p>Nachfolgend sind die Blöcke am Beispiel des Web-Interfaces des AVM Plugins nochmals dargestellt:<br />
<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1.jpg" alt="" width="1294" height="597" class="alignnone size-full wp-image-1813" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1.jpg 1294w, https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1-300x138.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1-768x354.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1-1024x472.jpg 1024w" sizes="(max-width: 1294px) 100vw, 1294px" /></p>
<h1>Tipps und Tricks</h1>
<h2>Eigene CSS und JS Dateien einbinden</h2>
<p>Will man eigene JavaScript oder CSS Files einbinden, so kann man die Blöcke des <code>base_plugin</code> einfach erweitern. Ein Beispiel wäre wie folgt:</p>
<pre class="language-django"><code>{%- block scripts %}
{{ super() }}
<span>&lt;script </span><span>src=</span><span>"/gstatic/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"</span><span>&gt;&lt;/script&gt;
</span><span>&lt;script </span><span>src=</span><span>"/gstatic/bootstrap-datepicker/dist/locales/bootstrap-datepicker.de.min.js"</span><span>&gt;&lt;/script&gt;
</span><span>&lt;script </span><span>src=</span><span>"/gstatic/bootstrap-datepicker/dist/locales/bootstrap-datepicker.fr.min.js"</span><span>&gt;&lt;/script&gt;
</span>{%- endblock scripts %}

{%- block styles %}
{{ super() }}
<span>&lt;link </span><span>rel=</span><span>"stylesheet" </span><span>href=</span><span>"/gstatic/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css" </span><span>type=</span><span>"text/css"</span><span>/&gt;
</span>{%- endblock styles %}
</code></pre>
<p>Über <code>super()</code> wird der Block eines der übergeordneten Templates, in diesem Fall in der <code>../modules/http/webif/gtemplates/base.html</code>, die der <code>plugin_base.html</code> übergeordnet ist, aufgerufen. Die nach <code>super()</code> folgenden Teile, werden dem Blockinhalt angehängt. Würde man <code>super()</code> weglassen, würde der Block komplett überschrieben.</p>
<h2>Unterschiedliche Seiten / Aktionen über Seitenaufrufe auslösen.</h2>
<p>Will man nicht nur eine Seite für das Plugin haben, so kann man entweder analog zur Methode <code>def index(self)</code> in der <code>__init__.py</code> weitere Methoden (und damit Seiten) definieren. In jeder dieser Methoden muss via <code>get_template</code> ein jeweils anderes Templates aus dem <code>templates</code> Ordner des Plugins geladen werden.</p>
<p>Eine Alternative zu diesem Vorgehen ist, nur die <code>index</code> Methode zu verwenden und die Auswahl des Templates in der <code>def index(self)</code> via Kommando-Parameter zu steuern. Neue Parameter kann man einfach in der Methode ergänzen, also bspw. <code>def index(self, <i>cmd=None</i>)</code>. Die Vorbelegung mit <code>None</code> ist dafür, dass der Parameter auch weggelassen werden kann.</p>
<p>Über <code>cmd</code> kann man nun im Python Code Aktionen auslösen oder abhängig vom Wert in <code>cmd</code> via <code>tmpl = self.tplenv.get_template('...')</code> andere Templates auswählen. <code>cmd</code> muss in einem Link / Formular als GET-Parameter übergeben werden, als bspw. mit <code>?cmd=...</code> an eine URL angehängt werden.</p>
<p>Ein Beispiel dazu kann im Web-Interface des Database-Plugin gefunden werden.</p>
<p>Wurde eine Aktion erfolgreich ausgeführt, empfiehlt es sich, die Info in <code>tmpl.render</code> an das Template zu übergeben. Über Bootstrap kann man nun schöne Status-Meldungen anzeigen:</p>
<pre class="language-django"><code>{% if delete_triggered %}
&lt;div class="mb-2 alert alert-success alert-dismissible fade show" role="alert"&gt;
    &lt;strong&gt;Löschauftrag für die Einträge von Item ID {{ item_id }} in der Tabelle "log" wurde erfolgreich initiiert!&lt;/strong&gt;&lt;br/&gt;
    Das Löschen kann noch kurze Zeit dauern, da die Ausführung des Delete Queries erst nach Abschluß der bestehenden Transaktionen erfolgen kann.
    &lt;button type="button" class="close" data-dismiss="alert" aria-label="Close"&gt;
        &lt;span aria-hidden="true"&gt;&amp;times;&lt;/span&gt;
    &lt;/button&gt;
&lt;/div&gt;
{% endif %}
</code></pre>
<h2>Internationalisierung</h2>
<p>Das Übersetzungssystem von SmartHomeNG kann auch für Plugins genutzt werden. Dazu muss direkt im Pluginverzeichnis eine Datei <code>locale.yaml</code> angelegt werden. In dieser werden Übersetzungen wie folgt definiert:</p>
<pre class="language-yaml"><code>
plugin_translations:
    # Translations for the plugin specially for the web interface
    'Aktionen':        {'de': 'Aktionen', 'en': 'Actions'}
    'Verbunden':       {'de': 'Verbunden', 'en': 'Connected'}
    'Ja':              {'de': 'Ja', 'en': 'Yes'}
    'Nein':            {'de': 'Nein', 'en': 'No'}
</code></pre>
<p>Im Template kann nun über die Syntax <code>{{ _('<em>&lt;Schlüssel aus der language.yaml&gt;</em>') }}</code> darauf zugegriffen werden:</p>
<pre class="language-django"><code>{% if check %}{{ _('Ja') }}{% else %}{{ _('Nein') }}{% endif %}</code></pre>
<p><span style="color: #ff0000;"><strong>Wir bitten darum, Web-Interfaces für Plugins immer mindestens auf Englisch und Deutsch zu erstellen!</strong></span></p>
<p><em>(Die in diesem Artikel verwendeten Screenshots wurden selber erstellt. Das Titelbild ist unter der Creative Commons Zero (CC0) Lizenz veröffentlicht und wurde von www.pexels.com bezogen.)</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/jetzt-wird-es-bunt-wie-man-ein-plugin-webinterface-schreibt-in-kuerze-ab-smarthomeng-1-5/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Entfernungsmessung mit ESP32 und SmartHomeNG &#8211; Teil 2: Anbindung eines SSD1306 OLED Displays</title>
		<link>https://www.smarthomeng.de/entfernungsmessung-mit-esp32-und-smarthomeng-teil-2-anbindung-eines-ssd1306-oled-displays</link>
					<comments>https://www.smarthomeng.de/entfernungsmessung-mit-esp32-und-smarthomeng-teil-2-anbindung-eines-ssd1306-oled-displays#respond</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Sat, 19 May 2018 17:46:32 +0000</pubDate>
				<category><![CDATA[Beispiel-Implementierungen]]></category>
		<category><![CDATA[Tipps & Tricks]]></category>
		<category><![CDATA[ESP32]]></category>
		<category><![CDATA[OLED Display]]></category>
		<category><![CDATA[SSD1306]]></category>
		<category><![CDATA[Wasserstand]]></category>
		<category><![CDATA[Webservices]]></category>
		<category><![CDATA[Zisterne]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=1671</guid>

					<description><![CDATA[Als nächsten Schritt meines Projekts zur Überwachung des Wasserstands meiner Zisterne, habe ich ein SSD1306 OLED Display ergänzt, das in meiner Garage in der Nähe der Pumpe installiert werden soll. Hier möchte ich die Wasserhöhe und &#8211; eines Tages &#8211; den Wasserstand anzeigen. Als Vorarbeiten wird im Wesentlichen der Artikel<a class="moretag" href="https://www.smarthomeng.de/entfernungsmessung-mit-esp32-und-smarthomeng-teil-2-anbindung-eines-ssd1306-oled-displays"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Als nächsten Schritt meines Projekts zur Überwachung des Wasserstands meiner Zisterne, habe ich ein SSD1306 OLED Display ergänzt, das in meiner Garage in der Nähe der Pumpe installiert werden soll. Hier möchte ich die Wasserhöhe und &#8211; eines Tages &#8211; den Wasserstand anzeigen.</p>
<p>Als Vorarbeiten wird im Wesentlichen der Artikel &#8222;<a href="https://www.smarthomeng.de/entfernungsmessung-auf-basis-eines-esp32-und-smarthomeng" target="_blank" rel="noopener">Entfernungsmessung auf Basis eines ESP32 und SmartHomeNG</a>&#8220; vorausgesetzt.<a href="https://www.smarthomeng.de/entfernungsmessung-auf-basis-eines-esp32-und-smarthomeng"></a></p>
<p>Mittlerweile bin ich bei der Messung des Ultraschallsignales des HR-SR04 allerdings auf die Arduino Bibliothek <a href="https://playground.arduino.cc/Code/NewPing" target="_blank" rel="noopener">NewPing</a> umgestiegen, die die Messung übernimmt:<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/05/newping_lib.jpg" alt="" width="726" height="96" class="alignnone size-full wp-image-1710" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/05/newping_lib.jpg 726w, https://www.smarthomeng.de/wp-content/uploads/2018/05/newping_lib-300x40.jpg 300w" sizes="(max-width: 726px) 100vw, 726px" />Als Display kommt ein kleines OLED Display mit SSD 1306 Controller zum Einsatz (<a href="https://www.az-delivery.de/products/0-96zolldisplay" target="_blank" rel="noopener">https://www.az-delivery.de/products/0-96zolldisplay</a>, ca. 7 Euro).</p>
<p>Das Display wird wie folgt zusätzlich zum HC-SR04 angeschlossen:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/05/Untitled-Sketch-2_Steckplatine.png" alt="" width="529" height="460" class="alignnone wp-image-1673" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/05/Untitled-Sketch-2_Steckplatine.png 1098w, https://www.smarthomeng.de/wp-content/uploads/2018/05/Untitled-Sketch-2_Steckplatine-300x261.png 300w, https://www.smarthomeng.de/wp-content/uploads/2018/05/Untitled-Sketch-2_Steckplatine-768x667.png 768w, https://www.smarthomeng.de/wp-content/uploads/2018/05/Untitled-Sketch-2_Steckplatine-1024x890.png 1024w" sizes="(max-width: 529px) 100vw, 529px" /></p>
<p>Als Bibliothek kommt die &#8222;<a href="https://github.com/ThingPulse/esp8266-oled-ssd1306" target="_blank" rel="noopener">ESP8266 and ESP32 Oled Driver for SSD1306 display</a>&#8220; von Daniel Eichhorn und Fabrice Weinberg in Version 4.0.0 zum Einsatz:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/05/display_lib.jpg" alt="" width="734" height="90" class="alignnone size-full wp-image-1685" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/05/display_lib.jpg 734w, https://www.smarthomeng.de/wp-content/uploads/2018/05/display_lib-300x37.jpg 300w" sizes="(max-width: 734px) 100vw, 734px" /></p>
<p>Der angepasst Sourcecode sieht nun wie folgt aus. Es ist dabei zu beachten, dass das Webservices- Plugin inzwischen über die REST Schnittstelle angesteuert wird. Weiterhin sind alle Log-Outputs für den Seriellen Monitor entfernt worden. Sämtliche relevanten Outputs werden jetzt auf dem Display ausgegeben.</p>
<pre><code class="language-c">
#include &lt;HTTPClient.h&gt;
#include &lt;NewPing.h&gt;
#include &lt;WiFi.h&gt;
#include &lt;WiFiClient.h&gt;
#include &lt;WiFiServer.h&gt;
#include &lt;WiFiUdp.h&gt;
#include &lt;Wire.h&gt;
#include "SSD1306.h"

const int triggerPin = 15;   // HC-SR04: Trigger Pin
const int echoPin = 4;       // HC-SR04: Echo Pin
const int sdaPin = 21;       // Display: SDA Pin
const int sclPin = 22;       // Display: SCL Pin
const int sensorRange = 280; // Reichweite (&lt; maximale Reichweite)

// HC-SR04
NewPing sonar(triggerPin, echoPin, sensorRange); // create NewPing instance

// Wifi
char ssid[] = "&lt;wlan_ssid&gt;";     // your network SSID (name)
char pass[] = "&lt;wlan_password&gt;"; // your network password
char host[] = "192.168.178.100"; // ip of service interface in SmartHomeNG
int port = 4321;                 // port of service interface in SmartHomeNG
char cm_item[] = "test.numeric";
int status = WL_IDLE_STATUS;
WiFiClient client;

// Display
SSD1306Wire display(0x3c, sdaPin, sclPin);

void setup() {
  Serial.begin(19200);
  // Init Display
  display.init();
  display.flipScreenVertically();
  // Init Wifi
  initialise_wifi();
}

void loop() {
  delay(50);
  unsigned int result = sonar.ping();
  float cm = sonar.convert_cm(result); //(result/2)/29.1; //

  while (client.available()) {
    char c = client.read();
    Serial.write(c);
  }

  if (cm &gt; 0) {
    if (client.connect(host, port)) {
      HTTPClient http;
      char portStr[12];
      sprintf(portStr, "%d", port);
      char url[128];
      sprintf(url, "http://%s:%s/rest/items/%s", host, portStr, cm_item);
      http.begin(url);
      http.addHeader("Content-Type", "application/json");
      char cmStr[12];
      char cmStrFull[24];
      sprintf(cmStr, "%f", cm);
      sprintf(cmStrFull, "%i cm", (int)cm);
      int httpCode = http.PUT(cmStr);
      String payload = http.getString();
      http.end();

      display.clear();
      display.setFont(ArialMT_Plain_16);
      display.drawString(0, 0, "Wasserstand:");
      display.setFont(ArialMT_Plain_24);
      display.drawString(0, 17, cmStrFull);
      display.display();
    }
  }
  // Wait 5 seconds and do next measurement
  delay(5000);
}

static void initialise_wifi(void) {
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 0, "Setting up wifi...");
  display.display();

  while (status != WL_CONNECTED) {
    display.drawString(0, 11, "Attempting to connect...");
    display.display();
    status = WiFi.begin(ssid, pass);
    delay(10000);
  }
  display.drawString(0, 22, "Connected to wifi!");
  display.display();
  printWifiStatus();
}

void printWifiStatus() {
  IPAddress ip = WiFi.localIP();
  String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
  display.drawString(0, 33, "IP: "+ipStr);
  display.display();
  long rssi = WiFi.RSSI();
  char rssiStr[255];
  sprintf(rssiStr, "RSSI: %d dBm", rssi);
  display.drawString(0, 44, rssiStr);
  display.display();
}</code></pre>
<p>Nach der Initialisierung des Displays in der Methode <code>setup</code>, kann mittels <code>display.drawString(...)</code> Text auf das Display gesendet werden. Der erste Parameter ist dabei die X-, der zweite die Y-Koordinate. Im dritten Parameter wird der String gesetzt. Mit <code>display.display();</code> werden alle zuvor eingesteuerten Operationen dann wirklich auf dem Display ausgegeben. Ein <code>display.clear();</code> leert die auf dem Display ausgegebenen Werte, damit neuer Text dargestellt werden kann. Wird kein <code>display.display();</code> aufgerufen, so wird immer mehr auf das Display geschrieben. Dies kann soweit führen, dass Text über bestehenden Text geschrieben wird.</p>
<p>Dies kann am Beispiel der WLAN-Initialisierung gut gesehen werden. Das Gehäuse ist dabei wieder von Thingiverse bezogen und via 3D Drucker gedruckt worden (<a href="https://www.thingiverse.com/thing:2176764" target="_blank" rel="noopener">https://www.thingiverse.com/thing:2176764</a>):</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/05/IMAG0778-1.jpg" alt="" width="546" height="418" class="alignnone wp-image-1699" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/05/IMAG0778-1.jpg 730w, https://www.smarthomeng.de/wp-content/uploads/2018/05/IMAG0778-1-300x230.jpg 300w" sizes="(max-width: 546px) 100vw, 546px" /><br />
Nach der Initialisierung wird nun auf dem Display der Abstand angezeigt:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/05/IMAG0776.jpg" alt="" width="516" height="556" class="alignnone wp-image-1690" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/05/IMAG0776.jpg 3024w, https://www.smarthomeng.de/wp-content/uploads/2018/05/IMAG0776-278x300.jpg 278w, https://www.smarthomeng.de/wp-content/uploads/2018/05/IMAG0776-768x827.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2018/05/IMAG0776-950x1024.jpg 950w" sizes="(max-width: 516px) 100vw, 516px" /></p>
<p>In Teil 3 folgt nun in Kürze noch ein Update zu meiner tatsächlichen Installation in der Zisterne und der Garage. Die Arbeiten sind bereits vollendet und die Werte werden via Kabel in die Garage auf den ESP32 übertragen. Mehr dazu aber in der nächsten Zeit&#8230;</p>
<p><em>(Die in diesem Artikel verwendeten Screenshots und Fotos wurden selber erstellt. Für den Schaltplan wurde das Tool<span> </span><a href="http://fritzing.org/home/" target="_blank" rel="noopener">Fritzing</a><span> </span>verwendet.)</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/entfernungsmessung-mit-esp32-und-smarthomeng-teil-2-anbindung-eines-ssd1306-oled-displays/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Zusätzliche und hilfreiche Zeitdaten über eine Logik berechnen</title>
		<link>https://www.smarthomeng.de/zeitdaten-ueber-eine-logik-berechnen</link>
					<comments>https://www.smarthomeng.de/zeitdaten-ueber-eine-logik-berechnen#comments</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Tue, 01 May 2018 10:12:40 +0000</pubDate>
				<category><![CDATA[Beispiel-Implementierungen]]></category>
		<category><![CDATA[ohne Kategorie]]></category>
		<category><![CDATA[Beispiel]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[Logik]]></category>
		<category><![CDATA[Logiken]]></category>
		<category><![CDATA[Shtime API]]></category>
		<category><![CDATA[Zeitdaten]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=1643</guid>

					<description><![CDATA[Mit SmartHomeNG existiert bereits eine Reihe zeitbezogener Daten über Shtime.get_instance().now(). Speziell für Datenbankabfragen kann es aber hilfreich sein, noch einige Zusatzitems zu haben. Diese werden im Rahmen dieses Artikels gezeigt. Die Items date: month: since: year: type: num cache: 'yes' dbstr: type: str cache: 'yes' eval_trigger: date.month.since.year eval: str(sh.date.month.since.year()) +<a class="moretag" href="https://www.smarthomeng.de/zeitdaten-ueber-eine-logik-berechnen"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Mit SmartHomeNG existiert bereits eine Reihe zeitbezogener Daten über <code>Shtime.get_instance().now()</code>. Speziell für Datenbankabfragen kann es aber hilfreich sein, noch einige Zusatzitems zu haben. Diese werden im Rahmen dieses Artikels gezeigt.</p>
<h1>Die Items</h1>
<pre><code class="language-yaml">
date:

    month:
        since:
            year:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.month.since.year
                    eval: str(sh.date.month.since.year()) + 'm'

        until:
            year:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.month.until.year
                    eval: str(sh.date.month.until.year()) + 'm'

    week:
        since:
            month:
                type: num
                cache: 'yes'
            year:
                type: num
                cache: 'yes'
        until:
            month:
                type: num
                cache: 'yes'
            year:
                type: num
                cache: 'yes'

    day:
        since:
            week:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.day.since.week
                    eval: str(sh.date.day.since.week()) + 'd'

            month:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.day.since.month
                    eval: str(sh.date.day.since.month()) + 'd'
            year:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.day.since.year
                    eval: str(sh.date.day.since.year()) + 'd'

        until:
            week:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.day.since.week
                    eval: str(sh.date.day.since.week()) + 'd'

            month:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.day.until.month
                    eval: str(sh.date.day.until.month()) + 'd'

            year:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.day.until.year
                    eval: str(sh.date.day.until.year()) + 'd'

    hour:
        since:
            midnight:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.hour.since.midnight
                    eval: str(sh.date.hour.since.midnight()) + 'h'

            week:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.hour.since.week
                    eval: str(sh.date.hour.since.week()) + 'h'

            month:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.hour.since.month
                    eval: str(sh.date.hour.since.month()) + 'h'

            year:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.hour.since.year
                    eval: str(sh.date.hour.since.year()) + 'h'

        until:
            midnight:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.hour.until.midnight
                    eval: str(sh.date.hour.until.midnight()) + 'h'

            week:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.hour.until.week
                    eval: str(sh.date.hour.until.week()) + 'h'

            month:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.hour.until.month
                    eval: str(sh.date.hour.until.month()) + 'h'

            year:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.hour.until.year
                    eval: str(sh.date.hour.until.year()) + 'h'

    minute:
        since:
            hour:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.since.hour
                    eval: str(sh.date.minute.since.hour()) + 'i'

            midnight:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.since.midnight
                    eval: str(sh.date.minute.since.midnight()) + 'i'

            week:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.since.week
                    eval: str(sh.date.minute.since.week()) + 'i'

            month:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.since.month
                    eval: str(sh.date.minute.since.month()) + 'i'

            year:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.since.year
                    eval: str(sh.date.minute.since.year()) + 'i'

        until:
            hour:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.until.hour
                    eval: str(sh.date.minute.until.hour()) + 'i'

            midnight:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.until.midnight
                    eval: str(sh.date.minute.until.midnight()) + 'i'

            week:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.until.week
                    eval: str(sh.date.minute.until.week()) + 'i'

            month:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.until.month
                    eval: str(sh.date.minute.until.month()) + 'i'

            year:
                type: num
                cache: 'yes'
                dbstr:
                    type: str
                    cache: 'yes'
                    eval_trigger: date.minute.until.year
                    eval: str(sh.date.minute.until.year()) + 'i'

    second:
        since:
            minute:
                type: num

            hour:
                type: num

            midnight:
                type: num

            week:
                type: num

            month:
                type: num

            year:
                type: num

        until:

            minute:
                type: num

            hour:
                type: num

            midnight:
                type: num

            week:
                type: num

            month:
                type: num

            year:
                type: num
</code></pre>
<h1>Die Logik</h1>
<p>Als Logik wird unter <code>logics/zeit.py</code> die nachfolgende Datei angelegt.</p>
<pre><code class="language-python">
<span># /logics/zeit.py
</span><span># !/usr/bin/env python3
</span><span>
</span>
from lib.shtime import Shtime<br/>
sh_now = Shtime.get_instance().now()
<span>
</span><span># Funktionen
</span><span>
</span><span>def </span><span>days_of_month</span>(month<span>, </span>year):
    <span>if </span>month <span>in </span>[<span>1</span><span>, </span><span>3</span><span>, </span><span>5</span><span>, </span><span>7</span><span>, </span><span>8</span><span>, </span><span>10</span><span>, </span><span>12</span>]:
        days = <span>31
</span><span>    </span><span>elif </span>month <span>in </span>[<span>4</span><span>, </span><span>6</span><span>, </span><span>9</span><span>, </span><span>11</span>]:
        days = <span>30
</span><span>    </span><span>elif </span>(year % <span>400 </span>== <span>0</span>) <span>or </span>((year % <span>4 </span>== <span>0</span>) <span>and not </span>(year % <span>100 </span>== <span>0</span>)):  <span># Schaltjahr
</span><span>        </span>days = <span>29
</span><span>    </span><span>else</span>:
        days = <span>28
</span><span>    </span><span>return </span>days

<span>def </span><span>days_of_year</span>(year):
    period_end = datetime.datetime(year<span>, </span><span>12</span><span>, </span><span>31</span>)
    days_of_year = (period_end - datetime.datetime(period_end.year<span>, </span><span>1</span><span>, </span><span>1</span>)).days + <span>1
</span><span>    </span><span>return </span>(days_of_year)

<span>def </span><span>day_of_year</span>(year<span>, </span>month<span>, </span>day):
    period_end = datetime.datetime(year<span>, </span>month<span>, </span>day)
    day_of_year = (period_end - datetime.datetime(period_end.year<span>, </span><span>1</span><span>, </span><span>1</span>)).days + <span>1
</span><span>    </span><span>return </span>(day_of_year)
<span>
</span><span># Sekunde/Minute
</span>sh.date.second.since.minute(sh_now.second)
sh.date.second.until.minute(<span>60 </span>- sh_now.second - <span>1</span>)

<span># Minute/Stunde
</span>sh.date.minute.since.hour(sh_now.minute)
sh.date.minute.until.hour(<span>60 </span>- sh_now.minute - <span>1</span>)

<span># Stunde/Tag
</span>sh.date.hour.since.midnight(sh_now.hour)
sh.date.hour.until.midnight(<span>24 </span>- sh_now.hour - <span>1</span>)

<span># Tag/Woche
</span>sh.date.day.since.week(sh_now.isoweekday())
sh.date.day.until.week(<span>7 </span>- sh_now.isoweekday())

<span># Stunde/Woche
</span>sh.date.hour.since.week(sh.date.hour.since.midnight() + (<span>24 </span>* (sh.date.day.since.week() - <span>1</span>)))
sh.date.hour.until.week(sh.date.hour.until.midnight() + (<span>24 </span>* sh.date.day.until.week()))

<span># Kalenderwoche/Jahr
</span>sh.date.week.since.year(sh_now.isocalendar()[<span>1</span>])
sh.date.week.until.year(<span>52</span>-sh_now.isocalendar()[<span>1</span>])

<span># Monat/Jahr
</span>sh.date.month.since.year(sh_now.month)
sh.date.month.until.year(<span>12 </span>- sh_now.month)

<span># Sekunde/Stunde
</span>sh.date.second.since.hour(sh.date.second.since.minute() + (<span>60 </span>* sh.date.minute.since.hour()))
sh.date.second.until.hour(sh.date.second.until.minute() + (<span>60 </span>* sh.date.minute.until.hour()))

<span># Sekunde/Tag
</span>sh.date.second.since.midnight(sh.date.second.since.minute() + (<span>3600 </span>* sh.date.hour.since.midnight()))
sh.date.second.until.midnight(sh.date.second.until.minute() + (<span>3600 </span>* sh.date.hour.until.midnight()))

<span># Minute/Tag
</span>sh.date.minute.since.midnight(sh.date.minute.since.hour() + (<span>60 </span>* sh.date.hour.since.midnight()))
sh.date.minute.until.midnight(sh.date.minute.until.hour() + (<span>60 </span>* sh.date.hour.until.midnight()))

<span># Minute/Woche
</span>sh.date.minute.since.week(sh.date.minute.since.hour() + (<span>60 </span>* sh.date.hour.since.week()))
sh.date.minute.until.week(sh.date.minute.until.hour() + (<span>60 </span>* sh.date.hour.until.week()))

<span># Sekunde/Woche
</span>sh.date.second.since.week(sh.date.second.since.minute() + (<span>60 </span>* sh.date.minute.since.week()))
sh.date.second.until.week(sh.date.second.until.minute() + (<span>60 </span>* sh.date.minute.until.week()))

<span># Tage/Monat
</span>sh.date.day.since.month(sh_now.day)
sh.date.day.until.month(days_of_month(sh_now.month<span>, </span>sh_now.year) - sh.date.day.since.month() - <span>1</span>)

<span># Wochen/Monat
</span>sh.date.week.since.month((sh.date.day.since.month()-<span>1</span>)//<span>7</span>+<span>1</span>)
sh.date.week.until.month((sh.date.day.until.month())//<span>7</span>)

<span># Tage/Jahr
</span>sh.date.day.since.year(day_of_year(sh_now.year<span>, </span>sh_now.month<span>, </span>sh_now.day) - <span>1</span>)
sh.date.day.until.year(days_of_year(sh_now.year) - sh.date.day.since.year() - <span>1</span>)

<span># Stunde/Monat
</span>sh.date.hour.since.month((<span>24 </span>* sh.date.day.since.month()) + sh.date.hour.since.midnight())
sh.date.hour.until.month((<span>24 </span>* days_of_month(sh_now.month<span>, </span>sh_now.year)) - sh.date.hour.since.month() - <span>1</span>)

<span># Stunde/Jahr
</span>sh.date.hour.since.year((<span>24 </span>* sh.date.day.since.year()) + sh.date.hour.since.midnight())
sh.date.hour.until.year((<span>24 </span>* days_of_year(sh_now.year)) - sh.date.hour.since.year() - <span>1</span>)

<span># Minute/Monat
</span>sh.date.minute.since.month((<span>60 </span>* sh.date.hour.since.month()) + sh.date.minute.since.hour())
sh.date.minute.until.month(sh.date.minute.since.month() - (<span>60 </span>* sh.date.hour.until.month()) - <span>1</span>)

<span># Minute/Jahr
</span>sh.date.minute.since.year((<span>60 </span>* sh.date.hour.since.year()) + sh.date.minute.since.hour())
sh.date.minute.until.year((<span>60 </span>* sh.date.hour.until.year()) + sh.date.minute.until.hour())

<span># Sekunde/Monat
</span>sh.date.second.since.month((<span>60 </span>* sh.date.minute.since.month()) + sh.date.second.since.minute())
sh.date.second.until.month((<span>60 </span>* sh.date.minute.until.month()) + sh.date.second.until.minute())

<span># Sekunde/Jahr
</span>sh.date.second.since.year((<span>60 </span>* sh.date.minute.since.year()) + sh.date.second.since.minute())
sh.date.second.until.year((<span>60 </span>* sh.date.minute.until.year()) + sh.date.second.until.minute())</code></pre>
<p>In der <code>etc/logic.yaml</code> wird sie wie folgt eingebunden:</p>
<pre><code class="language-yaml">
zeitberechnung:
    filename: zeit.py
    crontab:
        -   init
        -   '* * * *'
</code></pre>
<h1>Anwendungsbeispiel: Database Plugin</h1>
<p>Neben der Anzeige der Daten, macht es vor allem Sinn, die Werte im Zusammenspiel mit (historischen) Datenbank-Daten zu verwenden. Ein Beispiel wäre wie folgt:</p>
<pre><code class="language-yaml">
consumption_since_midnight:
	type: num
	eval: sh.knx.cellar.utility_room.water_meter.db('count&gt;0', sh.date.minute.since.midnight.dbstr(), 'now')
	eval_trigger: knx.cellar.utility_room.water_meter
	database@mysqldb: init
</code></pre>
<p>Im Eval-Ausdruck wird die Anzahl der Werte &gt; 0 ermittelt, die zwischen Mitternacht und dem IST-Zeitpunkt erfasst wurden. Da für 1 Liter jedes Mal ein Impuls mit einer &#8222;1&#8220; erfasst wird, erhält man so minutengenau die Anzahl der Liter seit Mitternacht. Die Methode <code>dbstr()</code> in <code>sh.date.minute.since.midnight.dbstr()</code> liefert direkt den Wert, den das Database-Plugin benötigt. Präziser gesagt, hängt sie dem numerischen Wert <anzahl der="" minuten=""> noch ein &#8222;i&#8220; an, so dass die Angabe auf Ebene des Plugins korrekt interpretiert wird.<br />
Über die Methode <code>.db</code> auf dem Item, wird die Datenbankabfrage gestartet. Das Item muss dazu mit dem Database-Plugin verknüpft sein <code>database@mysqldb: init</code> und bereits Daten im abgefragten Zeitraum erfasst haben.</anzahl></p>
<p><em>(Das Titelbild ist unter der Creative Commons Zero (CC0) Lizenz veröffentlicht und wurde von www.pexels.com bezogen.)</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/zeitdaten-ueber-eine-logik-berechnen/feed</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Das Traffic Plugin am Beispiel eines Staualarms</title>
		<link>https://www.smarthomeng.de/das-traffic-plugin-am-beispiel-eines-staualarms</link>
					<comments>https://www.smarthomeng.de/das-traffic-plugin-am-beispiel-eines-staualarms#respond</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Sat, 21 Apr 2018 16:04:27 +0000</pubDate>
				<category><![CDATA[Plugins]]></category>
		<category><![CDATA[Tipps & Tricks]]></category>
		<category><![CDATA[Alarm]]></category>
		<category><![CDATA[Fahrzeit]]></category>
		<category><![CDATA[Google Directions]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Traffic]]></category>
		<category><![CDATA[Traffic Plugin]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=1574</guid>

					<description><![CDATA[Pendeln in deutschen Großstädten ist kein Spaß. Laut einem Artikel von Spiegel Online stehen deutsche Autofahrer im Jahr 475.000 Stunden im Stau. Mit dem Traffic Plugin bietet SmartHomeNG die Möglichkeit, Fahrzeiten über die Google Directions API zu berechnen. So lässt sich auf einfache Art und Weise ein Alarmsystem bauen, dass<a class="moretag" href="https://www.smarthomeng.de/das-traffic-plugin-am-beispiel-eines-staualarms"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Pendeln in deutschen Großstädten ist kein Spaß. Laut einem Artikel von <a href="http://www.spiegel.de/auto/aktuell/adac-autofahrer-stehen-475-000-stunden-im-stau-a-1189334.html" target="_blank" rel="noopener">Spiegel Online</a> stehen deutsche Autofahrer im Jahr 475.000 Stunden im Stau.</p>
<p>Mit dem Traffic Plugin bietet SmartHomeNG die Möglichkeit, Fahrzeiten über die Google Directions API zu berechnen.</p>
<p><span id="more-1574"></span></p>
<p>So lässt sich auf einfache Art und Weise ein Alarmsystem bauen, dass einem im Büro Bescheid gibt, wenn es Zeit wird zu fahren (oder man länger bleiben sollte, damit sich der Stau wieder auflösen kann).</p>
<p>Die Erkennung, wo man sich gerade befindet, wird dabei, wie in <a href="https://www.smarthomeng.de/geozonen-basierte-services-mit-der-egigeozone-app-und-dem-network-plugin" target="_blank" rel="noopener">https://www.smarthomeng.de/geozonen-basierte-services-mit-der-egigeozone-app-und-dem-network-plugin</a> beschrieben, über EgiGeoZone und das Network Plugin umgesetzt.</p>
<h1>Das Traffic Plugin</h1>
<p>Die automatisch generierte Doku zum Traffic Plugin findet sich unter <a href="https://www.smarthomeng.de/user/plugins_doc/config/traffic.html" target="_blank" rel="noopener">https://www.smarthomeng.de/user/plugins_doc/config/traffic.html</a>.</p>
<p>Die Readme liegt unter <a href="https://www.smarthomeng.de/user/plugins/traffic/README.html" target="_blank" rel="noopener">https://www.smarthomeng.de/user/plugins/traffic/README.html</a>, der Quellcode auf Github unter <a href="https://github.com/smarthomeNG/plugins/tree/master/traffic" target="_blank" rel="noopener">https://github.com/smarthomeNG/plugins/tree/master/traffic</a>.</p>
<p>Der Support-Thread im Forum findet sich unter <a href="https://knx-user-forum.de/forum/supportforen/smarthome-py/1048446-traffic-plugin-support-thread" target="_blank" rel="noopener">https://knx-user-forum.de/forum/supportforen/smarthome-py/1048446-traffic-plugin-support-thread</a>.</p>
<p>Das Plugin ist schnell in der <code>etc/plugin.yaml</code> konfiguriert:</p>
<pre><code class=" language-yaml">
traffic:
    class_name: Traffic
    class_path: plugins.traffic
    apikey: your own api key
    language: de (optional)
</code></pre>
<p>Der persönliche API Key für die Directions API von Google kann unter <a href="https://developers.google.com/maps/documentation/directions/intro?hl=de" target="_blank" rel="noopener">https://developers.google.com/maps/documentation/directions/intro?hl=de</a> beantragt werden.</p>
<h1>Die Items</h1>
<p>Wichtig sind dabei folgende drei Items zur Erfassung des aktuellen Orts, an dem man sich aufhält. Die Items wurden bereits in dem oben genanntem Artikel zu EgiGeoZone eingeführt, und werden, wie im Artikel beschrieben, von EgiGeoZone bedatet:</p>
<pre class="language-yaml code-toolbar"><code class=" language-yaml"><span class="token key atrule">location</span><span class="token punctuation">:</span>

        <span class="token key atrule">lat</span><span class="token punctuation">:</span>
            <span class="token key atrule">type</span><span class="token punctuation">:</span> str
            <span class="token key atrule">visu_acl</span><span class="token punctuation">:</span> ro
            <span class="token key atrule">cache</span><span class="token punctuation">:</span> <span class="token string">'yes'</span>

        <span class="token key atrule">lon</span><span class="token punctuation">:</span>
            <span class="token key atrule">type</span><span class="token punctuation">:</span> str
            <span class="token key atrule">visu_acl</span><span class="token punctuation">:</span> ro
            <span class="token key atrule">cache</span><span class="token punctuation">:</span> <span class="token string">'yes'</span>

        <span class="token key atrule">zone</span><span class="token punctuation">:</span>
            <span class="token key atrule">type</span><span class="token punctuation">:</span> str
            <span class="token key atrule">visu_acl</span><span class="token punctuation">:</span> ro
            <span class="token key atrule">cache</span><span class="token punctuation">:</span> <span class="token string">'yes'</span>
</code></pre>
<p>Wichtig dabei ist, dass der Zonenname in EgiGeoZone für die Arbeitsstätte (oder mehrere Arbeitsstätten) den String &#8222;Arbeit&#8220; enthält und der Zonenname für daheim &#8222;Home&#8220;. Alternative Namen gehen auch, müssen dann aber in der Logik weiter unten angepasst werden.</p>
<p>Zusätzlich müssen noch die Home Koordinaten jeweils als Item definiert werden.<br />
<strong>Tipp:</strong> Alternativ können auch die Attribute <code>_lat</code> und <code>_lon</code> direkt auf dem SmartHome Objekt (<code>bin/smarthome.py</code>) genutzt werden.</p>
<pre class="language-yaml code-toolbar"><code class=" language-yaml"><span>location:
</span><span>
</span><span>    home:
</span><span>        lat:
</span><span>            type: </span>str
            <span>visu_acl: </span>ro
            <span>value: </span>48.xxxxxxx
            <span>cache: </span><span>'yes'
</span><span>
</span><span>        </span><span>lon:
</span><span>            type: </span>str
            <span>visu_acl: </span>ro
            <span>value: </span>11.xxxxxxx
            <span>cache: </span><span>'yes'</span></code></pre>
<p>Für die Speicherung der Reisedaten, die vom Traffic Plugin zurückgegeben werden, werden folgende Items benötigt:</p>
<pre class="language-yaml code-toolbar"><code class=" language-yaml">travel_info:

    calculate_way_home:
       type: bool
       cache: 'yes'
    
    calculate_way_work:
        type: bool
        cache: 'yes'

    travel_time:
        type: num
        
        in_traffic:
            type: num

    travel_distance:
        type: num
        
    travel_summary:
        type: str</code></pre>
<h1>Die Logik traffic_info.py</h1>
<p>Als nächstes muss die aktuelle Fahrzeit zyklisch jede Minute über eine Logik vom Plugin abgefragt werden.</p>
<pre><code class="language-yaml">TrafficInfo:
    filename: traffic_info.py
    crontab: '* * * *'</code></pre>
<p>Der Code der Logik sieht wie folgt aus:</p>
<pre><code class="language-yaml">
# Codeblock 1 - Fahrt von der Arbeit nach Hause
if sh.now().hour &lt; 20 and sh.now().hour &gt; 14 and sh.now().weekday() in [0, 1, 2, 3, 4] and ('Arbeit' in sh.location.zone()):
    sh.location.calculate_way_home(1)
    destination = sh.location.home.lat() + "," + sh.location.home.lon()
    origin = sh.location.lat() + "," + sh.location.lon()
else:
    sh.location.calculate_way_home(0)

# Codeblock 2 - Fahrt von zu Hause in die Arbeit
if sh.now().hour &lt; 10 and sh.now().hour &gt; 4 and sh.now().weekday() in [0, 1, 2, 3, 4] and sh.location.zone() == 'Home':
    sh.location.calculate_way_work(1)
    destination = "Musterstraße 5, München"
    origin = sh.location.home.lat() + "," + sh.location.home.lon()
else:
    sh.location.calculate_way_work(0)

# Codeblock 3 - Default: Fahrt nach Hause von "irgendwo".
if not sh.location.calculate_way_work() and sh.location.zone() != 'Home':
    destination = sh.location.home.lat() + "," + sh.location.home.lon()
    origin = sh.location.lat() + "," + sh.location.lon()
    sh.location.calculate_way_home(1)

# Codeblock 4 - Wege- und Zeitbestimmung, ggf. Alarmmeldung oder Reset der Items
if sh.location.calculate_way_work() or sh.location.calculate_way_home():
    route = sh.traffic.get_route_info(origin, destination, False)
    if route is not None:
        summary = route['summary'] + ": %.1f km in %.0f min" % (round(route['distance'] / 1000, 2), round(route['duration_in_traffic'] / 60, 2))
        sh.location.travel_time(route['duration'])
        if 'duration_in_traffic' in route:
            sh.location.travel_time.in_traffic(route['duration_in_traffic'])
        sh.location.travel_distance(route['distance'])
        sh.location.travel_summary(summary)
        if 'Arbeit' in sh.location.zone() and sh.location.travel_time.in_traffic() &gt;= 2500 &gt; sh.location.travel_time.in_traffic.prev_value() and sh.now().hour &lt; 20 and sh.now().hour &gt; 14:
            sh.pushbullet.note("Alarm: Verkehr", "%s Min. Fahrzeit nach Hause!" % round((sh.location.travel_time.in_traffic() / 60), 2))
else:
    sh.location.travel_time('')
    sh.location.travel_time.in_traffic('')
    sh.location.travel_distance('')
    sh.location.travel_summary('-')
</code></pre>
<p>Im ersten Block wird geprüft, ob die aktuelle Uhrzeit zwischen 14:00 und 20:00 liegt (Zeitraum für die Prüfung bzgl. &#8222;Weg von der Arbeit nach Hause&#8220;) und, ob der aktuelle Tag Montag &#8211; Freitag ist. Zusätzlich wird geprüft, ob man sich in einer Zone befindet, deren Name  die Zeichenkette &#8222;Arbeit&#8220; enthält. Ist dies der Fall, so werden das boolsche Item <code>calculate_way_home</code> auf <code>True</code> und als <code>destination</code> die Home-Koordinate gesetzt.</p>
<p>Der zweite Codeblock macht das Gleiche, aber für die (bevorstehende) Anfahrt in die Arbeit von zu Hause aus (4:00-10:00 als Zeitintervall). Die aktuelle Zone enthält den String &#8222;Home&#8220;, als Destination wird die Adresse der Arbeitsstätte gesetzt. <code>calculate_way_work</code> wird auf <code>True</code> gesetzt.</p>
<p>Im dritten Codeblock wird nun geprüft, ob man auf dem Weg zur Arbeit und nicht zu Hause ist. Ist man dies nicht, so wird auf Basis der aktuellen Position (die nicht zwingend die Arbeitsstätte ist) immer die Fahrzeit nach Hause berechnet. Diese kann bspw. in der Visu angezeigt werden. <code>calculate_way_home</code> wird für die nachfolgende Berechnung ebenfalls auf <code>True</code> gesetzt.</p>
<p>Im vierten Codeblock wird nun das Traffic Plugin aufgerufen. Über <code>get_route_info(origin, destination, False)</code> werden die Daten zur Route von der aktuellen Position zum Ziel abgefragt. Das Item <code>location.travel_time</code> wird mit der reinen Fahrzeit <em><strong>ohne</strong> Berücksichtigung der aktuellen Verkehrlage</em>, das Unteritem <code>location.travel_time.in_traffic</code> <em><strong>mit</strong> Berücksichtigung der aktuellen Verkehrslage</em> befüllt.<br />
In das Item <code>location.travel_distance</code> wird die aktuellen Distanz geschrieben.<br />
In <code>location.travel_summary</code> landet ein kurzes Summary als String.<br />
Zuletzt wird eine Alarmmeldung über das <a href="https://www.smarthomeng.de/user/plugins_doc/config/pushbullet.html" target="_blank" rel="noopener">Pushbullet Plugin</a> versendet: <code>sh.pushbullet.note("Alarm: Verkehr", "%s Min. Fahrzeit nach Hause!" % round((sh.location.travel_time.in_traffic() / 60), 2))</code></p>
<p>Das Ergebnis ist hier zu sehen:<br />
<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/04/Screenshot_20180422-072146.png" alt="" class="alignnone wp-image-1629" width="332" height="590" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/04/Screenshot_20180422-072146.png 1440w, https://www.smarthomeng.de/wp-content/uploads/2018/04/Screenshot_20180422-072146-169x300.png 169w, https://www.smarthomeng.de/wp-content/uploads/2018/04/Screenshot_20180422-072146-768x1365.png 768w, https://www.smarthomeng.de/wp-content/uploads/2018/04/Screenshot_20180422-072146-576x1024.png 576w" sizes="(max-width: 332px) 100vw, 332px" /></p>
<p>Neben der in diesem Artikel gezeigten Grundfunktionalität, lassen sich mit dem Plugin bzw. der Directions API auch noch deutlich mehr Informationen, wie bspw. die vollständigen Routing-Informationen zum Zielort, bestimmen.</p>
<p>Ich persönlich kombiniere einige dieser Daten mit der Anzeige meiner Position und der Wegstrecke nach Hause in Google Maps. Hierfür habe ich ein <a href="https://www.smartvisu.de" target="_blank" rel="noopener">smartVISU</a> Widget geschrieben. Dies ist Thema eines zukünftigen Artikels.</p>
<p><em>(Die in diesem Artikel verwendeten Screenshots wurden selber erstellt. Das Titelbild ist unter der Creative Commons Zero (CC0) Lizenz veröffentlicht und wurde von www.pexels.com bezogen.)</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/das-traffic-plugin-am-beispiel-eines-staualarms/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Vom Winde verweht</title>
		<link>https://www.smarthomeng.de/vom-winde-verweht</link>
					<comments>https://www.smarthomeng.de/vom-winde-verweht#comments</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Thu, 19 Apr 2018 17:55:38 +0000</pubDate>
				<category><![CDATA[Beispiel-Implementierungen]]></category>
		<category><![CDATA[Plugins]]></category>
		<category><![CDATA[Beaufort]]></category>
		<category><![CDATA[Logik]]></category>
		<category><![CDATA[smartVISU]]></category>
		<category><![CDATA[Windrichtung]]></category>
		<category><![CDATA[Windstärke]]></category>
		<category><![CDATA[Wundergrund]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=1516</guid>

					<description><![CDATA[Dieser Artikel beschäftigt sich mit dem Thema Wind und was sich daraus alles über Plugins und Logiken am Ende in der smartVISU realisieren lässt. Anmerkung: mittlerweile sind die API Keys für Wundergrund leider nicht mehr kostenlos! Die Anleitung muss also mit einem alternativen Plugin wie Darksky oder OpenWeatherMap durchgeführt werden!<a class="moretag" href="https://www.smarthomeng.de/vom-winde-verweht"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Dieser Artikel beschäftigt sich mit dem Thema Wind und was sich daraus alles über Plugins und Logiken am Ende in der smartVISU realisieren lässt.</p>
<p><strong>Anmerkung:</strong> mittlerweile sind die API Keys für Wundergrund leider nicht mehr kostenlos! Die Anleitung muss also mit einem alternativen Plugin wie <a href="https://github.com/smarthomeNG/plugins/tree/master/darksky" target="_blank" rel="noopener">Darksky</a> oder <a href="https://github.com/smarthomeNG/plugins/tree/develop/openweathermap" target="_blank" rel="noopener">OpenWeatherMap</a> durchgeführt werden!</p>
<p>Basis des Artikels sind Daten zu Windrichtung und Windgeschwindigkeit über das Wundergrund Plugin. Es lassen sich aber bspw. auch Daten der KNX Wetterstation verwenden, sofern vorhanden.</p>
<h1>Das Wundergrund Plugin konfigurieren</h1>
<p>Die automatisch generierte Doku zum Wundergrund Plugin findet sich unter <a href="https://www.smarthomeng.de/user/plugins_doc/config/wunderground.html" target="_blank" rel="noopener">https://www.smarthomeng.de/user/plugins_doc/config/wunderground.html</a>.</p>
<p>Das Plugin liegt auf Github unter <a href="https://github.com/smarthomeNG/plugins/tree/master/wunderground" target="_blank" rel="noopener">https://github.com/smarthomeNG/plugins/tree/master/wunderground</a>.</p>
<p>Als Erstes muss das Plugin in der <code>etc/plugin.yaml</code> eingetragen werden. Der <code>apikey</code> kann kostenlos <span style="color: #ff0000;">(Update: leider sind die API Keys inzwischen nicht mehr kostenlos!)</span> über <a href="https://www.wunderground.com/weather/api/d/pricing.html" target="_blank" rel="noopener">https://www.wunderground.com/weather/api/d/pricing.html</a> beantragt werden. Es ist darauf zu achten, dass das Updateintervall eine bestimmte Zeitdauer nicht überschreitet, speziell, wenn man Wundergrund auch direkt in der smartVISU nutzt. Das Limit sind 500 Calls pro Tag bzw. 10 Calls pro Minute. Das Default-Updateintervall für das Plugin sind 600 Sekunden, also einmal in 10 Minuten. Per <code>cycle</code> Attribut ließe sich dieses in der <code>plugin.yaml</code> anpassen.</p>
<p>Als <code>location</code> setzt man bspw. seinen Ort, es sind hier neben dem Namen aber auch Geokoordinaten möglich.</p>
<pre><code class="language-yaml">
wundergrund_wetter:
    class_name: Wunderground
    class_path: plugins.wunderground
    apikey: 'xxxxyyyyxxxxyyyy'
    language: 'de'
    location: 'Germany/Hamburg'
    item_subtree: weather.wundergrund
    # cycle: 600
</code></pre>
<p><code>item_subtree</code> ist der Ast im Itemtree, unter dem die Wundergrund-relevanten Items liegen. Im Beispiel ist es <code>weather.wundergrund</code>.<br />
Die notwendigen Items sehen wie folgt aus:</p>
<pre><code class="language-yaml">
%YAML 1.1
---
# items/wetter.yaml
weather:

    wundergrund:

        windrichtung:
            type: str
            wug_matchstring: current_observation/wind_dir

        windrichtung_grad:
            type: num
            wug_matchstring: current_observation/wind_degrees

        windgeschwindigkeit:
            type: num
            value: -9999
            wug_matchstring: current_observation/wind_kph
            wug_datatype: positive

            ms:
                type: num
                eval: (sh.weather.wundergrund.windgeschwindigkeit()/3.6)
                eval_trigger: weather.wundergrund.windgeschwindigkeit

            beaufort:
                type: num

            string:
                type: str
               
        windboeen:
            type: num
            value: -9999
            wug_matchstring: current_observation/wind_gust_kph
            wug_datatype: positive
</code></pre>
<p><strong>Tipp:</strong> Neben dem Wind gibt es über Wundergrund noch viele weitere Wetterdaten.</p>
<h1>Anzeige der Windrichtung in der smartVISU</h1>
<p>Die Windrichtung lässt sich nun in der smartVISU 2.9 sehr einfach ausgeben:</p>
<pre><code class="language-twig">
{{ icon.compass('weather.wundergrund.windrichtung_compass', '', 'weather.wundergrund.windrichtung_grad',  '0', '360', '') }}
{{ basic.print('weather.wundergrund.windrichtung', 'weather.wundergrund.windrichtung', 'text') }}
</code></pre>
<p>Das Ergebnis ist eine sehr schöne Darstellung der Windrichtung:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/04/windrichtung.jpg" alt="" class="alignnone size-full wp-image-1521" width="124" height="66" /></p>
<h1>Windstärke auswerten und in der smartVISU anzeigen</h1>
<p>Als nächstes soll die Windstärke ausgewertet werden. Dafür dient die nach Sir Francis Beaufort benannte Beaufortskala, die Windstärken in 13 Bereichte von 0 (Windstille) bis 12 (Orkan) klassifiziert.</p>
<p>Für diese Skala ist der Wert im Item <code>weather.wundergrund.windgeschwindigkeit.ms</code> notwendig, das via Eval-Ausdruck die Windgeschwindigkeit von Kilometer pro Stunde in Meter pro Sekunde umrechnet (<code>sh.weather.wundergrund.windgeschwindigkeit()/3.6</code>). Die Umrechnung triggert jedes Mal, wenn sich <code>weather.wundergrund.windgeschwindigkeit</code> verändert.</p>
<p>Für die Auswertung der Windstärke muss unter <code>logics/wind.py</code> eine neue Logik erstellt werden, die die Items <code>weather.wundergrund.windgeschwindigkeit.string</code> und <code>weather.wundergrund.windgeschwindigkeit.beaufort</code> befüllt:<br />
Der Eintrag in der <code>etc/logic.yaml</code> sieht wie folgt aus:</p>
<pre><code class="language-yaml">
WindLogic:
    crontab: init
    filename: wind.py
    watch_item: weather.wundergrund.windgeschwindigkeit.ms
</code></pre>
<p>Die Logik löst also jedes Mal aus, wenn sich <code>weather.wundergrund.windgeschwindigkeit.ms</code> ändert.</p>
<p>Der Code der Logik in der Datei <code>logics/wind.py</code> mappt nun die Windgeschwindigkeit in Meter pro Sekunde auf die Beaufortskala:</p>
<pre><code class="language-python">
if sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 0.3:
    sh.weather.wundergrund.windgeschwindigkeit.string("Windstille")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(0)
elif 0.3 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 1.6:
    sh.weather.wundergrund.windgeschwindigkeit.string("leiser Zug")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(1)
elif 1.6 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 3.4:
    sh.weather.wundergrund.windgeschwindigkeit.string("leichte Brise")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(2)
elif 3.4 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 5.5:
    sh.weather.wundergrund.windgeschwindigkeit.string("schwacher Wind")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(3)
elif 5.5 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 8.0:
    sh.weather.wundergrund.windgeschwindigkeit.string("mäßiger Wind")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(4)
elif 8.0 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 10.8:
    sh.weather.wundergrund.windgeschwindigkeit.string("frischer Wind")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(5)
elif 10.8 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 13.9:
    sh.weather.wundergrund.windgeschwindigkeit.string("starker Wind")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(6)
elif 13.9 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 17.2:
    sh.weather.wundergrund.windgeschwindigkeit.string("steifer Wind")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(7)
elif 17.2 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 20.8:
    sh.weather.wundergrund.windgeschwindigkeit.string("stürmischer Wind")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(8)
elif 20.8 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 24.5:
    sh.weather.wundergrund.windgeschwindigkeit.string("Sturm")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(9)
elif 24.5 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 28.5:
    sh.weather.wundergrund.windgeschwindigkeit.string("schwerer Sturm")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(10)
elif 28.5 &lt;= sh.weather.wundergrund.windgeschwindigkeit.ms() &lt; 32.7:
    sh.weather.wundergrund.windgeschwindigkeit.ms.string("orkanartiger Sturm")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(11)
else:
    sh.weather.wundergrund.windgeschwindigkeit.string("Orkan")
    sh.weather.wundergrund.windgeschwindigkeit.beaufort(12)
</code></pre>
<p>Damit die Logik effektiv wird, muss SmartHomeNG neu gestartet oder die Logik via Backend-Plugin aktiviert werden.</p>
<p>Sinnvollerweise kann diese Logik auch in einer Funktion gekapselt werden. Dies ist an einem Alternativansatz, der mit einer Lookup-Tabelle arbeitet, im Folgenden dargestellt:</p>
<pre><code class="language-python"><span class="pl-c">#!/usr/bin/env python3</span>
<span class="pl-c"># beaufort3.py</span>

<span class="pl-k">def</span> <span class="pl-en">get_beaufort</span>(<span class="pl-smi">speed</span>):
    <span class="pl-s"><span class="pl-pds">"""</span></span>
<span class="pl-s">    :parameter speed: windspeed in meter per second</span>
<span class="pl-s">    :return: a tuple of a string with the (german) description and an integer with beaufort speed</span>
<span class="pl-s">    <span class="pl-pds">"""</span></span>
    table <span class="pl-k">=</span> [ 
        (  <span class="pl-c1">0.3</span>, <span class="pl-s"><span class="pl-pds">"</span>Windstille<span class="pl-pds">"</span></span>,<span class="pl-c1">0</span>),
        (  <span class="pl-c1">1.6</span>, <span class="pl-s"><span class="pl-pds">"</span>leiser Zug<span class="pl-pds">"</span></span>,<span class="pl-c1">1</span>),
        (  <span class="pl-c1">3.4</span>, <span class="pl-s"><span class="pl-pds">"</span>leichte Brise<span class="pl-pds">"</span></span>,<span class="pl-c1">2</span>),
        (  <span class="pl-c1">5.5</span>, <span class="pl-s"><span class="pl-pds">"</span>schwacher Wind<span class="pl-pds">"</span></span>,<span class="pl-c1">3</span>),
        (  <span class="pl-c1">8.0</span>, <span class="pl-s"><span class="pl-pds">"</span>mäßiger Wind<span class="pl-pds">"</span></span>,<span class="pl-c1">4</span>),
        ( <span class="pl-c1">10.8</span>, <span class="pl-s"><span class="pl-pds">"</span>frischer Wind <span class="pl-pds">"</span></span>,<span class="pl-c1">5</span>),
        ( <span class="pl-c1">13.9</span>, <span class="pl-s"><span class="pl-pds">"</span>starker Wind<span class="pl-pds">"</span></span>,<span class="pl-c1">6</span>),
        ( <span class="pl-c1">17.2</span>, <span class="pl-s"><span class="pl-pds">"</span>steifer Wind<span class="pl-pds">"</span></span>,<span class="pl-c1">7</span>),
        ( <span class="pl-c1">20.8</span>, <span class="pl-s"><span class="pl-pds">"</span>stürmischer Wind<span class="pl-pds">"</span></span>,<span class="pl-c1">8</span>),
        ( <span class="pl-c1">24.5</span>, <span class="pl-s"><span class="pl-pds">"</span>Sturm<span class="pl-pds">"</span></span>,<span class="pl-c1">9</span>),
        ( <span class="pl-c1">28.5</span>, <span class="pl-s"><span class="pl-pds">"</span>schwerer Sturm<span class="pl-pds">"</span></span>,<span class="pl-c1">10</span>),
        ( <span class="pl-c1">32.7</span>, <span class="pl-s"><span class="pl-pds">"</span>orkanartiger Sturm<span class="pl-pds">"</span></span>,<span class="pl-c1">11</span>),
        ( <span class="pl-c1">999</span>,  <span class="pl-s"><span class="pl-pds">"</span>Orkan<span class="pl-pds">"</span></span>,<span class="pl-c1">12</span>) ]
    
    <span class="pl-k">try</span>:
        description <span class="pl-k">=</span> <span class="pl-c1">min</span>(<span class="pl-c1">filter</span>(<span class="pl-k">lambda</span> <span class="pl-smi">x</span>: x[<span class="pl-c1">0</span>] <span class="pl-k">&gt;=</span> speed, table))[<span class="pl-c1">1</span>]
        bft <span class="pl-k">=</span> <span class="pl-c1">min</span>(<span class="pl-c1">filter</span>(<span class="pl-k">lambda</span> <span class="pl-smi">x</span>: x[<span class="pl-c1">0</span>] <span class="pl-k">&gt;=</span> speed, table))[<span class="pl-c1">2</span>]
        <span class="pl-k">return</span> description,bft
    <span class="pl-k">except</span> <span class="pl-c1">ValueError</span>:
        <span class="pl-k">return</span> <span class="pl-c1">None</span>, <span class="pl-c1">None</span>

decription, bft <span class="pl-k">=</span> get_beaufort(sh.weather.wundergrund.windgeschwindigkeit.ms())
sh.weather.wundergrund.windgeschwindigkeit.string(description)
sh.weather.wundergrund.windgeschwindigkeit.beaufort(bft)
</code></pre>
<p>&nbsp;</p>
<p>In der smartVISU können diese Werte nun wie folgt angezeigt werden:</p>
<pre><code class="language-twig">
{{ basic.symbol('', '', '', 'weather_wind_speed_bft') }}                       
Bft: {{ basic.print('wind_weatherstation_bfvalue', 'knx.weather.wind.beaufort') }}, 
{{ basic.print('wind_weatherstation_string', 'knx.weather.wind.string', 'text') }}
</code></pre>
<p>Das Ergebnis:</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/04/beaufort.jpg" alt="" class="alignnone size-full wp-image-1544" width="180" height="55" /></p>
<p><em>(Die in diesem Artikel verwendeten Screenshots wurden selber erstellt. Das Titelbild ist unter der Creative Commons Zero (CC0) Lizenz veröffentlicht und wurde von www.pexels.com bezogen.)</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/vom-winde-verweht/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
