Do-It-Yourself Hardware Heimautomatisierung

MagicMirror² auf dem Smart Mirror

MagicMirror² im Badezimmer

Dies ist ein Follow-up zu meinem Artikel über die Smart Mirror Software. Ich hatte die Software MagicMirror² seinerzeit schon erwähnt, hatte allerdings auf mirr.os gesetzt. mirr.os bot einen schnelleren Start, da die ganze Distribution nur auf eine SD-Karte geflasht werden muss. Allerdings wurde es mit der Zeit leider immer langsamer und stürzt regelmäßig ab. Außerdem hat es nie so richtig gut mit dem Nextcloud-Kalender zusammengearbeitet. Im Gegensatz zu mirr.os versteht sich MagicMirror² eher als Software, die auf einem OS wie Raspbian / Raspberry Pi OS installiert wird. Ferner setzt es deutlich stärker auf Config-Dateien denn auf ein Webinterface zur Konfiguration. Was beide gemein haben ist, dass im Endeffekt beide eine Chrome-Engine via electron bzw. nodejs ausführen.

Voraussetzungen

Ich beschreibe in diesem Artikel den Aufbau der Software auf dem Raspberry Pi (3B+). Ursprünglich hatte ich einen Raspberry Pi Zero (1st gen) im Einsatz. Mittlerweile empfehlen beide Projekte allerdings mindestens einen Pi 2. Wie ich meinen Badezimmerspiegelschrank umgebaut habe, habe ich in diesem Artikel abgehandelt.

Da ich mich verständlicherweise nicht mit dem Notebook ins Bad stellen will, hab ich einen Pi 3 in ein Gehäuse mit Touchscreen gesetzt. So kann ich den MagicMirror² und die Module direkt am Schreibtisch testen, bzw. kontrollieren.

MagicMirror2 auf der Entwicklungsplattform

Installation von MagicMirror² auf Raspberry Pi OS

Die Dokumentation des Projekts ist ziemlich gut, daher werde ich sie hier nicht wiederkäuen, sondern lediglich darauf verweisen. Da der electron app wrapper eine grafische Anwendung ist, sollte man ein volles Raspberry Pi OS Image aufgespielt haben. Also keine Lite Variante. Ob 32 oder 64 bit ist egal. Ich habe 64 bit gewählt. Es gibt eine „automatische“ Installation, die ein Script direkt in die Shell pipen will – was immer eine schlechte Idee ist. Siehe hackme.sh. Daher bin ich den Weg der manuellen Installation gegangen.

Ist die Installation vollzogen, kann man die Software wie folgt von der CLI starten. Das geht auch wenn man per SSH verbunden ist.

npm run start

Aktualisierung

Die Doku weist explizit darauf hin, vor einem Update config.jscustom.css and modules, zu sichern und das Update wie folgt anzustoßen.

git pull && npm install --only=prod --omit=dev

Die Config und Module sind im git repo in der Ignorierliste geführt, solange man also nur hier Änderungen vornimmt, sollte es keine git-Konflikte geben. Ansonsten könnte man sie mit git reset --hard zurücksetzen – was dann natürlich die eigenen Änderungen rückgängig macht.

Initialkonfiguration

Im Zuge der Installation wird eine default config.js kopiert, die man nun anpassen sollte. Insbesondere die Sprache und das Datums- und Zeitformat sollte man den eigenen Vorlieben anpassen. port, address und ipWhitelist sind für eine autarke Installation (ohne weitere Clients) gut vorkonfiguriert.

MagicMirror² Autostart einrichten

Hier habe ich mich ebenfalls voll an die Dokumentation gehalten und pm2 nachinstalliert und konfiguriert wie dort beschrieben.

Raspberry Pi spezifische Anpassungen

Von all den im Wiki erwähnten Pi-spezifischen Anpassungen brauchte ich tatsächlich nur die ersten 2. Nämlich die Aktivierung des OpenGL Treibers und die Displayroation mittels xrandr um 90°, da mein Display hochkant im Spiegel eingebaut ist.

Ich bin mir gar nicht 100% sicher, ob der OpenGL-Treiber nicht schon aktiv war, trotzdem sollte man das kontrollieren, da auch die Displayrotation nur mit diesem funktioniert. Dazu muss in der /boot/config.txt folgende Zeile stehen:

dtoverlay=vc4-kms-v3d

Für die Displayrotation muss die Datei ~/.config/lxsession/LXDE-pi/autostart editieren und folgende Zeile hinzufügen.

@xrandr --output HDMI-1 --rotate rightCode-Sprache: CSS (css)

Modulkonfiguration und Installation zusätzlicher Module

Jetzt komme ich eigentlich erst zum wirklich interessanten Teil. Dem Platzieren und Konfigurieren der Module. MagicMirror² kommt mit zwei Handvoll default Modulen, alle weiteren müssen nachinstalliert werden. Zumeist wird hier ein weiteres, projektspezifisches git Repository in modules/ entspackt und in der config/config.js eingebunden. Abhängigkeiten installiert man im Modulverzeichnis durch ein npm install nach.

MagicMirror² Screenshot
MagicMirror² Screenshot mit einigen default und einigen 3rd party Modulen.

Neben einigen default Modulen verwende ich derzeit diese hier:

https://github.com/fewieden/MMM-Fuel

https://github.com/leinich/MMM-homeassistant-sensors

https://github.com/frdteknikelektro/MMM-SimpleLogo

Die Konfigurationsdatei ist gut kommentiert und leicht lesbar. Allerdings kann man relativ einfach Fehler in die strikte Stuktur mit den Klammern einbauen, deswegen lege ich sie hier komplett ab. So kann man sich die Passagen rauskopieren, die man übernehmen möchte.

/* Magic Mirror Config Sample
 *
 * By Michael Teeuw https://michaelteeuw.nl
 * MIT Licensed.
 *
 * For more information on how you can configure this file
 * see https://docs.magicmirror.builders/getting-started/configuration.html#general
 * and https://docs.magicmirror.builders/modules/configuration.html
 */
let config = {
	address: "localhost", 	// Address to listen on, can be:
							// - "localhost", "127.0.0.1", "::1" to listen on loopback interface
							// - another specific IPv4/6 to listen on a specific interface
							// - "0.0.0.0", "::" to listen on any interface
							// Default, when address config is left out or empty, is "localhost"
	port: 8080,
	basePath: "/", 	// The URL path where MagicMirror is hosted. If you are using a Reverse proxy
					// you must set the sub path here. basePath must end with a /
	ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], 	// Set [] to allow all IP addresses
	//address: [ "localhost", "0.0.0.0", "::" ],
	//ipWhitelist: [],	
		// or add a specific IPv4 of 192.168.1.5 :
		// ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.1.5"],
		// or IPv4 range of 192.168.3.0 --> 192.168.3.15 use CIDR format :
		// ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.3.0/28"],

	useHttps: false, 		// Support HTTPS or not, default "false" will use HTTP
	httpsPrivateKey: "", 	// HTTPS private key path, only require when useHttps is true
	httpsCertificate: "", 	// HTTPS Certificate path, only require when useHttps is true

	language: "de",
	locale: "de-DE",
	logLevel: ["INFO", "LOG", "WARN", "ERROR"], // Add "DEBUG" for even more logging
	timeFormat: 24,
	units: "metric",
	// serverOnly:  true/false/"local" ,
	// local for armv6l processors, default
	//   starts serveronly and then starts chrome browser
	// false, default for all NON-armv6l devices
	// true, force serveronly mode, because you want to.. no UI on this device

	modules: [
		{
			module: "alert",
		},
		{
			module: "updatenotification",
			position: "top_bar"
		},
		{
			module: "clock",
			position: "top_left"
		},
		{
			module: 'MMM-SimpleLogo',
			position: 'top_center',   // This can be any of the regions.
			config: {
				// The config property is optional.
				// See 'Configuration options' for more information.
				// defaults to logo.png in modules/MMM-SimpleLogo/public/
				text: "",
				position: "center",
				width: "150px",
			}
		},
		{
			module: "calendar",
			header: "Termine",
			wrapEvents: true,
			wrapLocationEvents: true,
			position: "top_left",
			showLocation: true,
			config: {
				calendars: [
					{
						symbol: "home",
						url: "https://cloud.foo.de/remote.php/dav/calendars/bar/private?export",
						user: "user",
						pass: "pass",
						method: "basic",
					},
					{
						symbol: "industry",
						url: "https://owa.foo.de/owa/calendar/456354636735673/calendar.ics",
					}
				]
			}
		},
		{
			module: "weather",
			position: "top_right",
			config: {
				weatherProvider: "openweathermap",
				type: "current",
				location: "Münster",
				locationID: "2867543", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
				apiKey: "api_key"
			}
		},
		{
			module: "weather",
			position: "top_right",
			config: {
				weatherProvider: "openweathermap",
				type: "forecast",
				location: "Münster",
				locationID: "2867543", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
				apiKey: "api_key"
			}
		},
		{
			module: "compliments",
			position: "lower_third",
			config: {
				remoteFile: "../../../config/compliments_de.json"
			}
		},
		{
			module: "newsfeed",
			position: "bottom_bar",
			config: {
				feeds: [
					{
						title: "Westfälische Nachrichten",
						url: "https://www.wn.de/rss/feed"
					},
					{
						title: "golem.de",
						url: "https://rss.golem.de/rss.php?feed=ATOM1.0"
					}
				],
				showSourceTitle: true,
				showPublishDate: true,
				broadcastNewsFeeds: true,
				broadcastNewsUpdates: true
			}
		},
		{
			module: "MMM-Fuel",
			header: "Tankstellen",
			position: "bottom_right",
			shortenText: "25",
			showOpenOnly: true,
			config: {
				api_key: "api_key",
				lat: 51.9293805,
				lng: 7.6819101,
				types: ["diesel"],
			}
		},
		{
			module: 'MMM-homeassistant-sensors',
			position: 'bottom_left',
			config: {
				host: "ha_ip_or_fqdn",
				port: "443",
				https: true,
				token: "long_lived_api_token",
				updateInterval: 10000,
				prettyName: false,
				stripName: false,
				debuglogging: false,
				values: [{
						sensor: "sensor.temperatur_aussen",
						//alertThreshold: 50,
						icons: [{
								"default": "thermometer"
							}
						]
					}, {
						sensor: "sensor.luftfeuchtigkeit_aussen",
						icons: [{
								"default": "water-percent"
							}
						]
					}, {
						sensor: "sensor.line_power_total",
						name: "Strom gesamt",
						altertThreshold: 2500,
						icons: [{
								"default": "flash"
							}
						]
					}, {
						sensor: "sensor.speedtest_ping",
						name: "Internetlatenz",
						alertThreshold: 100,
						icons: [{
								"default": "speedometer"
							}
						]
					}, {
						sensor: "sensor.internet_speed_in",
						icons: [{
								"default": "cloud-download"
							}
						]
					}, {
						sensor: "sensor.internet_speed_out",
						icons: [{
								"default": "cloud-upload"
							}
						]
					}, {
						sensor: "sensor.luftdruck",
						icons: [{
								"default": "gauge"
							}
						]
					}, {
						sensor: "switch.badezimmer_ventilator",
						icons: [{
								"state_off": "fan-off",
								"state_on": "fan"
							}
						]
					}, {
						sensor: "sensor.badezimmer_temperatur",
						icons: [{
								"default": "thermometer"
							}
						]
					}, {
						sensor: "sensor.badezimmer_luftfeuchtigkeit",
						alertThreshold: 50,
						icons: [{
								"default": "water-percent"
							}
						]

					}
			]
			}
		},
	]
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = config;}

Code-Sprache: JavaScript (javascript)

Für das prominent platzierte compliments Modul habe ich eine relativ umfangreiche Sammlung an Sprüchen zusammengestellt. Als Grundlage hierzu diente diese Seite und die Dokumentation. Diese compliments_de.json habe ich im ebenfalls im config-Verzeichnis abgelegt.

{
        "anytime" : [
                "Du siehst heute großartig aus!",
                "Heute ist ein großartiger Tag!",
                "Du machst den Unterschied!",
                "Ein wunderschönes Lächeln, für einen wunderschönen Tag",
                "Schaust du noch immer in den Spiegel?",
                "Du siehst wieder toll aus.",
                "Lächle! Du kannst sie nicht alle töten",
                "Die Zone ruft Dich, Stalker."
        ],
        "morning" : [
                "Dein Haar ist mega heute!",
                "Beginne den Tag mit einem Lächeln",
                "Das wird ein schöner Tag!",
                "Ist der Tag nicht großartig?",
                "Mach dich bereit für einen großartigen Tag!",
                "Dein Bart sieht klasse aus!",
                "Erst mal einen leckeren Kaffee :-)",
        ],
        "afternoon" : [
                "Mache noch schnell deine Arbeit fertig ...",
                "Nachmittag, nicht Morgen, nicht Mittag",
                "Dieser Tag ist großartig",
                "Bergfest!",
                "Beinahe Abendessenszeit!",
                "Zieh Dir doch noch einen Kaffe..."
        ],
        "evening" : [
                "Nur noch ein paar Sachen erledigen ...",
                "Guten Apetit",
                "Yeah, Freizeit",
                "Schon zu Abend gegessen?",
                "Du hast den Tag überlebt.",
                "Möchtest Du einen Spaziergang machen?",
                "Hacken oder Faulenzen? Das ist die Frage",
                "Hast Du Dich heute schon genug bewegt?",
                "Komm, gönn Dir ein kühles Blondes"
        ],
        "....-01-01" : [
                "Frohes Neues Jahr!",
                "Und wieder ein Jahr rum..."
        ],
        "....-01-21" : [
                "Herzlichen Glückwunsch zum Geburtstag!",
                "Schon wieder ein Jahr älter",
                "Du alter Sack! Und kein Stück weiser..."
        ],
        "day_sunny" : [
                "Sonnenschein!",
		"Heute wird ein sonniger Tag",
		"Ein perfekter Tag für dieses 'draußen'",
		"Genieß das Wetter, Sonnenschein"
	],
        "snow" : [
		"Zeit für eine Schneematschschlacht",
		"Pass auf im Verkehr, es könnte glatt sein",
		"Es schneit, es schneit, kommt alle aus dem Haus..."
	],
	"rain" : [
		"Vergiss Deinen Regenschirm nicht",
		"Hoffentlich musst Du heute nicht raus"
	],
	"showers" : [
	        "Was ein Schietwetter",
	        "Hoffentlich musst Du heute nicht vor die Tür..."
	],
	"thunderstorm" : [
	        "Hoffentlich musst Du heute nicht raus...",
	        "Auf dass Dich der Blitz nicht beim Scheißen treffen möge!",
	        "Ungemütliches Wetter draußen"
	],
	"night_clear" : [
	        "Lass doch mal Deine Augen gen Himmel wandern",
	        "Du könntest die Sterne beobachten",
	        "Hast Du eine Sternschnuppe gesehen?",
	        "Halt doch mal nach der ISS Ausschau"
        ]
}

Code-Sprache: JSON / JSON mit Kommentaren (json)

Autor

Seit Kindheitstagen ist der Computer sein Begleiter. Was mit Linux anfing, wurde 2005 ein/e Beruf/ung, die weit über den Arbeitsplatz hinausgeht. Durch stetige Weiterentwicklung fasste er auch im *BSD Segment Fuß und bietet mittlerweile professionelle Lösungen im Bereich Hosting, Networking und Infrastruktur an. Als Ausgleich beschäftigt er sich neben Computerspielen mit der Fotografie.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.