FreeCLinker as in Free Cam Linker. Pronounced freak linker because we know you bunch of freaks like to film yourself doing illegal stuff. ESP32 firmware that connects a DJI Action or GoPro camera to a Betaflight flight controller.
→ sheeprine.github.io/freeclinker — project site (flash firmware, configure over Web Serial, browse supported cameras) → Quickstart guide — wiring, build, flash, and web interface setup. The ESP32 bridges the two devices: it receives camera telemetry over BLE and forwards it to the FC via MSP serial, and automatically starts/stops camera recording when the FC arms or disarms.
DJI Action / GoPro Camera ←—BLE—→ ESP32 ←—MSP Serial—→ Betaflight FC
- The ESP32 scans for a supported camera and establishes a BLE connection.
- It subscribes to camera status updates, receiving battery percentage, recording state, and more.
- It polls the flight controller every 100 ms for arm state via
MSP_STATUS. - On arm, it sends a recording-start command to the camera. On disarm, recording stops.
- Battery and recording state are continuously forwarded to Betaflight as OSD telemetry.
| Data | MSP message | OSD location |
|---|---|---|
| Battery voltage, current, capacity, temperature | MSP2_CAMERA_BATTERY (0x3001) |
— |
| Battery percentage | MSP2_SET_TEXT |
Custom Message 1 (default: CAM: 75%) |
| Recording state / time | MSP2_SET_TEXT |
Custom Message 2 (default: REC 0:42 / IDLE / CAM:HOT) |
| Mode, resolution, FPS, stabilisation | MSP2_SET_TEXT |
Custom Message 3 (default: VID 4K/60 RS+) |
| Remaining record time and SD free space | MSP2_SET_TEXT |
Custom Message 4 (default: 30m 15.2G) |
All four custom messages are user-configurable templates (see set osd1–osd4 below).
Betaflight 4.5: the Custom Message 1-4 OSD fields were added in BF2025.12.1 and don't exist on 4.5. Enable bf45_compat to send telemetry through the Pilot Name and Craft Name fields instead, which BF4.5 does have (see set bf45_compat/pilot_tpl/craft_tpl below).
Connect the ESP32 to a free Betaflight FC UART. Pin numbers differ by board:
ESP32-C3 Super Mini
| ESP32-C3 pin | FC pin |
|---|---|
| 5V | 5V (BEC out) |
| GND | GND |
| IO4 (TX) | UARTx RX |
| IO5 (RX) | UARTx TX |
Standard ESP32 Dev Board
| ESP32 pin | FC pin |
|---|---|
| VIN (5V) | 5V (BEC out) |
| GND | GND |
| GPIO17 (TX) | UARTx RX |
| GPIO16 (RX) | UARTx TX |
Configure the FC UART for MSP at 115200 baud. See QUICKSTART.md for wiring diagrams.
A 3D printable case for the ESP32-C3 Super Mini is available at 3d/esp32_c3_sm_freeclinker v2.3mf (3MF format).
All tunable parameters are in include/config.h:
| Constant | Default | Description |
|---|---|---|
BF_TX_PIN / BF_RX_PIN |
17 / 16 | GPIO pins for MSP serial |
BF_BAUD |
115200 | MSP serial baud rate |
BLE_SCAN_DURATION_SECS |
5 | BLE scan window |
BLE_RECONNECT_DELAY_MS |
3000 | Backoff after failed connection |
MSP_BATTERY_KEEPALIVE_MS |
2000 | Battery telemetry resend interval |
Runtime settings are changed via the USB serial console and persisted across reboots:
| Command | Default | Description |
|---|---|---|
set camera_type <0-5> |
0 | Camera protocol: 0 = DJI, 1 = GoPro, 2 = Caddx Orca, 3 = Sony Alpha, 4 = Blackmagic, 5 = Insta360 (reboot to apply) |
set camera_match <0-2> |
0 | Camera matching strategy: 0 = fallback (preferred if found, else any eligible camera), 1 = strict (preferred only), 2 = best_signal (ignore preferred, connect to whichever eligible camera has the strongest RSSI) |
set wake_guard <0|1> |
1 | GoPro only. When 1, don't connect to a camera whose advertisement shows it's asleep/powered-down (a connection attempt would wake it). Bypassed for a manually selected camera (cameras connect <idx>) |
set stop_on_disarm <0|1> |
1 | Stop recording on FC disarm |
set disarm_delay <ms> |
0 | Delay between disarm and recording stop |
set aux_channel <0-12> |
0 | AUX channel for camera mode switch (0 = off) |
set aux_mode <0x##> |
0x00 | Camera mode when AUX is high (0x00=slow-motion, 0x01=video, 0x0A=hyperlapse). On GoPro, when already recording, AUX high/low triggers Burst Slo-Mo instead of switching presets. |
set osd1 <template> |
CAM:{bat} |
OSD Custom Message 1 template |
set osd2 <template> |
{rec}{recdur} |
OSD Custom Message 2 template |
set osd3 <template> |
{mode} {res}/{fps} {eis} |
OSD Custom Message 3 template |
set osd4 <template> |
{rleft} {rcap} |
OSD Custom Message 4 template |
set bf45_compat <0|1> |
0 | Betaflight 4.5 has no Custom Message 1-4 OSD fields. When 1, osd1–osd4 above stop being sent and pilot_tpl/craft_tpl are sent to Pilot Name/Craft Name instead |
set pilot_tpl <template> |
CAM:{bat} |
Pilot Name template — sent only when bf45_compat=1 |
set craft_tpl <template> |
{rec}{recdur} |
Craft Name template — sent only when bf45_compat=1 |
OSD template tokens: {bat} battery %, {rec} recording state, {recdur} recording duration (blank unless recording), {mode} camera mode, {res} resolution, {fps} frame rate, {eis} stabilisation, {rleft} SD time remaining, {rcap} SD space remaining.
Action commands:
| Command | Description |
|---|---|
status |
Report firmware version, uptime, free heap, WiFi AP state, and camera type/connection/telemetry |
record start |
Start camera recording immediately, independent of FC arm state |
record stop |
Stop camera recording immediately, independent of FC arm state |
reboot |
Restart the ESP32 |
Type help in the serial console to list all commands, show to print current settings.
There are two ways to reach the configuration UI:
Built-in WiFi AP (any browser, no cable)
If no camera connects within 30 seconds of boot, the ESP32 automatically starts a WiFi access point:
- SSID:
FreeCLinker(open, no password) - URL:
http://192.168.4.1
Connect your phone or laptop to the FreeCLinker network and open that URL. The AP shuts down as soon as a camera connects and restarts 30 s after a camera disconnects.
USB Serial (Chrome / Edge desktop)
→ sheeprine.github.io/freeclinker/config.html — hosted config UI
Or open web/config.html locally (no server required). Connect to the ESP32 via the browser's Web Serial dialog to get a graphical config panel and an interactive CLI terminal.
→ sheeprine.github.io/freeclinker/flash.html — no toolchain required
Select your board (ESP32 Dev Board or ESP32-C3 Super Mini), click Connect & Flash, and pick the serial port. The page fetches the latest firmware release automatically.
ESP32-C3: hold the BOOT button while plugging in to enter download mode.
Requires PlatformIO.
pio run # Build
pio run --target upload # Flash to ESP32
pio device monitor # View serial debug outputAny DJI Action camera that advertises BLE manufacturer ID 0x08AA with marker byte 0xFA, or whose device name contains "DJI Action". Uses the DJI proprietary BLE protocol (GATT service 0xFFF0). Status is pushed at 2 Hz once subscribed.
Any GoPro camera supporting the Open GoPro BLE API (HERO 9 and later), identified by advertised service UUID 0xFEA6. Uses standard TLV-encoded commands and registers for push notifications on status changes.
Older cameras that predate the preset-group scheme (HERO4/5 Session) are also supported: recording, mode switching, and telemetry fall back to that generation's status IDs and capture-mode command automatically, no configuration needed.
A powered-down GoPro keeps advertising for remote-wake, and a plain BLE connection attempt would wake it — undesirable on an FPV quad, where the pilot may have deliberately left a mounted camera off. With wake_guard on (the default), the ESP32 recognizes known sleep/awake advertisement states (currently HERO11 Mini and MAX2) and only auto-connects to a camera that's genuinely awake; unrecognized advertisement formats are logged but never auto-connected. Manually selecting a saved camera (cameras connect <idx> / hosted UI) always bypasses this filter. Disable with set wake_guard 0 if this misidentifies your camera generation.
To use a GoPro, connect via serial and run:
set camera_type 1
Then reboot the ESP32. To switch back to DJI: set camera_type 0 and reboot.
The Orca has no BLE pairing flow — it's controlled over the Wi-Fi network it creates itself, the same one the CaddxFPV app joins from a phone. Configure it via serial:
set camera_type 2
set caddx_ssid <the Orca's SSID>
Use wifi scan beforehand to find the SSID if you don't already know it. On boot, the ESP32 tries the factory default password (12345678) first; if the camera doesn't have that password, it'll log a message after ~30s asking you to set the real one:
set caddx_pass <password>
Reboot after either command to apply.
Just like DJI/GoPro cameras, every SSID the ESP32 successfully connects to is remembered in the camera list (cameras list) — configure multiple Orcas over time and switch between them with cameras connect <idx> (also requires a reboot, since Caddx has no live rescan to act on a selection while running).
Any Sony Alpha camera advertising manufacturer ID 0x012D with device type 0x0300 (most bodies also advertise a name starting with ILCE). Uses Sony's undocumented BLE remote-button protocol — start/stop simulates the physical record button, so there's no explicit mode-switch command (switchCameraMode is unsupported).
set camera_type 3
The first connection requires bonding: open Bluetooth Rmt Ctrl in the camera's menu before it will pair (Just Works — no PIN to enter). Reboot after setting the camera type. Only recording state, focus, shutter, and battery are reported; no resolution/fps/stabilisation telemetry is available from this protocol.
Any Blackmagic Design camera advertising the Blackmagic Camera Service (BLE UUID 291d567a-...). Uses the official Blackmagic Camera Control Protocol — the same one the cameras speak over SDI — re-exposed as BLE GATT characteristics.
set camera_type 4
Reboot after setting the camera type. The first connection requires pairing: the camera displays a 6-digit PIN on its own screen, which you must type into the USB serial console when the firmware prompts for it (there's no way to automate this — the ESP32 has no display). This only happens once; the bond is remembered afterwards. Only recording start/stop is supported (switchCameraMode is unsupported — these cameras have no separate photo/video mode) and no battery/resolution/fps/stabilisation telemetry is exposed over BLE for this camera family.
Identified by the advertised BLE camera-control service (UUID 0000be80-...) where the camera includes it in its advertisement, or — best-effort, since Insta360 publishes no scan-identification info — an advertised name that looks like a known model. Uses the same simple BLE command channel the official phone app speaks, no bonding required.
set camera_type 5
Reboot after setting the camera type. Only recording start/stop is supported (switchCameraMode is unsupported — the protocol only exposes separate "start recording in mode X" commands, not a stateless mode switch), confirmed via the camera's ack for each command rather than a pushed status. Battery is reported if the camera exposes the standard Bluetooth Battery Service; no resolution/fps/stabilisation telemetry is available (the camera's own status messages use a protobuf schema that isn't publicly available to decode).