feat: WASM modules based simulation in Companion (#6435)

This commit is contained in:
Raphael Coeffic
2026-03-20 12:19:17 +10:00
committed by GitHub
parent 5f8a38bb18
commit 04a2231aee
80 changed files with 3126 additions and 2220 deletions
+6 -2
View File
@@ -34,7 +34,6 @@ option(SIMU_LUA_COMPILER "Pre-compile and save Lua scripts in simulator." ON)
option(FAS_PROTOTYPE "Support of old FAS prototypes (different resistors)" OFF)
option(RAS "RAS (SWR) enabled" ON)
option(TEMPLATES "Model templates menu" OFF)
option(TRACE_SIMPGMSPACE "Turn on traces in simpgmspace.cpp" ON)
option(TRACE_LUA_INTERNALS "Turn on traces for Lua internals" OFF)
option(TRACE_AUDIO "Traces audio enabled" OFF)
option(DEBUG_SEGGER_RTT "Debug output to Segger RTT" OFF)
@@ -575,7 +574,10 @@ if(NATIVE_BUILD)
set_property(TARGET radiolib_native PROPERTY POSITION_INDEPENDENT_CODE ON)
add_subdirectory(targets/simu)
add_subdirectory(tests)
if(NOT WASI)
add_subdirectory(tests)
endif()
endif()
set(SRC ${SRC} ${FIRMWARE_SRC})
@@ -595,6 +597,8 @@ if(NOT USE_UNSUPPORTED_TOOLCHAIN AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_EQUA
message(FATAL_ERROR "Only Arm GNU Toolchain version 14.2.rel1 is supported!!")
endif()
enable_language(ASM)
set(CMAKE_C_FLAGS "${FIRMWARE_C_FLAGS}")
set(CMAKE_C_FLAGS_DEBUG "${FIRMWARE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${FIRMWARE_C_FLAGS}")
-7
View File
@@ -730,13 +730,6 @@ void AudioQueue::playTone(uint16_t freq, uint16_t len, uint16_t pause, uint8_t f
void AudioQueue::playFile(const char * filename, uint8_t flags, uint8_t id, int8_t fragmentVolume)
{
#if defined(SIMU)
TRACE("playFile(\"%s\", flags=%x, id=%d fragmentVolume=%d ee_general=%d)", filename, flags, id, fragmentVolume, g_eeGeneral.wavVolume);
if (strlen(filename) > AUDIO_FILENAME_MAXLEN) {
TRACE("file name too long! maximum length is %d characters", AUDIO_FILENAME_MAXLEN);
}
#endif
if (!sdMounted())
return;
+1 -1
View File
@@ -1536,7 +1536,7 @@ void edgeTxInit()
#if defined(AUDIO)
currentSpeakerVolume = requiredSpeakerVolume =
g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF;
limit<int>(0, g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF, VOLUME_LEVEL_MAX);
#if !defined(SOFTWARE_VOLUME)
audioSetVolume(currentSpeakerVolume);
#endif
-5
View File
@@ -27,11 +27,6 @@
#include "edgetx_helpers.h"
#include "touch.h"
#include "switches.h"
#if defined(SIMU)
#include "targets/simu/simpgmspace.h"
#endif
#include "board.h"
#if !defined(SIMU)
+5 -5
View File
@@ -23,8 +23,7 @@
#include "switches.h"
#include "hal/audio_driver.h"
#include "boards/generic_stm32/rgb_leds.h"
#include "os/time.h"
#if defined(COLORLCD)
void setRequestedMainView(uint8_t view);
@@ -380,9 +379,10 @@ void evalFunctions(CustomFunctionData * functions, CustomFunctionsContext & func
case FUNC_PUSH_CUST_SWITCH:
if (CFN_PARAM(cfn)) { // Duration is set
if (! CFN_VAL2(cfn) ) { // Duration not started yet
CFN_VAL2(cfn) = timersGetMsTick() + CFN_PARAM(cfn) * 100;
CFN_VAL2(cfn) = time_get_ms() + CFN_PARAM(cfn) * 100;
g_model.cfsSetSFState(CFN_CS_INDEX(cfn), 1);
} else if (timersGetMsTick() < (uint32_t)CFN_VAL2(cfn) ) { // Still within push duration
}
else if (time_get_ms() < (uint32_t)CFN_VAL2(cfn) ) { // Still within push duration
g_model.cfsSetSFState(CFN_CS_INDEX(cfn), 1);
}
} else { // No duration set
@@ -457,7 +457,7 @@ void evalFunctions(CustomFunctionData * functions, CustomFunctionsContext & func
#if defined(FUNCTION_SWITCHES)
if (CFN_FUNC(cfn) == FUNC_PUSH_CUST_SWITCH) {
// Handling duration after function is active
if (timersGetMsTick() < (uint32_t)CFN_VAL2(cfn)) {
if (time_get_ms() < (uint32_t)CFN_VAL2(cfn)) {
g_model.cfsSetSFState(CFN_CS_INDEX(cfn), 1);
}
else {
-2
View File
@@ -256,11 +256,9 @@ bool gpsNewFrameNMEA(char c)
gpsData.numSat = gps_Msg.numSat;
gpsData.hdop = gps_Msg.hdop;
if (gps_Msg.fix) {
__disable_irq(); // do the atomic update of lat/lon
gpsData.latitude = gps_Msg.latitude;
gpsData.longitude = gps_Msg.longitude;
gpsData.altitude = gps_Msg.altitude;
__enable_irq();
}
break;
case FRAME_RMC:
-2
View File
@@ -158,11 +158,9 @@ static void gpsProcessMessage(uint16_t msg_type, uint16_t msg_len,
gpsData.speed = pvt->gSpeed / 100; // speed in 0.1m/s
gpsData.groundCourse = pvt->headMot / 10000; // degrees * 10
if (gpsData.fix) {
__disable_irq(); // do the atomic update of lat/lon
gpsData.longitude = pvt->lon / 10; // degrees * 1.000.000
gpsData.latitude = pvt->lat / 10; // degrees * 1.000.000
gpsData.altitude = pvt->hMSL / 100; // altitude in 0.1m
__enable_irq();
}
#if defined(RTCLOCK)
@@ -529,6 +529,12 @@ coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char *s,
return ((flags & RIGHT) ? orig_pos : pos) - offsetX;
}
coord_t BitmapBuffer::drawText(coord_t x, coord_t y, const char* s, LcdFlags flags)
{
if (!s) return x;
return drawSizedText(x, y, s, strlen(s), flags);
}
// Resize and convert to LVGL image data format with alpha blending to
// background color
void BitmapBuffer::resizeToLVGL(coord_t w, coord_t h)
+1 -5
View File
@@ -132,11 +132,7 @@ class BitmapBuffer
coord_t drawSizedText(coord_t x, coord_t y, const char* s, uint8_t len,
LcdFlags flags = 0);
coord_t drawText(coord_t x, coord_t y, const char* s, LcdFlags flags = 0)
{
if (!s) return x;
return drawSizedText(x, y, s, strlen(s), flags);
}
coord_t drawText(coord_t x, coord_t y, const char* s, LcdFlags flags = 0);
void drawBitmap(coord_t x, coord_t y, const BitmapBuffer* bmp,
coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0,
+1 -3
View File
@@ -176,9 +176,7 @@ LabelDialog::LabelDialog(const char *label, int length, const char* title,
std::function<void(std::string)> _saveHandler) :
ModalWindow(false), saveHandler(std::move(_saveHandler))
{
assert(length <= MAX_LABEL_LENGTH);
strncpy(this->label, label, length);
strncpy(this->label, label, std::min(length, MAX_LABEL_LENGTH));
this->label[length] = '\0';
auto form = new Window(this, rect_t{});
+5 -6
View File
@@ -25,12 +25,11 @@
#include "layout.h"
#include "LvglWrapper.h"
#include "etx_lv_theme.h"
#include "os/sleep.h"
#include "sdcard.h"
#include "view_main.h"
// timers_driver.h
uint32_t timersGetMsTick();
#include "os/sleep.h"
#include "os/time.h"
#include "sdcard.h"
MainWindow* MainWindow::_instance = nullptr;
@@ -63,7 +62,7 @@ void MainWindow::run(bool trash)
LvglWrapper::instance()->run();
#if defined(DEBUG_WINDOWS)
auto start = timersGetMsTick();
auto start = time_get_ms();
#endif
if (widgetRefreshEnable)
@@ -85,7 +84,7 @@ void MainWindow::run(bool trash)
emptyTrash();
#if defined(DEBUG_WINDOWS)
auto delta = timersGetMsTick() - start;
auto delta = time_get_ms() - start;
if (delta > 10) {
TRACE_WINDOWS("MainWindow::run took %dms", delta);
}
@@ -27,6 +27,7 @@
#define SET_DIRTY() storageDirty(EE_MODEL)
#if defined(EXTERNAL_ANTENNA)
PXX1AntennaSettings::PXX1AntennaSettings(Window* parent,
const FlexGridLayout& g,
uint8_t moduleIdx) :
@@ -62,3 +63,4 @@ PXX1AntennaSettings::PXX1AntennaSettings(Window* parent,
antennaChoice->setAvailableHandler(
[=](int8_t mode) { return mode != ANTENNA_MODE_PER_MODEL; });
}
#endif
+2 -3
View File
@@ -30,8 +30,8 @@ uint8_t menuLevel = 0;
void popMenu()
{
killEvents(KEY_EXIT);
if (menuLevel == 0) return;
assert(menuLevel > 0);
menuLevel = menuLevel - 1;
menuEvent = EVT_ENTRY_UP;
// TODO ? AUDIO_KEY_PRESS();
@@ -57,6 +57,7 @@ void chainMenu(MenuHandlerFunc newMenu)
void pushMenu(MenuHandlerFunc newMenu)
{
killAllEvents();
if (menuLevel == DIM(menuHandlers) - 1) return;
if (menuLevel == 0) {
if (newMenu == menuTabGeneral[0].menuFunc)
@@ -71,8 +72,6 @@ void pushMenu(MenuHandlerFunc newMenu)
menuLevel++;
assert(menuLevel < DIM(menuHandlers));
menuHandlers[menuLevel] = newMenu;
menuEvent = EVT_ENTRY;
// ? AUDIO_KEY_PRESS();
+29 -9
View File
@@ -366,8 +366,6 @@ static uint32_t apply_low_pass_filter(uint32_t v, uint32_t v_prev,
static uint32_t apply_calibration(const CalibData* calib, uint32_t v)
{
// Simu uses normed inputs
#if !defined(SIMU)
// Apply calibration relative to mid-point
int32_t s = v - 2 * calib->mid;
s = s * (int32_t)RESX /
@@ -383,10 +381,7 @@ static uint32_t apply_calibration(const CalibData* calib, uint32_t v)
s = 4 * RESX;
}
v = s;
#endif
return v;
return s;
}
static uint32_t apply_multipos(const StepsCalibData* calib, uint32_t v)
@@ -407,6 +402,24 @@ static uint32_t apply_multipos(const StepsCalibData* calib, uint32_t v)
return ANAFILT_MAX;
}
#if defined(SIMU)
// In simulation, assume equally spaced positions across full ADC range
static uint32_t apply_multipos_simu(uint32_t v)
{
constexpr uint32_t ALPHA_MULT = JITTER_ALPHA * ANALOG_MULTIPLIER;
constexpr uint32_t ANAFILT_MAX = 2 * RESX * ALPHA_MULT;
constexpr uint32_t N = XPOTS_MULTIPOS_COUNT;
constexpr uint32_t COUNT = N - 1;
uint32_t pos = v * N / (ANAFILT_MAX + 1);
if (pos > COUNT) pos = COUNT;
return (pos < COUNT)
? (pos * (ANAFILT_MAX + ALPHA_MULT)) / COUNT
: ANAFILT_MAX;
}
#endif
void getADC()
{
auto max_analogs = adcGetMaxInputs(ADC_INPUT_ALL);
@@ -435,14 +448,17 @@ void getADC()
bool is_flex_input = (x >= pot_offset) && (x < pot_offset + max_pots);
bool is_multipos = is_flex_input && IS_POT_MULTIPOS(x - pot_offset);
// 1st: apply calibration
uint32_t v = getAnalogValue(x);
#if !defined(SIMU)
// Apply hardware calibration (not needed in simulation:
// the host already provides normalized ADC-range values)
if (x < max_calib_analogs && !is_multipos) {
v = apply_calibration(&g_eeGeneral.calib[x], v);
}
#endif
// 2nd: apply inversion
// Apply inversion
if (x < pot_offset && getStickInversion(inputMappingConvertMode(x))) {
v = 4 * RESX - v;
}
@@ -451,14 +467,18 @@ void getADC()
v = 4 * RESX - v;
}
// 3rd: apply filtering
// Apply filtering
s_anaFilt[x] = apply_low_pass_filter(v, s_anaFilt[x], x < max_mains);
if (is_multipos) {
#if defined(SIMU)
s_anaFilt[x] = apply_multipos_simu(s_anaFilt[x]);
#else
const auto* calib = (const StepsCalibData*)&g_eeGeneral.calib[x];
if (IS_MULTIPOS_CALIBRATED(calib)) {
s_anaFilt[x] = apply_multipos(calib, s_anaFilt[x]);
}
#endif
}
#if defined(JITTER_MEASURE)
+2 -2
View File
@@ -1200,7 +1200,6 @@ void evalMixes(uint8_t tick10ms)
weight += fp_act[p];
}
}
assert(weight);
mixerCurrentFlightMode = fm;
}
else {
@@ -1235,7 +1234,8 @@ void evalMixes(uint8_t tick10ms)
if (g_eeGeneral.volumeSrc) {
calcVolumeValue(g_eeGeneral.volumeSrc);
} else {
requiredSpeakerVolume = g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF;
requiredSpeakerVolume =
limit<int>(0, g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF, VOLUME_LEVEL_MAX);
}
}
#endif
+4 -3
View File
@@ -23,11 +23,12 @@
#include <chrono>
extern uint64_t simuTimerMicros(void);
uint32_t time_get_ms()
{
return simuTimerMicros() / 1000;
static auto _start = std::chrono::steady_clock::now();;
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
return duration.count();
}
time_point_t time_point_now()
+14 -2
View File
@@ -403,8 +403,14 @@ void getSwitchesPosition(bool startup)
if (IS_POT_MULTIPOS(i)) {
auto analog_idx = offset + i;
StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[analog_idx];
#if defined(SIMU)
{
uint8_t count = XPOTS_MULTIPOS_COUNT - 1;
#else
if (IS_MULTIPOS_CALIBRATED(calib)) {
uint8_t pos = anaIn(analog_idx) / (2 * RESX / calib->count);
uint8_t count = calib->count;
#endif
uint8_t pos = anaIn(analog_idx) / (2 * RESX / count);
uint8_t previousPos = potsPos[i] >> 4;
uint8_t previousStoredPos = potsPos[i] & 0x0F;
if (startup) {
@@ -839,9 +845,15 @@ swsrc_t getMovedSwitch()
for (int i = 0; i < MAX_POTS; i++) {
if (IS_POT_MULTIPOS(i)) {
StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[MAX_STICKS + i];
#if defined(SIMU)
{
uint8_t count = XPOTS_MULTIPOS_COUNT - 1;
#else
if (IS_MULTIPOS_CALIBRATED(calib)) {
uint8_t count = calib->count;
#endif
uint8_t prev = potsPos[i] & 0x0F;
uint8_t next = anaIn(MAX_STICKS + i) / (2 * RESX / calib->count);
uint8_t next = anaIn(MAX_STICKS + i) / (2 * RESX / count);
if (prev != next) {
result = SWSRC_FIRST_MULTIPOS_SWITCH + i * XPOTS_MULTIPOS_COUNT + next;
}
@@ -38,9 +38,4 @@
#endif
#endif
#if defined(SIMU)
#include "../simu/simpgmspace.h"
#endif
#include "delays_driver.h"
+79 -111
View File
@@ -5,8 +5,27 @@ if(NOT SIMU_TARGET)
return()
endif()
if(MINGW)
# struct packing breaks on MinGW w/out -mno-ms-bitfields:
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991
# http://stackoverflow.com/questions/24015852/struct-packing-and-alignment-with-mingw
#
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-ms-bitfields")
endif()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
if(ASAN AND NOT WASI)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
set(SIMU_DRIVERS
simpgmspace.cpp
simulib.cpp
simufatfs.cpp
simudisk.cpp
simulcd.cpp
@@ -20,10 +39,9 @@ set(SIMU_DRIVERS
bt_driver.cpp
timers_driver.cpp
abnormal_reboot.cpp
)
)
set(HW_DESC_JSON ${FLAVOUR}.json)
# AddHardwareDefTarget(${HW_DESC_JSON})
AddHWGenTarget(${HW_DESC_JSON} simu_keys simu_keys.inc)
AddHWGenTarget(${HW_DESC_JSON} simu_switches simu_switches.inc)
AddHWGenTarget(${HW_DESC_JSON} hal_adc_inputs hal_adc_inputs.inc)
@@ -32,35 +50,21 @@ set(SIMU_DRIVERS ${SIMU_DRIVERS}
${CMAKE_CURRENT_BINARY_DIR}/simu_keys.inc
${CMAKE_CURRENT_BINARY_DIR}/simu_switches.inc
${CMAKE_CURRENT_BINARY_DIR}/hal_adc_inputs.inc
${RADIO_SRC_DIR}/boards/hw_defs/${HW_DESC_JSON}
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
if(SIMU_LUA_COMPILER)
add_definitions(-DLUA_COMPILER)
endif()
if(SIMU_BOOTLOADER)
add_definitions(-DSIMU_BOOTLOADER)
set(SIMU_DRIVERS ${SIMU_DRIVERS}
../../bootloader/bin_files.cpp
../../bootloader/boot.cpp
# TODO - how to handle this for B&W (not currently used)?
../../gui/colorlcd/boot_menu.cpp
../../thirdparty/lz4/lz4.c
)
endif()
# Pack all simu driver sources into an object lib
add_library(simu_drivers OBJECT EXCLUDE_FROM_ALL
${SIMU_DRIVERS})
${SIMU_DRIVERS}
)
get_property(SIMU_SRC_OPTIONS
TARGET radiolib_native
PROPERTY INTERFACE_COMPILE_OPTIONS)
PROPERTY INTERFACE_COMPILE_OPTIONS
)
target_compile_options(simu_drivers PRIVATE ${SIMU_SRC_OPTIONS})
set_property(TARGET simu_drivers PROPERTY POSITION_INDEPENDENT_CODE ON)
if(SIMU_AUX)
@@ -71,101 +75,64 @@ endif()
set(SIMU_SRC
$<TARGET_OBJECTS:radiolib_native>
$<TARGET_OBJECTS:simu_drivers>
)
)
set(SIMU_SRC ${SIMU_SRC} PARENT_SCOPE)
# Set the options as well in parent scope to be used by unit tests
set(SIMU_SRC_OPTIONS ${SIMU_SRC_OPTIONS} PARENT_SCOPE)
if(Qt6Widgets_FOUND)
set(SIMULATOR_FLAVOUR edgetx-${FLAVOUR})
set(SIMULATOR_TARGET ${SIMULATOR_FLAVOUR}-simulator)
add_definitions(-DSIMULATOR_FLAVOUR="${SIMULATOR_FLAVOUR}")
include_directories(${COMPANION_SRC_DIRECTORY} ${COMPANION_SRC_DIRECTORY}/simulation)
qt_wrap_cpp(SIMULATOR_SRC
${COMPANION_SRC_DIRECTORY}/simulation/simulatorinterface.h
opentxsimulator.h
)
list(APPEND SIMULATOR_SRC ${SIMU_SRC} opentxsimulator.cpp)
add_library(${SIMULATOR_TARGET} SHARED ${SIMULATOR_SRC})
set_target_properties(${SIMULATOR_TARGET} PROPERTIES PREFIX "lib")
if(TARGET SDL2::SDL2)
set(SDL2_LIBRARIES SDL2::SDL2)
target_sources(${SIMULATOR_TARGET} PRIVATE simuaudio.cpp)
else()
target_sources(${SIMULATOR_TARGET} PRIVATE no_audio.cpp)
endif()
target_compile_options(${SIMULATOR_TARGET} PRIVATE ${SIMU_SRC_OPTIONS})
target_compile_definitions(${SIMULATOR_TARGET} PUBLIC ${APP_COMMON_DEFINES})
target_link_libraries(${SIMULATOR_TARGET} ${SDL2_LIBRARIES} Qt::Core Qt::SerialPort)
# Remove debug symbols on release builds
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
add_custom_command(TARGET ${SIMULATOR_TARGET} POST_BUILD
COMMAND ${CMAKE_STRIP} -x $<TARGET_FILE:${SIMULATOR_TARGET}>)
endif()
add_custom_command(
TARGET ${SIMULATOR_TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/plugins/
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:${SIMULATOR_TARGET}> ${CMAKE_BINARY_DIR}/plugins/
if(WASI)
# strip debug info in Release builds
list(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE
-Wl,--strip-debug
)
# Introduced at 2.5 - TODO replace with a code change
# When compiled shared simulators using libopenui and Companion have classes named MainWindow
# When the simulator is launched from Companion the runtime dynamic linking can result in Companion overiding the simulator
# and crashing the simulator under some OSes
# The flag forces the runtime linker to always look for references in the simulator first
if (NOT CLANG)
target_link_options(${SIMULATOR_TARGET} PRIVATE "LINKER:-Bsymbolic")
set(WASI_COMPILE_OPTIONS
-Os
-fno-exceptions
-fno-rtti
-ffunction-sections
-fdata-sections
# setjmp / longjmp handling
-mllvm -wasm-enable-sjlj
)
if(USE_LTO)
list(APPEND WASI_COMPILE_OPTIONS -flto=thin)
endif()
#
add_custom_target(libsimulator DEPENDS ${SIMULATOR_TARGET})
# Prepare the "all-simu-libs" target to build simulator libraries for *every* supported PCB type (PCB_TYPES list)
# (a fast build machine or corresponding amount of patience is recommended for this target).
if(${CMAKE_GENERATOR} MATCHES ".*Unix Makefiles$")
# Ensure parallel builds with GCC make (try with MAKEFLAGS but this seems to be getting clobbered somewhere).
# otherwise 'make' may complain: "warning: jobserver unavailable: using -j1..." and build single-threaded. (Ninja always rocks this.)
set(mkflags "$ENV{MAKEFLAGS}")
if (mkflags STREQUAL "")
set(mkflags "-j")
endif()
target_compile_options(radiolib_native PUBLIC ${WASI_COMPILE_OPTIONS})
target_compile_options(simu_drivers PUBLIC ${WASI_COMPILE_OPTIONS})
add_executable(wasi-module EXCLUDE_FROM_ALL
${SIMU_SRC}
)
set_target_properties(wasi-module PROPERTIES
OUTPUT_NAME "edgetx-${FLAVOUR}-simulator"
SUFFIX ".wasm"
)
target_compile_options(wasi-module PUBLIC ${WASI_COMPILE_OPTIONS})
target_link_options(wasi-module PRIVATE
-Wl,--import-memory
-Wl,--export-memory
-Wl,--max-memory=2147483648
-Wl,--gc-sections
# enable runtime to check stack / heap
-Wl,--export=__heap_base,--export=__data_end
-Wl,--export=malloc -Wl,--export=free
# no main()
-mexec-model=reactor
# setjmp / longjmp
-Wl,-mllvm,-wasm-enable-sjlj
-lsetjmp
)
if(USE_LTO)
target_link_options(wasi-module PRIVATE -Wl,--thinlto-jobs=all)
endif()
set(all_libs_cmd ${CMAKE_COMMAND} -E echo "Building all simulator libraries")
foreach(pcb ${PCB_TYPES})
set(pcbrev "")
if(pcb STREQUAL X12S)
set(pcbrev "-DPCBREV=13")
endif()
set(all_libs_cmd ${all_libs_cmd} && ${CMAKE_COMMAND} -DPCB=${pcb} ${pcbrev} -Wno-dev ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} && ${CMAKE_COMMAND} --build . --target libsimulator -- ${mkflags})
endforeach()
add_custom_target(all-simu-libs COMMAND ${all_libs_cmd} USES_TERMINAL WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
endif()
if(MINGW)
# struct packing breaks on MinGW w/out -mno-ms-bitfields: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 & http://stackoverflow.com/questions/24015852/struct-packing-and-alignment-with-mingw
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-ms-bitfields")
endif()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
if(ASAN)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
if(TARGET SDL2::SDL2)
else()
add_executable(simu
EXCLUDE_FROM_ALL
${SIMU_SRC}
@@ -177,15 +144,16 @@ if(TARGET SDL2::SDL2)
sdl_simu.cpp
)
include(FetchImgui)
target_compile_options(simu PUBLIC -DSIMU)
add_png_target(image_assets "assets/images/*.png")
add_dependencies(simu image_assets)
# STB
set(STB_DIR ${RADIO_SRC_DIR}/thirdparty/stb)
target_include_directories(simu PUBLIC ${STB_DIR})
target_link_libraries(simu PUBLIC imgui)
endif()
PrintTargetReport("simu/libsimulator")
# ImGui
include(FetchImgui)
target_link_libraries(simu PRIVATE imgui)
endif()
+15 -8
View File
@@ -19,20 +19,24 @@
* GNU General Public License for more details.
*/
#include "simulib.h"
#include "hal/adc_driver.h"
#include "definitions.h"
#include "definitions.h"
#include "myeeprom.h"
#include "board.h"
#include "edgetx.h"
#include "translations/translations.h"
#include "hal_adc_inputs.inc"
void enableVBatBridge(){}
void disableVBatBridge(){}
bool isVBatBridgeEnabled(){ return false; }
uint16_t getLuxSensorValue() { return 1024; }
uint16_t getLuxSensorValue()
{
if (adcGetMaxInputs(ADC_INPUT_LUX) < 1) return 0;
return anaIn(adcGetInputOffset(ADC_INPUT_LUX));
}
uint16_t getBatteryVoltage()
{
@@ -45,19 +49,22 @@ uint16_t getRTCBatteryVoltage()
return 300;
}
extern uint16_t simu_get_analog(uint8_t idx);
extern uint16_t simuGetAnalog(uint8_t idx);
static bool simu_start_conversion()
{
int max_input = adcGetInputOffset(ADC_INPUT_VBAT);
int max_input = adcGetMaxInputs(ADC_INPUT_ALL);
for (int i = 0; i < max_input; i++) {
setAnalogValue(i, simu_get_analog(i));
setAnalogValue(i, simuGetAnalog(i));
}
// set batteries default voltages
int i = adcGetInputOffset(ADC_INPUT_VBAT);
if (i > 0) {
uint16_t volts = (uint16_t)((g_eeGeneral.vBatWarn > 0 ? g_eeGeneral.vBatWarn : BATTERY_WARN) + 5) * 10; // +0.5V and prec2
// +0.5V and prec2
uint16_t vBatWarn = BATTERY_WARN;
if (g_eeGeneral.vBatWarn > 0) vBatWarn = g_eeGeneral.vBatWarn;
uint16_t volts = (vBatWarn + 5) * 10;
setAnalogValue(i, volts * 2);
}
+1 -3
View File
@@ -20,7 +20,7 @@
*/
#include "audio.h"
#include "simuaudio.h"
#include "simulib.h"
#if defined(AUDIO_HP_DETECT_PIN)
bool audioHeadphoneDetect()
@@ -51,8 +51,6 @@ int simuAudioGetVolume()
#endif
}
void simuQueueAudio(const uint8_t* data, uint32_t len);
void audioConsumeCurrentBuffer()
{
auto& fifo = audioQueue.buffersFifo;
+47
View File
@@ -0,0 +1,47 @@
# Simulator Plugin Interface
## Methods
```c
/**
* Simu module functions
*/
/* Simulator plugin name */
const char* simu_get_name();
/* Start/stop */
void simuInit();
void simuStop(); // should be async but isn't
bool simuIsRunning();
/* Live data */
unsigned int simu_get_num_outputs();
const int16_t* simu_get_outputs();
unsigned int simu_get_num_channels();
const int16_t* simu_get_channels();
unsigned int simu_get_num_logical_switches();
uint32_t* simu_get_logical_switches();
uint8_t simu_get_flight_mode();
uint8_t simu_get_trim_value(uint8_t flight_mode, uint8_t axis);
uint8_t simu_get_stick_mode();
uint16_t simu_get_trim_range();
void simu_get_flight_mode_name(uint8_t flight_mode, char* buffer, int buflen);
// TODO: get GVar definitions + values
/**
* Callbacks / native functions
*/
void simu_read_analogs(uint16_t* analogs, int num_analogs);
uint32_t simu_read_keys();
uint32_t simu_read_trims();
void simu_flush_display(bool backlight, uint8_t* buffer, int buflen);
```
-945
View File
@@ -1,945 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "opentxsimulator.h"
#include "edgetx.h"
#include "simulcd.h"
#include "simuaudio.h"
#include "switches.h"
#include "serial.h"
#include "myeeprom.h"
#include "hal/adc_driver.h"
#include "hal/rotary_encoder.h"
#include "os/time.h"
#include "boards/generic_stm32/rgb_leds.h"
#include <QDebug>
#include <QElapsedTimer>
#if !defined(MAX_LOGICAL_SWITCHES) && defined(NUM_CSW)
#define MAX_LOGICAL_SWITCHES NUM_CSW
#endif
#define GET_SWITCH_BOOL(sw__) getSwitch((sw__), 0);
#define ETXS_DBG qDebug() << "(" << simuTimerMicros() << "us)"
int16_t g_anas[MAX_ANALOG_INPUTS];
QVector<QIODevice *> OpenTxSimulator::tracebackDevices;
typedef struct {
uint8_t index;
QMutex mutex;
QQueue<uint8_t> receiveBuffer;
OpenTxSimulator * simulator;
} simulated_serial_port_t;
simulated_serial_port_t simulatedSerialPorts[MAX_AUX_SERIAL];
extern etx_serial_port_t * serialPorts[MAX_AUX_SERIAL];
#if defined(HARDWARE_TOUCH)
tmr10ms_t downTime = 0;
tmr10ms_t tapTime = 0;
short tapCount = 0;
#define TAP_TIME 25
#endif
uint16_t simu_get_analog(uint8_t idx)
{
// TODO: return raw values for ADC_INPUT_VBAT and ADC_INPUT_RTC_BAT
// 6POS simu mechanism use a different scale, so needs specific offset
if (IS_POT_MULTIPOS(idx - adcGetInputOffset(ADC_INPUT_FLEX))) {
// Use radio calibration data to determine conversion factor
StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[idx];
int range6POS = 2048; // Default if calibration is not valid
if (calib->count != 0) {
// calculate 6POS switch range from calibration data
int c1 = calib->steps[calib->count - 1] * 32; // last calibration value
int c2 = calib->steps[calib->count - 2] * 32; // 2nd last calibration value
range6POS = c1 + (c1 - c2) / 2;
}
return (g_anas[idx] * range6POS / 2048);
}
return (g_anas[idx] * 2) + 2048;
}
void firmwareTraceCb(const char * text)
{
foreach (QIODevice * dev, OpenTxSimulator::tracebackDevices) {
if (dev)
dev->write(text);
}
}
// Serial port handling needs to know about OpenTxSimulator, so we we
// need to update what's in simpgmspace.cpp when we have a simulator
// to point at.
static void* simulator_host_drv_init(void* hw_def, const etx_serial_init* dev)
{
if (hw_def == nullptr)
return nullptr;
simulated_serial_port_t *port = (simulated_serial_port_t *)hw_def;
port->simulator->drv_auxSerialInit(port->index, dev);
// Return the port definition as the context
return (void *)port;
}
static void simulator_host_drv_deinit(void* ctx)
{
if (ctx == nullptr)
return;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
port->simulator->drv_auxSerialDeinit(port->index);
}
static void simulator_host_drv_send_byte(void* ctx, uint8_t b)
{
if (ctx == nullptr)
return;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
port->simulator->drv_auxSerialSendByte(port->index, b);
}
static void simulator_host_drv_send_buffer(void* ctx, const uint8_t* b, uint32_t l)
{
if (ctx == nullptr)
return;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
port->simulator->drv_auxSerialSendBuffer(port->index, b, l);
}
static int simulator_host_drv_get_byte(void* ctx, uint8_t* b)
{
if (ctx == nullptr)
return 0;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
return port->simulator->drv_auxSerialGetByte(port->index, b);
}
static void simulator_host_drv_set_baudrate(void* ctx, uint32_t baudrate)
{
if (ctx == nullptr)
return;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
port->simulator->drv_auxSerialSetBaudrate(port->index, baudrate);
}
static bool simulator_host_drv_tx_completed(void* ctx) { return true; }
static const etx_serial_driver_t simulator_host_drv = {
.init = simulator_host_drv_init,
.deinit = simulator_host_drv_deinit,
.sendByte = simulator_host_drv_send_byte,
.sendBuffer = simulator_host_drv_send_buffer,
.txCompleted = simulator_host_drv_tx_completed,
.waitForTxCompleted = nullptr,
.enableRx = nullptr,
.getByte = simulator_host_drv_get_byte,
.getLastByte = nullptr,
.getBufferedBytes = nullptr,
.copyRxBuffer = nullptr,
.clearRxBuffer = nullptr,
.getBaudrate = nullptr,
.setBaudrate = simulator_host_drv_set_baudrate,
.setPolarity = nullptr,
.setHWOption = nullptr,
.setReceiveCb = nullptr,
.setIdleCb = nullptr,
.setBaudrateCb = nullptr,
};
OpenTxSimulator::OpenTxSimulator() :
SimulatorInterface(),
m_timer10ms(nullptr),
m_resetOutputsData(true),
m_stopRequested(false)
{
tracebackDevices.clear();
traceCallback = firmwareTraceCb;
// When we create the simulator, we change the UART driver
for (int i = 0; i < MAX_AUX_SERIAL; i++) {
etx_serial_port_t * port = serialPorts[i];
if (port != nullptr) {
port->uart = &simulator_host_drv;
port->hw_def = &(simulatedSerialPorts[i]);
simulatedSerialPorts[i].index = i;
simulatedSerialPorts[i].simulator = this;
}
}
}
OpenTxSimulator::~OpenTxSimulator()
{
traceCallback = nullptr;
tracebackDevices.clear();
for (int i = 0; i < MAX_AUX_SERIAL; i++) {
etx_serial_port_t * port = serialPorts[i];
if (port != nullptr) {
port->hw_def = nullptr;
}
}
if (m_timer10ms)
delete m_timer10ms;
if (isRunning()) {
stop();
QElapsedTimer tmout;
tmout.start();
while (isRunning() && !tmout.hasExpired(1000))
;
}
//qDebug() << "Deleting OpenTxSimulator";
}
QString OpenTxSimulator::name()
{
return QString(SIMULATOR_FLAVOUR);
}
bool OpenTxSimulator::isRunning()
{
QMutexLocker lckr(&m_mtxSimuMain);
return simuIsRunning();
}
void OpenTxSimulator::init()
{
if (isRunning())
return;
ETXS_DBG;
if (!m_timer10ms) {
// make sure we create & control the timer from current thread
m_timer10ms = new QTimer();
m_timer10ms->setInterval(10);
connect(m_timer10ms, &QTimer::timeout, this, &OpenTxSimulator::run);
connect(this, SIGNAL(started()), m_timer10ms, SLOT(start()));
connect(this, SIGNAL(stopped()), m_timer10ms, SLOT(stop()));
}
m_resetOutputsData = true;
setStopRequested(false);
QMutexLocker lckr(&m_mtxSimuMain);
memset(g_anas, 0, sizeof(g_anas));
simuInit();
if (adcGetMaxInputs(ADC_INPUT_RTC_BAT) > 0) {
auto idx = adcGetInputOffset(ADC_INPUT_RTC_BAT);
setAnalogValue(idx, 800);
}
}
void OpenTxSimulator::start(const char * filename, bool tests)
{
if (isRunning()) return;
ETXS_DBG << "file:" << filename << "tests:" << tests;
QMutexLocker lckr(&m_mtxSimuMain);
QMutexLocker slckr(&m_mtxSettings);
simuAudioInit();
simuStart(tests, simuSdDirectory.toLatin1().constData(),
simuSettingsDirectory.toLatin1().constData());
emit started();
QTimer::singleShot(0, this, SLOT(run())); // old style for Qt < 5.4
}
void OpenTxSimulator::stop()
{
if (!isRunning())
return;
ETXS_DBG;
setStopRequested(true);
QMutexLocker lckr(&m_mtxSimuMain);
simuStop();
simuAudioDeInit();
emit stopped();
}
void OpenTxSimulator::setSdPath(const QString & sdPath, const QString & settingsPath)
{
QMutexLocker lckr(&m_mtxSettings);
simuSdDirectory = sdPath;
simuSettingsDirectory = settingsPath;
}
void OpenTxSimulator::setVolumeGain(const int value)
{
QMutexLocker lckr(&m_mtxSettings);
volumeGain = value;
}
void OpenTxSimulator::setRadioData(const QByteArray & data)
{
// #if defined(EEPROM_SIZE)
// QMutexLocker lckr(&m_mtxRadioData);
// eeprom = (uint8_t *)malloc(qMin<int>(EEPROM_SIZE, data.size()));
// memcpy(eeprom, data.data(), qMin<int>(EEPROM_SIZE, data.size()));
// #endif
}
void OpenTxSimulator::readRadioData(QByteArray & dest)
{
// #if defined(EEPROM_SIZE)
// QMutexLocker lckr(&m_mtxRadioData);
// if (eeprom)
// memcpy(dest.data(), eeprom, qMin<int>(EEPROM_SIZE, dest.size()));
// #endif
}
uint8_t * OpenTxSimulator::getLcd()
{
return (uint8_t *)simuLcdBuf;
}
void OpenTxSimulator::setAnalogValue(uint8_t index, int16_t value)
{
static int dim = DIM(g_anas);
if (index < dim)
g_anas[index] = value;
}
void OpenTxSimulator::setSwitch(uint8_t swtch, int8_t state)
{
simuSetSwitch(swtch, state);
}
void OpenTxSimulator::setKey(uint8_t key, bool state)
{
simuSetKey(key, state);
}
void OpenTxSimulator::setTrimSwitch(uint8_t trim, bool state)
{
simuSetTrim(trim, state);
}
void OpenTxSimulator::setTrim(unsigned int idx, int value)
{
unsigned i = inputMappingConvertMode(idx);
uint8_t phase = getTrimFlightMode(getFlightMode(), i);
setTrimValue(phase, i, value);
}
void OpenTxSimulator::setTrainerInput(unsigned int inputNumber, int16_t value)
{
static unsigned dim = DIM(trainerInput);
if (inputNumber < dim)
trainerInput[inputNumber] = qMin(qMax((int16_t)-512, value), (int16_t)512);
}
void OpenTxSimulator::setInputValue(int type, uint8_t index, int16_t value)
{
//qDebug() << type << index << value << this->thread();
switch (type) {
case INPUT_SRC_ANALOG :
case INPUT_SRC_STICK :
case INPUT_SRC_KNOB :
case INPUT_SRC_SLIDER :
setAnalogValue(index, value);
break;
case INPUT_SRC_TXVIN :
if (adcGetMaxInputs(ADC_INPUT_VBAT) > 0) {
auto idx = adcGetInputOffset(ADC_INPUT_VBAT);
setAnalogValue(idx, value);
emit txBatteryVoltageChanged((unsigned int)value);
}
break;
case INPUT_SRC_SWITCH :
setSwitch(index, (int8_t)value);
break;
case INPUT_SRC_TRIM_SW :
setTrimSwitch(index, (bool)value);
break;
case INPUT_SRC_TRIM :
setTrim(index, value);
break;
case INPUT_SRC_KEY :
setKey(index, (bool)value);
break;
case INPUT_SRC_TRAINER :
setTrainerInput(index, value);
break;
case INPUT_SRC_ROTENC : // TODO
default:
return;
}
}
extern volatile rotenc_t rotencValue;
extern volatile uint32_t rotencDt;
void OpenTxSimulator::rotaryEncoderEvent(int steps)
{
if (steps == 0) return;
#if defined(ROTARY_ENCODER_NAVIGATION) && !defined(USE_HATS_AS_KEYS)
static uint32_t last_tick = 0;
if (steps != 0) {
if (g_eeGeneral.rotEncMode == ROTARY_ENCODER_MODE_INVERT_BOTH)
steps *= -1;
rotencValue += steps * ROTARY_ENCODER_GRANULARITY;
// TODO: set rotencDt
uint32_t now = time_get_ms();
uint32_t dt = now - last_tick;
rotencDt += dt;
last_tick = now;
}
#else
int key;
if (keyIsSupported(KEY_UP) | keyIsSupported(KEY_DOWN)) {
key = steps > 0 ? KEY_DOWN : KEY_UP;
} else if (keyIsSupported(KEY_PLUS) | keyIsSupported(KEY_MINUS)) {
key = steps > 0 ? KEY_MINUS : KEY_PLUS;
} else {
return; // not supposed to happen???
}
setKey(key, 1);
QTimer::singleShot(10, [this, key]() { setKey(key, 0); });
#endif // defined(ROTARY_ENCODER_NAVIGATION)
}
void OpenTxSimulator::touchEvent(int type, int x, int y)
{
#if defined(HARDWARE_TOUCH)
tmr10ms_t now = get_tmr10ms();
simTouchState.tapCount = 0;
#endif
switch (type) {
case TouchDown:
TRACE_WINDOWS("[Mouse Press] %d %d", x, y);
#if defined(HARDWARE_TOUCH)
simTouchState.event = TE_DOWN;
simTouchState.startX = simTouchState.x = x;
simTouchState.startY = simTouchState.y = y;
downTime = now;
#endif
break;
case TouchUp:
TRACE_WINDOWS("[Mouse Release] %d %d", x, y);
#if defined(HARDWARE_TOUCH)
if (simTouchState.event == TE_DOWN) {
simTouchState.event = TE_UP;
simTouchState.x = simTouchState.startX;
simTouchState.y = simTouchState.startY;
if (now - downTime <= TAP_TIME) {
if (now - tapTime > TAP_TIME)
tapCount = 1;
else
tapCount++;
simTouchState.tapCount = tapCount;
tapTime = now;
}
} else {
simTouchState.event = TE_SLIDE_END;
}
#endif
break;
case TouchSlide:
TRACE_WINDOWS("[Mouse Move] %d %d", x, y);
#if defined(HARDWARE_TOUCH)
simTouchState.deltaX += x - simTouchState.x;
simTouchState.deltaY += y - simTouchState.y;
if (simTouchState.event == TE_SLIDE ||
abs(simTouchState.deltaX) >= SLIDE_RANGE ||
abs(simTouchState.deltaY) >= SLIDE_RANGE) {
simTouchState.event = TE_SLIDE;
simTouchState.x = x;
simTouchState.y = y;
}
#endif
break;
}
#if defined(HARDWARE_TOUCH)
simTouchOccured=true;
#endif
}
void OpenTxSimulator::lcdFlushed()
{
::lcdFlushed();
}
void OpenTxSimulator::setTrainerTimeout(uint16_t ms)
{
trainerSetTimer(ms);
}
void OpenTxSimulator::sendTelemetry(const uint8_t module, const uint8_t protocol, const QByteArray data)
{
//ETXS_DBG << data;
switch (protocol) {
case SIMU_TELEMETRY_PROTOCOL_FRSKY_SPORT:
sportProcessTelemetryPacket(module,
(uint8_t *)data.constData(),
data.size());
break;
case SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB:
frskyDProcessPacket(module,
(uint8_t *)data.constData(),
data.size());
break;
case SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB_OOB:
// FrSky D telemetry is a stream which can span multiple
// packets. The telemetry parser _could_ be in the middle of a
// user packet when we want to inject telemetry, so we can't just
// call frskyDProcessPacket() with a USRPKT (at least not safely!)
// Instead we will bypass the telemetry stream parser and inject
// out of band into processHubPacket(). This way we also don't
// have to mess with byte stuffing and variable length packets.
//
// Note this doesn't take into account which module it's from, but
// that's how it works in the radio too if you have two frsky D
// modules running at once, so ¯\_(ツ)_/¯
{
uint8_t id = data[0];
uint16_t value = ((uint8_t)(data[2]) << 8) + (uint8_t)(data[1]);
processHubPacket(id, value);
}
break;
case SIMU_TELEMETRY_PROTOCOL_CROSSFIRE:
processCrossfireTelemetryFrame(module,
(uint8_t *)data.constData(),
data.size());
break;
default:
// Do nothing
break;
}
}
void OpenTxSimulator::sendInternalModuleTelemetry(const uint8_t protocol, const QByteArray data)
{
sendTelemetry(INTERNAL_MODULE, protocol, data);
}
void OpenTxSimulator::sendExternalModuleTelemetry(const uint8_t protocol, const QByteArray data)
{
sendTelemetry(EXTERNAL_MODULE, protocol, data);
}
uint8_t OpenTxSimulator::getSensorInstance(uint16_t id, uint8_t defaultValue)
{
for (int i = 0; i < MAX_TELEMETRY_SENSORS; i++) {
if (isTelemetryFieldAvailable(i)) {
TelemetrySensor * sensor = &g_model.telemetrySensors[i];
if (sensor->id == id) {
return sensor->frskyInstance.physID;
}
}
}
return defaultValue;
}
uint16_t OpenTxSimulator::getSensorRatio(uint16_t id)
{
for (int i = 0; i < MAX_TELEMETRY_SENSORS; i++) {
if (isTelemetryFieldAvailable(i)) {
TelemetrySensor * sensor = &g_model.telemetrySensors[i];
if (sensor->id == id) {
return sensor->custom.ratio;
}
}
}
return 0;
}
const int OpenTxSimulator::getCapability(Capability cap)
{
int ret = 0;
switch(cap) {
case CAP_LUA :
#ifdef LUA
ret = 1;
#endif
break;
case CAP_ROTARY_ENC :
break;
case CAP_ROTARY_ENC_NAV :
#ifdef ROTARY_ENCODER_NAVIGATION
ret = 1;
#endif
break;
case CAP_TELEM_FRSKY_SPORT :
ret = 1;
break;
case CAP_SERIAL_AUX1:
ret = (auxSerialGetPort(SP_AUX1) != nullptr);
break;
case CAP_SERIAL_AUX2:
ret = (auxSerialGetPort(SP_AUX2) != nullptr);
break;
}
return ret;
}
void OpenTxSimulator::setLuaStateReloadPermanentScripts()
{
#if defined(LUA)
luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
#endif
}
void OpenTxSimulator::addTracebackDevice(QIODevice * device)
{
QMutexLocker lckr(&m_mtxTbDevices);
if (device && !tracebackDevices.contains(device))
tracebackDevices.append(device);
}
void OpenTxSimulator::removeTracebackDevice(QIODevice * device)
{
if (device) {
QMutexLocker lckr(&m_mtxTbDevices);
// no QVector::removeAll() in Qt < 5.4
int i = 0;
foreach (QIODevice * d, tracebackDevices) {
if (d == device)
tracebackDevices.remove(i);
++i;
}
}
}
void OpenTxSimulator::receiveAuxSerialData(quint8 port_nr, const QByteArray & data)
{
if (port_nr >= MAX_AUX_SERIAL)
return;
QMutexLocker locker(&(simulatedSerialPorts[port_nr].mutex));
for (uint8_t byte : data)
simulatedSerialPorts[port_nr].receiveBuffer.enqueue(byte);
}
void OpenTxSimulator::drv_auxSerialSetBaudrate(quint8 port_nr, quint32 baudrate)
{
emit auxSerialSetBaudrate(port_nr, baudrate);
}
void OpenTxSimulator::drv_auxSerialInit(quint8 port_nr, const etx_serial_init* dev)
{
switch(dev->encoding) {
case ETX_Encoding_8N1:
emit auxSerialSetEncoding(port_nr, SERIAL_ENCODING_8N1);
break;
case ETX_Encoding_8E2:
emit auxSerialSetEncoding(port_nr, SERIAL_ENCODING_8E2);
break;
default:
// Do nothing, host hardware can't do SERIAL_ENCODING_PXX1_PWM
break;
}
if (dev->baudrate != 0)
emit auxSerialSetBaudrate(port_nr, dev->baudrate);
emit auxSerialStart(port_nr);
}
void OpenTxSimulator::drv_auxSerialDeinit(quint8 port_nr)
{
emit auxSerialStop(port_nr);
}
void OpenTxSimulator::drv_auxSerialSendByte(quint8 port_nr, uint8_t b)
{
QByteArray data = QByteArray((const char *)&b, 1);
emit auxSerialSendData(port_nr, data);
}
void OpenTxSimulator::drv_auxSerialSendBuffer(quint8 port_nr, const uint8_t* b, uint32_t l)
{
QByteArray data = QByteArray((const char *)b, l);
emit auxSerialSendData(port_nr, data);
}
int OpenTxSimulator::drv_auxSerialGetByte(quint8 port_nr, uint8_t *b)
{
// Obtain the port's mutex before messing with the buffer
QMutexLocker locker(&(simulatedSerialPorts[port_nr].mutex));
if (simulatedSerialPorts[port_nr].receiveBuffer.isEmpty())
return 0;
uint8_t byte = simulatedSerialPorts[port_nr].receiveBuffer.dequeue();
*b = byte;
return 1;
}
/*** Protected functions ***/
void OpenTxSimulator::run()
{
static uint32_t loops = 0;
static QElapsedTimer ts;
if (!loops)
ts.start();
if (isStopRequested()) {
return;
}
if (!isRunning()) {
QString err(getError());
emit runtimeError(err);
emit stopped();
return;
}
++loops;
checkLcdChanged();
checkFuncSwitchChanged();
if (!(loops % 5)) {
checkOutputsChanged();
}
if (!(loops % (SIMULATOR_INTERFACE_HEARTBEAT_PERIOD / 10))) {
emit heartbeat(loops, simuTimerMicros() / 1000);
}
}
bool OpenTxSimulator::isStopRequested()
{
QMutexLocker lckr(&m_mtxStopReq);
return m_stopRequested;
}
void OpenTxSimulator::setStopRequested(bool stop)
{
QMutexLocker lckr(&m_mtxStopReq);
m_stopRequested = stop;
}
bool OpenTxSimulator::checkLcdChanged()
{
if (simuLcdRefresh) {
simuLcdRefresh = false;
emit lcdChange(isBacklightEnabled());
return true;
}
return false;
}
void OpenTxSimulator::checkFuncSwitchChanged()
{
#if defined(FUNCTION_SWITCHES)
for (int i = 0; i < CPN_MAX_SWITCHES; i += 1) {
if (switchIsCustomSwitch(i)) {
uint8_t cfs = switchGetCustomSwitchIdx(i);
uint32_t c = rgbGetLedColor(cfs);
emit fsColorChange(i, c);
}
}
#endif
}
void OpenTxSimulator::checkOutputsChanged()
{
static TxOutputs lastOutputs;
static size_t chansDim = DIM(channelOutputs);
const static int16_t limit = 512 * 2;
qint32 tmpVal;
uint8_t i, idx;
const uint8_t phase = getFlightMode(); // edgetx.cpp
for (i=0; i < chansDim; i++) {
if (lastOutputs.chans[i] != channelOutputs[i] || m_resetOutputsData) {
emit channelOutValueChange(i, channelOutputs[i], (g_model.extendedLimits ? limit * LIMIT_EXT_PERCENT / 100 : limit));
emit outputValueChange(OUTPUT_SRC_CHAN_OUT, i, channelOutputs[i]);
lastOutputs.chans[i] = channelOutputs[i];
}
if (lastOutputs.ex_chans[i] != ex_chans[i] || m_resetOutputsData) {
emit channelMixValueChange(i, ex_chans[i], limit * 2);
emit outputValueChange(OUTPUT_SRC_CHAN_MIX, i, ex_chans[i]);
lastOutputs.ex_chans[i] = ex_chans[i];
}
}
for (i=0; i < MAX_LOGICAL_SWITCHES; i++) {
tmpVal = (qint32)GET_SWITCH_BOOL(SWSRC_FIRST_LOGICAL_SWITCH+i);
if (lastOutputs.vsw[i] != (bool)tmpVal || m_resetOutputsData) {
emit virtualSwValueChange(i, tmpVal);
emit outputValueChange(OUTPUT_SRC_VIRTUAL_SW, i, tmpVal);
lastOutputs.vsw[i] = tmpVal;
}
}
for (i=0; i < Board::TRIM_AXIS_COUNT; i++) {
idx = inputMappingConvertMode(i);
tmpVal = getTrimValue(getTrimFlightMode(phase, idx), idx);
if (lastOutputs.trims[i] != tmpVal || m_resetOutputsData) {
emit trimValueChange(i, tmpVal);
emit outputValueChange(OUTPUT_SRC_TRIM_VALUE, i, tmpVal);
lastOutputs.trims[i] = tmpVal;
}
}
tmpVal = g_model.extendedTrims ? TRIM_EXTENDED_MAX : TRIM_MAX;
if (lastOutputs.trimRange != tmpVal || m_resetOutputsData) {
emit trimRangeChange(Board::TRIM_AXIS_COUNT, -tmpVal, tmpVal);
emit outputValueChange(OUTPUT_SRC_TRIM_RANGE, Board::TRIM_AXIS_COUNT, tmpVal);
lastOutputs.trimRange = tmpVal;
}
if (lastOutputs.phase != phase || m_resetOutputsData) {
emit phaseChanged(phase, getCurrentPhaseName());
emit outputValueChange(OUTPUT_SRC_PHASE, 0, qint16(phase));
lastOutputs.phase = phase;
}
#if defined(GVAR_VALUE) && defined(GVARS)
gVarMode_t gvar;
for (uint8_t gv=0; gv < MAX_GVARS; gv++) {
gvar.prec = g_model.gvars[gv].prec;
gvar.unit = g_model.gvars[gv].unit;
for (uint8_t fm=0; fm < MAX_FLIGHT_MODES; fm++) {
gvar.mode = fm;
gvar.value = (int16_t)GVAR_VALUE(gv, getGVarFlightMode(fm, gv));
tmpVal = gvar;
if (lastOutputs.gvars[fm][gv] != tmpVal || m_resetOutputsData) {
lastOutputs.gvars[fm][gv] = tmpVal;
emit gVarValueChange(gv, tmpVal);
emit outputValueChange(OUTPUT_SRC_GVAR, gv, tmpVal);
}
}
}
#endif
m_resetOutputsData = false;
}
uint8_t OpenTxSimulator::getStickMode()
{
return limit<uint8_t>(0, g_eeGeneral.stickMode, 3);
}
const char * OpenTxSimulator::getPhaseName(unsigned int phase)
{
static char buff[LEN_FLIGHT_MODE_NAME+1];
strAppend(buff, g_model.flightModeData[phase].name, LEN_FLIGHT_MODE_NAME);
return buff;
}
const QString OpenTxSimulator::getCurrentPhaseName()
{
unsigned phase = getFlightMode();
QString name(getPhaseName(phase));
if (name.isEmpty())
name = QString::number(phase);
return name;
}
const char * OpenTxSimulator::getError()
{
return main_thread_error;
}
/*
* OpenTxSimulatorFactory
*/
class OpenTxSimulatorFactory: public SimulatorFactory
{
public:
OpenTxSimulatorFactory()
{
}
virtual SimulatorInterface * create()
{
return new OpenTxSimulator();
}
virtual QString name()
{
return QString(SIMULATOR_FLAVOUR);
}
virtual Board::Type type()
{
#if defined(PCBX12S)
return Board::BOARD_HORUS_X12S;
#elif defined(PCBX10)
return Board::BOARD_X10;
#elif defined(PCBX7ACCESS)
return Board::BOARD_TARANIS_X7_ACCESS;
#elif defined(PCBX7)
return Board::BOARD_TARANIS_X7;
#elif defined(PCBX9LITES)
return Board::BOARD_TARANIS_X9LITES;
#elif defined(PCBX9LITE)
return Board::BOARD_TARANIS_X9LITE;
#elif defined(PCBPL18)
return Board::BOARD_FLYSKY_PL18;
#else
return Board::BOARD_TARANIS_X9D;
#endif
}
};
extern "C" DLLEXPORT SimulatorFactory * registerSimu()
{
return new OpenTxSimulatorFactory();
}
-123
View File
@@ -1,123 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "simulatorinterface.h"
#include "dataconstants.h"
#include <QMutex>
#include <QObject>
#include <QTimer>
#if defined __GNUC__
#define DLLEXPORT
#else
#define DLLEXPORT __declspec(dllexport)
#endif
class DLLEXPORT OpenTxSimulator : public SimulatorInterface
{
Q_OBJECT
public:
enum TouchEvent {
TouchUp=0,
TouchDown,
TouchSlide
};
OpenTxSimulator();
virtual ~OpenTxSimulator();
virtual QString name();
virtual bool isRunning();
virtual void readRadioData(QByteArray & dest);
virtual uint8_t * getLcd();
virtual uint8_t getSensorInstance(uint16_t id, uint8_t defaultValue = 0);
virtual uint16_t getSensorRatio(uint16_t id);
virtual const int getCapability(Capability cap);
static QVector<QIODevice *> tracebackDevices;
void drv_auxSerialInit(quint8 port_num, const etx_serial_init* dev);
void drv_auxSerialDeinit(quint8 port_num);
void drv_auxSerialSetBaudrate(quint8 port_num, uint32_t baudrate);
void drv_auxSerialSendByte(quint8 port_num, uint8_t byte);
void drv_auxSerialSendBuffer(quint8 port_num, const uint8_t* data, uint32_t size);
int drv_auxSerialGetByte(quint8 port_num, uint8_t* data);
public slots:
virtual void init();
virtual void start(const char * filename = nullptr, bool tests = true);
virtual void stop();
virtual void setSdPath(const QString & sdPath = "", const QString & settingsPath = "");
virtual void setVolumeGain(const int value);
virtual void setRadioData(const QByteArray & data);
virtual void setAnalogValue(uint8_t index, int16_t value);
virtual void setKey(uint8_t key, bool state);
virtual void setSwitch(uint8_t swtch, int8_t state);
virtual void setTrim(unsigned int idx, int value);
virtual void setTrimSwitch(uint8_t trim, bool state);
virtual void setTrainerInput(unsigned int inputNumber, int16_t value);
virtual void setInputValue(int type, uint8_t index, int16_t value);
virtual void rotaryEncoderEvent(int steps);
virtual void touchEvent(int type, int x, int y);
virtual void lcdFlushed();
virtual void setTrainerTimeout(uint16_t ms);
virtual void sendTelemetry(const uint8_t module, const uint8_t protocol, const QByteArray data);
virtual void sendInternalModuleTelemetry(const uint8_t protocol, const QByteArray data);
virtual void sendExternalModuleTelemetry(const uint8_t protocol, const QByteArray data);
virtual void setLuaStateReloadPermanentScripts();
virtual void addTracebackDevice(QIODevice * device);
virtual void removeTracebackDevice(QIODevice * device);
virtual void receiveAuxSerialData(const quint8 port_num, const QByteArray & data);
protected slots:
void run();
protected:
bool isStopRequested();
void setStopRequested(bool stop);
bool checkLcdChanged();
void checkOutputsChanged();
void checkFuncSwitchChanged();
uint8_t getStickMode();
const char * getPhaseName(unsigned int phase);
const QString getCurrentPhaseName();
const char * getError();
QString simuSdDirectory;
QString simuSettingsDirectory;
QTimer * m_timer10ms;
QMutex m_mtxStopReq;
QMutex m_mtxSimuMain;
QMutex m_mtxRadioData;
QMutex m_mtxSettings;
QMutex m_mtxTbDevices;
int volumeGain;
bool m_resetOutputsData;
bool m_stopRequested;
};
+22 -5
View File
@@ -38,6 +38,10 @@
#include "hal/rotary_encoder.h"
#include "hal/switch_driver.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#if !SDL_VERSION_ATLEAST(2,0,19)
#error This backend requires SDL 2.0.19+ because of SDL_RenderGeometryRaw() function
#endif
@@ -62,7 +66,7 @@
#include "display.h"
#include "simuaudio.h"
#include "simpgmspace.h"
#include "simulib.h"
#include "hal/key_driver.h"
#include "hal/switch_driver.h"
@@ -83,9 +87,11 @@ static SDL_Texture* screen_frame_buffer;
static GimbalState stick_left = {{0.5f, 0.5f}, false};
static GimbalState stick_right = {{0.5f, 0.5f}, false};
#if !defined(__EMSCRIPTEN__)
static const unsigned char _icon_png[] = {
#include "icon.lbm"
};
#endif
#if defined(ROTARY_ENCODER_NAVIGATION)
extern volatile rotenc_t rotencValue;
@@ -292,7 +298,6 @@ static SDL_Texture* LoadTexture(SDL_Renderer* renderer, const unsigned char* pix
return texture;
}
static float switches_width()
{
const int max_sw_line = MAX_SWITCHES / 2;
@@ -731,10 +736,12 @@ int main(int argc, char* argv[])
SDL_GetRendererInfo(renderer, &info);
SDL_Log("Current SDL_Renderer: %s", info.name);
#if !defined(__EMSCRIPTEN__)
SDL_Surface* icon = LoadImage(_icon_png, sizeof(_icon_png));
if (window && icon) {
SDL_SetWindowIcon(window, icon);
}
#endif
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
@@ -779,8 +786,9 @@ int main(int argc, char* argv[])
// Init simulation
simuInit();
simuStart(true, args.getStoragePath().c_str(),
args.getSettingsPath().c_str());
simuFatfsSetPaths(args.getStoragePath().c_str(),
args.getSettingsPath().c_str());
simuStart();
// Main loop
SDL_SetEventFilter([](void*, SDL_Event* event){
@@ -792,6 +800,9 @@ int main(int argc, char* argv[])
return 1;
}, NULL);
#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop([]() { handleEvents(); }, 0, true);
#else
do {
Uint64 start_ts = SDL_GetPerformanceCounter();
if (!handleEvents()) break;
@@ -804,6 +815,7 @@ int main(int argc, char* argv[])
SDL_Delay(std::max(0,(int32_t)floor(16.666f - elapsedMS)));
} while(true);
#endif
// App cleanup
simuStop();
@@ -816,14 +828,16 @@ int main(int argc, char* argv[])
SDL_DestroyTexture(screen_frame_buffer);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
#if !defined(__EMSCRIPTEN__)
SDL_FreeSurface(icon);
#endif
SDL_CloseAudio();
SDL_Quit();
return 0;
}
uint16_t simu_get_analog(uint8_t idx)
uint16_t simuGetAnalog(uint8_t idx)
{
auto max_sticks = adcGetMaxInputs(ADC_INPUT_MAIN);
if (idx < max_sticks) {
@@ -857,3 +871,6 @@ uint16_t simu_get_analog(uint8_t idx)
// probably RTC_BAT
return 0;
}
void simuTrace(const char* text) {}
void simuLcdNotify() {}
-69
View File
@@ -1,69 +0,0 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stddef.h>
#include <errno.h>
#include <string>
#define __disable_irq()
#define __enable_irq()
extern char * main_thread_error;
uint64_t simuTimerMicros(void);
void simuSetKey(uint8_t key, bool state);
void simuSetTrim(uint8_t trim, bool state);
void simuSetSwitch(uint8_t swtch, int8_t state);
#if defined(__cplusplus)
void simuInit();
void simuStart(bool tests = true, const char * sdPath = nullptr, const char * settingsPath = nullptr);
void simuStop();
bool simuIsRunning();
void startEepromThread(const char * filename = "eeprom.bin");
void stopEepromThread();
#endif
void simuMain();
void simuFatfsSetPaths(const char * sdPath, const char * settingsPath);
std::string simuFatfsGetCurrentPath();
std::string simuFatfsGetRealPath(const std::string &p);
#if defined(TRACE_SIMPGMSPACE)
#undef TRACE_SIMPGMSPACE
#define TRACE_SIMPGMSPACE TRACE
#else
#define TRACE_SIMPGMSPACE(...)
#endif
#if defined(HARDWARE_TOUCH)
extern struct TouchState simTouchState;
extern bool simTouchOccured;
#endif
+1
View File
@@ -21,6 +21,7 @@
#include "audio.h"
#include "simuaudio.h"
#include "simulib.h"
#include <stdint.h>
#include <SDL.h>
-3
View File
@@ -21,9 +21,6 @@
#pragma once
#include <stdint.h>
bool simuAudioInit();
void simuAudioDeInit();
void simuQueueAudio(const uint8_t* data, uint32_t len);
int simuAudioGetVolume();
+38 -66
View File
@@ -19,7 +19,7 @@
* GNU General Public License for more details.
*/
#include "simpgmspace.h"
#include "simulib.h"
#if !defined(SIMU_DISKIO)
@@ -31,6 +31,7 @@
#include <filesystem>
#include <fstream>
#include <memory>
#include <optional>
#include <string>
namespace fs = std::filesystem;
@@ -200,22 +201,17 @@ FRESULT file_stat(const std::string& realPath, FILINFO* fno)
// Get last write time
auto ftime = fs::last_write_time(fsPath, ec);
if (!ec) {
try {
auto systime = ftime_to_systime(ftime);
auto time_t_val = std::chrono::system_clock::to_time_t(systime);
struct tm* ltime = localtime(&time_t_val);
auto systime = ftime_to_systime(ftime);
auto time_t_val = std::chrono::system_clock::to_time_t(systime);
struct tm* ltime = localtime(&time_t_val);
if (ltime) {
// Convert to FatFs format
fno->fdate = ((ltime->tm_year - 80) << 9) |
((ltime->tm_mon + 1) << 5) | ltime->tm_mday;
fno->ftime = (ltime->tm_hour << 11) | (ltime->tm_min << 5) |
(ltime->tm_sec / 2);
} else {
fno->fdate = 0;
fno->ftime = 0;
}
} catch (...) {
if (ltime) {
// Convert to FatFs format
fno->fdate = ((ltime->tm_year - 80) << 9) | ((ltime->tm_mon + 1) << 5) |
ltime->tm_mday;
fno->ftime =
(ltime->tm_hour << 11) | (ltime->tm_min << 5) | (ltime->tm_sec / 2);
} else {
fno->fdate = 0;
fno->ftime = 0;
}
@@ -286,16 +282,12 @@ FRESULT f_open(FIL* fil, const TCHAR* name, BYTE flag)
}
}
try {
auto simuFil = new _simu_FIL(realPath, mode);
if (simuFil->stream->is_open()) {
fil->obj.fs = reinterpret_cast<FATFS*>(simuFil);
return FR_OK;
} else {
delete simuFil;
return FR_INVALID_NAME;
}
} catch (...) {
auto simuFil = new _simu_FIL(realPath, mode);
if (simuFil->stream->is_open()) {
fil->obj.fs = reinterpret_cast<FATFS*>(simuFil);
return FR_OK;
} else {
delete simuFil;
return FR_INVALID_NAME;
}
}
@@ -408,35 +400,20 @@ struct _simu_DIR {
if (ec) {
iter = end_iter; // Set to end if error
}
skipDotEntries(); // TODO: check if necessary
}
private:
void skipDotEntries()
{
while (iter != end_iter) {
std::string filename = iter->path().filename().string();
if (filename != "." && filename != "..") {
break;
}
std::error_code ec;
++iter;
}
}
public:
bool hasNext() const { return iter != end_iter; }
fs::directory_entry getNext()
std::optional<fs::directory_entry> getNext()
{
if (iter == end_iter) {
throw std::runtime_error("No more entries");
return std::nullopt;
}
auto current = *iter;
std::error_code ec;
++iter;
skipDotEntries();
return current;
}
@@ -452,13 +429,8 @@ FRESULT f_opendir(DIR* rep, const TCHAR* name)
return FR_NO_PATH;
}
try {
rep->obj.fs = reinterpret_cast<FATFS*>(new _simu_DIR(path));
return FR_OK;
} catch (...) {
rep->obj.fs = nullptr;
return FR_NO_PATH;
}
rep->obj.fs = reinterpret_cast<FATFS*>(new _simu_DIR(path));
return FR_OK;
}
FRESULT f_closedir(DIR* rep)
@@ -479,24 +451,24 @@ FRESULT f_readdir(DIR* rep, FILINFO* fil)
return FR_NO_FILE;
}
try {
auto entry = sd->getNext();
if (fil != nullptr) {
memset(fil->fname, 0, FF_MAX_LFN);
std::string filename = entry.path().filename().string();
size_t copyLen = std::min(filename.length(), size_t(FF_MAX_LFN - 1));
strncpy(fil->fname, filename.c_str(), copyLen);
std::string fullPath = entry.path().string();
return file_stat(fullPath, fil);
}
return FR_OK;
} catch (...) {
auto maybe_entry = sd->getNext();
if (!maybe_entry.has_value()) {
return FR_NO_FILE;
}
auto entry = maybe_entry.value();
if (fil != nullptr) {
memset(fil->fname, 0, FF_MAX_LFN);
std::string filename = entry.path().filename().string();
size_t copyLen = std::min(filename.length(), size_t(FF_MAX_LFN - 1));
strncpy(fil->fname, filename.c_str(), copyLen);
std::string fullPath = entry.path().string();
return file_stat(fullPath, fil);
}
return FR_OK;
}
FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len)
+9 -5
View File
@@ -21,6 +21,7 @@
#include "lcd.h"
#include "simulcd.h"
#include "simulib.h"
#include "rtos.h"
#include <string.h>
#include <utility>
@@ -41,10 +42,11 @@ void lcdInit() {}
void lcdRefresh()
{
// Mark screen dirty for async refresh
simuLcdRefresh = true;
memcpy(simuLcdBuf, displayBuf, DISPLAY_BUFFER_SIZE * sizeof(pixel_t));
// Mark screen dirty and notify host for async refresh
simuLcdRefresh = true;
simuLcdNotify();
}
#else
@@ -102,8 +104,9 @@ static void simuRefreshLcd(lv_disp_drv_t * disp_drv, uint16_t *buffer, const rec
// simply set LVGL's buffer as our current frame buffer
simuLcdBuf = buffer;
// Trigger async refresh
// Trigger async refresh and notify host
simuLcdRefresh = true;
simuLcdNotify();
#else
_copy_area(simuLcdBackBuf, buffer, copy_area);
@@ -118,8 +121,9 @@ static void simuRefreshLcd(lv_disp_drv_t * disp_drv, uint16_t *buffer, const rec
simuLcdBackBuf = _LCD_BUF2;
}
// Trigger async refresh
// Trigger async refresh and notify host
simuLcdRefresh = true;
simuLcdNotify();
// Copy refreshed & rotated areas into new back buffer
uint16_t* src = simuLcdBuf;
@@ -19,29 +19,31 @@
* GNU General Public License for more details.
*/
#include "board.h"
#define SIMPGMSPC_USE_QT 0
#include "edgetx.h"
#include "simulib.h"
#include "simulcd.h"
#include "hal/adc_driver.h"
#include "hal/rotary_encoder.h"
#include "hal/usb_driver.h"
#include "hal/audio_driver.h"
#include "os/sleep.h"
#include "os/task.h"
#include "os/timer_native_impl.h"
#include <errno.h>
#include <stdarg.h>
#include <string>
#if !defined (_MSC_VER) || defined (__GNUC__)
#include <chrono>
#include <sys/time.h>
#include "edgetx.h"
#include "debug.h"
#include "switches.h"
#include "input_mapping.h"
#if defined(GVARS)
#include "gvars.h"
#endif
#include "trainer.h"
#include "telemetry/frsky.h"
#include "telemetry/crossfire.h"
#if defined(LUA)
#include "lua/lua_api.h"
#endif
#include <assert.h>
int g_snapshot_idx = 0;
@@ -61,32 +63,10 @@ rotenc_t rotaryEncoderGetValue()
return rotencValue / ROTARY_ENCODER_GRANULARITY;
}
// TODO: remove all STM32 defs
extern const etx_hal_adc_driver_t simu_adc_driver;
void lcdCopy(void * dest, void * src);
uint64_t simuTimerMicros(void)
{
auto now = std::chrono::steady_clock::now();
return (uint64_t) std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
}
uint16_t getTmr16KHz()
{
return simuTimerMicros() * 2 / 125;
}
uint16_t getTmr2MHz()
{
return simuTimerMicros() * 2;
}
uint32_t timersGetMsTick(void)
{
return simuTimerMicros() / 1000;
}
void lcdFlushed();
void simuInit()
{
@@ -94,6 +74,9 @@ void simuInit()
rotencValue = 0;
#endif
// Route firmware TRACE() output to host via WASM import
traceCallback = simuTrace;
// Init ADC driver callback
adcInit(&simu_adc_driver);
// Switches
@@ -103,7 +86,6 @@ void simuInit()
bool keysStates[MAX_KEYS] = { false };
void simuSetKey(uint8_t key, bool state)
{
// TRACE("simuSetKey(%d, %d)", key, state);
assert(key < DIM(keysStates));
keysStates[key] = state;
}
@@ -111,7 +93,6 @@ void simuSetKey(uint8_t key, bool state)
bool trimsStates[MAX_TRIMS * 2] = { false };
void simuSetTrim(uint8_t trim, bool state)
{
// TRACE("simuSetTrim(%d, %d)", trim, state);
assert(trim < DIM(trimsStates));
trimsStates[trim] = state;
}
@@ -125,7 +106,7 @@ static void* bootloaderThread(void*)
}
#endif
void simuStart(bool tests, const char * sdPath, const char * settingsPath)
void simuStart(bool tests)
{
if (simu_running)
return;
@@ -137,8 +118,6 @@ void simuStart(bool tests, const char * sdPath, const char * settingsPath)
startOptions = (tests ? 0 : OPENTX_START_NO_SPLASH | OPENTX_START_NO_CALIBRATION | OPENTX_START_NO_CHECKS);
simu_shutdown = false;
simuFatfsSetPaths(sdPath, settingsPath);
/*
g_tmr10ms must be non-zero otherwise some SF functions (that use this timer as a marker when it was last executed)
will be executed twice on startup. Normal radio does not see this issue because g_tmr10ms is already a big number
@@ -217,6 +196,42 @@ bool simuIsRunning()
return simu_running;
}
bool simuLcdChanged()
{
bool changed = simuLcdRefresh;
simuLcdRefresh = false;
return changed;
}
uint32_t simuLcdCopy(uint8_t* buf, uint32_t maxLen)
{
uint32_t size = DISPLAY_BUFFER_SIZE * sizeof(pixel_t);
if (size > maxLen) size = maxLen;
memcpy(buf, simuLcdBuf, size);
return size;
}
uint32_t simuLcdGetWidth()
{
return LCD_W;
}
uint32_t simuLcdGetHeight()
{
return LCD_H;
}
uint32_t simuLcdGetDepth()
{
#if defined(COLORLCD)
return 16;
#elif LCD_W == 212
return 4;
#else
return 1;
#endif
}
#if !defined(COLORLCD)
void lcdSetRefVolt(uint8_t val)
{
@@ -278,7 +293,6 @@ uint32_t readKeys()
for (int i = 0; i < MAX_KEYS; i++) {
if (keysStates[i]) {
// TRACE("key pressed %d", i);
result |= 1 << i;
}
}
@@ -292,7 +306,6 @@ uint32_t readTrims()
for (int i = 0; i < keysGetMaxTrims() * 2; i++) {
if (trimsStates[i]) {
// TRACE("trim pressed %d", i);
trims |= 1 << i;
}
}
@@ -508,3 +521,238 @@ struct TouchState getInternalTouchState()
return simTouchState;
}
#endif
void simuTouchDown(int16_t x, int16_t y)
{
#if defined(HARDWARE_TOUCH)
touchPanelDown(x, y);
#endif
}
void simuTouchUp()
{
#if defined(HARDWARE_TOUCH)
touchPanelUp();
#endif
}
void simuRotaryEncoderEvent(int32_t steps)
{
#if defined(ROTARY_ENCODER_NAVIGATION)
rotencValue += steps * ROTARY_ENCODER_GRANULARITY;
#endif
}
int32_t simuGetCapability(uint8_t cap)
{
switch (cap) {
case 0: // CAP_LUA
#ifdef LUA
return 1;
#else
return 0;
#endif
case 1: // CAP_ROTARY_ENC
return 0;
case 2: // CAP_ROTARY_ENC_NAV
#ifdef ROTARY_ENCODER_NAVIGATION
return 1;
#else
return 0;
#endif
case 3: // CAP_TELEM_FRSKY_SPORT
return 1;
case 4: // CAP_SERIAL_AUX1
return (auxSerialGetPort(SP_AUX1) != nullptr) ? 1 : 0;
case 5: // CAP_SERIAL_AUX2
return (auxSerialGetPort(SP_AUX2) != nullptr) ? 1 : 0;
default:
return 0;
}
}
void simuSetTrimValue(uint8_t idx, int32_t value)
{
unsigned i = inputMappingConvertMode(idx);
uint8_t phase = getTrimFlightMode(getFlightMode(), i);
setTrimValue(phase, i, value);
}
void simuSendTelemetry(uint8_t module, uint8_t protocol,
const uint8_t* data, uint32_t len)
{
switch (protocol) {
case 0: // SIMU_TELEMETRY_PROTOCOL_FRSKY_SPORT
sportProcessTelemetryPacket(module, data, len);
break;
case 1: // SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB
frskyDProcessPacket(module, data, len);
break;
case 2: // SIMU_TELEMETRY_PROTOCOL_CROSSFIRE
processCrossfireTelemetryFrame(module, (uint8_t*)data, len);
break;
case 3: // SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB_OOB
if (len >= 3) {
uint8_t id = data[0];
int16_t value = ((uint8_t)(data[2]) << 8) + (uint8_t)(data[1]);
processHubPacket(id, value);
}
break;
default:
break;
}
}
void simuLuaReloadPermanentScripts()
{
#if defined(LUA)
luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
#endif
}
void simuLcdFlushed()
{
::lcdFlushed();
}
uint8_t simuGetMaxTrainerChannels()
{
return MAX_TRAINER_CHANNELS;
}
void simuCopyTrainerInput(const int16_t* buf, uint8_t count)
{
if (count > MAX_TRAINER_CHANNELS)
count = MAX_TRAINER_CHANNELS;
for (uint8_t i = 0; i < count; i++) {
int16_t v = buf[i];
if (v < -512) v = -512;
if (v > 512) v = 512;
trainerInput[i] = v;
}
}
void simuSetTrainerTimeout(uint16_t ms)
{
trainerSetTimer(ms / 10);
}
// -- Output values --
uint8_t simuGetNumChannels()
{
return MAX_OUTPUT_CHANNELS;
}
uint8_t simuCopyChannelOutputs(int16_t* buf, uint8_t maxCount)
{
uint8_t n = MAX_OUTPUT_CHANNELS < maxCount ? MAX_OUTPUT_CHANNELS : maxCount;
memcpy(buf, channelOutputs, n * sizeof(int16_t));
return n;
}
uint8_t simuCopyMixOutputs(int16_t* buf, uint8_t maxCount)
{
uint8_t n = MAX_OUTPUT_CHANNELS < maxCount ? MAX_OUTPUT_CHANNELS : maxCount;
memcpy(buf, ex_chans, n * sizeof(int16_t));
return n;
}
uint8_t simuGetNumLogicalSwitches()
{
return MAX_LOGICAL_SWITCHES;
}
uint8_t simuCopyLogicalSwitches(uint8_t* buf, uint8_t maxCount)
{
uint8_t n = MAX_LOGICAL_SWITCHES < maxCount ? MAX_LOGICAL_SWITCHES : maxCount;
for (uint8_t i = 0; i < n; i++)
buf[i] = getSwitch(SWSRC_FIRST_LOGICAL_SWITCH + i, 0) ? 1 : 0;
return n;
}
int32_t simuGetTrimValue(uint8_t idx)
{
uint8_t phase = getFlightMode();
uint8_t mapped = inputMappingConvertMode(idx);
return getTrimValue(getTrimFlightMode(phase, mapped), mapped);
}
int16_t simuGetTrimRange()
{
return g_model.extendedTrims ? TRIM_EXTENDED_MAX : TRIM_MAX;
}
int32_t simuGetFlightMode()
{
return getFlightMode();
}
uint8_t simuGetNumGVars()
{
#if defined(GVARS)
return MAX_GVARS;
#else
return 0;
#endif
}
uint8_t simuGetNumFlightModes()
{
return MAX_FLIGHT_MODES;
}
int32_t simuGetGVar(uint8_t gv, uint8_t fm)
{
#if defined(GVARS)
if (gv < MAX_GVARS && fm < MAX_FLIGHT_MODES) {
uint8_t prec = g_model.gvars[gv].prec;
uint8_t unit = g_model.gvars[gv].unit;
int16_t value = (int16_t)GVAR_VALUE(gv, getGVarFlightMode(fm, gv));
// Encode as gVarMode_t: value[15:0] | mode[23:16] | prec[25:24] | unit[27:26]
return (((unit & 0x3) << 26) | ((prec & 0x3) << 24) |
((fm & 0xFF) << 16) | (value & 0xFFFF));
}
#endif
return 0;
}
bool simuGetBacklightState()
{
return isBacklightEnabled();
}
// -- Custom (function) switches --
uint8_t simuGetNumCustomSwitches()
{
return NUM_FUNCTIONS_SWITCHES;
}
uint8_t simuGetCustomSwitchIndex(uint8_t cfsIdx)
{
#if defined(FUNCTION_SWITCHES)
return switchGetSwitchFromCustomIdx(cfsIdx);
#else
(void)cfsIdx;
return 0;
#endif
}
bool simuGetCustomSwitchState(uint8_t idx)
{
#if defined(FUNCTION_SWITCHES)
if (idx < NUM_FUNCTIONS_SWITCHES)
return fsLedState(idx);
#endif
return false;
}
uint32_t simuGetCustomSwitchColor(uint8_t idx)
{
#if defined(FUNCTION_SWITCHES)
if (idx < NUM_FUNCTIONS_SWITCHES)
return fsGetLedRGB(idx);
#endif
return 0;
}
+168
View File
@@ -0,0 +1,168 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include <string>
#if __wasm__
#define WASM_EXPORT_AS(name) __attribute__((export_name(name)))
#define WASM_EXPORT(symbol) WASM_EXPORT_AS(#symbol) symbol
#define WASM_IMPORT_AS(name) __attribute__((import_name(name)))
#define WASM_IMPORT(symbol) WASM_IMPORT_AS(#symbol) symbol
#else
#define WASM_EXPORT_AS(name)
#define WASM_EXPORT(symbol) symbol
#define WASM_IMPORT_AS(name)
#define WASM_IMPORT(symbol) symbol
#endif
// -- WASM exports (called by host) --
// Lifecycle: call simuInit() once, then simuFatfsSetPaths() + simuStart().
// Poll simuIsRunning() periodically. Call simuStop() to shut down.
void WASM_EXPORT(simuInit)();
void WASM_EXPORT(simuStart)(bool tests = true);
void WASM_EXPORT(simuStop)();
bool WASM_EXPORT(simuIsRunning)();
// Set SD card and settings paths before simuStart() to avoid STORAGE WARNING.
void WASM_EXPORT(simuFatfsSetPaths)(const char * sdPath, const char * settingsPath);
// Input: keys use Board::Keys enum, switches use Board switch indices,
// trims use Board::TrimSwitches enum (momentary press, not value).
void WASM_EXPORT(simuSetKey)(uint8_t key, bool state);
void WASM_EXPORT(simuSetTrim)(uint8_t trim, bool state);
void WASM_EXPORT(simuSetSwitch)(uint8_t swtch, int8_t state);
// Touch: call simuTouchDown(x,y) on press and repeatedly during drag
// (continuous position updates), then simuTouchUp() on release.
// The firmware detects slides from successive position changes.
void WASM_EXPORT(simuTouchDown)(int16_t x, int16_t y);
void WASM_EXPORT(simuTouchUp)();
// LCD: the firmware calls simuLcdNotify() (import) when a new frame is ready.
// The host waits for this notification, then calls simuLcdCopy() to read the
// framebuffer and simuLcdFlushed() to signal that the buffer can be reused.
// simuLcdChanged() is kept for backward compatibility (Companion polling).
// Depth is bits per pixel (1, 4, or 16).
bool WASM_EXPORT(simuLcdChanged)();
uint32_t WASM_EXPORT(simuLcdCopy)(uint8_t* buf, uint32_t maxLen);
uint32_t WASM_EXPORT(simuLcdGetWidth)();
uint32_t WASM_EXPORT(simuLcdGetHeight)();
uint32_t WASM_EXPORT(simuLcdGetDepth)();
// Rotary encoder: positive steps = clockwise. The firmware handles
// mode inversion and key translation internally.
void WASM_EXPORT(simuRotaryEncoderEvent)(int32_t steps);
// Capabilities: cap values match SimulatorInterface::Capability enum
// (0=LUA, 1=ROTARY_ENC, 2=ROTARY_ENC_NAV, 3=TELEM_FRSKY_SPORT,
// 4=SERIAL_AUX1, 5=SERIAL_AUX2). Returns 0 or 1.
int32_t WASM_EXPORT(simuGetCapability)(uint8_t cap);
int WASM_EXPORT(simuAudioGetVolume)();
// Backlight state: returns true if backlight is currently enabled.
bool WASM_EXPORT(simuGetBacklightState)();
// Custom (function) switches: LED state and RGB color.
// idx is the custom switch index (0-based), not the global switch index.
// Use simuGetNumCustomSwitches() to get the count.
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);
// Value-based trim setting: idx is mapped through inputMappingConvertMode.
void WASM_EXPORT(simuSetTrimValue)(uint8_t idx, int32_t value);
// Telemetry injection: protocol matches SIMU_TELEMETRY_PROTOCOL_* enum.
// module: 0=internal, 1=external.
void WASM_EXPORT(simuSendTelemetry)(uint8_t module, uint8_t protocol,
const uint8_t* data, uint32_t len);
// Lua: trigger reload of permanent scripts.
void WASM_EXPORT(simuLuaReloadPermanentScripts)();
// LCD: notify firmware that host has consumed the LCD buffer.
void WASM_EXPORT(simuLcdFlushed)();
// Trainer input: bulk copy values (-512..512) into trainerInput[].
// Returns the max number of channels.
uint8_t WASM_EXPORT(simuGetMaxTrainerChannels)();
void WASM_EXPORT(simuCopyTrainerInput)(const int16_t* buf, uint8_t count);
void WASM_EXPORT(simuSetTrainerTimeout)(uint16_t ms);
// Output values: polled periodically by host (every ~50ms).
// Bulk copy channel outputs into buf (int16_t[]). Returns channel count.
uint8_t WASM_EXPORT(simuGetNumChannels)();
uint8_t WASM_EXPORT(simuCopyChannelOutputs)(int16_t* buf, uint8_t maxCount);
uint8_t WASM_EXPORT(simuCopyMixOutputs)(int16_t* buf, uint8_t maxCount);
// Bulk copy logical switch states into buf (uint8_t[], 0 or 1). Returns count.
uint8_t WASM_EXPORT(simuGetNumLogicalSwitches)();
uint8_t WASM_EXPORT(simuCopyLogicalSwitches)(uint8_t* buf, uint8_t maxCount);
// Trim values. idx 0..TRIM_AXIS_COUNT-1 (typically 8).
// Returns the trim value for the current flight mode.
int32_t WASM_EXPORT(simuGetTrimValue)(uint8_t idx);
int16_t WASM_EXPORT(simuGetTrimRange)();
// Flight mode: returns current flight mode index (0-based).
int32_t WASM_EXPORT(simuGetFlightMode)();
// GVars: gv 0..N-1, fm 0..M-1. Returns encoded gVarMode_t.
uint8_t WASM_EXPORT(simuGetNumGVars)();
uint8_t WASM_EXPORT(simuGetNumFlightModes)();
int32_t WASM_EXPORT(simuGetGVar)(uint8_t gv, uint8_t fm);
// -- WASM imports (provided by host) --
// simuGetAnalog: return ADC-range value for input at index idx.
// All inputs use 0..4096 range (center=2048), same as SDL simulator.
uint16_t WASM_IMPORT(simuGetAnalog)(uint8_t idx);
void WASM_IMPORT(simuQueueAudio)(const uint8_t* buf, uint32_t len);
// simuTrace: send debug/trace output text to the host for display.
// Called via traceCallback from debugPrintf().
void WASM_IMPORT(simuTrace)(const char* text);
// simuLcdNotify: signal the host that the LCD framebuffer has been updated
// and is ready to be copied. Called from lcdRefresh() (mono) and the LVGL
// flush callback (color). On the host side this wakes an Atomics.waitAsync
// listener so the frame can be rendered without polling.
void WASM_IMPORT(simuLcdNotify)();
// -- Internal (not exported) --
void simuMain();
std::string simuFatfsGetCurrentPath();
std::string simuFatfsGetRealPath(const std::string& p);
#if defined(HARDWARE_TOUCH)
extern struct TouchState simTouchState;
extern bool simTouchOccured;
#endif
+2
View File
@@ -19,6 +19,8 @@
* GNU General Public License for more details.
*/
#include "simulib.h"
#include "hal/switch_driver.h"
#include "definitions.h"
#include "myeeprom.h"
-1
View File
@@ -18,7 +18,6 @@ file(GLOB TEST_SRC_FILES ${RADIO_SRC_DIR}/tests/*.cpp
CONFIGURE_DEPENDS "${RADIO_SRC_DIR}/tests/*.cpp")
set(TEST_SRC_FILES ${TEST_SRC_FILES}
${CMAKE_CURRENT_SOURCE_DIR}/location.h
${SIMU_SRC}
)
+4 -6
View File
@@ -122,12 +122,10 @@ class TersePrinter : public EmptyTestEventListener {
int32_t lastAct = 0;
uint16_t simu_get_analog(uint8_t idx)
{
return 0;
}
void simuQueueAudio(const uint8_t*, uint32_t) {}
uint16_t simuGetAnalog(uint8_t) { return 0; }
void simuQueueAudio(const uint8_t *, uint32_t) {}
void simuTrace(const char* text) {}
void simuLcdNotify() {}
static char _stringResult[200];
+1
View File
@@ -25,6 +25,7 @@
#include <gtest/gtest.h>
#include "edgetx.h"
#include "simulib.h"
#include "model_init.h"
#include "switches.h"
#include "hal/switch_driver.h"
+1 -1
View File
@@ -25,7 +25,7 @@
#include <gtest/gtest.h>
#if !defined(COLORLCD)
#include "simpgmspace.h"
#include "simulib.h"
#include "simulcd.h"
#include "common/stdlcd/draw_functions.h"
+1 -1
View File
@@ -24,7 +24,7 @@
#if defined(COLORLCD)
#include "simpgmspace.h"
#include "simulib.h"
#include "bitmaps.h"
#include "debug.h"
+4
View File
@@ -20,6 +20,10 @@
// #include "user_config.h"
#if __wasm__
#define l_signalT long
#endif
// prevent localeconv()
#define getlocaledecpoint() ('.')