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.
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.js
, custom.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 right
Code-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.
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)