Skip to content

FPP Single Board Show - Dual AP mode (listener + show control) for Falcon Player

License

Notifications You must be signed in to change notification settings

UndocEng/fpp-sbs-plus

Repository files navigation

FPP Single Board Show Plus (SBS+)

Why a Single Board Show?

With the rise of WLED and other smart WiFi enabled LED controllers that have built-in support for receiving E1.31 or DDP data over WiFi, it's becoming practical to run a small holiday light show entirely from a single Raspberry Pi. Instead of needing dedicated show controllers and proprietary hardware, a few WLED-flashed ESP32 devices and a Pi running Falcon Player can drive a small neighborhood display — sequences, audio, and pixel control all from one board.

SBS+ takes this a step further: not only does the Pi run the show, it also serves as the WiFi access point for your smart devices and provides synced audio to your phone and your audience's phones — no internet connection, no cloud services, no extra hardware beyond what you already have. All the wonderful things FPP can do but now coupled with the ability to play audio from your phone/device. We even take a stab at correcting for Bluetooth delay with an offset setting.


SBS+ combines two tools into one install that runs on a single Raspberry Pi:

Component What it does
FPP Admin Control Show-owner control page — start/stop sequences and playlists, hear synced show audio, BT speaker delay calibration. Runs on the admin AP (wlan0).
FPP Phone Listener Public audience page — phones connect to an open WiFi AP (wlan1), get auto-redirected by captive portal, and hear synced show audio. No password, no app, no setup.

Both pages share the same WebSocket sync server and adaptive PLL engine, so the admin phone and every audience phone stay locked to FPP's playback position.

Tested on FPP 9.4 (Raspberry Pi OS Bookworm). Should work on any FPP version that uses /opt/fpp/www/ as its web root (FPP 8+).


What This Does

FPP Admin Control (admin.html)

The admin page is for the show owner. It gives you:

  1. Playback control — start/stop playlists and sequences from your phone
  2. Synced show audio — hear the show audio in real time, synced to FPP via adaptive PLL
  3. Clock management — detects when the Pi's clock is wrong (no RTC/NTP) and offers to set it from your phone
  4. BT speaker delay — select a Bluetooth delay profile to compensate for BT speaker latency, with a link to the calibration page
  5. Auto-detection — picks up sequences started from any source (FPP web UI, scheduler, API)
  6. Debug tools — live PLL state, clock offset, error history, playback rate, and client sync log

FPP Phone Listener (listen.html)

The listener page is for audience members. It provides:

  1. Synced show audio — phone plays the show audio in sync with FPP (same PLL as admin)
  2. Now Playing display — shows the current track name
  3. Zero setup — phones connect to the open WiFi AP and are auto-redirected by captive portal
  4. QR code compatible — works with QR codes generated by fpp-listener-sync
  5. BT speaker delay — same delay profile support as admin page
  6. Debug tools — same PLL diagnostics as admin (for troubleshooting)

Plugin Dashboard (Status > SBS Audio Sync)

The FPP plugin dashboard replaces the default network configuration page and provides:

  1. Network interface manager — card-based UI showing each detected interface (wlan0, wlan1, eth0) with a role selector (SBS, Listener, Show Network, Unused)
  2. Per-interface config — SSID, channel, password, IP address for each AP role
  3. Quick links — admin page, listener page, QR code generator, printable sign
  4. Connected clients — MAC address, IP, hostname, signal strength, and connection time for each device on the AP
  5. Logs & diagnostics — view logs from ws-sync, listener-ap, dnsmasq, or sync reports with selectable line counts
  6. Self-test — checks all services, ports, interfaces, and firewall rules
  7. Service restart — restart AP, WebSocket sync, or DNS/DHCP individually
  8. FPP integration — registers a header icon and footer button in FPP's web interface for quick access

BT Speaker Delay Calibration (calibrate.html)

For Bluetooth speakers connected to devices that are streaming audio from the Pi, audio arrives with a delay (typically 50-300ms). The calibration page helps measure and compensate for this:

  1. Test sequence generator — select output channels and generate a _bt_cal.fseq that flashes lights every second
  2. Calibration runner — start the test sequence and adjust the delay slider (0-500ms) until Web Audio clicks are in sync with the light flashes
  3. Delay profiles — save named profiles (stored in browser localStorage) and select them from admin.html or listen.html
  4. RPi Bluetooth control — scan for BT devices, pair, connect/disconnect, and adjust volume — all from the calibration page

SBS vs SBS+

Mode What it means Hardware
SBS (Single Board Show) One AP for admin + E1.31 show devices. No audience listener. Pi + 1 WiFi radio (onboard or USB)
SBS+ (Single Board Show Plus) SBS + adds a second AP for audience phones. Two isolated networks on one Pi. Pi + 2 WiFi radios

Dual-AP (SBS+ Mode)

SBS+ runs two WiFi access points on one Raspberry Pi — one for the show owner, one for the audience:

AP Role SSID (default) Security Purpose
Admin/SBS sbs EAVESDROP WPA2 Admin control page + E1.31 show devices (WLED bulbs, controllers). Default IP: 192.168.40.1
Phone Listener listener SHOW_AUDIO Open Audience phones hear synced show audio via captive portal. Default IP: 192.168.50.1

How it works:

  • The show owner connects to EAVESDROP (WPA2, default password Listen123) and opens admin.html to control the show
  • Audience phones connect to SHOW_AUDIO (open WiFi) and are automatically redirected to the listen page via captive portal
  • The two networks are fully isolated — phones on SHOW_AUDIO cannot reach admin pages, FPP settings, or E1.31 devices on the admin network
  • Both APs share the same WebSocket sync server, so all clients stay in sync
  • Ethernet remains the show backbone for E1.31/multisync to remote FPP players

To enable SBS+ mode:

  1. Plug in a USB WiFi adapter (for wlan1)
  2. Open the plugin dashboard (Status > SBS Audio Sync) and assign roles to each interface
  3. Save & Restart

In SBS mode (no USB adapter): Only one interface runs. The admin page and E1.31 devices share the same AP. The public listener page is still accessible but there's no separate audience AP.

WiFi Adapter Recommendations

The plugin is interface-agnostic — it reads roles.json and configures whatever wlan* interfaces are present. You can use any combination of onboard and USB WiFi:

Setup Interfaces Best for
Onboard + USB wlan0 (onboard) = SBS, wlan1 (USB) = Listener Most common. Simple, one adapter to buy.
Dual USB wlan0 + wlan1 (both USB) Best range and reliability. Disable onboard WiFi with dtoverlay=disable-wifi in /boot/config.txt. Frees the shared onboard radio for Bluetooth.
Onboard only wlan0 (onboard) = SBS SBS mode only (no audience AP).

For the Listener AP (audience phones), a USB adapter with an external antenna is recommended — it handles more concurrent clients and provides better range across a yard. The Pi's onboard BCM43455 (brcmfmac) is limited to ~10-15 clients and shares its radio with Bluetooth.

Recommended USB chipsets (good Linux AP support):

  • RTL8812AU — dual-band 2.4/5 GHz, external antenna, excellent AP mode
  • RT5370 — 2.4 GHz, compact, very reliable AP mode, well-supported on Pi

QR Codes

If you previously used fpp-listener-sync and generated QR codes with its qrcode.html page, those QR codes will continue to work. They point to http://<AP_IP>/listen/ which redirects to listen.html — the public listen page.

To access the admin page directly, navigate to http://<AP_IP>/listen/admin.html.

Clock Management

The Raspberry Pi 3B has no real-time clock (RTC) battery and typically relies on NTP for time sync. When running as a standalone AP (no internet connection), the Pi's clock resets to the OS image date on every reboot. This causes sync problems because the WebSocket server timestamps are wrong.

SBS+ handles this automatically:

  • Admin page (Option B): When the admin page connects and detects the Pi's clock is off by more than 60 seconds, it shows a warning banner: "Pi clock is wrong — off by ~X days. Set it from your phone?" Tapping Yes sends the phone's current time to the server, which sets the Pi's system clock. The ws-sync service has CAP_SYS_TIME capability so it can set the clock without root.

  • Both pages (Option A — automatic backup): The sync math compensates for clock mismatch by using the measured clock offset in all timestamp comparisons. Even if the Pi's clock is days off, sync still works because the PLL uses offset-adjusted timestamps. Messages older than 2 seconds (real elapsed) are silently discarded.


Support This Project

If this tool saved you time or made your show better, consider buying me a coffee or donate for me to get more tokens:

PayPal


Important: Must Be Installed on the Master

SBS+ must be installed on your master FPP (the one in player mode), not on a remote. It needs:

  • Direct access to the music files in /home/fpp/media/music/ (both Admin Control and Phone Listener serve audio from here)
  • The FPP API at 127.0.0.1 to read playback status and start/stop playlists
  • Remotes don't have to store media locally — they only receive channel data from the master. Typically, there is no media to play on a remote
  • SBS mode: No USB WiFi adapter needed — admin AP runs on onboard wlan0
  • SBS+ mode: Plug in a USB WiFi adapter for wlan1 (the audience listener AP)

If your master controls the show, that's where this goes.

What You Need

Before you start, make sure you have:

  • A Raspberry Pi running Falcon Player (FPP) 8.x+ in player (master) mode
  • Your FPP already has sequences (.fseq files) and matching audio files (.mp3) loaded
  • A computer on the same network as your FPP to run the install commands

Step-by-Step Install

Step 1: Open a terminal to your FPP

You need to get a command line on your FPP. Pick one of these methods:

Option A — SSH from your computer:

  • Windows: Open PowerShell or Command Prompt and type:

    ssh fpp@YOUR_FPP_IP
    

    Replace YOUR_FPP_IP with your FPP's IP address (for example 10.1.66.204). When it asks for a password, type falcon and press Enter.

  • Mac/Linux: Open Terminal and type the same command above.

Option B — Use the FPP web interface:

  • Open your browser and go to http://YOUR_FPP_IP/
  • Click on Content Setup in the menu
  • Click File Manager
  • (This method only works for uploading files — you'll still need SSH for the install command)

Step 2: Download this project onto your FPP

Once you're logged in via SSH, type these commands one at a time (press Enter after each one):

cd /home/fpp
git clone https://github.com/UndocEng/fpp-sbs-plus.git
cd fpp-sbs-plus

Step 3: Run the installer

sudo ./install.sh

The installer runs 16 steps including web file deployment, service installation, Apache configuration, plugin registration, and self-tests. On success you'll see:

=========================================
  Install successful! v4.3.1
=========================================
  FPP Dashboard: Status > SBS Audio Sync
  Admin:  http://YOUR_FPP_IP/listen/admin.html
  Sync:   WebSocket (ws://YOUR_FPP_IP/ws)
  SBS AP: EAVESDROP (WPA2) on wlan0 (192.168.40.1)
=========================================

If a USB WiFi adapter is detected and assigned as a listener, it also shows:

  Listen: SHOW_AUDIO (open) on wlan1 (192.168.50.1)
  Public: http://192.168.50.1/listen/

Step 4: Test it

  1. On your phone (connected to the same network as your FPP), open the browser
  2. Go to http://YOUR_FPP_IP/listen/
  3. You should see the Show Audio page with a Playback section at the top
  4. Select a sequence from the dropdown and tap Start — your lights and audio should begin
  5. Tap anywhere on the page to unlock audio (required by mobile browsers on first visit)

That's it! You're done.


How to Use It

Admin (show owner)

  1. Connect your phone to the EAVESDROP WiFi (default password: Listen123)
  2. Open http://192.168.40.1/listen/admin.html (or the AP IP you configured)
  3. Pick a playlist or sequence from the dropdown and tap Start
  4. Audio plays through your phone speaker while lights run on your E1.31 devices
  5. If you see a "Pi clock is wrong" banner, tap Yes to set the clock from your phone

Audience (Phone Listener)

  1. Audience members connect to the SHOW_AUDIO WiFi (open, no password)
  2. Their phone's captive portal auto-redirects to the listen page
  3. Audio starts syncing automatically when a sequence is playing
  4. No interaction needed — it just works

Auto-detection

Both pages automatically detect when a sequence is playing — even if started from FPP's web UI, the scheduler, or any other source. Just keep the page open and it will start syncing as soon as something plays.

Stopping a show

Tap the Stop button on the admin page.

Debug info

Both pages have a Debug checkbox at the bottom of the sync card to see live diagnostics: transport type, clock offset, PLL state, error history, and playback rate. The Client Log checkbox shows a running log of sync events. On the admin page, enabling Server Log sends telemetry to the Pi for analysis via sync.log.


Troubleshooting

"The page loads but I hear no audio"

  • On iPhone, check that the ringer switch (on the side of the phone) is not on silent
  • Turn up the volume on the phone
  • Tap anywhere on the page — mobile browsers require a user gesture before they'll play audio
  • Check that you have .mp3 files in /home/fpp/media/music/ with the same name as your .fseq files (e.g. Elvis.fseq needs Elvis.mp3)

"The sequence starts but the lights don't do anything"

This is an FPP output configuration issue, not a listener issue. Check:

  • Go to your FPP web interface (http://YOUR_FPP_IP/)
  • Click Input/Output Setup > Channel Outputs
  • Make sure your output universes are active (checkbox enabled)
  • Make sure the output IP addresses are correct (not your FPP's own IP)

"Audio is out of sync"

  • Enable the Debug checkbox and watch the PLL converge — it takes ~12-14 seconds after a track starts
  • If the admin page shows a "Pi clock is wrong" banner, tap Yes to set the clock from your phone — sync math compensates automatically, but setting the correct time helps
  • The Avg Error (2s) field should hover near 0 once locked — typical steady-state is 5-25ms
  • After a Pi reboot with no internet, the clock resets — open the admin page first to fix the clock before starting a show

"WebSocket not connecting"

  • Check the ws-sync service: sudo systemctl status ws-sync
  • View logs: journalctl -u ws-sync -f
  • Test the port: curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/ — should return 426
  • The page will automatically fall back to HTTP polling if WebSocket is unavailable

Updating

To get the latest version:

cd /home/fpp/fpp-sbs-plus
git pull
sudo ./install.sh

Uninstall

To completely remove everything this project installed:

cd /home/fpp/fpp-sbs-plus
sudo ./uninstall.sh

This removes:

  • All web files from /opt/fpp/www/listen/
  • The /music symlink
  • The ws-sync WebSocket service (stopped and disabled)
  • The listener-ap WiFi AP service (stopped, hostapd/dnsmasq killed for both admin and show APs)
  • The Apache WebSocket proxy configuration and SBS+ rewrite rules from the FPP VirtualHost
  • The sudoers entry for WiFi management
  • The config directory at /home/fpp/listen-sync/ (AP config, hostapd config, scripts, sync log)
  • The FPP plugin registration and header icon
  • The SBS+ footer button from custom.js
  • Network routing rules (nftables tables listener_ap and show_ap, policy routes)
  • Captive portal .htaccess from the web root

After uninstalling, your FPP is exactly as it was before. You can then delete the project folder:

rm -rf /home/fpp/fpp-sbs-plus

Files

Web Pages (deployed to /opt/fpp/www/listen/ and /opt/fpp/www/)

File Component What it does
www/listen/admin.html Admin Control Admin page — playback controls, synced audio, BT delay profiles, clock management, debug UI
www/listen/listen.html Phone Listener Public listen page — audio sync, now playing display, BT delay profiles, debug UI
www/listen/calibrate.html BT Calibration BT delay calibration — FSEQ generator, delay slider, profiles, BT device pairing/volume
www/listen/index.html Both Redirects to listen.html (makes QR codes and captive portal work)
www/listen/status.php Sync Returns current FPP playback status as JSON (HTTP polling fallback)
www/listen/admin.php Admin Backend Start/stop commands, BT device management, calibration FSEQ generation
www/listen/version.php Both Returns version info (reads from VERSION file)
www/listen/portal-api.php Phone Listener Captive portal API (RFC 8908) — tells phones where to redirect
www/listen/detect.php Phone Listener Legacy captive portal detection fallback for older devices
www/listen/logo.png Admin Control Admin logo (gold/amber)
www/listen/logo-public.png Phone Listener Listener logo (blue/cyan)
www/qrcode.html Utility QR code generator for listener page URL
www/music.php Utility Audio file server with CORS headers for direct streaming

Server (deployed to /home/fpp/listen-sync/)

File What it does
server/ws-sync-server.py Python WebSocket server — polls FPP API every 200ms, broadcasts state to all clients, handles clock sync pings, receives telemetry reports, and sets Pi clock from admin page
server/listener-ap.sh Brings up admin AP on wlan0 (with power save disabled), optionally show AP on wlan1 (SBS+), configures nftables isolation and captive portal
server/listener-ap.service Systemd service for the WiFi access point(s)

Config (deployed to /etc/systemd/system/ and /etc/apache2/)

File What it does
config/ws-sync.service Systemd service for the WebSocket server — runs as fpp user with CAP_SYS_TIME (for clock set), 64MB RAM / 25% CPU limits
config/apache-listener.conf Apache config — proxies /ws on port 80 to WebSocket server on port 8080
config/ap.conf Default AP config template (admin AP interface, IP, SBS+ settings)
config/hostapd-show.conf Default hostapd config for SBS+ public AP (open WiFi, ap_isolate=1)
www/.htaccess-show Captive portal rewrite template for SBS+ show AP — redirects all HTTP to listen.html, blocks admin.html

FPP Plugin

File What it does
plugin.php Plugin dashboard — card-based network interface manager, connected clients, logs, self-test
listener-api.php Dashboard backend — interface config, role management, service control, client scanning
api.php FPP plugin API — header indicator icon (gold headphones) linking to admin page
pluginInfo.json FPP plugin metadata for plugin manager registration
fpp-network.php Wrapper to load FPP's original networkconfig.php (backed up during install)
install.sh Installs everything — web files, services, AP, plugin, sudoers, Apache config, CRLF fixes
uninstall.sh Removes everything — restores FPP to original state

How Audio Sync Works

SBS+ uses an adaptive Phase-Locked Loop (PLL) to keep the phone's audio in sync with FPP's sequence playback. Instead of repeatedly jumping to the correct position (which causes audible pops), it smoothly adjusts the playback speed to converge on FPP's position and stay locked.

Transport

The WebSocket server (ws-sync-server.py) polls FPP's API every 200ms and broadcasts state to all connected clients concurrently. The browser connects via WebSocket (proxied through Apache on port 80 at /ws), with automatic HTTP polling fallback if WebSocket is unavailable.

NTP-style clock offset estimation uses ping/pong round-trips through the WebSocket, with a median filter + EWMA for stable offset calculation.

The PLL Algorithm

The sync engine runs through three phases:

1. Start (first message after a track begins)

  • Preloads the audio file and waits for metadata
  • Seeks to FPP's current position
  • Starts playback, enters 1.5-second settle period

2. Calibrate (~800ms minimum, 6+ samples)

  • Collects {local_time, fpp_position} pairs
  • Computes a least-squares linear regression to find the rate ratio between FPP's clock and the phone's clock
  • Clamps the base rate to +/-1% (rejects garbage calibration)

3. Locked (ongoing)

  • Computes phase error: fpp_position - audio.currentTime
  • Maintains a 2-second rolling average (avg2s) as PLL input — prevents oscillation from instantaneous noise
  • Adaptive gain: Kp = 0.01 * (1 + 4 * min(|avg2s|/200, 1)) — gentle when close (0.01), aggressive when far (0.05)
  • Log-compressed correction: rate = baseRate + sign(avg2s) * Kp * log1p((|avg2s| - deadZone) / 100)
  • Dead zone: no correction when error < 5ms
  • Rate learning: EMA (alpha=0.05) from 2-second observation windows, so corrections shrink as the true clock relationship is learned
  • Hard seek fallback: if error exceeds 2 seconds, seeks directly (with 2-second cooldown)

Result

After ~12-14 seconds (settle + calibration), the phone stays locked to FPP's position with 5-25ms steady-state error. The debug display shows live PLL state, error history, and playback rate.


Technical Details (for developers)

  • WebSocket transport: Python asyncio server polls FPP every 200ms, broadcasts {state, base, pos_ms, mp3_url, server_ms} to all clients concurrently via asyncio.gather() (prevents slow wlan1 clients from delaying wlan0 admin messages)
  • HTTP fallback: 250ms polling of status.php when WebSocket is unavailable
  • Clock offset: NTP-style estimation via WebSocket ping/pong, median filter + EWMA (alpha=0.3). Used in serverOk check so sync works even when Pi clock is wrong
  • Clock set: Admin page detects >60s clock mismatch and can push phone time to Pi via set_clock WebSocket message. Server uses date -s @<unix_sec> with CAP_SYS_TIME capability (no sudo needed)
  • Elapsed handling: Messages older than 2 seconds (real elapsed) are discarded. No upper clamp — true elapsed is used for accurate target position
  • PLL calibration: least-squares linear regression, 800ms minimum window, 6+ samples, base rate clamped to +/-1%
  • Locked correction: rate = baseRate + sign(avg2s) * Kp * log1p((|avg2s| - 5) / 100), Kp adaptive 0.01-0.05
  • Error averaging: 2-second rolling window (avg2s) as PLL input, all-time average for diagnostics
  • Hard seek: >2 seconds error, 2-second cooldown between seeks
  • Rate learning: EMA alpha=0.05 from 2-second windows
  • Position data: milliseconds_elapsed from FPP API (not seconds_played, which is whole-seconds only)
  • Server timestamp: server_ms captured at midpoint of API call, used for clock offset calculation; round() not intval() to avoid 32-bit overflow on Pi 3B
  • Apache proxy: mod_proxy_wstunnel proxies /ws on port 80 to Python server on port 8080
  • systemd service: runs as fpp user with 64MB RAM / 25% CPU limits, CAP_SYS_TIME for clock set, NoNewPrivileges=true for security
  • WiFi power save: Disabled on AP interfaces (iw dev set power_save off) — brcmfmac enables power save at boot which causes E1.31 devices to drop
  • Playback control: POST /api/command with "Start Playlist" / "Stop Now" commands
  • Audio unlock: browser autoplay policy requires a user gesture — first click/touch on the page silently plays and pauses to unlock the audio context
  • Network isolation: nftables rules isolate show AP (wlan1) from admin AP (wlan0) — audience phones cannot reach admin pages, FPP settings, or E1.31 devices
  • Policy routing: conntrack-based marks (0x64-0x6F) route replies back to the correct AP interface — supports up to 12 wireless interfaces
  • BT delay compensation: optional per-profile delay (0-500ms) subtracted from PLL target position. Profiles stored in browser localStorage, calibrated via calibrate.html
  • BT device management: admin.php provides scan, pair, connect, disconnect, and volume control via bluetoothctl and pactl

Roadmap

USB Bluetooth Audio Output (Planned)

For Bluetooth speakers connected to the Pi, audio arrives with a delay (typically 50-300ms). The calibration page helps measure and compensate for this: it plays synchronized click + flash patterns so you can dial in the exact offset for your speaker, then saves it as a named profile. The PLL subtracts this delay so lights and audio stay in sync.

The next step is native USB Bluetooth audio output from the Pi itself, allowing FPP to send show audio directly to a BT speaker without a phone relay. This would be a standalone FPP enhancement that works alongside SBS+:

  • BT pairing UI — scan, pair, trust, and connect Bluetooth speakers from a web page
  • ALSA bridge — use BlueALSA to present the paired BT speaker as a standard ALSA sound card that FPP can select as its audio output device
  • Playback delay compensation — a configurable BT audio delay setting so FPP can start the FSEQ sequence ahead of the audio, keeping lights and sound in sync despite A2DP encoding latency (typically 100-300ms)
  • Auto-reconnect — handle BT connection drops gracefully and reconnect without stopping the show

This feature is independent of the SBS+ plugin and could be contributed upstream to the FPP project.


About

FPP Single Board Show - Dual AP mode (listener + show control) for Falcon Player

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors