DIY Phoniebox Alternative

Jeder kennt die <$>Toniebox</$>. Man stellt eine Figur auf den Lautsprecher und dieser spielt dann das passende Hörspiel ab. Super einfach zu bedienen für Kids also. Mit den Ohren kann man noch etwas mehr steuern, aber ich weiß nicht was genau. Da ich jetzt auch im Club der Väter bin, nehme ich das als Vorwand und baue mir meinem Nachwuchs eine Phoniebox bzw. eine Phoniebox alternative.

Danke an alle beteiligten Entwickler für dieses tolle Projekt auf GitHub.

Allerdings hatte ich einige Probleme mit der Software, weshalb ich mich im laufe des Projekts dazu entschieden habe, mir die Funktionalität mit HomeAssistant zusammen zu basteln. Da muss man zwar etwas mehr einrichten aber zumindest auf meinem Raspi 3b+ funktioniert das System am Ende sogar performanter als die Phoniebox V2.

Inhalt

Zutaten

Die Phoniebox kann beim Bau erstmal etwas teurer ausfallen als wenn man das kommerzielle Vorbild kauft, aber wenn man für jede Figur einen 10er hinlegt, hat man fix den Break Even erreicht. Es gibt auch nicht die eine Phoniebox, sondern man kann sich sein SetUp selbst zusammenstellen. Ich hab z.B. noch zwei Visaton FSR 8 im Keller rumliegen, von meinem alten DIY Boombox Projekt. Aber da hätte ich irgendwie erstmal mit Verstärker etc. rumfummeln müssen. Also das kommt später mal.

Man kann also sehr Overkill werden, oder man macht es Basic und für letzteren Weg hab ich mich entschieden. Daher gibt es bei mir einfach einen USB RFID Reader und USB Lautsprecher. Hier sind meine Komponenten:

Raspi OS Lite 32-Bit installieren

Leider wird die 64-Bit Version von RaspiOS nicht unterstützt, daher flashen wir uns eine Lite (also Headless, ohne GUI) Version der 32-Bit Version auf unsere SD Karte. Dazu benutzen wir wie immer den Pi Imager. Außerdem muss man die Legacy Version (Also Bullseye statt Bookworm) nutzen – anders als im Screenshot dargestellt.

Nun macht es Sinn das Kennwort, den Hostnamen und die WLAN Einstellungen schon hier festzulegen (nachdem ihr auf Weiter geklickt habt). Dann flashen und abwarten. Wenn der Imager fertig ist, kommt die SD-Karte in den Raspberry Pi. Ich hab als Hostnamen also Phoniebox genommen.

Phoniebox V3 Installieren

Der Raspi bootet jetzt sobald ihr ihn mit Strom versorgt und versucht sich mit eurem Netzwerk zu verbinden. Wenn also keine Typos vorliegen, klappt das auch. Die IP finden wir über unser Router-WebInterface raus.

Dann öffnen wir unsere Konsole in Windows und verbinden uns mit ssh pi@eure_IP mit unserem RaspberryPi und geben das Passwort ein, was wir vorhin festgelegt haben.

ssh pi@192.168.178.xx

Dann können wir den Befehl aus dem Git kopieren und einfügen.

cd; bash <(wget -qO- https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/future3/main/installation/install-jukebox.sh)

Jetzt kommt eine Art Install-Wizard:

Yes!

IP V6 brauche ich im Heimnetz nicht, also wird es mit Y deaktiviert.

Ja, ist sinnvoll, das regelt aber meine FritzBox. Also n für nein.

AutoHotSpot ist super!

Aber ich will den Customizen. Also gebe ich hier meine Daten ein.

Bluetooth werde ich nicht benutzen.

Hier kommt es auf euer SetUp an. Bei meiner Variante lass ich die on-chip Soundkarte aktiviert.

Der RFID Reader ist das eigentliche Herzstück der Phoniebox. Also Ja

Samba ist sehr praktisch, um Files über das Heimnetz auf die Phoniebox zu schieben. Also hier auch ja.

WebApp, praktischer gehts nicht. Also auch ja.

Einen Bildschirm hab ich nicht vor zu benutzen, also N für nein.

Nun tüdelt der Raspi eine weile vor sich hin. Laut Guide im GitHub kann das, je nach Hardware und Internet-Geschwindigkeit zwischen 20 und 60 Minuten dauern. Man soll drauf achten, dass der Rechner nicht in den Sleepmodus geht, da sonst ggf. die SSH Verbindung unterbricht.

Zwischendurch fragt er mal nach dem Reader. Dieser sollte anscheinend schon eingesteckt sein, denn bei mir kam keine Liste, wie versprochen.
Dann führt er das SetUp zuende durch. Wir werden nach einem Reboot gefragt, das machen wir natürlich.

Beim nächsten Log-In werden wir mit dem Logo begrüßt:

Web Interface

Jetzt läuft unser WebInterface auch schon und wir können es im Browser mit der bekannten IP abrufen.

Da können wir uns schonmal etwas anschauen, was so geht. Man kann hier dann auch die Karten hinzufügen, etc. dazu aber später mehr.

Audio Konfigurieren

Hier führen wir ein Script aus und folgen den Anweisungen:

RPi-Jukebox-RFID/installation/components/setup_configure_audio.sh

RFID Reader Konfigurieren

Nun können wir nochmal unseren RFID Reader anschließen. Ich hab im GitHub nicht den richtigen Pfad gefunden, das ist für Anfänger etwas schwer nachzuvollziehen. Daher: Wenn ihr das RFID Reader Konfigurationsscript ausführen wollte nehmt den folgenden Befehl:

RPi-Jukebox-RFID/installation/components/setup_rfid_reader.sh

Da gab es bei mir dann ein Problem. Das Script ist immer abgebrochen, daher hab ich dann nochmal die Jukebox v2 installiert (Schritt vorher, aber anderer Link). Die Lösung (August 2025 – im Pre-Release ist das schon gefixt, gab dann aber Probleme beim Audio-Script)- Leider muss man aktuell noch jedes mal mit einem sauberen Image anfangen und dann neu installieren. Der Ablauf ist aber identisch.

Phoniebox V2 installieren

Falls bei euch die V3 auch nicht läuft, hier noch der Guide zur V2. Wir können wieder einfach ein Install One-Line ausführen, nachdem wir mit einem sauberen Image starten:

cd; rm install-jukebox.sh; wget https://raw.githubusercontent.com/MiczFlor/RPi-Jukebox-RFID/master/scripts/installscripts/install-jukebox.sh; chmod +x install-jukebox.sh; ./install-jukebox.sh

Das ganze ruft wieder einen Wizard auf, durch den wir uns entsprechend durchklicken. Sämtliche Hardware sollte schon vorher verbunden werden.

Dann brauchen wir einen reboot.

Wir loggen uns neu ein und dann schalten wir zunächst mal das WiFi Power Management aus:

sudo iwconfig wlan0 power off

Und das wars auch eigentlich schon. In der V2 sieht das Webinterface etwas oller aus, aber hat schon deutlich mehr funktionen:

Wenn ihr die Version mit Spotify habt, müsst ihr vorher noch ein Key-Paar mit euren Zugangsdaten erzeugen, die ihr dann im Wizard eingeben müsst:

Bei mir klappt das mit der Phoniebox leider nur so semi gut. Insgesamt läuft es und ich kann mit den Test-Files auch das Nutzererlebnis nachbilden. Aber wenn ich eigene hochgeladene Files oder Streams starten will, startet der Player leider nicht. Leider übersteigt das auch meine Coding-Skills. Also mal gucken wann die V3 weitere Features implementiert hat.

Phoniebox Alternative mit HomeAssistant und NodeRED

Wer nicht auf die Entwicklung von V3 warten möchte der kann sich mit ein wenig Geduld und im Prinzip der gleichen Hardware eine alternative Basteln. Dazu können wir z.B. HomeAssistant mit NodeRED benutzen. Falls ihr schon eine Instanz mit HomeAssistant habt, empfehle ich trotzdem eine neue für die Jukebox bzw. Phoniebox aufzusetzen. Prinzipiell gibt es hier aber verschiedene Möglichkeiten das ganze umzusetzen.

  • NFC Reader mit ESP und ESP Home oder Tasmota
  • NFC Reader am RaspberryPi mit NodeRED only
  • NFC Reader am Pi mit HomeAssistant
  • NRC Reader am Pi mit Python only
  • Lautsprecher am Raspi
  • Lautsprecher Satelliten

Streng genommen braucht ihr HomeAssistant also nicht, aber das macht uns das leben später einfacher und gibt uns viele coole Möglichkeiten, was wir eigentlich mit den RFID Karten noch so alles machen können. Zum Beispiel kann die Phoniebox Alternative dann auch ein generelles kinderfreundliches Interface werden, um auch andere Medien auf anderen Geräten abzuspielen. Oder man kann das Licht damit steuern, oder halt alles zusammen Mischen und einen KinderDisco Modus bauen. Auch könntet ihr z.B. so die Lautstärke an Tageszeiten oder Sonnenstand koppeln. Alles ist möglich.

Home Assistant flashen

Im RaspberryPi Imager kann man mittlerweile direkt ein HomeAssistant Image wählen und flashen:

Wenn Home Assistant fertig geflasht ist, kommt die SD Karte in den Raspberry Pi. Anders als normalerweise, kann man hier leider nicht vorher schon das WLAN einrichten. Daher muss jetzt erstmal der Pi per Netzwerkkabel ins heimische LAN.

Mit der IP und Port 8123 kommt ihr auf das Home Assistant Webinterface. Hier steht, das wir erstmal 20 Minuten warten müssen, dann können wir einen User anlegen und das erste SetUp machen. Da ich das aber nur als Krücke nutze, für die Jukebox, beachte ich das alles nicht so richtig.

http://192.168.178.xx:8123

Node RED AddOn

Dann wechseln wir zu Einstellungen – AddOns und installieren NodeRED.

NodeRED ist quasi Programmieren aber ohne Syntax. Mit der Node-basierten (zu deutsch Knoten) Oberfläche kann man logische Abfolgen verknüpfen und so Dinge automatisieren. Wenn das – dann das. Da kann man tolle Sachen mit machen, wie hier auch schon in manchen Beiträgen beschrieben.

Sobald NodeRED installiert ist, können wir das AddOn starten (in Seitenleiste anzeigen aktivieren) – das dauert einen Moment:

Und dann zur Benutzeroberfläche wechseln (hab ein Dark Theme aktiviert):

Während NodeRED installiert können wir in einem kleinen SideQuest unseren Raspi ins WLAN bringen. Unter Einstellungen – System – Netzwerk können wir das machen.

Palette in NodeRED installieren

Eigentlich wollte ich über eine contribute-palette gehen, aber ich hab die in HomeAssistants NodeRED nicht installiert bekommen. Es geht um diese hier: https://flows.nodered.org/node/node-red-contrib-usbhid

Die kann man wie auf den Screenshots installieren und muss nicht in der Konsole per npm installieren. Allerdings kommt bei mir im Log dann ganz viel Fehlerkram, der meine Kenntnisse übersteigt. Also gern in die Kommentare, wer da was weiß.

Alternative

Also muss eine alternative her. Wenn ich mir z.B. in der Phoniebox das Python Script zum auslesen des Readers anschaue:

https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/develop/scripts/Reader.py

Dann sehe ich folgenden Code, der nichtmal großartig librarys nutzt.

import os.path
import sys

from evdev import InputDevice, ecodes, list_devices
from select import select


def get_devices():
    return [InputDevice(fn) for fn in list_devices()]


class Reader:

    def __init__(self):
        path = os.path.dirname(os.path.realpath(__file__))
        self.keys = "X^1234567890XXXXqwertzuiopXXXXasdfghjklXXXXXyxcvbnmXXXXXXXXXXXXXXXXXXXXXXX"
        if not os.path.isfile(path + '/deviceName.txt'):
            sys.exit('Please run RegisterDevice.py first')
        else:
            with open(path + '/deviceName.txt', 'r') as f:
                deviceName = f.read()
            devices = get_devices()
            for device in devices:
                if device.name == deviceName:
                    self.dev = device
                    break
            try:
                self.dev
            except Exception:
                sys.exit('Could not find the device %s\n. Make sure is connected' % deviceName)

    def readCard(self):
        stri = ''
        key = ''
        while key != 'KEY_ENTER':
            r, w, x = select([self.dev], [], [])
            for event in self.dev.read():
                if event.type == 1 and event.value == 1:
                    stri += self.keys[event.code]
                    # print( keys[ event.code ] )
                    key = ecodes.KEY[event.code]
        return stri[:-1]

Dann ist das relativ überschaubar. Dann hab ich noch diesen Git hier gefunden:

https://github.com/yaseralie/Raspberry-Pi-USB-RFID-Reader-125-Khz/blob/main/codes/test_rfid1.py

Und auch dort ist der Code zum Auslesen überschaubar.

Ich hab also geschaut wie ich Python aus NodeRED heraus ausführen kann. Und daraus hat sich wieder ein SideQuest ergeben und ich hab herausgefunden, wie es noch viel einfacher geht:

Mit der KeyBoard Remote Integration.

Keyboard Remote

Wir können den USB Reader einfach anschließen und gehen dann unter Einstellungen – System – Hardware und dann auf gesamte Hardware anzeigen. Dann sehen wir unter event0 unseren RFID Reader.

Hier sehen wir den Pfad, der bei euch natürlich leicht anders sein kann:

/dev/input/by-id/usb-Sycreader_RFID_Technology_Co.__Ltd_SYC_ID_IC_USB_Reader_08FF20140315-event-kbd

Dann brauchen wir noch das Add-On „File Editor“ – damit wir unsere config.yaml editieren können, da die Integration Keyboard Remote nicht im GUI implementiert ist. Also wechseln wir wieder auf Einstellungen – AddOns und suchen im AddOn Store nach File Editor. Wenn wir schonmal hier sind würde ich auch noch das SSH Terminal installieren. Vergesst nicht die AddOns auch zu starten und dabei „In der Seitenleiste Anzeigen“ zu aktivieren.

Wenn wir soweit sind, gehen wir im File Editor in die config.yaml

Wir fügen dann unseren Codeblock hinzu, wobei ihr ggf. euren Pfad anpassen müsst:

#Keyboard Remote for RFID Reader Neuftech
keyboard_remote:
- device_descriptor: '/dev/input/by-id/usb-Sycreader_RFID_Technology_Co.__Ltd_SYC_ID_IC_USB_Reader_08FF20140315-event-kbd'
  type: "key_down"
  emulate_key_hold: true
  emulate_key_hold_delay: 0.25
  emulate_key_hold_repeat: 0.033

Speichern nicht vergessen. Oben rechts erkennen wir am kleinen grünen Haken das unsere Syntax zumindest erstmal keine groben Fehler hat. Wenn dort ein rotes X zu sehen ist, stimmen höchstwahrscheinlich die Einrückungen nicht. Dann müssen wir HomeAssistant einmal neu starten, da reicht aber der Quick Reset, wo einfach die YAML neu geladen werden unter Entwicklerwerkzeuge – YAML – Alle YAML Konfigurationen neu laden.

Jetzt können wir unter Entwicklerwerkzeuge – Ereignisse (Events) einen Listener starten, welcher auf die Keyboard Ereignisse lauscht. Dazu kommt der Name des Events ins entsprechende Feld.

keyboard_remote_command_received

Nun können wir eine unserer RFID Karten oder RFID Chips oder RFID Sticker über den Leser ziehen und schauen was passiert.

Lasst euch von meinem Screenshot nicht täuschen. Jede Karte hat nun mehrere Events abgefeuert. Jede Zahl ist wie eine Tasteneingabe auf der Tastatur. Aber damit können wir arbeiten. Soweit so gut. Und das ganz ohne wilde Python Scripts.

Jetzt gibt es grundsätzlich zwei Möglichkeiten die „Intelligenz“ abzubilden. Entweder mappen wir jede Karten ID in einer Liste zu entsprechenden Links zu Playlists oder was auch immer wir machen möchten. Oder wir speichern den Befehl direkt auf die Karte. Hat beides seine Vor und Nachteile.

Ich nehme Option 1 und lausche jetzt einfach so lange bis die ID durch ist, fasse diese in einem String zusammen und ordne das dann in Home Assistant bestimmte befehlen zu. Und das können wir halt super in Node RED machen.

Zurück in NodeRED

Wir suchen links in der Palette nach dem Event-Node und ziehen uns dem per Drag & Drop auf die Arbeitsfläche. Dann können wir mit Doppelklick in die Optionen des Nodes und wieder nach dem richtigen Event filtern.

keyboard_remote_command_received

Speichern auf den Fertig Button oben rechts. Dann brauchen wir noch einen Debug-Node und können beide Nodes verbinden (Drag & Drop auf dem Kästchen rechts und das Kabel zum nächsten Node ziehen). Mit Übernahme (deploy) aktivieren wir unseren Flow und können dann die Karte über den Reader ziehen.

Rechts können wir dann im Debug Reiter (kleines Käfer Icon) wieder die virtuellen Tastenschläge sehen. Man muss nach jedem Deploy einen kleinen Moment warten, bis dann auch alles funktioniert. Vorher kommt nix durch. Also nicht wundern, falls nix passiert im Debug Node.

Als nächstes fügen wir einen Join-Node dazwischen ein und fügen msg.payload.event.key_code zusammen zu einem String. Wichtig ist hierbei den String nach 11 Nachrichtenteilen auch weiter zu schicken, sonst passiert nix weiter und der Join Node sammelt einfach fröhlich für immer weiter.

Wir könnten jetzt noch die Zahlen in die korrekten IDs umwandeln, das spar ich mir aber zu beschreiben. Der Flow ist hier zum kopieren, da könnt ihr da mal reinschauen.

Ich entferne dann noch mit einem Function-Node alles was ich nicht brauche, so dass nur noch der zusammengefügte KeyCode in der msg.payload verbleibt.


var keyCode = msg.payload.event.key_code;

msg.payload = keyCode;
return msg;

Jetzt könnte man den String noch in eine Variable speichern und dann die normalen HA Automationen nutzen. Dazu legen wir einen Helfer (Einstellungen – Integrationen – Helfer) an.

Dann müssen wir wieder alle YAML neu laden und finden dann den Helfer auch im NodeRED. Den String zu speichern können wir mit einem Action-Node, den wir wie auf dem Bild konfigurieren. Wichtig ist hier im Feld Data den richtigen JSON String einzufügen.

Das ganze können wir prüfen, wenn wir in den Entwicklerwerkzeugen auf Zustände gehen und nach dem wir einen Tag gescannt haben, wird die letzte ID dort abgelegt.

Im Screenshot sind noch die Tastenschläge wie sie gelesen werden und noch nicht zur eigentlichen ID umgewandelt. Ich bin auch nicht sicher ob das Script für alle NFC Typen gleich sein muss. Ich nutze <$>diese Karten</$>, da funktioniert es.

Hier ist noch mein Flow zum Copy&Pasten. Vergesst nicht euren input_text zu aktualisieren.

[{"id":"1193d34d140a1043","type":"server-events","z":"943a2af227025912","name":"","server":"b1dc9f33.6a1d4","version":3,"exposeAsEntityConfig":"","eventType":"keyboard_remote_command_received","eventData":"","waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":370,"y":220,"wires":[["19370f8e00e8f7e7"]]},{"id":"6d7ae99e2df0803a","type":"debug","z":"943a2af227025912","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":220,"wires":[]},{"id":"891bf7082b0b3683","type":"join","z":"943a2af227025912","name":"Join Strokes","mode":"custom","build":"string","property":"payload.event.key_code","propertyType":"msg","key":"topic","joiner":"","joinerType":"str","useparts":false,"accumulate":false,"timeout":"","count":"11","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":740,"y":240,"wires":[["7307d6064019287a"]]},{"id":"7307d6064019287a","type":"function","z":"943a2af227025912","name":"reduce","func":"\nvar keyCode = msg.payload.event.key_code;\n\nmsg.payload = keyCode;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":850,"y":320,"wires":[["6d7ae99e2df0803a","b52a1fcade820ae7"]]},{"id":"19370f8e00e8f7e7","type":"function","z":"943a2af227025912","name":"function 2","func":"var keyCode = msg.payload.event.key_code;\n\nif (Number(keyCode) == 11){\n    msg.payload.event.key_code=0;\n}\nelse if (Number(keyCode) == 28){\n    msg.payload.event.key_code=\"\";\n}\nelse {\n    msg.payload.event.key_code = Number(keyCode) - 1;\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":640,"y":420,"wires":[["891bf7082b0b3683"]]},{"id":"b52a1fcade820ae7","type":"api-call-service","z":"943a2af227025912","name":"save","server":"b1dc9f33.6a1d4","version":7,"debugenabled":false,"action":"input_text.set_value","floorId":[],"areaId":[],"deviceId":[],"entityId":["input_text.last_tag"],"labelId":[],"data":"{\"value\":payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":true,"domain":"input_text","service":"set_value","x":990,"y":420,"wires":[[]]},{"id":"b1dc9f33.6a1d4","type":"server","name":"Home Assistant","addon":true}]

Nun nutzen wir einfach die letzte ID und das Event als Trigger und nutzen dann Variablen in der Automation wie einen klassischen switch case. So brauchen wir nur 1x den eigentliche Abspiel Befehl und können dann die URL sozusagen dynamisch befüllen.

Dafür brauchen wir noch einen Media Player. Home Assistant hat zwar einen, das ist aber eher zum Verwalten von Satelliten (also Alexa, Google Pod, etc) gedacht. Wir können hier z.B. VLC oder MPD nehmen. Ich hab beides Probiert und fand MPD resourcenschonender.

MPD einrichten

Das war für mich am Anfang etwas weird, da die MPD (Music Player Daemon) Integration standardmäßig im HomeAssistant kommt. Diese braucht aber ein AddOn um zu funktionieren. Da brauchen wir aber eine zusätzliche Repository. Wir gehen also auf Einstellungen – AddOns und öffnen den AddON Store. Dann oben auf die drei Punkte und auf Repositorys. Jetzt können wir folgende URL hinzufügen.

https://github.com/Poeschl-HomeAssistant-Addons/repository

Nun sehen wir im AddOn Store zusätzliche Quellen aus denen wir AddOns installieren können. Wir installieren MPD.

Wenn das durch ist wird diese wieder gestartet (und in der Seitenleiste anzeigen) und wir können noch die Integration hinzufügen. Da könnt ihr bei der IP einfach 127.0.0.1 eintragen. Die IP verweist immer auf das selbe Gerät.

Nun können wir Medien auf dem Raspberry Pi Host abspielen. Das können wir einmal über Medien – Google Text to Speech testen (Lautsprecher sollten angeschlossen sein).

Automation

Nun müssen wir noch die RFID Karte mit einem Befehl verknüpfen. Das machen wir mit einer Automation. Die erstellen wir unter Einstellungen – Automatisierung. Oben rechts können wir dann in den YAML Modus wechseln und einfach meinen Block reinkopieren, den ihr dann ggf. auf eure Variablen bzw. device_id anpassen müsst. Dazu könnt ihr in den Visuellen Editor zurück wechseln und euren Player bei Zielgerät auswählen.

alias: JukeBox Magic
description: >-
  When you Scan an RFID Tag and it got processed in NodeRED, it plays a file in
  MPD
triggers:
  - trigger: state
    entity_id:
      - input_text.last_tag
conditions: []
actions:
  - variables:
      tag_id: "{{ tag_id[states('input_text.last_tag')] }}"
  - action: media_player.play_media
    target:
      device_id:
        - c3b9ac45bff5e52d4f86e3a8e3680f3c
    data:
      media_content_id: "{{ tag_id }}"
      media_content_type: music
variables:
  tag_id:
    "0007937448": Tiergeräusche/rabe.mp3
    "0007937585": >-
      Hörspiele/Pumuckl/250503_1200_Pumuckl---Der-Hoerspiel-Klassiker_Pumuckl-und-der-Waldspaziergang.mp3
mode: single

Wichtig ist den Inhaltstyp auch wieder neu auszufüllen.

Das ganze funktioniert wir folgt: Wenn unser input_text Helfer aktualisiert wird, egal wie, dann feuert der die Wiedergabe im MPD. Und die media_content_id wird über die tag_id Variable gesteuert und entsprechend ausgetauscht.

Die media_content_id können wir herausfinden, wenn wir über das GUI in MPD unsere Files starten und dann unter Entwicklerwerkzeuge – Zustände bei media_player schauen. Bei mir ist es einfach immer der Dateipfad, wie auch im Code zu sehen.

"0007937585": >-
      Hörspiele/Pumuckl/250503_1200_Pumuckl---Der-Hoerspiel-Klassiker_Pumuckl-und-der-Waldspaziergang.mp3

Jetzt können wir einfach unsere Karten-IDs entweder einlesen oder direkt von der Karte ablesen falls aufgedruckt und unsere Medienbibliothek so aufbauen.

Das ganze funktioniert auch mit Playlisten. In dem Fall müssen wir hier als media_content_type Playlist übergeben. Also müssen wir hier noch unsere Automatisierung etwas verkomplizieren und checken, was wir abspielen wollen.

Dazu brauchen wir einfach eine zweite Variable, die jedem Tag noch den content type zuordnet. Ist also etwas mehr Arbeit beim Einpflegen.

alias: JukeBox Magic
description: >-
  When you Scan an RFID Tag and it got processed in NodeRED, it plays a file in
  MPD. Just add id with the tag ID and the correct media_content_id in the tag_id 
  variable under variables at the bottom and dont forget to add tag_types as well for playlists.
triggers:
  - trigger: state
    entity_id:
      - input_text.last_tag
conditions: []
actions:
  - variables:
      tag_id: "{{ tag_id[states('input_text.last_tag')] }}"
      tag_type: "{{ tag_id[states('input_text.last_tag')] }}"
  - action: media_player.play_media
    target:
      device_id:
        - c3b9ac45bff5e52d4f86e3a8e3680f3c
    data:
      media_content_id: "{{ tag_id }}"
      media_content_type: "{{ tag_type }}"
variables:
  tag_id:
    "0007937448": Eure_Playlist.m3u
    "0007937585": >-
      Hörspiele/Pumuckl/250503_1200_Pumuckl---Der-Hoerspiel-Klassiker_Pumuckl-und-der-Waldspaziergang.mp3
  tag_type:
    "0007937448": playlist
    "0007937585": music
mode: single

Und Webradios gehen auch. Da bleibt es bei type bei music aber in die content_id kommt einfach der Link zum Stream:

"0007937448": >-
      https://streams.fluxfm.de/live/mp3-320/audio/

Samba

Um ganz einfach Medien auf unsere Phoniebox Alternative schieben zu können, empfielt sich Samba Share. Das AddOn können wir einfach über en AddOn Store hinzufügen, legen in der Konfiguration noch Nutzernamen und Kennwort an und starten dann. Nun können wir einfach im Windows Explorer mit \\ und der IP auf unsere Freigegebenen Ordner zugreifen.

Der Pfad für die Mediendateien lautet:

\\192.168.178.XX\media\mpd\media\

Playlisten kommen in

\\192.168.178.XX\media\mpd\playlists\

NAS Files

Wenn wir dann Dateien abspielen möchten, die wir auf einem NAS liegen haben – z.B. auf unserer Torrentbox müssen wir noch das entsprechene (virtuelle) Laufwerk mounten.

Bei dem roten Feld muss der Ordner Name rein, den ihr Freigegeben habt. Dann noch Username / PW und dann sehen wir unser Netzlaufwerk unter Medien / My Media:

Testing ohne Karten

Falls wir keine Karten zur Hand haben, kann man die Funktion der Automatisierung auch über die Entwicklerwerkzeuge testen , in dem wir einfach händisch den Zustand des input_text mit einer in der Automatisierung hinterlegten Tag_ID überschreiben.

Alternative 2: Python Script in NodeRED

Hier der Vollständigkeit halber noch ganz Grob, die alternative mit Python, da wir ja was lernen wollen:

Wir können den folgenden Flow bei uns reinkopieren:

[{"id":"b9d7d6aff0016631","type":"inject","z":"4210b295c8131293","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":200,"wires":[["2e1daccf2a7b3d0f"]]},{"id":"d2d1450deaa588f4","type":"file","z":"4210b295c8131293","name":"","filename":".\\reader.py","filenameType":"str","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":450,"y":200,"wires":[["e140a8508fb10d96"]]},{"id":"e140a8508fb10d96","type":"debug","z":"4210b295c8131293","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":640,"y":200,"wires":[]},{"id":"2e1daccf2a7b3d0f","type":"template","z":"4210b295c8131293","name":"script","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"#!/usr/bin/env python3\nimport RPi.GPIO as GPIO\nimport time\nimport datetime\n\nserial = spi(port=0, device=0, gpio=noop())\nid = 1\n\nwith open('/dev/tty0', 'r') as tty:\n\twhile True:\n\t\t\n\t\trfid = tty.readline()\n\t\ttime.sleep(0.5)\n\t\tif rfid:\n\t\t\trfid = str(rfid)[:10]\n\t\t\tmsg = rfid\n\t\t\tprint (msg)\n\n\t\ttime.sleep(0.5)","output":"str","x":270,"y":200,"wires":[["d2d1450deaa588f4"]]},{"id":"2e26b84c0ce17312","type":"exec","z":"4210b295c8131293","command":"python ./reader.py -u","addpay":"","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"","x":360,"y":400,"wires":[["89589c56117004e0"],["7fdb901b144749c2"],["3f49b49308941782"]]},{"id":"739e08c1ec77c2a1","type":"inject","z":"4210b295c8131293","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":400,"wires":[["2e26b84c0ce17312"]]},{"id":"89589c56117004e0","type":"debug","z":"4210b295c8131293","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":590,"y":360,"wires":[]},{"id":"7fdb901b144749c2","type":"debug","z":"4210b295c8131293","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":590,"y":400,"wires":[]},{"id":"3f49b49308941782","type":"debug","z":"4210b295c8131293","name":"Return code","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":610,"y":440,"wires":[]}]

Das geht in dem man einfach einen Rechtklick auf die Zeichenfläche macht und dann unter Einfügen auf Import geht.

Dann können wir im Flow unser eigenes Script in eine .py File schreiben und diesen im zweiten Flow Aufrufen bzw. Ausführen.

Ganz cool eigentlich und sicher nützlich für andere Spielereien.

Quelle: https://flowfuse.com/blog/2024/07/calling-python-script-from-node-red/

Im Block ist schon ein Script was eigentlich einen RFID Reader lesen sollte, aber das hat bei mir nicht geklappt. Weiter unten zeige ich euch noch eine andere Variante, Python in Home Assistant zu nutzen.

On/off Shim bzw. Shutdown button

Wenn man nicht im HomeAssistant unterwegs ist könnte man einfach mit

curl https://get.pimoroni.com/onoffshim | bash

das nötige Softwaregerüst installieren. Aber wir sind mit root im System, da meckert das Script. Jetzt hab ich leider nicht herausgefunden wie man das ändern könnte. Ich hab das Teil aber schon angelötet, also ist aufgeben (noch) keine Option.

Nach ein wenig Recherche hab ich im RetroPi Forum ein Script gefunden. Damit und mit ein wenig „krimineller Energie“ sollten wir uns einen Workaround basteln können. Wir müssen uns nämlich ein Bash-Script bei Systemstart ausführen und das geht indirekt über das Terminal-AddOn.

Dabei braucht man aber ein Script in SystemD. Das gibt es bei mir aber nicht.

Der Kernel hat aber einen GPIO ShutOff implementiert. Um diesen zu aktivieren, müssen wir die /boot/config.txt editieren. Die Datei liegt aber auf einer anderen Partition, die nicht gemountet ist. In HomeAssisstant gibt es verschiende komplizierte wege das zu ändern. Für mich war es am einfachsten, den Pi auszuschalten und die SD Karte in den Laptop zu packen. Denn die boot Partition ist dann in Windows sichtbar und wir können einfach mit dem Editor die Zeile hinzufügen:

dtoverlay=gpio-shutdown,gpio_pin=4,active_low=1,gpio_pull=up

Wenn wir jetzt an GPIO pin 4 das Signal auf 0 (low) setzen, fährt der Raspi herunter und wartet dass der Pin wieder auf 1 (high) geht um dann zu booten. Diese Funktion muss man mit einem bash Script machen.

Das OnOffShim hat außerdem einen Trigger-Pin auf GPIO 17. Der Button am Board leitet das Signal dann an den Pi durch.

Letztendlich kann man also auf das OnOff Shim verzichten und einfach einen Button zwischen Ground und einem GPIO (hier 4) machen. Wenn man dann eine lange debounce-Zeit wählt, hat man den Effekt, dass man den Button etwas länger drücken muss. z.B. 3 Sekunden.

dtoverlay=gpio-shutdown,gpio_pin=4,active_low=1,gpio_pull=up,debounce=3000

Damit der Pi wieder bootet beim gleichen Knopf muss es aber GPIO 3 sein (wenn man nicht das OnOffShim nutzt).

Wie man also das OnOffSHIM unter HomeAssistant nutzt, bleibt offen.

Buttons

Jetzt wird es wild. Wir brauchen den Home Assistant Community Store – kurz HACS um die GPIO Integration zu bekommen. Den HACS bekommen wir, in dem wir bei unseren AddOn Repositories wieder eine neue Quelle hinzufügen:

https://github.com/hacs/addons

Dann installieren wir das GetHACS AddOn und starten es.

Das sollte uns den HACS installieren. Dann müssen wir noch in den Integrationen HACS hinzufügen:

Irgendwann müsst ihr euch noch mit eurem GitHub Account authentifizieren.

Wenn das alles läuft, dann installieren wir uns das GPIO AddOn:

Das ist eine reine config.yaml Integration. Das heißt hier brauchen wir nicht in den Integrationsmanager. Stattdessen öffnen wir den File-Editor und öffnen dann configuration.yaml in die wir die folgenden Buttons einfügen:

#GPIO Buttons
binary_sensor:
  - platform: rpi_gpio
    sensors:
      - port: 27
        name: "Play"
        unique_id: "play"
        bouncetime: 50
        invert_logic: false
        pull_mode: "DOWN"
        
      - port: 22
        name: "Next"
        unique_id: "next"
        bouncetime: 50
        invert_logic: false
        pull_mode: "DOWN"
        
      - port: 23
        name: "Prev"
        unique_id: "prev"
        bouncetime: 50
        invert_logic: false
        pull_mode: "DOWN"

Dann müssen wir HomeAssistant einmal neu starten, damit die Buttons auch im System sichtbar werden. Anschließend erstellen wir uns für jeden Button eine Automation mit der entsprechenden Funktion.

Als Trigger wählen wir Entität – Zustand. Und der soll sich von Aus zu Ein wechseln. Für den Play/Pause button soll es dann ja eben das entsprechende Kommando sein.

Und hier noch als YAML fürs fixe copy & paste:

alias: GPIO - Play/Pause
description: ""
triggers:
  - trigger: state
    entity_id:
      - binary_sensor.play
    from: "off"
    to: "on"
conditions: []
actions:
  - action: media_player.media_play_pause
    metadata: {}
    data: {}
    target:
      device_id: c3b9ac45bff5e52d4f86e3a8e3680f3c
mode: single
alias: GPIO - Prev
description: ""
triggers:
  - trigger: state
    entity_id:
      - binary_sensor.prev
    from: "off"
    to: "on"
conditions: []
actions:
  - action: media_player.media_previous_track
    metadata: {}
    data: {}
    target:
      device_id: c3b9ac45bff5e52d4f86e3a8e3680f3c
mode: single
alias: GPIO - Next
description: ""
triggers:
  - trigger: state
    entity_id:
      - binary_sensor.next
    from: "off"
    to: "on"
conditions: []
actions:
  - action: media_player.media_next_track
    metadata: {}
    data: {}
    target:
      device_id: c3b9ac45bff5e52d4f86e3a8e3680f3c
mode: single

Wir können dann an unseren Buttons je die rote Leitung auf einer Wago-Klemme zusammenlegen und mit einem Leiter auf den +5V Pin am Pi legen. Die schwarzen Kabel gehen auf den jeweiligen GPIO Pin aus der configuration.yaml. Das könnt ihr natürlich auch anpassen wenn ihr wollt.

Testen nicht vergessen und bei selbst gecrimpten Hülsen gern auch nochmal durchmessen vorher. Bei mir sind oft eher in der Hardware die Fehlerquellen.

Rotary Encoder

Den Rotary Encoder an den RaspberryPi GPIO Pins ans laufen zu bekommen war tricky! Ich hab Ewig und drei Tage gegoogelt und viele Varianten gefunden. Am Ende ist es aber doch relativ simpel. Ganz um ein wenig Python bin ich aber nicht herum gekommen.

Wie weiter oben schon, müssen wir in der boot/config.txt wieder einen device tree overlay hinzufügen, damit der Kernel das entsprechende Interface lädt. Die Pins könnt ihr natürlich wieder frei wählen.

dtoverlay=rotary-encoder,pin_a=24,pin_b=25,relative_axis=1

Wenn der Pi wieder gebootet ist, könnt ihr unter Einstellungen – Hardware auf gesamte Hardware anzeigen. Dann sollten wir ein neuen event Eintrag sehen. Der andere ist unser RFID reader. Wir brauchen wieder den Pfad.

/dev/input/by-path/platform-rotary@18-event

Python Scripts Pro

Als nächstes brauchen wir wieder eine Integration aus dem HACS. Nämlich Python Scripts Pro. HomeAssistant hat zwar standardmäßig eine Integration für Python Scripts aber die erlaubt keine Imports. Wir benötigen aber evdev für das Auslesen der Events. Wir wechseln also wieder zum HACS und suchen uns das entsprechende Softwarepaket und laden es herunter.

Auch diese Integration wird nur per YAML aktiviert. Dazu gehen wir also wieder in den File-Editor in die configuration.yaml. Wir fügen dann folgende Zeilen ein:

#pythonscript pro
python_script:  # no S at the end!
    requirements:
    - RPi.GPIO
    - evdev

Die bedeuten einfach nur, dass wenn wir Librarys brauchen, checkt die Integration ob diese schon installiert sind und wenn nicht, werden die Installiert. Wir brauchen RPi.GPIO und evdev.

Theoretisch kann die Integration auch direkt Python-Basierte Sensoren anlegen, das hat bei mir aber mit der While-Schleife nicht richtig funktioniert. Ggf. kann man das aber mit einem Update Intervall auffangen. War mir aber zu doof, deshalb mach ich einfach ein python script welches ich per Automatisierung nach dem Booten von HomeAssistant starte. Das läuft dann im Hintergrund und lauscht auf Events vom Encoder.

Wir legen uns also einen neuen Ordner an den wir z.B. python_scripts nennen. Und in dem Ordner legen wir eine Datei an die nennen wir rotary_encoder.py.

In die Datei kommt der folgende Code:

import RPi.GPIO as GPIO
import evdev

dev_rotary_encoder = evdev.InputDevice('/dev/input/event1')
rotary_change=40
#rotary_value = hass.states.get('media_player.music_player_daemon').attributes['volume_level'] * 100
hass.bus.fire("rotary_event", "Script is running")
        
while True:
    #rotary_change = hass.states.get('media_player.music_player_daemon').attributes['volume_level'] * 100
    while True:
            event = dev_rotary_encoder.read_one()
            #hass.bus.fire('rotary_event', {'event': f"{event}"})
            if event is None:
                #hass.bus.fire("rotary_event", rotary_change)
                break
            else:
                if event.type == evdev.ecodes.EV_REL:
                    #hass.bus.fire('rotary_event', {'event': f"{event.value}"})
                    rotary_change += event.value
                    hass.bus.fire('rotary_event', {'new_value': f"{rotary_change}"})
                    #hass.states.set('media_player.music_player_daemon').attributes['volume_level'] * 100
                    #hass.states.set('media_player.music_player_daemon', {
                    #    'volume_level': f"{rotary_change/100}"
                    #})

Die Auskommentierten zeilen habe ich zum Debugging gebraucht, die könnt ihr an sich auch Weglassen. Es ist nur vorsicht geboten, wenn ihr sehr viele Events feuert (erster if case). Da kann euer HomeAssistant zusammenbrechen irgendwann.

Ihr könnt Pyhton Scripte entwickeln, in dem ihr unter Entwicklerwerkzeuge – Aktionen den Dienst ausführt mit dem Pfad zum Script. Dann den Cache auf Aus setzen und ausführen. Nun könnt ihr Life den Code in der Datei bearbeiten und das script neu Starten mit dem Dienst.

Wenn ihr Events feuert müsst ihr in einem neuen Browser-Tab unter Entwicklerwerkzeuge – Ereignisse das entsprechende Ereignis aboniert. (Hier rotary_event).

Jetzt brauchen wir noch die entsprechenden Automatisierungen, die bei dem Event auch die Lautstärke anpassen und eine die das Script beim booten startet. Das geht weider unter Einstellungen – Automatisierungen.

alias: MPD - volume - start rotary script on boot
description: ""
triggers:
  - trigger: homeassistant
    event: start
conditions: []
actions:
  - action: python_script.exec
    metadata: {}
    data:
      file: python_scripts/rotary_encoder.py
      cache: false
mode: single
alias: MPD - volume control - event listener
description: ""
triggers:
  - trigger: event
    event_type: rotary_event
conditions: []
actions:
  - action: media_player.volume_set
    metadata: {}
    data:
      volume_level: "{{ trigger.event.data.new_value | int / 100 }}"
    target:
      device_id: c3b9ac45bff5e52d4f86e3a8e3680f3c
mode: single

Ich hab dann noch spielereien umgesetzt wie nach Sonnenuntergang die Lautstärke auf 20% zu setzen und den rotary encoder zu deaktivieren. und nach 7 uhr morgens wird das wieder aktiviert.

alias: MPD - volume 20% after sundown
description: ""
triggers:
  - trigger: sun
    event: sunset
    offset: 0
conditions: []
actions:
  - action: media_player.volume_set
    metadata: {}
    data:
      volume_level: 0.2
    target:
      device_id: c3b9ac45bff5e52d4f86e3a8e3680f3c
  - action: automation.turn_off
    metadata: {}
    data:
      stop_actions: true
    target:
      entity_id: automation.mpd_volume_control_event_listener
mode: single
alias: MPD - reactivate volume control in the morning
description: ""
triggers:
  - trigger: time
    at: "08:00:00"
    weekday:
      - mon
      - tue
      - wed
      - thu
      - fri
  - trigger: time
    at: "09:00:00"
    weekday:
      - sat
      - sun
conditions: []
actions:
  - action: automation.turn_on
    metadata: {}
    data: {}
    target:
      entity_id: automation.mpd_volume_control_event_listener
mode: single

Rotary Button

Dann kann man mit dem auch noch Klicken, z.B. zum Muten oder was euch sonst noch einfällt. Ggf. lässt man das bei groben Kinderhänden auch weg.

Da nehm ich einfach wieder einen GPIO Button her. Wir ergänzen also im FileEditor in der config.yaml:

      - port: 9
        name: "click"
        unique_id: "click"
        bouncetime: 50
        invert_logic: true 
        pull_mode: "DOWN"

Und triggern damit eine Automation.

alias: GPIO - RotaryEncoder Click - Mute
description: ""
triggers:
  - trigger: state
    entity_id:
      - binary_sensor.click
    from: "off"
    to: "on"
conditions: []
actions:
  - if:
      - condition: state
        entity_id: media_player.music_player_daemon
        attribute: is_volume_muted
        state: "false"
    then:
      - action: media_player.volume_mute
        metadata: {}
        data:
          is_volume_muted: false
        target:
          device_id: c3b9ac45bff5e52d4f86e3a8e3680f3c
    else:
      - action: media_player.volume_mute
        metadata: {}
        data:
          is_volume_muted: true
        target:
          device_id: c3b9ac45bff5e52d4f86e3a8e3680f3c
mode: single

RFID Karten gestalten

Damit das ganze System auch stylisch ist, mein Kind soll ja schließlich auch Designer werden (:D) kann man natürlich over the top bei den Karten gehen. Da es im laufe der Zeit aber ein lästiger Task sein kann, mach ich mir das leben einfacher mit einem Script und einem Template. Dazu kommt dann aber bald ein eigener Blog-Post.

Spotify und Spotify Connect

Natürlich geht auch Spotify. Ihr könnt dazu einfach die passenden Integrationen installieren. Der Pfad in der Automation (ihr erinnert euch, aka media ID) für Spotify ist dann leicht anders:

Music Assistant

Hier wollte ich noch Music Assistant erwähnen, das war aber zu Ressourcen fressend auf meinem Pi. Kann man als alternative zu MPD nehmen und dann mit der PhonieBox auch auf anderen Wiedergabegeräten im Haus die Medien abspielen. Ist relativ leicht zu installieren und ist ein vollwertiger Musik-Manager.

Gehäuse

Und hier kommt der Teil, der wirklich von mir ist:
Das PhonieMonster!

Ich hab ein paar Stunden investiert und ein süßes Housing gestaltet (ich bin ja schließlich ein Produktdesigner). Ich wollte, dass man auch Figuren mit NFC Tag per Magnet dransnappen kann, wie beim Vorbild. Dann kam mir die Idee, das man das ja auch für lustige Gesichter benutzen könnte, mit denen das PhonieMonster customized werden kann.

Da das ganze im 3D-Druck gemacht wird, kann man auch bei dem Lautstärkeregler kreativ werden und Öhrchen oder Hörner draus machen. Mit den heutigen 3D-Druckern sieht man auch fast keine Layer-Lines mehr, so dass man nicht noch aufwändig spachteln und lackieren muss, wenn man nicht möchte.

Die beiden Horn-Positionen haben auch ein Gewinde, so dass man z.B. eine Aufnahme für ein Bändchen einschrauben kann.

Im inneren gibt es eine Haltestruktur, die alle Komponenten aufnimmt. Es gibt so Platz für 3 Buttons oben, einen unten (An/Aus), den Raspi, den RFID Reader, die Speaker und eine Powerbank. Bei Gelegenheit mache ich noch eine Variante für 5 Buttons und ohne die „Hörner“

Ursprünglich wollte ich die Lautsprecherkappen abnehmbar machen. Da kam mir die Idee, das wären gute Augen auch und man kann die dann sogar drehen. Das ist drin geblieben, aber die Lautsprecher sind dann woanders hingewandert, da ich sonst nicht alles Unterbringen konnte. Mir war wichtig, dass das PhonieMonster nämlich auch auf dem A1 Mini Druckbar ist.

Und so ist aus einem kleinen Wochenend Projekt ein großes geworden. Ich hoffe es machen ein paar Leute mit und entwerfen neue Gesichter. Ich kann mir auch viele Bunte Varianten vorstellen:

Die Files zum selber Drucken gibt es bald bei MakerWorld. (Coming Soon – muss noch etwas verbessern, da die ganzen Kabel zu viel Platz wegnehmen.)

Happy Hacking!

$ Die mit einem $ gekennzeichneten Links, sind Affiliate Links. Wenn du über diese in den Shop gelangst und etwas kaufst, bekomme ich eine kleine Provision

Schreibe einen Kommentar

Geb mir einen aus :)

Wenn du das Zeug hier magst, denk doch über eine Spende nach um Server und Domain zu finanzieren.

$ Die mit einem $ gekennzeichneten Links, sind Affiliate Links. Wenn du über diese in den Shop gelangst und etwas kaufst, bekomme ich eine kleine Provision.

Suche & Filter