Last active
October 9, 2024 14:27
-
-
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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.