Category: Linux

Raspberry Pi Wecker mit Webradio (Teil 5)

Webradio steuern

Wie versprochen geht es heute weiter mit der Steuerung des Webradios. Wir wollen uns heute ansehen, wie man den Raspberry Pi über den GPIO Port ansteuert und dort Taster abfragt und eine LED ansteuert.

Hierzu müssen wir zunächst ein wenig Software installieren. Zu aller erst benötigt man die WiringPi Bibliothek. Eine Anleitung, wie man diese herunterlädt und installiert gibt es hier.

Anschliessend muss man noch den Music Player Daemon installieren und dazu den passenden Client, das geschieht mittels

sudo apt-get install mpc mpd

Jetzt öffnet man die Datei /etc/mpd.conf mit einem beliebigen Editor wie z.B. nano oder vim und sucht sich die Zeile, in der steht "bind to address" hier nehmen wir das # am Anfang weg und starten den Daemon mit

sudo /etc/init.d/mpd restart

neu, damit die Änderungen übernommen werden.

Als nächstes benötigen wir eine Playlist, die eine Liste von URLs zu Radio-Streams enthält, die gibt es zuhauf im Internet. Als Beispiel mag diese hier dienen:

http://www.ndr.de/resources/metadaten/audio/m3u/n-joy.m3u
http://mp3-live.swr3.de/swr3_m.m3u
http://mp3-live.dasding.de/dasding_m.m3u
http://mp3-live.swr.de/swr1bw_m.m3u
http://www.ndr.de/resources/metadaten/audio/m3u/ndr2_sh.m3u
http://www.chromanova.de/chromanova.-.chill.high.pls

Speichert diese einfach als webradio.pls im Ordner /home/pi/webradio ab. Diese benötigen wir später.

Jetzt kommt zunächst die WiringPi Bibliothek ins Spiel. Damit wir das Webradio von aussen steuern können, benötigen wir wenigstens 3 Tasten und eine LED. Die drei Tasten bekommen die Funktionen Start/Stop und Next (Nächstes Element der Playlist), eine Taste halten wir uns frei, damit wir später die Weckerfunktionen steuern können, die wir in einer der nächsten Folgen behandeln.

Verdrahten wir also unseren Raspberry GPIO Port wie folgt:

Raspberry Buttons

Raspberry Buttons

Die Pins 1,3, und 5 vom Stecker SV1 verbinden wir noch mittels dreier 10kOhm WIderstände mit Plus. Ich habe dies über einen Wannenstecker gelöst, damit ich die Buttons auf einer externen Platine montieren kann, die an die Oberseite des Gehäuses befestigt wird. Dazu in einer der späteren Folgen mehr.

Wenden wir uns der Software zu und erstellen uns ein kleines C-Programm, daß die Tasten auswertet und die LED schaltet.

#include <stdio.h>
#include <wiringPi.h>
#include <stdlib.h>
#include <syslog.h>

#define DEBOUNCE_TIME   150

int debounceTime = 0;

int on = 0;

void startStopRadio(void) {

        if (millis () < debounceTime) {
                debounceTime = millis () + DEBOUNCE_TIME ;
                return ;
        }

        if (on) {
                on = 0;
                system("mpc stop");
                syslog(LOG_NOTICE,"On.\n");
        }
        else {
                on = 1;
                system("mpc play");
                syslog(LOG_NOTICE,"Off.\n");
        }

        digitalWrite(1,on);

    while (digitalRead (0) == LOW)
                delay(1) ;

    debounceTime = millis () + DEBOUNCE_TIME ;

}

void nextSong(void) {

        if (millis () < debounceTime) {
                debounceTime = millis () + DEBOUNCE_TIME ;
                return;
        }

        // action

        system("mpc next");
        syslog(LOG_NOTICE,"Next song.\n");

        while (digitalRead (3) == LOW)
                delay(1);

        debounceTime = millis () + DEBOUNCE_TIME;
}

int main (int argc, char** argv)
{
        setlogmask (LOG_UPTO (LOG_NOTICE));

        openlog (NULL, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
        syslog (LOG_NOTICE, "Webradio control started by User %d", getuid ());

        int pin;

    if (wiringPiSetup () == -1) {
        syslog(LOG_NOTICE,"wiringPiSetup failed.\n");
                return 1 ;
        }

        system("gpio edge 0 falling");
        system("gpio edge 3 falling");

        pinMode(0,INPUT);
        pinMode(1,OUTPUT);
        pinMode(3,INPUT);

        if (wiringPiISR (0, INT_EDGE_FALLING, &startStopRadio) < 0) {
                syslog(LOG_NOTICE,"wiringPiISR for pin 0 failed.\n");
                return 1;
        }

        if (wiringPiISR (3, INT_EDGE_FALLING, &nextSong) < 0) {
                syslog(LOG_NOTICE,"wiringPiISR for pin 3 failed.\n");
                return 1;
        }

        for(;;){
                delay(100);
        }

        return 0;

}

Die beiden Funktionen startStopRadio und nextSong werden in Zeile 78 und 83 mit wiringPiISR an die fallende Flanke der jeweiligen Pins gebunden. Das heisst, daß jedesmal wenn der Pegel an den entsprechenden Pins auf LOW wechselt, wird ein Interrupt ausgelöst und die jeweilige Funktion aufgerufen.Wir erinnern uns, daß wir die Pins der Taster ja auf Masse gelegt haben und mit den Tastern auf Plus "ziehen".

Ansonsten läuft das Programm nun in einer Endlosschleife und wartet auf Ereignisse. In der Funktion startStopRadio wird auch noch die LED eingeschaltet, wenn das Webradio läuft.

Das Ganze speichern wir nun als webradio.c (auch wieder im Ordner /home/pi/webradio) und kompilieren mit

gcc -o webradio webradio.c -lwiringPi

Jetzt fehlt uns nur noch ein Skript, was das Programm beim Systemstart als Dienst startet:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          webradio
# Required-Start:    $all
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Webradio
# Description:       Webradio button control
### END INIT INFO

# Author: Matthias Pueski <matthias@pueski.de>
#

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Webradio"
NAME=webradio
DAEMON=/usr/sbin/$NAME
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon -b --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon -b --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
                $DAEMON_ARGS \
                || return 2
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.

        mpc clear
        mpc load /home/pi/webradio/webradio.pls

}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] && return 2
        # Many daemons don't delete their pidfiles when they exit.
        rm -f $PIDFILE
        return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        #
        # If the daemon can reload its configuration without
        # restarting (for example, when it is sent a SIGHUP),
        # then implement that here.
        #
        start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
        return 0
}

case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  status)
        status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
  #reload|force-reload)
        #
        # If do_reload() is not implemented then leave this commented out
        # and leave 'force-reload' as an alias for 'restart'.
        #
        #log_daemon_msg "Reloading $DESC" "$NAME"
        #do_reload
        #log_end_msg $?
        #;;
  restart|force-reload)
        #
        # If the "reload" option is implemented then remove the
        # 'force-reload' alias
        #
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
        exit 3
        ;;
esac

:

Dieses Skript liegt übrigens als Vorlage unter /etc/init.d/skeleton und kann für jeden beliebigen Zweck angepasst werden. Hierzu kopiert man sich einfach das Skript und passt die Variablen an seine Zwecke an. Man kopiert nun das Skript nach /etc/init.d und das Program nach /usr/sbin und führt folgenden Befehl aus:

update-rc.d webradio defaults

Jetzt wird das Skript nach jedem Start automatisch geladen und startet das Webradio Program im Hintergrund. Starten können wir es natürlich jetzt schon mit:

/etc/init.d/webradio start

Nun sollte sich das Radio mittels Taster starten lassen und die LED einschalten. Ob alles klappt, kann man sich mit

tail -f /var/log/syslog

ausgeben lassen. Falls es nicht funktioniert, muss man natürlich auf Fehlersuche gehen.

Das war es für heute. In der nächsten Folge löten wir uns die Uhrenplatine zusammen, die ich bereits habe fertigen lassen und machen uns Gedanken über die Alarmfunktionen.

Bis dahin wünsche ich allen Lesern fröhliches Basteln. 😉


Raspberry Pi Wecker mit Webradio (Teil 4)

In der letzten Folge hatten wir uns ja die Innereien des Uhrenmoduls angesehen. Heute wollen wir einen kurzen Blick darauf werfen, wie die Uhrzeit vom Raspberry auf das Uhrenmodul kommt und wie die Auswertung der Zeit erfolgt, die ja über die serielle Schnittstelle übertragen wird.

Zunächst müssen wir dem ATMega beibringen, Daten von der seriellen Schnittstelle zu lesen. Ich verwende hierzu die UART-Bibliothek von Peter Fleury.  Um diese zu verwenden, müssen wir die Bibliothek mittels des Befehls:

uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) );

initialisieren. Die UART_BAUD_RATE beträgt in meinem Falle 9600 Baud, es geht natürlich auch schneller. Die Geschwindigkeit ist aber für die Übertragung der Zeit völlig ausreichend. Nun müssen wir in der Hauptschleife des Programm zyklisch prüfen, ob Daten an der seriellen Schnittstelle anliegen, dafür basteln wir uns eine Funktion:

void getUart() {
    /*
     * Get received character from ringbuffer
     * uart_getc() returns in the lower byte the received character and
     * in the higher byte (bitmask) the last receive error
     * UART_NO_DATA is returned when no data is available.
     *
     */
    char c = uart_getc();

    if ( c & UART_NO_DATA )
    {
        /*
         * no data available from UART
         */
    }
    else
    {
        /*
         * new data available from UART
         * check for Frame or Overrun error
         */
        if ( c & UART_FRAME_ERROR )
        {
            /* Framing Error detected, i.e no stop bit detected */
            uart_puts_P("UART Frame Error: ");
        }
        if ( c & UART_OVERRUN_ERROR )
        {
            /*
             * Overrun, a character already present in the UART UDR register was
             * not read by the interrupt handler before the next character arrived,
             * one or more received characters have been dropped
             */
            uart_puts_P("UART Overrun Error: ");
        }
        if ( c & UART_BUFFER_OVERFLOW )
        {
            /*
             * We are not reading the receive buffer fast enough,
             * one or more received character have been dropped
             */
            uart_puts_P("Buffer overflow error: ");
        }

        if (charNum < 16) {

            if ((unsigned char)c == 'r') {

            	parseCommand(buffer,charNum);

            	uart_puts("rn");
            	uart_puts("Ok.rn");

            	charNum = 0;
            }
            else {
            	if (isalnum(c) || isblank(c)) {
					uart_putc(c);
					buffer[charNum] = (unsigned char)c;
					charNum++;
            	}
            }

        }

    }

}

Da wir jeweils immer nur ein Zeichen einlesen können, müssen wir eine maximale Länge (hier 16) definieren und uns merken, an welcher Position wir uns befinden. Am Ende des Kommandos erfolgt ein CR LF, damit der Controller weiss, daß wir einen kompletten Befehl empfangen haben. Jetzt fehlt nur noch das Auswerten des empfangenen Kommandos und das geht so:

void parseCommand(char* cmd, uint8_t len) {

	if (strncmp(cmd,"time",4) == 0) {

		char* t = &buffer[5];

		uint16_t time   = atoi(t);

		int hour = time / 100;
		int minute = time - ((time / 100) * 100);

		tmp_date.hour = hour;
		tmp_date.minute = minute;
		tmp_date.month = 1;
		tmp_date.year = 0;
		tmp_date.day = 1;

		if (isValidTime(tmp_date)) {
			current_date.hour = hour;
			current_date.minute = minute;
		}

	}
	else if(strncmp(cmd,"gettime",7) == 0) {
		sprintf(buffer,"rn%02d:%02d",current_date.hour,current_date.minute);
		uart_puts(buffer);
	}

}

Nun können wir über die serielle Schnittstelle die Zeit empfangen und die aktuelle Zeit ausgeben. Wie aber senden wir nun die Daten vom Raspberry?

Hierzu benötigen wir zunächst ein einfaches Python Script, was die Zeit als Kommandozeilenargument übergeben bekommt und diese dann an die serielle Schnittstelle sendet:

import serial
import sys
ser = serial.Serial("/dev/ttyAMA0",9600);
ser.write("time "+sys.argv[1]+"rn")
ser.close()

Und das Shellscript, welches wir später periodisch über einen Cronjob anstossen, um die Zeit zu synchronisieren:

#!/bin/bash ntpdate -s 0.de.pool.ntp.org date +"%H%M" | xargs python /home/pi/webradio/time.py

Damit haben wir nun alles zusammen und können einen Cronjob anlegen, der einmal in der Stunde die Zeit aktualisiert, hierzu verwenden wir den Befehl crontab -e und fügen folgende Zeile hinzu:

0 */1 * * * /home/pi/webradio/time.sh

Das Script muss sich natürlich am entsprechenden Ort befinden.

Das war's dann für heute. In der nächsten Folge schauen wir uns an, wie das eigentliche Webradio auf dem Raspberry funktioniert und wie wir selbiges mit externen Tastern steuern können.


Ruhig Blut

dummy

Soweit so gut, ich habe mich wieder ein wenig beruhigt nach der Linux-Orgie.

Ich habe noch ein wenig recherchiert und herausgefunden, daß mein Soundproblem offenbar ein bekannter Bug ist

https://bugzilla.kernel.org/show_bug.cgi?id=87771#c23

Das Problem betrifft wohl alle Mainboards mit dem Realtek ALC1150 Chipsatz, also in meinem Falle das MSI Z97 GAming 3 Board. Ich kann das komplett so nachvollziehen, die Mühe, das Kernel upzudaten spare ich mir mal, da ich meistens sowieso direkt Windows oder Linux starte und im laufenden Betrieb nicht neustarte.


Nichts dazugelernt?

Junge, junge! Ich war echt der Meinung, daß sich möglicherweise in den letzten 10 Jahren im Linux-Desktop Bereich etwas getan hat. Da habe ich leider falsch gelegen.

Nachdem ich im letzten Post berichtete, daß die Installation von Linux Mint auf meinem Notebook wie Butter erstaunlich gut von der Hand ging, dachte ich, daß es an der Zeit wäre, das ganze auch mal auf meinem Desktop zu installieren. Soweit so gut, ich besitze einen Standard PC von der Stange mit einer NVIDIA GTX970 und keinerlei exotischen Komponenten. Sollte also funktionieren oder? Also nicht lange gefackelt, die Windows Partition verkleinert und den bootfähigen USB-Stick vom letzten Mal eingesteckt.

Nachdem ich die ersten Probleme mit dem UEFI Boot umschifft habe, mit denen ich allerdings gerechnet hatte, lies sich Linux Mint dann parallel installieren und hat auch brav den Bootmanager installiert. Aber was soll ich euch sagen? Die NVIDIA Karte wurde nicht erkannt! Eine NVIDIA Karte! Hallo, geht's noch??? Da muss ich doch tatsächlich die Treiber manuell von der NVIDIA Seite herunterladen und ein Kernelmodul kompilieren. Ich kam mir vor wie in der Steinzeit, der gleiche Müll wie vor 10 Jahren! Abgesehen davon, daß sich der NVIDIA Installer darüber beschwerte, daß das Kernelmodul nicht geladen werden konnte, lief die ganze Geschichte dann nach einem Neustart, warum auch immer.

Als ich dann die Grafikkarte dann am Laufen hatte, gingen die Probleme natürlich weiter. Irgendwie konnte ich zwar Audio-Dateien abspielen, aber ich habe nichts gehört. Ich habe dann solange an den diversen Lautstärkereglern rumgespielt, bis ich irgendwann mal was gehört habe, leider alles viel zu leise. Eine umfangreiche Recherche in diversen Foren ergab dann, daß ich angeblich mittels des Alsamixers auf der Konsole (!!!!) den Sound lauterstellen sollte. Zahllose Versuche später, nachdem ich mich durch diverse Mixer und EInstellungtools gewühlt hatte, warf ich dann die Flinte ins Korn. Liebe Linux Entwickler, es kann echt nicht sein, daß es mindestens vier verschiedene Sound Systeme gibt, die irgendwie miteinander zusammenhängen: PulseAudio, Alsa, Jack, Mixer hier, Konsole da, Alsactl, blah blah blah. Was soll das? Wer soll das bedienen? Habt Ihr 'ne Meise?

Fuck you

Aber am Besten fand' ich die Tatsache, daß sich das ganze irgendwie von selbst repariert hat. *kopfkratz*

Ich höre immer das Geheule, daß sich Linux ja nicht auf dem Desktop durchsetzt, aber ich weiss jetzt wieder genau warum. Ich weiss, es kostet nichts und ich weiss auch, daß die meisten Entwickler in ihrer Freizeit an Linux arbeiten, aber irgendwas läuft hier gewaltig schief.


Linux Mint vom USB-Stick

WIe jedes Jahr um diese Zeit, wenn die Tage länger werden und ich gerade nichts besseres zu tun habe, beschäftige ich mich hin und wieder mit Linux. Ich finde es, obwohl ich zu 99% WIndows nutze, sehr wichtig, ab und zu einen Blick über den Tellerrand zu werfen. Vor einiger Zeit habe ich von Linux Mint gehört, einer Ubuntu basierten Distribution, die wohl besonders leicht zu installieren und zu bedienen ist.

Linux Mint

Linux Mint

Also nicht lang gefackelt und eine aktuelle Version von Linux Mint heruntergeladen und mit UNetbootin einen bootfähigen USB-Stick erstellt. Das geht ganz leicht. Man lädt sich ein Installationsimage herunter und erzeugt mit wenigen Mausklicks eine bootfähige Version der Linux Distribution. Dazu muss allerdings der Stick vorher unter Windows mit FAT32 formatiert werden, dann steht der Installation fast nichts mehr im Wege. Da ich noch ein Lenovo Thinkpad T420 besitze, was ich nur zum Surfen auf der Couch benutze, fiel auch die Wahl des Zielcomputers nicht schwer.

UNetbootin

UNetbootin

Nach zwei erfolglosen Installationen, die auf zwei (!) defekte USB-Sticks zurückzuführen waren, ist es mir dann tatsächlich mit einem Kingston 8GB Stick gelungen, Linux Mint auf dem T420 zu installieren. Und was soll man sagen, die Installation hat auf Anhieb geklappt und soweit ich sehen kann, funktioniert alles Out-Of-The-Box, inklusive Trackpad, Webcam, WLAN und Sound - Ganz großes Kino! Das einzige, was ich noch anpassen musste, war die Konfiguration des Trackpads, das Scrollen mit zwei Fingern war nicht standardmäßig aktiviert.

Ich werde in den nächsten Tagen meine Erfahrungen hier berichten. Mich interessiert besonders, wie gut sich Bilder unter Linux Mint verwalten lassen und ob man RAW Dateien von der Nikon irgendwie konvertiert bekommt. Vielleicht lässt sich ja auch irgendwie die Creative Suite von Adobe installieren, wer weiss?


(c) 2016 Matthias Pueski