418 lines
11 KiB
C++
418 lines
11 KiB
C++
/*
|
|
* 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 "edgetx.h"
|
|
#include "os/sleep.h"
|
|
#include "timers_driver.h"
|
|
#include "tasks/mixer_task.h"
|
|
#include "mixes.h"
|
|
#include "switches.h"
|
|
|
|
#if defined(FUNCTION_SWITCHES_RGB_LEDS)
|
|
#include "hal/rgbleds.h"
|
|
#endif
|
|
|
|
#if defined(COLORLCD)
|
|
#include "view_main.h"
|
|
#endif
|
|
|
|
#if defined(USBJ_EX)
|
|
#include "usb_joystick.h"
|
|
#endif
|
|
|
|
#if defined(MULTIMODULE)
|
|
#include "pulses/multi.h"
|
|
#if defined(MULTI_PROTOLIST)
|
|
#include "io/multi_protolist.h"
|
|
#endif
|
|
#endif
|
|
|
|
uint8_t storageDirtyMsk;
|
|
tmr10ms_t storageDirtyTime10ms;
|
|
|
|
#if defined(RTC_BACKUP_RAM)
|
|
uint8_t rambackupDirtyMsk = EE_GENERAL | EE_MODEL;
|
|
tmr10ms_t rambackupDirtyTime10ms;
|
|
#endif
|
|
|
|
void storageDirty(uint8_t msk)
|
|
{
|
|
storageDirtyMsk |= msk;
|
|
storageDirtyTime10ms = get_tmr10ms();
|
|
|
|
#if defined(RTC_BACKUP_RAM)
|
|
rambackupDirtyMsk = storageDirtyMsk;
|
|
rambackupDirtyTime10ms = storageDirtyTime10ms;
|
|
#endif
|
|
}
|
|
|
|
void preModelLoad()
|
|
{
|
|
watchdogSuspend(500/*5s*/);
|
|
|
|
logsClose();
|
|
|
|
bool needDelay = false;
|
|
if (mixerTaskStarted()) {
|
|
pulsesStop();
|
|
needDelay = true;
|
|
}
|
|
|
|
stopTrainer();
|
|
|
|
#if defined(FUNCTION_SWITCHES_RGB_LEDS)
|
|
turnOffRGBLeds();
|
|
#endif
|
|
|
|
if (needDelay) {
|
|
sleep_ms(200);
|
|
}
|
|
}
|
|
|
|
void postRadioSettingsLoad()
|
|
{
|
|
#if LCD_W == 128
|
|
// Prevent GVARS to be off when imported or manually modified yaml
|
|
// Since there is no way to have those back
|
|
g_eeGeneral.modelGVDisabled = false;
|
|
#endif
|
|
|
|
#if defined(PXX2)
|
|
if (is_memclear(g_eeGeneral.ownerRegistrationID, PXX2_LEN_REGISTRATION_ID)) {
|
|
setDefaultOwnerId();
|
|
}
|
|
#endif
|
|
#if defined(PCBX12S) && defined(INTERNAL_GPS)
|
|
// AUX2 is hardwired to AUX2 on X12S
|
|
serialSetMode(SP_AUX2, UART_MODE_GPS);
|
|
#endif
|
|
#if defined(USB_SERIAL)
|
|
// default VCP to CLI if not configured
|
|
// to something else as NONE.
|
|
if (isInternalModuleCrossfire() && serialGetMode(SP_VCP) == UART_MODE_NONE) {
|
|
serialSetMode(SP_VCP, UART_MODE_CLI);
|
|
}
|
|
#endif
|
|
#if defined(DEFAULT_INTERNAL_MODULE)
|
|
if (!g_eeGeneral.internalModule) {
|
|
g_eeGeneral.internalModule = DEFAULT_INTERNAL_MODULE;
|
|
}
|
|
#endif
|
|
#if !defined(DEBUG)
|
|
// clean up leftovers from a previous DEBUG config
|
|
for (uint8_t port_nr = 0; port_nr < MAX_AUX_SERIAL; port_nr++) {
|
|
if (serialGetMode(port_nr) == UART_MODE_DEBUG)
|
|
serialSetMode(port_nr, UART_MODE_NONE);
|
|
}
|
|
#endif
|
|
|
|
#if defined(COLORLCD)
|
|
// Ensure ON brightness >= OFF brightness
|
|
if ((BACKLIGHT_LEVEL_MAX - g_eeGeneral.backlightBright) < g_eeGeneral.blOffBright)
|
|
g_eeGeneral.backlightBright =
|
|
BACKLIGHT_LEVEL_MAX - g_eeGeneral.blOffBright;
|
|
#endif
|
|
}
|
|
|
|
static bool sortMixerLines()
|
|
{
|
|
// simple bubble sort
|
|
unsigned passes = 0;
|
|
unsigned swaps;
|
|
MixData tmp;
|
|
|
|
do {
|
|
swaps = 0;
|
|
for (int i = 0; i < MAX_MIXERS - 1; i++) {
|
|
auto a = mixAddress(i);
|
|
auto b = mixAddress(i + 1);
|
|
|
|
if (b->destCh < a->destCh) {
|
|
if (is_memclear(b, sizeof(MixData)))
|
|
break;
|
|
|
|
memcpy(&tmp, a, sizeof(MixData));
|
|
memcpy(a, b, sizeof(MixData));
|
|
memcpy(b, &tmp, sizeof(MixData));
|
|
++swaps;
|
|
}
|
|
}
|
|
++passes;
|
|
} while(swaps > 0);
|
|
|
|
// anything above 1 means that
|
|
// we changed something
|
|
return passes > 1;
|
|
}
|
|
|
|
static void sanitizeMixerLines()
|
|
{
|
|
bool dirty = sortMixerLines();
|
|
updateMixCount();
|
|
if (dirty) storageDirty(EE_MODEL);
|
|
}
|
|
|
|
void postModelLoad(bool alarms)
|
|
{
|
|
#if defined(COLORLCD)
|
|
if (!g_model.hasScreenData(0))
|
|
LayoutFactory::loadDefaultLayout();
|
|
|
|
if (g_model.topbarWidgetWidth[0] == 0) {
|
|
// Set default width for top bar widgets
|
|
for (int i = 0; i < MAX_TOPBAR_ZONES; i += 1)
|
|
g_model.topbarWidgetWidth[i] = 1;
|
|
|
|
// Load 'date time' widget if slot is empty
|
|
if (!g_model.getTopbarData()->hasWidget(MAX_TOPBAR_ZONES-1)) {
|
|
g_model.getTopbarData()->setWidgetName(MAX_TOPBAR_ZONES-1, "Date Time");
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
// Load 'radio info' widget if slot is empty
|
|
if (!g_model.getTopbarData()->hasWidget(MAX_TOPBAR_ZONES-2)) {
|
|
g_model.getTopbarData()->setWidgetName(MAX_TOPBAR_ZONES-2, "Radio Info");
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
#if defined(INTERNAL_GPS)
|
|
// Load 'internal gps' widget if slot is empty
|
|
if (!g_model.getTopbarData()->hasWidget(MAX_TOPBAR_ZONES-3)) {
|
|
g_model.getTopbarData()->setWidgetName(MAX_TOPBAR_ZONES-3, "Internal GPS");
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
#endif
|
|
}
|
|
#elif LCD_W == 128
|
|
// Prevent GVARS to be off when imported or manually modified yaml
|
|
// Since there is no way to have those back
|
|
g_model.modelGVDisabled = false;
|
|
#endif
|
|
|
|
#if defined(FUNCTION_SWITCHES)
|
|
setFSStartupPosition();
|
|
#endif
|
|
|
|
// Convert 'noGlobalFunctions' to 'radioGFDisabled'
|
|
// TODO: Remove sometime in the future (and remove 'noGlobalFunctions' property)
|
|
if (g_model.noGlobalFunctions) {
|
|
g_model.radioGFDisabled = OVERRIDE_OFF;
|
|
g_model.noGlobalFunctions = 0;
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
|
|
// fix #2552: reset rssiSource to default none (= 0)
|
|
if(g_model.rssiSource) {
|
|
g_model.rssiSource = 0;
|
|
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
#if defined(STM32F4) && defined(CROSSFIRE)
|
|
// Limit ext. CRSF speed to 3.75Mbps due to CRC errors at higher speeds
|
|
if(isModuleCrossfire(EXTERNAL_MODULE) && g_model.moduleData[EXTERNAL_MODULE].crsf.telemetryBaudrate == 4) {
|
|
TRACE("Downgrading external ELRS module baudrate");
|
|
g_model.moduleData[EXTERNAL_MODULE].crsf.telemetryBaudrate = 3;
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
#endif
|
|
|
|
#if defined(PXX2)
|
|
bool changed = false;
|
|
|
|
if (is_memclear(g_model.modelRegistrationID, PXX2_LEN_REGISTRATION_ID)) {
|
|
if (!is_memclear(g_eeGeneral.ownerRegistrationID, PXX2_LEN_REGISTRATION_ID)) {
|
|
memcpy(g_model.modelRegistrationID, g_eeGeneral.ownerRegistrationID, PXX2_LEN_REGISTRATION_ID);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
// fix colorLCD radios not writing yaml tag receivers
|
|
if(isModulePXX2(INTERNAL_MODULE)) {
|
|
ModuleData *intModule = &g_model.moduleData[INTERNAL_MODULE];
|
|
unsigned int oldVal = intModule->pxx2.receivers;
|
|
for(uint8_t receiverIdx = 0; receiverIdx < 3; receiverIdx++) {
|
|
if(intModule->pxx2.receiverName[receiverIdx][0])
|
|
intModule->pxx2.receivers |= (1 << receiverIdx);
|
|
}
|
|
if (oldVal != intModule->pxx2.receivers) changed = true;
|
|
}
|
|
|
|
if(isModulePXX2(EXTERNAL_MODULE)) {
|
|
ModuleData *extModule = &g_model.moduleData[EXTERNAL_MODULE];
|
|
unsigned int oldVal = extModule->pxx2.receivers;
|
|
for(uint8_t receiverIdx = 0; receiverIdx < 3; receiverIdx++) {
|
|
if(extModule->pxx2.receiverName[receiverIdx][0])
|
|
extModule->pxx2.receivers |= (1 << receiverIdx);
|
|
}
|
|
if (oldVal != extModule->pxx2.receivers) changed = true;
|
|
}
|
|
|
|
if (changed)
|
|
storageDirty(EE_MODEL);
|
|
#endif
|
|
|
|
#if defined(MULTIMODULE) && defined(MULTI_PROTOLIST)
|
|
MultiRfProtocols::removeInstance(EXTERNAL_MODULE);
|
|
#endif
|
|
|
|
AUDIO_FLUSH();
|
|
flightReset(false);
|
|
|
|
customFunctionsReset();
|
|
|
|
logicalSwitchesInit(false);
|
|
|
|
restoreTimers();
|
|
|
|
for (int i=0; i<MAX_TELEMETRY_SENSORS; i++) {
|
|
TelemetrySensor & sensor = g_model.telemetrySensors[i];
|
|
if (sensor.type == TELEM_TYPE_CALCULATED && sensor.persistent) {
|
|
telemetryItems[i].value = sensor.persistentValue;
|
|
telemetryItems[i].timeout = 0; // make value visible even before the first new value is received)
|
|
}
|
|
else {
|
|
telemetryItems[i].timeout = TELEMETRY_SENSOR_TIMEOUT_UNAVAILABLE;
|
|
}
|
|
}
|
|
|
|
loadCurves();
|
|
sanitizeMixerLines();
|
|
|
|
#if defined(GUI)
|
|
if (alarms) {
|
|
checkAll();
|
|
PLAY_MODEL_NAME();
|
|
}
|
|
#endif
|
|
|
|
// Mixer should only be restarted
|
|
// if we are switching between models,
|
|
// not on first boot (started later on)
|
|
if (mixerTaskStarted()) {
|
|
pulsesStart();
|
|
}
|
|
|
|
referenceModelAudioFiles();
|
|
|
|
#if !defined(COLORLCD)
|
|
LOAD_MODEL_BITMAP();
|
|
#endif
|
|
|
|
LUA_LOAD_MODEL_SCRIPTS();
|
|
|
|
SEND_FAILSAFE_1S();
|
|
|
|
// Reset debug stats for the newly loaded model (after checkAll() warnings)
|
|
maxMixerDuration = 0;
|
|
#if defined(LUA)
|
|
maxLuaInterval = 0;
|
|
maxLuaDuration = 0;
|
|
lastLuaTime = 0; // avoids counting the load time as one huge interval spike
|
|
#endif
|
|
}
|
|
|
|
void storageFlushCurrentModel()
|
|
{
|
|
saveTimers();
|
|
|
|
for (int i=0; i<MAX_TELEMETRY_SENSORS; i++) {
|
|
TelemetrySensor & sensor = g_model.telemetrySensors[i];
|
|
if (sensor.type == TELEM_TYPE_CALCULATED && sensor.persistent && sensor.persistentValue != telemetryItems[i].value) {
|
|
sensor.persistentValue = telemetryItems[i].value;
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
}
|
|
|
|
if (g_model.potsWarnMode == POTS_WARN_AUTO) {
|
|
for (int i=0; i<MAX_POTS; i++) {
|
|
if (g_model.potsWarnEnabled & (1 << i)) {
|
|
SAVE_POT_POSITION(i);
|
|
}
|
|
}
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
}
|
|
|
|
#if !defined(STORAGE_MODELSLIST)
|
|
void selectModel(uint8_t idx)
|
|
{
|
|
#if !defined(COLORLCD)
|
|
showMessageBox(STR_LOADINGMODEL);
|
|
#endif
|
|
storageFlushCurrentModel();
|
|
storageCheck(true); // force writing of current model data before this is changed
|
|
g_eeGeneral.currModel = idx;
|
|
storageDirty(EE_GENERAL);
|
|
loadModel(idx);
|
|
|
|
#if defined(USBJ_EX) && defined(STM32) && !defined(SIMU)
|
|
onUSBJoystickModelChanged();
|
|
#endif
|
|
}
|
|
|
|
uint8_t findEmptyModel(uint8_t id, bool down)
|
|
{
|
|
uint8_t i = id;
|
|
for (;;) {
|
|
i = (MAX_MODELS + (down ? i+1 : i-1)) % MAX_MODELS;
|
|
if (!modelExists(i)) break;
|
|
if (i == id) return 0xff; // no free space in directory left
|
|
}
|
|
return i;
|
|
}
|
|
|
|
ModelHeader modelHeaders[MAX_MODELS];
|
|
|
|
void loadModelHeaders()
|
|
{
|
|
for (uint32_t i=0; i<MAX_MODELS; i++) {
|
|
loadModelHeader(i, &modelHeaders[i]);
|
|
}
|
|
}
|
|
|
|
uint8_t findNextUnusedModelId(uint8_t index, uint8_t module)
|
|
{
|
|
uint8_t usedModelIds[(MAX_RXNUM + 7) / 8];
|
|
memset(usedModelIds, 0, sizeof(usedModelIds));
|
|
|
|
for (uint8_t modelIndex = 0; modelIndex < MAX_MODELS; modelIndex++) {
|
|
if (modelIndex == index)
|
|
continue;
|
|
|
|
uint8_t id = modelHeaders[modelIndex].modelId[module];
|
|
if (id == 0)
|
|
continue;
|
|
|
|
uint8_t mask = 1u << (id & 7u);
|
|
usedModelIds[id >> 3u] |= mask;
|
|
}
|
|
|
|
for (uint8_t id = 1; id <= getMaxRxNum(module); id++) {
|
|
uint8_t mask = 1u << (id & 7u);
|
|
if (!(usedModelIds[id >> 3u] & mask)) {
|
|
// found free ID
|
|
return id;
|
|
}
|
|
}
|
|
|
|
// failed finding something...
|
|
return 0;
|
|
}
|
|
#endif
|