Motivation
Alles fing damit an, dass unser Kombi-Verstäker im Wohnzimmer langsam verstarb. Erst versagte der DVD-Laser aufgrund der Temperatur im Gerät
und dann fing eine Endstufe das Spinnen an. Es war also an der Zeit einen neuen Verstäker zu kaufen. Nach längerer Recherche überzeugte
der NAD 7050D. Das Problem lag nur darin, dass der Verstäker voll digital arbeitet und keinen Analogeingang für ein UKW-Radio bietet.
Gut, was nun. Neben dem Fernsehton ist Radio hören die wesentliche Nutzung. Da war guter Rat teuer.
Zu dem Zeitpunkt dachte ich das erste Mal daran einen Raspberry Pi einzusetzen und den NAD als USB Sound Device zu nutzen. Mein VDR-Server mit vier USB-SAT-Receivern bietet live Streaming auch von SAT-Radio und der MPD kann das abspielen. Internet Radio schied bei 1,2MBit Downstream aus. Die Lösung mit einem Drehknopf Überzeugte meine Familie nicht wirklich. Es musste eine andere Lösung her.
Das die neue Lösung eine universelle Touch Oberfläche für Kommandozeilen wurde, ist ein angenehmer Nebeneffekt für weitere Ideen.
Gut, was nun. Neben dem Fernsehton ist Radio hören die wesentliche Nutzung. Da war guter Rat teuer.
Zu dem Zeitpunkt dachte ich das erste Mal daran einen Raspberry Pi einzusetzen und den NAD als USB Sound Device zu nutzen. Mein VDR-Server mit vier USB-SAT-Receivern bietet live Streaming auch von SAT-Radio und der MPD kann das abspielen. Internet Radio schied bei 1,2MBit Downstream aus. Die Lösung mit einem Drehknopf Überzeugte meine Familie nicht wirklich. Es musste eine andere Lösung her.
Das die neue Lösung eine universelle Touch Oberfläche für Kommandozeilen wurde, ist ein angenehmer Nebeneffekt für weitere Ideen.
Hardware
Tontec® 3.5 Zoll Raspberry Pi Touchscreen Display Monitor 480x320 LCD Bildschirm mit transparentem Gehäuse
Damit komme ich mit drei Kabeln ohne extra Netzteil aus und der Raspberry Pi nur läuft nur wenn der Verstärker auch an ist.
- 1x USB-A-Stecker auf Micro-B-Stecker Kabel als Stromversorgung durch den NAD Verstärker
- 1x USB-A-Stecker auf USB-B-Stecker um den NAD Verstärker als Sound Device zu verwenden
- 1x LAN Kabel für die Verbindung zum VDR-Server
Applikation
Erst einmal habe ich ganz normal ein aktuelles Raspbian Image auf die Micro-SD-Karte gespielt und aktualisiert:
Anschließend wird die SPI Kommunikation zum Display aktiviert. Entgegen der Anleitung des Herstellers ist beim Raspbian Jessie vom Mai 2016 der richtige Treiber bereits im Image enthalten.
Als nächstes ist die Anzeige auf das Touch Display umzustellen:
Zum Abschluss muss der Touch Sensor noch kalibriert werden. Bei mir haben diese Parameter funktioniert. Die Anleitung zum Kalibrieren ist Standard und hängt nicht vom Display ab.
Jetzt ist noch neu zu booten und alles sollte laufen.
Zu diesem Zeitpunkt ist es gut eine Maus angeschlossen zu haben. Dann lässt sich die Startzeile aufräumen (Mathematica, Wolfram und Bluetooth entfernen), der SSH-Server aktivieren und der Samba-Server abschalten.
Als nächstes sind der Mediaplayer Daemon und Client zu installieren
Dann wird eine Playlist erstellt. In meinem Fall enthält sie Links zum VDR Streaming Server. Ich bitte den Servernamen <server> durch den Namen des eigenen Servers zu ersetzen.
Nun fehlt noch die Umstellung auf das USB-Audio Gerät. Dazu sind die vorhandenen audio_output Bereiche vollständig als Kommentar zu setzen und das USB-Gerät als Ausgabe am Ende einzutragen.
Mit den folgenden Kommandos wird der Daemon zum Einlesen der geänderten Konfiguration neu gestartet und in meinem Fall Bayer3 mit voller Lautstärke ausgegeben.
Jetzt sollte das laufende Programm des Senders zu hören sein.
Nun wird es spannend. David Hunts Projekt Lapse-Pi Touch hat mich inspiriert.
Er verwendet zur Bedienung seiner Anwendung mit einem Touch Display die Python-Bibliothek PyGame.
Das mache ich mit dem folgenden Python Script auch. Ich habe sein Script genommen und geringfügig angepasst. Erst einmal werden Bibliotheken importiert, die so gebraucht werden. Dann definiere ich ein paar Parameter für den Bildschirm.
Das Entscheidende ist seine Klasse Button. Sie macht alles, was für die Bedienung wichtig ist. Die Methode selected wertet die Touch Bedienung aus und startet die Kommandos über shlep. Mit der Funktion SetCaption vereinfache ich mir auch das Leben. Der entscheidende Punkt ist die Initialisierung des buttons Arrays. Hier werden für jeden Knopf Position und Größe, die anzuzeigende Grafik sowie die ausführende Callback Funktion definiert. Die Grafik für die Buttons ist jeweils ein transparentes PNG mit 80x80 Pixel.
Mit shlep wird der MPC ordentlich gestartet.
Das Backlight wird ohne Warnungen eingeschaltet. Dann wird die Spielebiliothek initialisiert, die Button Liste durchgelesen und die Maus unsichtbar gemacht.
Die While-Schleife läuft endlos, bearbeitet die gedrückten Buttons und schaltet das Backlight nach 30s ab. Das war's.
Um beim Einschalten des Backlights sicher nichts zu bewirken, sollte die Applikationsfläche mit den Buttons getroffen werden. Trifft an die Caption (Titelzeile) so geht das Backlight nicht an und das Fenster wird verschoben.
sudo apt-get update
sudo apt-get upgrade
sudo reboot
Anschließend wird die SPI Kommunikation zum Display aktiviert. Entgegen der Anleitung des Herstellers ist beim Raspbian Jessie vom Mai 2016 der richtige Treiber bereits im Image enthalten.
sudo nano /boot/config.txtdtparam=spi=on
dtoverlay=mz61581
Als nächstes ist die Anzeige auf das Touch Display umzustellen:
sudo nano /usr/share/X11/xorg.conf.d/99-fbturbo.confOption "fbdev" "/dev/fb1"
Zum Abschluss muss der Touch Sensor noch kalibriert werden. Bei mir haben diese Parameter funktioniert. Die Anleitung zum Kalibrieren ist Standard und hängt nicht vom Display ab.
sudo nano /usr/share/X11/xorg.conf.d/99-calibration.confSection "InputClass"
Identifier "calibration"
MatchProduct "ADS7846 Touchscreen"
Option "Calibration" "192 3914 251 3784"
Option "SwapAxes" "0"
EndSection
Jetzt ist noch neu zu booten und alles sollte laufen.
sudo reboot
Zu diesem Zeitpunkt ist es gut eine Maus angeschlossen zu haben. Dann lässt sich die Startzeile aufräumen (Mathematica, Wolfram und Bluetooth entfernen), der SSH-Server aktivieren und der Samba-Server abschalten.
Als nächstes sind der Mediaplayer Daemon und Client zu installieren
sudo apt-get install mpd mpc
Dann wird eine Playlist erstellt. In meinem Fall enthält sie Links zum VDR Streaming Server. Ich bitte den Servernamen <server> durch den Namen des eigenen Servers zu ersetzen.
sudo nano /var/lib/mpd/playlists/sender.m3u#EXTM3U
#EXTINF:1,Antenne Bayern
http://<server>:3000/ES/S19.2E-133-7-170
#EXTINF:2,NDR 2
http://<server>:3000/ES/S19.2E-1-1093-28437
#EXTINF:3,BAYERN 3
http://<server>:3000/ES/S19.2E-1-1093-28402
#EXTINF:4,Fritz
http://<server>:3000/ES/S19.2E-1-1093-28457
#EXTINF:5,PULS
http://<server>:3000/ES/S19.2E-1-1093-28406
#EXTINF:6,hr3
http://<server>:3000/ES/S19.2E-1-1093-28421
#EXTINF:7,SWR3
http://<server>:3000/ES/S19.2E-1-1093-28468
#EXTINF:8,MDR JUMP
http://<server>:3000/ES/S19.2E-1-1093-28432
#EXTINF:9,MDR SPUTNIK
http://<server>:3000/ES/S19.2E-1-1093-28433
#EXTINF:10,N-JOY
http://<server>:3000/ES/S19.2E-1-1093-28440
#EXTINF:11,1LIVE
http://<server>:3000/ES/S19.2E-1-1093-28475
#EXTINF:12,1LIVE diGGi
http://<server>:3000/ES/S19.2E-1-1093-28481
#EXTINF:13,DASDING
http://<server>:3000/ES/S19.2E-1-1093-28471
#EXTINF:14,YOU FM
http://<server>:3000/ES/S19.2E-1-1093-28423
Nun fehlt noch die Umstellung auf das USB-Audio Gerät. Dazu sind die vorhandenen audio_output Bereiche vollständig als Kommentar zu setzen und das USB-Gerät als Ausgabe am Ende einzutragen.
sudo nano /etc/mpd.confaudio_output {
type "alsa"
name "NAD USB Audio"
device "hw:1,0" # optional
}
Mit den folgenden Kommandos wird der Daemon zum Einlesen der geänderten Konfiguration neu gestartet und in meinem Fall Bayer3 mit voller Lautstärke ausgegeben.
sudo service mpd restart
mpc load sender
mpc playlist
mpc play 3
Jetzt sollte das laufende Programm des Senders zu hören sein.
Nun wird es spannend. David Hunts Projekt Lapse-Pi Touch hat mich inspiriert.
Er verwendet zur Bedienung seiner Anwendung mit einem Touch Display die Python-Bibliothek PyGame.
Das mache ich mit dem folgenden Python Script auch. Ich habe sein Script genommen und geringfügig angepasst. Erst einmal werden Bibliotheken importiert, die so gebraucht werden. Dann definiere ich ein paar Parameter für den Bildschirm.
Das Entscheidende ist seine Klasse Button. Sie macht alles, was für die Bedienung wichtig ist. Die Methode selected wertet die Touch Bedienung aus und startet die Kommandos über shlep. Mit der Funktion SetCaption vereinfache ich mir auch das Leben. Der entscheidende Punkt ist die Initialisierung des buttons Arrays. Hier werden für jeden Knopf Position und Größe, die anzuzeigende Grafik sowie die ausführende Callback Funktion definiert. Die Grafik für die Buttons ist jeweils ein transparentes PNG mit 80x80 Pixel.
Mit shlep wird der MPC ordentlich gestartet.
Das Backlight wird ohne Warnungen eingeschaltet. Dann wird die Spielebiliothek initialisiert, die Button Liste durchgelesen und die Maus unsichtbar gemacht.
Die While-Schleife läuft endlos, bearbeitet die gedrückten Buttons und schaltet das Backlight nach 30s ab. Das war's.
Um beim Einschalten des Backlights sicher nichts zu bewirken, sollte die Applikationsfläche mit den Buttons getroffen werden. Trifft an die Caption (Titelzeile) so geht das Backlight nicht an und das Fenster wird verschoben.
Update 18.12.2021 "Rasbian OS Buster"
- shlepResult wird neu ermittelt
- die Initalisierung des MPD mit shlep hat sich geändert
nano /home/pi/pygameradio/pygameradio.py#!/usr/bin/python
# -*- coding: utf-8 -*-
# UI to use Icons to control MPC
import pygame, sys, shlex, time
import RPi.GPIO as GPIO
from subprocess import Popen, PIPE
from pygame.locals import *
WINDOWWIDTH = 480 # size of window's width in pixels
WINDOWHEIGHT = 320 # size of windows' height in pixels
WINDOWBORDER = 12 # border if each button
BOTTONSIZEX = WINDOWWIDTH/6
BOTTONSIZEY = WINDOWHEIGHT/4
BOTTONSTEPX = BOTTONSIZEX+WINDOWBORDER
BOTTONSTEPY = BOTTONSIZEY+WINDOWBORDER
# R G B
WHITE = (255, 255, 255)
BGCOLOR = WHITE
BACKLIGHTTIMEOUT = 30
BacklightTimer = BACKLIGHTTIMEOUT
# Button is a simple tappable screen region. Each has:
# - bounding rect ((X,Y,W,H) in pixels)
# - background Icon, always centered
# - single callback function
# - string value passed to callback
# Occasionally Buttons are used as a convenience for positioning Icons
# but the taps are ignored. Stacking order is important; when Buttons
# overlap, lowest/first Button in list takes precedence when processing
# input, and highest/last Button is drawn atop prior Button(s). This is
# used, for example, to center an Icon by creating a passive Button the
# width of the full screen, but with other buttons left or right that
# may take input precedence (e.g. the Effect labels & buttons).
# After Icons are loaded at runtime, a pass is made through the global
# buttons[] list to assign the Icon objects (from names) to each Button.
class Button:
def __init__(self, rect, picture, callback, command):
self.rect = rect # Bounds
self.bg = pygame.image.load(picture) # Background of the button
self.callback = callback # Callback function
self.cmd = command # Value passed to callback
def selected(self, pos):
x1 = self.rect[0]
y1 = self.rect[1]
x2 = x1 + self.rect[2] - 1
y2 = y1 + self.rect[3] - 1
if ((pos[0] >= x1) and (pos[0] <= x2) and
(pos[1] >= y1) and (pos[1] <= y2)):
pygame.display.set_caption("")
if self.callback:
if self.cmd is None: self.callback()
else: self.callback(self.cmd)
return True
return False
def draw(self, screen):
if self.color:
screen.fill(self.color, self.rect)
if self.iconBg:
screen.blit(self.iconBg.bitmap,
(self.rect[0]+(self.rect[2]-self.iconBg.bitmap.get_width())/2,
self.rect[1]+(self.rect[3]-self.iconBg.bitmap.get_height())/2))
if self.iconFg:
screen.blit(self.iconFg.bitmap,
(self.rect[0]+(self.rect[2]-self.iconFg.bitmap.get_width())/2,
self.rect[1]+(self.rect[3]-self.iconFg.bitmap.get_height())/2))
def setBg(self, name):
if name is None:
self.iconBg = None
else:
for i in icons:
if name == i.name:
self.iconBg = i
break
def shlep(cmd):
'''shlex split and popen
'''
parsed_cmd = shlex.split("/bin/bash -c '" + cmd + "'")
proc = Popen(parsed_cmd, stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()
return (proc.returncode, out, err)
def SetCaption():
shlepResult = shlep('mpc | head -n 1 | cut -d\: -f1')
sender = shlepResult[1].rstrip('\n')
if sender[:6] == "volume":
pygame.display.set_caption("Aus")
else:
pygame.display.set_caption("Aktuell läuft: " + sender)
buttons = [
Button((WINDOWBORDER, WINDOWBORDER, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/antenne.png', shlep, 'mpc play 1'),
Button((WINDOWBORDER+BOTTONSTEPX, WINDOWBORDER, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/ndr2.png', shlep, 'mpc play 2'),
Button((WINDOWBORDER+(2*BOTTONSTEPX), WINDOWBORDER, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/bayern3.png', shlep, 'mpc play 3'),
Button((WINDOWBORDER+(3*BOTTONSTEPX), WINDOWBORDER, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/fritz.png', shlep, 'mpc play 4'),
Button((WINDOWBORDER+(4*BOTTONSTEPX), WINDOWBORDER, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/puls.png', shlep, 'mpc play 5'),
Button((WINDOWBORDER, WINDOWBORDER+BOTTONSTEPY, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/hr3.png', shlep, 'mpc play 6'),
Button((WINDOWBORDER+BOTTONSTEPX, WINDOWBORDER+BOTTONSTEPY, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/swr3.png', shlep, 'mpc play 7'),
Button((WINDOWBORDER+(2*BOTTONSTEPX), WINDOWBORDER+BOTTONSTEPY, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/jump.png', shlep, 'mpc play 8'),
Button((WINDOWBORDER+(3*BOTTONSTEPX), WINDOWBORDER+BOTTONSTEPY, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/sputnik.png', shlep, 'mpc play 9'),
Button((WINDOWBORDER+(4*BOTTONSTEPX), WINDOWBORDER+BOTTONSTEPY, BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/n-joy.png', shlep, 'mpc play 10'),
Button((WINDOWBORDER, WINDOWBORDER+(2*BOTTONSTEPY), BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/1live.png', shlep, 'mpc play 11'),
Button((WINDOWBORDER+BOTTONSTEPX, WINDOWBORDER+(2*BOTTONSTEPY), BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/1live-diggi.png', shlep, 'mpc play 12'),
Button((WINDOWBORDER+(2*BOTTONSTEPX), WINDOWBORDER+(2*BOTTONSTEPY), BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/dasding.png', shlep, 'mpc play 13'),
Button((WINDOWBORDER+(3*BOTTONSTEPX), WINDOWBORDER+(2*BOTTONSTEPY), BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/youfm.png', shlep, 'mpc play 14'),
Button((WINDOWBORDER+(4*BOTTONSTEPX), WINDOWBORDER+(2*BOTTONSTEPY), BOTTONSIZEX, BOTTONSIZEY), '/home/pi/pygameradio/schalter.png', shlep, 'mpc stop')
]
# Initalize the MPD
shlep('sleep 2; mpc stop; mpc clear; mpc load sender; sleep 2; mpc play 3')
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(18,GPIO.OUT)
pygame.init()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
DISPLAYSURF.fill(BGCOLOR)
for item in buttons:
DISPLAYSURF.blit(item.bg,(item.rect[0],item.rect[1]))
pygame.mouse.set_visible(False)
pygame.display.update()
SetCaption()
while True: # main game loop
for event in pygame.event.get():
if(event.type is MOUSEBUTTONDOWN):
if BacklightTimer < 0:
BacklightTimer = BACKLIGHTTIMEOUT
# Backlight on
GPIO.output(18,0)
else:
pos = pygame.mouse.get_pos()
for b in buttons:
if b.selected(pos):
BacklightTimer = BACKLIGHTTIMEOUT
SetCaption()
break
if event.type == QUIT:
pygame.quit()
sys.exit()
if BacklightTimer > 0:
BacklightTimer = BacklightTimer -1
elif BacklightTimer == 0:
BacklightTimer = -1
# Backlight off
GPIO.output(18,1)
time.sleep(1)
Nun muss die Radio Oberfläche noch automatisch gestartet werden. Dazu wird die Autostart Datei um eine Zeile ergänzt.
Update 18.12.2021 "Rasbian OS Buster"
- der Autostart erfolgt jetzt anders
sudo nano /etc/xdg/autostart/piradio.desktop[Desktop Entry]
Type=Application
Name=PiRadio
Comment=Touch Radio
NoDisplay=true
Exec=/usr/bin/python /home/pi/pygameradio/pygameradio.py
NotShowIn=GNOME;KDE;XFCE;
Mit einem Reboot sollte nun alles ordnungsgemäß arbeiten.
sudo reboot
Wenn jemand Anregungen für mein SAT-Radio mit Touch Oberfläche hat, bin ich über eMail zuerreichen.
Ich freue mich auch über eine eMail die mir nur von einem erfolgreichen Nachbau berichtet.
Nun wünsche viel Erfolg!