From b16524837ad2dd62a5b785819781bdfe41880a29 Mon Sep 17 00:00:00 2001 From: philmoz Date: Sun, 2 Jun 2024 16:22:36 +1000 Subject: [PATCH] feat: allow Sticky LS state to persist across EM or optionally across reboot (#4978) --- .../edgetx/yaml_logicalswitchdata.cpp | 5 ++++ companion/src/firmwares/logicalswitchdata.h | 2 ++ companion/src/modeledit/logicalswitches.cpp | 22 ++++++++++++++ companion/src/modeledit/logicalswitches.h | 2 ++ companion/src/modelprinter.cpp | 2 ++ radio/src/datastructs_private.h | 6 ++-- .../src/gui/128x64/model_logical_switches.cpp | 9 ++++++ .../src/gui/212x64/model_logical_switches.cpp | 29 ++++++++++++++----- radio/src/gui/212x64/widgets.cpp | 2 ++ .../gui/colorlcd/model_logical_switches.cpp | 7 +++++ radio/src/opentx.cpp | 1 + radio/src/storage/storage_common.cpp | 3 ++ .../storage/yaml/yaml_datastructs_128x64.cpp | 4 +-- .../storage/yaml/yaml_datastructs_nv14.cpp | 4 +-- .../storage/yaml/yaml_datastructs_pl18.cpp | 4 +-- .../src/storage/yaml/yaml_datastructs_t20.cpp | 4 +-- .../storage/yaml/yaml_datastructs_tpro.cpp | 4 +-- .../src/storage/yaml/yaml_datastructs_x10.cpp | 4 +-- .../storage/yaml/yaml_datastructs_x12s.cpp | 4 +-- .../src/storage/yaml/yaml_datastructs_x9d.cpp | 4 +-- .../src/storage/yaml/yaml_datastructs_x9e.cpp | 4 +-- .../storage/yaml/yaml_datastructs_xlites.cpp | 4 +-- radio/src/switches.cpp | 14 +++++++++ radio/src/switches.h | 3 ++ radio/src/translations.h | 18 ++---------- radio/src/translations/fr.h | 2 +- 26 files changed, 121 insertions(+), 46 deletions(-) diff --git a/companion/src/firmwares/edgetx/yaml_logicalswitchdata.cpp b/companion/src/firmwares/edgetx/yaml_logicalswitchdata.cpp index fad87f41f..8c24c82ac 100644 --- a/companion/src/firmwares/edgetx/yaml_logicalswitchdata.cpp +++ b/companion/src/firmwares/edgetx/yaml_logicalswitchdata.cpp @@ -116,6 +116,8 @@ Node convert::encode(const LogicalSwitchData& rhs) node["delay"] = rhs.delay; node["duration"] = rhs.duration; node["andsw"] = YamlRawSwitchEncode(RawSwitch(rhs.andsw)); + node["lsPersist"] = (int)rhs.lsPersist; + node["lsState"] = (int)rhs.lsState; return node; } @@ -200,6 +202,9 @@ bool convert::decode(const Node& node, node["andsw"] >> andsw; rhs.andsw = andsw.toValue(); + node["lsPersist"] >> rhs.lsPersist; + node["lsState"] >> rhs.lsState; + return true; } } // namespace YAML diff --git a/companion/src/firmwares/logicalswitchdata.h b/companion/src/firmwares/logicalswitchdata.h index 1b1c50820..67bb3078f 100644 --- a/companion/src/firmwares/logicalswitchdata.h +++ b/companion/src/firmwares/logicalswitchdata.h @@ -76,6 +76,8 @@ class LogicalSwitchData { unsigned int delay; unsigned int duration; int andsw; + bool lsState; + bool lsPersist; void clear() { memset(this, 0, sizeof(LogicalSwitchData)); } bool isEmpty() const; diff --git a/companion/src/modeledit/logicalswitches.cpp b/companion/src/modeledit/logicalswitches.cpp index 6db5ecef7..e7da33f62 100644 --- a/companion/src/modeledit/logicalswitches.cpp +++ b/companion/src/modeledit/logicalswitches.cpp @@ -47,6 +47,7 @@ LogicalSwitchesPanel::LogicalSwitchesPanel(QWidget * parent, ModelData & model, if (lsCapabilityExt) { headerLabels << tr("Duration") << tr("Delay"); } + headerLabels << tr("Persistent"); TableLayout * tableLayout = new TableLayout(this, lsCapability, headerLabels); const int channelsMax = model.getChannelsMax(true); @@ -152,6 +153,13 @@ LogicalSwitchesPanel::LogicalSwitchesPanel(QWidget * parent, ModelData & model, connect(dsbDelay[i], SIGNAL(valueChanged(double)), this, SLOT(onDelayChanged(double))); tableLayout->addWidget(i, 6, dsbDelay[i]); } + + cbPersist[i] = new QCheckBox(this); + cbPersist[i]->setProperty("index", i); + cbPersist[i]->setText(tr("")); + cbPersist[i]->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + connect(cbPersist[i], SIGNAL(stateChanged(int)), this, SLOT(onPersistChanged())); + tableLayout->addWidget(i, 7, cbPersist[i]); } disableMouseScrolling(); @@ -257,6 +265,16 @@ void LogicalSwitchesPanel::onDelayChanged(double delay) } } +void LogicalSwitchesPanel::onPersistChanged() +{ + if (!lock) { + QCheckBox *ckb = qobject_cast(sender()); + int index = ckb->property("index").toInt(); + model->logicalSw[index].lsPersist = (ckb->checkState() ? 1 : 0); + emit modified(); + } +} + void LogicalSwitchesPanel::onOffsetChanged() { offsetChangedAt(sender()->property("index").toInt()); @@ -343,6 +361,7 @@ void LogicalSwitchesPanel::updateTimerParam(QDoubleSpinBox *sb, int timer, doubl #define DELAY_ENABLED 0x40 #define DURATION_ENABLED 0x80 #define LINE_ENABLED 0x100 +#define PERSIST_ENABLED 0x200 void LogicalSwitchesPanel::updateLine(int i) { @@ -391,6 +410,8 @@ void LogicalSwitchesPanel::updateLine(int i) } case LS_FAMILY_STICKY: // no break + mask |= PERSIST_ENABLED; + cbPersist[i]->setChecked(model->logicalSw[i].lsPersist); case LS_FAMILY_VBOOL: mask |= SOURCE1_VISIBLE | SOURCE2_VISIBLE; cbSource1[i]->setModel(rawSwitchFilteredModel); @@ -432,6 +453,7 @@ void LogicalSwitchesPanel::updateLine(int i) dsbOffset2[i]->setVisible(mask & VALUE3_VISIBLE); teOffset[i]->setVisible(mask & VALUE_TO_VISIBLE); cbAndSwitch[i]->setVisible(mask & LINE_ENABLED); + cbPersist[i]->setVisible(mask & PERSIST_ENABLED); if (lsCapabilityExt) { dsbDuration[i]->setVisible(mask & DURATION_ENABLED); dsbDelay[i]->setVisible(mask & DELAY_ENABLED); diff --git a/companion/src/modeledit/logicalswitches.h b/companion/src/modeledit/logicalswitches.h index cfee61ea5..4d6287859 100644 --- a/companion/src/modeledit/logicalswitches.h +++ b/companion/src/modeledit/logicalswitches.h @@ -47,6 +47,7 @@ class LogicalSwitchesPanel : public ModelPanel void onAndSwitchChanged(int value); void onDurationChanged(double duration); void onDelayChanged(double delay); + void onPersistChanged(); void onOffsetChanged(); bool offsetChangedAt(int index); void updateLine(int index); @@ -74,6 +75,7 @@ class LogicalSwitchesPanel : public ModelPanel QDoubleSpinBox * dsbDelay[CPN_MAX_LOGICAL_SWITCHES]; QComboBox * cbSource1[CPN_MAX_LOGICAL_SWITCHES]; QComboBox * cbSource2[CPN_MAX_LOGICAL_SWITCHES]; + QCheckBox * cbPersist[CPN_MAX_LOGICAL_SWITCHES]; CompoundItemModelFactory * sharedItemModels; FilteredItemModel * rawSwitchFilteredModel; FilteredItemModel * rawSourceFilteredModel; diff --git a/companion/src/modelprinter.cpp b/companion/src/modelprinter.cpp index c1a28cb61..5264ecb47 100644 --- a/companion/src/modelprinter.cpp +++ b/companion/src/modelprinter.cpp @@ -531,6 +531,8 @@ QString ModelPrinter::printLogicalSwitchLine(int idx) sw1Name = RawSwitch(ls.val1).toString(getCurrentBoard(), &generalSettings); sw2Name = RawSwitch(ls.val2).toString(getCurrentBoard(), &generalSettings); result += tr("Sticky") + QString("(%1, %2)").arg(sw1Name).arg(sw2Name); + if (ls.lsPersist) + result += tr(" Persistent"); break; case LS_FAMILY_TIMER: result += tr("Timer") + QString("(%1, %2)").arg(ValToTim(ls.val1)).arg(ValToTim(ls.val2)); diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index ba05e2bac..4f7d92f1e 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -134,9 +134,9 @@ PACK(struct LogicalSwitchData { CUST_ATTR(def,r_logicSw,w_logicSw); int32_t v1:10 SKIP; int32_t v3:10 SKIP; - int32_t andsw:10 CUST(r_swtchSrc,w_swtchSrc); // TODO rename to xswtch - uint32_t andswtype:1 SKIP; // TODO rename to xswtchType (AND / OR) - uint32_t spare:1 SKIP; // anything else needed? + int32_t andsw:10 CUST(r_swtchSrc,w_swtchSrc); + uint32_t lsPersist:1; + uint32_t lsState:1; int16_t v2 SKIP; uint8_t delay; uint8_t duration; diff --git a/radio/src/gui/128x64/model_logical_switches.cpp b/radio/src/gui/128x64/model_logical_switches.cpp index 43422a462..6c2fff74a 100644 --- a/radio/src/gui/128x64/model_logical_switches.cpp +++ b/radio/src/gui/128x64/model_logical_switches.cpp @@ -29,6 +29,7 @@ enum LogicalSwitchFields { LS_FIELD_ANDSW, LS_FIELD_DURATION, LS_FIELD_DELAY, + LS_FIELD_PERSIST, LS_FIELD_COUNT, LS_FIELD_LAST = LS_FIELD_COUNT-1 }; @@ -219,6 +220,14 @@ void menuModelLogicalSwitchOne(event_t event) CHECK_INCDEC_MODELVAR_ZERO(event, cs->delay, MAX_LS_DELAY); } break; + + case LS_FIELD_PERSIST: + if (cstate == LS_FAMILY_STICKY) { + lcdDrawTextAlignedLeft(y, STR_PERSISTENT); + drawCheckBox(CSWONE_2ND_COLUMN, y, cs->lsPersist, attr); + if (attr) cs->lsPersist = checkIncDecModel(event, cs->lsPersist, 0, 1); + } + break; } } } diff --git a/radio/src/gui/212x64/model_logical_switches.cpp b/radio/src/gui/212x64/model_logical_switches.cpp index 72899ae7d..03b3d9a19 100644 --- a/radio/src/gui/212x64/model_logical_switches.cpp +++ b/radio/src/gui/212x64/model_logical_switches.cpp @@ -30,6 +30,7 @@ enum LogicalSwitchFields { LS_FIELD_ANDSW, LS_FIELD_DURATION, LS_FIELD_DELAY, + LS_FIELD_PERSIST, LS_FIELD_COUNT, LS_FIELD_LAST = LS_FIELD_COUNT-1 }; @@ -37,9 +38,10 @@ enum LogicalSwitchFields { #define CSW_1ST_COLUMN (4*FW-3) #define CSW_2ND_COLUMN (8*FW+2+FW/2) #define CSW_3RD_COLUMN (14*FW+1+FW/2) -#define CSW_4TH_COLUMN (22*FW) -#define CSW_5TH_COLUMN (26*FW+3) -#define CSW_6TH_COLUMN (31*FW+1) +#define CSW_4TH_COLUMN (21*FW) +#define CSW_5TH_COLUMN (25*FW+3) +#define CSW_6TH_COLUMN (29*FW+3) +#define CSW_7TH_COLUMN (33*FW+3) void putsEdgeDelayParam(coord_t x, coord_t y, LogicalSwitchData * cs, uint8_t lattr, uint8_t rattr) { @@ -83,10 +85,7 @@ void menuModelLogicalSwitches(event_t event) int k = 0; int sub = menuVerticalPosition; horzpos_t horz = menuHorizontalPosition; - - if (horz>=0) { - drawColumnHeader(STR_LSW_HEADERS, horz); - } + bool showHeader = true; if (horz<0 && event==EVT_KEY_LONG(KEY_ENTER) && !READ_ONLY()) { LogicalSwitchData * cs = lswAddress(sub); @@ -202,6 +201,15 @@ void menuModelLogicalSwitches(event_t event) if (attr && horz == LS_FIELD_V3 && cstate != LS_FAMILY_EDGE) { repeatLastCursorMove(event); + showHeader = false; + } + + // Persistent + if (cstate == LS_FAMILY_STICKY) { + drawCheckBox(CSW_7TH_COLUMN, y, cs->lsPersist, horz == LS_FIELD_PERSIST ? attr : 0); + } else if (attr && horz == LS_FIELD_PERSIST) { + repeatLastCursorMove(event); + showHeader = false; } if (s_editMode>0 && attr) { @@ -253,7 +261,14 @@ void menuModelLogicalSwitches(event_t event) case LS_FIELD_DELAY: CHECK_INCDEC_MODELVAR_ZERO(event, cs->delay, MAX_LS_DELAY); break; + case LS_FIELD_PERSIST: + cs->lsPersist = checkIncDecModel(event, cs->lsPersist, 0, 1); + break; } } } + + if (showHeader && menuHorizontalPosition>=0) { + drawColumnHeader(STR_LSW_HEADERS, menuHorizontalPosition); + } } diff --git a/radio/src/gui/212x64/widgets.cpp b/radio/src/gui/212x64/widgets.cpp index 223192363..1b526dac7 100644 --- a/radio/src/gui/212x64/widgets.cpp +++ b/radio/src/gui/212x64/widgets.cpp @@ -35,7 +35,9 @@ void drawStick(coord_t centrex, int16_t xval, int16_t yval) void drawColumnHeader(const char * const *headers, uint8_t index) { + lcdDrawText(16*FW+3, 0, " ", 0); lcdDrawText(17*FW, 0, headers[index], 0); + lcdDrawText(lcdNextPos, 0, " ", 0); } void drawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) diff --git a/radio/src/gui/colorlcd/model_logical_switches.cpp b/radio/src/gui/colorlcd/model_logical_switches.cpp index b3f5631d3..47739abcc 100644 --- a/radio/src/gui/colorlcd/model_logical_switches.cpp +++ b/radio/src/gui/colorlcd/model_logical_switches.cpp @@ -245,6 +245,13 @@ class LogicalSwitchEditPage : public Page return formatNumberAsString(value, PREC1, 0, nullptr, "s"); }); } + + // Sticky persist + if (cstate == LS_FAMILY_STICKY) { + line = logicalSwitchOneWindow->newLine(grid); + new StaticText(line, rect_t{}, STR_PERSISTENT); + new ToggleSwitch(line, rect_t{}, GET_SET_DEFAULT(cs->lsPersist)); + } } void buildBody(Window* window) diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index e39bb4c32..218fe4447 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -1434,6 +1434,7 @@ void edgeTxInit() // SDCARD not available, try to restore last model from RAM TRACE("rambackupRestore"); rambackupRestore(); + logicalSwitchesInit(true); } else { storageReadAll(); diff --git a/radio/src/storage/storage_common.cpp b/radio/src/storage/storage_common.cpp index 03d1de6ae..39ae74b47 100644 --- a/radio/src/storage/storage_common.cpp +++ b/radio/src/storage/storage_common.cpp @@ -23,6 +23,7 @@ #include "timers_driver.h" #include "tasks/mixer_task.h" #include "mixes.h" +#include "switches.h" #if defined(USBJ_EX) #include "usb_joystick.h" @@ -244,6 +245,8 @@ if(g_model.rssiSource) { customFunctionsReset(); + logicalSwitchesInit(false); + restoreTimers(); for (int i=0; ifunc == LS_FUNC_STICKY && (force || ls->lsPersist)) { + lswFm[mixerCurrentFlightMode].lsw[idx].lastValue = ls->lsState; + } + } +} + bool getLogicalSwitch(uint8_t idx) { LogicalSwitchData * ls = lswAddress(idx); @@ -745,6 +755,10 @@ void evalLogicalSwitches(bool isCurrentFlightmode) } } context.state = result; + if ((g_model.logicalSw[idx].func == LS_FUNC_STICKY) && (g_model.logicalSw[idx].lsState != result)) { + g_model.logicalSw[idx].lsState = result; + storageDirty(EE_MODEL); + } } } diff --git a/radio/src/switches.h b/radio/src/switches.h index 76ee5bb2d..f04ddd9da 100644 --- a/radio/src/switches.h +++ b/radio/src/switches.h @@ -85,6 +85,9 @@ bool switchHasCustomName(uint8_t idx); SwitchConfig switchGetMaxType(uint8_t idx); +// Restore switch state +void logicalSwitchesInit(bool force); + #if defined(FUNCTION_SWITCHES) uint8_t getFSLogicalState(uint8_t index); void setFSLogicalState(uint8_t index, uint8_t value); diff --git a/radio/src/translations.h b/radio/src/translations.h index 30327af46..454f824fc 100644 --- a/radio/src/translations.h +++ b/radio/src/translations.h @@ -84,12 +84,6 @@ #define PSIZE(x) ( sizeof(x) - 1 ) -#if defined(PCBTARANIS) -#define HINT(s) " " s " " -#else -#define HINT(s) s -#endif - #if LCD_W < LCD_H // Portrait mode #define TR3(x, y, z) z #define TR(x, y) y @@ -967,16 +961,10 @@ extern const char STR_MIXNAME[]; extern const char STR_INPUTNAME[]; extern const char STR_EXPONAME[]; -#if defined(COLORLCD) - #define TR_PHASES_HEADERS { TR_PHASES_HEADERS_NAME, TR_PHASES_HEADERS_SW, TR_PHASES_HEADERS_RUD_TRIM, TR_PHASES_HEADERS_ELE_TRIM, TR_PHASES_HEADERS_THT_TRIM, TR_PHASES_HEADERS_AIL_TRIM, TR_PHASES_HEADERS_CH5_TRIM, TR_PHASES_HEADERS_CH6_TRIM, TR_PHASES_HEADERS_FAD_IN, TR_PHASES_HEADERS_FAD_OUT } -#elif defined(PCBTARANIS) - #define TR_PHASES_HEADERS { HINT(TR_PHASES_HEADERS_NAME), HINT(TR_PHASES_HEADERS_SW), HINT(TR_PHASES_HEADERS_RUD_TRIM), HINT(TR_PHASES_HEADERS_ELE_TRIM), HINT(TR_PHASES_HEADERS_THT_TRIM), HINT(TR_PHASES_HEADERS_AIL_TRIM), HINT(TR_PHASES_HEADERS_FAD_IN), HINT(TR_PHASES_HEADERS_FAD_OUT) } -#endif - -#define TR_LIMITS_HEADERS { HINT(TR_LIMITS_HEADERS_NAME), HINT(TR_LIMITS_HEADERS_SUBTRIM), HINT(TR_LIMITS_HEADERS_MIN), HINT(TR_LIMITS_HEADERS_MAX), HINT(TR_LIMITS_HEADERS_DIRECTION), HINT(TR_LIMITS_HEADERS_CURVE), HINT(TR_LIMITS_HEADERS_PPMCENTER), HINT(TR_LIMITS_HEADERS_SUBTRIMMODE) } -#define TR_LSW_HEADERS { HINT(TR_FUNC), HINT(TR_V1), HINT(TR_V2), HINT(TR_V2), HINT(TR_AND_SWITCH), HINT(TR_DURATION), HINT(TR_DELAY) } - #if LCD_W >= 212 +#define TR_PHASES_HEADERS { TR_PHASES_HEADERS_NAME, TR_PHASES_HEADERS_SW, TR_PHASES_HEADERS_RUD_TRIM, TR_PHASES_HEADERS_ELE_TRIM, TR_PHASES_HEADERS_THT_TRIM, TR_PHASES_HEADERS_AIL_TRIM, TR_PHASES_HEADERS_FAD_IN, TR_PHASES_HEADERS_FAD_OUT } +#define TR_LIMITS_HEADERS { TR_LIMITS_HEADERS_NAME, TR_LIMITS_HEADERS_SUBTRIM, TR_LIMITS_HEADERS_MIN, TR_LIMITS_HEADERS_MAX, TR_LIMITS_HEADERS_DIRECTION, TR_LIMITS_HEADERS_CURVE, TR_LIMITS_HEADERS_PPMCENTER, TR_LIMITS_HEADERS_SUBTRIMMODE } +#define TR_LSW_HEADERS { TR_FUNC, TR_V1, TR_V2, TR_V2, TR_AND_SWITCH, TR_DURATION, TR_DELAY, TR_PERSISTENT } extern const char * const STR_PHASES_HEADERS[]; extern const char * const STR_LIMITS_HEADERS[]; extern const char * const STR_LSW_HEADERS[]; diff --git a/radio/src/translations/fr.h b/radio/src/translations/fr.h index 4278f7bc2..5bcfb310a 100644 --- a/radio/src/translations/fr.h +++ b/radio/src/translations/fr.h @@ -378,7 +378,7 @@ #define TR_THROTTLEREVERSE TR("Inv. gaz", "Inversion gaz") #define TR_MINUTEBEEP TR("Bip min.", "Annonces minutes") #define TR_BEEPCOUNTDOWN TR("Bip fin", "Compte à rebours") -#define TR_PERSISTENT TR( "Persist.", "Persistant") +#define TR_PERSISTENT TR("Persist.", "Persistant") #define TR_BACKLIGHT_LABEL "Rétroéclairage" #define TR_GHOST_MENU_LABEL "MENU GHOST" #define TR_STATUS "Version"