The NDI Receiver now includes a web interface for remote monitoring and control, adapted from the TouchDesigner NDINamedRouterExt architecture.
┌─────────────────────┐
│ Web Browser │
│ (templates/) │
│ - Modern UI │
│ - Real-time │
└──────┬──────────────┘
│ WebSocket (8080)
│ HTTP (80)
▼
┌─────────────────────┐
│ start_server.py │
│ - HTTP Server │
│ - Serves HTML/CSS │
└─────────────────────┘
┌─────────────────────┐
│ websocket_server.py │
│ - WebSocket Server │
│ - Real-time msgs │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ ndi_receiver_ext.py │
│ - State management │
│ - Control handlers │
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ ndi_receiver.py │
│ - Main application │
│ - NDI + Display │
└─────────────────────┘
pip3 install websocketspython3 ndi_receiver.py --config config.led_screen.json --web-serverLocal access:
http://localhost
From another device on the network:
http://[raspberry-pi-ip]
Example:
http://192.168.1.100
http://raspberrypi.local
- Current connected source
- Available NDI sources
- FPS (if enabled)
- Connection status
- Resolution
- Pattern configuration
- Switch between sources
- Refresh source list
- View source history
- Connection health monitoring
- State broadcasts every 10 seconds
- Instant source change notifications
- Connection status updates
- FPS monitoring
The protocol is fully compatible with TouchDesigner's NDINamedRouter.
Request current state:
{"action": "request_state"}Change source:
{
"action": "set_source",
"source_name": "MACBOOK (output_led)"
}Refresh sources:
{"action": "refresh_sources"}Health check:
{"action": "ping"}State update:
{
"action": "state_update",
"state": {
"sources": ["MACBOOK (output_led)", "STUDIO (main_led)"],
"current_source": "MACBOOK (output_led)",
"connected": true,
"fps": 60.0,
"resolution": [1920, 1080],
"pattern": ".*_led",
"pattern_info": {
"pattern": ".*_led",
"case_sensitive": false,
"plural_handling": false
},
"auto_switch_enabled": true,
"last_update": 1699999999.123
}
}Source changed:
{
"action": "source_changed",
"source_name": "STUDIO (main_led)"
}Error:
{
"action": "error",
"message": "Failed to connect to source"
}Pong response:
{
"action": "pong",
"timestamp": 1699999999.123
}# Custom HTTP port
python3 ndi_receiver.py --web-server --web-port 8090
# Custom WebSocket port
python3 ndi_receiver.py --web-server --websocket-port 9000
# Both custom
python3 ndi_receiver.py --web-server --web-port 8090 --websocket-port 9000Add to your config.json:
{
"web": {
"enabled": true,
"http_port": 80,
"websocket_port": 8080
}
}src/ndi_receiver_ext.py
- NDIReceiverExt class
- WebHandler class
- State management
- Message handling
- Adapted from
NDINamedRouterExt.py
src/websocket_server.py
- WebSocket server implementation
- Client connection management
- Message routing
start_server.py
- HTTP server for web interface
- Serves templates/index.html
- Copied from TD_NDI_NamedRouter
templates/index.html
- Web interface UI
- Copied from TD_NDI_NamedRouter
- Will be adapted for single-source display
# Start receiver with web interface
python3 ndi_receiver.py \
--config config.led_screen.json \
--web-server \
--show-fps
# Access from phone/tablet
# http://raspberrypi.local# Start headless with web interface
python3 ndi_receiver.py \
--web-server \
--no-display \
--source-pattern ".*_led"
# Control from another computer
# http://192.168.1.100# Use non-privileged ports (no sudo)
python3 ndi_receiver.py \
--web-server \
--web-port 8080 \
--websocket-port 8081- No authentication currently implemented
- Runs on all network interfaces (0.0.0.0)
- Anyone on network can access
- Suitable for trusted networks only
Future enhancements:
- Basic authentication
- API keys
- HTTPS support
- Access control lists
# Error: Port 80 is already in use
# Solution: Use custom port
python3 ndi_receiver.py --web-server --web-port 8080- Check WebSocket port is open
- Verify firewall settings
- Check logs for errors
- Try default ports (80, 8080)
- Check Raspberry Pi IP:
hostname -I - Verify same network
- Check firewall:
sudo ufw status - Try .local address:
raspberrypi.local
- Minimal overhead: WebSocket updates every 10s only
- No impact on FPS: Updates run in separate thread
- Memory efficient: Only broadcasts to active clients
- Scalable: Supports multiple simultaneous clients
Planned enhancements:
- Simplify UI for single source (remove multi-block layout)
- Add FPS graph
- Add source history log
- Add configuration editor
- Add authentication
- Mobile-optimized layout
# Test WebSocket connection
python3 -c "
import asyncio
import websockets
import json
async def test():
uri = 'ws://localhost:8080'
async with websockets.connect(uri) as websocket:
# Request state
await websocket.send(json.dumps({'action': 'request_state'}))
response = await websocket.recv()
print(json.dumps(json.loads(response), indent=2))
asyncio.run(test())
"Web interface architecture adapted from:
- TD_NDI_NamedRouter by Dan@DAN-4090
- WebSocket protocol compatible with TouchDesigner implementation
- UI templates reused from TouchDesigner project