Open FPV Link
A design for replacing the DJI O4 with an open digital video link: an OpenIPC air unit on the aircraft, WFB-ng over RTL8812 radios, and a Radxa ground receiver into HDMI goggles. Researched 2026-09-26. Nothing here is measured on our hardware yet; the last section says how to measure it.
Not the flight link. Ryan chose the Walksnail Avatar HD Moonlight kit on 2026-10-06, in place of the O4 and the RunCam WiFiLink 2 (see FPV cameras). MAVLink and the RTK corrections now go over the LoRa link. This page stays as the research for an open link, and its latency measurement method applies to the Walksnail too.
Why do it
Section titled “Why do it”The O4 is excellent and closed. It takes only DJI’s camera, has one antenna, and carries only video and its own OSD. An open link takes any supported camera, including the Raspberry Pi ones, has two antenna ports on the aircraft and as many receive cards on the ground as we like, and carries MAVLink next to the video. That last point could retire the SiK radio, whose antenna is the one we had to put outside the tail (see Antennas).
The cost was latency. Out of the box the open systems measure 60 to 100 ms against the O4’s 20. Most of that gap is buffering we can remove.
Where the latency goes
Section titled “Where the latency goes”The radio is not the slow part. Each stage between the sensor and your eye holds a whole frame, or a whole error-correction block, before it passes anything on. DJI streams slices of each frame through a chain it designed end to end. At 60 fps a frame is 16.7 ms, so every stage that waits for a full frame costs up to that much.
Published totals, from the OpenIPC wiki: about 60 ms at 720p60, 80 ms at 1080p60, 100 ms at 1080p30. No source publishes a stage-by-stage budget, so the split below is our estimate.
| Stage | At 60 fps, defaults | At 120 fps, tuned | What shortens it |
|---|---|---|---|
| Sensor exposure and readout | 8 to 17 ms | 4 to 8 ms | Frame rate, short shutter |
| Image processing (ISP) | 5 to 17 ms | 2 to 5 ms | isp.lowDelay |
| Encoder (H.265) | 5 to 17 ms | 3 to 8 ms | Frame rate, slices, intra refresh instead of large I-frames |
| FEC block wait | 0 to 17 ms | 0 to 3 ms | None with no loss; smaller fec_k and a fec_timeout cap it on loss |
| Air time | 5 to 12 ms | 2 to 5 ms | Higher MCS, wider channel |
| Receive, FEC and video decode | 3 to 9 ms | 3 to 6 ms | Hardware decode on the Radxa’s RK3566 |
| Wait for the display refresh | 8 to 25 ms | 5 to 15 ms | 100 or 120 Hz output, direct DRM scanout (PixelPilot_rk) |
| Goggle input lag | 1 to 30 ms | about 1 ms | Goggles with a zero-buffer HDMI input |
| Total | 60 to 100 ms | about 25 to 45 ms |
Three levers do most of the work:
- Frame rate. 120 fps halves every per-frame stage. The IMX335 sensor runs 1080p120 (and 144 fps
cropped with OpenIPC’s
waybeam_vencencoder); the IMX415 tops out at 1080p90 or 720p120. - The display path. A 60 Hz monitor adds up to a frame of waiting and many goggles buffer HDMI input. HDZero’s goggles claim 1 ms or less on HDMI at up to 720p100, and PixelPilot_rk puts the decoded frame straight on the display plane without a CPU copy.
- Air time. A 720p120 frame at 8 Mbps is about 8 kB, 12.5 kB after FEC. At the default MCS1 on 20 MHz that takes 7.7 ms to send; at MCS3, 3.8 ms. I-frames are several times larger, which is why intra refresh (spreading the refresh across frames) matters.
The best reported open figure is about 25 ms at 720p120 (Oscar Liang, OpenIPC APFPV), neither with a full configuration and method. Our realistic target is 30 to 40 ms, roughly DJI O3 class and 10 to 20 ms behind the O4. Note that DJI quotes the first moment the image changes on screen; an independent two-LED test put the O4 at 15 ms to first change and 25 ms for the whole frame. Measure both ways.
For this aircraft that gap matters less than for a racing quad. The TerraHawk flies its survey legs in assisted and autopilot modes, and the autopilot does the landings.
Proposed hardware
Section titled “Proposed hardware”Revised 2026-09-28 (th-main’s shortlist, docs/agent-threads/fpv-link/20260928T205407Z-th-main-camera-shortlist.md).
The OpenIPC Thinker can no longer be bought: since 2025-08-04 store.openipc.org redirects to Open
Collective, and no retailer lists it. Its schematic, Gerbers and STEP went public at
KennyPlus/Thinker338Q on 2026-09-22, and Ryan wants to
explore building a small run with th-eda. Until then the primary candidate is a finished unit.
| Item | Choice | Why | Price (2026-09-28) |
|---|---|---|---|
| Air unit, primary | RunCam WiFiLink 2 (Ryan, 2026-09-28) | SSC338Q with an IMX415 and a built-in RTL8812EU radio to 29 dBm, 2 x IPEX1. 1472 x 816 at 120 fps is its 720p120 route. M12 lens, 160 degrees. 19 x 19 mm camera on a 130 mm cable; 30.6 x 33 mm board on 25.5 mm holes; 25 g, 30 g with its fan. 9 to 22 V in, 15 W max | $129.99 at RunCam, in stock |
| Air unit, to build | OpenIPC Thinker Base, IMX335, plus a BL-M8812EU2 card | The IMX335’s 1080p120 mode, 2 to 6S input, 25 x 25 mm board. Needs the URLLC image for the RTL8812EU. M8 lens, not M12 | Not for sale; build from the published files |
| Alternatives | Emax Wyvern Link V2 800 mW (IMX415, cased, 48 g); Emax Wyvern Link 200 mW (13.8 g, a quarter of the power) | Same SoC and sensor | V2 sold out at Emax USA ($112.89); 200 mW $68.90 |
| Ground receiver | Radxa Zero 3W with PixelPilot_rk | RK3566 hardware H.265 decode, direct DRM display, 720p100 output | $20 to $66 |
| Ground radios | Two receive cards, RTL8812EU or ALFA AWUS036ACH | WFB-ng merges packets from every card | card-dependent |
| Goggles | HDZero BoxPro or Goggle 2 | Zero-buffer HDMI input at 720p100 | $299 (BoxPro) |
Sensors, from OpenIPC’s firmware source, not listings. The SSC338Q driver set covers several sensors, but each device image keeps only some: the RunCam, Emax and Caddx images keep the IMX415 only, and the URLLC image drops the IMX307. There is no low-light second camera to recommend: no compact IMX307 or IMX327 module is on sale, the IMX307 has no high-frame-rate mode, and each board has one camera port.
Where it goes on the aircraft
Section titled “Where it goes on the aircraft”The air unit replaces the O4 in the nose, behind the camera. The RunCam’s 19 x 19 mm camera fits the 21 mm window of the short nose cone and the 20 mm cradle on the Camera Arm with no spacers (th-blender, 1dbefba). Its board is on 25.5 mm holes, so an adapter replaces the O4 cradle on the Camera Arm (th-blender). The radio is on the same board, so there is no separate card. The manual says the nose-cone vents give the VTX some airflow in forward flight but recommends a fan, and this card must have one.
Keep both air antennas in the nose and put the diversity on the ground. The card has two ports and transmits on both at once. At 5.8 GHz a coax run to a winglet costs more than the spatial diversity gains: thin 1.13 mm coax loses roughly 3 to 4 dB per metre and RG316 about 1.5 to 2 (both estimates). So use the nose cone’s two rim notches (see Antennas), antennas about 90 degrees apart, and get spatial diversity where coax costs nothing, with two or three receive cards on separate antennas at the ground receiver. Never power the card without both antennas fitted.
The RunCam takes 9 to 22 V directly and draws at most 15 W, about 1.25 A from the 12 V video rail (the 12 V rail gives 2 A continuous since 2026-10-08, from a Matek Power Hub V2.1; it was a BEC12S-PRO at 5 A). A separately built Thinker with an external 800 mW card still needs the card’s own 5 V regulator, as below.
Telemetry and RTK over the same link
Section titled “Telemetry and RTK over the same link”WFB-ng carries MAVLink both ways next to the video (README, MAVLink mode), and ArduPilot takes RTK corrections over any MAVLink connection. Capacity is not a concern: RTCM at 1 Hz is a few kbit/s. Two details matter. Use the MAVLink stream, not the IP tunnel, with MAVLink FEC at 1/2, because the tunnel stalls when it shares video’s 8/12 FEC (sbc-groundstations #106). And put the ground receiver at the hangar, next to the RTK base, so the corrections go straight into it.
The catch is a shared failure: at the edge of 5.8 GHz range video, telemetry and RTK go together, and 915 MHz carries further and around obstacles. Keep the SiK radio until the open link’s range is measured on this airframe, then decide.
Running it as a ham
Section titled “Running it as a ham”A licence opens the 5650 to 5925 MHz amateur segment at more power than Part 15 allows, but four rules shape how.
| Rule | What it means here |
|---|---|
| No messages that obscure meaning (§97.113(a)(4)) | WFB-ng’s encryption cannot be switched off. A published key may argue intent, but the FCC has not ruled on it. The clean paths are a null-cipher patch to WFB-ng, or RubyFPV, which removed link encryption |
| Identify every 10 minutes and at the end (§97.119) | Neither system has a callsign option. A callsign burned into the video, or a periodic plaintext ID frame, are the likely methods; neither is confirmed compliant |
| Band | 5650 to 5925 MHz is secondary, so do not interfere. Standard Wi-Fi channels 132 to 177 already sit inside it. Avoid 2390 to 2450 MHz, which would clash with the ExpressLRS control link |
| No pecuniary interest (§97.113(a)(3)) | Paid survey work cannot use Part 97. Commercial flights need Part 15 limits and certified hardware |
Power: use the least that does the job. The card’s 800 mW is already well past what the link needs at visual-line-of-sight range; the ham licence mainly buys headroom and the freedom to use a high-gain tracking antenna on the ground.
Avionics decisions
Section titled “Avionics decisions”Reviewed by the avionics session on 2026-09-26. The four items above stand as proposed, with the rules below added so the link fits the rest of the system.
Which UART. The O4’s DisplayPort port is freed when the O4 goes, and the air unit takes it:
SERIALn_PROTOCOL 2 (MAVLink2), 115200 baud or whatever WFB-ng’s MAVLink mode is set to on the
air side, and the SRn_ stream rates raised so the ground OSD has attitude and position at 10 Hz.
The first-flight FC is now the Lumenier LUX F765 (th-avionics, 3c9f3f6). Its HD VTX port carries
UART4 as SERIAL6 for the O4’s DisplayPort, so the open air unit takes SERIAL6. Pin 6 of that port
is the receiver’s RX7, so the video unit’s SBUS wire must not land there. th-avionics owns the UART map.
Power. The LUX F765’s HD VTX port has a power pin at 9 to 10 V, 2 A. That suits the DJI O4,
which DJI rates at 3.7 to 13.2 V and which draws about 6 W (DJI,
Oscar Liang). It does not suit the RunCam WiFiLink 2:
9 V is the bottom of its 9 to 22 V range, and a sag during transmission resets it. So the open air
unit runs from a switched 12 V rail. Since 2026-10-08 (Ryan) that rail is the 12 V output of a Matek Power Hub V2.1, 2 A
continuous, input 7 to 26 V (th-main). Before that it was a Matek BEC12S-PRO, and the rest of this paragraph is that design: 9 to 55 V in, 12 V at 5 A continuous (9 A peak), 35 x 24 x 5.5 mm, 5 g
(Matek). It needs 14 V in for a stable 12 V, and a
6S pack at 3.0 V per cell still gives 18 V. It has no enable input (Matek’s page lists none; an
earlier version of this page said it had, from a third-party summary, and was wrong). The switch is
therefore a Pololu Mini MOSFET Slide Switch SV (#2811) on the BEC’s 12 V output: 4.5 to 32 V, 2.0 A
continuous at 55 °C, a logic input “ON” that switches on above about 1 V (Pololu).
The video loads draw at most about 1.25 A at 12 V (the RunCam WiFiLink 2’s 15 W), so it mounts beside the
BEC in the bay, away from the video unit’s heat, and a load above about 1.6 A needs a bigger switch
(th-telemetry, 2026-09-28).
The FC drives “ON” from AUX 4 as relay 2, with RELAY2_DEFAULT 1 so the video comes on at boot. The
input is off when open, so a broken control wire turns the video off; the board’s slide switch in its
“on” position overrides the FC and keeps the video on, for a bench or a flight where switching is not
wanted. Put a 470 uF low-ESR capacitor across the rail at the video unit; users report noise on 6S
without one. Feed the video unit’s input from this 12 V (the O4, the RunCam WiFiLink 2, or the Thinker’s 2 to 6S input). The radio card gets its own 5 V, 3 A buck from
that 12 V rail, so its 1.8 A peaks never touch the Thinker’s 3 A BEC or the FC’s 5 V. That rail is
switched by the MOSFET switch from the FC, so the FC can shut the whole link down on the pad while
charging, and the hangar can bring it up before a launch. Budget: about 13 W for the card at full
power plus the air unit, against roughly 5 W for the O4. At 6S that is about 0.55 A, a minute or
two of endurance.
One link carries RTK corrections at a time. ArduPilot reassembles GPS_RTCM_DATA fragments
by sequence number without regard to which link they came from. Two independent streams of the
same corrections interleave and corrupt each other. While the SiK stays, the dock controller sends
corrections over the SiK only, and the WFB-ng ground receiver at the hangar carries video and
telemetry but must not inject RTCM. If the SiK is ever retired, the corrections move to WFB-ng and
the SiK path is disabled, not left running alongside.
An open uplink needs MAVLink2 signing. With WFB-ng’s cipher nulled for Part 97, anyone in range can send MAVLink to the aircraft, and MAVLink carries arm, mode and mission commands. Turn on MAVLink2 signing on that serial port. Signing appends an authentication code and leaves every byte of the message readable, so it does not obscure meaning the way encryption does. That is our reading of §97.113(a)(4), not a ruling. The ELRS control link is separate and stays as it is.
GCS failsafe. The FC treats every MAVLink link as a GCS and FS_GCS_ENABL acts on the loss of
all of them. Both ground ends, the SiK and the WFB-ng receiver, live at the hangar, so the failsafe
behaves as one link. If a second WFB-ng receiver is ever carried in the field, the failsafe timing
needs a second look.
The nose is shared with the airspeed sensor. The DroneCAN airspeed board and its pitot tube also live in the nose now. It is a sensitive differential-pressure front end. Keep it and its CAN cable on the opposite side of the nose from the two antennas and the fan, and confirm on the bench that airspeed noise does not rise when the card transmits at full power.
Camera module fit. The O4 (non-Pro) camera is 12.36 mm and uses the 15 mm nose cone with spacers. The RunCam and Emax 19 x 19 mm cameras fit the 21 mm cone directly. The Thinker’s 14 x 14 mm IMX335 module fits the 15 mm cone with about 0.5 mm a side (estimate). Measure the real part before printing.
The aft bay (resolved 2026-09-28). The ELRS receiver and the SiK radio now sit on the Avionics Plate, and their antennas are in the keels (see Antennas).
How to measure it
Section titled “How to measure it”Before trusting any number above, measure glass-to-glass on the bench:
- Put an LED in front of the air unit’s camera, driven by a button or a square wave.
- Film the LED and the goggle screen together with a phone at 240 fps or faster.
- Count frames from the LED lighting to the screen changing, and to the full frame updating.
- Repeat at 60, 100 and 120 fps, with and without intra refresh, and near the edge of range.
The same test on the DJI O4 gives a like-for-like comparison.
Recommended starting configuration
Section titled “Recommended starting configuration”- Air: RunCam WiFiLink 2 with a Commonlands CIL292 lens (2.3 mm f-theta M12, IR-cut; Ryan,
2026-09-29). Default sensor mode: IMX415 1080p90, binned from the whole sensor, encoded at
1280 x 720 at 90 fps. That keeps the full field of view (about 139 x 79 degrees, estimate)
for piloting. The 1472 x 816 at 120 fps mode is a crop (about 110 x 61 degrees, estimate): it saves
about 3 ms per frame-holding stage, but loses a fifth of the width and a quarter of the height.
Test both (step 4 of the method); take 120 fps only if the latency gain is worth the view.
H.265 CBR at 8 to 12 Mbps,
lowDelayand slices (orwaybeam_vencwith intra refresh), MCS2 to 3 at 20 MHz, STBC and LDPC on, video FEC 8/12 withfec_delay = 0, MAVLink FEC 1/2, MAVLink2 from the LUX F765’sSERIAL6. - Ground: Radxa Zero 3W, PixelPilot_rk at 1280 x 720 at 100 Hz, two receive cards, HDZero goggles over HDMI. Match the display rate to the camera where the goggles allow it (90 fps into a 100 Hz display judders, as 120 into 100 drops frames). If they do not, test 90 fps against 60.
- Expect 30 to 40 ms glass-to-glass in clean conditions, rising with I-frames, packet loss and heat.
Source: docs/fpv-link.md in the repo. Edit it there; this page is generated.
Comments
Comment on the whole page here, or on one section or step with the button beside it.