feat: WASM modules based simulation in Companion (#6435)
This commit is contained in:
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* 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 "simulib.h"
|
||||
#include "simulcd.h"
|
||||
|
||||
#include "hal/adc_driver.h"
|
||||
#include "hal/rotary_encoder.h"
|
||||
#include "hal/usb_driver.h"
|
||||
|
||||
#include "os/sleep.h"
|
||||
#include "os/task.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;
|
||||
|
||||
extern uint8_t startOptions;
|
||||
|
||||
char * main_thread_error = nullptr;
|
||||
|
||||
bool simu_shutdown = false;
|
||||
bool simu_running = false;
|
||||
|
||||
|
||||
volatile rotenc_t rotencValue = 0;
|
||||
volatile uint32_t rotencDt = 0;
|
||||
|
||||
rotenc_t rotaryEncoderGetValue()
|
||||
{
|
||||
return rotencValue / ROTARY_ENCODER_GRANULARITY;
|
||||
}
|
||||
|
||||
extern const etx_hal_adc_driver_t simu_adc_driver;
|
||||
|
||||
void lcdCopy(void * dest, void * src);
|
||||
void lcdFlushed();
|
||||
|
||||
void simuInit()
|
||||
{
|
||||
#if defined(ROTARY_ENCODER_NAVIGATION)
|
||||
rotencValue = 0;
|
||||
#endif
|
||||
|
||||
// Route firmware TRACE() output to host via WASM import
|
||||
traceCallback = simuTrace;
|
||||
|
||||
// Init ADC driver callback
|
||||
adcInit(&simu_adc_driver);
|
||||
// Switches
|
||||
switchInit();
|
||||
}
|
||||
|
||||
bool keysStates[MAX_KEYS] = { false };
|
||||
void simuSetKey(uint8_t key, bool state)
|
||||
{
|
||||
assert(key < DIM(keysStates));
|
||||
keysStates[key] = state;
|
||||
}
|
||||
|
||||
bool trimsStates[MAX_TRIMS * 2] = { false };
|
||||
void simuSetTrim(uint8_t trim, bool state)
|
||||
{
|
||||
assert(trim < DIM(trimsStates));
|
||||
trimsStates[trim] = state;
|
||||
}
|
||||
|
||||
#if defined(SIMU_BOOTLOADER)
|
||||
int bootloaderMain();
|
||||
static void* bootloaderThread(void*)
|
||||
{
|
||||
bootloaderMain();
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
void simuStart(bool tests)
|
||||
{
|
||||
if (simu_running)
|
||||
return;
|
||||
|
||||
#if !defined(COLORLCD)
|
||||
menuLevel = 0;
|
||||
#endif
|
||||
|
||||
startOptions = (tests ? 0 : OPENTX_START_NO_SPLASH | OPENTX_START_NO_CALIBRATION | OPENTX_START_NO_CHECKS);
|
||||
simu_shutdown = false;
|
||||
|
||||
/*
|
||||
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
|
||||
before the first call to the Special Functions. Not so in the simulator.
|
||||
|
||||
There is another issue, some other function static variables depend on this value. If simulator is started
|
||||
multiple times in one Companion session, they are set to their initial values only first time the simulator
|
||||
is started. Therefore g_tmr10ms must also be set to non-zero value only the first time, then it must be left
|
||||
alone to continue from the previous simulator session value. See the issue #2446
|
||||
|
||||
*/
|
||||
if (g_tmr10ms == 0) {
|
||||
g_tmr10ms = 1;
|
||||
}
|
||||
|
||||
#if defined(RTCLOCK)
|
||||
time_t rawtime;
|
||||
struct tm * timeinfo;
|
||||
time (&rawtime);
|
||||
timeinfo = localtime (&rawtime);
|
||||
|
||||
if (timeinfo != nullptr) {
|
||||
struct gtm gti;
|
||||
gti.tm_sec = timeinfo->tm_sec;
|
||||
gti.tm_min = timeinfo->tm_min;
|
||||
gti.tm_hour = timeinfo->tm_hour;
|
||||
gti.tm_mday = timeinfo->tm_mday;
|
||||
gti.tm_mon = timeinfo->tm_mon;
|
||||
gti.tm_year = timeinfo->tm_year;
|
||||
gti.tm_wday = timeinfo->tm_wday;
|
||||
gti.tm_yday = timeinfo->tm_yday;
|
||||
g_rtcTime = gmktime(>i);
|
||||
} else {
|
||||
g_rtcTime = rawtime;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Init LCD callbacks
|
||||
lcdInit();
|
||||
|
||||
#if !defined(SIMU_BOOTLOADER)
|
||||
simuMain();
|
||||
#else
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
struct sched_param sp;
|
||||
sp.sched_priority = SCHED_RR;
|
||||
pthread_attr_setschedparam(&attr, &sp);
|
||||
|
||||
pthread_t bl_pid;
|
||||
pthread_create(&bl_pid, &attr, &bootloaderThread, nullptr);
|
||||
#endif
|
||||
|
||||
simu_running = true;
|
||||
}
|
||||
|
||||
extern task_handle_t mixerTaskId;
|
||||
extern task_handle_t menusTaskId;
|
||||
#if defined(AUDIO)
|
||||
extern task_handle_t audioTaskId;
|
||||
#endif
|
||||
|
||||
void simuStop()
|
||||
{
|
||||
if (!simu_running)
|
||||
return;
|
||||
|
||||
simu_shutdown = true;
|
||||
task_shutdown_all();
|
||||
|
||||
simu_running = false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LCD_W == 128
|
||||
void lcdSetInvert(bool invert)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void boardInit()
|
||||
{
|
||||
switchInit();
|
||||
}
|
||||
|
||||
uint32_t pwrCheck() { return simu_shutdown ? e_power_off : e_power_on; }
|
||||
|
||||
bool pwrPressed() { return false; }
|
||||
bool pwrOffPressed()
|
||||
{
|
||||
#if defined(PWR_BUTTON_PRESS)
|
||||
return pwrPressed();
|
||||
#else
|
||||
return !pwrPressed();
|
||||
#endif
|
||||
}
|
||||
|
||||
void pwrInit() {}
|
||||
void pwrOn() {}
|
||||
void pwrOff() {}
|
||||
|
||||
bool UNEXPECTED_SHUTDOWN() { return false; }
|
||||
void SET_POWER_REASON(uint32_t value) {}
|
||||
|
||||
#if defined(TRIMS_EMULATE_BUTTONS)
|
||||
bool trimsAsButtons = false;
|
||||
|
||||
void setHatsAsKeys(bool val) { trimsAsButtons = val; }
|
||||
|
||||
bool getHatsAsKeys()
|
||||
{
|
||||
bool lua = false;
|
||||
#if defined(LUA)
|
||||
lua = isLuaStandaloneRunning();
|
||||
#endif
|
||||
return (trimsAsButtons || lua);
|
||||
}
|
||||
#endif
|
||||
|
||||
void pollKeys()
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t readKeys()
|
||||
{
|
||||
uint32_t result = 0;
|
||||
|
||||
for (int i = 0; i < MAX_KEYS; i++) {
|
||||
if (keysStates[i]) {
|
||||
result |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t readTrims()
|
||||
{
|
||||
uint32_t trims = 0;
|
||||
|
||||
for (int i = 0; i < keysGetMaxTrims() * 2; i++) {
|
||||
if (trimsStates[i]) {
|
||||
trims |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
return trims;
|
||||
}
|
||||
|
||||
int usbPlugged() { return false; }
|
||||
int getSelectedUsbMode() { return USB_JOYSTICK_MODE; }
|
||||
void setSelectedUsbMode(int mode) {}
|
||||
void delay_ms(uint32_t ms) { }
|
||||
void delay_us(uint16_t us) { }
|
||||
|
||||
void flashWrite(uint32_t *address, const uint32_t *buffer)
|
||||
{
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
uint32_t isBootloaderStart(const uint8_t * block)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if defined(PCBXLITES)
|
||||
bool isJackPlugged()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void serialPrintf(const char * format, ...) { }
|
||||
void serialCrlf() { }
|
||||
void serialPutc(char c) { }
|
||||
|
||||
void boardOff()
|
||||
{
|
||||
}
|
||||
|
||||
void hapticOff() {}
|
||||
|
||||
#if defined(HAS_HARDWARE_OPTIONS)
|
||||
HardwareOptions hardwareOptions;
|
||||
#endif
|
||||
|
||||
uint32_t Master_frequency = 0;
|
||||
uint32_t Current_used = 0;
|
||||
uint16_t Current_max = 0;
|
||||
|
||||
void setSticksGain(uint8_t)
|
||||
{
|
||||
}
|
||||
|
||||
uint16_t getCurrent()
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
void calcConsumption()
|
||||
{
|
||||
}
|
||||
|
||||
void handleJackConnection() {}
|
||||
|
||||
int trainerModuleSbusGetByte(unsigned char*) { return 0; }
|
||||
|
||||
void rtcInit()
|
||||
{
|
||||
}
|
||||
|
||||
void rtcGetTime(struct gtm * t)
|
||||
{
|
||||
}
|
||||
|
||||
void rtcSetTime(const struct gtm * t)
|
||||
{
|
||||
}
|
||||
|
||||
#if defined(PCBTARANIS)
|
||||
void sdPoll10ms() {}
|
||||
#endif
|
||||
|
||||
uint32_t SD_GetCardType() { return 0; }
|
||||
|
||||
#if defined(USB_SERIAL)
|
||||
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) { }
|
||||
|
||||
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,
|
||||
.waitForTxCompleted = nullptr,
|
||||
.enableRx = nullptr,
|
||||
.getByte = null_drv_get_byte,
|
||||
.getLastByte = nullptr,
|
||||
.getBufferedBytes = nullptr,
|
||||
.copyRxBuffer = nullptr,
|
||||
.clearRxBuffer = nullptr,
|
||||
.getBaudrate = nullptr,
|
||||
.setBaudrate = null_drv_set_baudrate,
|
||||
.setPolarity = nullptr,
|
||||
.setHWOption = nullptr,
|
||||
.setReceiveCb = nullptr,
|
||||
.setIdleCb = nullptr,
|
||||
.setBaudrateCb = nullptr,
|
||||
};
|
||||
|
||||
#if defined(AUX_SERIAL_PWR_GPIO)
|
||||
static void null_pwr_aux(uint8_t) {}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(AUX_SERIAL)
|
||||
#if defined(AUX_SERIAL_PWR_GPIO)
|
||||
#define AUX_SERIAL_PWR null_pwr_aux
|
||||
#else
|
||||
#define AUX_SERIAL_PWR nullptr
|
||||
#endif
|
||||
static etx_serial_port_t auxSerialPort = {
|
||||
"AUX1",
|
||||
&null_drv,
|
||||
nullptr,
|
||||
AUX_SERIAL_PWR
|
||||
};
|
||||
#define AUX_SERIAL_PORT &auxSerialPort
|
||||
#else
|
||||
#define AUX_SERIAL_PORT nullptr
|
||||
#endif
|
||||
|
||||
#if defined(AUX2_SERIAL)
|
||||
#if defined(AUX_SERIAL_PWR_GPIO)
|
||||
#define AUX2_SERIAL_PWR null_pwr_aux
|
||||
#else
|
||||
#define AUX2_SERIAL_PWR nullptr
|
||||
#endif
|
||||
static etx_serial_port_t aux2SerialPort = {
|
||||
"AUX2",
|
||||
&null_drv,
|
||||
nullptr,
|
||||
AUX2_SERIAL_PWR
|
||||
};
|
||||
#define AUX2_SERIAL_PORT &aux2SerialPort
|
||||
#else
|
||||
#define AUX2_SERIAL_PORT nullptr
|
||||
#endif // AUX2_SERIAL
|
||||
|
||||
etx_serial_port_t* serialPorts[MAX_AUX_SERIAL] = {
|
||||
AUX_SERIAL_PORT,
|
||||
AUX2_SERIAL_PORT,
|
||||
};
|
||||
|
||||
const etx_serial_port_t* auxSerialGetPort(int port_nr)
|
||||
{
|
||||
if (port_nr >= MAX_AUX_SERIAL) return nullptr;
|
||||
return serialPorts[port_nr];
|
||||
}
|
||||
|
||||
#if defined(HARDWARE_TOUCH)
|
||||
struct TouchState simTouchState = {};
|
||||
bool simTouchOccured = false;
|
||||
|
||||
bool touchPanelInit()
|
||||
{
|
||||
simTouchState.x = simTouchState.y = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool touchPanelEventOccured()
|
||||
{
|
||||
if(simTouchOccured)
|
||||
{
|
||||
simTouchOccured = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void touchPanelDown(short x, short y)
|
||||
{
|
||||
simTouchState.x = x;
|
||||
simTouchState.y = y;
|
||||
simTouchState.event = TE_DOWN;
|
||||
simTouchOccured = true;
|
||||
}
|
||||
|
||||
void touchPanelUp()
|
||||
{
|
||||
simTouchState.event = TE_UP;
|
||||
simTouchOccured = true;
|
||||
}
|
||||
|
||||
struct TouchState touchPanelRead()
|
||||
{
|
||||
struct TouchState st = simTouchState;
|
||||
simTouchState.deltaX = 0;
|
||||
simTouchState.deltaY = 0;
|
||||
return st;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user