diff --git a/companion/src/simulation/wasmsimulatorinterface.cpp b/companion/src/simulation/wasmsimulatorinterface.cpp index 97657d665..35d5dbe0c 100644 --- a/companion/src/simulation/wasmsimulatorinterface.cpp +++ b/companion/src/simulation/wasmsimulatorinterface.cpp @@ -68,11 +68,61 @@ static void host_simuLcdNotify(wasm_exec_env_t exec_env) iface->notifyLcdReady(); } +// WAMR native callbacks: aux serial bridge (firmware -> host). These run on +// the WAMR worker thread; the WasmSimulatorInterface methods they call emit +// Qt signals, which Qt::AutoConnection automatically queues to the GUI +// thread where HostSerialConnector lives. +static void host_simuAuxSerialStart(wasm_exec_env_t exec_env, uint32_t port_nr, + uint32_t baudrate, uint32_t encoding) +{ + auto * inst = wasm_runtime_get_module_inst(exec_env); + auto * iface = static_cast( + wasm_runtime_get_custom_data(inst)); + if (iface) + iface->onAuxSerialStart((uint8_t)port_nr, baudrate, (uint8_t)encoding); +} + +static void host_simuAuxSerialStop(wasm_exec_env_t exec_env, uint32_t port_nr) +{ + auto * inst = wasm_runtime_get_module_inst(exec_env); + auto * iface = static_cast( + wasm_runtime_get_custom_data(inst)); + if (iface) + iface->onAuxSerialStop((uint8_t)port_nr); +} + +static void host_simuAuxSerialSetBaudrate(wasm_exec_env_t exec_env, + uint32_t port_nr, uint32_t baudrate) +{ + auto * inst = wasm_runtime_get_module_inst(exec_env); + auto * iface = static_cast( + wasm_runtime_get_custom_data(inst)); + if (iface) + iface->onAuxSerialSetBaudrate((uint8_t)port_nr, baudrate); +} + +static void host_simuAuxSerialSendBuffer(wasm_exec_env_t exec_env, + uint32_t port_nr, uint8_t * data, + uint32_t len) +{ + auto * inst = wasm_runtime_get_module_inst(exec_env); + auto * iface = static_cast( + wasm_runtime_get_custom_data(inst)); + if (iface && data && len > 0) + iface->onAuxSerialSendBuffer((uint8_t)port_nr, data, len); +} + static NativeSymbol s_nativeSymbols[] = { {"simuGetAnalog", (void *)host_simuGetAnalog, "(i)i", nullptr}, {"simuQueueAudio", (void *)host_simuQueueAudio, "(*~)", nullptr}, {"simuTrace", (void *)host_simuTrace, "($)", nullptr}, {"simuLcdNotify", (void *)host_simuLcdNotify, "()", nullptr}, + {"simuAuxSerialStart", (void *)host_simuAuxSerialStart, "(iii)", nullptr}, + {"simuAuxSerialStop", (void *)host_simuAuxSerialStop, "(i)", nullptr}, + {"simuAuxSerialSetBaudrate", (void *)host_simuAuxSerialSetBaudrate, + "(ii)", nullptr}, + {"simuAuxSerialSendBuffer", (void *)host_simuAuxSerialSendBuffer, + "(i*~)", nullptr}, }; WasmSimulatorInterface::WasmSimulatorInterface(const QString & wasmPath, @@ -338,6 +388,10 @@ bool WasmSimulatorInterface::resolveExports() m_fnGetCustomSwitchIndex = wasm_runtime_lookup_function(m_moduleInst, "simuGetCustomSwitchIndex"); + // Aux serial: host -> firmware data injection + m_fnAuxSerialReceive = + wasm_runtime_lookup_function(m_moduleInst, "simuAuxSerialReceive"); + m_fnMalloc = wasm_runtime_lookup_function(m_moduleInst, "malloc"); m_fnFree = wasm_runtime_lookup_function(m_moduleInst, "free"); @@ -857,8 +911,56 @@ void WasmSimulatorInterface::queueAudio(const uint8_t * buf, uint32_t len) void WasmSimulatorInterface::receiveAuxSerialData(const quint8 port_num, const QByteArray & data) { - Q_UNUSED(port_num); - Q_UNUSED(data); + if (!m_fnAuxSerialReceive || !m_execEnv || !m_fnMalloc || !m_fnFree || + data.isEmpty()) + return; + + QMutexLocker lckr(&m_mutex); + uint32_t len = data.size(); + uint32_t allocArgv[1] = {len}; + if (!wasm_runtime_call_wasm(m_execEnv, m_fnMalloc, 1, allocArgv) || + !allocArgv[0]) + return; + + uint32_t wasmBuf = allocArgv[0]; + void * nativePtr = wasm_runtime_addr_app_to_native(m_moduleInst, wasmBuf); + if (nativePtr) + memcpy(nativePtr, data.constData(), len); + + uint32_t argv[3] = {(uint32_t)port_num, wasmBuf, len}; + wasm_runtime_call_wasm(m_execEnv, m_fnAuxSerialReceive, 3, argv); + + uint32_t freeArgv[1] = {wasmBuf}; + wasm_runtime_call_wasm(m_execEnv, m_fnFree, 1, freeArgv); +} + +void WasmSimulatorInterface::onAuxSerialStart(uint8_t port_nr, + uint32_t baudrate, + uint8_t encoding) +{ + emit auxSerialSetEncoding(port_nr, encoding); + if (baudrate != 0) + emit auxSerialSetBaudrate(port_nr, baudrate); + emit auxSerialStart(port_nr); +} + +void WasmSimulatorInterface::onAuxSerialStop(uint8_t port_nr) +{ + emit auxSerialStop(port_nr); +} + +void WasmSimulatorInterface::onAuxSerialSetBaudrate(uint8_t port_nr, + uint32_t baudrate) +{ + emit auxSerialSetBaudrate(port_nr, baudrate); +} + +void WasmSimulatorInterface::onAuxSerialSendBuffer(uint8_t port_nr, + const uint8_t * data, + uint32_t len) +{ + emit auxSerialSendData(port_nr, + QByteArray((const char *)data, (int)len)); } void WasmSimulatorInterface::run() diff --git a/companion/src/simulation/wasmsimulatorinterface.h b/companion/src/simulation/wasmsimulatorinterface.h index dd9670ffe..651bab8a3 100644 --- a/companion/src/simulation/wasmsimulatorinterface.h +++ b/companion/src/simulation/wasmsimulatorinterface.h @@ -84,6 +84,15 @@ class WasmSimulatorInterface : public SimulatorInterface // Called by WASM import simuLcdNotify (from WAMR thread) void notifyLcdReady(); + // Called by WASM aux serial imports (from WAMR thread). These re-emit + // the matching SimulatorInterface signals so that HostSerialConnector + // (wired up in SimulatorMainWindow) drives the real host serial port. + void onAuxSerialStart(uint8_t port_nr, uint32_t baudrate, uint8_t encoding); + void onAuxSerialStop(uint8_t port_nr); + void onAuxSerialSetBaudrate(uint8_t port_nr, uint32_t baudrate); + void onAuxSerialSendBuffer(uint8_t port_nr, const uint8_t * data, + uint32_t len); + protected slots: void run(); void onLcdNotify(); @@ -196,6 +205,9 @@ class WasmSimulatorInterface : public SimulatorInterface static constexpr int MAX_FS_LEDS = 8; uint32_t m_lastFSLedColors[MAX_FS_LEDS] = {}; + // Aux serial: host -> firmware data injection + wasm_function_inst_t m_fnAuxSerialReceive = nullptr; + wasm_function_inst_t m_fnMalloc = nullptr; wasm_function_inst_t m_fnFree = nullptr; }; diff --git a/radio/src/targets/simu/simulib.cpp b/radio/src/targets/simu/simulib.cpp index b3d6233d8..68578b670 100644 --- a/radio/src/targets/simu/simulib.cpp +++ b/radio/src/targets/simu/simulib.cpp @@ -44,6 +44,7 @@ #endif #include +#include int g_snapshot_idx = 0; @@ -68,6 +69,10 @@ extern const etx_hal_adc_driver_t simu_adc_driver; void lcdCopy(void * dest, void * src); void lcdFlushed(); +#if defined(AUX_SERIAL) || defined(AUX2_SERIAL) +static void hostSerialInit(); +#endif + void simuInit() { #if defined(ROTARY_ENCODER_NAVIGATION) @@ -81,6 +86,10 @@ void simuInit() adcInit(&simu_adc_driver); // Switches switchInit(); + +#if defined(AUX_SERIAL) || defined(AUX2_SERIAL) + hostSerialInit(); +#endif } bool keysStates[MAX_KEYS] = { false }; @@ -394,29 +403,111 @@ const etx_serial_port_t UsbSerialPort = { "USB-VCP", nullptr, nullptr }; #endif #if defined(AUX_SERIAL) || defined(AUX2_SERIAL) -static void* null_drv_init(void* hw_def, const etx_serial_init* dev) { return nullptr; } -static void null_drv_deinit(void* ctx) { } -static void null_drv_send_byte(void* ctx, uint8_t b) { } -static void null_drv_send_buffer(void* ctx, const uint8_t* b, uint32_t l) { } -static bool null_drv_tx_completed(void* ctx) { return true; } -static int null_drv_get_byte(void* ctx, uint8_t* b) { return 0; } -static void null_drv_set_baudrate(void* ctx, uint32_t baudrate) { } +#if !defined(__wasm__) +// Native builds (unit-tests, SDL simulator) don't have a WASM host to +// forward aux-serial traffic to: WASM_IMPORT is a no-op macro off-target, +// so the declarations in simulib.h become plain externs with no +// definitions. Provide no-op stubs so host_drv_* can link — the host +// bridge is only meaningfully exercised in the WASM simulator, where +// these symbols are resolved as WAMR imports by Companion. +void simuAuxSerialStart(uint8_t, uint32_t, uint8_t) {} +void simuAuxSerialStop(uint8_t) {} +void simuAuxSerialSetBaudrate(uint8_t, uint32_t) {} +void simuAuxSerialSendBuffer(uint8_t, const uint8_t*, uint32_t) {} +#endif -const etx_serial_driver_t null_drv = { - .init = null_drv_init, - .deinit = null_drv_deinit, - .sendByte = null_drv_send_byte, - .sendBuffer = null_drv_send_buffer, - .txCompleted = null_drv_tx_completed, +// Per-port bridge state. TX is forwarded to the host via WASM imports; +// RX bytes pushed in via simuAuxSerialReceive() are buffered in rxQueue +// and consumed by the firmware through host_drv_get_byte(). +struct host_serial_port_t { + uint8_t index; + mutex_handle_t rxMutex; + std::deque rxQueue; +}; + +static host_serial_port_t hostSerialPorts[MAX_AUX_SERIAL] = { + { SP_AUX1, {}, {} }, + { SP_AUX2, {}, {} }, +}; + +static void hostSerialInit() +{ + for (uint8_t i = 0; i < MAX_AUX_SERIAL; ++i) + mutex_create(&hostSerialPorts[i].rxMutex); +} + +static void* host_drv_init(void* hw_def, const etx_serial_init* dev) +{ + if (hw_def == nullptr || dev == nullptr) return nullptr; + + auto* port = static_cast(hw_def); + mutex_lock(&port->rxMutex); + port->rxQueue.clear(); + mutex_unlock(&port->rxMutex); + simuAuxSerialStart(port->index, dev->baudrate, dev->encoding); + return port; +} + +static void host_drv_deinit(void* ctx) +{ + if (ctx == nullptr) return; + auto* port = static_cast(ctx); + simuAuxSerialStop(port->index); +} + +static void host_drv_send_byte(void* ctx, uint8_t b) +{ + if (ctx == nullptr) return; + auto* port = static_cast(ctx); + simuAuxSerialSendBuffer(port->index, &b, 1); +} + +static void host_drv_send_buffer(void* ctx, const uint8_t* b, uint32_t l) +{ + if (ctx == nullptr || b == nullptr || l == 0) return; + auto* port = static_cast(ctx); + simuAuxSerialSendBuffer(port->index, b, l); +} + +static bool host_drv_tx_completed(void*) { return true; } + +static int host_drv_get_byte(void* ctx, uint8_t* b) +{ + if (ctx == nullptr || b == nullptr) return 0; + auto* port = static_cast(ctx); + mutex_lock(&port->rxMutex); + if (port->rxQueue.empty()) { + mutex_unlock(&port->rxMutex); + return 0; + } + *b = port->rxQueue.front(); + port->rxQueue.pop_front(); + mutex_unlock(&port->rxMutex); + return 1; +} + +static void host_drv_set_baudrate(void* ctx, uint32_t baudrate) +{ + if (ctx == nullptr) return; + auto* port = static_cast(ctx); + simuAuxSerialSetBaudrate(port->index, baudrate); +} + +const etx_serial_driver_t host_drv = { + .init = host_drv_init, + .deinit = host_drv_deinit, + .sendByte = host_drv_send_byte, + .sendBuffer = host_drv_send_buffer, + .txCompleted = host_drv_tx_completed, .waitForTxCompleted = nullptr, .enableRx = nullptr, - .getByte = null_drv_get_byte, + .getByte = host_drv_get_byte, .getLastByte = nullptr, .getBufferedBytes = nullptr, .copyRxBuffer = nullptr, .clearRxBuffer = nullptr, .getBaudrate = nullptr, - .setBaudrate = null_drv_set_baudrate, + .setBaudrate = host_drv_set_baudrate, .setPolarity = nullptr, .setHWOption = nullptr, .setReceiveCb = nullptr, @@ -424,6 +515,16 @@ const etx_serial_driver_t null_drv = { .setBaudrateCb = nullptr, }; +void simuAuxSerialReceive(uint8_t port_nr, const uint8_t* data, uint32_t len) +{ + if (port_nr >= MAX_AUX_SERIAL || data == nullptr || len == 0) return; + auto& port = hostSerialPorts[port_nr]; + mutex_lock(&port.rxMutex); + for (uint32_t i = 0; i < len; ++i) + port.rxQueue.push_back(data[i]); + mutex_unlock(&port.rxMutex); +} + #if defined(AUX_SERIAL_PWR_GPIO) static void null_pwr_aux(uint8_t) {} #endif @@ -437,8 +538,8 @@ static void null_pwr_aux(uint8_t) {} #endif static etx_serial_port_t auxSerialPort = { "AUX1", - &null_drv, - nullptr, + &host_drv, + &hostSerialPorts[SP_AUX1], AUX_SERIAL_PWR }; #define AUX_SERIAL_PORT &auxSerialPort @@ -454,8 +555,8 @@ static etx_serial_port_t auxSerialPort = { #endif static etx_serial_port_t aux2SerialPort = { "AUX2", - &null_drv, - nullptr, + &host_drv, + &hostSerialPorts[SP_AUX2], AUX2_SERIAL_PWR }; #define AUX2_SERIAL_PORT &aux2SerialPort diff --git a/radio/src/targets/simu/simulib.h b/radio/src/targets/simu/simulib.h index 5becb60c5..bbce5b426 100644 --- a/radio/src/targets/simu/simulib.h +++ b/radio/src/targets/simu/simulib.h @@ -140,6 +140,11 @@ uint8_t WASM_EXPORT(simuGetNumGVars)(); uint8_t WASM_EXPORT(simuGetNumFlightModes)(); int32_t WASM_EXPORT(simuGetGVar)(uint8_t gv, uint8_t fm); +// Aux serial: push bytes received from a host serial port into the firmware's +// rx queue for the matching aux port (port_nr is 0 for AUX1, 1 for AUX2). +void WASM_EXPORT(simuAuxSerialReceive)(uint8_t port_nr, const uint8_t* data, + uint32_t len); + // -- WASM imports (provided by host) -- // simuGetAnalog: return ADC-range value for input at index idx. @@ -157,6 +162,18 @@ void WASM_IMPORT(simuTrace)(const char* text); // listener so the frame can be rendered without polling. void WASM_IMPORT(simuLcdNotify)(); +// Aux serial bridge (firmware -> host). port_nr is 0 for AUX1, 1 for AUX2. +// encoding values match SimulatorSerialEncoding (0=8N1, 1=8E2, 2=PXX1_PWM) +// and ETX_Encoding_* — they share the same numeric values. Called when the +// firmware initialises an aux serial port (start), shuts it down (stop), +// reconfigures the baudrate, or transmits data. +void WASM_IMPORT(simuAuxSerialStart)(uint8_t port_nr, uint32_t baudrate, + uint8_t encoding); +void WASM_IMPORT(simuAuxSerialStop)(uint8_t port_nr); +void WASM_IMPORT(simuAuxSerialSetBaudrate)(uint8_t port_nr, uint32_t baudrate); +void WASM_IMPORT(simuAuxSerialSendBuffer)(uint8_t port_nr, const uint8_t* data, + uint32_t len); + // -- Internal (not exported) -- void simuMain(); std::string simuFatfsGetCurrentPath();