feat(cpn): visual and audible feedback for haptic in simulator (#7322)
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user