feat(cpn): visual and audible feedback for haptic in simulator (#7322)

This commit is contained in:
Kudzz
2026-07-20 19:09:43 +10:00
committed by GitHub
parent 8aa6ff723e
commit 14e6d6ce01
9 changed files with 89 additions and 4 deletions
@@ -191,6 +191,7 @@ class SimulatorInterface : public QObject
void auxSerialStart(const quint8 port_num);
void auxSerialStop(const quint8 port_num);
void txBatteryVoltageChanged(const int voltage);
void hapticChanged(int intensity);
void fsColorChange(quint8 index, qint32 color);
};
@@ -38,6 +38,9 @@
#include <QDir>
#include <QLabel>
#include <QMessageBox>
#include <QTimer>
#include <QGraphicsOpacityEffect>
#include <QPropertyAnimation>
extern AppData g; // ensure what "g" means
@@ -173,6 +176,7 @@ SimulatorMainWindow::SimulatorMainWindow(QWidget *parent, const QString & simula
connect(m_simulator, &SimulatorInterface::auxSerialStart, hostSerialConnector, &HostSerialConnector::serialStart);
connect(m_simulator, &SimulatorInterface::auxSerialStop, hostSerialConnector, &HostSerialConnector::serialStop);
connect(m_simulator, &SimulatorInterface::txBatteryVoltageChanged, this, &SimulatorMainWindow::onTxBatteryVoltageChanged);
connect(m_simulator, &SimulatorInterface::hapticChanged, this, &SimulatorMainWindow::onHapticChanged);
}
SimulatorMainWindow::~SimulatorMainWindow()
@@ -613,3 +617,33 @@ void SimulatorMainWindow::onTxBatteryVoltageChanged(const unsigned int voltage)
{
m_batVoltage = voltage;
}
// Haptic feedback handler: provides visual and audible substitutes
// for physical vibration when running in the simulator.
void SimulatorMainWindow::onHapticChanged(int intensity) {
Q_UNUSED(intensity);
// AUDIBLE: system beep as substitute for haptic motor sound
QApplication::beep();
// VISUAL: animate opacity to simulate vibration
// Uses QGraphicsEffect which works on Wayland without moving the window
if (m_simulatorWidget) {
QGraphicsOpacityEffect* effect = new QGraphicsOpacityEffect(m_simulatorWidget);
m_simulatorWidget->setGraphicsEffect(effect);
QPropertyAnimation* anim = new QPropertyAnimation(effect, "opacity", effect);
anim->setDuration(200);
anim->setKeyValueAt(0.0, 1.0);
anim->setKeyValueAt(0.25, 0.5);
anim->setKeyValueAt(0.5, 1.0);
anim->setKeyValueAt(0.75, 0.5);
anim->setKeyValueAt(1.0, 1.0);
anim->start(QAbstractAnimation::DeleteWhenStopped);
connect(anim, &QPropertyAnimation::finished, this, [this, effect]() {
if (m_simulatorWidget && m_simulatorWidget->graphicsEffect() == effect)
m_simulatorWidget->setGraphicsEffect(nullptr);
});
}
}
@@ -89,6 +89,7 @@ class SimulatorMainWindow : public QMainWindow
// TODO: detect if changed via ui as currently event only on GeneralSettings (re)load
void onSettingsBatteryChanged(const int batMin, const int batMax, const unsigned int batWarn);
void onTxBatteryVoltageChanged(const unsigned int voltage); // something else changed voltage
void onHapticChanged(int intensity);
protected:
void createDockWidgets();
@@ -18,6 +18,7 @@
#include <QDebug>
#include <QFile>
#include <QElapsedTimer>
#include <QTimer>
// WAMR native callback: called by WASM module to get analog values in
// ADC range (0..4096, center=2048). The WASM ADC driver does a direct
@@ -131,7 +132,8 @@ WasmSimulatorInterface::WasmSimulatorInterface(const QString & wasmPath,
: SimulatorInterface(),
m_wasmPath(wasmPath),
m_boardName(boardName),
m_boardType(boardType)
m_boardType(boardType),
m_lastHaptic(0)
{
}
@@ -392,6 +394,9 @@ bool WasmSimulatorInterface::resolveExports()
m_fnAuxSerialReceive =
wasm_runtime_lookup_function(m_moduleInst, "simuAuxSerialReceive");
// Haptic feedback polling
m_fnGetHaptic = wasm_runtime_lookup_function(m_moduleInst, "simuGetHaptic");
m_fnMalloc = wasm_runtime_lookup_function(m_moduleInst, "malloc");
m_fnFree = wasm_runtime_lookup_function(m_moduleInst, "free");
@@ -481,6 +486,7 @@ void WasmSimulatorInterface::init()
m_lastOutputs.clear();
m_resetOutputsData = true;
m_lastHaptic = 0;
qDebug() << "WASM simulator" << m_boardName << "initialized: LCD"
<< m_lcdWidth << "x" << m_lcdHeight << "depth" << m_lcdDepth;
@@ -1010,6 +1016,23 @@ void WasmSimulatorInterface::run()
if (!(loops % 5)) {
QMutexLocker lckr(&m_mutex);
checkOutputsChanged();
// 1. Read the global variable from the WASM module
// "simuHapticValue" must match exactly what you wrote in simulib.cpp
// Poll haptic state from WASM every 50ms.
// When haptic fires, emit hapticChanged() to trigger
// visual (window jitter) and audible (beep) feedback in the simulatormainwindow.cpp
// as a substitute for physical vibration hardware.
if (m_fnGetHaptic) {
uint32_t argv[1] = {0};
if (wasm_runtime_call_wasm(m_execEnv, m_fnGetHaptic, 0, argv)) {
uint32_t currentHapticValue = argv[0];
if (currentHapticValue != m_lastHaptic) {
m_lastHaptic = currentHapticValue;
emit hapticChanged((int)currentHapticValue);
}
}
}
}
}
@@ -189,6 +189,7 @@ class WasmSimulatorInterface : public SimulatorInterface
wasm_function_inst_t m_fnGetMaxTrainerChannels = nullptr;
wasm_function_inst_t m_fnCopyTrainerInput = nullptr;
wasm_function_inst_t m_fnSetTrainerTimeout = nullptr;
wasm_function_inst_t m_fnGetHaptic = nullptr;
// Backlight and function switch LEDs
wasm_function_inst_t m_fnGetBacklightState = nullptr;
@@ -208,6 +209,8 @@ class WasmSimulatorInterface : public SimulatorInterface
// Aux serial: host -> firmware data injection
wasm_function_inst_t m_fnAuxSerialReceive = nullptr;
uint32_t m_lastHaptic = 0;
wasm_function_inst_t m_fnMalloc = nullptr;
wasm_function_inst_t m_fnFree = nullptr;
};
+9
View File
@@ -22,6 +22,11 @@
#include "edgetx.h"
#include "haptic.h"
#if defined(SIMU)
#include <stdint.h>
extern uint32_t simuHapticValue;
#endif
hapticQueue::hapticQueue()
{
buzzTimeLeft = 0;
@@ -96,6 +101,10 @@ void hapticQueue::play(uint8_t tLen, uint8_t tPause, uint8_t tFlags)
void hapticQueue::event(uint8_t e)
{
if (g_eeGeneral.hapticMode >= e_mode_nokeys || (g_eeGeneral.hapticMode >= e_mode_alarms && e <= AU_ERROR)) {
#if defined(SIMU)
simuHapticValue++;
return;
#endif
if (e <= AU_ERROR)
play(15, 3, PLAY_NOW);
else if (e <= AU_MIX_WARNING_3)
+14 -2
View File
@@ -47,6 +47,20 @@
#include <assert.h>
#include <deque>
#include <stdint.h>
// Monotonic event counter incremented each time haptic fires.
// Host (WasmSimulatorInterface) compares against last-seen value to detect new events.
uint32_t simuHapticValue = 0;
// Required stubs — called by firmware but no-ops in simulator
void hapticOn(uint32_t pwmPercent) { (void)pwmPercent; }
void hapticOff() {}
// WASM export — polled by host every 50ms to detect haptic events
uint32_t WASM_EXPORT(simuGetHaptic)() {
return simuHapticValue;
}
int g_snapshot_idx = 0;
@@ -361,8 +375,6 @@ void boardOff()
{
}
void hapticOff() {}
#if defined(HAS_HARDWARE_OPTIONS)
HardwareOptions hardwareOptions;
#endif
+2 -1
View File
@@ -95,6 +95,7 @@ uint8_t WASM_EXPORT(simuGetNumCustomSwitches)();
uint8_t WASM_EXPORT(simuGetCustomSwitchIndex)(uint8_t cfsIdx);
bool WASM_EXPORT(simuGetCustomSwitchState)(uint8_t idx);
uint32_t WASM_EXPORT(simuGetCustomSwitchColor)(uint8_t idx);
uint32_t WASM_EXPORT(simuGetHaptic)();
// Value-based trim setting: idx is mapped through inputMappingConvertMode.
void WASM_EXPORT(simuSetTrimValue)(uint8_t idx, int32_t value);
@@ -185,7 +186,7 @@ void WASM_IMPORT(simuAuxSerialStart)(uint8_t port_nr, uint32_t baudrate,
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);
uint32_t len);
// -- Internal (not exported) --
void simuMain();
+1
View File
@@ -308,6 +308,7 @@ elseif(PCB STREQUAL X7)
set(AUX_SERIAL ON)
endif()
elseif(PCBREV STREQUAL POCKET)
set(HAPTIC YES)
set(DEFAULT_INTERNAL_MODULE CROSSFIRE CACHE STRING "Default internal module")
set(INTERNAL_MODULE_SERIAL YES)
set(MODULE_SIZE_SML YES)