diff --git a/radio/src/gui/colorlcd/CMakeLists.txt b/radio/src/gui/colorlcd/CMakeLists.txt index f2d7138f0..84e41732b 100644 --- a/radio/src/gui/colorlcd/CMakeLists.txt +++ b/radio/src/gui/colorlcd/CMakeLists.txt @@ -53,6 +53,7 @@ set(GUI_SRC bind_menu_d16.cpp trainer_setup.cpp custom_failsafe.cpp + ppm_settings.cpp module_setup.cpp timer_setup.cpp trims_setup.cpp @@ -130,6 +131,18 @@ if(PXX2 OR MULTIMODULE) add_gui_src(radio_spectrum_analyser.cpp) endif() +if(CROSSFIRE) + add_gui_src(crossfire_settings.cpp) +endif() + +if(AFHDS2 OR AFHDS3) + add_gui_src(flysky_settings.cpp) +endif() + +if(MULTIMODULE) + add_gui_src(mpm_settings.cpp) +endif() + if(PXX2) add_gui_src(access_settings.cpp) endif() diff --git a/radio/src/gui/colorlcd/access_settings.cpp b/radio/src/gui/colorlcd/access_settings.cpp index 137dc4d7b..7fbdc5040 100644 --- a/radio/src/gui/colorlcd/access_settings.cpp +++ b/radio/src/gui/colorlcd/access_settings.cpp @@ -27,6 +27,8 @@ #define SET_DIRTY() storageDirty(EE_MODEL) +namespace pxx2 { + static void startBindWaitDialog(Window* parent, uint8_t moduleIdx, uint8_t receiverIdx) { @@ -195,7 +197,7 @@ void BindWaitDialog::checkEvents() Dialog::checkEvents(); } -ReceiverButton::ReceiverButton(FormGroup* parent, rect_t rect, +ReceiverButton::ReceiverButton(Window* parent, rect_t rect, uint8_t moduleIdx, uint8_t receiverIdx) : TextButton(parent, rect, STR_BIND, std::bind(&ReceiverButton::pressBind, this)), @@ -880,3 +882,5 @@ void RxOptions::writeSettings() state = RO_WriteSettings; } } + +}; diff --git a/radio/src/gui/colorlcd/access_settings.h b/radio/src/gui/colorlcd/access_settings.h index 2dd45015a..ffe005728 100644 --- a/radio/src/gui/colorlcd/access_settings.h +++ b/radio/src/gui/colorlcd/access_settings.h @@ -28,6 +28,8 @@ #include "textedit.h" #include "numberedit.h" +namespace pxx2 { + class BindRxChoiceMenu : public Menu { public: @@ -58,7 +60,7 @@ class BindWaitDialog : public Dialog class ReceiverButton : public TextButton { public: - ReceiverButton(FormGroup* parent, rect_t rect, uint8_t moduleIdx, + ReceiverButton(Window* parent, rect_t rect, uint8_t moduleIdx, uint8_t receiverIdx); uint8_t pressBind(); @@ -143,3 +145,5 @@ class RxOptions : public Dialog void update(); void writeSettings(); }; + +}; diff --git a/radio/src/gui/colorlcd/bind_menu_d16.cpp b/radio/src/gui/colorlcd/bind_menu_d16.cpp index cdd5285b6..12b897dad 100644 --- a/radio/src/gui/colorlcd/bind_menu_d16.cpp +++ b/radio/src/gui/colorlcd/bind_menu_d16.cpp @@ -41,7 +41,10 @@ BindChoiceMenu::BindChoiceMenu(Window *parent, uint8_t moduleIdx, [=]() { onSelect(Bind_9_16_TELEM_OFF); }); } setTitle(STR_SELECT_MODE); - setCancelHandler(onCancel); + setCancelHandler([=] { + moduleState[moduleIdx].mode = MODULE_MODE_NORMAL; + onCancel(); + }); } void BindChoiceMenu::onSelect(BindChanMode mode) @@ -64,16 +67,16 @@ void BindChoiceMenu::onSelect(BindChanMode mode) break; } + ModuleData* md = &g_model.moduleData[moduleIdx]; if (isModuleMultimodule(moduleIdx)) { - g_model.moduleData[moduleIdx].multi.receiverTelemetryOff = - !receiverTelemetry; - g_model.moduleData[moduleIdx].multi.receiverHigherChannels = - receiverHigherChannels; + md->multi.receiverTelemetryOff = !receiverTelemetry; + md->multi.receiverHigherChannels = receiverHigherChannels; + setMultiBindStatus(moduleIdx, MULTI_BIND_INITIATED); } else { - g_model.moduleData[moduleIdx].pxx.receiverTelemetryOff = !receiverTelemetry; - g_model.moduleData[moduleIdx].pxx.receiverHigherChannels = - receiverHigherChannels; + md->pxx.receiverTelemetryOff = !receiverTelemetry; + md->pxx.receiverHigherChannels = receiverHigherChannels; } + moduleState[moduleIdx].mode = MODULE_MODE_BIND; if (onPressHandler) onPressHandler(); } diff --git a/radio/src/gui/colorlcd/crossfire_settings.cpp b/radio/src/gui/colorlcd/crossfire_settings.cpp new file mode 100644 index 000000000..1ea5d1321 --- /dev/null +++ b/radio/src/gui/colorlcd/crossfire_settings.cpp @@ -0,0 +1,66 @@ +/* + * 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 "crossfire_settings.h" +#include "opentx.h" + +#include "mixer_scheduler.h" + +#define SET_DIRTY() storageDirty(EE_MODEL) + +CrossfireSettings::CrossfireSettings(Window* parent, const FlexGridLayout& g, + uint8_t moduleIdx) : + FormGroup(parent, rect_t{}), md(&g_model.moduleData[moduleIdx]) +{ + FlexGridLayout grid(g); + setFlexLayout(); + + if (moduleIdx == EXTERNAL_MODULE) { + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_BAUDRATE, 0, COLOR_THEME_PRIMARY1); + new Choice( + line, rect_t{}, STR_CRSF_BAUDRATE, 0, CROSSFIRE_MAX_INTERNAL_BAUDRATE, + [=]() -> int { + return CROSSFIRE_STORE_TO_INDEX(md->crsf.telemetryBaudrate); + }, + [=](int newValue) { + md->crsf.telemetryBaudrate = CROSSFIRE_INDEX_TO_STORE(newValue); + SET_DIRTY(); + restartModule(moduleIdx); + }); + } else { + // char buf[6]; + // new StaticText(this, grid.getFieldSlot(2, 1), + // getStringAtIndex(buf, STR_CRSF_BAUDRATE, + // CROSSFIRE_STORE_TO_INDEX( + // g_eeGeneral.internalModuleBaudrate)), + // 0, COLOR_THEME_PRIMARY1); + } + + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_STATUS, 0, COLOR_THEME_PRIMARY1); + new DynamicText(line, rect_t{}, [=] { + char msg[64] = ""; + sprintf(msg, "%d Hz %" PRIu32 " Err", 1000000 / getMixerSchedulerPeriod(), + telemetryErrors); + return std::string(msg); + }); +} diff --git a/radio/src/gui/colorlcd/crossfire_settings.h b/radio/src/gui/colorlcd/crossfire_settings.h new file mode 100644 index 000000000..d93f5c86a --- /dev/null +++ b/radio/src/gui/colorlcd/crossfire_settings.h @@ -0,0 +1,37 @@ +/* + * 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 "form.h" +#include "module_setup.h" + +struct ModuleData; + +class CrossfireSettings : public FormGroup, public ModuleOptions +{ + ModuleData* md; + + void update() override {} + +public: + CrossfireSettings(Window* parent, const FlexGridLayout& g, uint8_t moduleIdx); +}; diff --git a/radio/src/gui/colorlcd/flysky_settings.cpp b/radio/src/gui/colorlcd/flysky_settings.cpp new file mode 100644 index 000000000..9ec958273 --- /dev/null +++ b/radio/src/gui/colorlcd/flysky_settings.cpp @@ -0,0 +1,154 @@ +/* + * 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 "flysky_settings.h" +#include "opentx.h" + +#include "pulses/flysky.h" + +#define SET_DIRTY() storageDirty(EE_MODEL) + +class FSProtoOpts : public FormGroup +{ + std::function getMode; + std::function setMode; + +public: + FSProtoOpts(Window* parent, std::function getMode, + std::function setMode); +}; + +FSProtoOpts::FSProtoOpts(Window* parent, std::function getMode, + std::function setMode) : + FormGroup(parent, rect_t{}), + getMode(std::move(getMode)), + setMode(std::move(setMode)) +{ + setFlexLayout(LV_FLEX_FLOW_ROW); + + // PPM / PWM + new Choice( + this, rect_t{}, STR_FLYSKY_PULSE_PROTO, 0, 1, + [=]() { return getMode() >> 1; }, + [=](int v) { setMode((getMode() & 1) | ((v & 1) << 1)); SET_DIRTY(); }); + // SBUS / iBUS + new Choice( + this, rect_t{}, STR_FLYSKY_SERIAL_PROTO, 0, 1, + [=]() { return getMode() & 1; }, + [=](int v) { setMode((getMode() & 2) | (v & 1)); SET_DIRTY(); }); +} + +FlySkySettings::FlySkySettings(Window* parent, const FlexGridLayout& g, + uint8_t moduleIdx) : + FormGroup(parent, rect_t{}), md(&g_model.moduleData[moduleIdx]) +{ + FlexGridLayout grid(g); + setFlexLayout(); + + // RX options: + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_OPTIONS, 0, COLOR_THEME_PRIMARY1); + +#if defined(AFHDS2) + if (isModuleAFHDS2A(moduleIdx)) { + new FSProtoOpts( + line, [=]() { return md->flysky.mode; }, + [=](uint8_t v) { md->flysky.mode = v; }); + +#if defined(AFHDS2) && defined(PCBNV14) + if (getNV14RfFwVersion() >= 0x1000E) { + line = newLine(&grid); + static const _rf_power = {"Default", "High"}; + new StaticText(line, rect_t{}, STR_MULTI_RFPOWER); + new Choice(line, rect_t{}, _rf_power, 0, 1, + GET_DEFAULT(md->flysky.rfPower), + [=](int32_t newValue) -> void { + md->flysky.rfPower = newValue; + resetPulsesAFHDS2(); + }); + } +#endif + } +#endif +#if defined(AFHDS3) + if (isModuleAFHDS3(moduleIdx)) { + new FSProtoOpts( + line, [=]() { return md->afhds3.mode; }, + [=](uint8_t v) { md->afhds3.mode = v; }); + + // TYPE + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_TYPE, 0, COLOR_THEME_PRIMARY1); + + // This is chosen when binding (menu? see stdlcd/model_setup_afhds3.cpp) + new StaticText(line, rect_t{}, + md->afhds3.telemetry ? STR_AFHDS3_ONE_TO_ONE_TELEMETRY + : TR_AFHDS3_ONE_TO_MANY, + 0, COLOR_THEME_PRIMARY1); + + // Status + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_MODULE_STATUS, 0, COLOR_THEME_PRIMARY1); + new DynamicText(line, rect_t{}, [=] { + char msg[64] = ""; + getModuleStatusString(moduleIdx, msg); + return std::string(msg); + }); + + // Power source + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_AFHDS3_POWER_SOURCE, 0, + COLOR_THEME_PRIMARY1); + new DynamicText(line, rect_t{}, [=] { + char msg[64] = ""; + getModuleSyncStatusString(moduleIdx, msg); + return std::string(msg); + }); + + // RX Freq + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_AFHDS3_RX_FREQ, 0, COLOR_THEME_PRIMARY1); + auto edit = new NumberEdit(line, rect_t{}, MIN_FREQ, MAX_FREQ, + GET_DEFAULT(md->afhds3.rxFreq())); + edit->setSetValueHandler( + [=](int32_t newValue) { md->afhds3.setRxFreq((uint16_t)newValue); }); + edit->setSuffix(STR_HZ); + + // Module actual power + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_AFHDS3_ACTUAL_POWER, 0, + COLOR_THEME_PRIMARY1); + new DynamicText(line, rect_t{}, [=] { + char msg[64] = ""; + getStringAtIndex(msg, STR_AFHDS3_POWERS, actualAfhdsRunPower(moduleIdx)); + return std::string(msg); + }); + + // Module power + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_RF_POWER, 0, COLOR_THEME_PRIMARY1); + new Choice(line, rect_t{}, STR_AFHDS3_POWERS, + afhds3::RUN_POWER::RUN_POWER_FIRST, + afhds3::RUN_POWER::RUN_POWER_LAST, + GET_SET_DEFAULT(md->afhds3.runPower)); + } +#endif +} diff --git a/radio/src/gui/colorlcd/flysky_settings.h b/radio/src/gui/colorlcd/flysky_settings.h new file mode 100644 index 000000000..f97737a84 --- /dev/null +++ b/radio/src/gui/colorlcd/flysky_settings.h @@ -0,0 +1,37 @@ +/* + * 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 "form.h" +#include "module_setup.h" + +struct ModuleData; + +class FlySkySettings : public FormGroup, public ModuleOptions +{ + ModuleData* md; + + void update() override {} + +public: + FlySkySettings(Window* parent, const FlexGridLayout& g, uint8_t moduleIdx); +}; diff --git a/radio/src/gui/colorlcd/module_setup.cpp b/radio/src/gui/colorlcd/module_setup.cpp index d95a57692..2674a7c85 100644 --- a/radio/src/gui/colorlcd/module_setup.cpp +++ b/radio/src/gui/colorlcd/module_setup.cpp @@ -25,481 +25,262 @@ #include "form.h" #include "choice.h" #include "button.h" -#include "gridlayout.h" #include "mixer_scheduler.h" -#include "multi_rfprotos.h" -#include "io/multi_protolist.h" -#include "pulses/flysky.h" #include "bind_menu_d16.h" #include "custom_failsafe.h" +#include "ppm_settings.h" #if defined(PXX2) #include "access_settings.h" #endif +#if defined(CROSSFIRE) +#include "crossfire_settings.h" +#endif + +#if defined(AFHDS2) || defined(AFHDS3) +#include "flysky_settings.h" +#endif + +#if defined(MULTIMODULE) +#include "mpm_settings.h" +#include "multi_rfprotos.h" +#include "io/multi_protolist.h" +#endif + #define SET_DIRTY() storageDirty(EE_MODEL) +static const lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2), + LV_GRID_TEMPLATE_LAST}; +static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; + +struct ChannelRange : public FormGroup { + uint8_t moduleIdx; + NumberEdit* chStart; + NumberEdit* chEnd; + + ChannelRange(Window* parent, uint8_t moduleIdx); + + void setStart(uint32_t newValue); + void setEnd(uint32_t newValue); + + void update(); + void updateStart(); + void updateEnd(); +}; + +ChannelRange::ChannelRange(Window* parent, uint8_t moduleIdx) : + FormGroup(parent, rect_t{}), moduleIdx(moduleIdx) +{ + setFlexLayout(LV_FLEX_FLOW_ROW); + + ModuleData* md = &g_model.moduleData[moduleIdx]; + chStart = new NumberEdit(this, rect_t{}, 1, 1, GET_DEFAULT(1 + md->channelsStart)); + + chStart->setPrefix(STR_CH); + chStart->setSetValueHandler([=](int32_t newValue) { setStart(newValue); }); + + chEnd = new NumberEdit(this, rect_t{}, 8, 8, + GET_DEFAULT(md->channelsStart + 8 + md->channelsCount)); + + chEnd->setPrefix(STR_CH); + chEnd->setSetValueHandler([=](int32_t newValue) { setEnd(newValue); }); + + update(); +} + +void ChannelRange::setStart(uint32_t newValue) +{ + ModuleData* md = &g_model.moduleData[moduleIdx]; + md->channelsStart = newValue - 1; + updateEnd(); + SET_DIRTY(); +} + +void ChannelRange::setEnd(uint32_t newValue) +{ + ModuleData* md = &g_model.moduleData[moduleIdx]; + md->channelsCount = newValue - md->channelsStart - 8; + updateStart(); + SET_DIRTY(); +} + +void ChannelRange::updateEnd() +{ + ModuleData* md = &g_model.moduleData[moduleIdx]; + auto min_ch = md->channelsStart + minModuleChannels(moduleIdx); + chEnd->setMin(min_ch); + + auto max_ch = md->channelsStart + maxModuleChannels(moduleIdx); + max_ch = min(MAX_OUTPUT_CHANNELS, max_ch); + chEnd->setMax(max_ch); + chEnd->setDefault(max_ch); + + chEnd->update(); +} + +void ChannelRange::updateStart() +{ + chStart->setMax(MAX_OUTPUT_CHANNELS - sentModuleChannels(moduleIdx) + 1); +} + +void ChannelRange::update() +{ + // ModuleData* md = &g_model.moduleData[moduleIdx]; + + updateStart(); + updateEnd(); + + auto min_mod_ch = minModuleChannels(moduleIdx); + auto max_mod_ch = maxModuleChannels(moduleIdx); + chEnd->enable(min_mod_ch < max_mod_ch); + + if (chEnd->getValue() > chEnd->getMax()) + chEnd->setValue(chEnd->getMax()); + + if (!isModulePXX2(moduleIdx)) { + chEnd->setAvailableHandler(nullptr); + } +#if defined(PXX2) + else { + chEnd->setAvailableHandler( + [=](int value) { return isPxx2IsrmChannelsCountAllowed(value - 8); }); + } +#endif +} + class ModuleWindow : public FormGroup { public: - ModuleWindow(FormWindow *parent, const rect_t &rect, uint8_t moduleIdx); - void update(); + ModuleWindow(Window* parent, uint8_t moduleIdx); + void updateModule(); + void updateSubType(); protected: uint8_t moduleIdx; - bool hasFailsafe = false; - Choice *rfChoice = nullptr; + ModuleOptions* modOpts = nullptr; + ChannelRange* chRange = nullptr; TextButton *bindButton = nullptr; TextButton *rangeButton = nullptr; TextButton *registerButton = nullptr; - Choice *failSafeChoice = nullptr; + Window *fsLine = nullptr; - void addChannelRange(FormGridLayout &grid); void startRSSIDialog(std::function closeHandler = nullptr); - void checkEvents() override; }; -ModuleWindow::ModuleWindow(FormWindow *parent, const rect_t &rect, - uint8_t moduleIdx) : - FormGroup(parent, rect, FORWARD_SCROLL | NO_FOCUS /*| FORM_FORWARD_FOCUS*/), +struct FailsafeChoice : public FormGroup { + FailsafeChoice(Window* parent, uint8_t moduleIdx); +}; + +static void fs_changed(lv_event_t* e) +{ + auto obj = lv_event_get_target(e); + auto fs = (Choice*)lv_obj_get_user_data(obj); + if (!fs) return; + + auto btn_obj = (lv_obj_t*)lv_event_get_user_data(e); + if (!btn_obj) return; + + if (fs->getIntValue() == FAILSAFE_CUSTOM) { + lv_obj_clear_flag(btn_obj, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(btn_obj, LV_OBJ_FLAG_HIDDEN); + } +} + +FailsafeChoice::FailsafeChoice(Window* parent, uint8_t moduleIdx) : + FormGroup(parent, rect_t{}) +{ + setFlexLayout(LV_FLEX_FLOW_ROW); + + auto md = &g_model.moduleData[moduleIdx]; + auto choice = + new Choice(this, rect_t{}, STR_VFAILSAFE, 0, FAILSAFE_LAST, + GET_DEFAULT(md->failsafeMode), [=](int32_t newValue) { + md->failsafeMode = newValue; + SET_DIRTY(); + }); + + auto btn = new TextButton(this, rect_t{}, STR_SET, [=]() -> uint8_t { + new FailSafePage(moduleIdx); + return 0; + }); + + auto c_obj = choice->getLvObj(); + auto btn_obj = btn->getLvObj(); + lv_obj_add_event_cb(c_obj, fs_changed, LV_EVENT_VALUE_CHANGED, btn_obj); + lv_event_send(c_obj, LV_EVENT_VALUE_CHANGED, nullptr); +} + +ModuleWindow::ModuleWindow(Window* parent, uint8_t moduleIdx) : + FormGroup(parent, rect_t{}), moduleIdx(moduleIdx) { - update(); + setFlexLayout(); + updateModule(); + // updateSubType(); } -void ModuleWindow::addChannelRange(FormGridLayout &grid) +void ModuleWindow::updateModule() { - new StaticText(this, grid.getLabelSlot(), STR_CHANNELRANGE, 0, - COLOR_THEME_PRIMARY1); - auto channelStart = new NumberEdit( - this, grid.getFieldSlot(2, 0), 1, - MAX_OUTPUT_CHANNELS - sentModuleChannels(moduleIdx) + 1, - GET_DEFAULT(1 + g_model.moduleData[moduleIdx].channelsStart)); - auto channelEnd = new NumberEdit( - this, grid.getFieldSlot(2, 1), - g_model.moduleData[moduleIdx].channelsStart + - minModuleChannels(moduleIdx), - min(MAX_OUTPUT_CHANNELS, - g_model.moduleData[moduleIdx].channelsStart + - maxModuleChannels(moduleIdx)), - GET_DEFAULT(g_model.moduleData[moduleIdx].channelsStart + 8 + - g_model.moduleData[moduleIdx].channelsCount)); - if (isModulePXX2(moduleIdx)) { - channelEnd->setAvailableHandler( - [=](int value) { return isPxx2IsrmChannelsCountAllowed(value - 8); }); - } - channelStart->setPrefix(STR_CH); - channelEnd->setPrefix(STR_CH); - channelStart->setSetValueHandler([=](int32_t newValue) { - g_model.moduleData[moduleIdx].channelsStart = newValue - 1; - SET_DIRTY(); - channelEnd->setMin(g_model.moduleData[moduleIdx].channelsStart + - minModuleChannels(moduleIdx)); - channelEnd->setMax(min(MAX_OUTPUT_CHANNELS, - g_model.moduleData[moduleIdx].channelsStart + - maxModuleChannels(moduleIdx))); - channelEnd->invalidate(); - }); - channelEnd->setSetValueHandler([=](int32_t newValue) { - g_model.moduleData[moduleIdx].channelsCount = - newValue - g_model.moduleData[moduleIdx].channelsStart - 8; - SET_DIRTY(); - channelStart->setMax(MAX_OUTPUT_CHANNELS - sentModuleChannels(moduleIdx) + - 1); - }); - channelEnd->enable(minModuleChannels(moduleIdx) < - maxModuleChannels(moduleIdx)); - if (channelEnd->getValue() > channelEnd->getMax()) - channelEnd->setValue(channelEnd->getMax()); -} - -void ModuleWindow::update() -{ - FormGridLayout grid; + FlexGridLayout grid(col_dsc, row_dsc, 2); clear(); - if (moduleIdx == INTERNAL_MODULE && isModuleCrossfire(moduleIdx)) { - char buf[6]; - new StaticText(this, grid.getFieldSlot(2, 1), - getStringAtIndex(buf, STR_CRSF_BAUDRATE, - CROSSFIRE_STORE_TO_INDEX( - g_eeGeneral.internalModuleBaudrate)), - 0, COLOR_THEME_PRIMARY1); - } - // Module parameters - if (moduleIdx == EXTERNAL_MODULE && isModuleCrossfire(moduleIdx)) { - new StaticText(this, grid.getLabelSlot(), STR_BAUDRATE, 0, - COLOR_THEME_PRIMARY1); - new Choice( - this, grid.getFieldSlot(1, 0), STR_CRSF_BAUDRATE, 0, - CROSSFIRE_MAX_INTERNAL_BAUDRATE, - [=]() -> int { - return CROSSFIRE_STORE_TO_INDEX( - g_model.moduleData[moduleIdx].crsf.telemetryBaudrate); - }, - [=](int newValue) { - g_model.moduleData[moduleIdx].crsf.telemetryBaudrate = - CROSSFIRE_INDEX_TO_STORE(newValue); - SET_DIRTY(); - restartModule(moduleIdx); - }); - grid.nextLine(); - } - if (isModuleCrossfire(moduleIdx)) { - new StaticText(this, grid.getLabelSlot(), STR_STATUS, 0, - COLOR_THEME_PRIMARY1); - new DynamicText(this, grid.getFieldSlot(), [=] { - char msg[64] = ""; - sprintf(msg, "%d Hz %" PRIu32 " Err", 1000000 / getMixerSchedulerPeriod(), - telemetryErrors); - return std::string(msg); - }); - grid.nextLine(); - } + ModuleData* md = &g_model.moduleData[moduleIdx]; + modOpts = nullptr; + if (md->type == MODULE_TYPE_NONE) { + return; + } +#if defined(CROSSFIRE) + else if (isModuleCrossfire(moduleIdx)) { + modOpts = new CrossfireSettings(this, grid, moduleIdx); + } +#endif #if defined(AFHDS2) || defined(AFHDS3) - if (isModuleFlySky(moduleIdx)) { - - // RX options: - new StaticText(this, grid.getLabelSlot(), STR_OPTIONS, 0, - COLOR_THEME_PRIMARY1); - -#if defined(AFHDS2) - if (isModuleAFHDS2A(moduleIdx)) { - // PPM / PWM - new Choice( - this, grid.getFieldSlot(2, 0), STR_FLYSKY_PULSE_PROTO, 0, 1, - [=]() { return g_model.moduleData[moduleIdx].flysky.mode >> 1; }, - [=](int v) { - g_model.moduleData[moduleIdx].flysky.mode = - (g_model.moduleData[moduleIdx].flysky.mode & 1) | - ((v & 1) << 1); - }); - // SBUS / iBUS - new Choice( - this, grid.getFieldSlot(2, 1), STR_FLYSKY_SERIAL_PROTO, 0, 1, - [=]() { return g_model.moduleData[moduleIdx].flysky.mode & 1; }, - [=](int v) { - g_model.moduleData[moduleIdx].flysky.mode = - (g_model.moduleData[moduleIdx].flysky.mode & 2) | (v & 1); - }); - grid.nextLine(); - } -#endif -#if defined(AFHDS3) - if (isModuleAFHDS3(moduleIdx)) { - // PPM / PWM - new Choice( - this, grid.getFieldSlot(2, 0), STR_FLYSKY_PULSE_PROTO, 0, 1, - [=]() { return g_model.moduleData[moduleIdx].afhds3.mode >> 1; }, - [=](int v) { - g_model.moduleData[moduleIdx].afhds3.mode = - (g_model.moduleData[moduleIdx].afhds3.mode & 1) | - ((v & 1) << 1); - }); - // SBUS / iBUS - new Choice( - this, grid.getFieldSlot(2, 1), STR_FLYSKY_SERIAL_PROTO, 0, 1, - [=]() { return g_model.moduleData[moduleIdx].afhds3.mode & 1; }, - [=](int v) { - g_model.moduleData[moduleIdx].afhds3.mode = - (g_model.moduleData[moduleIdx].afhds3.mode & 2) | (v & 1); - }); - grid.nextLine(); - - // TYPE - new StaticText(this, grid.getLabelSlot(), STR_TYPE, 0, - COLOR_THEME_PRIMARY1); - - // This is chosen when binding (menu? see stdlcd/model_setup_afhds3.cpp) - new StaticText(this, grid.getFieldSlot(), - g_model.moduleData[moduleIdx].afhds3.telemetry - ? STR_AFHDS3_ONE_TO_ONE_TELEMETRY - : TR_AFHDS3_ONE_TO_MANY, - 0, COLOR_THEME_PRIMARY1); - grid.nextLine(); - - // Status - new StaticText(this, grid.getLabelSlot(), STR_MODULE_STATUS, 0, - COLOR_THEME_PRIMARY1); - new DynamicText(this, grid.getFieldSlot(), [=] { - char msg[64] = ""; - getModuleStatusString(moduleIdx, msg); - return std::string(msg); - }); - grid.nextLine(); - - // Power source - new StaticText(this, grid.getLabelSlot(), STR_AFHDS3_POWER_SOURCE, 0, - COLOR_THEME_PRIMARY1); - new DynamicText(this, grid.getFieldSlot(), [=] { - char msg[64] = ""; - getModuleSyncStatusString(moduleIdx, msg); - return std::string(msg); - }); - grid.nextLine(); - - // RX Freq - new StaticText(this, grid.getLabelSlot(), STR_AFHDS3_RX_FREQ, 0, - COLOR_THEME_PRIMARY1); - auto edit = new NumberEdit( - this, grid.getFieldSlot(2, 0), MIN_FREQ, MAX_FREQ, - GET_DEFAULT(g_model.moduleData[moduleIdx].afhds3.rxFreq())); - edit->setSetValueHandler([=](int32_t newValue) { - g_model.moduleData[moduleIdx].afhds3.setRxFreq((uint16_t)newValue); - }); - edit->setSuffix(STR_HZ); - grid.nextLine(); - - // Module actual power - new StaticText(this, grid.getLabelSlot(), STR_AFHDS3_ACTUAL_POWER, 0, - COLOR_THEME_PRIMARY1); - new DynamicText(this, grid.getFieldSlot(), [=] { - char msg[64] = ""; - getStringAtIndex(msg, STR_AFHDS3_POWERS, - actualAfhdsRunPower(moduleIdx)); - return std::string(msg); - }); - grid.nextLine(); - - // Module power - new StaticText(this, grid.getLabelSlot(), STR_RF_POWER, 0, - COLOR_THEME_PRIMARY1); - new Choice( - this, grid.getFieldSlot(2, 0), STR_AFHDS3_POWERS, - afhds3::RUN_POWER::RUN_POWER_FIRST, afhds3::RUN_POWER::RUN_POWER_LAST, - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].afhds3.runPower)); - grid.nextLine(); - } -#endif + else if (isModuleFlySky(moduleIdx)) { + modOpts = new FlySkySettings(this, grid, moduleIdx); } #endif #if defined(MULTIMODULE) else if (isModuleMultimodule(moduleIdx)) { - - // TODO: needs to be placed differently - // MultiModuleStatus &status = getMultiModuleStatus(moduleIdx); - // if (status.protocolName[0] && status.isValid()) { - // new StaticText(this, grid.getFieldSlot(2, 1), status.protocolName, 0, - // COLOR_THEME_PRIMARY1); - // grid.nextLine(); - // } - - Choice *mmSubProto = nullptr; - new StaticText(this, grid.getLabelSlot(), STR_RF_PROTOCOL, 0, - COLOR_THEME_PRIMARY1); - - auto *rfProto = MultiRfProtocols::instance(moduleIdx)->getProto( - g_model.moduleData[moduleIdx].multi.rfProtocol); - - if (rfProto && !rfProto->subProtos.empty()) { - // Subtype (D16, DSMX,...) - mmSubProto = new Choice( - this, grid.getFieldSlot(), rfProto->subProtos, 0, - rfProto->subProtos.size() - 1, - [=]() { return g_model.moduleData[moduleIdx].subType; }, - [=](int16_t newValue) { - g_model.moduleData[moduleIdx].subType = newValue; - resetMultiProtocolsOptions(moduleIdx); - SET_DIRTY(); - // update(); - // if (mmSubProto != nullptr) mmSubProto->setFocus(SET_FOCUS_DEFAULT); - }); - } - grid.nextLine(); - - // Multimodule status - new StaticText(this, grid.getLabelSlot(), STR_MODULE_STATUS, 0, - COLOR_THEME_PRIMARY1); - new DynamicText( - this, grid.getFieldSlot(), - [=] { - char msg[64] = ""; - getModuleStatusString(moduleIdx, msg); - return std::string(msg); - }, - COLOR_THEME_PRIMARY1); - grid.nextLine(); - - const uint8_t multi_proto = g_model.moduleData[moduleIdx].multi.rfProtocol; - if (rfProto) { - // Multi optional feature row - const char *title = rfProto->getOptionStr(); - if (title != nullptr) { - new StaticText(this, grid.getLabelSlot(), title, 0, - COLOR_THEME_PRIMARY1); - - int8_t min, max; - getMultiOptionValues(multi_proto, min, max); - - if (title == STR_MULTI_RFPOWER) { - new Choice( - this, grid.getFieldSlot(2, 0), STR_MULTI_POWER, 0, 15, - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].multi.optionValue)); - } else if (title == STR_MULTI_TELEMETRY) { - new Choice( - this, grid.getFieldSlot(2, 0), STR_MULTI_TELEMETRY_MODE, min, max, - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].multi.optionValue)); - } else if (title == STR_MULTI_WBUS) { - new Choice( - this, grid.getFieldSlot(2, 0), STR_MULTI_WBUS_MODE, 0, 1, - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].multi.optionValue)); - } else if (multi_proto == MODULE_SUBTYPE_MULTI_FS_AFHDS2A) { - auto edit = new NumberEdit( - this, grid.getFieldSlot(2, 0), 50, 400, - GET_DEFAULT(50 + - 5 * g_model.moduleData[moduleIdx].multi.optionValue), - SET_VALUE(g_model.moduleData[moduleIdx].multi.optionValue, - (newValue - 50) / 5)); - edit->setStep(5); - } else if (multi_proto == MODULE_SUBTYPE_MULTI_DSM2) { - new CheckBox( - this, grid.getFieldSlot(2, 0), - [=]() { - return g_model.moduleData[moduleIdx].multi.optionValue & 0x01; - }, - [=](int16_t newValue) { - g_model.moduleData[moduleIdx].multi.optionValue = - (g_model.moduleData[moduleIdx].multi.optionValue & 0xFE) + - newValue; - }); - } else { - if (min == 0 && max == 1) { - new CheckBox(this, grid.getFieldSlot(2, 0), - GET_SET_DEFAULT( - g_model.moduleData[moduleIdx].multi.optionValue)); - } else { - new NumberEdit( - this, grid.getFieldSlot(2, 0), -128, 127, - GET_SET_DEFAULT( - g_model.moduleData[moduleIdx].multi.optionValue)); - - // Show RSSI next to RF Freq Fine Tune - if (title == STR_MULTI_RFTUNE) { - new DynamicNumber( - this, grid.getFieldSlot(2, 1), - [] { return (int)TELEMETRY_RSSI(); }, 0, "RSSI: ", " db"); - } - } - } - grid.nextLine(); - } - } - - if (multi_proto == MODULE_SUBTYPE_MULTI_DSM2) { - const char *servoRates[] = {"22ms", "11ms"}; - - new StaticText(this, grid.getLabelSlot(), STR_MULTI_SERVOFREQ, 0, - COLOR_THEME_PRIMARY1); - new Choice( - this, grid.getFieldSlot(), servoRates, 0, 1, - [=]() { - return (g_model.moduleData[moduleIdx].multi.optionValue & 0x02) >> - 1; - }, - [=](int16_t newValue) { - g_model.moduleData[moduleIdx].multi.optionValue = - (g_model.moduleData[moduleIdx].multi.optionValue & 0xFD) + - (newValue << 1); - }); - } else { - // Bind on power up - new StaticText(this, grid.getLabelSlot(), STR_MULTI_AUTOBIND, 0, - COLOR_THEME_PRIMARY1); - new CheckBox( - this, grid.getFieldSlot(), - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].multi.autoBindMode)); - } - grid.nextLine(); - - // Low power mode - new StaticText(this, grid.getLabelSlot(), STR_MULTI_LOWPOWER, 0, - COLOR_THEME_PRIMARY1); - new CheckBox( - this, grid.getFieldSlot(), - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].multi.lowPowerMode)); - grid.nextLine(); - - // Disable telemetry - new StaticText(this, grid.getLabelSlot(), STR_DISABLE_TELEM, 0, - COLOR_THEME_PRIMARY1); - new CheckBox( - this, grid.getFieldSlot(), - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].multi.disableTelemetry)); - grid.nextLine(); - - if (rfProto && rfProto->supportsDisableMapping()) { - // Disable channel mapping - new StaticText(this, grid.getLabelSlot(), STR_DISABLE_CH_MAP, 0, - COLOR_THEME_PRIMARY1); - new CheckBox( - this, grid.getFieldSlot(), - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].multi.disableMapping)); - grid.nextLine(); - } + modOpts = new MultimoduleSettings(this, grid, moduleIdx); } #endif // Channel Range - if (g_model.moduleData[moduleIdx].type != MODULE_TYPE_NONE) { - addChannelRange(grid); // TODO XJT2 should only set channel count of - // 8/16/24 - grid.nextLine(); - } + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_CHANNELRANGE, 0, COLOR_THEME_PRIMARY1); + chRange = new ChannelRange(line, moduleIdx); // PPM modules if (isModulePPM(moduleIdx)) { - // PPM frame - new StaticText(this, grid.getLabelSlot(), STR_PPMFRAME, 0, - COLOR_THEME_PRIMARY1); - - // PPM frame length - auto edit = new NumberEdit( - this, grid.getFieldSlot(3, 0), 125, 35 * PPM_STEP_SIZE + PPM_DEF_PERIOD, - GET_DEFAULT(g_model.moduleData[moduleIdx].ppm.frameLength * - PPM_STEP_SIZE + - PPM_DEF_PERIOD), - SET_VALUE(g_model.moduleData[moduleIdx].ppm.frameLength, - (newValue - PPM_DEF_PERIOD) / PPM_STEP_SIZE), - 0, PREC1); - edit->setStep(PPM_STEP_SIZE); - edit->setSuffix(STR_MS); - - // PPM frame delay - edit = new NumberEdit( - this, grid.getFieldSlot(3, 1), 100, 800, - GET_DEFAULT(g_model.moduleData[moduleIdx].ppm.delay * 50 + 300), - SET_VALUE(g_model.moduleData[moduleIdx].ppm.delay, - (newValue - 300) / 50)); - edit->setStep(50); - edit->setSuffix(STR_US); - - // PPM Polarity - new Choice(this, grid.getFieldSlot(3, 2), STR_PPM_POL, 0, 1, - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].ppm.pulsePol)); - grid.nextLine(); + new PpmSettings(this, grid, moduleIdx); } - // Module parameters + // Generic module parameters // Bind and Range buttons if (!isModuleRFAccess(moduleIdx) && (isModuleModelIndexAvailable(moduleIdx) || isModuleBindRangeAvailable(moduleIdx))) { - uint8_t thirdColumn = 0; - new StaticText(this, grid.getLabelSlot(), STR_RECEIVER, 0, - COLOR_THEME_PRIMARY1); + + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_RECEIVER, 0, COLOR_THEME_PRIMARY1); + + auto box = new FormGroup(line, rect_t{}); + box->setFlexLayout(LV_FLEX_FLOW_ROW); // Model index if (isModuleModelIndexAvailable(moduleIdx)) { - thirdColumn++; - new NumberEdit(this, grid.getFieldSlot(3, 0), 0, getMaxRxNum(moduleIdx), - GET_DEFAULT(g_model.header.modelId[moduleIdx]), - [=](int32_t newValue) { - if (newValue != g_model.header.modelId[moduleIdx]) { - g_model.header.modelId[moduleIdx] = newValue; + auto modelId = &g_model.header.modelId[moduleIdx]; + new NumberEdit(box, rect_t{}, 0, getMaxRxNum(moduleIdx), + GET_DEFAULT(*modelId), [=](int32_t newValue) { + if (newValue != *modelId) { + *modelId = newValue; if (isModuleCrossfire(moduleIdx)) { moduleState[moduleIdx].counter = CRSF_FRAME_MODELID; } @@ -509,9 +290,7 @@ void ModuleWindow::update() } if (isModuleBindRangeAvailable(moduleIdx)) { - bindButton = new TextButton( - this, grid.getFieldSlot(2 + thirdColumn, 0 + thirdColumn), - STR_MODULE_BIND); + bindButton = new TextButton(box, rect_t{},STR_MODULE_BIND); bindButton->setPressHandler([=]() -> uint8_t { if (moduleState[moduleIdx].mode == MODULE_MODE_RANGECHECK) { if (rangeButton) rangeButton->check(false); @@ -527,21 +306,8 @@ void ModuleWindow::update() if (isModuleR9MNonAccess(moduleIdx) || isModuleD16(moduleIdx) || IS_R9_MULTI(moduleIdx)) { new BindChoiceMenu( - this, moduleIdx, - [=]() { -#if defined(MULTIMODULE) - if (isModuleMultimodule(moduleIdx)) { - setMultiBindStatus(moduleIdx, MULTI_BIND_INITIATED); - } -#endif - moduleState[moduleIdx].mode = MODULE_MODE_BIND; - bindButton->check(true); - }, - [=]() { - moduleState[moduleIdx].mode = MODULE_MODE_NORMAL; - bindButton->check(false); - }); - + Layer::back(), moduleIdx, [=]() { bindButton->check(true); }, + [=]() { bindButton->check(false); }); return 0; } #if defined(MULTIMODULE) @@ -577,9 +343,7 @@ void ModuleWindow::update() }); if (isModuleRangeAvailable(moduleIdx)) { - rangeButton = new TextButton( - this, grid.getFieldSlot(2 + thirdColumn, 1 + thirdColumn), - STR_MODULE_RANGE); + rangeButton = new TextButton(box, rect_t{}, STR_MODULE_RANGE); rangeButton->setPressHandler([=]() -> uint8_t { if (moduleState[moduleIdx].mode == MODULE_MODE_BIND) { bindButton->check(false); @@ -607,63 +371,25 @@ void ModuleWindow::update() }); } } - - grid.nextLine(); } -#if defined(AFHDS2) && defined(PCBNV14) - if (isModuleAFHDS2A(moduleIdx) && getNV14RfFwVersion() >= 0x1000E) { - new StaticText(this, grid.getLabelSlot(), STR_MULTI_RFPOWER); - new Choice(this, grid.getFieldSlot(), - "\007" - "Default" - "High", - 0, 1, GET_DEFAULT(g_model.moduleData[moduleIdx].flysky.rfPower), - [=](int32_t newValue) -> void { - g_model.moduleData[moduleIdx].flysky.rfPower = newValue; - resetPulsesAFHDS2(); - }); - grid.nextLine(); - } -#endif - // Failsafe - if (isModuleFailsafeAvailable(moduleIdx)) { - hasFailsafe = true; - new StaticText(this, grid.getLabelSlot(), STR_FAILSAFE, 0, - COLOR_THEME_PRIMARY1); - failSafeChoice = new Choice( - this, grid.getFieldSlot(2, 0), STR_VFAILSAFE, 0, FAILSAFE_LAST, - GET_DEFAULT(g_model.moduleData[moduleIdx].failsafeMode), - [=](int32_t newValue) { - g_model.moduleData[moduleIdx].failsafeMode = newValue; - SET_DIRTY(); - // update(); - // failSafeChoice->setFocus(SET_FOCUS_DEFAULT); - }); - if (g_model.moduleData[moduleIdx].failsafeMode == FAILSAFE_CUSTOM) { - new TextButton(this, grid.getFieldSlot(2, 1), STR_SET, [=]() -> uint8_t { - new FailSafePage(moduleIdx); - return 1; - }); - } - grid.nextLine(); - } + fsLine = newLine(&grid); + new StaticText(fsLine, rect_t{}, STR_FAILSAFE, 0, COLOR_THEME_PRIMARY1); + new FailsafeChoice(fsLine, moduleIdx); #if defined(PXX2) // Register and Range buttons if (isModuleRFAccess(moduleIdx)) { - new StaticText(this, grid.getLabelSlot(), STR_MODULE, 0, - COLOR_THEME_PRIMARY1); - registerButton = - new TextButton(this, grid.getFieldSlot(2, 0), STR_REGISTER); + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_MODULE, 0, COLOR_THEME_PRIMARY1); + registerButton = new TextButton(line, rect_t{}, STR_REGISTER); registerButton->setPressHandler([=]() -> uint8_t { - new RegisterDialog(this, moduleIdx); + new pxx2::RegisterDialog(Layer::back(), moduleIdx); return 0; }); - rangeButton = - new TextButton(this, grid.getFieldSlot(2, 1), STR_MODULE_RANGE); + rangeButton = new TextButton(line, rect_t{}, STR_MODULE_RANGE); rangeButton->setPressHandler([=]() -> uint8_t { if (moduleState[moduleIdx].mode == MODULE_MODE_RANGECHECK) { moduleState[moduleIdx].mode = MODULE_MODE_NORMAL; @@ -675,81 +401,92 @@ void ModuleWindow::update() } }); - grid.nextLine(); - - new StaticText(this, grid.getLabelSlot(), TR_OPTIONS, 0, - COLOR_THEME_PRIMARY1); - auto options = new TextButton(this, grid.getFieldSlot(2, 0), TR_SET); + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_OPTIONS, 0, COLOR_THEME_PRIMARY1); + auto options = new TextButton(line, rect_t{}, STR_SET); options->setPressHandler([=]() { - new ModuleOptions(this, moduleIdx); + new pxx2::ModuleOptions(Layer::back(), moduleIdx); return 0; }); - grid.nextLine(); } #endif // R9M Power if (isModuleR9M_FCC_VARIANT(moduleIdx)) { - new StaticText(this, grid.getLabelSlot(), STR_RF_POWER, 0, - COLOR_THEME_PRIMARY1); - new Choice(this, grid.getFieldSlot(), STR_R9M_FCC_POWER_VALUES, 0, - R9M_FCC_POWER_MAX, - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].pxx.power)); + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_RF_POWER, 0, COLOR_THEME_PRIMARY1); + new Choice(line, rect_t{}, STR_R9M_FCC_POWER_VALUES, 0, R9M_FCC_POWER_MAX, + GET_SET_DEFAULT(md->pxx.power)); } if (isModuleR9M_LBT(moduleIdx)) { - new StaticText(this, grid.getLabelSlot(), STR_RF_POWER, 0, - COLOR_THEME_PRIMARY1); - new Choice(this, grid.getFieldSlot(), STR_R9M_LBT_POWER_VALUES, 0, + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_RF_POWER, 0, COLOR_THEME_PRIMARY1); + new Choice(line, rect_t{}, STR_R9M_LBT_POWER_VALUES, 0, R9M_LBT_POWER_MAX, - GET_DEFAULT(min(g_model.moduleData[moduleIdx].pxx.power, + GET_DEFAULT(min(md->pxx.power, R9M_LBT_POWER_MAX)), - SET_DEFAULT(g_model.moduleData[moduleIdx].pxx.power)); + SET_DEFAULT(md->pxx.power)); } #if defined(PXX2) // Receivers if (isModuleRFAccess(moduleIdx)) { for (uint8_t receiverIdx = 0; receiverIdx < PXX2_MAX_RECEIVERS_PER_MODULE; receiverIdx++) { + char label[] = TR_RECEIVER " X"; label[sizeof(label) - 2] = '1' + receiverIdx; - new StaticText(this, grid.getLabelSlot(), label, 0, - COLOR_THEME_PRIMARY1); - new ReceiverButton(this, grid.getFieldSlot(2, 0), moduleIdx, receiverIdx); - grid.nextLine(); + + auto line = newLine(&grid); + new StaticText(line, rect_t{}, label, 0, COLOR_THEME_PRIMARY1); + new pxx2::ReceiverButton(line, rect_t{}, moduleIdx, receiverIdx); } } #endif // SBUS refresh rate if (isModuleSBUS(moduleIdx)) { - new StaticText(this, grid.getLabelSlot(), STR_REFRESHRATE, 0, - COLOR_THEME_PRIMARY1); + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_REFRESHRATE, 0, COLOR_THEME_PRIMARY1); + + auto box = new FormGroup(line, rect_t{}); + box->setFlexLayout(LV_FLEX_FLOW_ROW); + auto edit = new NumberEdit( - this, grid.getFieldSlot(2, 0), SBUS_MIN_PERIOD, SBUS_MAX_PERIOD, - GET_DEFAULT((int16_t)g_model.moduleData[moduleIdx].sbus.refreshRate * + box, rect_t{}, SBUS_MIN_PERIOD, SBUS_MAX_PERIOD, + GET_DEFAULT((int16_t)md->sbus.refreshRate * SBUS_STEPSIZE + SBUS_DEF_PERIOD), - SET_VALUE(g_model.moduleData[moduleIdx].sbus.refreshRate, + SET_VALUE(md->sbus.refreshRate, (newValue - SBUS_DEF_PERIOD) / SBUS_STEPSIZE), 0, PREC1); edit->setSuffix(STR_MS); edit->setStep(SBUS_STEPSIZE); - new Choice(this, grid.getFieldSlot(2, 1), STR_SBUS_INVERSION_VALUES, 0, 1, - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].sbus.noninverted)); + new Choice(box, rect_t{}, STR_SBUS_INVERSION_VALUES, 0, 1, + GET_SET_DEFAULT(md->sbus.noninverted)); #if defined(RADIO_TX16S) - grid.nextLine(); - new StaticText(this, grid.getFieldSlot(1, 0), STR_WARN_5VOLTS, 0, - COLOR_THEME_PRIMARY1); + new StaticText(this, rect_t{}, STR_WARN_5VOLTS, 0, COLOR_THEME_PRIMARY1); #endif - grid.nextLine(); } if (isModuleGhost(moduleIdx)) { - new StaticText(this, grid.getLabelSlot(), "Raw 12 bits", 0, - COLOR_THEME_PRIMARY1); - new CheckBox( - this, grid.getFieldSlot(), - GET_SET_DEFAULT(g_model.moduleData[moduleIdx].ghost.raw12bits)); + auto line = newLine(&grid); + new StaticText(line, rect_t{}, "Raw 12 bits", 0, COLOR_THEME_PRIMARY1); + new CheckBox(line, rect_t{}, GET_SET_DEFAULT(md->ghost.raw12bits)); + } + + updateSubType(); +} + +void ModuleWindow::updateSubType() +{ + if (modOpts) modOpts->update(); + if (chRange) chRange->update(); + if (fsLine) { + if (isModuleFailsafeAvailable(moduleIdx)) { + lv_obj_clear_flag(fsLine->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(fsLine->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } } } @@ -779,30 +516,29 @@ void ModuleWindow::startRSSIDialog(std::function closeHandler) }); } -void ModuleWindow::checkEvents() -{ - if (isModuleFailsafeAvailable(moduleIdx) != hasFailsafe && rfChoice && - !rfChoice->isEditMode()) { - hasFailsafe = isModuleFailsafeAvailable(moduleIdx); - update(); - } +// void ModuleWindow::checkEvents() +// { +// if (isModuleFailsafeAvailable(moduleIdx) != hasFailsafe && rfChoice && +// !rfChoice->isEditMode()) { +// hasFailsafe = isModuleFailsafeAvailable(moduleIdx); +// update(); +// } - FormGroup::checkEvents(); -} +// FormGroup::checkEvents(); +// } class ModuleSubTypeChoice: public Choice { uint8_t moduleIdx; public: - ModuleSubTypeChoice(Window* parent, const rect_t &rect, uint8_t moduleIdx); + ModuleSubTypeChoice(Window* parent, uint8_t moduleIdx); void update(); void openMenu() override; }; -ModuleSubTypeChoice::ModuleSubTypeChoice(Window *parent, const rect_t &rect, - uint8_t moduleIdx) : - Choice(parent, rect, 0, 0, nullptr), moduleIdx(moduleIdx) +ModuleSubTypeChoice::ModuleSubTypeChoice(Window *parent, uint8_t moduleIdx) : + Choice(parent, rect_t{}, 0, 0, nullptr), moduleIdx(moduleIdx) { ModuleData *md = &g_model.moduleData[moduleIdx]; setGetValueHandler(GET_DEFAULT(md->subType)); @@ -903,19 +639,14 @@ void ModuleSubTypeChoice::update() } #endif else { - disable(); lv_obj_add_flag(lvobj, LV_OBJ_FLAG_HIDDEN); return; } - enable(); lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_HIDDEN); // update choice value lv_event_send(lvobj, LV_EVENT_VALUE_CHANGED, nullptr); - - // update module parameters - } void ModuleSubTypeChoice::openMenu() @@ -951,53 +682,47 @@ static void update_module_window(lv_event_t* e) ModuleWindow* mw = (ModuleWindow*)lv_event_get_user_data(e); if (!mw) return; - mw->update(); + mw->updateSubType(); } ModulePage::ModulePage(uint8_t moduleIdx) : Page(ICON_MODEL_SETUP) { const char* title = moduleIdx == INTERNAL_MODULE ? STR_INTERNALRF : STR_EXTERNALRF; + header.setTitle(title); - new StaticText(&header, - {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, - PAGE_LINE_HEIGHT}, - title, 0, COLOR_THEME_PRIMARY2); + auto form = new FormGroup(&body, rect_t{}); + form->setFlexLayout(); + form->padAll(lv_dpx(8)); + + FlexGridLayout grid(col_dsc, row_dsc, 2); - FormGridLayout grid; - // lv_obj_set_height(body.getLvObj(), LV_SIZE_CONTENT); - // Module Type - new StaticText(&body, grid.getLabelSlot(), STR_MODE, 0, - COLOR_THEME_PRIMARY1); + auto line = form->newLine(&grid); + new StaticText(line, rect_t{}, STR_MODE, 0, COLOR_THEME_PRIMARY1); + + auto box = new FormGroup(line, rect_t{}); + box->setFlexLayout(LV_FLEX_FLOW_ROW); ModuleData* md = &g_model.moduleData[moduleIdx]; - auto moduleChoice = new Choice( - &body, grid.getFieldSlot(2, 0), STR_INTERNAL_MODULE_PROTOCOLS, - MODULE_TYPE_NONE, MODULE_TYPE_COUNT - 1, - GET_DEFAULT(md->type)); + auto moduleChoice = new Choice(box, rect_t{}, STR_INTERNAL_MODULE_PROTOCOLS, + MODULE_TYPE_NONE, MODULE_TYPE_COUNT - 1, + GET_DEFAULT(md->type)); moduleChoice->setAvailableHandler([=](int8_t moduleType) { return moduleIdx == INTERNAL_MODULE ? isInternalModuleAvailable(moduleType) : isExternalModuleAvailable(moduleType); }); - auto subTypeChoice = new ModuleSubTypeChoice(&body, grid.getFieldSlot(2, 1), moduleIdx); - grid.nextLine(); - - coord_t y = grid.getWindowHeight(); - // coord_t h = body.height() - y; - coord_t w = body.width(); - - auto moduleWindow = new ModuleWindow(&body, {0, y, w, 0}, moduleIdx); - lv_obj_set_height(moduleWindow->getLvObj(), LV_SIZE_CONTENT); + auto subTypeChoice = new ModuleSubTypeChoice(box, moduleIdx); + auto moduleWindow = new ModuleWindow(form, moduleIdx); // This needs to be after moduleWindow has been created moduleChoice->setSetValueHandler([=](int32_t newValue) { setModuleType(moduleIdx, newValue); subTypeChoice->update(); - moduleWindow->update(); + moduleWindow->updateModule(); SET_DIRTY(); }); diff --git a/radio/src/gui/colorlcd/module_setup.h b/radio/src/gui/colorlcd/module_setup.h index 80e8b99ac..4ff918b89 100644 --- a/radio/src/gui/colorlcd/module_setup.h +++ b/radio/src/gui/colorlcd/module_setup.h @@ -26,3 +26,9 @@ struct ModulePage : public Page { ModulePage(uint8_t moduleIdx); }; + +class ModuleOptions +{ + public: + virtual void update() = 0; +}; diff --git a/radio/src/gui/colorlcd/mpm_settings.cpp b/radio/src/gui/colorlcd/mpm_settings.cpp new file mode 100644 index 000000000..66c453cf3 --- /dev/null +++ b/radio/src/gui/colorlcd/mpm_settings.cpp @@ -0,0 +1,294 @@ +/* + * 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 "mpm_settings.h" +#include "opentx.h" + +#include "multi_rfprotos.h" +#include "io/multi_protolist.h" + +#define SET_DIRTY() storageDirty(EE_MODEL) + +struct MPMProtoOption : public FormGroup::Line +{ + StaticText* label; + Choice* choice; + NumberEdit* edit; + CheckBox* cb; + DynamicNumber* rssi; + + MPMProtoOption(FormGroup* form, FlexGridLayout *layout); + void update(const MultiRfProtocols::RfProto* rfProto, ModuleData* md); +}; + +MPMProtoOption::MPMProtoOption(FormGroup* form, FlexGridLayout *layout) : + FormGroup::Line(form, layout) +{ + if (layout) layout->resetPos(); + label = new StaticText(this, rect_t{}, "", 0, COLOR_THEME_PRIMARY1); + + auto box = new FormGroup(this, rect_t{}); + box->setFlexLayout(LV_FLEX_FLOW_ROW); + + choice = new Choice(box, rect_t{}, 0, 0, nullptr); + edit = new NumberEdit(box, rect_t{}, 0, 0, nullptr); + cb = new CheckBox(box, rect_t{}, nullptr, nullptr); + rssi = new DynamicNumber( + box, rect_t{}, [] { return (int)TELEMETRY_RSSI(); }, 0, "RSSI: ", " db"); +} + +void MPMProtoOption::update(const MultiRfProtocols::RfProto* rfProto, ModuleData* md) +{ + if (!rfProto || !rfProto->getOptionStr()) { + lv_obj_add_flag(lvobj, LV_OBJ_FLAG_HIDDEN); + return; + } + + lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_HIDDEN); + + const char *title = rfProto->getOptionStr(); + label->setText(title); + + lv_obj_add_flag(choice->getLvObj(), LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(edit->getLvObj(), LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(cb->getLvObj(), LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(rssi->getLvObj(), LV_OBJ_FLAG_HIDDEN); + + int8_t min, max; + getMultiOptionValues(rfProto->proto, min, max); + + if (title == STR_MULTI_RFPOWER) { + choice->setValues(STR_MULTI_POWER); + choice->setMin(0); + choice->setMax(15); + choice->setGetValueHandler(GET_DEFAULT(md->multi.optionValue)); + choice->setSetValueHandler(SET_DEFAULT(md->multi.optionValue)); + lv_obj_clear_flag(choice->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else if (title == STR_MULTI_TELEMETRY) { + choice->setValues(STR_MULTI_TELEMETRY_MODE); + choice->setMin(min); + choice->setMax(max); + choice->setGetValueHandler(GET_DEFAULT(md->multi.optionValue)); + choice->setSetValueHandler(SET_DEFAULT(md->multi.optionValue)); + lv_obj_clear_flag(choice->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else if (title == STR_MULTI_WBUS) { + choice->setValues(STR_MULTI_WBUS_MODE); + choice->setMin(0); + choice->setMax(1); + choice->setGetValueHandler(GET_DEFAULT(md->multi.optionValue)); + choice->setSetValueHandler(SET_DEFAULT(md->multi.optionValue)); + lv_obj_clear_flag(choice->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else if (rfProto->proto == MODULE_SUBTYPE_MULTI_FS_AFHDS2A) { + edit->setMin(50); + edit->setMax(400); + edit->setGetValueHandler(GET_DEFAULT(50 + 5 * md->multi.optionValue)); + edit->setSetValueHandler(SET_VALUE(md->multi.optionValue, (newValue - 50) / 5)); + edit->setStep(5); + lv_obj_clear_flag(edit->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else if (rfProto->proto == MODULE_SUBTYPE_MULTI_DSM2) { + cb->setGetValueHandler([=]() { return md->multi.optionValue & 0x01; }); + cb->setSetValueHandler([=](int8_t newValue) { + md->multi.optionValue = (md->multi.optionValue & 0xFE) + newValue; + }); + lv_obj_clear_flag(cb->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else { + if (min == 0 && max == 1) { + cb->setGetValueHandler(GET_DEFAULT(md->multi.optionValue)); + cb->setSetValueHandler(SET_DEFAULT(md->multi.optionValue)); + lv_obj_clear_flag(cb->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else { + edit->setMin(-128); + edit->setMax(127); + edit->setGetValueHandler(GET_DEFAULT(md->multi.optionValue)); + edit->setSetValueHandler(SET_DEFAULT(md->multi.optionValue)); + lv_obj_clear_flag(edit->getLvObj(), LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(rssi->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } + } +} + +struct MPMSubtype : public FormGroup::Line +{ + Choice* choice; + + MPMSubtype(FormGroup* form, FlexGridLayout *layout, uint8_t moduleIdx); + void update(const MultiRfProtocols::RfProto* rfProto, ModuleData* md); +}; + +MPMSubtype::MPMSubtype(FormGroup* form, FlexGridLayout *layout, uint8_t moduleIdx) : + FormGroup::Line(form, layout) +{ + if (layout) layout->resetPos(); + new StaticText(this, rect_t{}, STR_RF_PROTOCOL, 0, COLOR_THEME_PRIMARY1); + + auto md = &g_model.moduleData[moduleIdx]; + choice = new Choice( + this, rect_t{}, 0, 0, [=]() { return md->subType; }, + [=](int16_t newValue) { + md->subType = newValue; + resetMultiProtocolsOptions(moduleIdx); + SET_DIRTY(); + }); +} + +void MPMSubtype::update(const MultiRfProtocols::RfProto* rfProto, ModuleData* md) +{ + if (!rfProto) { + lv_obj_add_flag(lvobj, LV_OBJ_FLAG_HIDDEN); + return; + } + + choice->setValues(rfProto->subProtos); + choice->setMax(rfProto->subProtos.size() - 1); + + lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_HIDDEN); + lv_event_send(choice->getLvObj(), LV_EVENT_VALUE_CHANGED, nullptr); +} + +struct MPMServoRate : public FormGroup::Line { + MPMServoRate(FormGroup* form, FlexGridLayout* layout, uint8_t moduleIdx); +}; + +static const char* _servoRates[] = {"22ms", "11ms"}; + +MPMServoRate::MPMServoRate(FormGroup* form, FlexGridLayout *layout, uint8_t moduleIdx) : + FormGroup::Line(form, layout) +{ + if (layout) layout->resetPos(); + new StaticText(this, rect_t{}, STR_MULTI_SERVOFREQ, 0, COLOR_THEME_PRIMARY1); + + auto md = &g_model.moduleData[moduleIdx]; + new Choice( + this, rect_t{}, _servoRates, 0, 1, + [=]() { return (md->multi.optionValue & 0x02) >> 1; }, + [=](int16_t newValue) { + md->multi.optionValue = + (md->multi.optionValue & 0xFD) + (newValue << 1); + }); +} + +struct MPMAutobind : public FormGroup::Line { + MPMAutobind(FormGroup* form, FlexGridLayout* layout, uint8_t moduleIdx); +}; + +MPMAutobind::MPMAutobind(FormGroup* form, FlexGridLayout *layout, uint8_t moduleIdx) : + FormGroup::Line(form, layout) +{ + if (layout) layout->resetPos(); + new StaticText(this, rect_t{}, STR_MULTI_AUTOBIND, 0, COLOR_THEME_PRIMARY1); + + auto md = &g_model.moduleData[moduleIdx]; + new CheckBox(this, rect_t{}, GET_SET_DEFAULT(md->multi.autoBindMode)); +} + +struct MPMChannelMap : public FormGroup::Line +{ + MPMChannelMap(FormGroup* form, FlexGridLayout *layout, uint8_t moduleIdx); + void update(const MultiRfProtocols::RfProto* rfProto); +}; + +MPMChannelMap::MPMChannelMap(FormGroup* form, FlexGridLayout *layout, uint8_t moduleIdx) : + FormGroup::Line(form, layout) +{ + if (layout) layout->resetPos(); + new StaticText(this, rect_t{}, STR_DISABLE_CH_MAP, 0, COLOR_THEME_PRIMARY1); + + auto md = &g_model.moduleData[moduleIdx]; + new CheckBox(this, rect_t{}, GET_SET_DEFAULT(md->multi.disableMapping)); +} + +void MPMChannelMap::update(const MultiRfProtocols::RfProto* rfProto) +{ + if (rfProto && rfProto->supportsDisableMapping()) { + lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(lvobj, LV_OBJ_FLAG_HIDDEN); + } +} + +MultimoduleSettings::MultimoduleSettings(Window *parent, + const FlexGridLayout &g, + uint8_t moduleIdx) : + FormGroup(parent, rect_t{}), md(&g_model.moduleData[moduleIdx]), + moduleIdx(moduleIdx) +{ + FlexGridLayout grid(g); + setFlexLayout(); + + // TODO: needs to be placed differently + // MultiModuleStatus &status = getMultiModuleStatus(moduleIdx); + // if (status.protocolName[0] && status.isValid()) { + // new StaticText(this, grid.getFieldSlot(2, 1), status.protocolName, 0, + // COLOR_THEME_PRIMARY1); + // grid.nextLine(); + // } + + // Multimodule status + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_MODULE_STATUS, 0, COLOR_THEME_PRIMARY1); + new DynamicText( + line, rect_t{}, + [=] { + char msg[64] = ""; + getModuleStatusString(moduleIdx, msg); + return std::string(msg); + }, + COLOR_THEME_PRIMARY1); + + st_line = new MPMSubtype(this, &grid, moduleIdx); + // TODO: set event cb on st_line->choice / LV_EVENT_VALUE_CHANGED ? + + opt_line = new MPMProtoOption(this, &grid); + sr_line = new MPMServoRate(this, &grid, moduleIdx); + ab_line = new MPMAutobind(this, &grid, moduleIdx); + + // Low power mode + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_MULTI_LOWPOWER, 0, COLOR_THEME_PRIMARY1); + new CheckBox(line, rect_t{}, GET_SET_DEFAULT(md->multi.lowPowerMode)); + + // Disable telemetry + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_DISABLE_TELEM, 0, COLOR_THEME_PRIMARY1); + new CheckBox(line, rect_t{}, GET_SET_DEFAULT(md->multi.disableTelemetry)); + + cm_line = new MPMChannelMap(this, &grid, moduleIdx); +} + +void MultimoduleSettings::update() +{ + auto mpm_rfprotos = MultiRfProtocols::instance(moduleIdx); + auto rfProto = mpm_rfprotos->getProto(md->multi.rfProtocol); + + st_line->update(rfProto, md); + opt_line->update(rfProto, md); + + auto multi_proto = md->multi.rfProtocol; + if (multi_proto == MODULE_SUBTYPE_MULTI_DSM2) { + lv_obj_clear_flag(sr_line->getLvObj(), LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(ab_line->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(sr_line->getLvObj(), LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(ab_line->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } + + cm_line->update(rfProto); +} diff --git a/radio/src/gui/colorlcd/mpm_settings.h b/radio/src/gui/colorlcd/mpm_settings.h new file mode 100644 index 000000000..fec1e47e2 --- /dev/null +++ b/radio/src/gui/colorlcd/mpm_settings.h @@ -0,0 +1,49 @@ +/* + * 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 "form.h" +#include "module_setup.h" + +struct ModuleData; +struct MPMProtoOption; +struct MPMSubtype; +struct MPMServoRate; +struct MPMAutobind; +struct MPMChannelMap; + +class MultimoduleSettings : public FormGroup, public ModuleOptions +{ + ModuleData* md; + uint8_t moduleIdx; + + MPMSubtype* st_line; + MPMProtoOption* opt_line; + MPMServoRate* sr_line; + MPMAutobind* ab_line; + MPMChannelMap* cm_line; + + void update() override; + +public: + MultimoduleSettings(Window* parent, const FlexGridLayout& g, uint8_t moduleIdx); +}; diff --git a/radio/src/gui/colorlcd/ppm_settings.cpp b/radio/src/gui/colorlcd/ppm_settings.cpp new file mode 100644 index 000000000..74f88b33d --- /dev/null +++ b/radio/src/gui/colorlcd/ppm_settings.cpp @@ -0,0 +1,66 @@ +/* + * 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 "ppm_settings.h" +#include "opentx.h" + +#define SET_DIRTY() storageDirty(EE_MODEL) + +template +PpmFrameSettings::PpmFrameSettings(Window* parent, const FlexGridLayout& g, T* ppm) : + FormGroup(parent, rect_t{}) +{ + setFlexLayout(LV_FLEX_FLOW_ROW); + + // PPM frame length + auto edit = new NumberEdit( + this, rect_t{}, 125, 35 * PPM_STEP_SIZE + PPM_DEF_PERIOD, + GET_DEFAULT(ppm->frameLength * PPM_STEP_SIZE + PPM_DEF_PERIOD), + SET_VALUE(ppm->frameLength, (newValue - PPM_DEF_PERIOD) / PPM_STEP_SIZE), + 0, PREC1); + edit->setStep(PPM_STEP_SIZE); + edit->setSuffix(STR_MS); + + // PPM frame delay + edit = new NumberEdit(this, rect_t{}, 100, 800, + GET_DEFAULT(ppm->delay * 50 + 300), + SET_VALUE(ppm->delay, (newValue - 300) / 50)); + edit->setStep(50); + edit->setSuffix(STR_US); + + // PPM Polarity + new Choice(this, rect_t{}, STR_PPM_POL, 0, 1, GET_SET_DEFAULT(ppm->pulsePol)); +} + +// explicit instantiation +template struct PpmFrameSettings; + +PpmSettings::PpmSettings(Window* parent, const FlexGridLayout& g, uint8_t moduleIdx) : + FormGroup(parent, rect_t{}), md(&g_model.moduleData[moduleIdx]) +{ + FlexGridLayout grid(g); + setFlexLayout(); + + // PPM frame + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_PPMFRAME, 0, COLOR_THEME_PRIMARY1); + new PpmFrameSettings(line, grid, &md->ppm); +} diff --git a/radio/src/gui/colorlcd/ppm_settings.h b/radio/src/gui/colorlcd/ppm_settings.h new file mode 100644 index 000000000..610f6ede3 --- /dev/null +++ b/radio/src/gui/colorlcd/ppm_settings.h @@ -0,0 +1,39 @@ +/* + * 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 "form.h" + +struct ModuleData; + +class PpmSettings : public FormGroup +{ + ModuleData* md; + +public: + PpmSettings(Window* parent, const FlexGridLayout& g, uint8_t moduleIdx); +}; + +template +struct PpmFrameSettings : public FormGroup { + PpmFrameSettings(Window* parent, const FlexGridLayout& g, T* ppm); +}; diff --git a/radio/src/gui/colorlcd/trainer_setup.cpp b/radio/src/gui/colorlcd/trainer_setup.cpp index 06e2dbbc6..ddd247aa7 100644 --- a/radio/src/gui/colorlcd/trainer_setup.cpp +++ b/radio/src/gui/colorlcd/trainer_setup.cpp @@ -230,6 +230,7 @@ void TrainerModuleWindow::update() COLOR_THEME_PRIMARY1); // PPM frame length + // TODO: use PpmFrameSettings auto edit = new NumberEdit( this, grid.getFieldSlot(3, 0), 125, 35 * 5 + 225, GET_DEFAULT(g_model.trainerData.frameLength * 5 + 225),