Introduction
We wanted an easy way to control a few aspects of our Bluesound Node box. While we could control the volume with the television remote, we could not easily switch inputs. Accessing favorites was limited to the set-top box and is restricted. For example we could not easily select a favorite saved in the Tune-In service.
This controller is simple on purpose. However, the way it's written it can easily be modified and gain expanded functionality. Pretty much if you can craft the URL to the Bluesound API, the sky is the limit!
Before You Begin
You'll need to familiarize yourself with the Bluesound API.
Full disclosure I found this document later in my efforts and only referenced it a little bit.
What I did find are some wonderful libraries with extremely helpful README documents. This is the one I referenced the most.
Once you get the gist of the URL format, it becomes finding exactly what you're looking for. The toughest for me was locating the specific URLs for the Tune-In favorites. Here's where to find them:
http://<IP of your Bluesound Node>:11000/RadioBrowse?service=TuneIn&url=presets
Building the Controller
Notifications and API Infrastructure
In order to be notified when you need to charge the battery, you'll need MQTT.
AdafruitIO would be perfect for this as you would get your MQTT server, dashboard, and notifications handled in one place.
For my project I use a local MQTT server, Mosquitto MQTT that I have running on a Raspberry Pi Zero W.
For handling notifications I'm using a local Node-Red server running on a Raspberry Pi Zero 2 W.
Hardware
You don't need stacking headers for your project. You will see them on my board in the pictures, however, the headers were soldered on for testing out a different project. I did end up using them for the wake from sleep switch, however you could choose to direct solder to the board.
In order to use the Keyboard, the Rotary Encoder, and do battery voltage monitoring you will need to solder either the A0, A1, or A2 pin on the bottom of the Rotary Encoder Breakout Board. The reason is because the default I2C address for battery monitoring on the Feather is 0x36 which also happens to be the default I2C address for the Rotary Encoder Board. It's all explained very well in the Primary Guide.
The Code
The project is available on Github.
CircuitPython Libraries Needed
os
adafruit_connection_manager
adafruit_minimqtt.adafruit_minimqtt
board
socketpool
wifi
time
adafruit_neokey.neokey1x4
adafruit_logging
adafruit_requests
adafruit_requests import OutOfRetries
adafruit_seesaw import digitalio, rotaryio, seesaw
ElementTree
adafruit_led_animation.color
adafruit_max1704x
ElementTree is available on Github. It is provided by Nearadoc.
Code Highlights
I use the testing flag to change logging level to debug and to print the requests and MQTT calls.
Project will publish messages to the MQTT service and will send API requests to the Bluesound Node device.
# Handle all MQTT publish requests
def do_publish(feed, msg):
if testing:
logger.debug(f"Testing: would publish {msg} to {feed}")
else:
try:
my_mqtt.publish(feed, msg)
except MMQTTException:
logger.error("unable to connect to remote MQTT broker, message not sent")
my_mqtt.disconnect()
pass
# Handle all API requests
def send_request(url):
if testing:
logger.debug(f"Would send {url}")
else:
try:
requests.post(url)
except OutOfRetries as e:
logger.error(f"unable to send message: {e}")
pass
Bluesound API calls look like this:
# ---- Bluesound Node ---- #
baseURL = os.getenv("bluesound_baseUrl")
logger.debug(f"base url is {baseURL}")
opticalInput = "Play?url=Capture%3Ahw%3Aimxspdif%2C0%2F1%2F25%2F2%3Fid%3Dinput1&preset_id&image=/images/capture/ic_opticalinput.png"
hdmiInput = "Play?url=Capture%3Ahw%3Aimxspdif%2C0%2F1%2F25%2F2%3Fid%3Dinput2&preset_id&image=/images/capture/ic_hdmi.png"
volumeQuery = "Volume"
volumeChange = "Volume?level="
aloha_joe_play = "Play?url=TuneIn%3As49372&preset_id&image=http://cdn-radiotime-logos.tunein.com/s49372g.png"
aloha_joe_pause = "Pause?url=TuneIn%3As49372&preset_id&image=http://cdn-radiotime-logos.tunein.com/s49372g.png"
When the battery gets below 3.7V a message will be sent to an MQTT feed. Node-Red will subscribe to this feed and when a message is received will send an email alerting me that it's time to put the device on the charger. I set a flag so that this message is only sent once. When the battery reaches a voltage of 4.0 or higher, a battery charged message will be sent and the flag reset.
# Check the voltage of the battery, send a message to MQTT if it's below 3.7V
if batteryCheck is None or time.monotonic() > batteryCheck + batteryCheckWait:
logger.debug("battery check hello")
batteryVoltage, batteryPercentage = monitor_battery()
if batteryVoltage < 3.7 and not batteryWarn:
logger.debug("We need to warn")
batteryWarn = True
do_publish(battery_feed, batteryVoltage)
elif batteryVoltage >= 4.0 and batteryWarn:
logger.debug("We need to reset the warn flag")
do_publish(battery_feed, batteryVoltage)
batteryWarn = False
else:
logger.debug(f"battery warning {batteryVoltage:.2f} Volts, and battery warn is {batteryWarn}")
batteryCheck = time.monotonic()
I added in logic for the companion to exit and sleep until awakened by pressing a momentary switch installed on the side. I need to do this since when the companion is powered up it's kind of lit up like Vegas and the battery just doesn't last like that.
if time.monotonic() > sleeping + wait_to_sleep:
hibernate_alarm = alarm.pin.PinAlarm(pin=nightNight_pin, value=False, edge=False, pull=True)
alarm.exit_and_deep_sleep_until_alarms(hibernate_alarm)
The Housing
The housing is print ready, load the STL file in your slicer of choice and pick a filament.
If you'd like to modify the case you'll need OpenSCAD and this amazing library, YAPP Box.
There is support in the YAPP_Box library to put labels directly on the case. This works really well if you want etched into the case style labels. For ease of reading I wanted raised labels that I could paint. Using the library to do this made printing the case a lot more complicated than it should be. Therefore I opted to create two plates that I will glue to the top of the case. The code to include the etched labels is still in the code, just commented out.
I painted the letters with acrylic paint and seal with Mod Podge Acrylic Sealer.
There are snap joins, one each, on the front, back, left, and right, of the case allowing for a secure fit.
- Filament Used: FlashForge PLA - color: Marsala (base) and Hatch PETG - color: Transparent White
- Bed Temp: 55 and 80
- Nozzle Temp: 220 and 245
- Raft: No
- Brim: No (PLA), Yes (PETG) but only perimeter
- Supports: Yes, only touching plate, should be 4; one under each standoff for the keyboard and maybe manually add one in the cutout for the momentary switch
Putting It All Together
The Base
- Put two UGlu Dashes on the battery, top and bottom.
- Connect the battery to the Feather ESP32-S3.
- Run the battery wire under the standoffs for a cleaner look.
- Using four (4) M2x5 metal screws, secure the Feather to the standoffs.
- Press the battery, with the UGlu Dashes exposed, to the base of the case, over the venting.
The Lid
- Solder one wire for ground and one for data to the momentary switch. Use solid core if you're using a header on the Feather board or stranded if you're soldering directly to the board
- Strip the exposed end of the wires to either to insert into the headers on the board or solder directly to the Feather board
- Secure the momentary switch to the lid using the supplied hardware
- Secure the rotary encoder board to the lid using four (4) M2.5x5 metal screws.
- Secure the keyboard board to the lid using four (4) M2.5x4 metal screws.
- Install the MX keys to the board and the key covers to the MX keys.
- Connect the keyboard to the rotary encoder board using one of the Stemma QT cables.
- Connect the rotary encoder or keyboard to the Feather board using the other Stemma QT cable.
- Connect one lead from the switch to D9 and the other to ground
- Press the reset key on the Feather board once everything is fully assembled
-
(Optional) After painting the raised lettering on the label boards and letting it dry thoroughly, secure the boards to the lid with super glue.
- Connect to the lid to the base and you're good to go.
This page (Bluesound Node Companion) was last updated on March 25, 2026.
Text editor powered by tinymce.