A Raspberry Pi project for simple NDI (Network Device Interface) functionality with LED screen testing capabilities.
This repository contains tools and utilities for Raspberry Pi projects, including:
- LED Test Pattern Generator: A comprehensive test pattern generator for 320x320 LED screens
- NDI Integration: Simple NDI functionality for network video streaming
- Display Management: Tools for handling various display configurations on Raspberry Pi
- Auto-discovery: Automatically finds and connects to NDI sources matching regex patterns
- Intelligent switching: Switches to newly appeared sources automatically
- Smart fallback: Falls back to previous source when current source dies
- Dead source detection: Detects and recovers from non-responsive sources
- Manual override: Web interface for remote source selection and control
- ๐ Lock functionality: Lock sources to prevent automatic switching (mirroring TD_NDI_NamedRouter API)
- 60 FPS performance: Direct BGRA format for maximum throughput
- Flexible configuration: JSON-based config for display, content, and NDI settings
- Remote control: Select NDI sources from any device on the network
- Real-time monitoring: Live FPS, resolution, and connection status
- Source management: View available sources and switch between them instantly
- ๐ Lock button: Toggle lock state to prevent/allow automatic source switching
- Smart override logic: Manual selection respects new/reappearing sources
- WebSocket updates: Automatic state synchronization across all clients
- Mobile-friendly: Responsive design works on phones, tablets, and desktops
- Centralized control: Manage multiple TD_NDI_NamedRouter and RPi receivers from one interface
- Component aggregation: Merge outputs from all connected components into unified view
- Auto-discovery: Components auto-register with unique IDs and display names
- Command routing: Browser commands automatically routed to correct component
- Flexible deployment: Run bridge on RPi or any machine, components connect as clients
- Three modes: Standalone (local only), bridge-only (no local web), or hybrid (both)
- Dual resolution support: Separate display and content resolutions
- Content positioning: Pixel-perfect or named position placement
- Rotation: 0ยฐ, 90ยฐ, 180ยฐ, 270ยฐ rotation support
- Scaling modes: fit, fill, stretch, or none
- FPS monitoring: Real-time performance tracking
- Multiple test patterns optimized for LED screens
- Automatic video driver detection (KMSDRM, DirectFB, X11)
- Position and rotation controls for display alignment
- Text-based orientation verification
- Real-time pattern cycling
- Raspberry Pi 5 (tested on Raspberry Pi OS Bookworm/Trixie)
- Python 3.13+
- Pygame library with SDL 2.32.4+ (for HDMI display access via KMS/DRM)
- NDI SDK (installed at
/usr/local/lib/libndi.so.6) - WebSockets library (
websockets>=10.0for web interface) - FFmpeg with NDI support (optional, see FFMPEG_NDI_BUILD.md)
- HDMI display or LED screen
- Network connectivity (for NDI and web interface)
โ ๏ธ Important: Use system Python (/usr/bin/python3) rather than a virtual environment. Older SDL versions (e.g., SDL 2.28.4 in some venv installations) cannot access the display via KMS/DRM. System Python typically has the newer SDL 2.32.4+ which works correctly.
# Clone the repository
git clone <repository-url>
cd RpiSimpleNDI
# Install Python dependencies
pip3 install -r requirements.txt# 1. Install system dependencies
sudo apt update
sudo apt install python3-pygame python3-pip python3-numpy
# 2. Install NDI SDK
# Download from: https://ndi.video/for-developers/ndi-sdk/download/
# Then:
sudo cp libndi.so.6.* /usr/local/lib/
sudo ldconfig
# 3. (Optional) Build FFmpeg with NDI support
# See FFMPEG_NDI_BUILD.md for complete instructions
# 4. Install Python dependencies
pip3 install -r requirements.txtTo run the NDI receiver automatically on boot:
# Install the service
sudo cp ndi-receiver.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable ndi-receiver.service
# Start the service immediately
sudo systemctl start ndi-receiver.service
# Check service status
sudo systemctl status ndi-receiver.service
# View live logs
sudo journalctl -u ndi-receiver.service -f
# Stop the service
sudo systemctl stop ndi-receiver.service
# Disable auto-start
sudo systemctl disable ndi-receiver.serviceThe service will:
- โ Start automatically on boot
- โ Restart automatically if it crashes
- โ Wait for network connectivity before starting
- โ
Load configuration from
config.led_screen.json - โ Auto-reconnect to display & NDI if disconnected/reconnected (new!)
Note: By default, the service runs without a local web interface. Configure bridge connection in your config file, or edit the service file to add --web-server for standalone operation.
To run the bridge server automatically on boot:
# Install the bridge service
sudo cp bridge-server.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable bridge-server.service
# Start the service immediately
sudo systemctl start bridge-server.service
# Check service status
sudo systemctl status bridge-server.service
# View live logs
sudo journalctl -u bridge-server.service -f
# Stop the service
sudo systemctl stop bridge-server.service
# Disable auto-start
sudo systemctl disable bridge-server.serviceThe bridge service will:
- โ Start HTTP server on port 8000 for web interface
- โ Accept browser WebSocket connections on port 8080
- โ Accept component connections on port 8081
- โ Restart automatically if it crashes
- โ Wait for network connectivity before starting
Scenario 1: Standalone Receiver (Local Web Interface Only)
# Edit ndi-receiver.service to add --web-server flag:
# ExecStart=.../python3 ndi_receiver.py --config config.led_screen.json --web-server
sudo systemctl enable ndi-receiver.service
# Access at http://raspberrypi.local:8000Scenario 2: RPi as Bridge Hub (Most Common)
# Enable both services (no port conflict - receiver uses bridge)
sudo systemctl enable bridge-server.service
sudo systemctl enable ndi-receiver.service
# Configure bridge in config.led_screen.json:
# "bridge": {"enabled": true, "url": "ws://localhost:8081"}
# Access unified interface at http://raspberrypi.local:8000Scenario 3: Receiver Only (Connect to External Bridge)
# Only enable receiver
sudo systemctl enable ndi-receiver.service
# Configure external bridge in config.led_screen.json:
# "bridge": {"enabled": true, "url": "ws://TD_MACHINE_IP:8081"}
# Control via TD_NDI_NamedRouter web interfacePort Conflict Warning: Don't run ndi-receiver.service with --web-server AND bridge-server.service together without changing ports - they both use 8000 and 8080 by default!
The program now handles disconnections gracefully - both display and NDI sources:
- ๐ No display at startup? Program starts anyway and retries every 5 seconds
- ๐บ Display unplugged? Video reception continues, reconnects automatically when plugged back in
- ๐ Automatic recovery No manual intervention needed - just plug the display back in
- ๐ก No NDI sources at startup? Program starts anyway and retries every 5 seconds
- ๐ NDI source disappeared? Automatically detects and reconnects when available
- ๐ Web interface always works Monitor and control via browser even without NDI or display
- ๐ฏ Smart switching Works with auto-switching and manual selection features
How it works:
- Program checks for display and NDI every 5 seconds when disconnected
- Web server continues running without interruption
- Display and NDI resume automatically when reconnected
- No crashes, no restarts needed!
Note: For production LED installations with permanent displays, consider using an HDMI locking cable to prevent accidental disconnection.
๐ฏ Quick Start: See CLI_GUIDE.md for complete CLI documentation.
๐ Studio Setup: See STUDIO_SETUP.md for studio-specific instructions.
# Quick start with auto-discovery
python3 ndi_receiver.py
# With web interface for remote control
python3 ndi_receiver.py --config config.led_screen.json --web-server
# List available NDI sources
python3 list_ndi_sources.py
# Use a configuration file
python3 ndi_receiver.py --config config.led_screen.json
# Override config with CLI options
python3 ndi_receiver.py --config config.led_screen.json --show-fps --debug
# Test patterns only (no NDI)
python3 ndi_receiver.py --test-patternSee python3 ndi_receiver.py --help for all options.
# Start with web interface enabled
python3 ndi_receiver.py --config config.led_screen.json --web-server
# Access from browser
http://localhost:8000 # Local access
http://raspberrypi.local:8000 # Network access (mDNS)
http://[raspberry-pi-ip]:8000 # Direct IP access
# Custom ports
python3 ndi_receiver.py --web-server --web-port 8090 --websocket-port 9000Features:
- ๐๏ธ Remote source selection: Choose any NDI source from dropdown
- ๐ Live monitoring: Real-time FPS, resolution, connection status
- ๐ Auto-refresh: Source list updates automatically
- ๐ฑ Mobile-friendly: Works on phones and tablets
- ๐ Multi-client: Multiple browsers can connect simultaneously
The receiver automatically manages NDI source connections:
Automatic Source Discovery:
- Scans for NDI sources matching regex patterns (e.g.,
".*_led") - Example:
"MACBOOK (TouchDesigner_led)"โ matches,"MACBOOK (output)"โ doesn't match - See REGEX_PATTERN_GUIDE.md for pattern examples
Intelligent Switching:
- ๐ New source appears โ Switches immediately to the new source
- โป๏ธ Source reappears โ Switches back when a source comes back online
โ ๏ธ Current source dies โ Falls back to previous source if available- ๐ Predictable fallback โ Always returns to the last working source
- ๐๏ธ Manual override โ Web interface selection takes priority, but respects new sources
Manual Override Logic:
- Manual source selection from web interface is "sticky"
- Prevents switching to existing sources
- Still switches to new/reappearing sources matching pattern
- Clears override if manually selected source dies/disappears
Dead Source Detection:
- Monitors frame reception in real-time
- After 2 seconds without frames โ forces source check
- After 5 seconds without frames โ marks source as dead
- Automatically finds and switches to working alternatives
Disable auto-switching:
python3 ndi_receiver.py --no-auto-switchThe receiver includes a Lock feature (mirroring the TD_NDI_NamedRouter API) to prevent automatic source switching:
Lock Modes:
- ๐ Unlocked (default): Automatic source switching is enabled based on regex patterns
- ๐ Locked: Automatic source switching is disabled - current source remains fixed
How to Lock:
Via CLI:
# Start with lock enabled
python3 ndi_receiver.py --lock
# Combine with config and web server
python3 ndi_receiver.py --config config.led_screen.json --web-server --lockVia Web Interface:
- Click the ๐/๐ button next to the output name
- ๐ = Unlocked (auto-switching enabled)
- ๐ = Locked (auto-switching disabled)
- Visual indicator: Locked outputs show ๐ in title and orange border
When to Use Lock:
- โ Production shows: Lock to prevent unexpected source changes during performance
- โ Testing specific sources: Lock while debugging a particular NDI source
- โ Manual control: When you want complete control over source selection
- โ Stable setups: Lock after finding the right source to prevent auto-switching
Lock Behavior:
- When locked, the receiver will NOT automatically switch sources even if:
- New matching sources appear on the network
- Current source disappears and reappears
- Regex patterns would normally trigger a switch
- Manual source selection via web interface still works when locked
- Can be toggled on/off at any time via CLI or web interface
- State is included in WebSocket updates for real-time synchronization
The bridge server allows you to control multiple NDI routing components (TouchDesigner instances, Raspberry Pi receivers) through a single unified web interface.
Starting the Bridge Server:
You have two options:
Option 1: Use RPi Bridge Server (Included)
# Start on default ports (HTTP: 8000, Browser WS: 8080, Component WS: 8081)
python3 bridge_server.py
# Don't auto-open browser
python3 bridge_server.py --no-browser
# Custom ports
python3 bridge_server.py --port 8090 -w 9000 -t 9001Option 2: Use TD_NDI_NamedRouter Bridge (External)
# On your TouchDesigner/main computer, run the TD_NDI_NamedRouter bridge:
cd /path/to/TD_NDI_NamedRouter
python3 start_server.py
# Then connect your RPi receivers to it (see below)Architecture:
- Port 8000 (HTTP): Serves the web interface HTML page
- Port 8080 (WebSocket): Browser JavaScript connects here for real-time updates
- Port 8081 (WebSocket): Components (TD instances, RPi receivers) connect here as clients
- Bridge merges all component states and displays them in a unified interface
- Commands from browsers are routed to specific components by
component_id
Connecting RPi Receiver to Bridge:
Via config file:
{
"name": "LED Screen",
"bridge": {
"enabled": true,
"url": "ws://bridge-server-ip:8081",
"component_id": "LED_Wall_Main",
"component_name": "LED Wall - Stage Left"
}
}Via CLI:
# Connect to local RPi bridge
python3 ndi_receiver.py --config config.json --bridge-url ws://localhost:8081
# Connect to external TD_NDI_NamedRouter bridge
python3 ndi_receiver.py --config config.json --bridge-url ws://192.168.1.100:8081
# Bridge only (no local web server) - saves resources
python3 ndi_receiver.py --config config.json --bridge-url ws://192.168.1.100:8081 --bridge-only
# Hybrid mode (local web server + bridge connection)
python3 ndi_receiver.py --config config.json --web-server --bridge-url ws://192.168.1.100:8081Connecting TouchDesigner to Bridge:
In TouchDesigner NDI_NamedRouter:
- Set WebSocket DAT to CLIENT mode
- Network Address:
bridge-server-ip - Port:
8081 - Active: โ
- Callbacks: Point to
websocket1_callbacks
Features:
- Unified control of all outputs from one interface
- Auto-discovery of connected components
- Component-specific lock states
- Real-time state synchronization
- Source lists aggregated from all components
- Per-component routing of commands
Use Cases:
- Control multiple LED walls from one interface
- Mix TouchDesigner and Raspberry Pi outputs
- Centralized monitoring of all NDI routing
- Remote control from any device on network
NDI Receiver (Direct, 60 FPS):
python3 ndi_receiver_native_display.pyLED Test Patterns:
python3 led_test_pattern.pyControls:
- ESC: Exit
- SPACE: Cycle patterns (test mode only)
- P: Cycle LED screen positions
- R: Cycle rotation angles
RpiSimpleNDI/
โโโ ndi_receiver.py # Main NDI receiver CLI โญ
โโโ bridge_server.py # Bridge server for multi-component control
โโโ ndi-receiver.service # Systemd service for NDI receiver
โโโ bridge-server.service # Systemd service for bridge server
โโโ list_ndi_sources.py # NDI source discovery utility
โโโ src/ # Modular source code
โ โโโ ndi_handler.py # NDI management (auto-switching, regex)
โ โโโ display_handler.py # Display/rendering
โ โโโ config.py # Configuration management
โ โโโ ndi_receiver_ext.py # Web interface extension (WebSocket handling)
โ โโโ server_handler.py # Bridge client handler
โ โโโ websocket_server.py # WebSocket server
โ โโโ test_patterns.py # Test patterns
โโโ templates/ # Web interface files
โ โโโ index.html # Web UI (adapted from TouchDesigner)
โโโ config.example.json # Example configuration
โโโ config.led_screen.json # LED screen config
โโโ config.adaptive.json # Adaptive display config
โโโ config.regex_example.json # Regex pattern examples
โโโ README.md # This file
โโโ CLI_GUIDE.md # Complete CLI documentation
โโโ CONFIG_GUIDE.md # Configuration guide
โโโ REGEX_PATTERN_GUIDE.md # Regex pattern examples
โโโ STUDIO_SETUP.md # Studio setup guide
โโโ PERFORMANCE_TIPS.md # Optimization guide
โโโ FFMPEG_NDI_BUILD.md # FFmpeg compilation guide
โโโ led_test_pattern.py # Legacy test patterns
โโโ ndi_receiver_native_display.py # Legacy receiver (60 FPS)
โโโ requirements.txt # Python dependencies
โโโ venv/ # Virtual environment (not recommended, use system Python)
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
# List available NDI sources
python3 list_ndi_sources.py
# Filter by suffix
python3 list_ndi_sources.py --suffix _led
# Wait longer for discovery
python3 list_ndi_sources.py --timeout 10No NDI sources found:
- Check network connectivity (both devices on same network)
- Verify avahi-daemon is running:
systemctl status avahi-daemon - Check firewall settings (NDI uses TCP port 5960 and UDP multicast)
- Ensure NDI sender is active
Auto-switching not working:
- Verify your NDI source name matches the regex pattern (default:
".*_led") - NDI format:
"COMPUTERNAME (SourceName_led)" - Check pattern in config:
"source_pattern": ".*_led" - Check logs with
--debugflag
Web interface not loading:
- Check if both HTTP and WebSocket servers started
- Verify ports are not in use:
sudo lsof -i :8000 -i :8080 - Access via:
http://localhost:8000orhttp://raspberrypi.local:8000 - Check browser console for WebSocket errors
Manual source selection not working:
- Verify source is available in the network
- Check WebSocket connection (should show "Connected" in UI)
- Look for errors in browser console (F12)
- Check terminal logs for connection errors
Low FPS / Performance issues:
- Use
--cpu-performanceflag to set CPU governor to performance mode - Check PERFORMANCE_TIPS.md for optimization techniques
- Ensure you're using BGRA color format (default)
Display issues:
- Use test patterns to verify:
python3 ndi_receiver.py --test-pattern - Check config file display/content resolution settings
- Verify rotation and positioning in config
Service won't start / No display connected:
- โ Good news! The service now works without a display connected
- Check service status:
sudo systemctl status ndi-receiver.service - View live logs:
sudo journalctl -u ndi-receiver.service -f - Look for "Display not available - will auto-reconnect" message
- Web interface will still work at
http://localhost:8000 - Display will reconnect automatically when plugged in (checks every 5s)
For issues and questions:
- Create an issue in this repository
- Check the documentation in the
docs/folder - Review troubleshooting guides
- ๐ Bridge server: Run centralized server to control multiple components (TD + RPi)
- ๐ Bridge client: Connect RPi receiver to bridge server for unified control
- ๐ฏ Component registration: Auto-register with unique component ID and display name
- ๐ State aggregation: Bridge merges states from all connected components
- ๐๏ธ Unified interface: Control all outputs through single web interface
- ๐ Command routing: Browser commands routed to specific components by ID
- ๐ Auto-reconnect: Bridge client auto-reconnects on disconnection
- ๐ Config support: Bridge URL configurable via JSON config or CLI args
- ๐ Three modes: Standalone, bridge-only, or hybrid (local + bridge)
- ๐ง Flexible deployment: Run bridge on RPi or any machine with Python
- ๐ External bridge support: Can connect to TD_NDI_NamedRouter bridge server
- ๐ ๏ธ Non-privileged ports: Bridge HTTP server uses port 8000 (no sudo required)
- โ๏ธ Systemd service: Auto-start bridge server on boot with
bridge-server.service
- ๐ Lock functionality: Prevent automatic source switching with lock/unlock toggle
- ๐๏ธ Web UI lock button: Visual lock control with ๐/๐ button next to output name
- ๐ CLI lock flag: Start with
--lockto begin with source locked - ๐ก WebSocket lock command:
set_lockaction for remote lock control - ๐จ Lock visual indicators: Orange border and ๐ icon when locked
- ๐ Lock state in updates: Lock state included in all state updates
- ๐ฏ API compatibility: Mirrors TD_NDI_NamedRouter lock behavior and API
- ๐ Display auto-reconnection: Automatically reconnects if display is disconnected/reconnected
- ๐ก NDI auto-reconnection: Automatically reconnects to NDI sources when they appear/reappear
- ๐ช Headless operation: Runs without display or NDI sources, auto-connects when available
- ๐ Graceful degradation: Web server continues running even without display or NDI
- ๐ Periodic reconnection checks: Attempts reconnection every 5 seconds for both display and NDI
- ๐ก๏ธ Crash prevention: No more exits due to missing/disconnected displays or NDI sources
- ๐ Systemd service: Auto-start on boot with proper restart handling
- ๐ Web interface: Remote control and monitoring via browser
- ๐๏ธ Manual source selection: Override auto-switching from any device
- ๐ก WebSocket integration: Real-time state synchronization
- ๐ Smart manual override: Respects new/reappearing sources even with manual selection
- ๐งฎ Regex pattern matching: Flexible source filtering with full regex support
- โก Non-blocking architecture: Background threads for state broadcasting
- ๐ Live monitoring: FPS, resolution, and connection status in browser
- ๐ฏ Source caching: Performance optimization for source discovery
- ๐ง Signal handling: Clean shutdown with Ctrl+C
- ๐ฑ Mobile-friendly UI: Responsive design for all devices
- ๐ฏ Intelligent NDI source switching: Automatically switches to new sources with
_ledsuffix - ๐ Smart fallback: Falls back to previous source when current source dies
- ๐ Dead source detection: Detects non-responsive sources and recovers automatically
- ๐ Source discovery utility:
list_ndi_sources.pyfor quick NDI network diagnostics - โ๏ธ Modular architecture: Refactored into
src/with separate handlers - ๐ JSON configuration: Flexible config files for different display setups
- ๐จ Dual resolution support: Separate display and content resolution handling
- ๐ Advanced positioning: Named or pixel-based content positioning
- ๐ Multiple scaling modes: fit, fill, stretch, or none
- ๐พ Config profiles: Pre-made configs for LED screen, LCD, and adaptive setups
- LED test pattern generator with orientation testing
- Basic NDI functionality with native SDK integration
- 60 FPS BGRA direct format reception
- Raspberry Pi optimized display handling