A low-cost, camera-driven follow-spot system built for live events — automatically aligns a DMX moving head to wherever the camera is pointed, using a LiDAR distance sensor and real-time 3D vector math.
Built as a school thesis project at Norbert-Gymnasium Knechtsteden (2023/24). The system replaces commercial follow-spot solutions costing ~€15,000 with a self-built alternative for under €300.
A lighting operator uses a gamepad to pan and tilt a camera mounted inside a converted moving head. The system:
- Reads the camera's pan/tilt angles and a LiDAR distance measurement
- Calculates the 3D coordinates of the illumination point using vector geometry
- Derives the pan/tilt angles the light moving head needs to hit the same point
- Sends the result as DMX values via sACN to a GrandMA3 4-Port Node, which drives the moving head
The operator sees a live NDI video feed from the camera and never has to think about angles or distances — the math runs automatically on every input.
┌─────────────┐ Bluetooth ┌──────────────────┐
│ Gamepad │ ──────────────────────► │ node_controller │
└─────────────┘ │ (Electron app) │
└────────┬─────────┘
│ POST /controller/input
▼
┌─────────────────────┐ GET /distance ┌──────────────────┐
│ node_camera │ ◄──────────────── │ node_server │
│ (Raspberry Pi 4b) │ ──────────────► │ (Python/Flask) │
│ Camera + LiDAR │ distance data └────────┬─────────┘
│ NDI video stream │ ──────────────────────────►│ NDI stream to controller
└─────────────────────┘ │
│ sACN (E1.31)
▼
┌──────────────────┐ DMX ┌─────────────┐
│ GrandMA3 4-Port │ ─────────────► │ Moving Head │
│ Node │ └─────────────┘
└──────────────────┘
| Node | Hardware | Language | Role |
|---|---|---|---|
node_camera |
Raspberry Pi 4b (8 GB) | Rust | Reads LiDAR sensor (Benewake TF02-Pro), exposes distance via REST API, streams video via NDI |
node_controller |
Any PC/laptop | JavaScript (Electron) | Operator UI — gamepad input, live NDI video feed, faders for dim/zoom/CTO/focus, sends POST requests to server |
node_server |
Any PC (can be same as controller) | Python (Flask) | Receives controller input, fetches LiDAR distance, runs 3D vector calculations, outputs DMX universe via sACN |
The system uses a Cartesian coordinate system with the camera at the origin. Given:
- Camera pan (φ) and tilt (γ) angles
- LiDAR distance
‖b‖to the illumination point
It calculates the illumination point B using:
b₃ = ‖b‖ · sin(γ) (z coordinate)
b₂ = ‖b‖ · sin(φ) · cos(γ) (y coordinate)
b₁ = √(‖b‖² - b₃²) · cos(φ) (x coordinate)
Then, knowing the moving head position M, it derives the direction vector u = B - M and solves for the moving head's required pan/tilt. Results are converted to DMX values (0–255) with fine-resolution sub-steps.
- Rust — LiDAR sensor driver, REST API for distance data (chosen for low latency and memory safety)
- Python — Flask web server, NumPy vector math, Pandas CSV database, sACN DMX output, LRU-cached calculations
- JavaScript / Electron — Cross-platform operator desktop app, gamepad API, NDI video stream rendering
- Protocols — NDI (low-latency video), sACN / E1.31 (DMX over IP), HTTP REST (inter-node communication), DMX512
| Component | Purpose |
|---|---|
| Raspberry Pi 4b (8 GB RAM) | Camera node compute |
| Benewake TF02-Pro LiDAR | Distance measurement (up to 40 m, ±1–2 cm accuracy) |
| Converted broken moving head | Housing for the pan/tilt camera platform |
| GrandMA3 4-Port Node | sACN → DMX converter |
| Sony PS4 DualShock 4 | Operator gamepad (any gamepad with at least one joystick works) |
Total hardware cost: ~€300 (vs. ~€15,000 for a used commercial system like the Robe RoboSpot)
Requires: Raspberry Pi OS Lite (Bullseye), Rust toolchain, Benewake TF02-Pro connected via GPIO
cd node_camera
cargo build --releaseFor NDI video streaming, follow the setup in raspindi.
To run both services automatically on boot, create systemd services (see thesis documentation in docs/).
Requires: Python 3.x
cd node_server
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python server.pyCopy .env.example to .env and fill in your configuration before starting.
Requires: Node.js
cd node_controller
npm install
npm startEnter the server IP/port, show name and controller token in the GUI before use.
SpotSystem/
├── node_camera/ # Rust — Raspberry Pi LiDAR + NDI
├── node_controller/ # Electron — operator desktop app
├── node_server/ # Python/Flask — calculations, DMX, web UI
│ ├── server.py
│ ├── calculations.py # DMXCalculator class — all vector math
│ ├── hashing.py # password hashing for web UI auth
│ ├── database/ # CSV-based show and settings storage
│ ├── static/
│ └── templates/ # Flask HTML templates
└── docs/ # Thesis (German) + architecture notes
The server API includes three layers of access control on every controller request:
- Data completeness check
- Token validation (controller token vs. stored token)
- IP whitelist check (only the configured controller IP is accepted)
Web UI login is password-protected with hashed credentials stored in the CSV database.
Written as a Facharbeit (extended school research project) for the Computer Science Q1 course at Norbert-Gymnasium Knechtsteden, submitted March 2024.
