Wecker

Tick, Tock, SoundClock

Dieses Projekt habe ich schon vor längerer Zeit abgeschlossen. Einen ersten Versuch – Wecker in kleinem furnierten Gehäuse – hatte ich Ende 2020 unternommen, jedoch irgendwann wegen eines für mich nicht lösbaren Problems liegen lassen. Etwa ein Jahr später habe ich dann neu begonnen und schließlich auch das Problem einwandfrei identifizieren und lösen können. In dieser zweiten Version habe ich den Wecker in ein Nachtschränkchen integriert, das nun über meinem Bett hängt.

Ich hatte damals schon einiges dokumentiert, aber leider nicht so strukturiert und ausführlich wie ich mir das wünschen würde, sodass es mit etwas Aufwand (und Mut zur Lücke) verbunden ist, diesen Beitrag zu schreiben. Es stört mich aber, dass ich den Beitrag damals nicht zu Ende gebracht habe und ich glaube, dass der ein oder andere Abschnitt für andere hilfreich oder interessant sein könnte.

Das Datum des Beitrags bezieht sich auf die Fertigstellung des Nachtschränkchens mit integriertem Wecker, wobei ich später noch Verbesserungen und Bugfixes an der Software vorgenommen habe.

Youtube-Überblick über das abgeschlossene Projekt


Welchen Nutzen hat ein eigenständiger Wecker in einer Welt voller Smartphones?

Mir ging es im Wesentlichen darum, dass ich mein Telefon gerne Nachts ganz ausschalte. Mit Verschlüsselung funktionierte jedoch der Weckruf im ausgeschalteten Zustand nicht, sodass ich das Telefon zwangsläufig im Flugzeugmodus belassen musste.

Zudem wollte ich abends/nachts/morgens die Möglichkeit haben, die Uhrzeit zu erfahren ohne gleich von einem grellen Display geblendet zu werden.

Zu diesem Zeitpunkt war mein erster Gedanke noch “Kaufen”, also quälte ich mich eine halbe Nacht lang durch eine Onlinerecherche, um dann festzustellen, dass die wenigen Geräte die in Frage kamen mir nicht so richtig gefielen und dann meist viel zu teuer erschienen.

Also fing es in meinem Kopf an zu rotieren und nach und nach kam der Gedanke, dass ich mir den Wecker wohl doch selber bauen sollte. Ich hatte ohnehin Lust etwas mit einem Raspberry Pi herumzuspielen. Und irgendwo war ich auf ein Video gestoßen in dem über kapazitative Touch-Buttons Berührungen durch eine Holzoberfläche hindurch registriert wurden. Oder war es ein Video, in dem ein Display durch eine Furnieroberfläche hindurch strahlte?

So entstand langsam das Konzept eines kleinen geschlossenen Holzkastens, der insgeheim ein Wecker sein sollte. Möglichst schlicht und unaufdringlich. Als Wecktöne wollte ich beliebige Lieder abspielen können. Die Bedienung über die Buttons sollte möglichst einfach bleiben und sich somit (zunächst) auf das Wesentliche beschränken. Für ein komfortables Einrichten von Weckrufen und weitere Einstellungen wollte ich eine App auf meinem Android-Telefon haben.

Ich suchte mir also Stück für Stück die benötigten Teile zusammen und legte los.

Erste Versuche

Zunächst galt es, die Hardware ansteuern zu können und zu schauen, ob meine Idee überhaupt so umsetzbar sein würde, wie ich mir das vorstellte (deutsch: ‘Proof of Concept’).

Ich hatte noch keine Ahnung davon, wie ich die Buttons, das Display und die Soundkarte auf dem Raspberry Pi zum Laufen kriege und war zunächst etwas überwältigt von den vielen neuen Begriffen wie GPIO mit diversen PINs und was noch so dazugehörte.

Die Idee, das Display durch das Furnier hindurch scheinen zu lassen, wandelte ich in diesem Zuge ab. Es hätte möglicherweise funktioniert, aber ich war nicht sicher, ob das Display dann gut genug lesbar wäre, zumal mein Furnier auch nicht das dünnste war (ich hatte zu diesem Zeitpunkt zudem noch keinen Hobel, habe also gar nicht erst in Erwägung gezogen, es dünner zu machen). Also entschied ich mich, das Display stattdessen hinter Leinenstoff zu verbergen (den hatte ich nämlich auch schon und mir gefiel die Kombination aus dem dunklen Walnussfurnier und dem eher hellen Leinen).

Zudem wollte ich klären, ob die kapazitativen Touch-Buttons wirklich durch die entsprechend dicke Holzschicht hindurch Berührung registrieren können.

Es klappte!

Hardware

Als Grundlage entschied ich mich für den Raspberry Pi Zero W. Er ist recht klein, günstig und stromsparend, bietet aber dennoch den Komfort, den auch seine größeren Geschwister haben mit WLAN, Debian-basiertem Betriebssystem und natürlich Python. Außerdem erwies sich die große Verbreitung als Pluspunkt, da es zahlreiche Quellen im Netz gibt, sowie etliche Hardware-Module, die sich in der Regel gut über Python ansteuern lassen. Und die Rechenleistung sollte für einen Wecker auch mehr als genug sein.

Die wesentliche Hardware auf einen Blick

SD-Karte

Bei der SD-Karte achtete ich auf eine gute Qualität und Industriestandard, da die Karte mit dem Dauereinsatz und ständigen Schreibzugriffen klarkommen muss. Immerhin läuft darauf das komplette Betriebssystem. Außerdem möchte ich nicht, dass durch Fehler auf der Karte unvorhergesehene Effekte auftreten, die mich möglicherweise bei der Fehlersuche zur Verzweiflung bringen könnten.

Neues Image vorbereiten

Hier empfehle ich den offiziellen Imager von Raspberry Pi, der sehr einfach zu bedienen ist und sogar die wichtigsten Schritte zur Einrichtung bietet, sodass man dann sofort per SSH auf das frische Image zugreifen kann. Natürlich braucht man einen SD-Kartenleser für seinen Computer.

Backup erstellen via SSH

Ich habe das Backup via SSH erstellt, weil ich um an die SD-Karte zu kommen den kompletten RPi wieder umständlich aus dem kleinen Gehäuse hätte entfernen müssen.

Falls man leicht an die SD-Karte kommt, ist die bevorzugte Variante die Karte über einen Kartenleser am PC anzuschließen, da dann das Backup deutlich schneller geht. Ich fühle mich auch wohler, wenn das Backup nicht während des laufenden Betriebs gemacht wird.

So geht’s:

ssh pi@alarmclock "sudo dd if=/dev/mmcblk0 bs=4M | gzip -" | dd of=alarmclock-backup.gz

Das Backup wird direkt gepackt, um Platz zu sparen. Das lohnt sich insbesondere, da das gesamte Dateisystem gesichert wird einschließlich der leeren Blöcke. Beim Packen wird das Ganze dann auf ein sinnvolles Maß heruntergestaucht.

Es kann sich lohnen vorher im System aufzuräumen und nicht benötigte Komponenten zu deinstallieren, insbesondere wenn sie groß sind. Z.B. libreoffice oder wolfram-engine.

mmcblk0 ist, soweit ich weiß, immer die SD auf dem RPi. Es kann aber sinnvoll sein, dass vorher zu prüfen, z.B. mit fdisk -l.

Das Backup kann je nach Kartengröße durchaus ein paar Stunden dauern. Ein Fortschritt ist bei dieser Methode nicht erkennbar, also Geduld wahren.

Es gibt viele weitere Möglichkeiten für das Backup. Die von mir gewählte ist vermutlich nicht die Beste davon, aber sie funktioniert und ist so niedrigschwellig, dass ich nachvollziehen kann was da passiert. Es könnte sich aber lohnen, einen Blick auf raspiBackup oder andere Tools zu werfen.

Backup Wiederherstellen

Falls das Image auf eine andere SD-Karte “gleicher” Größe geschrieben werden soll, kann das unter Umständen zu Problemen führen. Die SD-Karten sind nicht wirklich exakt gleich groß. Daher kann es passieren, dass das Image nicht auf die neue Karte passt.

Zwei Möglichkeiten, das vor Erstellen des Backup zu berücksichtigen:

  1. Fülle nicht beschriebene Blöcke auf der Karte vor dem Backup mit 0. Das geht z.B. mit zerofill. dd wird dann zusätzlich mit conv=sparse aufgerufen, um die Nullen wegzuwerfen. Nachteil: Das intensive Schreiben ist nicht gerade gut für die Karte. Es ist aber bei sporadischem Einsatz vertretbar.
  2. Benutze gparted, um den genutzten Bereich auf der SD etwas zu verkleinern, sodass es auf jede etwa gleich große Karte passen sollte.

Ich habe mich entschieden, es darauf ankommen zu lassen und das Problem dann beim Wiederherstellen des Image zu lösen.

Hier gibt es eine Anleitung zum Vergrößern/Verkleinern der Images für den RPi: Shrinking Raspberry Pi SD Card Images Dann allerdings von einem Linux-OS. Dort ist zudem ein Skript verlinkt, das diese Schritte übernehmen kann (ich habe es nicht getestet): Cotswold Raspberry Jam Github

Ein Wiederherstellen ist nicht im laufenden Betrieb möglich, kann also nicht remote ausgeführt werden. Daher heißt es in diesem Fall, die Karte ausbauen und mit Kartenleser an den PC zu hängen.

Peripherie

Meines Wissens gab es damals im Raspberry Pi Imager noch nicht die Möglichkeit, das WLAN und den SSH-Zugang gleich einzurichten. Es war also nötig, erstmal Maus, Tastatur und Bildschirm anzuschließen, um die initiale Einrichtung direkt auf dem Gerät vorzunehmen.

Da das Netzteil einen der beiden Micro-USB-Ports am RPi blockiert, müssen Maus und Tastatur gemeinsam am verbliebenen Port angeschlossen werden (wenn man auf der Kommandozeile bleibt, kann man sich die Maus natürlich auch sparen). Der Monitorausgang ist Micro-HDMI.

Inzwischen spare ich mir den ganzen Spaß und greife immer sofort über SSH auf den RPi zu. Ich wähle zudem ein Image ohne grafische Oberfläche, da ich dann auch noch Speicherplatz auf der SD-Karte spare.

Netzwerk

Im Netzwerk soll mein Wecker als soundclock gefunden werden. Daher trage ich das in /etc/hostname ein.

Ich habe zudem in /etc/hosts ein Loopback des Localhost eingerichtet: 127.0.1.1 soundclock Ich weiß allerdings nicht mehr, warum ich das getan habe.

Unter Windows musste ich leider trotzdem noch manuell einen Eintrag bei den hosts hinterlegen (die Datei liegt im Windows-Verzeichnis unter System32\drivers\etc). Das läuft nach dem gleichen Prinzip, nur nehme ich hier statt des Localhost die IP des RPi im lokalen Netzwerk (dafür stelle ich im Router ein, dass dem RPi immer die gleiche IP zugewiesen wird).

Abbrüche der SSH-Verbindung

Ich hatte anfangs des Öfteren lästige Verbindungsabbrüche bei meiner SSH-Verbindung, was insbesondere bei längerdauernden Vorgängen, wie z.B. Software-Updates sehr lästig war. Ich glaube allerdings, dass diese Probleme auf neueren Raspbian-Versionen nicht mehr auftreten.

Die Probleme ließen sich, soweit ich mich erinnere, durch folgende Einstellungen in /etc/ssh/sshd_config beheben:

  • ClientAliveCountMax 3
  • ClientAliveInterval 1200

Mögliche weitere Lösungen:

  • in sshd_config zusätzlich TCPKeepAlive no einstellen
  • Power-Management für den WLAN-Adapter deaktivieren

GPIO (General Purpose IO)

Wie der Name vermuten lässt, kann über GPIO alles Mögliche an den RPi angeschlossen werden.

Ich habe eine Weile gebraucht, herauszufinden an welche Pins ich was anschließen muss. Über die GPIO-Pins können verschiedene Schnittstellen betrieben werden (SPI, I2C, UART, PCM) und es gibt mehrere Ausgänge für Spannung und Erde.

Sehr hilfreich finde ich folgendes interaktive Pinout: https://pinout.xyz/pinout/spi#

Belegung PWR und GND

2 x 3V Power + 2 x 5V Power, Aufteilung:

  • Display: 3V
  • Mpr121: 3V
  • Buttonreihe oben: teilt sich 5V
  • Buttonreihe unten: teilt sich 5V

Der Pi hat 8 Ground-Anschlüsse, ich benötige:

  • Display: 1
  • Mpr121: 1, gibt aber auch 1 Ground-Anschluss weiter
  • Also bleiben 7 Ground-Anschlüsse für die Touch-Buttons
  • Jeder Touch-Button benötigt einen Ground-Anschluss
  • Wenn ich mehr als 7 Touch-Buttons verbaue, müssen sich manche den Ground teilen

Display

Hier gibt es viele Möglichkeiten, die über Größe, Auflösung, Farbe hinausgehen.

Ich finde ePaper spannend und würde gerne mal etwas damit ausprobieren. Es sieht cool aus und ist angenehm zu lesen, da es nicht leuchtet. Man benötigt kaum Strom, um das Angezeigte zu erhalten, sondern hauptsächlich, um das Bild zu ändern. Außerdem ist es biegsam. Für mich ist es nur ausgeschieden, weil ich die Uhrzeit auch nachts erkennen können will und dann wieder eine zusätzliche Beleuchtung nötig gewesen wäre.

Ich habe auch über Touch-Bedienung über das Display nachgedacht, mich dann aber dagegen entschieden. Da hätte es neben Touch-Displays auch Touch-Lösungen gegeben, die vor oder auf dem Display liegen.

Ich habe mich für eine LED-Matrix aus Modulen von 8x8 Feldern entschieden, da ich den Oldschool-Faktor für mein Projekt mag. Nachteil (speziell an meiner Wahl) ist die Größe (viel Platz für wenig Bildpunkte). Es gibt sicher auch ähnliche Matrizen mit gleicher LED-Zahl auf weniger Raum. Und dann gibt es noch haufenweise andere Optionen wie z.B. 7-Segment-Anzeigen.

64er Luma LED Matrix Display

Bei mir sind bereits 4 8x8 Module kombiniert und komplett zusammengebaut. Es gibt das auch zum selbst basteln, wobei man auch mal den Lötkolben schwingen müsste.

Die Module können am Rand um weitere ergänzt werden, sodass sich eine lange Reihe ergibt. Sicher könnte man sie auch untereinander setzen, aber dann müsste man die Reihen getrennt ansteuern oder von einer Reihe zur nächsten eine Verbindung über Kabel herstellen.

Ich habe erstmal mit den 4 Modulen begonnen, um die Ansteuerung auszuprobieren und die Möglichkeiten abzuschätzen. Nachdem ich etwas mit der Anzeige von Text, Uhrzeit und Datum gespielt hatte, bin ich dann auch dabei geblieben.

Auf der Webseite des Anbieters habe ich Datenblätter, Treiber/Libraries und Anleitung gefunden.

Anschlüsse: VCC, DIN, CS, CL

Hier habe ich erstmal eine ganze Weile gebraucht, um herauszufinden, was diese Abkürzungen bedeuten sollen und welche Display-PINs ich nun mit welchen RPi-PINs verbinden muss:

  • GND war klar
  • Dank der Schematik konnte ich schnell herausfinden, dass VCC den 5V-Spannungseingang meint.
  • DIN steht für data in und es ließ sich erkennen, dass das an SPI0 MOSI (GPIO 10, Pin 19) gehört.
  • CL steht für CLK (clock) und soll nach (altem!) Pi-Schema an 11 SCLK. GPIO 11 liegt bei mir (neuere Pi-Modelle) auf Pin 23. Dort findet sich bei mir SPI0 SCLK. Ich bin dann davon ausgegangen, dass das passt.
  • CS soll an 8 CS0. An GPIO 8 (Pin 24) lag bei mir SPI0 CE0 N. Ich schätzte, dass das passen könnte, aber wollte es gerne noch bestätigen.
  • In der Anleitung des Matrixdisplay fand ich heraus, dass CLK auch SCK und CS auch SS ist. Das hat mich nochmal bezüglich der CLK bestätigt, aber für CS reichte mir die Information noch nicht.
  • Über das verlinkte GPIO Pinout fand ich einen Link zur offiziellen RPi-Seite mit weiteren Infos zu SPI. Dort fand ich dann heraus, dass CS für chip select steht, an anderer Stelle, dass CE (chip enable) oft chip select genannt wird. Jetzt fiel der Groschen und der Tabelleneintrag (CE0, Pin 24, GPIO 8, SPI0_CE0_N) nahm den letzten Zweifel. Ich konnte also wie gedacht fortfahren (und hatte die ausführliche RPi-Hardware-Dokumentation gefunden, hurra!).

Wenn man verpeilt ist und trotz mehrfachem Nachprüfen versehentlich die falschen Pins anschließt, kann das zu sehr merkwürdigen Effekten führen, die das Debuggen quasi unmöglich machen (außer natürlich man schaut einfach mal nach, ob es passt und konzentriert sich dabei kurz).

Auch wenn man es im Normalfall nicht benötigen dürfte, notiere ich hier mal, wie man die Schnittstelle zurücksetzen kann:

  • Über lsmod die Schnittstelle finden –> Das Gerät ist die SPI-Schnittstelle des Controllers BCM2835: spi_bcm2835
  • Entfernen: sudo rmmod spi_bcm2835
  • Hinzufügen: sudo modprobe spi_bcm2835

Es bietet sich an, das in ein kleines Skript auszulagern. Dann kann man es auch einfacher direkt im Anschluss an andere Befehle ausführen, z.B. python3 bla.py && ./reset-display.sh.

SPI Geräte anzeigen: ls /dev/*spi* –> /dev/spidev0.0 /dev/spidev0.1

Display ansteuern

Nun ging es daran, das Display über Python anzusteuern. Ich installierte die Abhängigkeiten, die in der Anleitung genannt werden und die Python-Library (luma.led_matrix) global, da ich sie global auf dem RPi für alle Projekte verfügbar haben wollte.

Ich übernahm den umfangreichen Python-Beispielcode aus der Anleitung und brachte ihn auf dem Pi zum Laufen. Ich setzte --cascaded 4, da ich 4 Matrizen habe. Ich musste die Reihenfolge umkehren (blocks_arranged_in_reverse_order=True bzw. --reverse-order true je nach Skript). --block-orientation 90 war nötig, damit der Text in jeder Matrix die richtige Orientierung hat.

Also: python3 display-test.py --cascaded 4 --block-orientation 90 --rotate 0 --reverse-order true

Um das Matrix-Display in meiner Python-Anwendung nutzen zu können, war es schließlcih doch nötig luma.led_matrix in der virtuellen Umgebung des Projekts zu installieren.

SPI in Python zum Laufen bringen

Zusätzlich zu den Libraries (sudo apt-get install -y python-dev python3-dev, sudo apt-get install -y python-spidev python3-spidev) musste ich noch py-spidev installieren:

cd ~
git clone https://github.com/Gadgetoid/py-spidev.git
cd py-spidev
sudo python setup.py install
sudo python3 setup.py install
cd ~

Vielleicht kann es auch über pip installiert werden. Das habe ich nicht ausprobiert.

Bilder

Soundkarte und Lautsprecher

Hier gibt es natürlich wieder unendliche Produktvielfalt. Ich habe erstmal einfach irgendwas genommen, was auf den ersten Blick brauchbar schien. Je nach Anspruch und Pi-Modell könnte auch der Sound ausreichen, den der Pi von Haus aus mitbringt. Der Pi Zero hat allerdings diesbezüglich nur digitales Audio über HDMI.

Für den zweiten Wecker habe ich ich für Hifiberry entschieden.

Die Soundkarte ist ein sogenannter HAT, der über alle GPIO-Pins oben auf den Raspberry Pi aufgesteckt wird. Dabei habe ich darauf geachtet, dass die Soundkarte selbst wieder GPIO-Pins hat, sonst hätte ich die übrige Hardware nicht mehr anschließen können.

Details zum Einrichten

In /boot/config.txt:

  • dtparam=audio=on auskommentieren
  • dtoverlay=hifiberry-dac

In /etc/asound.conf:

pcm.!default {
  type hw card 0
}
ctl.!default {
  type hw card 0
}
Bilder

Capacitative Touch Sensors

Ich habe wesentlich mehr Buttons eingebaut, als aktuell benötigt. Zum einen habe ich oben mittig den großen Snooze-Button über zwei Buttons abgebildet. Außerdem habe ich noch viele Ideen für erweiterte Funktionalität.

Für die Buttons und den MPR121 musste ich die PINs selbst anlöten. Das ist zum Teil nicht besonders schön geworden, war aber auch nicht wirklich schwierig und das Ergebnis brauchbar. Zum Löten kann ich ansonsten nicht viel sagen. Falls ich in einem zukünftigen Projekt größere Lötaufgaben vor mir hätte, würde ich vermutlich bessere Löt-Ausrüstung anschaffen, da ich hier wirklich das Billigste vom Billigen habe und das die Sache nicht gerade leichter macht. Ansonsten hilft dann wohl Üben (und vielleicht zwischendurch noch ein paar Youtube-Anleitungen schauen).

Zunächst musste (und wollte) ich etwas experimentieren. Ich wusste nicht, ob die bestellten Sensoren wirklich durch das Holz hindurch funktionieren. Außerdem wollte ich etwas mit Kupferband, elektrisch leitender Farbe und, nun ja, Obst spielen.

Meine Buttons/Sensoren: TTP223
Sensor Breakout, um mehr Buttons ansteuern zu können: MPR121

Hilfreiche Links:

Die besten Ergebnisse habe ich erzielt, indem ich selbst den raw_value ausgewertet habe. Einen geeigneten Threshold für den raw_value habe ich experimentell bestimmt.

Zudem erlaubte mir dieser Ansatz, das störende Blinken des MPR121 beim Auslösen eines Buttons zu deaktivieren, indem ich dort den Threshold auf den Maximalwert (255) setzte, sodass kein Touch mehr registriert wurde.

Nachdem ich mit meinen Tests zufrieden war, konnte ich die Buttons mit Sekundenkleber in das Gehäuse einkleben.

Die Buttons funktionierten dann auch, allerdings nicht alle und nicht immer. Schlimmer noch lösten sie oft aus, obwohl sie gar nicht gedrückt wurden. Und es gab für mich einfach viel zu viele Möglichkeiten, woran das liegen könnte:

  • Lag es an den Kabeln?
  • Lagen die Buttons nicht ganz plan auf?
  • Waren die Buttons zu dicht beieinander? Oder zu nah an der Front?
  • Lag es am Sekundenkleber oder dass dieser ungleichmäßig verteilt war?
  • War vielleicht der Button defekt?
  • Gab es ein Problem mit dem Anschluss am MPR121?
  • War es vielleicht doch nur ein Initialisierungsproblem?
  • Konnten (subtile) Änderungen bzw. Bewegungen am Gehäuse oder den Kabeln die Kapazität verändern?

Wenn etwas neu (für mich) ist, erweist sich die Fehlersuche eben als deutlich schwieriger. An Ideen, woran es liegen könnte, mangelt es meist nicht, nur bin ich dann noch nicht in der Lage, diese Ideen auf ihre Wahrscheinlichkeit hin korrekt einzuschätzen. So bleibt nur wildes Durchprobieren etwaiger (teils recht esoterisch erscheinender) Lösungsansätze. Aber mit der Zeit kommt ja dann die Erfahrung und beim nächsten ähnlichen Projekt geht die Sache dann schon deutlich weniger holprig vonstatten.

Ich habe so einige Buttons zerstört, indem ich sie wieder aus dem Gehäuse herausgetrennt und ausgetauscht habe (das klappte übrigens am besten mit einem scharfen Stecheisen). Ich hatte auch Versuche unternommen, einen Kondensator auf den Button zu löten, um die Empfindlichkeit zu verringern. Zudem habe ich die Aufteilung der Kabel für Spannung und Erde im Laufe der Zeit verbessert.

Was ich auch versuchte, ich kam einfach nicht auf eine Lösung. Also ließ ich das Projekt erstmal liegen.

Bis ich etwa ein Jahr später beschloss, ein Hängeschränkchen mit integriertem Wecker für mein Schlafzimmer zu bauen. Dort plante ich dann deutlich mehr Platz für den Wecker ein, als in meinem ersten Gehäuse, in der Hoffnung dadurch mögliche Interferenzen zu vermeiden und besser an die Einzelteile zu kommen, um Fehler zu beseitigen (die Buttons habe ich dort auch nicht fest eingeklebt).
Im neuen Gehäuse klappte dann auch alles. Bis es irgendwann wieder nicht klappte. Aber diesmal konnte ich die Ursache identifizieren (nicht zuletzt, weil ich besser in das Gehäuse schauen konnte und dort mehr Platz war). Die Lautsprecherkabel störten die Buttons, wenn sie ihnen oder ihren Kabeln zu nahe kamen. Das konnte ich durch etwas Kabelmanagement mit Isolierband in den Griff bekommen.

Und jetzt wusste ich auch, wo das Problem am ersten Gehäuse lag (und warum es schlimmer wurde, wenn ich das Gehäuse schloss). Ein erster Versuch, die Kabel mit Alufolie zu isolieren war ein Teilerfolg. Ich muss allerdings zusätzlich die Lautsprecherkabel kürzen und noch sauberer arbeiten, um das Problem dort vollständig zu lösen (gerade habe ich allerdings den Pi Zero für mein CameraCtrl-Projekt im Einsatz).

Bilder

Optimierung

Hilfreich: Artikel zu Stromsparen beim Raspberry Pi

HDMI deaktivieren

Das spart Strom.

HDMI kann abgeschaltet werden durch:

/usr/bin/tvservice -o

Ich trage es in /etc/rc.local ein, damit es nach dem (Re-)Boot deaktiviert ist.

Statusabfrage: /usr/bin/tvservice -s

HDMI kann wieder eingeschaltet werden mittels:

/usr/bin/tvservice -p

Power (Act) LED(s) deaktivieren

Die Power-LED leuchtet (wenn auch minimal) durch die Front, daher möchte ich sie deaktivieren.

Um die LEDs abzuschalten, sollte man folgende Zeilen in /boot/config.txt einfügen:

ACT LED:

  • dtparam=act_led_trigger=none
  • dtparam=act_led_activelow=off

PWR LED:

  • dtparam=pwr_led_trigger=none
  • dtparam=pwr_led_activelow=off

Die Raspberry Pi Zero / Zero W’s haben nur die ACT LED. Ich habe trotzdem beides eingetragen.

Nutzerrechte auf dem Raspberry Pi einrichten

Ich habe in Raspbian zwei Nutzer eingerichtet.

Über den ersten Nutzer verbinde ich mich per SSH zum Raspberry Pi und nehme alle erforderlichen Einstellungen vor und was es sonst noch so einzurichten gibt. Daher braucht dieser Nutzer auch weitgehende Zugriffsrechte. Standardmäßig heißt dieser Nutzer pi, es ist aber aus Sicherheitsgründen zu empfehlen, das zu ändern (das Passwort sowieso). Ich werde im Folgenden aber so tun, als ob der Nutzer weiterhin pi hieße.

Den zweiten Nutzer nenne ich hier www-data, da er auch im weiter unten zu findenden Beispiel zum Einrichten der Server-Produktivumgebung so genannt wird. Dies ist der Nutzer über den der Server auf dem Raspberry Pi ausgeführt wird. Er sollte so wenig Rechte wie möglich eingeräumt bekommen. Damit die Wecker-Anwendung aber ausgeführt werden kann, sind dennoch einige Rechte, z.B. zum Abspielen von Audio nötig.

Die Rechte sind über Nutzer-Gruppen sinnvoll gebündelt. Die Gruppen eines Nutzers kann man sich ganz einfach anzeigen lassen: groups pi

So fügt man einen Nutzer zu einer Gruppe hinzu: sudo usermod -a -G audio www-data

Das Entfernen von Gruppen ist etwas umständlich, dafür müssen die zu erhaltenden Gruppen alle gelistet werden. Der Befehl wird nach folgendem Schema aufgebaut: sudo usermod -G gruppe1,gruppe2,... www-data

Virtuelle Python-Umgebung und Nutzer-Rechte

Damit meine Anwendung von www-data ausgeführt werden kann, muss dieser auch die Rechte über die Dateien haben (einschließlich der Dateien in der virtuellen Umgebung). Das führte bei mir zu Problemen, wenn ich Änderungen an der Anwendung selbst vornehmen oder die verwendeten Libraries in der vituellen Umgebung aktualisieren wollte. Änderungen hatte ich in der Regel auf meinem Windows-Rechner vorgenommen und wollte sie nun per git pull auf den RPi übertragen.

Beholfen habe ich mir, indem ich alle Rechte vor Änderungen an der virtuellen Umgebung an den Nutzer pi übertragen und anschließend an www-data zurückgegeben habe. Rekursiv können die Dateirechte in einem Verzeichnis wie folgt übertragen werden:

sudo chown -R new-owner:new-owner folder

Änderungen aus dem Git-Repository habe ich mir durch das Ausführen mit Administratorrechten geholt (sudo git pull). Anschließend habe ich trotzdem nochmal die Rechte für alle Dateien an www-data übertragen, damit es für neue und geänderte Dateien auch wieder passte.

Probleme mit Pulse-Audio

Für meine Anwendung habe ich python-vlc verwendet. Dabei hatte ich ein paar Schwierigkeiten mit pulseaudio (Schnittstelle zur Audio-Hardware).

Nach einiger Fehlersuche fand ich heraus, dass pulseaudio auf das Home-Verzeichnis des Nutzers zugreifen wollte. Das hatte ich allerdings für www-data nicht angelegt. Nachholen ließ sich das über: sudo mkhomedir_helper www-data

Man hätte das aber auch über Parameter direkt beim Anlegen des Nutzers tun können: -m (--create-home)

Manuell kann das auch über /etc/passwd konfiguriert werden. Da ist es auch möglich, die Start-Shell des Nutzers festzulegen. Ein Eintrag ist wie folgt aufgebaut: Username:Passwort:UserID:GroupID:Beschreibung:Homeverzeichnis:Startshell

Um sicherzustellen, dass nach all dem Pulse-Audio für den Nutzer auch korrekt funktioniert, habe ich es neu installiert. Das war allerdings etwas tricky, da es viele Abhängigkeiten gibt und pulse audio in der Regel auch läuft. Ich bin dabei wie folgt vorgegangen (danke Internet):

sudo apt-get purge pulseaudio
sudo apt-get clean && sudo apt-get autoremove

Neustart, dann:

rm -r ~/.pulse ~/.asound* ~/.pulse-cookie ~/.config/pulse
sudo apt-get install pulseaudio

Fehler bei rm dabei ignorieren. Und noch ein Neustart.

Ich bin im Nachhinein nicht sicher, ob das Neuinstallieren von pulseaudio nötig war. In jedem Fall benötigt www-data wohl die Gruppen audio, pulse und pulse-access, damit es klappen kann.

Software

Die Wecker-Software habe ich als Client-/Server-Anwendung implementiert.

Auf dem Raspberry Pi läuft meine SoundClock als Server mit REST API, die es dem Client erlaubt, Informationen vom Wecker auszulesen (z.B. eine Liste der Weckrufe) und den Wecker zu bedienen (z.B. einen neuen Weckruf hinzufügen). Auch die Bedienung über die Buttons am Gerät und die Ansteuerung der übrigen Hardware ist Teil dieser Anwendung.

Der Client ClockSetter spricht die REST API an und erlaubt somit die Bedienung des Weckers. Dabei lag der Fokus für mich darauf, den Client als App für Android nutzen zu können.

SoundClock

Die SoundClock läuft auf dem Raspberry Pi als Herzstück des Weckers. Ich habe bei der Entwicklung jedoch darauf geachtet, dass sie auch in anderen Umgebungen lauffähig bleibt. Allerdings standen die eigentliche Wecker-Hardware (Buttons, Display) und die dazugehörigen Libraries natürlich nur auf dem Raspberry Pi zur Verfügung. Gelöst habe ich das, indem ich die geräteunabhängigen Code-Teile in abstrakten Klassen implementiert habe, z.B. AbstractDisplayCtl, von denen dann eine Dummy-Klasse und eine mit der tatsächlichen Hardwareansteuerung erbt, also z.B. DummyDisplayCtl und LEDMatrixDisplayCtl.

Meine Unterscheidung, ob ich auf dem echten Gerät unterwegs bin, sieht so aus:

running_on_device = sys.platform.lower() == 'linux' and os.uname().machine.__contains__('arm')

So konnte ich den Großteil der Entwicklung bequem in PyCharm auf meinem Windows-Rechner machen. Auch die automatischen Tests laufen geräteunabhängig. Die echte Geräteansteuerung kann auf diesem Weg natürlich nur getestet werden, wenn die Tests auf dem Raspberry Pi ausgeführt werden. Aber das machte letztlich ohnehin nur einen kleinen Teil der Entwicklung aus.

Server

Die REST API habe ich mit Python Flask realisiert. Flask ist, wie ich finde, sehr einfach und komfortabel zu benutzen und dennoch sehr mächtig.

Falls du mehr zur Benutzung von Flask erfahren möchtest, schau doch mal in meinen Beitrag zu CameraCtrl. Dort gehe ich mehr ins Detail und du kannst den Quellcode einsehen.

Einen Flask-Server sollte man in einer Produktivumgebung nicht direkt als Schnittstelle zur Außenwelt verwenden. Ich habe für das Aufsetzen der Produktivumgebung Nginx und Gunicorn verwendet und mich dabei weitgehend an folgender Anleitung entlanggehangelt:

Nginx/Gunicorn/Flask

Zulässige Uploadgröße erhöhen

Während der Musikupload in der Windows-Testumgebung problemlos funktionierte, wurden die Uploads auf dem Pi wegen unzulässiger Größe blockiert. Ich habe zum Glück schnell herausgefunden, dass es nicht an der Flask-App lag, in der die Größe korrekt definiert war, sondern am Nginx-Server.

Das Setzen der maximalen Größe in der Nginx-Konfiguration der Anwendung (/etc/nginx/sites-enabled/soundclock) hat hier Abhilfe geschaffen. Z.B. client_max_body_size 256M;

CORS

Ich weiß leider nicht mehr genau, warum ich CORS (Cross origin resource sharing) aktivieren musste.

Im Browser können Anfragen (über Skripte) von einer (Sub-)domain nur auf die gleiche (Sub-)domain gemacht werden, damit nicht Cookies von einer Webseite auf eine andere übertragen werden und dort möglicherweise unerwünschte Aktionen ermöglichen. Wenn beide Seiten in der Kommunikation CORS erlauben, kann diese Einschränkung aufgehoben werden.

Ich nehme an, dass die Art und Weise in der mein ClockSetter-Client implementiert ist CORS erfordert (zumindest wenn er im Webbrowser ausgeführt wird).

So habe ich CORS im Server eingebaut:

if os.getenv('CORS') == 'on':  
    logging.info("Running with CORS")  
    from flask_cors import CORS  
    use_cors = True  
else:  
    use_cors = False  
app = Flask(__name__)  
if use_cors:  
    cors = CORS(app)  
    app.config['CORS_HEADERS'] = 'Content-Type'

In der Flask-Umgebung .env kann ich CORS dann bei Bedarf aktivieren: CORS='on'

Config

Anwendungs- bzw. Nutzer-Einstellungen werden in einer JSON-Konfiguration abgelegt.

Dazu gehören beispielsweise verschiedene Anwendungspfade, aber auch Display-Einstellungen wie Helligkeit oder Leuchtdauer.

Die Display-Einstellungen sehen beispielsweise so aus:

  "display": {  
    "light_on_time": 5,  
    "time_format": "%H:%M:%S",  
    "brightness": 40  
  }

Für die Weckrufe und die Musik gibt es jeweils eigene JSON-Dateien.

Buttons

Die Buttons habe ich auch über die JSON-Config konfigurierbar gemacht. So kann ich einfach Änderungen vornehmen, wenn sich etwas an der Verkabelung und somit an den Button-IDs ändert. Außerdem habe ich ja noch einige ungenutzte Buttons für zukünftige Erweiterungen und kann diese dann bequem einrichten. Das callback-Attribut gibt die Funktion an, die bei Auslösen des Buttons aufgerufen wird. Neben den Input-Triggern über Einzelbuttons kann ich über trigger_and und trigger_or zusammengesetzte Buttons kreieren.

Hier ist eine Beispiel-Konfiguration:

"input_listener": {  
  "trigger": [  
    {  
      "type": "trigger_or",  
      "children": [  
        {  
          "type": "button",  
          "id": 2  
        },  
        {  
          "type": "button",  
          "id": 3  
        }  
      ],  
      "callback": "display_and_snooze_func"  
    },  
    {  
      "type": "trigger_and",  
      "children": [  
        {  
          "type": "button",  
          "id": 0  
        },  
        {  
          "type": "button",  
          "id": 1  
        }  
      ],  
      "callback": "stop_alarm_func"  
    }  
  ]  
}

Die Snooze-Funktion und das Anzeigen der Uhrzeit ist über einen von zwei Buttons auslösbar (die Buttons liegen nebeneinander, sozusagen als ein großer Button). Um einen laufenden Weckruf auszuschalten muss ich jedoch zwei Buttons gleichzeitig betätigen, sodass ich den Weckruf nicht versehentlich im Halbschlaf abschalte.

Musik und Weckrufe abspielen

Anfangs hatte ich den PyGame Mixer verwendet, um Musik und Weckrufe abzuspielen. Ich war jedoch mit den Möglichkeiten nicht zufrieden und bin auf VLC umgestiegen. Dafür bietet python-vlc eine mächtige, wenn auch nicht unbedingt leicht verständliche, Anbindung.

Das Abspielen von Audio-Dateien habe ich einer MusicPlayer-Klasse implementiert, die VLC intern über einen Media-List-Player der VLC-Instanz ansteuert:

self.media_list_player = vlc_instance.media_list_player_new()  
self.media_player = self.media_list_player.get_media_player()

So ist das Abspielen von Weckrufen in Dauerschleife möglich und ich könnte zukünftig recht einfach die Musikwiedergabe über Playlists integrieren.

Von diesem MusicPlayer habe ich zwei Instanzen, damit Musik und Weckrufe unabhängig voneinander abgespielt werden können. So kann ein Weckruf auch eine laufende Musikwiedergabe pausieren und anschließend wieder fortsetzen, wobei auch Lautstärkeeinstellungen korrekt berücksichtigt werden.

Da ich nie Musik auf meiner SoundClock höre, ist dieser Teil der Anwendung sehr spartanisch implementiert. Den dazugehörigen Teil der Client-UI nutze ich hauptsächlich, um neue Musikdateien auf die SoundClock zu übertragen und als möglichen Weckton einzustellen (nicht, dass ich das besonders oft tun würde).

ClockSetter

Den SoundClock-Client habe ich mit Ionic React entwickelt, einer Technologie die auf NodeJS basiert. Grundlage für diese Entscheidung war, dass es (relative) Plattformunabhängigkeit versprach und sowohl in Hinblick auf die UI als auch die API-Anbindung geeignet erschien. Ich wollte in erster Linie eine App für mein Android-Telefon entwickeln, aber auch eine ausführbare Anwendung für meinen Windows-Rechner in der Hinterhand haben.

Ich hatte zunächst etwas Schwierigkeiten, mich in die andere Denkweise der Entwicklung einer React-Anwendung einzufinden. Manches hat mir dabei gut gefallen und ich mochte, wie schnell die UI dank Icons und anderer fertiger Bausteine von Ionic gut aussah und sich rund anfühlte. Für die Auswahl der Urzeit im Android-Style musste ich eine zusätzliche Lösung in das Projekt integrieren.

Es gab jedoch mehrere für mich wichtige Punkte, die mich von Ionic React abgebracht haben:

  • Ich habe bei den NodeJS-Paketabhängigkeiten wieder mal die Krise bekommen. Es ist mir unbegreiflich, wie es andere schaffen, stabile Produktivsysteme mit NodeJS zu entwickeln und diese aktuell zu halten. Beim Versuch, die verwendeten Projektabhängigkeiten auf eine halbwegs neue Version zu bringen, bin ich nach mehrfachem stundenlangem Gewürge immer wieder an den Punkt gekommen, dass das aufgrund von Mehrfachabhängigkeiten mit verschiedenen Versionsanforderungen schlicht nicht möglich war.
  • Ich hatte beabsichtigt, die Windows-Anwendung mittels Electron zu bauen, stellte dann aber fest, dass das wider anderer Angaben doch nicht mehr von Ionic React unterstützt wurde. Mit der Lösung, die ich mir daraufhin mittels eines Chromium-Browser zusammengestöpselt hatte, war ich nicht zufrieden.
  • Auch das Android-Deployment hakte ganz schön und wirkte so, als ob es gar nicht vernünftig gepflegt wurde. Auch hier bin ich mehrfach bei Aktualisierungsversuchen gescheitert und stieß immer wieder auf Probleme, die ich glaubte, beim letzten Deploy-Vorgang bereits gelöst zu haben.
  • Trotz gewisser Vorzüge bin ich mit der Entwicklung in Ionic React nie wirklich warm geworden.

Ich habe noch viele (nicht gerade dringende) Ideen, wie ich meine Anwendung erweitern möchte. Aber ich habe beschlossen, das nicht mit Ionic React fortzuführen. Viel lieber entwickle ich ein Webinterface, das direkt auf dem RPi läuft und somit auf so ziemlich jeden Gerät einfach über den Webbrowser zur Verfügung steht. Zusätzlich würde ich eventuell noch eine neue UI mit Python Kivy entwickeln, die ebenfalls (mit etwas Zusatz-Aufwand) auf vielen verschiedenen Plattformen “ausgeliefert” werden kann.

Bilder

Ich entschuldige mich für die schlechten Screenshots.

Gehäuse

Das erste Weckergehäuse habe ich Ende 2020 gefertigt. Da meine Werkzeugauswahl zu diesem Zeitpunkt noch recht limitiert war habe ich mir dafür im Bastelladen einen Laubsägekasten für Kinder angeschafft. Im gleichen Laden bekam ich auch das Laubholz.

Wenn ich mich richtig erinnere habe ich die Teile alle mit Sekundenkleber verbunden. Erst für das Furnieren kam dann Holzleim zum Einsatz. Und ja, ich habe mir nur dafür ein Bügeleisen angeschafft – aber inzwischen habe ich es sogar schon mal zum Bügeln von Stoff verwendet.

Für die Hardware-Halterungen habe ich kleine Holzstückchen eingeklebt. Mir gefiel auch die Idee, Stücke von Büroklammern zu nutzen, um die Hardware zusätzlich zu sichern, aber beim vielen Hin und Her hat es mich dann eher genervt und irgendwann mussten sie dran glauben. Die SD-Karte ist mir übrigens auch einmal durchgebrochen, da sie wohl bei dem Gemurkse auf engem Raum einmal zu viel Druck abbekommen hat.

Ich mag dieses Gehäuse immer noch sehr (und finde bestimmt noch einen Einsatz dafür), wobei sich die beengte Bauweise als sehr hinderlich erwies, da ich viel mit dem Hardware-Setup zu kämpfen hatte, aber dazu habe ich ja weiter oben schon berichtet.

Bilder

Nachtschränkchen

Ich war (eigentlich von Anfang an) unzufrieden mit meinem Nachttisch, den ich irgendwann über Kleinanzeigen erstanden hatte. Er gefiel mir nicht (typische Heimwerker-Konstruktion mit viel Schrauben), ließ sich nicht richtig sauber machen (schlecht behandelte Oberfläche) und stand ständig im Weg (z.B. beim Lüften).

Meine Anforderungen:

  • Hängeschränkchen (damit es mir nicht dauernd im Weg ist)
  • Aus Nussbaum mit traditionellen Holzverbindungen (das zweite Mal in meinem Leben, dass ich Schwalbenschwanzverbindungen herstellte)
  • Wecker
  • Taschentuchspender
  • geschlossene Aufbewahrung für Kleinkram
  • Handy-Ablage
  • genug Platz für Bücher
    • Ursprünglich hatte ich geplant, einen Stopper oben anzubringen, um großformatige Bücher aufrecht gegen die Wand lehnen zu können ohne dass sie abrutschen. Das hat sich dann als überflüssig herausgestellt, ich wurde noch von keinem Buch erschlagen.

Da ich mit den Proportionen und überhaupt mit allem ziemlich unsicher war, erstellte ich einen sehr simplen Mockup aus Papier. Im Laufe des Projekts bin ich recht stark von dem abgewichen was ich mir da überlegt hatte, aber es war hilfreich um eine grobe Vorstellung zu bekommen.

Ich wusste nicht so recht, wo ich anständiges Holz bekommen konnte und kaufte es über Ebay. Dadurch war das ohnehin sehr teure Nussbaumholz sicher noch ein gutes Stück teurer und ich habe mich bei der Menge stark eingeschränkt.

Das zwang mich dazu, mehr helle Teile des Holzes mitzuverwenden, als mir eigentlich lieb war. Ich entschied mich dann zu einer Anordnung bei der es sozusagen einen Farbverlauf von rechts vorne (hell) zu links hinten (dunkel) geben sollte. Das ist mir allerdings nur teilweise gelungen.

In diesem Projekt habe ich zum ersten Mal Türen gebaut und eingebaut. Dabei wusste ich noch nicht immer so genau was ich da eigentlich tue und habe entsprechend ein paar Fehler gemacht. Daher sind die Türen nicht ganz gerade geworden und fügen sich auch nicht so optimal in den Korpus ein. Davon ab bin ich recht zufrieden mit dem Ergebnis. Ich mag die kleinen Türchen, die fast schon etwas von Modellbau haben, und mit gefallen die schlichten Griffe aus Bindfaden.

Die Touch-Buttons für den Wecker wollte ich eigentlich in den Korpus integrieren, also in den oberen und unteren Boden. Ich entschied mich später aber, sie in einem eigenen Rahmen unterzubringen auf dem auch das Leinen gespannt ist, hinter dem sich das Display und die Lautsprecher verbergen. Das war eine gute Entscheidung, da es deutlich einfacher war, gut funktioniert und zukünftige Änderungen erlaubt.

Details der Konstruktion kannst du den Bildern entnehmen.

Bilder


Mockup

Materialvorbereitung


Tod dem Wurm


Ich entsorge Hobelspäne nicht mehr im Bioabfall.
Über Kleinanzeigen findet man dafür immer Abnehmer*innen

Korpusbau

Ich fürchte, hier fehlt einiges an Bild-Dokumentation. Vermutlich war in der Zeit (mal wieder) mein Telefon defekt und ich habe daher keine gemacht. Schade.

Die Front für den Wecker

Abschließende Details und Oberflächenbehandlung

Hier nochmal das Projektvideo auf Youtube: