Umbau eines alten Küchenradios als WLAN-Radio/Squeezelite Client
Die folgende Anleitung beschreibt den Umbau eines alten Küchenradios in einen vollwertigen Squeezelite Client für das Logitech Media Center. Verwendet werden die Lautsprecher des alten Radios sowie die Bedienelemente. Für einen guten WAF (Woman Acceptance Faktor) kann der Client über 6 Taster und einen Drehimpulsgeber vor Ort ohne App, Handy, etc. bedient werden. Über den Drehimpulsgeber wird die Lautstärke geregelt, die 6 Taster dienen zur Belegung mit Radiosender-Favoriten und zum Ausschalten. Je nach Ausstattung eures Radios kann man selbstverständlich mehr oder weniger Tasten verwenden oder auch nachträglich Taster und/oder Drehimpulsgeber hinzufügen. Die Belegung der Taster erfolgt rein softwareseitig und kann natürlich dem eigenen Bedarf angepasst werden.
Das Ganze ist in einem Nachmittag erledigt. Mehr als rudimentäre Lötkenntnisse sind nicht notwendig.
Diskussion und Fragen zum Artikel bitte im Loxforum: https://www.loxforum.com/forum/projektforen/musicserver4lox/hardware/290286-umbau-eines-alten-k%C3%BCchenradios-in-einen-squeezelite-client-wlan
Hardware
Raspberry Pi ZeroW oder alternativ Raspberry Pi A3+: ca. 20 EUR
Der ZeroW oder der A3+ haben genügend Leistungsreserve für einen Squeezelite Client und verbrauchen am wenigsten Strom. Darauf solltet ihr achten, da der Pi im Dauerbetrieb läuft. Ich habe den ZeroW verwendet, da er von der Leistung her vollkommen ausreicht, sehr günstig ist und aus der Pi-Familie am wenigsten Strom verbraucht. Vergesst nicht auch eine SD-Karte (mindestens 16 GB) und ein Netzteil dazu zu bestellen. Ein Gehäuse braucht ihr nicht.
HiFiBerry MiniAMP: ca. 20 EUR
Der HiFiBerry MiniAMP ist ein kleines Verstärkermodul. Das Shield wird direkt auf die GPIO-Leiste des Raspberrys aufgesteckt. Der Verstärker bietet 2x3W Stereo. Das reicht für ein Küchenradio vollkommen aus (auch mit ordentlicher Lautstärke). Wer mehr Leistung benötigt, kann natürlich auch auf ein größeres Modell ausweichen, z. B. den HiFiBerry AMP2. Dieser bietet bis zu 60W, benötigt aber eine 12V Spannungsversorgung (dafür ist dann kein extra Netzteil für den RaspPi mehr notwendig). Aus meiner Sicht ist diese Leistung für ein Küchenradio aber nicht notwendig.
Stacking Header 40 polig, RM 2,54: ca. 1,50 EUR
Der Stacking-Header kommt zwischen den Raspberry und den HiFiBerry. So können wir einzelne GPIOs sehr einfach "herausführen" und parallel zum HiFiBerry benutzen. Die GPIOs benötigen wir als Eingänge für die Taster und den Drehimpulsgeber.
Ihr müsst später darauf achten, dass ihr keine GPIOs benutzt, die der HiFiBerry selbst benötigt! Ich gehe darauf später noch einmal ein. Weitere Informationen findet ihr hier: https://www.hifiberry.com/docs/hardware/gpio-usage-of-hifiberry-boards/
Steckbrückenkabel, ca. 15 Stück, Buchse: ca. 5 EUR
Einige Steckbrückenkabel, um vom GPIO-Header (bzw. dem Stacking Header) zu den Tastern und dem Drehimpulsgeber verkabeln zu können. Man kann natürlich auch normale Kabel nehmen und anlöten. Ich finde, dass es mit den Steckbrückenkabeln etwas einfacher geht. Achtet darauf Kabel mit Steckbuchse zu nehmen (nicht die mit Pin). Beispiel:
Bedienelemente: 0-10 EUR
In meinem Küchenradio war bereits ein Drehimpulsgeber (3-Pin) verbaut sowie 6 Taster. Diese habe ich einfach weiterverwendet. Dazu habe ich die Elemente von den vorhandenen Platinen ausgelötet und auf zwei kleinen Lochrasterplatinen wieder aufgebaut.
Wer eigene Bedienelemente ins Radio einbauen möchte, besorgt sich einfach entsprechende Taster (keine Schalter!) und einen Drehimpulsgeber (mit 3 PINs). Schaut einfach, was Euch gefällt. Beispiele:
Auf der linken Seite seht ihr die Taster und den Drehimpulsgeber aus meinem Radio. Rechts dann der Neuaufbau auf einer Lochrasterplatine. Die Verschaltung findet ihr weiter unten unter "Aufbau".
Software
Auf dem Raspberry läuft als Betriebssystem der LoxBerry. Als einzigstes Plugin wurde das SqueezeLite Plugin von @Christian Fenzl installiert. Auf dem LoxBerry müsst ihr daran denken, den Treiber für die HiFiBerry Soundmodule noch zu aktivieren: Hifiberry Soundmodule nutzen
Es sollte natürlich auch jede andere SqueezeLite-Distribution für den Raspberry funktionieren (z. B. der piCorePlayer oder Max2Play) oder auch eine manuelle Installation mit Raspbian und Squeezelite möglich sein.
Die Abfrage der Taster und des Drehimpulsgebers erfolgt über ein einfaches Pythonskript, welches permanent im Hintergrund läuft. Die Installation beschreibe ich weiter unten. Das Pythonskript setzt über das Netzwerk dann die entsprechenden Befehle an den Logitech Media Server ab (über dessen JSON-Schnittstelle).
Aufbau
Der Aufbau ist simpel. Die Taster werden gegen GND verkabelt (kann alternativ auch gegen +3.3V verkabelt werden), der Drehimpulsgeber muss gegen +3.3V verkabelt werden (der gemeinsame PIN, meist der Mittlere). Wir benötigen keine Pullup- oder Pulldown-Widerstände, da wir die internen Widerstände des RaspPi nutzen werden. Diese können per Software aktiviert werden.
Ihr müsst unbedingt darauf achten, dass ihr die GPIOs bzw. PINs nicht verwendet, die der HifiBerry selbst belegt. Weitere Informationen findet ihr hier: https://www.hifiberry.com/docs/hardware/gpio-usage-of-hifiberry-boards/ Beim HiFiBerry MiniAMP sind das: GPIO 16, 18-21 und 26 (PINs 36, 12, 35, 38, 40 und 37). Die Belegung des Headers findet ihr in der Raspberry Dokumentation: https://www.raspberrypi.org/documentation/usage/gpio/
Zunächst steckt man den Stacking Header auf den RaspPi und biegt sich die PINs, die man als GPIO verwenden möchte, nach außen. Daran kann man dann die Jumperkabel seitlich anschließen. So kann man die Taster und den Drehimpulsgeber anschließen und gleichzeitig später den HiFiBerry MiniAMP ebenfalls auf den Header stecken.
Ich benutze die folgenden GPIOs:
GPIO 17 (PIN 11): Drehimpulsgeber
GPIO 27 (PIN 13): Drehimpulsgeber
GPIO 23 (PIN 16): Button 1
GPIO 24 (PIN 18): Button 2
GPIO 25 (PIN 22): Button 3
GPIO 8 (PIN 24): Button 4
GPIO 7 (PIN 26): Button 5
GPIO 6 (PIN 31): Button 6
PIN 20: Ground
PIN 17: +3.3V
Installation
Das Pythoskript zur Abfrage der Taster und es Drehimpulsgebers könnt ihr hier herunterladen: https://github.com/mschlenstedt/squeezelite_radiotasten
Das Skript benötigt die RPi.GPIO Bibliothek. Dazu müssen zunächst noch die Development-Librarys der Distribution installiert werden:
apt-get install python3-dev
Anschließend müssenüber pip noch folgende Pakete nachinstalliert werden:
pip install setuptools
pip install wheel
pip install RPi.GPIO
Das eigentliche Skript zur Abfrage der Taster kann irgendwo im Filesystem installiert werden. Auf dem LoxBerry bietet sich das Verzeichnis /opt/loxberry/bin/plugins/legacy/radiotasten
an. Das wird bei einem Update nicht überschrieben.
Alle Einstellungen erfolgen am Anfang des Skriptes. Die Einstellungen sollten selbsterklärend sein.
radiotasten.py
#!/usr/bin/env python2
import RPi.GPIO as GPIO
from encoder import Encoder
import time
import os
#
# Einstellungen
#
# GPIO-Bezeichnungien (BCM) im Skript verwenden
GPIO.setmode(GPIO.BCM)
# LMS Einstellungen
# Player, der mit den Tasten gesteuert werden soll
player = "02:a7:37:bc:bd:1d"
# Host Deines Logitech Media Server (oder IP-Adresse)
server = "192.168.3.213"
# Port am LMS für CLI Kommandos
port = "9000"
# GPIO-Pins Drehimpulsgeber - gemeinsamer PIN auf +3.3V
in_a = 17
in_b = 27
# GPIO-Pins Pushbutton - wahlweise gegen GND oder +3.3V
in_c = 23
in_d = 24
in_e = 25
in_f = 8
in_g = 7
in_h = 6
# LMS Kommandos Drehimpulsgeber
# Links: Vol -5%
cmd_a = "curl -s -H \"Content-Type: application/json\" -X POST -d '{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + player + "\", [\"mixer\",\"volume\",\"+5\"]]}' http://" + server + ":" + port + "/jsonrpc.js"
# Rechts: Vol +5%
cmd_b = "curl -s -H \"Content-Type: application/json\" -X POST -d '{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + player + "\", [\"mixer\",\"volume\",\"-5\"]]}' http://" + server + ":" + port + "/jsonrpc.js"
# LMS Kommandos Pushbuttons
# Play Favorit 1 (Liste startet mit 0)
cmd_c = "curl -s -H \"Content-Type: application/json\" -X POST -d '{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + player + "\", [\"favorites\",\"playlist\",\"play\",\"item_id:0\"]]}' http://" + server + ":" + port + "/jsonrpc.js"
# Play Favorit 2
cmd_d = "curl -s -H \"Content-Type: application/json\" -X POST -d '{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + player + "\", [\"favorites\",\"playlist\",\"play\",\"item_id:1\"]]}' http://" + server + ":" + port + "/jsonrpc.js"
# Play Favorit 3
cmd_e = "curl -s -H \"Content-Type: application/json\" -X POST -d '{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + player + "\", [\"favorites\",\"playlist\",\"play\",\"item_id:2\"]]}' http://" + server + ":" + port + "/jsonrpc.js"
# Play Favorit 4
cmd_f = "curl -s -H \"Content-Type: application/json\" -X POST -d '{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + player + "\", [\"favorites\",\"playlist\",\"play\",\"item_id:3\"]]}' http://" + server + ":" + port + "/jsonrpc.js"
# Play Favorit 5
cmd_g = "curl -s -H \"Content-Type: application/json\" -X POST -d '{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + player + "\", [\"favorites\",\"playlist\",\"play\",\"item_id:4\"]]}' http://" + server + ":" + port + "/jsonrpc.js"
# Stop
cmd_h = "curl -s -H \"Content-Type: application/json\" -X POST -d '{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + player + "\", [\"mode\",\"stop\"]]}' http://" + server + ":" + port + "/jsonrpc.js"
# Pullup-/Pulldown-Widerstand einschalten
# Bei Verschaltung gegen GND: GPIO.PUD_UP
# Bei Verschaltung gegen +3.3V: GPIO.PUD_DOWN
pullupdown_in_c = GPIO.PUD_UP
pullupdown_in_d = GPIO.PUD_UP
pullupdown_in_e = GPIO.PUD_UP
pullupdown_in_f = GPIO.PUD_UP
pullupdown_in_g = GPIO.PUD_UP
pullupdown_in_h = GPIO.PUD_UP
# Bounce-Zeit in ms
bouncetime = 250
#
# Einstellungen Ende
#
# GPIO Settings
GPIO.setup(in_c, GPIO.IN, pull_up_down = pullupdown_in_c)
GPIO.setup(in_d, GPIO.IN, pull_up_down = pullupdown_in_d)
GPIO.setup(in_e, GPIO.IN, pull_up_down = pullupdown_in_e)
GPIO.setup(in_f, GPIO.IN, pull_up_down = pullupdown_in_f)
GPIO.setup(in_g, GPIO.IN, pull_up_down = pullupdown_in_g)
GPIO.setup(in_h, GPIO.IN, pull_up_down = pullupdown_in_h)
if pullupdown_in_c == GPIO.PUD_UP:
edge_in_c = GPIO.FALLING
else:
edge_in_c = GPIO.RISING
if pullupdown_in_d == GPIO.PUD_UP:
edge_in_d = GPIO.FALLING
else:
edge_in_d = GPIO.RISING
if pullupdown_in_e == GPIO.PUD_UP:
edge_in_e = GPIO.FALLING
else:
edge_in_e = GPIO.RISING
if pullupdown_in_f == GPIO.PUD_UP:
edge_in_f = GPIO.FALLING
else:
edge_in_f = GPIO.RISING
if pullupdown_in_g == GPIO.PUD_UP:
edge_in_g = GPIO.FALLING
else:
edge_in_g = GPIO.RISING
if pullupdown_in_h == GPIO.PUD_UP:
edge_in_h = GPIO.FALLING
else:
edge_in_h = GPIO.RISING
# Drehimpulsgeber
oldvalue = 0
def DIG1(value):
global oldvalue
if value > oldvalue:
print str(value) + " -> Richtung ist rechts. Sende: "
os.system(cmd_a)
print ""
if value < oldvalue:
print str(value) + " -> Richtung ist links. Sende: "
os.system(cmd_b)
print ""
oldvalue = value
# Button 1
def Button1(channel):
print "Button 1 aktiviert. Sende:"
os.system(cmd_c)
print ""
# Button 2
def Button2(channel):
print "Button 2 aktiviert. Sende:"
os.system(cmd_d)
print ""
# Button 3
def Button3(channel):
print "Button 3 aktiviert. Sende:"
os.system(cmd_e)
print ""
# Button 4
def Button4(channel):
print "Button 4 aktiviert. Sende:"
os.system(cmd_f)
print ""
# Button 5
def Button5(channel):
print "Button 5 aktiviert. Sende:"
os.system(cmd_g)
print ""
# Button 6
def Button6(channel):
print "Button 6 aktiviert. Sende:"
os.system(cmd_h)
print ""
# Interrupts
e1 = Encoder(in_a, in_b, callback = DIG1)
GPIO.add_event_detect(in_c, edge_in_c, callback = Button1, bouncetime = bouncetime)
GPIO.add_event_detect(in_d, edge_in_d, callback = Button2, bouncetime = bouncetime)
GPIO.add_event_detect(in_e, edge_in_e, callback = Button3, bouncetime = bouncetime)
GPIO.add_event_detect(in_f, edge_in_f, callback = Button4, bouncetime = bouncetime)
GPIO.add_event_detect(in_g, edge_in_g, callback = Button5, bouncetime = bouncetime)
GPIO.add_event_detect(in_h, edge_in_h, callback = Button6, bouncetime = bouncetime)
# Schleife
try:
while True:
time.sleep(1)
except:
GPIO.cleanup()
print "\nBye"
Ein kleines "Watchdog" Skript überwacht, ob das eigentliche Pythonskript noch läuft und startet es bei einem Problem gegebenenfalls neu.
watchdog.sh
#!/bin/bash
pgrep -f radiotasten.py >/dev/null 2>&1
if [[ $? -eq 1 ]]
then
echo "Skript wird neu gestartet"
./radiotasten.py &
else
echo "Skript läuft noch"
fi
Das Watchdog-Skript startet man regelmäßig per Cron (z. B. alle 5 Minuten). Dazu legt man auf dem Loxberry eine neue crontab unter /opt/loxberry/system/cron/cron.d/radiotasten
an. Der Inhalt muss wie folgt aussehen:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=""
#
# m h dom mon dow user command
*/5 * * * * loxberry cd /opt/loxberry/bin/plugins/radiotasten && ./watchdog.sh >/dev/null 2>&1
Hinweis:
Ich habe festgestellt, dass die Bedienung des Radios über die Ein- und Ausgänge nach einiger Zeit (mehrere Tage) nicht mehr funktioniert, obwohl das Radiotasten-Skript noch läuft. Ich vermute ein Problem mit den GPIOs und den Interrupts. Nach einem Neustart des Skripts funktioniert wieder alles einwandfrei. Daher starte ich mittlerweile das Skript einfach jede Nacht einmal neu. Dazu fügt man dem obigen Crontab-File einfach eine weitere Zeile an (hier wird das Skript um 03:01 jedne Tag neu gestartet):
Das war's auch schon. Ab sofort kannst Du Dein WLAN-Radio über die Tasten und natürlich auch direkt als vollwertigen Netzwerk-Player steuern.
Wer die Befehle der einzelnen Taster anpassen möchte, findet unter folgenden Links weitere Informationen:
Funktionsprinzip der JSON-API: https://gist.github.com/samtherussell/335bf9ba75363bd167d2470b8689d9f2
Gesamte LMS CLI Dokumentation: https://github.com/elParaguayo/LMS-CLI-Documentation/blob/master/LMS-CLI.md