制造商零件编号 DFR0675-EN
STEREOBLUETOOTHAMPLIFIERBOARD
DFRobot
License: See Original Project Amplifiers Barrel Audio Connectors Raspberry Pi SBC
Last summer I purchased an antique radio from a local auction sale for only a few dollars. The radio is a Stewart Warner Model 205CK that I believe was made around 1941. After bringing home the radio and evaluating its restoration potential, I had an idea: what if I retrofit it with a Raspberry Pi to turn it into an internet radio? I had a few different Raspberry Pis sitting around unused, so I thought this might be an interesting project.
The first idea I had was to carefully remove the internal electronics from the original build and then see about placing the new electronics into the case. Prior to doing this, however, I remembered I had a small amplifier board, 1738-DFR0675-EN-ND, that I could pull from another project to do some testing. The original speaker in the cabinet had an interesting set of connections that I hadn’t seen before, I believe they are just referred to as spring clips.
These made it easy to evaluate to see if a small amplifier like this would provide enough power for the speaker. After plugging in the amplifier and powering it with a 12V battery, the sound was pretty good, just not as loud as I thought it would be. To further investigate the project, I used my multimeter to measure the speaker's impedance. I found that it was reading approximately 7 ohms. While it may not be a perfect match for the amp, I believe it will likely work, as the amp has specifications for both 4-ohm and 8-ohm speaker configurations. During the amplifier test, I noticed how great the speaker sounded. The music, while not overly loud, filled the room with a warm and pleasant tone that was enjoyable to listen to. I was truly impressed by this.
Now that I had the initial testing out of the way, I decided the next step was to figure out which Raspberry Pi I wanted to use. I first started to think about the Raspberry Pi Zero 2 W that I hadn’t used in a project yet. I decided to create some test code to see if I could make it work.
While I’ve done some programming with a Raspberry Pi, I thought this project would be an interesting test case to see what using ChatGPT and Claude could do to help me speed up my coding time. I should state that I did use the free version of each of these LLM’s to create this project.
To start, I went to Claude’s website and posted the following Prompt.
You are an experienced Python programmer that rarely makes mistakes. I am working on creating an antique radio into an internet radio using a Raspberry Pi zero 2w, an Pimoroni Audio DAC, and a Hi-Fi Bluetooth radio receiver from DFRobot. Can you help me with the code to get this up and running? I would like for it to be an internet radio along with an Airplay receiver as well.
In my time looking at various prompt engineering videos, I’ve heard from several people that if you give a role to the LLM, it will provide a better, more accurate output.
For the initial test of the code, I decided to try to run the sound through my Bluetooth headphones connected to the Pi instead of the speaker. I figured this would be an easy way to test the code prior to setting up all the hardware.
After connecting my headphones to the Pi with Bluetooth, I copied and pasted the first batch of code that Claude sent over.
import subprocess
import os
import signal
import time
from threading import Thread
import vlc
class VintageRadio:
def init(self):
# Initialize audio components
self.current_source = None
self.player = None
self.airplay_process = None
self.radio_stations = {
'1': 'http://media-ice.musicradio.com/ClassicFMMP3',
'2': 'http://icecast.vrtcdn.be/radio1-high.mp3',
'3': 'http://stream.live.vc.bbcmedia.co.uk/bbc_radio_two'
}
# Set up audio output to Pimoroni DAC
os.environ['ALSA_CARD'] = 'sndrPihifiberry'
def start_airplay(self):
"""Start Shairport-sync for AirPlay receiving"""
if self.airplay_process is None:
try:
self.stop_radio()
self.airplay_process = subprocess.Popen(
['shairport-sync', '-o', 'alsa', '--name', 'Vintage Radio'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
self.current_source = 'airplay'
print("AirPlay receiver started")
except Exception as e:
print(f"Error starting AirPlay: {e}")
def stop_airplay(self):
"""Stop AirPlay receiver"""
if self.airplay_process:
self.airplay_process.terminate()
self.airplay_process = None
self.current_source = None
def play_radio(self, station_number):
"""Play internet radio station"""
if station_number in self.radio_stations:
try:
self.stop_airplay()
if self.player:
self.player.stop()
# Create a new VLC instance
instance = vlc.Instance('--aout=alsa')
self.player = instance.media_player_new()
media = instance.media_new(self.radio_stations[station_number])
self.player.set_media(media)
self.player.play()
self.current_source = 'radio'
print(f"Playing radio station {station_number}")
except Exception as e:
print(f"Error playing radio: {e}")
def stop_radio(self):
"""Stop radio playback"""
if self.player:
self.player.stop()
self.player = None
self.current_source = None
def setup_bluetooth(self):
"""Initialize Bluetooth receiver"""
try:
# Enable Bluetooth audio sink
subprocess.run(['sudo', 'bluetoothctl', 'discoverable', 'on'])
subprocess.run(['sudo', 'bluetoothctl', 'pairable', 'on'])
subprocess.run(['sudo', 'bluetoothctl', 'agent', 'NoInputNoOutput'])
print("Bluetooth receiver ready")
except Exception as e:
print(f"Error setting up Bluetooth: {e}")
def cleanup(self):
"""Clean up all audio processes"""
self.stop_radio()
self.stop_airplay()
def main():
radio = VintageRadio()
radio.setup_bluetooth()
while True:
print("\nVintage Radio Control")
print("1. Play Radio Station 1 (Classic FM)")
print("2. Play Radio Station 2 (Radio 1)")
print("3. Play Radio Station 3 (BBC Radio 2)")
print("4. Start AirPlay Receiver")
print("5. Stop All")
print("6. Exit")
choice = input("Select an option: ")
if choice in ['1', '2', '3']:
radio.play_radio(choice)
elif choice == '4':
radio.start_airplay()
elif choice == '5':
radio.cleanup()
elif choice == '6':
radio.cleanup()
break
if name == "__main__":
main()
Prior to running the code, Claude also mentioned that I needed to update the Pi as well as install some other packages that weren’t included.
These are the commands that it suggested
sudo apt-get update
sudo apt-get install python3-vlc shairport-sync bluetooth bluez-tools
# Add this to /boot/config.txt
dtoverlay=hifiberry-dac
# Edit /etc/bluetooth/main.conf
# Add or modify:
Class = 0x200414
Enable = Source,Sink,Media,Socket
chmod +x radio_script.py
After implementing all these changes, I tried testing out the code by running the Python script. It seemed like it was working correctly, however, I was unable to get any sound to play through my headphones.
The next prompt I sent to Claude was:
I haven’t integrated the DFRobot Bluetooth amp or the Pimoroni DAC, could you rewrite the code so that I can test the radio over Bluetooth to some headphones.
After a few short moments, Claude gave me the next batch of code. I saved the new code and then ran it again only to find out I was still having issues with the sound working. At that point, I decided to do some testing on the Pi itself. I hooked up a screen and a keyboard and mouse to the Pi, however at this point I’m not exactly sure what I did but the Pi would no longer power up. After trying several things, I resorted to changing to a Pi 4B instead of the Zero 2 W. I figured this would help in the long run as I would need fewer extra parts to get the radio up and running. The Pi 4B had an Aux output on it, so I could directly connect between the Pi and the Bluetooth amp that I was looking at using.
After setting up the new PI, I first tried connecting my headphones via Bluetooth and tried playing some YouTube videos. The sound came through the headphones loud and clear.
Being that had worked, I went back to Claude and entered the following prompt:
Can you remove all of the Bluetooth things from the code? In testing the Bluetooth portion of the Pi is working correctly already without modification.
Also, can you change the radio station dictionary to this '1': {'name': 'Lofi 1', 'url': 'http://192.95.39.65:5607'}, '2': {'name': 'Lofi 2', 'url': 'https://cast.magicstreams.gr:9125'}, '3': {'name': 'Lofi 3', 'url': 'http://5.39.82.157:8054'}
Claude gave me another rendition of the code.
This new code worked perfectly. I was able to switch between stations as well as stop and restart the radio by entering the numbers for each option in the terminal.
After getting this part of the code working, I attempted to get an airplay function set up as well. Also, at this point, I had to switch from Claude to ChatGPT as I had hit my daily limit for requests. I copied the code into ChatGPT and then asked if it could add the airplay receiver portion to the code. After much frustration and effort in trying to get it to work, I decided to stop working on that portion of the code and try to get some other ideas working.
The next idea I had for this build was to add a sound effect like an old-time radio tuning between stations each time the station was changed. I asked ChatGPT if it was possible to add some sort of sound effect between stations playing and it said that indeed it should be. It then instructed me to search the web for a royalty-free sound effect of “radio tuning noise” or “AM radio static”. After searching the web, I was able to come across an MP3 that sounded very close to what I originally had in mind.
The next step that was given was to install pygame on the Pi by running this command
Pip install pygame
After downloading the .mp3 file onto the Pi, I then asked how I could go about adding a random 5-second clip of the .mp3 after a station had been selected. It then updated the code for me, and I saved it to the Pi. The first few renditions of the code were close to what I was expecting but not quite right. The first kept playing the full one-minute plus .mp3 and the second rendition did play for 5 seconds as I wanted, however, it didn’t fade in/out like I was hoping. I then asked if it would be able to add a fade in/out sequence to the code. It added the correct fade out and in, but there was a half-second dead spot between the two. I then asked if it was able to overlap the sound clip and the radio station a little bit so that it was more of a smooth transition. Once this prompt was sent, I received the code that would ultimately end up in the final build. It did everything I needed it to and faded in and out exactly like I wanted. After getting the code, I updated and added some new stations to the station dictionary.
import pygame
import time
import random
import vlc
import subprocess
import logging
from threading import Thread
from tyPing import Optional, Dict
# Set up logging with more detailed format
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class InternetRadio:
def init(self):
# Initialize pygame mixer with higher quality settings
pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=4096)
self.player: Optional[vlc.MediaPlayer] = None
self.airplay_process: Optional[subprocess.Popen] = None
self.current_source: Optional[str] = None
self.current_station: Optional[str] = None
self.vlc_volume = 0 # Track VLC player volume
# Enhanced radio stations dictionary with metadata
self.radio_stations: Dict[str, dict] = {
'1': {
'name': 'Lofi 1',
'url': 'http://192.95.39.65:5607',
'description': 'Relaxing lofi beats'
},
'2': {
'name': 'MPR News',
'url': 'https://nis.stream.publicradio.org/nis.mp3',
'description': 'Minnesota Public Radio News'
},
'3': {
'name': 'Lofi 3',
'url': 'http://5.39.82.157:8054',
'description': 'Study beats and ambient sounds'
},
'4': {
'name': 'Minnesota Jazz Tracks',
'url': 'http://kbem-live.streamguys1.com/jazztracks_mp3',
'description': 'The Jazz of 10,000 Lakes'
},
'5': {
'name': 'Swing Street Radio',
'url': 'http://s6.voscast.com:11312',
'description': 'Swing Music'
},
'6': {
'name': 'Swing City Radio',
'url': 'http://bluford.torontocast.com:8006/stream',
'description': 'Catch your favorites of the 20s 30s and 40s'
},
'7': {
'name': 'Yesterday USA - Blue',
'url': 'https://media.classicairwaves.com:8018/stream',
'description': 'Welcome to Yesterday USA, where Old Time Radio Programs rule the day. For more than 37 years we have been playing classic old time radio shows hosted by volunteers who are no>
},
}
def list_stations(self) -> None:
"""Display all available radio stations with their descriptions"""
print("\n=== Available Radio Stations ===")
for number, station in self.radio_stations.items():
print(f"{number}. {station['name']} - {station['description']}")
print("=" * 30 + "\n")
def play_radio(self, station_number: str) -> None:
"""
Play internet radio station with smooth transition effects
"""
if station_number not in self.radio_stations:
logger.error(f"Invalid station number: {station_number}")
print("Invalid station number. Please try again.")
return
try:
self.stop_airplay()
self.current_station = station_number
# Create new VLC instance with initial volume at 0
instance = vlc.Instance('--verbose=2')
if self.player:
self.player.stop()
self.player = instance.media_player_new()
url = self.radio_stations[station_number]['url']
media = instance.media_new(url)
self.player.set_media(media)
# Start radio playback at 0 volume
self.player.audio_set_volume(0)
self.player.play()
# Start transition effect in a separate thread
Thread(target=self._transition_audio).start()
except Exception as e:
logger.error(f"Error playing radio: {e}")
print(f"Error playing radio station. Please try again.")
def transitionaudio(self) -> None:
"""
Handle the smooth transition between tuning noise and radio station
"""
try:
# Play tuning noise
pygame.mixer.music.load('radio_tuning.mp3')
pygame.mixer.music.play()
pygame.mixer.music.set_volume(1.0)
# Wait for a moment with full tuning noise
time.sleep(1.5)
# Crossfade between tuning noise and radio
steps = 50 # More steps for smoother transition
fade_duration = 3.0 # Longer fade for more vintage feel
step_time = fade_duration / steps
for i in range(steps + 1):
# Calculate volumes using sine wave for smooth transition
tuning_volume = cos_volume = (steps - i) / steps
radio_volume = sin_volume = i / steps
# Update volumes
pygame.mixer.music.set_volume(tuning_volume)
self.player.audio_set_volume(int(radio_volume * 100))
time.sleep(step_time)
# Ensure tuning noise is fully stopped
pygame.mixer.music.stop()
except Exception as e:
logger.error(f"Error during audio transition: {e}")
def start_airplay(self) -> None:
"""Start Shairport-sync for AirPlay receiving"""
if self.airplay_process is None:
try:
self.stop_radio()
self.airplay_process = subprocess.Popen(
['shairport-sync', '-o', 'alsa', '--name', 'Vintage Radio'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
self.current_source = 'airplay'
logger.info("AirPlay receiver started successfully")
print("AirPlay receiver is now active")
except Exception as e:
logger.error(f"Error starting AirPlay: {e}")
print("Failed to start AirPlay receiver. Please check if shairport-sync is installed.")
def stop_airplay(self) -> None:
"""Stop AirPlay receiver"""
if self.airplay_process:
self.airplay_process.terminate()
self.airplay_process = None
self.current_source = None
logger.info("AirPlay receiver stopped")
print("AirPlay receiver stopped.")
def stop_radio(self) -> None:
"""Stop radio playback with fade out effect"""
if self.player:
# Fade out radio
current_volume = self.player.audio_get_volume()
steps = 20
for i in range(steps):
volume = int(current_volume * (steps - i) / steps)
self.player.audio_set_volume(volume)
time.sleep(0.05)
self.player.stop()
self.player = None
self.current_station = None
logger.info("Radio playback stopped")
print("Radio playback stopped.")
def get_status(self) -> str:
"""Get detailed current status of the radio"""
status = "\n=== Radio Status ===\n"
if self.current_source == 'airplay':
status += "Current mode: AirPlay\n"
elif self.current_station:
station = self.radio_stations[self.current_station]
status += f"Current mode: Radio\n"
status += f"Playing: {station['name']} - {station['description']}\n"
else:
status += "Current mode: Idle\n"
return status + "=" * 20 + "\n"
def main():
radio = InternetRadio()
while True:
print("\n=== Vintage Internet Radio Control ===")
print("1. List Radio Stations")
print("2. Play Radio Station")
print("3. Start AirPlay Receiver")
print("4. Stop Playback")
print("5. Show Status")
print("6. Exit")
print("=" * 35)
try:
choice = input("\nSelect an option (1-6): ").strip()
if choice == '1':
radio.list_stations()
elif choice == '2':
radio.list_stations()
station = input("Enter station number to play: ").strip()
radio.play_radio(station)
elif choice == '3':
radio.start_airplay()
elif choice == '4':
radio.stop_radio()
radio.stop_airplay()
elif choice == '5':
print(radio.get_status())
elif choice == '6':
print("\nStopPing all playback...")
radio.stop_radio()
radio.stop_airplay()
print("Goodbye!")
break
else:
print("Invalid option, please enter a number between 1 and 6.")
except KeyboardInterrupt:
print("\nShutting down gracefully...")
radio.stop_radio()
radio.stop_airplay()
break
except Exception as e:
logger.error(f"Unexpected error: {e}")
print("An error occurred. Please try again.")
if name == "__main__":
main()
At this point, I had saved 8 different renditions of the code to the pi, so that I could revert to older ones if I ran into issues. After getting the main controls working, the next task I asked ChatGPT was if it could set up a local webpage that I would be able to control the Pi from. It then set off creating a Flask page that would handle the controls. Prior to this build, I had never heard of Flask, so I had to do a bit of research to learn more about it. Luckily, we already have a few projects on Maker.io related to Flask. After a short time, ChatGPT gave me the following bit of code.
from flask import Flask, render_template, request, jsonify
import threading
from radio8_setup import InternetRadio # Ensure your existing script is named properly
app = Flask(__name__)
radio = InternetRadio()
@app.route('/')
def index():
return render_template('index.html', stations=radio.radio_stations)
@app.route('/play/<station_number>', methods=['POST'])
def play_station(station_number):
threading.Thread(target=radio.play_radio, args=(station_number,)).start()
return jsonify({"status": "playing", "station": station_number})
@app.route('/stop', methods=['POST'])
def stop_radio():
radio.stop_radio()
return jsonify({"status": "stopped"})
@app.route('/volume', methods=['POST'])
def set_volume():
volume = int(request.form['volume'])
if radio.player:
radio.player.audio_set_volume(volume)
return jsonify({"status": "volume_set", "volume": volume})
if name == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
For the Flask program to work, I also needed to have an index.html file put together. After prompting ChatGPT, here is the code that it gave me. I updated the background image to one I found online.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Antique Radio Controller</title>
<style>
body {
background-image: url('https://wallpapercave.com/wp/wp8461192.jpg');
background-size: cover;
text-align: center;
font-family: Arial, sans-serif;
color: white;
}
.button {
display: block;
width: 200px;
margin: 10px auto;
padding: 10px;
background-color: brown;
color: white;
border: none;
cursor: pointer;
}
.slider {
width: 50%;
margin: 20px auto;
}
</style>
</head>
<body>
<h1>Antique Radio Controller</h1>
{% for key, station in stations.items() %}
<button class="button" onclick="playStation('{{ key }}')">{{ station.name }}</button>
{% endfor %}
<br>
<input type="range" min="0" max="100" class="slider" oninput="setVolume(this.value)">
<br>
<button class="button" onclick="stopRadio()">Stop</button>
<script>
function playStation(station) {
fetch(`/play/${station}`, { method: 'POST' });
}
function stopRadio() {
fetch('/stop', { method: 'POST' });
}
function setVolume(volume) {
fetch('/volume', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: volume=${volume}
});
}
</script>
</body>
</html>
Now that the Flask page was set up and the Python script was completed, the last task I had was to find out which parts I wanted to use in the final build. I started looking at the DigiKey website for another audio amp that would be rather low-cost and possibly have a couple of different options for how the media is delivered. I discovered a HiFi Bluetooth amplifier from DFRobot, part 1738-DFR0803-ND, which had everything I was hoping to find. It had options for both Bluetooth and auxiliary inputs. This gave me the ability to connect to the radio over Bluetooth instead of Airplay or via the internet radio that I was building via the aux input. This was the perfect choice for this build.
After selecting the amplifier, I wanted to use, I looked for a short audio plug-to-plug (aux cable) to connect the Raspberry Pi to the amplifier. I found part number TL1621-ND, which is 1 foot long.
After getting these parts, I then dove into the build itself. My original thought was to take out the original internals to the radio, but after seeing what I would need to get this to work, I decided that I would keep the radio as original as possible with the internals intact. I figured that way if someone later wanted to restore the radio, everything was still in its original place.
To start the hardware installation, I first looked for a Raspberry Pi 4 case suitable for a VESA mount on Printables and began 3D printing it. Next, I found a case for the amplifier, which was listed as the Wuzhi ZK-502C case, also on Printables. I then took that file and added a few mounting tabs in the slicer software I was using. My plan was to mount the case to the bottom of the shelf below the Raspberry Pi, as I thought this would be the simplest solution. After getting both pieces printed, I dove into the assembly. There was already a passthrough hole drilled into the wooden shelf at the back of the radio, so I decided to screw the Raspberry Pi case into the shelf next to this hole. Directly beneath it, I mounted the Hi-Fi amplifier close enough for the short aux cable I found to reach. From there I ran the speaker wire from the amp down to the speaker and connected both positive and negative wires to their respective terminals. Lastly, I ran the power supplies for both the amp and the Pi down to the bottom of the cabinet. From there I attached both power supplies to a dual connection extension cord so that I would only have to plug the one cord into the wall to power both devices.
At this stage, the complete build was assembled and sounded fantastic. However, I felt that something was still missing from the front. While the project looked impressive overall, the display seemed to lack adequate backlighting. Initially, I considered using an LED strip for the backlighting, but after assessing the limited space available, I decided to explore other options. I remembered that Adafruit had flexible LED filaments that they refer to NOODS. I found part 1528-5504-ND which I thought would look pretty good. I connected the setup to my benchtop power supply to determine the appropriate resistor size for achieving the desired level of backlighting. After conducting a series of tests, including positioning the filament within the radio below the display, I discovered that a 4.5-ohm resistor provided the perfect color I was aiming for at 3.3V. The reason I wanted to adjust it for that voltage is so I could directly connect it to the 3.3V pin on the Raspberry Pi for power. After getting the LED filament wired and put into place, I realized I needed something to hold it there. Instead of using glue to adhere it, I resorted to 3D printing a long rectangular piece that would just wedge it in place. That way it could be easily removed if needed.
I should mention that I downloaded an app called Wuzhi Audio on my phone. This app allows me to easily switch between Bluetooth and Aux input for the amplifier. This feature makes it even easier than I imagined to play songs from my phone on the radio.
Here is the reveal of the final product and how it turned out.
This is how the web control interface looks:
And here is a short video of it in action:
Overall, this build was a lot of fun, and I was able to test the viability of using LLM’s to help with my coding. I would say that looking back on this project, LLM’s are great at getting the groundwork of a project. It helped get the project 80% of the way completed and it needed guidance along the way to get it there, but it for sure helped speed up my coding.