⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Conversation

@sensei-hacker
Copy link
Member

@sensei-hacker sensei-hacker commented Feb 7, 2026

User description

This allows demo mode to work without a needing a platform-specific sitl.exe.
This is one step to allowing Configurator to load as a PWA if desired. It runs successfully in Configurator, including saving settings, rebooting, etc.

Scavanger's implentation is probably better overall #11282

I am putting this here mostly for comparison and there are a couple of things in this implementation that might be added to PR #11282 .

Scavanger's handling of PGs looks significantly better (though I haven't actually tested his to see that it works). Also the pthread support may be better (I used ASYNCIFY).

The serial communication in this implementation is "interrupt driven", while the other uses polling.
This implementation is more similar to how hardware is handled - the hardware triggers an interrupt when data is available. In my wasm implementation, that interrupt is of course triggered by software, but otherwise works like a hardware interrupt, the same as a hardware FC.

The approach to rebooting used in PR #11282 didn't work for me when I had tried it, so I used a different method for reboot.

Lastly, I added -gsource-map to wasm.cmake so that you can debug in the C code in the browser. This is very helpful for debugging and has no runtime cost.


PR Type

Enhancement, Tests


Description

This description is generated by an AI tool. It may have inaccuracies

  • Enables SITL (Software In The Loop) to run as WebAssembly (WASM) in-browser and within Configurator without requiring platform-specific executables

  • Allows demo mode functionality to work without needing sitl.exe or other platform-specific binaries

  • Implements interrupt-driven serial communication similar to hardware FC behavior, where interrupts are triggered by software events

  • Adds source map support (-gsource-map in wasm.cmake) for debugging C code directly in the browser with no runtime cost

  • Supports essential operations including settings persistence and device rebooting within the WASM environment

  • Provides an alternative implementation approach for comparison with other WASM SITL implementations, featuring different handling of parameter groups (PGs) and threading models

  • Uses ASYNCIFY for pthread support rather than alternative approaches

  • Implements custom reboot mechanism that differs from polling-based approaches

  • Represents a step toward enabling Configurator to function as a Progressive Web App (PWA)


Diagram Walkthrough

flowchart LR
  A["Traditional SITL<br/>Platform-specific exe"] -->|"Replaced by"| B["WASM SITL<br/>Browser/Configurator"]
  B -->|"Enables"| C["Demo Mode<br/>No exe needed"]
  B -->|"Supports"| D["Debugging<br/>Source maps"]
  B -->|"Features"| E["Interrupt-driven<br/>Serial I/O"]
Loading

File Walkthrough

Relevant files

Add CMake infrastructure for building INAV as WebAssembly:
- New wasm.cmake toolchain file for Emscripten compiler
- WASM-specific settings in sitl.cmake (ASYNCIFY, exports, memory growth)
- Build type detection in settings.cmake
- Emscripten SDK path checks in wasm-checks.cmake

This enables building SITL firmware that runs in web browsers.
Modify parameter group system to support WASM lazy allocation:
- Add wasmPgEnsureAllocated() call in PG accessor macros for __EMSCRIPTEN__
- Include wasm_pg_runtime.h header when building for WASM
- Restore ZERO_FARRAY macro in config_streamer_ram.c

In WASM builds, PG memory cannot be allocated by the linker at
compile-time, so accessor macros trigger runtime allocation on
first access.
Integrate WASM support into flight controller core:
- main.c: Call wasmMspProcess() in main loop for MSP handling
- fc_init.c: Initialize WASM PG system and serial port at boot
- fc_core.c: Use emscripten_force_exit() for clean WASM reboot
- config.c: Support WASM PG initialization in config system
- serial.c: Register WASM virtual serial port

These hooks enable the firmware to run as WebAssembly while
maintaining compatibility with native SITL builds.
New serial driver that communicates over WebSocket for SITL:
- Enables configurator PWA to connect to native SITL via WebSocket
- Implements standard serialPort_t interface (read, write, available)
- Supports both WASM and native SITL builds
- Ring buffer based TX/RX with configurable sizes

This allows the web-based configurator to connect to SITL running
either natively (via WebSocket) or in-browser (via WASM).
Complete WASM SITL implementation for running firmware in browsers:

Serial transport (serial_wasm.c/h):
- Virtual serial port with ring buffers for JS<->WASM communication
- Exported functions: serialWriteByte, serialReadByte, serialAvailable
- Interrupt-style callback notification when responses ready

MSP bridge (wasm_msp_bridge.c/h):
- Connects WASM serial port to standard MSP infrastructure
- Reuses existing mspSerialProcessOnePort() - zero MSP code duplication

Parameter group system (wasm_pg_registry.c/h, wasm_pg_runtime.c/h):
- Manual PG registry (linker script not supported in WASM)
- Lazy memory allocation on first PG access
- Handles both system configs and profile arrays

Platform stubs (wasm_stubs.c):
- Stub implementations for hardware-dependent functions
- Time functions using emscripten_get_now()

Target config (target.c):
- WASM-specific target initialization
Testing and development utilities for WASM SITL:

Test harness (src/test/wasm/):
- Browser-based MSP test interface
- Sends MSP commands and displays responses
- Useful for validating WASM build without full configurator

PG registry generator (src/utils/):
- Script to generate wasm_pg_registry.c from PG declarations
- Scans source for PG_REGISTER macros
- Outputs manual registry for WASM builds
Implement persistent storage for WASM SITL settings using browser
IndexedDB. Settings now survive page reloads.

Firmware changes:
- Add wasm_eeprom_bridge.c/h exposing EEPROM buffer to JavaScript
- Add wasmNotifyEepromSaved() callback in config_streamer_ram.c
- Export EEPROM functions and HEAPU8/callMain in cmake/sitl.cmake

The JavaScript side (in configurator) loads stored EEPROM data from
IndexedDB before calling main(), allowing the firmware to initialize
with previously saved settings.
@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

All compliance sections have been disabled in the configurations.

Comment on lines +472 to +474
uint8_t payload[WS_MAX_PACKET_SIZE];
size_t payload_len = 0;
ssize_t consumed = ws_decode_frame(port, buffer, recvSize, payload, &payload_len);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: In wsReceive, reduce the payload buffer size from WS_MAX_PACKET_SIZE to WS_BUFFER_SIZE to prevent a potential stack overflow, as the received data chunk is limited to WS_BUFFER_SIZE. [possible issue, importance: 9]

Suggested change
uint8_t payload[WS_MAX_PACKET_SIZE];
size_t payload_len = 0;
ssize_t consumed = ws_decode_frame(port, buffer, recvSize, payload, &payload_len);
static ssize_t ws_decode_frame(wsPort_t *port, const uint8_t *data, size_t len, uint8_t *payload, size_t *payload_len, size_t max_payload_len)
{
if (len < 2) return 0; // Need at least 2 bytes
...
// Decode payload (unmask if needed)
if (opcode == WS_OPCODE_BINARY || opcode == WS_OPCODE_TEXT || opcode == WS_OPCODE_CONTINUATION) {
if (payload_length > max_payload_len) {
// Prevent buffer overflow if decoded payload is larger than the provided buffer
return -1; // Or handle error appropriately
}
for (size_t i = 0; i < payload_length; i++) {
payload[i] = masked ? (p[i] ^ mask[i % 4]) : p[i];
}
*payload_len = payload_length;
}
...
}
...
static int wsReceive(wsPort_t *port)
{
...
// Receive WebSocket frame
uint8_t buffer[WS_BUFFER_SIZE];
ssize_t recvSize = recv(port->clientSocketFd, buffer, WS_BUFFER_SIZE, 0);
...
// Decode WebSocket frame
uint8_t payload[WS_BUFFER_SIZE];
size_t payload_len = 0;
ssize_t consumed = ws_decode_frame(port, buffer, recvSize, payload, &payload_len, sizeof(payload));
...
}

Comment on lines +618 to +639
void wsWriteBuf(serialPort_t *instance, const void *data, int count)
{
wsPort_t *port = (wsPort_t*)instance;

if (!port->isClientConnected || !port->isHandshakeComplete) {
return;
}

// Encode as WebSocket binary frame
uint8_t frame[WS_MAX_PACKET_SIZE + 14]; // +14 for header
size_t frame_len;
ws_encode_frame((const uint8_t *)data, count, WS_OPCODE_BINARY, frame, &frame_len);

fprintf(stderr, "[WEBSOCKET] UART%d TX %d bytes: ", port->id, count);
const uint8_t *payload = (const uint8_t *)data;
for (int i = 0; i < count && i < 32; i++) {
fprintf(stderr, "%02x ", payload[i]);
}
fprintf(stderr, "\n");

send(port->clientSocketFd, frame, frame_len, 0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: In wsWriteBuf, replace the large stack-allocated frame buffer with heap allocation (malloc/free) to prevent potential stack overflows when handling large data writes. [possible issue, importance: 9]

Suggested change
void wsWriteBuf(serialPort_t *instance, const void *data, int count)
{
wsPort_t *port = (wsPort_t*)instance;
if (!port->isClientConnected || !port->isHandshakeComplete) {
return;
}
// Encode as WebSocket binary frame
uint8_t frame[WS_MAX_PACKET_SIZE + 14]; // +14 for header
size_t frame_len;
ws_encode_frame((const uint8_t *)data, count, WS_OPCODE_BINARY, frame, &frame_len);
fprintf(stderr, "[WEBSOCKET] UART%d TX %d bytes: ", port->id, count);
const uint8_t *payload = (const uint8_t *)data;
for (int i = 0; i < count && i < 32; i++) {
fprintf(stderr, "%02x ", payload[i]);
}
fprintf(stderr, "\n");
send(port->clientSocketFd, frame, frame_len, 0);
}
void wsWriteBuf(serialPort_t *instance, const void *data, int count)
{
wsPort_t *port = (wsPort_t*)instance;
if (!port->isClientConnected || !port->isHandshakeComplete) {
return;
}
// Allocate frame buffer on the heap to avoid stack overflow
size_t max_frame_size = count + 14; // payload + max header size
uint8_t *frame = malloc(max_frame_size);
if (!frame) {
fprintf(stderr, "[WEBSOCKET] Failed to allocate memory for send buffer\n");
return;
}
// Encode as WebSocket binary frame
size_t frame_len;
ws_encode_frame((const uint8_t *)data, count, WS_OPCODE_BINARY, frame, &frame_len);
fprintf(stderr, "[WEBSOCKET] UART%d TX %d bytes: ", port->id, count);
const uint8_t *payload = (const uint8_t *)data;
for (int i = 0; i < count && i < 32; i++) {
fprintf(stderr, "%02x ", payload[i]);
}
fprintf(stderr, "\n");
send(port->clientSocketFd, frame, frame_len, 0);
free(frame);
}

Comment on lines +50 to +66
static void* fixupProfilePointer(const pgRegistry_t *reg, pgRegistry_t *mutableReg)
{
if (!mutableReg->ptr) {
// Allocate the pointer variable
uint8_t **currentPtr = (uint8_t**)calloc(1, sizeof(uint8_t*));
if (!currentPtr) {
PG_ALLOC_ERROR(pgN(reg), sizeof(uint8_t*));
return NULL;
}
*currentPtr = mutableReg->address;
mutableReg->ptr = currentPtr;
} else if (!*mutableReg->ptr) {
// Pointer exists but points to NULL
*mutableReg->ptr = mutableReg->address;
}
return *mutableReg->ptr;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: In fixupProfilePointer, avoid dereferencing mutableReg->ptr in the else if condition to prevent potential crashes from garbage pointer values, which could lead to undefined behavior. [possible issue, importance: 7]

Suggested change
static void* fixupProfilePointer(const pgRegistry_t *reg, pgRegistry_t *mutableReg)
{
if (!mutableReg->ptr) {
// Allocate the pointer variable
uint8_t **currentPtr = (uint8_t**)calloc(1, sizeof(uint8_t*));
if (!currentPtr) {
PG_ALLOC_ERROR(pgN(reg), sizeof(uint8_t*));
return NULL;
}
*currentPtr = mutableReg->address;
mutableReg->ptr = currentPtr;
} else if (!*mutableReg->ptr) {
// Pointer exists but points to NULL
*mutableReg->ptr = mutableReg->address;
}
return *mutableReg->ptr;
}
static void* fixupProfilePointer(const pgRegistry_t *reg, pgRegistry_t *mutableReg)
{
if (!mutableReg->ptr) {
// Allocate the pointer variable
uint8_t **currentPtr = (uint8_t**)calloc(1, sizeof(uint8_t*));
if (!currentPtr) {
PG_ALLOC_ERROR(pgN(reg), sizeof(uint8_t*));
return NULL;
}
*currentPtr = mutableReg->address;
mutableReg->ptr = currentPtr;
}
// After allocation (or if it existed), ensure the pointer it holds is valid.
if (!*mutableReg->ptr) {
*mutableReg->ptr = mutableReg->address;
}
return *mutableReg->ptr;
}

Comment on lines +596 to +600
int err = pthread_create(&port->receiveThread, NULL, wsReceiveThread, (void*)port);
if (err < 0){
fprintf(stderr, "[WEBSOCKET] Unable to create receive thread for UART%d\n", id);
return NULL;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Correct the error check for pthread_create from err < 0 to err != 0, as the function returns a positive error code on failure, not a negative one. [possible issue, importance: 8]

Suggested change
int err = pthread_create(&port->receiveThread, NULL, wsReceiveThread, (void*)port);
if (err < 0){
fprintf(stderr, "[WEBSOCKET] Unable to create receive thread for UART%d\n", id);
return NULL;
}
int err = pthread_create(&port->receiveThread, NULL, wsReceiveThread, (void*)port);
if (err != 0){
fprintf(stderr, "[WEBSOCKET] Unable to create receive thread for UART%d\n", id);
return NULL;
}

Comment on lines +537 to +545
int one = 1;
err = setsockopt(port->socketFd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
err = setsockopt(port->socketFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
err = fcntl(port->socketFd, F_SETFL, fcntl(port->socketFd, F_GETFL, 0) | O_NONBLOCK);

if (err < 0){
fprintf(stderr, "[WEBSOCKET] Unable to set socket options\n");
return NULL;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: In wsReConfigure, check the return value of each setsockopt and fcntl call individually to ensure all socket options are set correctly and to handle any specific failures. [possible issue, importance: 6]

Suggested change
int one = 1;
err = setsockopt(port->socketFd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
err = setsockopt(port->socketFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
err = fcntl(port->socketFd, F_SETFL, fcntl(port->socketFd, F_GETFL, 0) | O_NONBLOCK);
if (err < 0){
fprintf(stderr, "[WEBSOCKET] Unable to set socket options\n");
return NULL;
}
int one = 1;
if (setsockopt(port->socketFd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) < 0) {
fprintf(stderr, "[WEBSOCKET] Failed to set TCP_NODELAY: %s\n", strerror(errno));
return NULL;
}
if (setsockopt(port->socketFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
fprintf(stderr, "[WEBSOCKET] Failed to set SO_REUSEADDR: %s\n", strerror(errno));
return NULL;
}
if (fcntl(port->socketFd, F_SETFL, fcntl(port->socketFd, F_GETFL, 0) | O_NONBLOCK) < 0) {
fprintf(stderr, "[WEBSOCKET] Failed to set non-blocking mode: %s\n", strerror(errno));
return NULL;
}

-sASYNCIFY=1
-sWEBSOCKET_URL="ws://localhost:5771"
-sFORCE_FILESYSTEM=1
-sEXPORTED_FUNCTIONS=_main,_serialWriteByte,_serialReadByte,_serialAvailable,_serialGetRxDroppedBytes,_serialGetTxDroppedBytes,_wasmGetEepromPtr,_wasmGetEepromSize,_wasmReloadConfig,_wasmIsEepromValid,_malloc,_free
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Add the missing MSP bridge functions (_wasm_msp_process_command, _wasm_msp_get_api_version, _wasm_msp_get_fc_variant) to the EXPORTED_FUNCTIONS list in cmake/sitl.cmake to ensure they are available to the JavaScript test harness. [possible issue, importance: 9]

Suggested change
-sEXPORTED_FUNCTIONS=_main,_serialWriteByte,_serialReadByte,_serialAvailable,_serialGetRxDroppedBytes,_serialGetTxDroppedBytes,_wasmGetEepromPtr,_wasmGetEepromSize,_wasmReloadConfig,_wasmIsEepromValid,_malloc,_free
-sEXPORTED_FUNCTIONS=_main,_serialWriteByte,_serialReadByte,_serialAvailable,_serialGetRxDroppedBytes,_serialGetTxDroppedBytes,_wasmGetEepromPtr,_wasmGetEepromSize,_wasmReloadConfig,_wasmIsEepromValid,_wasm_msp_process_command,_wasm_msp_get_api_version,_wasm_msp_get_fc_variant,_malloc,_free

Comment on lines +25 to +28
PG_NAMES=$(cd "$INAV_ROOT" && grep -r "^PG_REGISTER" --include="*.c" src/main/ | \
grep -v "ledstrip.c:\|light_ws2811strip.c:\|esc_sensor.c:\|piniobox.c:\|osd_joystick.c:\|lights.c:\|rpm_filter.c:\|smartport_master.c:" | \
sed -E 's/.*:PG_REGISTER_ARRAY[^(]*\([^,]*,[^,]*, ([a-zA-Z0-9_]+),.*/\1/; t; s/.*:PG_REGISTER[^(]*\([^,]*, ([a-zA-Z0-9_]+),.*/\1/' | \
sort -u)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Update the sed command in generate_wasm_pg_registry.sh to correctly parse all variants of the PG_REGISTER macro, not just two, to prevent an incomplete parameter group registry. [possible issue, importance: 8]

Suggested change
PG_NAMES=$(cd "$INAV_ROOT" && grep -r "^PG_REGISTER" --include="*.c" src/main/ | \
grep -v "ledstrip.c:\|light_ws2811strip.c:\|esc_sensor.c:\|piniobox.c:\|osd_joystick.c:\|lights.c:\|rpm_filter.c:\|smartport_master.c:" | \
sed -E 's/.*:PG_REGISTER_ARRAY[^(]*\([^,]*,[^,]*, ([a-zA-Z0-9_]+),.*/\1/; t; s/.*:PG_REGISTER[^(]*\([^,]*, ([a-zA-Z0-9_]+),.*/\1/' | \
sort -u)
PG_NAMES=$(cd "$INAV_ROOT" && grep -r "^PG_REGISTER" --include="*.c" src/main/ | \
grep -v "ledstrip.c:\|light_ws2811strip.c:\|esc_sensor.c:\|piniobox.c:\|osd_joystick.c:\|lights.c:\|rpm_filter.c:\|smartport_master.c:" | \
sed -E 's/.*:PG_REGISTER(_ARRAY|_WITH_RESET_FN)?[^(]*\([^,]*,[^,]*, *([a-zA-Z0-9_]+),.*/\2/; t; s/.*:PG_REGISTER[^(]*\([^,]*, *([a-zA-Z0-9_]+),.*/\1/' | \
sort -u)

Comment on lines +329 to +352
// Check if we have full payload
if (len < bytes_consumed + payload_length) {
return 0; // Need more data
}

// Handle control frames
if (opcode == WS_OPCODE_CLOSE) {
return -1; // Connection close
}

if (opcode == WS_OPCODE_PING) {
// Respond with PONG
uint8_t pong_frame[2] = {0x8A, 0x00}; // FIN + PONG opcode, no payload
send(port->clientSocketFd, pong_frame, 2, 0);
return bytes_consumed + payload_length;
}

// Decode payload (unmask if needed)
if (opcode == WS_OPCODE_BINARY || opcode == WS_OPCODE_TEXT || opcode == WS_OPCODE_CONTINUATION) {
for (size_t i = 0; i < payload_length; i++) {
payload[i] = masked ? (p[i] ^ mask[i % 4]) : p[i];
}
*payload_len = payload_length;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Validate that payload_length does not exceed the provided payload buffer capacity (e.g. WS_MAX_PACKET_SIZE) before copying/unmasking, and return a clear error/close on overflow. [Learned best practice, importance: 6]

Suggested change
// Check if we have full payload
if (len < bytes_consumed + payload_length) {
return 0; // Need more data
}
// Handle control frames
if (opcode == WS_OPCODE_CLOSE) {
return -1; // Connection close
}
if (opcode == WS_OPCODE_PING) {
// Respond with PONG
uint8_t pong_frame[2] = {0x8A, 0x00}; // FIN + PONG opcode, no payload
send(port->clientSocketFd, pong_frame, 2, 0);
return bytes_consumed + payload_length;
}
// Decode payload (unmask if needed)
if (opcode == WS_OPCODE_BINARY || opcode == WS_OPCODE_TEXT || opcode == WS_OPCODE_CONTINUATION) {
for (size_t i = 0; i < payload_length; i++) {
payload[i] = masked ? (p[i] ^ mask[i % 4]) : p[i];
}
*payload_len = payload_length;
}
// Check if we have full payload
if (len < bytes_consumed + payload_length) {
return 0; // Need more data
}
// Guard against payload buffer overflow
if (payload_length > WS_MAX_PACKET_SIZE) {
return -1; // Protocol error / too large for our buffer
}
...
// Decode payload (unmask if needed)
if (opcode == WS_OPCODE_BINARY || opcode == WS_OPCODE_TEXT || opcode == WS_OPCODE_CONTINUATION) {
for (size_t i = 0; i < payload_length; i++) {
payload[i] = masked ? (p[i] ^ mask[i % 4]) : p[i];
}
*payload_len = (size_t)payload_length;
}

@Scavanger
Copy link
Contributor

Scavanger commented Feb 8, 2026

Haha, great!

I'll have to take a closer look at that.

The serial interface looks good, I'm not particularly pleased with the polling either, but there was no other way as my approach uses threading, (emscripten_main_loop ‘manages’ a maximum of 60 fps/hz) and otherwise there were always race conditions.

The approach of using WebAssembly as a generic platform for everyone is brilliant, I hadn't even thought of that.
Just one thing, how should a TCP/UDP connection (without websocket-to-socket proxy) e.g, to XPlane (main use for SITL) look like?

Incidentally, Emscripten can also create ES6-compatible code, which makes integration much easier. Above all, webassembly can be completely unloaded and reloaded without reloading the entire browser page, which makes the ‘FC reboot’ much more convenient.

One more thing: I've found that the Origin Private File System (OPFS) works much more simply and reliably than indexedDb.

More on this soon :)

@sensei-hacker
Copy link
Member Author

sensei-hacker commented Feb 8, 2026

To me, the advantage of running in a browser is that you don't have to run native code - you can do PWA and run it right on your phone or whatever.

My thought on X-Plane is if you're running X-Plane, you're running native code, so it's okay that you need a native code exe to provide the socket.
So what's the highest performance method of implementing a native-code proxy.exe?
Probably by using the attached script:

mkproxy.sh.txt

@Scavanger
Copy link
Contributor

Scavanger commented Feb 8, 2026

My thought on X-Plane is if you're running X-Plane, you're running native code, so it's okay that you need a native code exe to provide the socket. So what's the highest performance method of implementing a native-code proxy.exe? Probably by using the attached script:

https://github.com/emscripten-core/emscripten/tree/main/tools/websocket_to_posix_proxy
or
https://github.com/Scavanger/websocket_to_posix_proxy (just a C++ Wrapper for the above)

Possible solutions:

  • Realfight: Refactor code to use emscriptens fetch API (Realflight provdes a REST API, so no problem here)
  • X-Plane:
    • Intergrate the proxy to the X-Plane INAV Plugin
    • Drop "native" UDP Dref communication and add an Websocket server to INAV Plugin
    • Drop X-PLane 11 support for WASM, X-PLane 12 has an HTTP/Websocket server

Electron:
Just export the fake sensor functions and let the Electron main thread (its a node Environment, so were not bound to browser restrictions) handle TCP/UDP connections.

@sensei-hacker
Copy link
Member Author

sensei-hacker commented Feb 8, 2026

https://github.com/emscripten-core/emscripten/tree/main/tools/websocket_to_posix_proxy
or
https://github.com/Scavanger/websocket_to_posix_proxy (just a C++ Wrapper for the above)

What advantage does that native executable have vs this one ?

X-Plane 12 requires a beefy GPU to keep up with INAV, X-Plane 11 doesn't.
A GPU that cost $350 a year ago, and costs $800 today. 😢

I'm not seeing a reason to run a 500 KB native executable AND running wasm if you can do the same thing better - with much better performance, more simply, by running the 500 KB executable and not wasm. If you're already running a native executable, does wasm also give you anything besides slowness? And complexity.

@Scavanger
Copy link
Contributor

https://github.com/emscripten-core/emscripten/tree/main/tools/websocket_to_posix_proxy
or
https://github.com/Scavanger/websocket_to_posix_proxy (just a C++ Wrapper for the above)

What advantage does that native executable have vs this one ?

Do you mean serial_porxy? That's something completely different. Serial proxy to use the FC as a receiver in SITL.

X-Plane 12 requires a beefy GPU to keep up with INAV, X-Plane 11 doesn't. A GPU that cost $350 a year ago, and costs $800 today. 😢

Yeah, thats true, we should keep supporting X-Plane 11, Just an idea.

I'm not seeing a reason to run a 500 KB native executable AND running wasm if you can do the same thing better - with much better performance, more simply, by running the 500 KB executable and not wasm. If you're already running a native executable, does wasm also give you anything besides slowness? And complexity.

So, what would be the solution?

Leave it as it is and use SITL in PWA primarily for demo mode, mark SITL as “Works but not recommended,” and tell people to use the native app for SITL?

@sensei-hacker
Copy link
Member Author

Yeah as far as I can see, the use case for wasm is when you're just running the browser, not a native exe. So demo mode or similar.

Do you mean serial_porxy?

If you want a native executable that accepts tcp sockets, that's just target.c

Once we have a native executable running, I'm not seeing the benefit of then mucking about with websockets.
If we didn't already have native builds of sitl, a proxy might be a way to get there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants