Skip to content

Instantly share code, notes, and snippets.

@Lukelectro
Last active July 27, 2025 09:45
Show Gist options
  • Save Lukelectro/572967275a47b363e7bb8e6f457347d6 to your computer and use it in GitHub Desktop.
Save Lukelectro/572967275a47b363e7bb8e6f457347d6 to your computer and use it in GitHub Desktop.
radio stream receiver for raspi, bodged together from url mentioned in comments and a few other sources: A website with streaming urls (https://hendrikjansen.nl/henk/streaming.html) and a few adafruit / sparkfun tutorials on audio-to-usb-soundcard and readonly OS. (strangely, after routing audio to card 1 which is the usb card and setting the FS…
#!/usr/bin/env python3
# https://raspberrytips.nl/internet-radio-luisteren-raspberry-pi/
# modified to use vlc and a simple hd44780lcd + 3 buttons
# Re-modified 10-2024 to use --loop and --https-reconnect hoping to fix a 'sometimes stops playing' issue
# Re-modifief 07-2025 to use ffplayer instead of vlc. vlc --loop loops the playlist, should use --repeat to use the item, but --http-reconnect seems not to work an it is a known wont fix issue anyway: https://bugs.launchpad.net/ubuntu/+source/vlc/+bug/790216 so I'll simply use ffplay which is included on raspbian. And put it in a loop to restart when crashed:.
# while true; do ffplay -i https://icecast.omroep.nl/radio1-bb-aac -nodisp -vn -autoexit; sleep 5; done
#should also split all hd44780 to another file, but hey...
import time
import os
import sys
import signal
import RPi.GPIO as GPIO
from gpiozero import Button
PY3 = sys.version_info[0] >= 3
if not PY3:
print("Radio only works with python3")
sys.exit(1)
from threading import Barrier
import subprocess
from subprocess import call
UPDATE_INTERVAL = 1
#https://hendrikjansen.nl/henk/streaming.html#po
STATIONS = [
{'name': "NPO Radio 1",
'source': 'https://icecast.omroep.nl/radio1-bb-aac',
'info': "NPO Radio 1"},
{'name': "NPO Radio 5",
'source': 'http://icecast.omroep.nl/radio5-bb-mp3',
'info': "NPO Radio 5"},
{'name': "NPO Radio 4",
'source': 'http://icecast.omroep.nl/radio4-bb-mp3',
'info': "NPO Radio 4"},
{'name': "NPO Radio 2",
'source': 'http://icecast.omroep.nl/radio2-bb-mp3',
'info': "NPO Radio 2"},
{'name': "Arrow cl. Rock",
'source': 'https://stream.player.arrow.nl/arrow',
'info': "Arrow cl. Rock"},
{'name': "Classic",
'source': 'https://classic.nl/streams/?s=main',
'info': "Classic"},
{'name': "Omroep West",
'source': 'http://icecast.stream.bbvms.com/omroepwest_radio',
'info': "Omroep West"},
]
# Zuordnung der GPIO Pins (ggf. anpassen)
#switches on 26,19,13,6
nextstation = Button(26)
prevstation = Button(19)
playpause = Button (13, hold_time=3)
#onoff = button(6)
LCD_RS = 14
LCD_E = 15
LCD_RW = 18
LCD_DATA4 = 23
LCD_DATA5 = 24
LCD_DATA6 = 25
LCD_DATA7 = 8
LCD_WIDTH = 20 # Zeichen je Zeile
LCD_LINE_1 = 0x80 # Adresse der ersten Display Zeile
LCD_LINE_2 = 0xC0 # Adresse der zweiten Display Zeile
LCD_LINE_3 = 0x94 # Adresse der 3te Display Zeile
LCD_LINE_4 = 0xD4 # Adresse der 4te Display Zeile
LCD_CHR = GPIO.HIGH
LCD_CMD = GPIO.LOW
E_PULSE = 0.0005
E_DELAY = 0.0005
def lcd_send_byte(bits, mode):
# Pins auf LOW setzen
GPIO.output(LCD_RS, mode)
GPIO.output(LCD_DATA4, GPIO.LOW)
GPIO.output(LCD_DATA5, GPIO.LOW)
GPIO.output(LCD_DATA6, GPIO.LOW)
GPIO.output(LCD_DATA7, GPIO.LOW)
if bits & 0x10 == 0x10:
GPIO.output(LCD_DATA4, GPIO.HIGH)
if bits & 0x20 == 0x20:
GPIO.output(LCD_DATA5, GPIO.HIGH)
if bits & 0x40 == 0x40:
GPIO.output(LCD_DATA6, GPIO.HIGH)
if bits & 0x80 == 0x80:
GPIO.output(LCD_DATA7, GPIO.HIGH)
time.sleep(E_DELAY)
GPIO.output(LCD_E, GPIO.HIGH)
time.sleep(E_PULSE)
GPIO.output(LCD_E, GPIO.LOW)
time.sleep(E_DELAY)
GPIO.output(LCD_DATA4, GPIO.LOW)
GPIO.output(LCD_DATA5, GPIO.LOW)
GPIO.output(LCD_DATA6, GPIO.LOW)
GPIO.output(LCD_DATA7, GPIO.LOW)
if bits&0x01==0x01:
GPIO.output(LCD_DATA4, GPIO.HIGH)
if bits&0x02==0x02:
GPIO.output(LCD_DATA5, GPIO.HIGH)
if bits&0x04==0x04:
GPIO.output(LCD_DATA6, GPIO.HIGH)
if bits&0x08==0x08:
GPIO.output(LCD_DATA7, GPIO.HIGH)
time.sleep(E_DELAY)
GPIO.output(LCD_E, GPIO.HIGH)
time.sleep(E_PULSE)
GPIO.output(LCD_E, GPIO.LOW)
time.sleep(E_DELAY)
def display_init():
GPIO.output(LCD_RW, GPIO.LOW)
lcd_send_byte(0x33, LCD_CMD)
lcd_send_byte(0x32, LCD_CMD)
lcd_send_byte(0x28, LCD_CMD)
lcd_send_byte(0x0C, LCD_CMD)
lcd_send_byte(0x06, LCD_CMD)
lcd_send_byte(0x01, LCD_CMD)
def lcd_message(message):
message = message.ljust(LCD_WIDTH," ")
for i in range(LCD_WIDTH):
lcd_send_byte(ord(message[i]),LCD_CHR)
def lcd_message_at(message,line):
if line == 1:
lcd_send_byte(LCD_LINE_1, LCD_CMD)
if line == 2:
lcd_send_byte(LCD_LINE_2, LCD_CMD)
if line == 3:
lcd_send_byte(LCD_LINE_3, LCD_CMD)
if line == 4:
lcd_send_byte(LCD_LINE_4, LCD_CMD)
lcd_message(message)
class Radio(object):
def __init__(self, start_station=0):
self.current_station_index = start_station
self.playing_process = None
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(LCD_E, GPIO.OUT)
GPIO.setup(LCD_RS, GPIO.OUT)
GPIO.setup(LCD_RW, GPIO.OUT)
GPIO.setup(LCD_DATA4, GPIO.OUT)
GPIO.setup(LCD_DATA5, GPIO.OUT)
GPIO.setup(LCD_DATA6, GPIO.OUT)
GPIO.setup(LCD_DATA7, GPIO.OUT)
display_init()
@property
def current_station(self):
"""Returns the current station dict."""
return STATIONS[self.current_station_index]
@property
def playing(self):
return self._is_playing
@playing.setter
def playing(self, should_play):
if should_play:
self.play()
else:
self.stop()
@property
def text_status(self):
"""Returns a text represenation of the playing status."""
if self.playing:
return "Now Playing"
else:
return "Stopped"
def play(self):
"""Plays the current radio station."""
print("Playing {}.".format(self.current_station['name']))
play_command = "while true; do ffplay -i {stationsource} -vn -nodisp -autoexit; sleep 5; done".format(stationsource=self.current_station['source'])
self.playing_process = subprocess.Popen(
play_command,
shell=True,
preexec_fn=os.setsid)
self._is_playing = True
self.update_display()
def stop(self):
"""Stops the current radio station."""
print("Stopping radio.")
os.killpg(self.playing_process.pid, signal.SIGTERM)
self._is_playing = False
self.update_playing()
def change_station(self, new_station_index):
"""Change the station index."""
was_playing = self.playing
if was_playing:
self.stop()
self.current_station_index = new_station_index % len(STATIONS)
if was_playing:
self.play()
def next_station(self, event=None):
self.change_station(self.current_station_index + 1)
def previous_station(self, event=None):
self.change_station(self.current_station_index - 1)
def update_display(self):
self.update_station()
self.update_playing()
def update_playing(self):
"""Updated the playing status."""
if self.playing:
lcd_message_at("playing",1)
else:
lcd_message_at("pause",1)
def update_station(self):
"""Updates the station status."""
message = self.current_station['name'].ljust(LCD_WIDTH-1)
lcd_message_at(message,2)
def toggle_playing(self, event=None):
if self.playing:
self.stop()
else:
self.play()
def close(self):
self.stop()
lcd_message_at("Radio uit",1)
lcd_message_at("Wacht op LED",2)
def radio_preset_switch(event):
global radio
radio.change_station(event.pin_num)
if __name__ == "__main__":
global radio
radio = Radio()
radio.play()
global end_barrier
end_barrier = Barrier(2)
nextstation.when_pressed = radio.next_station
prevstation.when_pressed = radio.previous_station
playpause.when_pressed =radio.toggle_playing
playpause.when_held = end_barrier.wait
end_barrier.wait()
radio.close()
call('sudo shutdown -h now', shell=True)
@Lukelectro
Copy link
Author

strangely, after routing audio to card 1 which is the usb card and setting the FS readonly, sound comes from hdmi monitor. Setting it to card 2 in alsa-conf, being the hdmi monitor, causes sound to come from hdmi monitor before setting the filesystem to overlay/readonly, but from the usb sound card after setting filesystem to readonly. So that's how it is used now, as in daily use there is no hdmi connected.

Also, I use gpiozero instead of the original 'button listener' . And since mplayer is not included in the current raspberry Pi OS, I use VLC.

@Lukelectro
Copy link
Author

No longer using VLC, as there where problems: Sudden silence/stream stoppend/no error/vlc still running. Similar to https://bugs.launchpad.net/ubuntu/+source/vlc/+bug/790216 which is also where I got the solution from.

Also modified the audio path, removing all cards except the USB sound card, so that is always the default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment