244 total views
Har du en Raspberry Pi liggende er det en enkel jobb å lage en Internet-radio med akkurat den betjeningen du liker best. Her forklarer vi hvor enkel og grei denne oppgaven er.
(c) Anders Fongen, april 2024
Innledning
Min daglige radiolytting starter til frokosten, hvor jeg foretrekker at ett trykk skal starte radioen og gi meg morgensendingen fra NRK. Jeg har en DAB-radio som gjør dette, men jeg ønsker også å lytte på stasjoner som ikke finnes på DAB-nettverket der jeg bor, men som kun finnes som Internett-strømmer.
I noen år hadde jeg en stemmestyrt Google Nest som tok inn radiostasjoner basert på TuneIn-appen, men siden den gang har NRK sluttet å strømme via TuneIn, så da ble det en periode DAB-radio side om side med en Bluetooth-mottaker for å spille TuneIn-stasjoner.
Jeg er ingen “storbruker” av Internet-radio, og er fornøyd med 6-8 stasjoner, inkludert NRK sine. Jeg stiller heller ingen andre krav til betjeningen enn at den skal være enkel og uten krav til øyekontakt. Den skal kunne betjenes i mørke og når jeg ikke har brillene på meg. Altså ingen touch-skjerm, men heller en knapperad.
Eller enda bedre: KUN ÉN KNAPP!
Betjeningsdesign
Internet-radioen trenger egentlig bare én knapp, som slår radioen av og på, og som kan brukes til å stege gjennom listen av forhåndlagrede stasjoner.
- Radio av: Ett trykk slår på radioen og spiller den første stasjonen i listen
- Radio på: Ett trykk skifter avspillingen til neste stasjon i listen, eller går til toppen av listen ved siste stasjon.
- Radio på: Langt trykk slår av radioen
Lydsignalet fra Internet-radioen går til en separat forsterker, der finnes volumkontrollen.
Valg av maskinvare
I denne bloggartikkelen har jeg forklart hvordan en Internett-radio kan bygges på en mikrokontroller (Raspberry Pico) og en separat MP3-dekoder. Dette var et morsomt prosjekt, men denne gangen skal jeg presentere noe enklere som lar seg realisere på én kveld:
Jeg har noen eldre Raspberry Pi versjon 3 liggende. Disse kan startes med Linux, de har programvare for å dekode MP3, og de har en analog lydutgang som kan koples til en forsterker eller et headsett. De har også WLAN-adapter som kan motta datastrømmer via hjemmenettet. I sum er Raspberry Pi noe dyrere enn de to komponentene jeg brukte i nevnte bloggartikkel, men i tillegg til en enklere konstruksjon oppnår vi også å ha hele kretsen inni et standard kabinett for Raspberry Pi.
Den ene knappen som tillater brukerbetjeningen er av den typen som ofte brukes til eksperimentering. Den koples til GPIO-bussen og limes til lokket på kabinettet.
Den analoge lydporten på Raspberry Pi hadde et rykte for dårlig lydkvalitet i tidligere versjoner av maskinvaren, men i Pi versjon 3 er denne kvaliteten tilfredsstillende.
Valg og konstruksjon av programvare
Ved installasjon av den anbefalte Linux-versjonen for Raspberry Pi, kalt Raspbian, blir det også installert et avspillingsprogram som heter VLC. Dersom man kopler skjerm og tastatur til Pi er VLC egentlig alt vi trenger for å strømme radioprogrammer. Brukerdesignet som vi har bestemt, utelukker derimot denne løsningen, vi skal kun ha Rasperry Pi i en boks ved siden av forsterkeren vår.
Vi kommer derfor til å lage et enkelt Python-program som avleser tilstanden på betjeningsknappen (trykket ned eller fri), som har en liste over adresser til de lagrede radiostasjonene, og som starter og stopper VLC-programmet med den ønskede radiostasjonen. Programkoden er vist lenger ned i denne artikkelen.
La oss starte med hvordan betjeningsknappen kan avleses av Pythonprogrammet. Python på Raspberry Pi kommer med ferdiginstallerte moduler for å behandle data (sende og motta) via GPIO-bussen, som er raden med pinner langs kanten av kretskortet.
Vi kopler knappen til pin 3 (også kalt GPIO2) og pin 6, som er jordforbindelse. GPIO2 er koplet til en intern pull-up motstand, som medfører at det står en spenning på porten når knappen er fri (avlest verdi 1) og med jordkontakt når knappen er trykket ned (avlest verdi 0). Koden for å avlese verdien på pin 3 ser slik ut:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.board)
GPIO.setup(3,GPIO.IN) # Setter pin 3 som inndata-port
verdi = GPIO.input(3) # avleser verdien på pin 3.
Når man avleser knapper på denne måten må man ta hensyn til såkalt prell (switch bouncing), og vi har i denne koden valgt å måle tilstanden over noen millisekunder for at prellet skal roe seg. Derfor er Python-koden mer omstendelig enn om prell ikke fantes.
Utenom denne koden for å avlese langt og kort trykk på betjeningsknappen finner vi også kode for å starte VLC-programmet gjennom en såkalt subprosess, som utføres i parallell med resten av Python-programmet. Dermed kan vi avlese knappetrykk også mens VLC-programmet kjøres.
Trenger ikke grafisk brukergrensesnitt
VLC kjører normalt med et grafisk brukergrensesnitt, men det er bortkastet ressursbruk i vårt tilfelle hvor ingen skjerm er tilkoplet. Vi bruker derfor programmet CVLC, som gjør det vi ønsker med konsollbetjening (kommandolinje) og mindre ressursbruk. Et eksempel på hvordan vi starter og stopper CVLC i en subprosess ser slik ut:
import subprocess,time
url = "http://lyd1.lokalradio.no/oradio_hq"
p = subprocess.Popen(['cvlc',url]) # Starter avspillng
time.sleep(5) # Venter i 5 sekunder mens cvlc spiller
p.kill() # Stopper så avspillingen
Full programkode
Denne forklaringen av virkemåten burde være dekkende for å forstå den aktuelle programkoden som brukes. Den ser slik ut:
import RPi.GPIO as GPIO
import time,subprocess
button = 3
GPIO.setmode(GPIO.BOARD)
GPIO.setup(button,GPIO.IN)
b = GPIO.input(button)
radio_urls = ["http://lyd.nrk.no/nrk_radio_p1_innlandet_mp3_m?_hdr=0",\
"http://lyd.nrk.no/nrk_radio_klassisk_mp3_m?_hdr=0",\
"http://lyd.nrk.no/nrk_radio_jazz_mp3_m?_hdr=0",\
"https://dispatcher.rndfnk.com/br/br2/live/mp3/mid",\
"http://lyd1.lokalradio.no/oradio_hq",\
"http://freshgrass.streamguys1.com/folkalley-128mp3"]
current_channel_number = 0
def start_radio_channel(url):
return subprocess.Popen(['cvlc',url])
def this_channel():
return radio_urls[current_channel_number]
def switch_channel():
global current_channel_number
global radio_urls
current_channel_number = (current_channel_number+1) % len(radio_urls)
return this_channel()
def button_switch():
global button
if GPIO.input(button) == 0:
# Debounce, wait for 100 ms
time.sleep(0.1)
if GPIO.input(button) == 0:
return 1 # True
return 0
def button_wait():
# If button already pressed, wait until released
while button_switch() == 1:
time.sleep(0.1)
while button_switch() == 0: #This call returns in 0.1s
time.sleep(0.1) # Loop consumes 0.2s per iteration
# Do not return until the button is released, but allow
# a long press to switch off the radio
loop_counter = 0
while button_switch() == 1: # returns i 0.1s
loop_counter += 1
if loop_counter == 15: # 3 seconds hold
return 2
time.sleep(0.1) # Another 0.1s
return 1
radio_process = None
while True:
c = button_wait()
if c==2 and radio_process != None:
radio_process.kill()
radio_process = None
elif c==1:
if radio_process == None:
current_channel_number = 0
radio_process = start_radio_channel(this_channel())
else:
radio_process.kill()
radio_process = start_radio_channel(switch_channel())
Demonstrasjonsvideo
Her følger en kort demonstrasjonsvideo:
Oppsummering, forslag til flere funksjoner
Det finnes masse eksempler på Internet-radioer som bygges selv, denne som jeg her viser er svært enkelt og er laget for mine behov. For dine egne behov, bruk gjerne deler av denne programkoden og lag dine egne funksjoner. Eksempler på en videreutvikling kan inkludere slike funksjoner:
- Flere knapper, kanskje én for hver forhåndslagrede stasjon
- En roterende bryter (rotary encoder) for å velge stasjon
- Et display (LED eller OLED) som viser stasjonsnavn
- Et web-grensesnitt for å redigere stasjonslisten
- Klokkefunksjoner for automatisk avspilling (vekkerklokke)
- Bluetooth-mottaker for avspilling fra mobiltelefonen (beskrevet her)