← Agentic AI & Automation
Open source · Event & venue capture

Camera System — Planica Zipline & Adventure Parks

A camera system built for two use cases: Planica (Slovenia), the world’s largest ski jump, where GoPro Max 360° cameras record zipline descents; and adventure parks, where GoPro Hero 12+ cameras automatically photograph guests. Everything is centrally managed over MikroTik routers and WireGuard VPN tunnels, with a Raspberry Pi on-site to trigger and monitor cameras.

Python Raspberry Pi Bluetooth LE WireGuard MikroTik PowerShell
Planica · venue display (tablica)
⚡
Built with agentic AI. Development on this project runs through multi-agent Claude Code workflows — developer → independent-reviewer cycles with evidence gates, batched into unattended runs that execute overnight and resume from committed ledgers. How the agent system works →

How it works

At Planica, a Raspberry Pi continuously scans for known GoPro Max cameras over Bluetooth; when a camera comes into range it sends the BLE start-record command — staff just power the cameras on and recording starts itself. Footage syncs to a PC, where a PowerShell + ExtendScript pipeline drives Adobe Premiere to cut and export each guest's clip automatically. At adventure parks, Hero 12+ cameras shoot automatic guest photos. Every remote site is reachable from one place over WireGuard tunnels between MikroTik routers — SSH, file pull, monitoring and updates, as if on the same LAN.

Planica — zipline 360° recording

  • A Python service on the Pi scans for known GoPro MAC addresses; a camera that (re)appears or regains signal strength gets the BLE “start record” write.
  • Multiple cameras supported; failed triggers escalate to email alerts after retries; systemd keeps the service alive.
  • New .360 exports are picked up by a watch folder that launches the Premiere auto-edit and export for that guest's code.

Central management & monitoring

  • WireGuard tunnel between venue and centre MikroTiks; the Pi has a stable private IP over the tunnel.
  • A watchdog pings the Pi and emails on outage — with a “back online” all-clear when the site recovers.
  • The same VPN carries SSH, footage transfer and configuration, so no site visit is needed for routine ops.

The hard parts

Behind the "cameras start themselves" behavior sits real engineering: signal processing on camera telemetry, reverse-engineering an undocumented 360° projection, and making remote embedded hardware field-replaceable.

Gyroscope-driven ride detection

The auto-edit needs the ride's true start and finish inside a long recording. The pipeline extracts the GoPro's GPMF telemetry, computes angular-velocity magnitude at 800 Hz, and applies dual rolling windows (1 s and 5 s) with a reset rule so pre-ride handling never false-triggers. No manual scrubbing, ever.

360° reprojection in stock FFmpeg

Instead of a custom FFmpeg fork, the GoPro MAX EAC→equirectangular projection was ported to NumPy and emitted as remap lookup tables for stock FFmpeg. GoPro documents neither the orientation-quaternion convention nor FFmpeg's Euler order, so three independent calibration scripts recover the exact rotation empirically — one fits it over ~130k pixels with no convention assumptions at all.

Bluetooth reliability & replaceable hardware

  • Radio role separation: a USB dongle scans while the onboard radio does the GATT writes — adapters resolved by bus type, never by hciN index, which swaps between reboots.
  • BD_ADDR cloning: GoPros check the paired Bluetooth address, not a host key — so a replacement Pi presents the original's address and all 14 cameras accept it with zero re-pairing.
  • Zero-touch provisioning: one idempotent setup script rebuilds any SD card — service, auto-updater and sudoers included.

Operational hardening & human factors

  • Four independent watchdog layers for a remote LTE-only site: router ping watchdog, LTE-bounce script, nightly reboot, and a ping monitor on the Pi.
  • The GoPro export app is driven via Windows UI Automation by element name and live bounding box — surviving moved windows, resolutions and DPI scaling.
  • Guest redemption codes use a 29-character alphabet with every visually confusable glyph removed, collision-checked against full history — the code is printed, spoken and typed by the public.

Source code

Both halves of the Planica system are public on GitHub. The delivery PC and the capture Pi are separate repos because they deploy to different machines and fail in different ways.

Fougl/pc_planica

The delivery half: the Windows pipeline that pulls each 360° recording, finds the ride in the GoPro telemetry, reframes it, edits it into the Premiere Pro template and exports it under the guest’s ride code. Retries and self-monitoring wrap every stage, and the same repo drives the photo-capture site.

Start with final_rpi.ps1 (the per-site pipeline), start_finish.py (ride detection from the gyroscope) and gopro_exporter_test/ (the UI-Automation driver and the stock-ffmpeg reprojection prototype).

Open on GitHub ↗

Fougl/rpi_planica

The capture half: a systemd service on the Raspberry Pi that scans for the cameras over Bluetooth LE, arms a camera when it comes back into range and escalates by email after repeated failures. The README documents the two-radio design and the watchdog for a failure that raises nothing.

Start with py_new.py (scanner, controller, watchdog and retry) and bt_adapters.py (which radio scans and which one writes, resolved by USB bus rather than index).

Open on GitHub ↗