A lightweight Python script for Raspberry Pi that reads NFC tags via a Waveshare PN532 HAT and triggers playback on Plexamp Headless.
Each NFC tag contains a pre-encoded Plexamp playback URL which is resolved and sent to the local Plexamp instance. The listener can run as a standalone NFC-to-Plexamp player, or as the NFC hardware layer for A Clockwork Plex, the touchscreen dashboard for Plexamp, weather, AirPlay and settings.
- Designed for Raspberry Pi 4 or newer with Raspberry Pi OS
- Uses Adafruit's CircuitPython PN532 and Blinka libraries
- Compatible with Plexamp Headless running on
http://localhost:32500 - Full startup automation with systemd and Chromium kiosk mode
- Optional AirPlay receiver setup using Shairport Sync, with Plexamp handover hooks
- Optional integration with A Clockwork Plex so NFC scans switch the dashboard to the embedded Plexamp screen
- Tested on current Raspberry Pi OS releases using Python 3.13, where
python3-lgpioand a venv created with--system-site-packagesare required
Perfect for kiosks, jukeboxes, man caves, bedside music systems, or DIY smart audio stations. Tap an NFC tag to instantly queue and play an album in Plexamp.
With A Clockwork Plex installed as well, the flow becomes:
Scan NFC album tag
β
Read Plexamp playback URL from the tag
β
Convert listen.plex.tv URL to local Plexamp Headless URL
β
Trigger playback on http://localhost:32500
β
Ask A Clockwork Plex to switch the touchscreen display to /plexamp
- Raspberry Pi β Any model with GPIO and internet access. A Raspberry Pi 4 Model B or faster is recommended for smoother Plexamp browsing performance.
- NFC tap-to-play is fast.
- The Plexamp UI is heavier, so slower Pi models can feel sluggish in kiosk mode.
- PN532 NFC HAT β In I2C mode.
- Tested working version: The Pi Hut PN532 NFC HAT
- Hardware documentation and DIP switch guide: Waveshare PN532 NFC HAT Wiki
- Raspberry Pi OS β Current 64-bit Raspberry Pi OS is recommended.
- Chromium browser β Usually preinstalled on Raspberry Pi OS.
- Plex Pass account β Required for Plexamp Headless.
- Plexamp Headless β Installed locally and reachable on
http://localhost:32500. - Python 3.9+ and pip.
- Internet access to install dependencies.
- Optional: Shairport Sync and Avahi for AirPlay receiver support.
- Optional:
A Clockwork Plexfor the touchscreen dashboard, embedded Plexamp page, weather display and hidden navigation drawer.
- Adafruit for the
adafruit-circuitpython-pn532library - Plex for Plexamp and Plexamp Headless
- Waveshare for the PN532 hardware
- The Pi Hut for the tested PN532 NFC HAT listing and documentation links
- Shairport Sync for AirPlay audio receiver support
- Inspiration from tgp-2's Plexamp setup gist
- Prepare the SD card with Raspberry Pi OS using Raspberry Pi Imager.
- Choose the correct Pi version and the 64-bit OS where possible.
- Configure SSH and Wi-Fi during imaging if you want a headless setup.
- Configure the DIP switches on the PN532 HAT for I2C mode using the Waveshare PN532 NFC HAT Wiki.
- Fit the PN532 HAT to the Pi GPIO header.
- Insert the SD card and power on the Pi.
After boot, check that the I2C device is visible:
sudo raspi-config
# Interface Options β I2C β Enable
sudo rebootThen:
ls -l /dev/i2c-1
i2cdetect -y 1When the Waveshare PN532 HAT is correctly configured in I2C mode, you should usually see a device at:
0x24
Once the hardware is set up with a fresh install of Raspberry Pi OS, install Plexamp Headless first, then run the provided setup.sh script to automate the NFC listener and kiosk setup.
This is especially helpful for fresh installs or when you want to get up and running quickly.
The setup.sh script performs these actions:
- Updates the system using
apt update && apt upgrade - Installs required packages:
python3,python3-pip,python3-venvpython3-lgpiogit,i2c-tools,curlchromiumorchromium-browser, depending on Raspberry Pi OS release
- Enables I2C via
raspi-config - Enables SSH via
raspi-config - Adds the current user to useful hardware access groups such as
i2c,gpio, andspi - Clones this GitHub repo to
~/Plexamp-NFC-Listener, or updates it if already cloned - Creates the Python virtual environment using
--system-site-packages - Installs Python dependencies from
requirements.txt - Checks that
lgpio,board, andbusiocan be imported - Creates and enables the
nfc-listener.servicesystemd unit so it runs at boot - Configures Chromium to open Plexamp or the dashboard in full-screen kiosk mode
- Optionally installs Shairport Sync as an AirPlay receiver
Use the community installer from tgp-2. This requires interactive input:
wget https://gist.githubusercontent.com/tgp-2/65e6f2f637bc81df2c9fd9ba33f73bc6/raw/plexamp-install.sh
bash ./plexamp-install.shDuring the installer:
- Paste a Plex claim code from https://plex.tv/claim
- Enter a unique name for your Plexamp player
- Let the installer create/start
plexamp.service
After installation, reboot:
sudo rebootAfter reboot, open Plexamp on the Pi to complete login and configuration:
http://localhost:32500
Download and run the setup script:
wget https://raw.githubusercontent.com/AndyBettger/Plexamp-NFC-Listener/main/setup.sh
bash setup.sh
sudo rebootThe installer asks whether to install AirPlay support. If enabled during an interactive run, it also lists detected ALSA playback devices and asks which one Shairport Sync should use. Pressing Enter accepts the recommended device.
To run non-interactively with AirPlay enabled:
INSTALL_AIRPLAY=yes AIRPLAY_NAME="Plexamp Bedroom" bash setup.sh
sudo rebootYou can also override the ALSA output device used by Shairport Sync:
INSTALL_AIRPLAY=yes AIRPLAY_NAME="Plexamp Bedroom" AIRPLAY_OUTPUT_DEVICE="plughw:CARD=Pro,DEV=0" bash setup.shA Clockwork Plex is the touchscreen dashboard that sits around Plexamp. It provides:
- Clock screen
- Detailed weather screen
- Embedded Plexamp iframe page
- Hidden bottom navigation drawer with swipe/tap handle
- AirPlay active screen
- Settings page
When A Clockwork Plex is installed on the same Pi, the NFC listener will try to run this helper after successful playback:
/home/andy/A-Clockwork-Plex/scripts/nfc-plexamp-mode.sh
That helper switches the dashboard to Plexamp mode. If xdotool is not installed, A Clockwork Plex still has a browser-side mode watcher, so the currently open dashboard page can notice the mode change and move itself to /plexamp.
By default, the listener expects:
/home/andy/A-Clockwork-Plex
You can override the display switch command with an environment variable in the systemd service:
Environment=PLEXAMP_DISPLAY_SWITCH_COMMAND=/path/to/nfc-plexamp-mode.shIf the helper does not exist, the listener falls back to this dashboard API call:
http://localhost:8088/api/mode/plexamp
You can override that too:
Environment=PLEXAMP_DASHBOARD_MODE_URL=http://localhost:8088/api/mode/plexampThen reload and restart the service:
sudo systemctl daemon-reload
sudo systemctl restart nfc-listener.serviceAirPlay support is provided by Shairport Sync. Because Plexamp Headless and Shairport Sync can both want the same audio device, this project uses handover hooks instead of letting both services fight for the output.
The handover flow is:
AirPlay starts
β
Pause Plexamp via http://localhost:32500/player/playback/pause
β
Stop plexamp.service so the audio device is released
β
Shairport Sync plays the AirPlay stream
β
AirPlay ends
β
Start plexamp.service again
This avoids the common problem where AirPlay appears to connect but no audio plays because Plexamp still has hold of the DAC/audio output.
On Raspberry Pi systems with HDMI plus an audio HAT, the ALSA default device may point to HDMI rather than the DAC. During interactive setup, the script lists detected playback devices and lets you choose one by number.
For example, a Raspberry Pi with a DAC Pro may show something like:
1) plughw:CARD=vc4hdmi0,DEV=0 β vc4-hdmi-0 - MAI PCM i2s-hifi-0
2) plughw:CARD=vc4hdmi1,DEV=0 β vc4-hdmi-1 - MAI PCM i2s-hifi-0
3) plughw:CARD=Pro,DEV=0 β RPi DAC Pro - Raspberry Pi DAC Pro HiFi pcm512x-hifi-0 β recommended
4) default β Default ALSA device
C) Custom ALSA device string
For a Raspberry Pi DAC Pro, choose:
plughw:CARD=Pro,DEV=0
The plughw form is preferred because it targets the actual hardware device while still allowing ALSA to handle useful software conversion.
The setup script creates these helper scripts:
/usr/local/bin/plexamp-airplay-start
/usr/local/bin/plexamp-airplay-stop
It also configures Shairport Sync session hooks in:
/etc/shairport-sync.conf
and backs up any existing config first.
Use this method if you prefer not to run the automated setup script, or if you need to troubleshoot each stage manually.
Prepare Raspberry Pi OS with Raspberry Pi Imager, enable SSH if required, and configure the PN532 HAT DIP switches for I2C mode using the Waveshare PN532 NFC HAT Wiki.
sudo raspi-config
# Interface Options β SSH β Enable
# Interface Options β I2C β Enable
sudo rebootsudo apt update && sudo apt upgrade -yUse the installer provided by tgp-2:
wget https://gist.githubusercontent.com/tgp-2/65e6f2f637bc81df2c9fd9ba33f73bc6/raw/79dfa75db81be185bcc84faa54b38604b185a619/plexamp-install.sh
bash ./plexamp-install.shDuring the installer:
- Enter the Plex claim code from https://plex.tv/claim
- Enter a unique name for your Plexamp player
- Allow the installer to create
plexamp.service
After install completes, reboot:
sudo rebootAfter reboot, open this in Chromium on the Raspberry Pi to complete Plexamp login and configuration:
http://localhost:32500
sudo apt install -y python3 python3-pip python3-venv python3-lgpio git i2c-tools curl
sudo apt install -y chromium || sudo apt install -y chromium-browser
sudo usermod -aG i2c,gpio,spi "$USER"python3-lgpio is required by Adafruit Blinka on newer Raspberry Pi OS releases. The virtual environment should be created with access to system site packages so the lgpio module can be imported.
Log out and back in, or reboot, after changing groups.
cd ~
git clone https://github.com/AndyBettger/Plexamp-NFC-Listener.git
cd Plexamp-NFC-Listener
python3 -m venv venv --system-site-packages
source venv/bin/activate
python -m pip install --upgrade pip setuptools wheel
python -m pip install -r requirements.txtCheck the key hardware imports:
python - <<'PY'
import lgpio
import board
import busio
print("lgpio OK")
print("board/busio OK")
PYls -l /dev/i2c-1
i2cdetect -y 1When the Waveshare PN532 HAT is in I2C mode, you should usually see a device at 0x24.
cd ~/Plexamp-NFC-Listener
source venv/bin/activate
python nfc_listener.pyScan a Plexamp NFC tag. A successful scan should show output similar to:
π― Parsed Tag URL:
https://listen.plex.tv/player/playback/playMedia?uri=...
π Converted to local: http://localhost:32500/player/playback/playMedia?uri=...
β
Playback triggered!
π₯οΈ Dashboard switched to Plexamp mode.
If A Clockwork Plex is not installed, playback can still work; you may simply see a warning that the dashboard switch could not be performed.
For standalone Plexamp kiosk mode, open Plexamp directly:
http://localhost:32500
For A Clockwork Plex integration, point Chromium at the dashboard instead:
http://localhost:8088/clock
Current Raspberry Pi OS releases use labwc/Wayland. Create or edit:
mkdir -p ~/.config/labwc
nano ~/.config/labwc/autostartFor standalone Plexamp:
sleep 10
chromium --kiosk --start-maximized --noerrdialogs --disable-infobars --no-first-run "http://localhost:32500" &For A Clockwork Plex:
sleep 10
chromium --kiosk --start-maximized --noerrdialogs --disable-infobars --no-first-run "http://localhost:8088/clock" &For older X11/LXDE releases, use the legacy autostart file instead:
mkdir -p ~/.config/autostart
nano ~/.config/autostart/kiosk.desktopStandalone Plexamp example:
[Desktop Entry]
Type=Application
Name=Plexamp Kiosk
Exec=bash -c 'sleep 10 && chromium --kiosk --start-maximized --noerrdialogs --disable-infobars --no-first-run "http://localhost:32500"'
X-GNOME-Autostart-enabled=trueA Clockwork Plex example:
[Desktop Entry]
Type=Application
Name=A Clockwork Plex Kiosk
Exec=bash -c 'sleep 10 && chromium --kiosk --start-maximized --noerrdialogs --disable-infobars --no-first-run "http://localhost:8088/clock"'
X-GNOME-Autostart-enabled=trueIf your OS only provides the old command name, replace chromium with chromium-browser.
sudo apt install -y shairport-sync avahi-daemon alsa-utils curl sudoList available playback devices:
aplay -l
aplay -LChoose the ALSA output device that points to your DAC or audio HAT. For example, the Raspberry Pi DAC Pro is commonly:
plughw:CARD=Pro,DEV=0
Create the AirPlay start hook:
sudo tee /usr/local/bin/plexamp-airplay-start >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
curl --silent --fail --max-time 2 "http://localhost:32500/player/playback/pause" >/dev/null 2>&1 || true
sleep 1
sudo systemctl stop plexamp.service >/dev/null 2>&1 || true
EOFCreate the AirPlay stop hook:
sudo tee /usr/local/bin/plexamp-airplay-stop >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
sudo systemctl start plexamp.service >/dev/null 2>&1 || true
EOFMake both hooks executable:
sudo chmod +x /usr/local/bin/plexamp-airplay-start /usr/local/bin/plexamp-airplay-stopAllow the shairport-sync user to start and stop only the Plexamp service:
SYSTEMCTL_CMD="$(command -v systemctl)"
printf 'shairport-sync ALL=(root) NOPASSWD: %s stop plexamp.service, %s start plexamp.service\n' "$SYSTEMCTL_CMD" "$SYSTEMCTL_CMD" | sudo tee /etc/sudoers.d/shairport-sync-plexamp
sudo chmod 0440 /etc/sudoers.d/shairport-sync-plexamp
sudo visudo -cf /etc/sudoers.d/shairport-sync-plexampBack up and write the Shairport Sync config:
sudo cp /etc/shairport-sync.conf "/etc/shairport-sync.conf.backup.$(date +%Y%m%d-%H%M%S)"
sudo tee /etc/shairport-sync.conf >/dev/null <<'EOF'
general = {
name = "Plexamp Bedroom";
output_backend = "alsa";
};
sessioncontrol = {
run_this_before_play_begins = "/usr/local/bin/plexamp-airplay-start";
run_this_after_play_ends = "/usr/local/bin/plexamp-airplay-stop";
active_state_timeout = 10.0;
wait_for_completion = "yes";
};
alsa = {
output_device = "plughw:CARD=Pro,DEV=0";
};
EOFReplace plughw:CARD=Pro,DEV=0 with the device chosen from aplay -L if your DAC uses a different ALSA name.
Enable and restart the services:
sudo systemctl enable avahi-daemon
sudo systemctl restart avahi-daemon
sudo systemctl enable shairport-sync
sudo systemctl restart shairport-sync
sudo systemctl start plexamp.servicecd ~/Plexamp-NFC-Listener
sudo cp nfc-listener.service /etc/systemd/system/nfc-listener.service
sudo systemctl daemon-reload
sudo systemctl enable nfc-listener.service
sudo systemctl restart nfc-listener.serviceCheck status:
systemctl status nfc-listener.service --no-pagerView live logs:
journalctl -u nfc-listener.service -fThe listener expects tags written by the Plexamp iPhone app or equivalent, containing a URL beginning with:
https://listen.plex.tv/player/playback/playMedia?uri=
The script converts that to the local Plexamp Headless endpoint:
http://localhost:32500/player/playback/playMedia?uri=
curl "http://localhost:32500/player/playback/play"
curl "http://localhost:32500/player/playback/pause"
curl "http://localhost:32500/player/playback/playPause"
curl "http://localhost:32500/player/playback/stop"On newer Raspberry Pi OS releases, rebuild the venv using system site packages:
cd ~/Plexamp-NFC-Listener
deactivate 2>/dev/null || true
rm -rf venv
sudo apt update
sudo apt install -y python3-venv python3-lgpio i2c-tools
python3 -m venv venv --system-site-packages
source venv/bin/activate
python -m pip install --upgrade pip setuptools wheel
python -m pip install -r requirements.txtThen test:
python - <<'PY'
import lgpio
import board
import busio
print("lgpio OK")
print("board/busio OK")
PYCheck I2C is enabled and the HAT DIP switches are set for I2C mode:
sudo raspi-config
ls -l /dev/i2c-1
i2cdetect -y 1Also re-check the Waveshare PN532 NFC HAT Wiki for the correct DIP switch positions.
Confirm Plexamp Headless is running:
systemctl status plexamp.service --no-pager
curl "http://localhost:32500/player/playback/playPause"Check the NFC listener logs:
journalctl -u nfc-listener.service -fIf you see:
xdotool is not installed; mode state was updated but browser was not navigated.
that is usually fine with the current A Clockwork Plex dashboard, because the browser-side mode watcher should notice the new mode and navigate itself. Make sure A Clockwork Plex has been pulled/restarted and the browser page has been refreshed once.
You can test the dashboard mode endpoint manually:
curl -X POST http://localhost:8088/api/mode/plexampReset the failed service state and inspect the logs:
sudo systemctl reset-failed nfc-listener.service
sudo systemctl restart nfc-listener.service
sudo journalctl -u nfc-listener.service -b -n 100 --no-pagerFor current Raspberry Pi OS releases, check:
cat ~/.config/labwc/autostart
command -v chromiumFor older releases, check:
cat ~/.config/autostart/kiosk.desktop
command -v chromium-browserCheck Shairport Sync and Avahi:
systemctl status shairport-sync
systemctl status avahi-daemon
journalctl -u shairport-sync -b -n 100 --no-pagerMake sure the phone, Mac, or iPad is on the same network/VLAN as the Pi, and that mDNS/Bonjour traffic is not blocked.
First confirm the selected ALSA device points to the real audio output, not HDMI:
aplay -l
aplay -L
cat /etc/shairport-sync.conf | grep -A3 '^alsa'On a Raspberry Pi DAC Pro, the Shairport Sync output should usually be:
plughw:CARD=Pro,DEV=0
You can test the DAC directly with:
sudo systemctl stop shairport-sync
sudo systemctl stop plexamp.service
speaker-test -D plughw:CARD=Pro,DEV=0 -c 2 -t wavCheck that Plexamp is being stopped when AirPlay begins:
journalctl -u shairport-sync -b -n 100 --no-pager
systemctl status plexamp.serviceYou can manually test the hooks:
/usr/local/bin/plexamp-airplay-start
systemctl status plexamp.service
/usr/local/bin/plexamp-airplay-stop
systemctl status plexamp.serviceIf the DAC uses a different ALSA device, rerun setup interactively and select the correct output, or force it with:
INSTALL_AIRPLAY=yes AIRPLAY_OUTPUT_DEVICE="plughw:CARD=Pro,DEV=0" bash setup.shcd ~/Plexamp-NFC-Listener
git pull
python -m py_compile nfc_listener.py
sudo systemctl restart nfc-listener.serviceScan an NFC tag written with a Plexamp sharing link and enjoy physical control of your music.
This project is licensed under the MIT License.