Hintergrund: Es gibt schöne digitale Bilderrahmen fertig zu kaufen. Oftmals kommen diese mit einer Software wie Frameo und haben ein Android OS unter der Haube. Mit ADB lassen sich solche Produkte auch in Loxone einbinden und steuern, siehe hier.
Allerdings sind die Anpassungsmöglichkeiten sehr beschränkt. Daher wollte ich einen voll konfigurier- und steuerbaren selber bauen, und dabei beim Formfaktor schlank und beim Stromverbrauch gering bleiben. Folgende Anleitung mag vllt. jemandem helfen / inspirieren:
Hardware:
- Raspberry Pi "Zero 2 W" + Gehäuse (GeekPi Raspberry Zero 2 W Aluminium case, passive cooling), ca. 39,99€
- 15,6'' Tragbarer Monitor / Touch Screen, 1920x1080, 1,76 x 35,6 x 21,3 cm; 1,9 kg ca. 115€
- Netzteil: offizielles Raspberry Pi 5,1V, 3A, USB-C, ca. 13 €
- Kleinteile / Kabel: ca. 40€
- MicroSD Karte, 16GB, ca. 10€
- HDMI: 20cm FPC Flachbandkabel + HDMI Standard male down, mini HDMI straight: ca. 3 x 5,99€ = ca. 18€
- Adapter: USB-C 180 grad (2x): ca. 6,64€
- Kabel: Micro-USB - USB-C, 15,2 cm: 5,97 €
- Y-Kabel: USB-C → USB-C + Micro USB (5V power only, no PD)
Betriebssystem / OS:
- microSD-Karte in Laptop stecken
- download, install & run: "Raspberry Pi Imager" für PC/Mac
- Raspberry Pi Device → Pi Zero 2 W,
- Raspberry Pi OS (64bit) inkl. Desktop;
- Storage: Adapter/SD-Karte auswählen
- Adjust Setting:
- hostname: z.B. raspberrypi.local;
- username: NAME; password: PW;
- WLan einrichten: set your WiFi SSID + Password
- Ausführen: ca. 10 mins
- microSD-Karte in Raspberry stecken → Power on.
- IP fest vergeben am Router: <IP>
- ssh NAME@<IP>(bei SSH Problemen nach neuem Aufsetzen: ssh-keygen -R <IP> → yes) → Passwort eingeben: PW
- Raspberry auf den neusten Stand bringen: sudo apt-get update (ca. 1min), dann: sudo apt-get upgrade (ca. 1h). Dann: sudo reboot
- disable BLE: sudo nano /boot/firmware/config.txt → add: dtoverlay=disable-bt. Dann: sudo reboot
- disable WiFi Power Saving Mode: check: iwconfig → "Power Management: On" → schlecht!
sudo crontab -e → opens the root crontab in your chosen editor...
# add the following line at the bottom of the root crontab:
@reboot /usr/sbin/iw wlan0 set power_save off > /home/<user>/power_save_log.txt 2>&1 - ggf. sudo nmtui → Netzwerkeinstellungen konfigurieren / editieren: Netzwerk mit nur 2,4GHz auswählen!
- MagicMirror2 installieren: link (siehe Abschnitt: "Automatic Installation Scripts") → ca. 30min. Installiert npm / node. Auch das Skript für pm2 ausführen (siehe "Add using pm2 to autostart MagicMirror at bootup"). Dann: sudo reboot.
- MagicMirror Module (MMMs) installieren (z.B.: MMM-Wallpaper → für Bilder-Slideshow im Hintergrund, MMM-CalDAV → für iCloud-Kalender-Einbindung: .env-Datei ist anzupassen!, MMM-CalendarExt3Agenda → Agenda Anzeige + Mini-Kalender, MMM-MoonPhase → Mondphasen-Anzeige, MMM-Pinfo → System-Monitoring, MMM-Remote-Control → macht den Bilderrahmen fernsteuernbar!, MMM-TouchButton → Steuerung über Touch-Display, planetrise → Planeten Auf-&Untergänge): jeweils wie folgt:
- cd ~/MagicMirror/modules
- git clone https://github.com/someone/MMM-XYZ.git (hier den link jeweils anpassen)
- cd MMM-XYZ (den Ordner jeweils anpassen)
- npm install
- Dann: pm2 start MagicMirror ("pm2 stop MagicMirror" → stoppt die App, "pm2 status" zeigt den Status)
Wenn man eine Synology NAS hat, so kann man einen Ordner für NFS freigeben. Mit der "Drive" App fürs Smartphone kann man dann, sofern bei den Smartphone-Kamera-Einstellungen "maximale Kompatibilität" ausgewählt wurde (→ .jpegs statt .heics), Fotos auf seine NAS hochladen und den entsprechenden Ordner dann per symlink live und direkt als Basis für das MMM-Wallpaper Modul nutzen.
Stichwort hier: Permanent mount eines NFS Ordners: link, link2, link3 → hier einen Ordner wählen auf den man in der Synology Smart-Phone App Zugriff hat. z.B. einen erstellten "Bilderrahmen"-Ordner im Photos Ordner im home-Verzeichnis.
sudo nano /etc/fstab → dann folgendes hinzufügen um bei jedem Neustart den Ordner gemounted zu haben:
<IPSynology>:/volume1/homes /mnt/NAShomes nfs auto,hard,nofail 0 0
Dann einen symlink erstellen, um im MagicMirror-Modul die NAS-Bilder zu sehen. Den "ImagesL"-Ordner kann man dann im Modul "MMM-Wallpaper Modul" als Quelle für "lokale Dateien" angeben:
ln -s /mnt/NAShomes/SynologyNAME/Photos/PhotoLibrary/Bilderrahmen ~/MagicMirror/ImagesL
Auch möglich: manuell per sftp:
e.g. für SimpleBGSlideshow: imagePaths: [], // resolves relative to MM root. So 'Images' => ~MagicMirror/Images
Transfer images via sftp (link) → put -r localFolder
sftp USERNAME@<IP>
cd MagicMirror/
lcd Documents/FolderContainingImagesFolder
put -r ImagesL
exit
→ Folder named "Images" copied from local PC/Laptop to remote Raspberry /MagicMirror/ImagesL
Einstellungen des MM:
Die Einstellungen des MagicMirrors, also das Setup / Aussehen / Positionierung der "Module" (der installierten MMMs) des digitalen Bilderrahmen stellt man dann über die Datei ~MagicMirror/config/config.js ein.
Hierzu:
cd ~MagicMirror/config
nano config.js
Speichern mit Ctrl + O, Schließen mit Ctrl + O.
Anbei eine Beispielhafte config.js welche die obigen Module konfiguriert.
Hierbei sind beispielhaft 4 iCloud-Kalender eingebunden (Icons und Farben sind konfigurierbar). Die Wetter-Vorschau erscheint im Agenda-Modul.
/* Config Sample * * For more information on how you can configure this file * see https://docs.magicmirror.builders/configuration/introduction.html * and https://docs.magicmirror.builders/modules/configuration.html * * You can use environment variables using a `config.js.template` file instead of `config.js` * which will be converted to `config.js` while starting. For more information * see https://docs.magicmirror.builders/configuration/introduction.html#enviromnent-variables */ let config = { address: "0.0.0.0", // 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", "<IPLoxberry>"], // Set [] to allow all IP addresses // 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", // this variable is provided as a consistent location // it is currently only used by 3rd party modules. no MagicMirror code uses this value // as we have no usage, we have no constraints on what this field holds // see https://en.wikipedia.org/wiki/Locale_(computer_software) for the possibilities logLevel: ["INFO", "LOG", "WARN", "ERROR"], // Add "DEBUG" for even more logging timeFormat: 24, units: "metric", modules: [ { module: "alert", }, { module: "clock", position: "top_left" }, { module: "calendar", header: "Feiertage", //position: "bottom_center", config: { broadcastPastEvents: true, // <= IMPORTANT to see past events maximumEntries: 2, fadePoint: 0.25, calendars: [ { fetchInterval: 605000, //7 * 24 * 60 * 60 * 1000, symbol: "calendar-xmark", name: "DE-Feiertage", color: "red", url: "https://www.feiertage-deutschland.de/kalender-download/ics/feiertage-deutschland.ics" } ] } }, { module: "calendar", header: "iCloud: 1", //position: "bottom_center", config: { broadcastPastEvents: true, // <= IMPORTANT to see past events maximumEntries: 6, fadePoint: 0.5, calendars: [ { fetchInterval: 630000, symbol: "house", name: "Cal1N", url: "http://localhost:8080/CALDAV/ICLOUD_Cal1.ics", color: "SkyBlue", broadcastPastEvents: true, // <= IMPORTANT to see past events auth: { // REQUIRED user: "user", // DEFINED in .env file. pass: "pass", method: "basic" } }, ] } }, { module: "calendar", header: "iCloud: 2", //position: "bottom_center", config: { broadcastPastEvents: true, // <= IMPORTANT to see past events maximumEntries: 4, fadePoint: 0.5, calendars: [ { fetchInterval: 620000, symbol: "briefcase", name: "Cal2N", url: "http://localhost:8080/CALDAV/ICLOUD_Cal2.ics", color: "Orange", broadcastPastEvents: true, // <= IMPORTANT to see past events auth: { // REQUIRED user: "user", // DEFINED in .env file. pass: "pass", method: "basic" } }, ] } }, { module: "calendar", header: "iCloud: 3", //position: "bottom_center", config: { broadcastPastEvents: true, // <= IMPORTANT to see past events maximumEntries: 4, fadePoint: 0.5, calendars: [ { fetchInterval: 610000, symbol: "school", name: "Cal3N", url: "http://localhost:8080/CALDAV/ICLOUD_Cal3.ics", color: "Orchid", broadcastPastEvents: true, // <= IMPORTANT to see past events auth: { // REQUIRED user: "user", // DEFINED in .env file. pass: "pass", method: "basic" } }, ] } }, { module: "calendar", header: "iCloud: 4", //position: "bottom_center", config: { broadcastPastEvents: true, // <= IMPORTANT to see past events maximumEntries: 4, fadePoint: 0.5, calendars: [ { fetchInterval: 600000, symbol: "trash", name: "Cal4N", url: "http://localhost:8080/CALDAV/ICLOUD_Cal4.ics", color: "Gray", broadcastPastEvents: true, // <= IMPORTANT to see past events auth: { // REQUIRED user: "User", // DEFINED in .env file. pass: "pass", method: "basic" } }, ] } }, { module: "MMM-CalendarExt3Agenda", position: "top_left", header: "Agenda", refreshInterval: 300000, config: { instanceId: "basicCalendar", locale: "de-DE", firstDayOfWeek: 1, startDayIndex: 0, endDayIndex: 7, calendarSet: ["DE-Feiertage", "Cal1N", "Cal2N", "Cal3N", "Cal4N"], } }, { module: "weather", position: "top_right", config: { weatherProvider: "openmeteo", showFeelsLike: false, type: "current", lat: LAT, lon: LON } }, { module: "weather", //position: "top_right", header: "Wettervorhersage", config: { weatherProvider: "openmeteo", type: "forecast", lat: LAT, lon: LON } }, { module: "MMM-Wallpaper", position: "fullscreen_below", config: { // See "Configuration options" for more information. source: "local:/home/NAME/MagicMirror/ImagesL", updateInterval: 600 * 1000, slideInterval: 60 * 1000, crossfade: false, filter: "", //"grayscale(0.5) brightness(0.5)" caption: false, //recurseLocalDirectories: true, //shuffle: true, } }, { module: "planetrise", position: "top_right", // This can be any of the regions. header: "Planet Rise", config: { // Place the latitude and longitude of your mirror latitude: LAT, longitude: LON, bodies: {'Sun': '☉', 'Moon': '☽', //'Mercury': '☿', 'Venus': '♀', 'Mars': '♂', 'Jupiter': '♃', 'Saturn': '♄', } } }, { module: "MMM-MoonPhase", position: "top_right", config: { updateInterval: 43200000, hemisphere: "N", resolution: "detailed", title: true, phase: true, nextFull: false, age: false, phaseAge: true, phaseAgeTotal: true, size: 75, moonAlign: "end", textAlign: "end", alpha: 0.7, riseAndSet: { display: false, lat: LAT, lon: LON, gmtOffset: 2.0 } } }, { module: "MMM-CalDAV", // This module works in background, so `position` is not needed. config: { timeRangeStart: -30, // Get events from 30 days before servers: [ { // example of icloud or basic auth envPrefix: "ICLOUD_", // prefix for identifying each server serverUrl: "https://caldav.icloud.com", // Ask to your CALDAV provider targets: ["Cal1", "Cal2", "Cal3", "Cal4"], }, ], } }, { module: 'MMM-Pinfo', position: 'bottom_right', config: { refresh: 5000, valueAlign: "right", DEVICE: { displayModel: true, displaySerial: false }, NETWORK: { displayIPv4: true }, CPU: { displayUsage: true }, UPTIME: { displayUptime: true }, WARNING: { enable: true, interval: 1000 * 60 * 5, check: { CPU_TEMP: 65, CPU_USAGE: 75, RAM_USED: 80, STORAGE_USED: 80 } }, } }, { module: 'MMM-Remote-Control', //position: 'bottom_right', // you can hide this module afterwards from the remote control itself config: { customCommand: { monitorOnCommand: 'wlr-randr --output HDMI-A-1 --on', monitorOffCommand: 'wlr-randr --output HDMI-A-1 --off'}, // Optional, See "Using Custom Commands" below showModuleApiMenu: true, // Optional, Enable the Module Controls menu secureEndpoints: false, // Optional, See API/README.md pm2ProcessName: 'MagicMirror', } }, { module: "MMM-TouchButton", position: "bottom_left", config: { buttons: [ { name: "Shutdown", title: "Shutdown", icon: "fa fa-power-off", command: "sudo", args: "shutdown -h now" }, { name: "MMStop", title: "Stop MM", icon: "fa fa-stop", command: "pm2 stop MagicMirror", }, { name: "Next", title: "Next Image", icon: "fa fa-arrow-right", notification: "LOAD_NEXT_WALLPAPER", }, ] }, }, ] }; /*************** DO NOT EDIT THE LINE BELOW ***************/ if (typeof module !== "undefined") { module.exports = config; }
Ferner kann man die Schriftarten und weitere Details in der custom.css Datei anpassen. Dazu:
cd ~MagicMirror/css
nano custom.css
z.B.: wie folgt:
.normal, .dimmed, header, body { color: #fff; } .module.MMM-CalendarExt3Agenda { background-color:rgba(0,0,0,0.6); border-radius:8px; padding:8px; } .MMM-Pinfo { opacity: 0.5; } :root { --gap-body-top: 30px; --gap-body-right: 30px; --gap-body-bottom: 30px; --gap-body-left: 30px; } .clock .time { font-weight: bold; } .weather .bright { font-weight: bold; }
Ferngesteuert werden kann der digitale Bilderrahmen über das Module MMM-Remote-Control, welches eine http-API bereitstellt. Folgende eingaben an einem Terminal (auf Raspberry oder sonstwo) sollten dann funktionieren:
Restart MM:
curl -X GET 'http://<IP>:8080/api/restart'
BRIGHTNESS Control:
curl -X GET 'http://<IP>:8080/api/brightness/X' (X=0..100)
Monitor ON/OFF: (link)
curl -X GET 'http://<IP>:8080/api/monitor/off' bzw. on
Next image (for "MMM-Wallpaper" module, via notification):
curl -X GET http://<IP>:8080/api/notification/LOAD_NEXT_WALLPAPER
Background ON / OFF bzw. Images Slideshow ON / OFF:
curl -X GET 'http://<IP>:8080/remote?action=SHOW&module=module_10_MMM-Wallpaper'
curl -X GET 'http://<IP>:8080/remote?action=HIDE&module=module_10_MMM-Wallpaper'
EXTRA MODULES OFF:
curl -X GET 'http://<IP>:8080/remote?action=HIDE&module=module_11_planetrise' 'http://<IP>:8080/remote?action=HIDE&module=module_12_MMM-MoonPhase' 'http://<IP>:8080/remote?action=HIDE&module=module_7_MMM-CalendarExt3Agenda' 'http://<IP>:8080/remote?action=HIDE&module=module_14_MMM-Pinfo' 'http://<IP>:8080/remote?action=HIDE&module=module_16_MMM-TouchButton' 'http://<IP>:8080/remote?action=HIDE&module=module_15_MMM-Remote-Control'
EXTRA MODULES ON:
curl -X GET 'http://<IP>:8080/remote?action=SHOW&module=module_11_planetrise' 'http://<IP>:8080/remote?action=SHOW&module=module_12_MMM-MoonPhase' 'http://<IP>:8080/remote?action=SHOW&module=module_7_MMM-CalendarExt3Agenda' 'http://<IP>:8080/remote?action=SHOW&module=module_14_MMM-Pinfo' 'http://<IP>:8080/remote?action=SHOW&module=module_16_MMM-TouchButton' 'http://<IP>:8080/remote?action=SHOW&module=module_15_MMM-Remote-Control'
Nachricht auf den Bildschirm anzeigen:
curl -X GET 'http://<IP>:8080/api/module/alert/showalert?message=Hello&timer=2000'
natürlich kann man diese Befehle auch über virtuelle Ausgänge in Loxone absetzen.
Fazit:
Der Raspberry Pi Zero 2 W ist etwas träge. Der Raspi-Bilderrahmen ist vollumfänglich konfigurierbar und erlaubt mehr als der fertige Bildschirm von der Stange. Die Einrichtung dauerte allerdings eine ganze Weile bis die richtige Kombination von Modulen und Einstellungen gefunden war. Auch die Stromversorgung scheint wichtig. Aktuell ist der Bildschirm direkt per Raspberry Pi original Netzteil (USB-C) angeschlossen und der Raspberry von dort per USB-C → USB Micro. Update: jetzt sind der Pi und der Bildschirm über ein Y-Splitter-Kabel beide direkt an das Netzteil angeschlossen.
Bilder / Ergebnis
Anbei ein Beispiel für einen gekauften Bilderrahmen vs. den Bilderrahmen Marke Eigenbau..
Stromverbrauch: ca. 6W - 8W (Raspberry + 15,6'' Display) / 2W Standby vs. ca. 17W - 20W für den gekauften Bilderrahmen (21,5'') / 2W Standby.
Kommentar