As an aging British man I find myself increasingly dependent on the soothing burble of BBC Radio 4. It’s like an old friend, albeit one that talks incessantly about Brexit. But I also live in San Francisco, so I can’t just switch on the wireless to get my fix of the Today program.
Which is why I built Humphry — a Pi-powered “radio” with a single button, which plays BBC Radio 4 time-synced to local time. Much like the eponymous John Humphrys it’s stubbornly straightforward — that’s all it will ever do. Right now it doesn’t even have a volume control; the level’s fixed at “reassuring murmur”. All the code is open source under GNU GPL v3.0
Hit that big red button, BBC Radio 4 starts playing, synced to your local time. Hit it again, it stops. Sounds simple, but from the start — when I’d never coded a line of Python — to the finished first version took me over a year to complete, in spare moments alongside running Big Health. Barring some rough edges it works just how I wanted it to, and I use it every day.
The process has been inspiring—I now see coding as the ultimate creative tool, I’m in awe at the open source community, and it gave me a fulfilling project to work on with my dad. This post details that journey at a level of detail I would have appreciated when I got started, dead ends and all, should you want to make your own or just marvel at this weird obsession of mine.
Step 1: learn how to code
When I started in 2017 the only similar project I could find was the Radio-4-Matic by Adam Foster, from back in 2013. Much of the detail was lost on me but it gave me some helpful clues —he used the get_iplayer Perl script on a Raspberry Pi to download audio content from the BBC’s iPlayer service.
So the next step was to learn to code. I dug out an old kid-sized Kano keyboard, bought a Raspberry Pi 3B+, and plugged it all into my TV. I ordered Programming the Raspberry Pi by Simon Monk and the Adafruit Pi starter kit, which together are a great primer on Python for the Pi. After a couple of months of on-and-off study, a few blinking LEDs and moderate wrist strain later, I knew enough Python to make a start.
It’s ugly but it works (sometimes)
My software needed two main components: a downloader that would somehow get audio files from BBC iPlayer, and a player that would play them, 8 hours delayed from the UK, at the press of a button.
Adam Foster’s approach (as far as I could tell) had been to continually record a stream of Radio 4, which his player would play at an offset of 8 hours. However get_iplayer no longer supported streaming, so I needed a different approach.
I downloaded a few test audio files using get_player and noticed that radio programs became available very soon after broadcast. Plus each audio file had a <firstbcast> tag in its meta-data, containing the broadcast date and time.
So I built a first version of the downloader.py script that would tell get_player to download all BBC Radio 4 programs available since 8 hours prior. A player.py script would then find the right audio file based on the time in <firstbcast> and tell the Pi’s native omxplayer media player to play it, offset by however many minutes past the program’s start time the current time was.
And…it worked! Sort of. The Today program played perfectly, welcoming John Humphrys back into my bedroom (as it were). But at other times of day I noticed eery gaps with no audio. Then I realised— the <firstbcast> tag only included the first broadcast date, so repeats weren’t downloaded. Plus even I could tell that this approach was comically fragile. It was time for a rethink.
When Humphry met JSON
In an ideal world I would have a reliable schedule of the day’s radio programs in a usable format. I could then use this as an index to determine which programs to download, and which to play at a given time.
On inspecting the BBC Radio 4 website’s page source I realised that all the fields I needed for my schedule index were buried in the HTML — program name, start and end times, and crucially the “pid” — the unique program ID allocated by the BBC, and usable by get_iplayer to download audio files. Using pids would be resilient and allow me to play repeats.
So I set about building a scraper script. Using BeautifulSoup and a tortuous, custom loop it extracted each of the key fields from the HTML, then appended to a dictionary which it then saved off to a JSON file. When it worked for the first time it felt like magic — ping, a perfectly formatted JSON file!
However my home-baked HTML parser was fragile, susceptible to even the smallest change to the BBC website. Sure enough, one day it just wouldn’t run. I re-scoured the BBC HTML and right there, embedded in the webpage, was a fully-formed JSON-LD document with all the info I needed!
So I rebuilt the scraper to extract and save the JSON, and re-wrote downloader.py to use the current day’s JSON schedule file. In parallel I rewrote the player.py script to use the JSON schedule file to identify which audio file to play.
With all that now working I added a cleanup function to delete old audio and JSON files (crucial to adhere to the BBC’s licensing rules) and then packaged it up into a more compact format — a main.py script to run the cleanup, scraper and downloader scripts every hour. Player.py would run in parallel, ready to start playing the current audio file at the press of a button.
With the code working, Humphry was ready to take physical form. Over the 2018 winter holidays my Dad and I, with the help of his precision drill set (for which he has a deep and mysterious love), knocked together Humphry in record time. Here’s us working on it, and the finished item.
For Humphry to work all you really need is a Raspberry Pi, a button (connected to GPIO pin 18 on the Pi by default) and any amplified speaker connected to your Pi’s 3.5mm audio output. However my setup used parts, mainly from Adafruit and the awesome Pimoroni in my native Yorkshire:
- Raspberry Pi 3B+ (today I’d use a 3A+ since we don’t need all those ports)
- 3" speaker (4 Ohm 3 Watt)
- Mono amp
- Mini arcade button
- 3.5mm plug to pigtail audio cable (to run from the headphone jack on the Pi to your amp)
- Mini-breadboard (we cut one of these in half to mount the amp — not elegant but effective)
- 2 x female-male 3" jumper cables
- 2 x female-whatever jumper cables (we cut the non-female ends off to solder to the button)
- Pirate-brand Plastic Loot Box (also useful for storing doubloons)
- Stand-offs (to mount the Pi in the box)
- Various screws (found in my dad’s garage)
I had no experience of electronics (who could guess from the photos above?) but it all connected pretty intuitively:
- The 3.5mm audio cable carries the audio signal from the headphone socket on the Pi to the amp (one internal wire and the encasing bare wire connect to the A+/A- connections on the amp).
- The amp’s audio output in turn connects to the speaker (+ve and -ve connections on amp and speaker connect to their counterparts).
- The amp is powered from the GPIO pins on the Pi (any 5V pin and ground pin connected to Vin and Gnd connections on the amp respectively).
- The button connects to GPIO pin 18 and any ground pin.
For ease of connection to GPIO pins and the mini-breadboard I used jumper cables (either whole or cut in half and soldered) for everything except the amp-speaker connection (where I used off-cuts from the 3.5mm audio cable).
For the box, we cut holes for speaker and button using the drill, then sandpapered them smooth. We drilled mounting holes for the Pi and the speaker, ventilation holes on the sides and base, and a hole for the power supply through the side.
Running the code automatically
With the hardware set up and all the code written, the final step was to get it all to run automatically when I plugged in the Pi.
To do this I used cron — the task scheduler built into the Pi operating system. Cron is pretty simple in concept — you edit a file called crontab to add any tasks you want to run automatically at a given frequency and time. But it took me ages to get it to work.
What I eventually realised is that cron doesn’t have any context whatsoever, so needs to be told everything explicitly — from the interpreter path, to changing directories in your cron path to where your scripts are, to setting PYTHONPATH to point to the location of the modules your scripts need.
The result of all this is described in the set up notes on github. It boils down to adding two cron tasks: running player.py every time you boot up the Pi (so it’s always waiting for a button press to start/stop playing audio), and running main.py every hour (which deletes old JSON and audio files, scrapes the BBC site to get a fresh JSON schedule file and then downloads any new audio).
I’ve been using this first complete version of Humphry for a while now and there are numerous improvements I want to make — adding a battery, automatic roll over from one radio program to the next, and so on. I’m also sure the code is crappy in all sorts of ways. But the satisfying thing is it works — and I hope this helps at least one other Radio 4 addict out there. I’d love to hear from you in any case!