Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ESP-WFB-FPV Ground Station

A Windows ground station for FPV over WiFi (wfb-ng) using an ESP32-C5 USB radio dongle and a PC-side decoder. The PC runs wfb-gs: a tray application that talks to the dongle over a serial TLV protocol, decrypts wfb-ng video on the host, and publishes low-latency streams through ZLMediaKit (RTSP / WebRTC) plus in-browser WebCodecs and optional D3D11 native fullscreen.


ui.png

Overview

Layer Role
ESP32-C5 firmware WiFi monitor-mode capture, 802.11 frame forwarding, channel/MAC filtering, “seen remotes” discovery
Ground station (wfb-gs) Serial adapter management, wfb-ng decrypt + FEC, media muxing, HTTP/JSON-RPC UI, RTSP/WebRTC server
Web UI (web-ui) Vue 3 SPA: adapter control, listen/unlisten, live preview, media source browser, firmware flash

Decryption happens on the PC, not on the ESP32. The dongle is a radio front-end.


Architecture

flowchart TB
  subgraph Airside["Air side (OpenIPC / wfb-ng)"]
    TX["FPV transmitter\n(H264/H265 over wfb-ng)"]
  end

  subgraph Dongle["ESP32-C5 dongle (firmware/)"]
    WIFI["WiFi monitor capture"]
    TLV["USB Serial JTAG\nTLV protocol"]
    WIFI --> TLV
  end

  subgraph GS["Ground station — wfb-gs.exe (src/)"]
    ADAPT["RadioAdapterMgr\nEsp32UartRadioAdapter"]
    HUB["RadioDataHub"]
    DEC["WFBDecoder\n(FEC + Aggregator)"]
    ZLM["ZLMediaKit Muxer\nRTSP / WebRTC"]
    NALU["NaluWsHub\n/ws/nalu"]
    D3D["NativeD3dPlayer\nD3D11 fullscreen"]
    HTTP["HttpService\nUI + JSON-RPC"]
    RPC["Controller"]

    TLV --> ADAPT --> HUB --> DEC
    DEC --> ZLM
    DEC --> NALU
    DEC --> D3D
    HTTP --> RPC
    RPC --> ADAPT
    RPC --> DEC
  end

  subgraph UI["Web UI (web-ui/)"]
    VUE["Vue 3 SPA\nStatus · MediaSources"]
    WC["WebCodecs player"]
    RTC["WebRTC player"]
  end

  TX -.-> WIFI
  HTTP --> VUE
  NALU --> WC
  ZLM --> RTC
  RPC --> VUE
Loading

End-to-end data path

802.11 frames (WiFi air)
  → ESP32-C5 promiscuous capture
  → USB serial TLV (RSSI + channel + raw 802.11)
  → Esp32UartRadioAdapter → RadioFrame
  → RadioDataHub (dispatch by source MAC)
  → WFBDecoder::input
       → IEEE 802.11 payload extraction + channel_id match
       → wfb-ng decrypt + Reed–Solomon FEC (libsodium)
       → RTP depacketize → H.264 / H.265 NALU
  → parallel outputs:
       ① ZLM MultiMediaSourceMuxer  →  RTSP / WebRTC
       ② NaluWsHub                  →  WebSocket (WebCodecs in browser)
       ③ NativeD3dPlayer            →  D3D11 hardware decode + present

Default channel_id (must match the air side):

channel_id = (link_id << 8) | radio_port

OpenIPC default link_id is 7669206.


Repository layout

ESP-WFB-FPV/
├── src/                 # Ground station C++ source (wfb-gs)
│   ├── app/             # Entry, AppContext, tray, browser launcher
│   ├── ctrl/            # JSON-RPC Controller
│   ├── radio/           # Serial TLV, adapters, device poll, firmware flash
│   ├── wfb/             # wfb-ng decoder (FEC, Aggregator, IEEE 802.11 helpers)
│   ├── net/             # HTTP, WebSocket (/ws/nalu), RPC routing
│   ├── render/          # Native D3D11 player
│   └── util/            # MAC, RemoteId
├── firmware/            # ESP32-C5 firmware (ESP-IDF)
│   └── main/            # WiFi capture, serial protocol, status LED
├── web-ui/              # Vue 3 + Vite source
├── web/                 # Build output: single-file index.html (embedded in exe)
├── 3rd/                 # ZLMediaKit, libsodium, OpenSSL, libsrtp
├── conf/                # gs.default.json (copied next to exe on build)
├── tools/               # esptool.exe (bundled for OTA flash RPC)
├── build-all.ps1        # One-shot build: web-ui + wfb-gs + firmware
└── CMakeLists.txt       # Ground station build

Ground station components

AppContext (src/app/)

Central lifecycle and service wiring:

  • Binds HTTP on 127.0.0.1 (random port) with auth token
  • Starts RTSP on port 554 (falls back if busy)
  • Starts WebRTC UDP when ENABLE_WEBRTC is on
  • Owns RadioAdapterMgr, DevicePoller, embedded UI resource
  • Opens the UI in Edge/Chrome app mode (--app=http://127.0.0.1:<port>/?token=...)

Radio layer (src/radio/)

Component Purpose
RadioAdapter Abstract dongle: connect, set channel, start/stop capture, seen remotes
Esp32UartRadioAdapter ESP32 UART/JTAG implementation
RadioAdapterMgr Factory registry, COM port scan, hot-plug
TlvCodec Host-side TLV encode/decode (mirrors firmware/main/wfb_tlv.h)
RadioDataHub Fan-out captured frames to WFBDecoder listeners by source MAC
DevicePoller Periodic USB serial discovery (separate event thread)
FirmwareFlasher OTA flash via bundled esptool.exe

Connect sequence: RESET → VERSION → SET_WIFI_CHANNEL → START_LISTEN

WFB decoder (src/wfb/)

One WFBDecoder instance per active listen session:

  • Aggregator — wfb-ng packet reassembly + libsodium decrypt
  • fec.c — Reed–Solomon FEC (from fpv4win)
  • ZLM RtpCodec → Annex-B NALU frames
  • Registers media as app=wfb, stream=<remote_id>

Controller / JSON-RPC (src/ctrl/)

Single entry: POST /rpc?token=<token>

Namespace Methods
sys.* ping, getInfo (name, version, local_ip, rtsp_port, http_port)
radio.* listAdapters, connect, disconnect, setWifiChannel, getSeen, flashFirmware, flashStatus
wfb.* listen, unlisten
media.* list, nativeFullscreen, nativeFullscreenOff, nativeFullscreenStatus

Network & playback (src/net/, src/render/)

Path Consumer
GET / Embedded Vue UI
POST /rpc All ground-station control
WS /ws/nalu?stream=<id>&token= Browser WebCodecs (low latency)
POST /index/api/webrtc?app=&stream=&type=play Browser WebRTC (ZLM)
rtsp://<lan_ip>:554/wfb/<remote_id> External RTSP clients (VLC, etc.)
RPC media.nativeFullscreen D3D11 native window (ESC to exit)

ESP32-C5 firmware (firmware/)

Target: ESP32-C5 (USB Serial JTAG, WiFi 6).

Module File Role
Main app_main.c Boot, start WiFi + serial tasks
WiFi wfb_wifi.c Promiscuous mode, channel filter, MAC filter, seen table, TX
Serial wfb_serial.c TLV command/response handling
Protocol wfb_tlv.h, wfb_proto.h Shared protocol with host

The firmware does not decrypt wfb-ng. It forwards raw 802.11 MPDU payloads that match the configured filters.

Output: firmware/build/esp-wfb-fpv.bin


Web UI (web-ui/)

Vue 3 + Vite. Production build uses vite-plugin-singlefile → one self-contained web/index.html embedded in the exe (no external assets).

Layout

  • Sidebar — title FPV Station, tabs:
    • Status — adapters, remotes, connect/disconnect, listen/unlisten, WebCodecs players, log, firmware flash modals
    • MediaSources — all ZLM media sources, RTSP URLs, live preview grid

API layer

  • src/api/rpc.js — JSON-RPC transport
  • src/api/api.js — typed wrappers for every RPC method
  • src/composables/useGroundStation.js — adapter polling, sessions, players
  • src/composables/useMediaSources.js — media list + server info polling

Playback in the browser

Source Player Path
app=wfb WcPlayer.vue WebSocket NALU → WebCodecs
Other ZLM apps WebRtcPlayer.vue ZLM WebRTC API
Any wfb stream RPC Native D3D11 fullscreen

Key concepts

RemoteId

Identifies one wfb-ng logical link on the air side:

struct RemoteId {
    MacAddr src;      // transmitter MAC (OpenIPC WiFi MAC)
    uint32_t link_id; // default 7669206 (OpenIPC)
};

String form (ZLM stream id, session key):

57:42:75:05:d6:00_7669206

Multi-adapter

  • Each ESP32 dongle = one RadioAdapter (id = COM port name)
  • Multiple dongles can run in parallel on different channels
  • Each adapter can hold multiple listen sessions (different remotes)
  • MAC filters are merged per adapter (rebuildAdapterFilter)

Listen session

Created by wfb.listen with:

  • mac, link_id, adapter_id
  • keypair_b64 (wfb-ng rx.key)
  • codec: H264 or H265

Stopped by wfb.unlisten, adapter disconnect, or firmware flash.

Keypairs are saved under <exe_dir>/keys/listen_<remote_id>.key.


Build

Prerequisites

  • Windows: VS 2022 (x64), CMake 3.16+
  • Node.js: for web-ui
  • ESP-IDF 5.5+: for firmware (optional)

One-shot (recommended)

.\build-all.ps1

Options: -SkipWeb, -SkipGs, -SkipFirmware, -Config Release|Debug, -IdfPath d:\esp-idf-v5.5.5

Manual steps

REM 1. Web UI → single embedded HTML
cd web-ui
npm install
npm run build
cd ..

REM 2. Ground station
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release --target wfb-gs

REM 3. Firmware (ESP-IDF environment required)
cd firmware
idf.py set-target esp32c5
idf.py build

Outputs:

Artifact Path
Ground station build/bin/Release/wfb-gs.exe
Embedded UI web/index.html (built from web-ui/)
Firmware firmware/build/esp-wfb-fpv.bin

Web UI development

cd web-ui
npm run dev

Set VITE_GS_URL=http://127.0.0.1:<http_port> to proxy /rpc and /ws to a running wfb-gs instance.


Dependencies

Library Location Used for
ZLMediaKit 3rd/ZLMediaKit/ RTSP, WebRTC, RTP, HTTP stack
libsodium 3rd/libsodium/ wfb-ng crypto (static link)
OpenSSL 3rd/openssl/ WebRTC DTLS, self-signed cert
libsrtp 3rd/libsrtp/ WebRTC SRTP
Windows SDK D3D11, Media Foundation, Win32 serial, tray

ZLMediaKit is built as a CMake subdirectory with WebRTC and OpenSSL enabled; HLS/FFmpeg/player modules are disabled for a lean ground-station build.


Runtime

  1. Plug in ESP32-C5 dongle (VID 303A / PID 1001).
  2. Launch wfb-gs.exe — single-instance tray app; browser opens automatically.
  3. Status tab → Connect adapter → pick WiFi channel.
  4. Select a seen remote → Listen → choose rx.key, link_id, codec.
  5. Video appears in WebCodecs players; RTSP/WebRTC URLs are on the MediaSources tab.

HTTP listens on localhost only. RTSP binds on all interfaces (use sys.getInfolocal_ip + rtsp_port for LAN URLs).


License & credits

About

FpvStation: Receive wfb-ng WiFi broadcast (H264/H265) from OpenIPC via ESP32-C5 and achieve low latency playback.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages