Skip to main content

Adaptive Link

Adaptive link is the feedback loop from the ground station to the air unit. It lets the transmitter react to link quality by changing video and radio behavior. The ground station reports what it is seeing; the air side decides how to react.

Inputs

openipc-rs records:

  • Realtek RSSI/SNR samples from packets matching the configured video channel,
  • WFB FEC recovered and lost counters,
  • packet-loss events that should request an IDR/keyframe burst.

The scoring logic follows the working PixelPilot/OpenIPC shape where it matters on the wire: Realtek raw RSSI is mapped into the 1000..2000 score range, FEC loss/recovery deltas drive fec_change, and the packet is sent periodically on the tunnel uplink. The app still exposes raw RSSI and SNR separately so the UI can show the underlying radio data rather than only the score.

Feedback Format

The feedback text follows the aviateur and standalone adaptive-link ground station format:

<gs_time>:<score>:<score>:<fec_recovered>:<lost>:<rssi>:<snr>:<num_ants>:<noise_penalty>:<fec_change>[:<idr_code>]\n

The text is prefixed with a 32-bit big-endian length. Nebulus queues that UDP payload through the same openipc-uplink smoltcp network used by SSH, which builds IPv4/UDP and applies the WFB tunnel's 2-byte IP-length prefix:

10.5.0.1:54321 -> 10.5.0.10:9999

That payload is encrypted, FEC-wrapped, converted to radiotap plus 802.11, and sent through the Realtek bulk-OUT endpoint on WFB tunnel/data uplink port 0xa0 (160 decimal), not the telemetry uplink port 0x90.

The default fec_change thresholds match PixelPilot:

Conditionfec_change
lost packets in the last second > 25
recovered packets > 304
recovered packets > 243
recovered packets > 142
recovered packets > 81

The transmitter-side adaptive-link process decides what those values mean for bitrate, GOP, FEC denominator, and keyframe requests.

The feedback loop above is the production protocol. Active probing is a separate commissioning and diagnostics tool that can measure operating headroom before choosing a policy:

  • wfb_tx --mcs-sweep MCS0,MCS2,MCS4,... changes the per-packet radiotap rate at a fixed dwell interval. A receiver can align delivery and SNR samples with the emitted dwell markers and select the highest rate that clears its target.
  • --thermal-poll-ms records transmitter thermal drift during the sweep. It is a safety input, not another link-quality score.
  • RealtekDevice::start_continuous_tx[_async] supplies a full-channel stimulus for spectral and thermal characterization. It is intentionally not used as a decodable link probe and should not be left running continuously.
  • retune[_async] plus read_rx_energy[_async] lets an application scan a channel list for a continuous rendezvous beacon after feedback is lost.

These are explicit building blocks. Neither the driver nor openipc-core silently changes channels, rates, receive chains, notches, or power behind an application's back.

Adaptive link is not the same thing as "the ground station automatically picks TX power." In OpenIPC setups, the feedback packet gives the air unit enough information to adjust behavior such as bitrate, FEC, and keyframe requests. Any actual policy on the transmitter side belongs to the air unit.

Ground-Side TX Power Override

Manual uplink TX-power override is implemented through Realtek TXAGC programming for RTL8812/RTL8821 tables, RTL8814 command writes, and Jaguar3 per-path references. Jaguar1 accepts indexes 0..=63; Jaguar3 uses its full 7-bit 0..=127 range. The API is exposed in native and browser paths, but still needs live on-air validation across adapter models.

In the station UI this is the "Uplink TX power" slider shown when adaptive link is enabled. In the CLI it is --alink-tx-power POWER, where POWER is a TXAGC index accepted by the driver.

Browser And Native Flow

Native:

RX bulk transfers -> adaptive counters -> smoltcp UDP -> WFB uplink -> nusb bulk OUT

Browser:

WebUSB RX -> Rust/WASM counters -> smoltcp UDP -> WFB uplink -> WebUSB bulk OUT

The feedback, userspace network, priority scheduler, packet aggregation, WFB construction, and retry policy are shared Rust. Only the USB submission and completion API is platform-specific. A frame is counted as delivered only after native nusb or WebUSB reports the full transfer and any required Jaguar1 terminating ZLP. Stalls, timeouts, and short writes retain the same encrypted frame bytes for a bounded retry.