Files
2026-08-03 16:37:20 +08:00

7.1 KiB

EdgeTX Web Simulator

Browser-based EdgeTX radio simulator using WebAssembly + WASI threads.

Prerequisites

Quick Start

npm install
npm run dev

Open http://localhost:5173 in Chrome, Edge, Safari, or any browser supporting SharedArrayBuffer and Atomics.waitAsync.

Building WASM Modules

The build requires wasi-sdk. The build script resolves it automatically:

  1. $WASI_SDK_PATH environment variable (if set)
  2. /opt/wasi-sdk/ (default install path, used in CI)
  3. Auto-download via cmake/FetchWasiSDK.cmake (fetched once, cached across builds)

From the EdgeTX repository root:

# Build all supported radios
tools/build-wasm-modules.sh

# Build specific radios only
FLAVOR="tx16s;t12;x9dp2019" tools/build-wasm-modules.sh

Output .wasm files are written to output/. Copy them to web/public/:

cp output/*.wasm web/public/

Supported Radios

The radios available in the web UI are defined in public/radios.json. This file is generated from the authoritative hardware definitions in radio/src/boards/hw_defs/. To regenerate it (e.g. after adding a new radio target):

node web/scripts/gen-radios-json.js

The script extracts inputs, switches, trims, keys, and display info from the hw_defs JSON files. Key left/right side placement matches Companion's layout.

Architecture

                    Browser Main Thread
 ┌─────────────────────────────────────────────────────┐
 │                                                     │
 │  App.svelte ──── WasmRunner ──── LcdRenderer        │
 │  (UI, controls)  (loader)        (WebGL canvas)     │
 │       │               │                             │
 │       │          SharedArrayBuffer                  │
 │       │          (analogs, LCD sync)                │
 │       │               │                             │
 │       ▼               ▼                             │
 │  AudioContext    WASM Worker Threads (WASI)         │
 │  (scheduled     ┌──────────────────────────┐        │
 │   playback)     │ worker.ts + FsProxyClient│        │
 │                 └────────────┬─────────────┘        │
 │                    SAB+Atomics (sync I/O)           │
 │                              │                      │
 │                 ┌────────────▼─────────────┐        │
 │                 │ FS Worker (fs-worker.ts) │        │
 │                 │ OpfsBackend (OPFS)       │        │
 │                 └──────────────────────────┘        │
 └─────────────────────────────────────────────────────┘

Lifecycle

  1. Radio selection — user picks a radio from the dropdown (persisted to localStorage). The FS Worker is spawned and OPFS is scanned for existing data. File uploads are available immediately.
  2. Run — WASM module is fetched, compiled, and instantiated. Worker threads are spawned with shared memory. The FS Worker's Atomics loop is started. The simulator begins running.
  3. Stop — firmware shutdown is signalled, worker threads are terminated after a grace period. The FS Worker stays alive for uploads.
  4. Radio switch — the old instance is torn down (FS Worker included), and a new one is initialized for the selected radio.

Key Components

  • WasmRunner — loads WASM, creates shared memory, manages the FS Worker and WASI thread pool. Exposes initFs() (spawn FS Worker), load() (compile + instantiate WASM), stopSim() (terminate threads), and stopFs() (terminate FS Worker).
  • FS Worker (fs-worker.ts) — dedicated worker that owns all OPFS state. Serves synchronous filesystem requests from WASM workers via SharedArrayBuffer + Atomics, and async UI requests (uploads, reads, wipe) via postMessage.
  • OpfsBackend (opfs-backend.ts) — in-memory directory tree backed by OPFS SyncAccessHandles. Provides a synchronous Node.js-like filesystem API.
  • FsProxyClient (fs-proxy-client.ts) — worker-side stub that implements fs.*Sync methods by writing requests to a shared buffer and blocking on Atomics.wait until the FS Worker responds.
  • LcdRenderer (lcd-renderer.ts) — WebGL-based renderer supporting RGB565 (16-bit color), 4-bit grayscale, and 1-bit monochrome (column-major) LCD formats.
  • Audio — worker threads relay PCM samples via postMessage. The main thread schedules them as AudioBufferSource nodes for gapless 32 kHz playback.

Key Files

File Description
src/App.svelte Main UI: radio selector, LCD display, controls, file management
src/lib/wasm-runner.ts WASM loader, FS Worker lifecycle, thread management
src/lib/worker.ts WASM worker thread entry point (WASI + thread init)
src/lib/fs-worker.ts FS Worker: OPFS owner, Atomics dispatch loop, UI file ops
src/lib/opfs-backend.ts OPFS-backed synchronous filesystem implementation
src/lib/fs-proxy-client.ts Worker-side blocking filesystem proxy
src/lib/fs-proxy-protocol.ts Shared protocol constants and serialization
src/lib/lcd-renderer.ts LCD framebuffer rendering (RGB565, 4-bit grayscale, 1-bit mono)
public/radios.json Radio definitions (generated — do not edit manually)
public/_headers COOP/COEP headers for production deployment
scripts/gen-radios-json.js Generates radios.json from radio/src/boards/hw_defs/

Browser Requirements

  • SharedArrayBuffer (requires COOP/COEP headers, configured in vite.config.ts and public/_headers)
  • Atomics.waitAsync — Chrome 87+, Safari 16.4+, Edge 87+, Firefox 145+
  • WebAssembly threads (shared memory)
  • Origin Private File System (persistent storage across sessions)

Development

npm run dev      # Start dev server with HMR
npm run build    # Production build to dist/
npm run preview  # Preview production build

Debugging

  • Filesystem tracing: Open browser console and run fsTrace = true to log all filesystem operations.
  • Trace window: The simulator UI includes a scrollable trace output showing firmware boot messages and runtime output.

Production Deployment

The dev server sets the required COOP/COEP headers automatically. For production, public/_headers provides them for platforms like Cloudflare Pages. For other hosts, configure your web server to set:

Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin

Cloudflare Pages

npm run build
npx wrangler pages deploy dist --project-name=edgetx-simulator