A lightweight Python service that lets external hardware controllers (like microcontrollers or button boxes) send simple playback commands to a Plexamp headless instance.
The control listener reads serial commands from connected devices (e.g., Arduino, RP2040, ESP32) over USB and translates them into Plexamp HTTP API calls — letting you control playback, skip tracks, and adjust volume from physical buttons or other input sources.
- Controls Plexamp headless through its local HTTP API (
http://127.0.0.1:32500) - Listens for commands over a serial interface (
/dev/ttyACM*) - Basic playback controls:
PLAYPAUSENEXTPREVIOUS
- Volume control:
VOL_UPVOL_DOWN
- Automatically reconnects if the serial device is unplugged or lost
- Runs as a systemd service for continuous background operation
- Linux device running Plexamp headless
- Python 3.8 or newer
- USB-connected microcontroller that sends simple newline-terminated serial messages (examples below)
curlavailable on the host (used by the Python script to call local Plexamp endpoints)- Assumes the running user is the default
piuser. Update plexamp-control.service with a different user before install if you are using a different user.
Clone the repository to your Plexamp device (or copy the files there), then run the included installer:
git clone https://github.com/<yourusername>/plexamp-control-listener.git
cd plexamp-control-listener
chmod +x install.sh
./install.shWhat install.sh does:
- Creates a Python virtual environment (if missing).
- Installs
pyserialinto the venv. - Patches the systemd service to use the venv's python.
- Copies the patched service to
/etc/systemd/system/. - Enables and starts
plexamp-control.service.
After install, view logs with:
journalctl -fu plexamp-control.serviceEdit plexamp-control.py to tune behavior and endpoints. Key variables at the top of the file:
BAUD_RATE = 115200
PLEXAMP_HOST = "http://127.0.0.1:32500"
COMMANDS = {
"PLAY": f"curl -s {PLEXAMP_HOST}/player/playback/play",
"PAUSE": f"curl -s {PLEXAMP_HOST}/player/playback/pause",
"NEXT": f"curl -s {PLEXAMP_HOST}/player/playback/skipNext",
"PREVIOUS": f"curl -s {PLEXAMP_HOST}/player/playback/skipPrevious",
}
VOLUME_STEP = 5
MIN_VOLUME = 0
MAX_VOLUME = 100
VOLUME_REFRESH_SEC = 30- The service finds a serial device by scanning
/dev/ttyACM*. - It opens the serial port and reads newline-terminated text lines.
- Each received line (e.g.
PLAY) is mapped to a shellcurlcommand sent to Plexamp's local HTTP endpoint. - Volume commands fetch/update current volume using the Plexamp timeline XML endpoint and call the setParameters API to change volume.
Send any of these (each on its own line):
PLAY
PAUSE
NEXT
PREVIOUS
VOL_UP
VOL_DOWN
import board
import digitalio
import time
#import usb_cdc
#print(dir(board))
# Button pin setup
buttons = {
"PLAY": board.GP27,
"PAUSE": board.GP26,
"NEXT": board.GP15,
"PREVIOUS": board.GP14,
"VOL_UP": board.GP13,
"VOL_DOWN": board.GP12,
}
# Configure pins
button_inputs = {}
button_states = {}
for name, pin in buttons.items():
btn = digitalio.DigitalInOut(pin)
btn.direction = digitalio.Direction.INPUT
btn.pull = digitalio.Pull.UP
button_inputs[name] = btn
button_states[name] = False # debounce state
while True:
for name, btn in button_inputs.items():
if not btn.value and not button_states[name]:
# Button just pressed
print(name)
#usb_cdc.data.write((name + "\n").encode())
button_states[name] = True
elif btn.value and button_states[name]:
# Button released
button_states[name] = False
time.sleep(0.05) # debounce delay
systemctl status plexamp-control.service
sudo journalctl -f -u plexamp-control.service
sudo systemctl restart plexamp-control.service
sudo systemctl disable plexamp-control.service- No serial device found: Check with
ls /dev/ttyACM* /dev/ttyUSB* - Permission errors: Add user to
dialout:sudo usermod -a -G dialout $USER - Volume not changing: Confirm Plexamp is reachable at
PLEXAMP_HOST - Service won't start: Run
sudo journalctl -u plexamp-control.service -xe