What does this name mean?
Let's immediately explain the name of the project.
In Italy, where I live, the national radio/tv broadcasting system (RAI), along with the mainline phone carrier (formerly Telecom, now TIM), used to broadcast 6 radio channels on the phone line, thus providing a service called Filodiffusione ("wire broadcast").
The service started in the late fifties and lasted (in forms other than telephone lines) until a few years ago. You can find a full description on this wiki page (in Italian).
You could rent (almost free) a standalone device comprising an internal speaker and often two external speakers aesthetically matched. The device itself was synecdochically called the Filodiffusione.
Unfortunately, I don't have the two speakers anymore (who knows where they ended up to be...)
Et voilà, here it is!
My idea is to repurpose this object, keeping it externally as similar as possible to its original form, as a wi-fi web radio player.
As this "wired broadcast" will become a "wireless broadcast", so "filo" (wired) will become "senza filo" (wireless). (Of course "wired/wireless" is referring to radio source).
The core of this new web radio player will be a Raspberry PI 3b+ provided by E14 after the first proposal of this project.
This really vintage looking object has taken place in my various houses in the last 30 years, mostly because it has a sentimental value to me (when I was a small kid it was my main music source) and also because I consider it aesthetically pleasing.
If you want one, it's easy to find on eBay (for example here).
Opening it
The bottom is in black plastic, while the top is fully made of wood. Let's unscrew and unmount the various parts:
The speaker clearly states "8 ohm" in it, that will ease its interfacing. PSU is a 220 to 12V AC, guess I won't use it but if it still works I can save it for something else.
Removing the front aluminium needs cutting the bi-adhesive:
The first two potentiometers are for highs ("alti"), lows ("bassi"), while the rightmost one is used for power on/off and volume.
Two things are noteworthy: the metal lever that makes the pot simulate a switch (while the device is actually always powered on!) and the little "Christmas tree light" working as a pre-LED!
Note: obviously the lever was not in this position but horizontal, tangent to the pot knob neck (not visible here).
The PCBs and the soldered components are a masterpiece of vintage electronics technology.
The 6 buttons are self-excluding by the use of a lever that disables the previous at the press of the new one. This is the real "radio button" that Apple introduced as a metaphor! After desoldering, this one will be very useful.
I won't, anyway, reuse the potentiometers (even if they are still working), nor the metal bracket that holds the PCB and pots.
Finally, let's examine the connections on the back:
Two wires are coming out: the white one is for the AC and it will be reused, the other is for the phone line and there's no use for it.
The red object is a voltage changer, it allowed to switch between voltages (e.g. 100/220V). The leftmost connector is for the external speakers. I don't know about the middle DIN.
Just to end this first step, I tested the speaker by soldering a mono jack to the two poles. It seems to work!
Choosing the components
Let's elaborate on the pieces necessary to obtain the device that I have in mind. It is important to note that my aim is to build an object that I can use every day, so this it's not just an exercise for me.
I want:
- a "knob" to "tune" (choose) a radio station and one for the volume/mute function
- a display to visualize stations and song info when available
- a way to reuse the six radio buttons as presets
- a way to reuse the internal speaker
- access to USB, audio, and HDMI ports
- the possibility to define the list of radio names and URLs from outside, and mark the 6 presets on the list
- the possibility to configure wifi from the device
For the "knobs" I chose KY040 encoders with PCB and push button (foto).
The presence of the PCB is useful because it provides pins that are directly usable with Dupont cables, and pull-up SMT resistors.
Note:
For the display, I chose an OLED 1.3" 128x64 pixel with I2C connection. There are also 0.9" displays very similar, but I think that a few millimeters more are better.
Some of these devices have SSD1306 controller, mine has SH1106. There is no real difference, as far as I know, if you use the right library to drive it.
The six radio buttons, which are actually switches, can be directly interfaced to GPIO using pull-up resistors.
This type of switch, still available if you search around, has several contacts that can be used in a chain to build a "deviator" (i.e. the pool of switches that allows you to turn light on/off in rooms from different positions).
In fact, in the original device, the 6 switches were not used this way but instead each button disables another thanks to the presence of a lever that makes a switch click the previous out when you push it.
If you look at the video where I test the internal speaker, I am just using my smartphone as audio source. The volume is rather low, and after the initial happiness of seeing that the speaker was still working, I realized that I needed an amplifier.
I chose this one, a tiny mono amplifier circuit based on Texas Instruments TPA3118 "30-W stereo, 60-W mono, 4.5- to 26-V supply, analog input Class-D audio amplifier" chip. It also sports an interesting "mute" couple of pins.
To replicate the HDMI port on the back, I just bought a short HDMI male to female extension cable.
For the audio jack output, I built a Y shielded cable, with one branch repeating stereo output to a female 3.5 mm jack and the other summing the two channels to one by means of two 1Kohm resistors.
More on points 6. and 7. later (it's all software).
The circuit
This is the final circuit with regards to RasPI. The two encoders use 5V while the display and pull-ups use 3.3V. The red push-button is used to activate the settings menu.
Building the radio software
At first, I examined some specialized OS for the RasPI (e.g. RuneAudio), then I decided to go the standard way: get an SD, use the RasPI Imager and install a Raspberry PI OS (formerly known as Raspbian).
Then, I had to find a suitable audio streaming software. I needed one that:
- can be driven by command line and/or by libraries
- can manage different types of streaming sources, codecs and formats (SHOUTcast, Icecast, mp3, m3u8, pls, etc.)
- can show streaming info (when available) like the artist and name the song that is playing
I evaluated VLC and XMMS but as they are mainly desktop applications with a command-line overlay I decided on the MPD/mpc system (https://www.musicpd.org/doc/html/user.html).
Installing and configuring MPD was relatively straightforward. I only encountered some issues in configuring where to output sound. In fact, I had to try a lot of settings combinations to be able just to get any sound playing!
I won't get into much detail here, but to simplify the main issue is that RasPI expects to use HDMI high-quality audio as default output, while I simply had a speaker connected to the analog stereo audio jack.
I didn't use HDMI audio because it requires an audio/video splitter or a monitor/TV with speakers (that I don't have in my studio). A simple explanation is here.
But after some tweaking in the raspi-config and in the mpd.conf file, all went smoothly.
The front-end to MPD is the mpc application. Listening to a stream playing in background is as easy as:
mpc add http://sc2.radiocaroline.net:8040/; mpc play
More useful commands are
mpc stop //stops stream mpc clear //removes URL mpc volume 50 // sets mpc volume (relative to RasPI main volume) mpc current //returns URL, volume, output device and song info (if available)
Finding radio URLs is not an immediate task. You have to check your favorite radio's website, find a live playing gadget, use browser developer tools or inspect page source, and somehow it doesn't work.
Luckily, I found out the awesome (even if ugly looking) fmstream.org, huge help for the job.
The main application
I did most of the work in a single Python application. I am a part-time developer, it's not my main job but sometimes I have to write code in various languages (e.g. Java, Javascript, C, etc.).
I knew Python only from some short scripts I have made for specific applications, like retrieving logs, etc. It turned out I like it very much because the syntax is light, no semicolons, readable statements, and other nice things.
If you can cope with strict indentation and print() with parenthesis, it's really comfortable ;-).
You can find the source and other support files in this Github repo.
Using the display
I found most of the information on how to interface an I2C display from this tutorial. I am linking it because in general, I think that the wheel must not be reinvented each time, so if a tutorial works it is worth linking it and give his author the respect he needs.
The article makes extensive use of the Luma library, derived from PIL (Python Image Library). I just used sh1106 instead of ssd1306 because of the controller in my display.
GPIO interfacing
I decided to use the RPi library because it is rather straightforward to use. There are obviously other that are optimized for realtime applications, etc. but it's not my case.
Encoders and buttons
As it was my first time using these devices with the RasPi, I searched a lot on how to do it. The known main problem is debouncing the readings. There are various ways to do this, and I preferred a software solution to avoid adding circuits.
For the encoders, I found this article specific to KY040 type, and ported the Arduino code to Python, adapting some parts to better fit my needs. The idea is to verify all the possible states for the 2-channel reading (CLK/DT or A/B) and filter out the one that have no meaning. The result is more turning on the encoder but more precise forward/backward readouts.
For the buttons, I used interrupt callbacks also from RPi. The use of this functionality allows to avoid polling and also has built-in debouncing.
The interrupt is configured like this
GPIO.add_event_detect(list_sw, GPIO.FALLING, callback=list_push_callback, bouncetime=300)
where list_sw is the GPIO BCM pin number and list_push_callback is the procedure to be called for each FALLING state of the pin.
The push-button in the KY040 has already a pull-up resistor, while I had to add one for each preset switch and for the settings button, as you can see in the Fritzing circuit.
Program structure
There are 4 main threads (yes, I had to face their use, and it was the source of most of my late-night coding...):
- current radio / current song information display
- radio list browsing
- volume change
- preset buttons management
The first three compete for the display, so I had to use a Lock to avoid glitches in visualizing the info.
The idea is that the main visualization is always the radio and song, then if you browse the radio list just by turning the related knob the list is displayed; if you stop moving, the radio info come back, but if you turn the knob again the position you reached is still the same.
The volume works the same way, just displaying the current volume until you stop changing it.
The preset thread continuously polls for the state of the six switches and updates an internal list. This is done just to capture the preset's steady situation (as the switch change is immediately managed by the corresponding interrupt procedure).
A note about the song information.
By using the mpc current command, it is possible to get this information if the stream makes it available. What is rather strange is that I haven't been able in any way to capture the output from the subprocess command running mpc current. It is not in the stdin, stdout, stderr, in any pipe, it's nowhere. So the only way to get this precious info was to start an every-second watch from CLI,
watch -n 1 'mpc current > radio_info.txt'
redirecting it to a radio_info.txt file, and continuously reading this file from the song info thread. The watch process is auto-run at startup.
Settings menu
By pressing the related push-button, you can spawn a menu that allows you to change some settings, go to standby, or shutdown.
Even if as you will see the RasPi is accessible by USB/HDMI, that is not too comfortable and often not viable. So how to connect to WiFi? I could implement a full interface to SSID search, password insertion etc. but I decided to put this in the "future improvements" list.
Instead, with "WiFi WPS" option you can exploit the WPS functionality of a router (most of them have it). The RasPi will save the connection info in the /etc/wpa_supplicant/wpa_supplicant.conf file and make it permanent.
After connection, you can look at the retrieved IP address with the "Show IP" option. You can use this address if you want to connect to the RasPi via VNC or SSH (I enabled them) or if you want to update the radio list (more on this later).
"Standby" just stops MPC, mutes the volume and clears the display. In fact this is just temporary, by moving the selection knob you reactivate the settings menu, and on exit the previous radio and volume will be restored.
"Shutdown" says bye and executes sudo shutdown 0. Simple, isn't it?
The radio list
It is implemented as a simple list <radio name>|<radio URL>. Presets are marked with a <preset number.> in front of the radio name.
How to modify the list? I installed Lighttpd and PHP and created a simple form with a TEXTAREA:
<?php $fn = "radio_list.txt"; $file = fopen($fn, "r+") or die("Unable to open file!"); $size = filesize($fn); if($_POST['allfile']) { fwrite($file, $_POST['allfile']); rewind($file); } $text = fread($file, $size); fclose($file); ?> <form action="<?=$PHP_SELF?>" method="post"> <textarea COLS="120" ROWS="30" name="allfile"><?=$text?></textarea> <br/> <button type="submit">Update</button> </form>
After changing, you can reload the list from the settings menu by choosing "Reload list".
A database table and a nice interface for editing is in the todos list.
Assembling it
First things first. Gathering all the pieces to evaluate the internal disposition.
Both RasPi and the tiny amplifier needs to be powered. I bought a 5V-3A USB for the first, a 12V-2A for the other, and unassembled both by using a Dremel and a certain amount of violence ;-)
Only half the internal volume is available due to the presence of the big speaker. Moreover, I'd like to position the RasPi in a way I can make the USB accessible.
(yes, I used hot glue, and I can describe its color as "A whiter shade of pale")
For the pull-ups of the preset switches and settings button I used a piece of stripboard:
Just a detail of the amplifier input: as you can remember from the audio Y cable drawing, two resistors are used to convert stereo to mono. Thermo-shrinkable tube is your friend.
You can see the two mute contacts. When closed, the amplifier stops output. So I decided to add a switch to use this feature. It is useful when you use the stereo plug on the back (directly connected to the audio jack, see previous drawing) to disable the internal speaker.
FASTON connectors are good to connect the speaker, in case I need to detach it temporarily.
The silkscreen in the front aluminium panel was a bit ruined in the rightmost knob numbers, so I decided to clear them using isopropilic alcohol and make a blank space for the display.
{gallery} Front panel workout |
---|
I designed and 3d printed a small frame to mask the visible aluminium edge
I decided to use the small hole over the display to accommodate an internal activity LED. In practice, this LED substitutes the green light. See here how to do it.
So I connected this LED to GPIO pin 25, through a 330 ohm resistor. I glued both the LED and the display on the inside, after finding the right position with the display turned on.
To accommodate the display and the screws needed to block the encoders, I had to dremelize the front plastic, with no mercy at all.
The back panel allowed me to host the main power switch, the audio output, the HDMI output from the extension cable, and the settings button.
I also cut a hole with a cup saw to make one USB accessible. It was difficult to cut a better shape because of the combination of plastic and wood wall.
The final internal disposition... ehm... but I can assure you it is all working, look at the video below!
See it in action
Friends, questions and Spotify
After reading the first publication my project, a friend of mine told me "nice! so I can play Spotify on it?"
Well.. er.. not exactly, my friend. But the idea is good indeed!
The hunt for the right tool
I started searching for usable APIs (Application Programming Interfaces) to connect to Spotify, get playlists, albums, songs, and stream them.
No need to write everything from scratch, because someone has created Mopidy: a simple Python server extending MPD and allowing to connect to several streaming services like Spotify, Soundcloud, etc.
Following the installation steps was an easy task because RasPI is well-considered and obviously a good candidate for the job.
Mopidy in detail
Mopidy is plugin/extension based. Two of them were of obvious interest to me.
The first is mopidy-spotify. You need to have a Spotify Premium subscription (which I have) and generate an API client id/client secret pair from this page.
This pair, along with Spotify username and password must be put inside Mopidy configuration file in the Spotify section.
When you launch mopidy from CLI (or the corresponding service via systemctl) you can see from the logs if the connection is going through. I did and it went rather smooth.
Now you would want to test the streaming part, so you need a client. There are plenty of, see here. In particular, using mpc could be a good thing, especially because I learned to use it in this project.
Enter mopidy-mpd extension. It's a full MPD server implementation to make Mopidy available from MPD clients. I installed it, tested it with mpc and it was ok.
How to use it
You can make good use of the playlists in your connected account with these commands:
mpc lsplaylists # list all the playlists mpc load "<full playlist name>" # load one playlist mpc play # play first song of the playlist mpc play 3 # play third song mpc next # skip to next song mpc prev # skip to previous song mpc current #show current song info
There are many more possible commands to seek inside songs, etc. but I won't go into more detail here. mpc help is your friend.
Putting it in the project
As my project is MPD/mpc based, most of the higher layer is still working. In fact, the song information thread, based on mpc current command, still shows the right string on the display without touching anything.
Most of the application structure can be kept, but the user interface general mechanism must be rewritten.
In general, I had to switch from a simple mpc play of a stream to a double level mpc load plus mpc play. Not too complicated, but I had to cope with a two-tier model.
Two lists are better than one
The main task is to provide the possibility to switch from an "all playlists list" to a "song list" when you choose one playlist.
The problem is I don't have enough controls: one knob plus his button is usable, the other needs to be dedicated to volume and mute/pause.
The six preset buttons have no use here (preset playlists are actually meaningless), so I decided to introduce two "modes": playlist mode and song list mode.
With button n.5 you enter playlist mode, browse all the playlists with the knob and select it with the knob push button. The playlist starts playing from the first song, and goes on playing showing the name of each song.
With button n.6 you enter song mode, browse the songs with the knob and select one of them with the button, thus directly skipping to it.
State of the application
I had to rewrite a lot of the original "radio" application; at present it is working but it can be improved (I made good use of the extended deadline but time was really tight).
I chose to keep it alternative to the radio version, still have to think about how to switch between the two (maybe on the settings page).
I decided to download the playlists' list to a file (by using mpc lsplaylists > filename), make it editable via PHP in the same way I did for the radio list. This makes it easier to restrict the playlist set and to avoid long and error-prone reads directly from Spotify.
An additional task, as soon as I find the time, is to add two tiny push buttons, maybe on the side or below the display, to implement previous, next, and seek. These two buttons can be also used on the radio version.
And one more thing... a web application that implements remote control by calling mpc command from php.
See it working
Links summary
MPD/mpc: https://www.musicpd.org/doc/html/user.html
Configuring audio on RasPI: https://www.raspberrypi.org/documentation/configuration/audio-config.md
OLED display setup: http://codelectron.com/setup-oled-display-raspberry-pi-python/
Luma OLED library: https://github.com/rm-hull/luma.oled
RPi library: https://sourceforge.net/p/raspberry-gpio-python/wiki/Home/
Better KY040 reading: https://www.best-microcontroller-projects.com/rotary-encoder.html
Change the GPIO of the activity LED: https://www.raspberrypi.org/forums/viewtopic.php?t=158293
Project Github: https://github.com/simont-org/SenzaFiloDiffusione
Mopidy: https://mopidy.com/
Installation for Raspbian: https://docs.mopidy.com/en/latest/installation/raspberrypi/#how-to-for-raspbian
Mopidy-spotify: https://mopidy.com/ext/spotify/
Mopidy-MPD: https://github.com/mopidy/mopidy-mpd
Mopidy clients: https://docs.mopidy.com/en/latest/clients/
Top Comments