This project began simply because I had an extra Raspberry Pi laying around and I was looking for a quick an easy project to knock out in a weekend. I ultimately settled on making an Internet radio using the Pi. I had a small '60s Zenith radio laying around that I could use. Like most of my projects, it grew into something much more complicated pretty quickly.
An Internet radio did eventually come out of this project idea but it took longer than a weekend! I ended up using a 1930s Truetone tube radio instead of the 1960s Zenith that I had on hand. I also wrote a custom front end to replace the existing dial. The front end is written in Python3 using the pygame library. To control the interface I used rotary encoders. Finally for the amp, I just gutted an old pair of Dell speakers and bought a cheap speaker from Amazon.
And here is the radio in action, sorry for the poor filming!
The first rotary encoder doesn't do anything but it is hooked up. Maybe some time in the future I will think of some purpose for it!
The next one controls volume and if it is pressed it mutes/unmutes the audio.
The third one selects the current 'station' by causing the needle to spin. If pressed it shows information about the currently playing song/station.
The one on the far right selects the genre of music, and changes the background graphic at the same time. If pushed when the radio is on it shuts the Raspberry Pi down. If the Pi is already shut down it starts it back up.
A good friend of mine ended up with an old 1930s TrueTone tube radio and asked if I would be interested in it. Old radios are awesome so of course I said yes! Initially I planned to restore the radio to working order. However, after digging up the schematics and closely looking over the radio I realized I would be replacing just about every capacitor on the board! And that would be just to get it to the point where I could apply power and start testing. I was already working on a Raspberry Pi based Internet radio and the TrueTone would be much cooler than the '60s Zenith I had planned on using.
I knew I would have to replace the dial with a screen of some kind and find some way to turn input from the knobs into something that could be read by the Pi. I found a dirt cheap screen on Amazon that looked like it could do the trick. The specific model I used was the "Eleduino 7 inch Portable Monitor" but it appears to be out of stock now. For the knobs I decided to use rotary encoders. They have a analog feel that suits the radio but provide a digital output that can be read from the Pi's GPIO pins. I ended up purchasing a few "Podoy Rotary Encoder Switches" from Amazon for a couple bucks.
If you're not familiar with rotary encoders they are a bit strange. They have three pins, an A, a B, and a ground. When the encoder is turned A and the B are shorted to ground in a specific order and you can use that to tell which way the encoder is turning and how far. When it comes to wiring an encoder up to the pi, its pretty simple. For my encoders the middle pin was ground, so you wire that to one of the ground pins on the Pi (confusing I know). Pins A and B just need to go to regular GPIO pins. You may want to avoid the 5v and 3v power pins though. For example, I wired the knob that controls the needle to ground, A to BCM 17, and B to BCM 27. The encoders I bought also have momentary switches if you press them. I wired one side to ground and the other to BCM 22. An important thing to note is that you will call them in your script by their 'Wiring Pi pin #s.' A wonderful resource that helped me figure out which pins were which is pinout.xyz
Next, I had to mount everything in the radio! I tried to avoid any modifications that would be visible from outside the radio. Everything is mounted in such a way that it can be easily removed and there won't be nasty scars if I ever decide to restore this radio to it's original condition.
Now the fun bit! creating the front-end took a bit of work and a TON of experimentation. I hit many dead ends along the way!! I am not planning on going over the code in great detail but I will try to explain the parts that gave me the most challenges. As a reminder, you can grab a copy of my code from gitlab: https://gitlab.com/tibernut/radioPi/blob/master/radioGUI.py
The first challenge was figuring out what to use to display the images and the 'spinning' needle. I have created GUIs with Python using Tkinter but it didn't seem appropriate for this project. After some googling I found out about pygame. Pygame is a library for creating simple games in Python. After browsing their documentation it sounded like it had everything I needed. There are tons of resources for learning how to use pygame so I won't re-invent the wheel here.
The next part that gave me trouble was simply reading values from my encoders. Initially I simply used Raspberry Pi's GPIO library and it seemed to work fine. Here is my original code:
#!/usr/bin/python3 from RPi import GPIO from time import sleep clk = 2 dt = 3 outfile = open("encoderstate.txt", "ab", 0) GPIO.setmode(GPIO.BCM) GPIO.setup(clk, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(dt, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) counter = 0 clkLastState = GPIO.input(clk) try: while True: #print(GPIO.input(clk)) #print(GPIO.input(dt)) clkState = GPIO.input(clk) dtState = GPIO.input(dt) if clkState != clkLastState: if dtState != clkState: counter += 1 outfile.write(counter) else: counter -= 1 outfile.write(counter) print(counter) clkLastState = clkState sleep(0.01) finally: GPIO.cleanup()
This worked great... until I put it into pygame's mainloop. The loop running pygame was simply too slow to get reliable input from the rotary encoders. Reading every value from the encoders is extremely important. You must know which pin came first or you won't know which way the knob is turning. The radio 'needle' would jump around unpredictably when values were missed. Well, back to the drawing board!!
Eventually I stumbled on guyc's excellent py-gaugette library! Using this library I was able to create a worker thread that would run independently of the pygame loop so that no values would be missed. If the pygame loop slowed for a moment the needle wouldn't suddenly start moving backwards. You simply set up the worker thread and then in your loop you can read how many 'steps' the encoder has taken with .get_steps() . See the snippit below to get an idea of how it works:
#gadgett worker thread to read rotary encoders gpio = gaugette.gpio.GPIO() encoder = gaugette.rotary_encoder.RotaryEncoder.Worker(gpio, A_PIN, B_PIN) encoder.start() ....otherstuff.... #read tuner (inside pygame loop) delta = encoder.get_steps() if delta != 0: rotation = rotation + (delta/1.5) rotation = int(round(rotation)) if rotation < 0: rotation = rotation + 360 elif rotation > 359: rotation = rotation - 360
The actual playing of stations is handled by mpd and mpc. They will allow you play audio files, streams, etc. from bash. In python that makes it a simple OS call to control the audio.
I've tried to comment my code to make it easy to follow but if you end up with questions, drop me a line. If you have any suggestions on where I could have done better, drop me a line too! I'm completely self-taught so I am always up for learning from those more knowledgeable.