Skip to content

Instantly share code, notes, and snippets.

@Lukelectro
Last active October 9, 2024 14:27
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
# requires `mplayer` to be installed
# v1.0
# 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
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']))
if self.current_station['source'].split("?")[0][-3:] in ['m3u', 'pls']:
play_command = "cvlc --playlist-skip-ads {stationsource}".format(
stationsource=self.current_station['source'])
else:
play_command = "cvlc {stationsource}".format(
stationsource=self.current_station['source'])
play_command += " --loop --http-reconnect"
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.

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