5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
25 enum MenuModelSetupItems
{
29 ITEM_MODEL_TIMER1_NAME
,
30 ITEM_MODEL_TIMER1_PERSISTENT
,
31 ITEM_MODEL_TIMER1_MINUTE_BEEP
,
32 ITEM_MODEL_TIMER1_COUNTDOWN_BEEP
,
35 ITEM_MODEL_TIMER2_NAME
,
36 ITEM_MODEL_TIMER2_PERSISTENT
,
37 ITEM_MODEL_TIMER2_MINUTE_BEEP
,
38 ITEM_MODEL_TIMER2_COUNTDOWN_BEEP
,
42 ITEM_MODEL_TIMER3_NAME
,
43 ITEM_MODEL_TIMER3_PERSISTENT
,
44 ITEM_MODEL_TIMER3_MINUTE_BEEP
,
45 ITEM_MODEL_TIMER3_COUNTDOWN_BEEP
,
48 ITEM_MODEL_TOP_LCD_TIMER
,
50 ITEM_MODEL_EXTENDED_LIMITS
,
51 ITEM_MODEL_EXTENDED_TRIMS
,
52 ITEM_MODEL_DISPLAY_TRIMS
,
54 ITEM_MODEL_THROTTLE_LABEL
,
55 ITEM_MODEL_THROTTLE_REVERSED
,
56 ITEM_MODEL_THROTTLE_TRACE
,
57 ITEM_MODEL_THROTTLE_TRIM
,
58 ITEM_MODEL_PREFLIGHT_LABEL
,
59 ITEM_MODEL_CHECKLIST_DISPLAY
,
60 ITEM_MODEL_THROTTLE_WARNING
,
61 ITEM_MODEL_SWITCHES_WARNING
,
63 ITEM_MODEL_SWITCHES_WARNING2
,
64 ITEM_MODEL_SWITCHES_WARNING3
,
66 ITEM_MODEL_POTS_WARNING
,
68 ITEM_MODEL_POTS_WARNING2
,
70 ITEM_MODEL_BEEP_CENTER
,
71 ITEM_MODEL_USE_GLOBAL_FUNCTIONS
,
72 ITEM_MODEL_INTERNAL_MODULE_LABEL
,
73 ITEM_MODEL_INTERNAL_MODULE_MODE
,
74 ITEM_MODEL_INTERNAL_MODULE_CHANNELS
,
75 ITEM_MODEL_INTERNAL_MODULE_BIND
,
76 ITEM_MODEL_INTERNAL_MODULE_FAILSAFE
,
77 ITEM_MODEL_EXTERNAL_MODULE_LABEL
,
78 ITEM_MODEL_EXTERNAL_MODULE_MODE
,
79 #if defined (MULTIMODULE)
80 ITEM_MODEL_EXTERNAL_MODULE_STATUS
,
81 ITEM_MODEL_EXTERNAL_MODULE_SYNCSTATUS
,
83 ITEM_MODEL_EXTERNAL_MODULE_CHANNELS
,
84 ITEM_MODEL_EXTERNAL_MODULE_BIND
,
85 ITEM_MODEL_EXTERNAL_MODULE_FAILSAFE
,
86 ITEM_MODEL_EXTERNAL_MODULE_OPTIONS
,
87 #if defined(MULTIMODULE)
88 ITEM_MODEL_EXTERNAL_MODULE_AUTOBIND
,
90 ITEM_MODEL_EXTERNAL_MODULE_POWER
,
91 ITEM_MODEL_TRAINER_LABEL
,
92 ITEM_MODEL_TRAINER_MODE
,
93 ITEM_MODEL_TRAINER_LINE1
,
94 ITEM_MODEL_TRAINER_LINE2
,
98 #define FIELD_PROTOCOL_MAX 1
100 #define MODEL_SETUP_2ND_COLUMN (LCD_W-17*FW-MENUS_SCROLLBAR_WIDTH-1)
101 #define MODEL_SETUP_3RD_COLUMN (MODEL_SETUP_2ND_COLUMN+6*FW)
102 #define MODEL_SETUP_BIND_OFS 3*FW-2
103 #define MODEL_SETUP_RANGE_OFS 7*FW
104 #define MODEL_SETUP_SET_FAILSAFE_OFS 10*FW-2
106 #define CURRENT_MODULE_EDITED(k) (k>=ITEM_MODEL_TRAINER_LABEL ? TRAINER_MODULE : (k>=ITEM_MODEL_EXTERNAL_MODULE_LABEL ? EXTERNAL_MODULE : INTERNAL_MODULE))
108 void onBindMenu(const char * result
)
110 uint8_t moduleIdx
= CURRENT_MODULE_EDITED(menuVerticalPosition
);
112 if (result
== STR_BINDING_25MW_CH1_8_TELEM_OFF
) {
113 g_model
.moduleData
[moduleIdx
].pxx
.power
= R9M_LBT_POWER_25
;
114 g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
= true;
115 g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
= false;
117 else if (result
== STR_BINDING_25MW_CH1_8_TELEM_ON
) {
118 g_model
.moduleData
[moduleIdx
].pxx
.power
= R9M_LBT_POWER_25
;
119 g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
= false;
120 g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
= false;
122 else if (result
== STR_BINDING_500MW_CH1_8_TELEM_OFF
) {
123 g_model
.moduleData
[moduleIdx
].pxx
.power
= R9M_LBT_POWER_500
;
124 g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
= true;
125 g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
= false;
127 else if (result
== STR_BINDING_500MW_CH9_16_TELEM_OFF
) {
128 g_model
.moduleData
[moduleIdx
].pxx
.power
= R9M_LBT_POWER_500
;
129 g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
= true;
130 g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
= true;
132 else if (result
== STR_BINDING_1_8_TELEM_ON
) {
133 g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
= false;
134 g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
= false;
136 else if (result
== STR_BINDING_1_8_TELEM_OFF
) {
137 g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
= true;
138 g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
= false;
140 else if (result
== STR_BINDING_9_16_TELEM_ON
) {
141 g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
= false;
142 g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
= true;
144 else if (result
== STR_BINDING_9_16_TELEM_OFF
) {
145 g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
= true;
146 g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
= true;
152 moduleFlag
[moduleIdx
] = MODULE_BIND
;
155 void copySelection(char * dst
, const char * src
, uint8_t size
)
157 if (memcmp(src
, "---", 3) == 0)
158 memset(dst
, 0, size
);
160 memcpy(dst
, src
, size
);
163 void onModelSetupBitmapMenu(const char * result
)
165 if (result
== STR_UPDATE_LIST
) {
166 if (!sdListFiles(BITMAPS_PATH
, BITMAPS_EXT
, sizeof(g_model
.header
.bitmap
), NULL
)) {
167 POPUP_WARNING(STR_NO_BITMAPS_ON_SD
);
171 // The user choosed a bmp file in the list
172 copySelection(g_model
.header
.bitmap
, result
, sizeof(g_model
.header
.bitmap
));
173 memcpy(modelHeaders
[g_eeGeneral
.currModel
].bitmap
, g_model
.header
.bitmap
, sizeof(g_model
.header
.bitmap
));
174 storageDirty(EE_MODEL
);
178 void editTimerMode(int timerIdx
, coord_t y
, LcdFlags attr
, event_t event
)
180 TimerData
& timer
= g_model
.timers
[timerIdx
];
181 drawStringWithIndex(0*FW
, y
, STR_TIMER
, timerIdx
+1);
182 drawTimerMode(MODEL_SETUP_2ND_COLUMN
, y
, timer
.mode
, menuHorizontalPosition
==0 ? attr
: 0);
183 drawTimer(MODEL_SETUP_3RD_COLUMN
, y
, timer
.start
, menuHorizontalPosition
==1 ? attr
|TIMEHOUR
: TIMEHOUR
, menuHorizontalPosition
==2 ? attr
|TIMEHOUR
: TIMEHOUR
);
184 if (attr
&& menuHorizontalPosition
< 0) {
185 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN
-1, y
-1, 13*FW
-3, FH
+1);
187 if (attr
&& s_editMode
>0) {
188 div_t qr
= div(timer
.start
, 60);
189 switch (menuHorizontalPosition
) {
192 swsrc_t timerMode
= timer
.mode
;
193 if (timerMode
< 0) timerMode
-= TMRMODE_COUNT
-1;
194 CHECK_INCDEC_MODELVAR_CHECK(event
, timerMode
, -TMRMODE_COUNT
-SWSRC_LAST
+1, TMRMODE_COUNT
+SWSRC_LAST
-1, isSwitchAvailableInTimers
);
195 if (timerMode
< 0) timerMode
+= TMRMODE_COUNT
-1;
196 timer
.mode
= timerMode
;
197 #if defined(AUTOSWITCH)
199 swsrc_t val
= timer
.mode
- (TMRMODE_COUNT
-1);
200 swsrc_t switchVal
= checkIncDecMovedSwitch(val
);
201 if (val
!= switchVal
) {
202 timer
.mode
= switchVal
+ (TMRMODE_COUNT
-1);
203 storageDirty(EE_MODEL
);
210 qr
.quot
= checkIncDec(event
, qr
.quot
, 0, 1439, EE_MODEL
| NO_INCDEC_MARKS
); // 23h59
211 timer
.start
= qr
.rem
+ qr
.quot
*60;
214 qr
.rem
-= checkIncDecModel(event
, qr
.rem
+2, 1, 62)-2;
215 timer
.start
-= qr
.rem
;
216 if ((int16_t)timer
.start
< 0) timer
.start
=0;
217 if ((int32_t)timer
.start
> 86399) timer
.start
=86399; // 23h59:59
223 void editTimerCountdown(int timerIdx
, coord_t y
, LcdFlags attr
, event_t event
)
225 TimerData
& timer
= g_model
.timers
[timerIdx
];
226 lcdDrawTextAlignedLeft(y
, STR_BEEPCOUNTDOWN
);
227 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_VBEEPCOUNTDOWN
, timer
.countdownBeep
, (menuHorizontalPosition
==0 ? attr
: 0));
228 if (timer
.countdownBeep
!= COUNTDOWN_SILENT
) {
229 lcdDrawNumber(MODEL_SETUP_3RD_COLUMN
, y
, TIMER_COUNTDOWN_START(timerIdx
), (menuHorizontalPosition
== 1 ? attr
: 0) | LEFT
);
230 lcdDrawChar(lcdLastRightPos
, y
, 's');
232 if (attr
&& s_editMode
>0) {
233 switch (menuHorizontalPosition
) {
235 CHECK_INCDEC_MODELVAR(event
, timer
.countdownBeep
, COUNTDOWN_SILENT
, COUNTDOWN_COUNT
- 1);
238 timer
.countdownStart
= -checkIncDecModel(event
, -timer
.countdownStart
, -1, +2);
244 int getSwitchWarningsCount()
247 for (int i
=0; i
<NUM_SWITCHES
; ++i
) {
248 if (SWITCH_WARNING_ALLOWED(i
)) {
255 #define IF_INTERNAL_MODULE_ON(x) (IS_INTERNAL_MODULE_ENABLED() ? (uint8_t)(x) : HIDDEN_ROW)
256 #if defined(TARANIS_INTERNAL_PPM)
257 #define INTERNAL_MODULE_MODE_ROWS (IS_MODULE_XJT(INTERNAL_MODULE) ? (uint8_t)1 : (uint8_t)0) // Module type + RF protocols
259 #define INTERNAL_MODULE_MODE_ROWS 0 // (OFF / RF protocols)
261 #define IF_EXTERNAL_MODULE_ON(x) (IS_EXTERNAL_MODULE_ENABLED() ? (uint8_t)(x) : HIDDEN_ROW)
262 #define INTERNAL_MODULE_CHANNELS_ROWS IF_INTERNAL_MODULE_ON(1)
263 #define PORT_CHANNELS_ROWS(x) (x==INTERNAL_MODULE ? INTERNAL_MODULE_CHANNELS_ROWS : (x==EXTERNAL_MODULE ? EXTERNAL_MODULE_CHANNELS_ROWS : 1))
265 #if defined(BLUETOOTH) && defined(USEHORUSBT)
266 #define TRAINER_LINE1_BLUETOOTH_M_ROWS ((bluetoothDistantAddr[0] == 0 || bluetoothState == BLUETOOTH_STATE_CONNECTED) ? (uint8_t)0 : (uint8_t)1)
267 #define TRAINER_LINE1_ROWS (g_model.trainerMode == TRAINER_MODE_SLAVE ? (uint8_t)1 : (g_model.trainerMode == TRAINER_MODE_MASTER_BLUETOOTH ? TRAINER_LINE1_BLUETOOTH_M_ROWS : (g_model.trainerMode == TRAINER_MODE_SLAVE_BLUETOOTH ? (uint8_t)1 : HIDDEN_ROW)))
268 #define TRAINER_LINE2_ROWS (g_model.trainerMode == TRAINER_MODE_SLAVE ? (uint8_t)2 : HIDDEN_ROW)
270 #define TRAINER_LINE1_ROWS (g_model.trainerMode == TRAINER_MODE_SLAVE ? (uint8_t)1 : HIDDEN_ROW)
271 #define TRAINER_LINE2_ROWS (g_model.trainerMode == TRAINER_MODE_SLAVE ? (uint8_t)2 : HIDDEN_ROW)
274 #define TIMER_ROWS(x) 2|NAVIGATION_LINE_BY_LINE, 0, 0, 0, g_model.timers[x].countdownBeep != COUNTDOWN_SILENT ? (uint8_t) 1 : (uint8_t)0
276 #define EXTERNAL_MODULE_MODE_ROWS (IS_MODULE_PXX(EXTERNAL_MODULE) || IS_MODULE_DSM2(EXTERNAL_MODULE)) ? (uint8_t)1 : IS_MODULE_MULTIMODULE(EXTERNAL_MODULE) ? MULTIMODULE_MODE_ROWS(EXTERNAL_MODULE) : (uint8_t)0
279 #define TIMERS_ROWS TIMER_ROWS(0)
281 #define TIMERS_ROWS TIMER_ROWS(0), TIMER_ROWS(1)
283 #define TIMERS_ROWS TIMER_ROWS(0), TIMER_ROWS(1), TIMER_ROWS(2)
286 #define SW_WARN_ITEMS() uint8_t(NAVIGATION_LINE_BY_LINE|(getSwitchWarningsCount()-1)), uint8_t(getSwitchWarningsCount() > 8 ? TITLE_ROW : HIDDEN_ROW), uint8_t(getSwitchWarningsCount() > 16 ? TITLE_ROW : HIDDEN_ROW)
287 #define POT_WARN_ITEMS() uint8_t(g_model.potsWarnMode ? NAVIGATION_LINE_BY_LINE|(NUM_POTS+NUM_SLIDERS) : 0), uint8_t(g_model.potsWarnMode ? TITLE_ROW : HIDDEN_ROW)
288 #define TOPLCD_ROWS 0,
290 #define SW_WARN_ITEMS() uint8_t(NAVIGATION_LINE_BY_LINE|getSwitchWarningsCount())
291 #define POT_WARN_ITEMS() uint8_t(g_model.potsWarnMode ? NAVIGATION_LINE_BY_LINE|(NUM_POTS+NUM_SLIDERS) : 0)
296 void menuModelSetup(event_t event
)
298 horzpos_t l_posHorz
= menuHorizontalPosition
;
299 bool CURSOR_ON_CELL
= (menuHorizontalPosition
>= 0);
300 #if defined(TARANIS_INTERNAL_PPM)
301 MENU_TAB({ 0, 0, TIMERS_ROWS
, TOPLCD_ROWS
0, 1, 0, 0,
302 LABEL(Throttle
), 0, 0, 0,
303 LABEL(PreflightCheck
), 0, 0, SW_WARN_ITEMS(), POT_WARN_ITEMS(), NAVIGATION_LINE_BY_LINE
|(NUM_STICKS
+NUM_POTS
+NUM_SLIDERS
+NUM_ROTARY_ENCODERS
-1), 0,
304 LABEL(InternalModule
),
305 INTERNAL_MODULE_MODE_ROWS
,
306 INTERNAL_MODULE_CHANNELS_ROWS
,
307 IF_INTERNAL_MODULE_ON(IS_MODULE_XJT(INTERNAL_MODULE
) ? (HAS_RF_PROTOCOL_MODELINDEX(g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
) ? (uint8_t)2 : (uint8_t)1) : (IS_MODULE_PPM(INTERNAL_MODULE
) ? (uint8_t)1 : HIDDEN_ROW
)),
308 IF_INTERNAL_MODULE_ON((IS_MODULE_XJT(INTERNAL_MODULE
)) ? FAILSAFE_ROWS(INTERNAL_MODULE
) : HIDDEN_ROW
),
309 LABEL(ExternalModule
),
310 EXTERNAL_MODULE_MODE_ROWS
,
311 MULTIMODULE_STATUS_ROWS
312 EXTERNAL_MODULE_CHANNELS_ROWS
,
313 (IS_MODULE_XJT(EXTERNAL_MODULE
) && !HAS_RF_PROTOCOL_FAILSAFE(g_model
.moduleData
[EXTERNAL_MODULE
].rfProtocol
)) ? (uint8_t)1 : (IS_MODULE_PPM(EXTERNAL_MODULE
) || IS_MODULE_XJT(EXTERNAL_MODULE
) || IS_MODULE_DSM2(EXTERNAL_MODULE
) || IS_MODULE_MULTIMODULE(EXTERNAL_MODULE
)) ? (uint8_t)2 : HIDDEN_ROW
,
314 FAILSAFE_ROWS(EXTERNAL_MODULE
), EXTERNAL_MODULE_OPTION_ROW
, MULTIMODULE_MODULE_ROWS EXTERNAL_MODULE_POWER_ROW
,
315 LABEL(Trainer
), 0, TRAINER_LINE1_ROWS
, TRAINER_LINE2_ROWS
});
317 MENU_TAB({ 0, 0, TIMERS_ROWS
, TOPLCD_ROWS
0, 1, 0, 0,
318 LABEL(Throttle
), 0, 0, 0,
319 LABEL(PreflightCheck
), 0, 0, SW_WARN_ITEMS(), POT_WARN_ITEMS(), NAVIGATION_LINE_BY_LINE
|(NUM_STICKS
+NUM_POTS
+NUM_SLIDERS
+NUM_ROTARY_ENCODERS
-1), 0,
320 LABEL(InternalModule
),
321 INTERNAL_MODULE_MODE_ROWS
,
322 INTERNAL_MODULE_CHANNELS_ROWS
,
323 IF_INTERNAL_MODULE_ON(HAS_RF_PROTOCOL_MODELINDEX(g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
) ? (uint8_t)2 : (uint8_t)1),
324 IF_INTERNAL_MODULE_ON(FAILSAFE_ROWS(INTERNAL_MODULE
)),
325 LABEL(ExternalModule
),
326 EXTERNAL_MODULE_MODE_ROWS
,
327 MULTIMODULE_STATUS_ROWS
328 EXTERNAL_MODULE_CHANNELS_ROWS
,
329 ((IS_MODULE_XJT(EXTERNAL_MODULE
) && !HAS_RF_PROTOCOL_FAILSAFE(g_model
.moduleData
[EXTERNAL_MODULE
].rfProtocol
)) || IS_MODULE_SBUS(EXTERNAL_MODULE
)) ? (uint8_t)1 : (IS_MODULE_PPM(EXTERNAL_MODULE
) || IS_MODULE_PXX(EXTERNAL_MODULE
) || IS_MODULE_DSM2(EXTERNAL_MODULE
) || IS_MODULE_MULTIMODULE(EXTERNAL_MODULE
)) ? (uint8_t)2 : HIDDEN_ROW
,
330 FAILSAFE_ROWS(EXTERNAL_MODULE
),
331 EXTERNAL_MODULE_OPTION_ROW
,
332 MULTIMODULE_MODULE_ROWS
333 EXTERNAL_MODULE_POWER_ROW
,
334 LABEL(Trainer
), 0, TRAINER_LINE1_ROWS
, TRAINER_LINE2_ROWS
});
336 MENU_CHECK(STR_MENUSETUP
, menuTabModel
, MENU_MODEL_SETUP
, ITEM_MODEL_SETUP_MAX
);
338 #if (defined(DSM2) || defined(PXX))
345 int sub
= menuVerticalPosition
;
347 for (int i
=0; i
<NUM_BODY_LINES
; ++i
) {
348 coord_t y
= MENU_HEADER_HEIGHT
+ 1 + i
*FH
;
349 uint8_t k
= i
+menuVerticalOffset
;
350 for (int j
=0; j
<=k
; j
++) {
351 if (mstate_tab
[j
] == HIDDEN_ROW
)
355 LcdFlags blink
= ((s_editMode
>0) ? BLINK
|INVERS
: INVERS
);
356 LcdFlags attr
= (sub
== k
? blink
: 0);
359 case ITEM_MODEL_NAME
:
360 editSingleName(MODEL_SETUP_2ND_COLUMN
, y
, STR_MODELNAME
, g_model
.header
.name
, sizeof(g_model
.header
.name
), event
, attr
);
361 memcpy(modelHeaders
[g_eeGeneral
.currModel
].name
, g_model
.header
.name
, sizeof(g_model
.header
.name
));
364 case ITEM_MODEL_BITMAP
:
365 lcdDrawTextAlignedLeft(y
, STR_BITMAP
);
366 if (ZEXIST(g_model
.header
.bitmap
))
367 lcdDrawSizedText(MODEL_SETUP_2ND_COLUMN
, y
, g_model
.header
.bitmap
, sizeof(g_model
.header
.bitmap
), attr
);
369 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_VCSWFUNC
, 0, attr
);
370 if (attr
&& event
==EVT_KEY_BREAK(KEY_ENTER
) && READ_ONLY_UNLOCKED()) {
372 if (sdListFiles(BITMAPS_PATH
, BITMAPS_EXT
, sizeof(g_model
.header
.bitmap
), g_model
.header
.bitmap
, LIST_NONE_SD_FILE
)) {
373 POPUP_MENU_START(onModelSetupBitmapMenu
);
376 POPUP_WARNING(STR_NO_BITMAPS_ON_SD
);
381 case ITEM_MODEL_TIMER1
:
382 editTimerMode(0, y
, attr
, event
);
385 case ITEM_MODEL_TIMER1_NAME
:
386 editSingleName(MODEL_SETUP_2ND_COLUMN
, y
, STR_TIMER_NAME
, g_model
.timers
[0].name
, LEN_TIMER_NAME
, event
, attr
);
389 case ITEM_MODEL_TIMER1_MINUTE_BEEP
:
390 g_model
.timers
[0].minuteBeep
= editCheckBox(g_model
.timers
[0].minuteBeep
, MODEL_SETUP_2ND_COLUMN
, y
, STR_MINUTEBEEP
, attr
, event
);
393 case ITEM_MODEL_TIMER1_COUNTDOWN_BEEP
:
394 editTimerCountdown(0, y
, attr
, event
);
397 case ITEM_MODEL_TIMER1_PERSISTENT
:
398 g_model
.timers
[0].persistent
= editChoice(MODEL_SETUP_2ND_COLUMN
, y
, STR_PERSISTENT
, STR_VPERSISTENT
, g_model
.timers
[0].persistent
, 0, 2, attr
, event
);
402 case ITEM_MODEL_TIMER2
:
403 editTimerMode(1, y
, attr
, event
);
406 case ITEM_MODEL_TIMER2_NAME
:
407 editSingleName(MODEL_SETUP_2ND_COLUMN
, y
, STR_TIMER_NAME
, g_model
.timers
[1].name
, LEN_TIMER_NAME
, event
, attr
);
410 case ITEM_MODEL_TIMER2_MINUTE_BEEP
:
411 g_model
.timers
[1].minuteBeep
= editCheckBox(g_model
.timers
[1].minuteBeep
, MODEL_SETUP_2ND_COLUMN
, y
, STR_MINUTEBEEP
, attr
, event
);
414 case ITEM_MODEL_TIMER2_COUNTDOWN_BEEP
:
415 editTimerCountdown(1, y
, attr
, event
);
418 case ITEM_MODEL_TIMER2_PERSISTENT
:
419 g_model
.timers
[1].persistent
= editChoice(MODEL_SETUP_2ND_COLUMN
, y
, STR_PERSISTENT
, STR_VPERSISTENT
, g_model
.timers
[1].persistent
, 0, 2, attr
, event
);
424 case ITEM_MODEL_TIMER3
:
425 editTimerMode(2, y
, attr
, event
);
428 case ITEM_MODEL_TIMER3_NAME
:
429 editSingleName(MODEL_SETUP_2ND_COLUMN
, y
, STR_TIMER_NAME
, g_model
.timers
[2].name
, LEN_TIMER_NAME
, event
, attr
);
432 case ITEM_MODEL_TIMER3_MINUTE_BEEP
:
433 g_model
.timers
[2].minuteBeep
= editCheckBox(g_model
.timers
[2].minuteBeep
, MODEL_SETUP_2ND_COLUMN
, y
, STR_MINUTEBEEP
, attr
, event
);
436 case ITEM_MODEL_TIMER3_COUNTDOWN_BEEP
:
437 editTimerCountdown(2, y
, attr
, event
);
440 case ITEM_MODEL_TIMER3_PERSISTENT
:
441 g_model
.timers
[2].persistent
= editChoice(MODEL_SETUP_2ND_COLUMN
, y
, STR_PERSISTENT
, STR_VPERSISTENT
, g_model
.timers
[2].persistent
, 0, 2, attr
, event
);
446 case ITEM_MODEL_TOP_LCD_TIMER
:
447 lcdDrawTextAlignedLeft(y
, STR_TOPLCDTIMER
);
448 drawStringWithIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_TIMER
, g_model
.toplcdTimer
+1, attr
);
450 g_model
.toplcdTimer
= checkIncDec(event
, g_model
.toplcdTimer
, 0, TIMERS
-1, EE_MODEL
);
455 case ITEM_MODEL_EXTENDED_LIMITS
:
456 ON_OFF_MENU_ITEM(g_model
.extendedLimits
, MODEL_SETUP_2ND_COLUMN
, y
, STR_ELIMITS
, attr
, event
);
459 case ITEM_MODEL_EXTENDED_TRIMS
:
460 ON_OFF_MENU_ITEM(g_model
.extendedTrims
, MODEL_SETUP_2ND_COLUMN
, y
, STR_ETRIMS
, menuHorizontalPosition
<=0 ? attr
: 0, event
==EVT_KEY_BREAK(KEY_ENTER
) ? event
: 0);
461 lcdDrawText(MODEL_SETUP_2ND_COLUMN
+3*FW
, y
, STR_RESET_BTN
, (menuHorizontalPosition
>0 && !NO_HIGHLIGHT()) ? attr
: 0);
462 if (attr
&& menuHorizontalPosition
>0) {
464 if (event
==EVT_KEY_LONG(KEY_ENTER
)) {
465 START_NO_HIGHLIGHT();
466 for (uint8_t i
=0; i
<MAX_FLIGHT_MODES
; i
++) {
467 memclear(&g_model
.flightModeData
[i
], TRIMS_ARRAY_SIZE
);
469 storageDirty(EE_MODEL
);
475 case ITEM_MODEL_DISPLAY_TRIMS
:
476 g_model
.displayTrims
= editChoice(MODEL_SETUP_2ND_COLUMN
, y
, STR_DISPLAY_TRIMS
, STR_VDISPLAYTRIMS
, g_model
.displayTrims
, 0, 2, attr
, event
);
479 case ITEM_MODEL_TRIM_INC
:
480 g_model
.trimInc
= editChoice(MODEL_SETUP_2ND_COLUMN
, y
, STR_TRIMINC
, STR_VTRIMINC
, g_model
.trimInc
, -2, 2, attr
, event
);
483 case ITEM_MODEL_THROTTLE_LABEL
:
484 lcdDrawTextAlignedLeft(y
, STR_THROTTLE_LABEL
);
487 case ITEM_MODEL_THROTTLE_REVERSED
:
488 ON_OFF_MENU_ITEM(g_model
.throttleReversed
, MODEL_SETUP_2ND_COLUMN
, y
, STR_THROTTLEREVERSE
, attr
, event
) ;
491 case ITEM_MODEL_THROTTLE_TRACE
:
493 lcdDrawTextAlignedLeft(y
, STR_TTRACE
);
494 if (attr
) CHECK_INCDEC_MODELVAR_ZERO_CHECK(event
, g_model
.thrTraceSrc
, NUM_POTS
+NUM_SLIDERS
+MAX_OUTPUT_CHANNELS
, isThrottleSourceAvailable
);
495 uint8_t idx
= g_model
.thrTraceSrc
+ MIXSRC_Thr
;
496 if (idx
> MIXSRC_Thr
)
498 if (idx
>= MIXSRC_FIRST_POT
+NUM_POTS
+NUM_SLIDERS
)
499 idx
+= MIXSRC_CH1
- MIXSRC_FIRST_POT
- NUM_POTS
- NUM_SLIDERS
;
500 drawSource(MODEL_SETUP_2ND_COLUMN
, y
, idx
, attr
);
504 case ITEM_MODEL_THROTTLE_TRIM
:
505 ON_OFF_MENU_ITEM(g_model
.thrTrim
, MODEL_SETUP_2ND_COLUMN
, y
, STR_TTRIM
, attr
, event
);
508 case ITEM_MODEL_PREFLIGHT_LABEL
:
509 lcdDrawTextAlignedLeft(y
, STR_PREFLIGHT
);
512 case ITEM_MODEL_CHECKLIST_DISPLAY
:
513 ON_OFF_MENU_ITEM(g_model
.displayChecklist
, MODEL_SETUP_2ND_COLUMN
, y
, STR_CHECKLIST
, attr
, event
);
516 case ITEM_MODEL_THROTTLE_WARNING
:
517 g_model
.disableThrottleWarning
= !editCheckBox(!g_model
.disableThrottleWarning
, MODEL_SETUP_2ND_COLUMN
, y
, STR_THROTTLEWARNING
, attr
, event
);
521 case ITEM_MODEL_SWITCHES_WARNING2
:
522 case ITEM_MODEL_SWITCHES_WARNING3
:
523 case ITEM_MODEL_POTS_WARNING2
:
525 if (CURSOR_MOVED_LEFT(event
))
526 menuVerticalOffset
--;
528 menuVerticalOffset
++;
533 case ITEM_MODEL_SWITCHES_WARNING
:
536 if (i
>=NUM_BODY_LINES
-2 && getSwitchWarningsCount() > 8*(NUM_BODY_LINES
-i
)) {
537 if (CURSOR_MOVED_LEFT(event
))
538 menuVerticalOffset
--;
540 menuVerticalOffset
++;
544 lcdDrawTextAlignedLeft(y
, STR_SWITCHWARNING
);
545 swarnstate_t states
= g_model
.switchWarningState
;
551 case EVT_KEY_BREAK(KEY_ENTER
):
554 case EVT_KEY_LONG(KEY_ENTER
):
555 if (menuHorizontalPosition
< 0) {
556 START_NO_HIGHLIGHT();
558 g_model
.switchWarningState
= switches_states
;
560 storageDirty(EE_MODEL
);
568 LcdFlags line
= attr
;
571 for (int i
=0; i
<NUM_SWITCHES
; i
++) {
572 if (SWITCH_WARNING_ALLOWED(i
)) {
573 div_t qr
= div(current
, 8);
574 if (!READ_ONLY() && event
==EVT_KEY_BREAK(KEY_ENTER
) && line
&& l_posHorz
==current
) {
575 g_model
.switchWarningEnable
^= (1 << i
);
576 storageDirty(EE_MODEL
);
578 uint8_t swactive
= !(g_model
.switchWarningEnable
& (1<<i
));
579 c
= "\300-\301"[states
& 0x03];
580 lcdDrawChar(MODEL_SETUP_2ND_COLUMN
+qr
.rem
*(2*FW
+1), y
+FH
*qr
.quot
, 'A'+i
, line
&& (menuHorizontalPosition
==current
) ? INVERS
: 0);
581 if (swactive
) lcdDrawChar(lcdNextPos
, y
+FH
*qr
.quot
, c
);
586 if (attr
&& menuHorizontalPosition
< 0) {
588 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN
-1, y
-1, 8*(2*FW
+1), 1+FH
*((current
+7)/8));
590 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN
-1, y
-1, current
*(2*FW
+1), FH
+1);
596 case ITEM_MODEL_POTS_WARNING
:
598 if (i
==NUM_BODY_LINES
-1 && g_model
.potsWarnMode
) {
599 if (CURSOR_MOVED_LEFT(event
))
600 menuVerticalOffset
--;
602 menuVerticalOffset
++;
607 lcdDrawTextAlignedLeft(y
, STR_POTWARNING
);
608 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, PSTR("\004""OFF\0""Man\0""Auto"), g_model
.potsWarnMode
, (menuHorizontalPosition
== 0) ? attr
: 0);
609 if (attr
&& (menuHorizontalPosition
== 0)) {
610 CHECK_INCDEC_MODELVAR(event
, g_model
.potsWarnMode
, POTS_WARN_OFF
, POTS_WARN_AUTO
);
611 storageDirty(EE_MODEL
);
615 if (menuHorizontalPosition
> 0) s_editMode
= 0;
616 if (!READ_ONLY() && menuHorizontalPosition
> 0) {
618 case EVT_KEY_LONG(KEY_ENTER
):
620 if (g_model
.potsWarnMode
== POTS_WARN_MANUAL
) {
621 SAVE_POT_POSITION(menuHorizontalPosition
-1);
623 storageDirty(EE_MODEL
);
626 case EVT_KEY_BREAK(KEY_ENTER
):
627 g_model
.potsWarnEnabled
^= (1 << (menuHorizontalPosition
-1));
628 storageDirty(EE_MODEL
);
633 if (g_model
.potsWarnMode
) {
634 coord_t x
= MODEL_SETUP_2ND_COLUMN
+28;
635 for (int i
=0; i
<NUM_POTS
+NUM_SLIDERS
; ++i
) {
636 if (i
<NUM_XPOTS
&& !IS_POT_SLIDER_AVAILABLE(POT1
+i
)) {
637 if (attr
&& (menuHorizontalPosition
==i
+1)) REPEAT_LAST_CURSOR_MOVE();
641 if (i
== NUM_XPOTS
) {
643 x
= MODEL_SETUP_2ND_COLUMN
;
646 LcdFlags flags
= ((menuHorizontalPosition
==i
+1) && attr
) ? BLINK
: 0;
647 if ((!attr
|| menuHorizontalPosition
>= 0) && !(g_model
.potsWarnEnabled
& (1 << i
))) {
651 // TODO add a new function
652 lcdDrawSizedText(x
, y
, STR_VSRCRAW
+2+STR_VSRCRAW
[0]*(NUM_STICKS
+1+i
), STR_VSRCRAW
[0]-1, flags
& ~ZCHAR
);
657 if (attr
&& menuHorizontalPosition
< 0) {
659 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN
-1, y
-FH
-1, LCD_W
-MODEL_SETUP_2ND_COLUMN
-MENUS_SCROLLBAR_WIDTH
+1, 2*FH
+1);
661 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN
-1, y
-1, LCD_W
-MODEL_SETUP_2ND_COLUMN
-MENUS_SCROLLBAR_WIDTH
+1, FH
+1);
666 case ITEM_MODEL_BEEP_CENTER
:
668 lcdDrawTextAlignedLeft(y
, STR_BEEPCTR
);
669 coord_t x
= MODEL_SETUP_2ND_COLUMN
;
670 for (int i
=0; i
<NUM_STICKS
+NUM_POTS
+NUM_SLIDERS
+NUM_ROTARY_ENCODERS
; i
++) {
671 if (i
>=POT1
&& i
<POT1
+NUM_XPOTS
&& !IS_POT_SLIDER_AVAILABLE(i
)) {
672 if (attr
&& menuHorizontalPosition
== i
) REPEAT_LAST_CURSOR_MOVE();
675 lcdDrawTextAtIndex(x
, y
, STR_RETA123
, i
, ((menuHorizontalPosition
==i
) && attr
) ? BLINK
|INVERS
: (((g_model
.beepANACenter
& ((BeepANACenter
)1<<i
)) || (attr
&& CURSOR_ON_LINE())) ? INVERS
: 0 ) );
678 if (attr
&& CURSOR_ON_CELL
) {
679 if (event
==EVT_KEY_BREAK(KEY_ENTER
)) {
680 if (READ_ONLY_UNLOCKED()) {
682 g_model
.beepANACenter
^= ((BeepANACenter
)1<<menuHorizontalPosition
);
683 storageDirty(EE_MODEL
);
690 case ITEM_MODEL_USE_GLOBAL_FUNCTIONS
:
691 lcdDrawTextAlignedLeft(y
, STR_USE_GLOBAL_FUNCS
);
692 drawCheckBox(MODEL_SETUP_2ND_COLUMN
, y
, !g_model
.noGlobalFunctions
, attr
);
693 if (attr
) g_model
.noGlobalFunctions
= !checkIncDecModel(event
, !g_model
.noGlobalFunctions
, 0, 1);
696 case ITEM_MODEL_INTERNAL_MODULE_LABEL
:
697 lcdDrawTextAlignedLeft(y
, TR_INTERNALRF
);
700 #if defined(TARANIS_INTERNAL_PPM)
701 case ITEM_MODEL_INTERNAL_MODULE_MODE
:
702 lcdDrawTextAlignedLeft(y
, STR_MODE
);
703 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_TARANIS_PROTOCOLS
, g_model
.moduleData
[INTERNAL_MODULE
].type
, menuHorizontalPosition
==0 ? attr
: 0);
704 if (IS_MODULE_XJT(INTERNAL_MODULE
))
705 lcdDrawTextAtIndex(MODEL_SETUP_3RD_COLUMN
, y
, STR_XJT_PROTOCOLS
, 1+g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
, menuHorizontalPosition
==1 ? attr
: 0);
706 if (attr
&& s_editMode
>0) {
707 switch (menuHorizontalPosition
) {
709 g_model
.moduleData
[INTERNAL_MODULE
].type
= checkIncDec(event
, g_model
.moduleData
[INTERNAL_MODULE
].type
, MODULE_TYPE_NONE
, MODULE_TYPE_COUNT
-2, EE_MODEL
, isModuleAvailable
);
710 if (checkIncDec_Ret
) {
711 g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
= 0;
712 g_model
.moduleData
[INTERNAL_MODULE
].channelsStart
= 0;
713 g_model
.moduleData
[INTERNAL_MODULE
].channelsCount
= DEFAULT_CHANNELS(INTERNAL_MODULE
);;
717 g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
= checkIncDec(event
, g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
, RF_PROTO_X16
, RF_PROTO_LAST
, EE_MODEL
, isRfProtocolAvailable
);
718 if (checkIncDec_Ret
) {
719 g_model
.moduleData
[INTERNAL_MODULE
].channelsStart
= 0;
720 g_model
.moduleData
[INTERNAL_MODULE
].channelsCount
= DEFAULT_CHANNELS(INTERNAL_MODULE
);
726 case ITEM_MODEL_INTERNAL_MODULE_MODE
:
727 lcdDrawTextAlignedLeft(y
, STR_MODE
);
728 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_XJT_PROTOCOLS
, 1+g_model
.moduleData
[0].rfProtocol
, attr
);
730 g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
= checkIncDec(event
, g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
, -1, RF_PROTO_LAST
, EE_MODEL
, isRfProtocolAvailable
);
732 if (checkIncDec_Ret
) {
733 g_model
.moduleData
[INTERNAL_MODULE
].type
= MODULE_TYPE_XJT
;
734 g_model
.moduleData
[INTERNAL_MODULE
].channelsStart
= 0;
735 g_model
.moduleData
[INTERNAL_MODULE
].channelsCount
= DEFAULT_CHANNELS(INTERNAL_MODULE
);
736 if (g_model
.moduleData
[INTERNAL_MODULE
].rfProtocol
== RF_PROTO_OFF
)
737 g_model
.moduleData
[INTERNAL_MODULE
].type
= MODULE_TYPE_NONE
;
742 case ITEM_MODEL_TRAINER_MODE
:
743 lcdDrawTextAlignedLeft(y
, STR_MODE
);
744 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_VTRAINERMODES
, g_model
.trainerMode
, attr
);
746 g_model
.trainerMode
= checkIncDec(event
, g_model
.trainerMode
, 0, TRAINER_MODE_MAX(), EE_MODEL
, isTrainerModeAvailable
);
748 #if defined(BLUETOOTH) && defined(USEHORUSBT)
749 if (attr
&& checkIncDec_Ret
) {
750 bluetoothState
= BLUETOOTH_STATE_OFF
;
751 bluetoothDistantAddr
[0] = 0;
756 case ITEM_MODEL_EXTERNAL_MODULE_LABEL
:
757 lcdDrawTextAlignedLeft(y
, TR_EXTERNALRF
);
760 case ITEM_MODEL_EXTERNAL_MODULE_MODE
:
761 lcdDrawTextAlignedLeft(y
, STR_MODE
);
762 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_TARANIS_PROTOCOLS
, g_model
.moduleData
[EXTERNAL_MODULE
].type
, menuHorizontalPosition
==0 ? attr
: 0);
763 if (IS_MODULE_XJT(EXTERNAL_MODULE
))
764 lcdDrawTextAtIndex(MODEL_SETUP_3RD_COLUMN
, y
, STR_XJT_PROTOCOLS
, 1+g_model
.moduleData
[EXTERNAL_MODULE
].rfProtocol
, menuHorizontalPosition
==1 ? attr
: 0);
765 else if (IS_MODULE_DSM2(EXTERNAL_MODULE
))
766 lcdDrawTextAtIndex(MODEL_SETUP_3RD_COLUMN
, y
, STR_DSM_PROTOCOLS
, g_model
.moduleData
[EXTERNAL_MODULE
].rfProtocol
, menuHorizontalPosition
==1 ? attr
: 0);
767 else if (IS_MODULE_R9M(EXTERNAL_MODULE
))
768 lcdDrawTextAtIndex(MODEL_SETUP_3RD_COLUMN
, y
, STR_R9M_MODES
, g_model
.moduleData
[EXTERNAL_MODULE
].subType
, (menuHorizontalPosition
==1 ? attr
: 0));
769 #if defined(MULTIMODULE)
770 else if (IS_MODULE_MULTIMODULE(EXTERNAL_MODULE
)) {
771 uint8_t multi_rfProto
= g_model
.moduleData
[EXTERNAL_MODULE
].getMultiProtocol(false);
773 // Do not use MODEL_SETUP_3RD_COLUMN here since some the protocol string are so long that we cannot afford the 2 spaces (+6) here
774 if (g_model
.moduleData
[EXTERNAL_MODULE
].multi
.customProto
) {
775 lcdDrawText(MODEL_SETUP_2ND_COLUMN
+ 5 * FW
, y
, STR_MULTI_CUSTOM
, menuHorizontalPosition
== 1 ? attr
: 0);
776 lcdDrawNumber(MODEL_SETUP_2ND_COLUMN
+ 14 * FW
, y
, g_model
.moduleData
[EXTERNAL_MODULE
].getMultiProtocol(false), menuHorizontalPosition
== 2 ? attr
: 0, 2);
777 lcdDrawNumber(MODEL_SETUP_2ND_COLUMN
+ 16 * FW
, y
, g_model
.moduleData
[EXTERNAL_MODULE
].subType
, menuHorizontalPosition
== 3 ? attr
: 0, 2);
780 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
+5*FW
, y
, STR_MULTI_PROTOCOLS
, multi_rfProto
, menuHorizontalPosition
==1 ? attr
: 0);
782 const mm_protocol_definition
*pdef
= getMultiProtocolDefinition(multi_rfProto
);
783 if (pdef
->subTypeString
!= nullptr)
784 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
+11*FW
, y
, pdef
->subTypeString
, g_model
.moduleData
[EXTERNAL_MODULE
].subType
, menuHorizontalPosition
==2 ? attr
: 0);
788 if (attr
&& s_editMode
>0) {
789 switch (menuHorizontalPosition
) {
791 g_model
.moduleData
[EXTERNAL_MODULE
].type
= checkIncDec(event
, g_model
.moduleData
[EXTERNAL_MODULE
].type
, MODULE_TYPE_NONE
, IS_TRAINER_EXTERNAL_MODULE() ? MODULE_TYPE_NONE
: MODULE_TYPE_COUNT
-1, EE_MODEL
, isModuleAvailable
);
792 if (checkIncDec_Ret
) {
793 g_model
.moduleData
[EXTERNAL_MODULE
].rfProtocol
= 0;
794 g_model
.moduleData
[EXTERNAL_MODULE
].channelsStart
= 0;
795 g_model
.moduleData
[EXTERNAL_MODULE
].channelsCount
= DEFAULT_CHANNELS(EXTERNAL_MODULE
);
796 if (IS_MODULE_SBUS(EXTERNAL_MODULE
))
797 g_model
.moduleData
[EXTERNAL_MODULE
].sbus
.refreshRate
= -31;
801 if (IS_MODULE_DSM2(EXTERNAL_MODULE
))
802 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[EXTERNAL_MODULE
].rfProtocol
, DSM2_PROTO_LP45
, DSM2_PROTO_DSMX
);
803 else if (IS_MODULE_R9M(EXTERNAL_MODULE
))
804 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[EXTERNAL_MODULE
].subType
, MODULE_SUBTYPE_R9M_FCC
, MODULE_SUBTYPE_R9M_LBT
);
805 #if defined(MULTIMODULE)
806 else if (IS_MODULE_MULTIMODULE(EXTERNAL_MODULE
)) {
807 int multiRfProto
= g_model
.moduleData
[EXTERNAL_MODULE
].multi
.customProto
== 1 ? MM_RF_PROTO_CUSTOM
: g_model
.moduleData
[EXTERNAL_MODULE
].getMultiProtocol(false);
808 CHECK_INCDEC_MODELVAR(event
, multiRfProto
, MM_RF_PROTO_FIRST
, MM_RF_PROTO_LAST
);
809 if (checkIncDec_Ret
) {
810 g_model
.moduleData
[EXTERNAL_MODULE
].multi
.customProto
= (multiRfProto
== MM_RF_PROTO_CUSTOM
);
811 if (!g_model
.moduleData
[EXTERNAL_MODULE
].multi
.customProto
)
812 g_model
.moduleData
[EXTERNAL_MODULE
].setMultiProtocol(multiRfProto
);
813 g_model
.moduleData
[EXTERNAL_MODULE
].subType
= 0;
814 // Sensible default for DSM2 (same as for ppm): 7ch@22ms + Autodetect settings enabled
815 if (g_model
.moduleData
[EXTERNAL_MODULE
].getMultiProtocol(true) == MM_RF_PROTO_DSM2
) {
816 g_model
.moduleData
[EXTERNAL_MODULE
].multi
.autoBindMode
= 1;
819 g_model
.moduleData
[EXTERNAL_MODULE
].multi
.autoBindMode
= 0;
821 g_model
.moduleData
[EXTERNAL_MODULE
].multi
.optionValue
= 0;
826 g_model
.moduleData
[EXTERNAL_MODULE
].rfProtocol
= checkIncDec(event
, g_model
.moduleData
[EXTERNAL_MODULE
].rfProtocol
, RF_PROTO_X16
, RF_PROTO_LAST
, EE_MODEL
, isRfProtocolAvailable
);
828 if (checkIncDec_Ret
) {
829 g_model
.moduleData
[EXTERNAL_MODULE
].channelsStart
= 0;
830 g_model
.moduleData
[EXTERNAL_MODULE
].channelsCount
= DEFAULT_CHANNELS(EXTERNAL_MODULE
);
833 #if defined(MULTIMODULE)
835 if (g_model
.moduleData
[EXTERNAL_MODULE
].multi
.customProto
) {
836 g_model
.moduleData
[EXTERNAL_MODULE
].setMultiProtocol(checkIncDec(event
, g_model
.moduleData
[EXTERNAL_MODULE
].getMultiProtocol(false), 0, 63, EE_MODEL
));
840 const mm_protocol_definition
* pdef
= getMultiProtocolDefinition(g_model
.moduleData
[EXTERNAL_MODULE
].getMultiProtocol(false));
841 if (pdef
->maxSubtype
> 0)
842 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[EXTERNAL_MODULE
].subType
, 0, pdef
->maxSubtype
);
846 // Custom protocol, third column is subtype
847 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[EXTERNAL_MODULE
].subType
, 0, 7);
854 case ITEM_MODEL_TRAINER_LABEL
:
855 lcdDrawTextAlignedLeft(y
, STR_TRAINER
);
858 #if defined(BLUETOOTH) && defined(USEHORUSBT)
859 case ITEM_MODEL_TRAINER_LINE1
:
860 if (g_model
.trainerMode
== TRAINER_MODE_MASTER_BLUETOOTH
) {
864 if (bluetoothDistantAddr
[0]) {
865 lcdDrawText(INDENT_WIDTH
, y
+1, bluetoothDistantAddr
, TINSIZE
);
866 if (bluetoothState
!= BLUETOOTH_STATE_CONNECTED
) {
867 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, BUTTON("Bind"), menuHorizontalPosition
== 0 ? attr
: 0);
868 lcdDrawText(MODEL_SETUP_2ND_COLUMN
+5*FW
, y
, BUTTON("Clear"), menuHorizontalPosition
== 1 ? attr
: 0);
871 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, BUTTON("Clear"), attr
);
873 if (attr
&& event
== EVT_KEY_FIRST(KEY_ENTER
)) {
874 if (bluetoothState
== BLUETOOTH_STATE_CONNECTED
|| menuHorizontalPosition
== 1) {
875 bluetoothState
= BLUETOOTH_STATE_OFF
;
876 bluetoothDistantAddr
[0] = 0;
879 bluetoothState
= BLUETOOTH_STATE_BIND_REQUESTED
;
884 lcdDrawText(INDENT_WIDTH
, y
, "---");
885 if (bluetoothState
< BLUETOOTH_STATE_IDLE
)
886 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, BUTTON("Init"), attr
);
888 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, BUTTON("Discover"), attr
);
889 if (attr
&& event
== EVT_KEY_FIRST(KEY_ENTER
)) {
890 if (bluetoothState
< BLUETOOTH_STATE_IDLE
)
891 bluetoothState
= BLUETOOTH_STATE_OFF
;
893 bluetoothState
= BLUETOOTH_STATE_DISCOVER_REQUESTED
;
900 case ITEM_MODEL_TRAINER_LINE1
:
902 case ITEM_MODEL_INTERNAL_MODULE_CHANNELS
:
903 case ITEM_MODEL_EXTERNAL_MODULE_CHANNELS
:
905 uint8_t moduleIdx
= CURRENT_MODULE_EDITED(k
);
906 ModuleData
& moduleData
= g_model
.moduleData
[moduleIdx
];
907 lcdDrawTextAlignedLeft(y
, STR_CHANNELRANGE
);
908 if ((int8_t)PORT_CHANNELS_ROWS(moduleIdx
) >= 0) {
909 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, STR_CH
, menuHorizontalPosition
==0 ? attr
: 0);
910 lcdDrawNumber(lcdLastRightPos
, y
, moduleData
.channelsStart
+1, LEFT
| (menuHorizontalPosition
==0 ? attr
: 0));
911 lcdDrawChar(lcdLastRightPos
, y
, '-');
912 lcdDrawNumber(lcdLastRightPos
+ FW
+1, y
, moduleData
.channelsStart
+NUM_CHANNELS(moduleIdx
), LEFT
| (menuHorizontalPosition
==1 ? attr
: 0));
913 if (IS_R9M_OR_XJTD16(moduleIdx
)) {
914 if (NUM_CHANNELS(moduleIdx
) > 8)
915 lcdDrawText(lcdLastRightPos
+5, y
, "(18ms)");
917 lcdDrawText(lcdLastRightPos
+5, y
, "(9ms)");
919 if (attr
&& s_editMode
>0) {
920 switch (menuHorizontalPosition
) {
922 CHECK_INCDEC_MODELVAR_ZERO(event
, moduleData
.channelsStart
, 32-8-moduleData
.channelsCount
);
925 CHECK_INCDEC_MODELVAR(event
, moduleData
.channelsCount
, -4, min
<int8_t>(MAX_CHANNELS(moduleIdx
), 32-8-moduleData
.channelsStart
));
926 #if defined(TARANIS_INTERNAL_PPM)
927 if ((k
== ITEM_MODEL_EXTERNAL_MODULE_CHANNELS
&& g_model
.moduleData
[EXTERNAL_MODULE
].type
== MODULE_TYPE_PPM
) || (k
== ITEM_MODEL_INTERNAL_MODULE_CHANNELS
&& g_model
.moduleData
[INTERNAL_MODULE
].type
== MODULE_TYPE_PPM
) || (k
== ITEM_MODEL_TRAINER_LINE1
)) {
928 SET_DEFAULT_PPM_FRAME_LENGTH(moduleIdx
);
931 if ((k
== ITEM_MODEL_EXTERNAL_MODULE_CHANNELS
&& g_model
.moduleData
[EXTERNAL_MODULE
].type
== MODULE_TYPE_PPM
) || (k
== ITEM_MODEL_TRAINER_LINE1
)) {
932 SET_DEFAULT_PPM_FRAME_LENGTH(moduleIdx
);
942 case ITEM_MODEL_INTERNAL_MODULE_BIND
:
943 case ITEM_MODEL_EXTERNAL_MODULE_BIND
:
944 case ITEM_MODEL_TRAINER_LINE2
:
946 uint8_t moduleIdx
= CURRENT_MODULE_EDITED(k
);
947 ModuleData
& moduleData
= g_model
.moduleData
[moduleIdx
];
948 if (IS_MODULE_PPM(moduleIdx
)) {
949 lcdDrawTextAlignedLeft(y
, STR_PPMFRAME
);
950 lcdDrawNumber(MODEL_SETUP_2ND_COLUMN
, y
, (int16_t)moduleData
.ppm
.frameLength
*5 + 225, (menuHorizontalPosition
<=0 ? attr
: 0) | PREC1
|LEFT
);
951 lcdDrawText(lcdLastRightPos
, y
, STR_MS
);
952 lcdDrawNumber(MODEL_SETUP_2ND_COLUMN
+6*FW
, y
, (moduleData
.ppm
.delay
*50)+300, (CURSOR_ON_LINE() || menuHorizontalPosition
==1) ? attr
: 0);
953 lcdDrawChar(lcdLastRightPos
, y
, 'u');
954 lcdDrawChar(MODEL_SETUP_2ND_COLUMN
+12*FW
, y
, moduleData
.ppm
.pulsePol
? '+' : '-', (CURSOR_ON_LINE() || menuHorizontalPosition
==2) ? attr
: 0);
956 if (attr
&& s_editMode
>0) {
957 switch (menuHorizontalPosition
) {
959 CHECK_INCDEC_MODELVAR(event
, moduleData
.ppm
.frameLength
, -20, 35);
962 CHECK_INCDEC_MODELVAR(event
, moduleData
.ppm
.delay
, -4, 10);
965 CHECK_INCDEC_MODELVAR_ZERO(event
, moduleData
.ppm
.pulsePol
, 1);
970 else if (IS_MODULE_SBUS(moduleIdx
)) {
971 lcdDrawTextAlignedLeft(y
, STR_REFRESHRATE
);
972 lcdDrawNumber(MODEL_SETUP_2ND_COLUMN
, y
, (int16_t)moduleData
.ppm
.frameLength
*5 + 225, (menuHorizontalPosition
<=0 ? attr
: 0) | PREC1
|LEFT
);
973 lcdDrawText(lcdLastRightPos
, y
, STR_MS
);
974 lcdDrawText(MODEL_SETUP_3RD_COLUMN
, y
, moduleData
.sbus
.noninverted
? "not inverted" : "normal", (CURSOR_ON_LINE() || menuHorizontalPosition
==1) ? attr
: 0);
976 if (attr
&& s_editMode
>0) {
977 switch (menuHorizontalPosition
) {
979 CHECK_INCDEC_MODELVAR(event
, moduleData
.ppm
.frameLength
, -33, 35);
982 CHECK_INCDEC_MODELVAR_ZERO(event
, moduleData
.sbus
.noninverted
, 1);
988 horzpos_t l_posHorz
= menuHorizontalPosition
;
989 coord_t xOffsetBind
= MODEL_SETUP_BIND_OFS
;
990 if (IS_MODULE_XJT(moduleIdx
) && g_model
.moduleData
[moduleIdx
].rfProtocol
== RF_PROTO_D8
) {
992 lcdDrawTextAlignedLeft(y
, STR_RECEIVER
);
993 if (attr
) l_posHorz
+= 1;
996 lcdDrawTextAlignedLeft(y
, STR_RECEIVER_NUM
);
998 if (IS_MODULE_PXX(moduleIdx
) || IS_MODULE_DSM2(moduleIdx
) || IS_MODULE_MULTIMODULE(moduleIdx
)) {
1000 lcdDrawNumber(MODEL_SETUP_2ND_COLUMN
, y
, g_model
.header
.modelId
[moduleIdx
], (l_posHorz
==0 ? attr
: 0) | LEADING0
|LEFT
, 2);
1001 if (attr
&& l_posHorz
==0) {
1003 CHECK_INCDEC_MODELVAR_ZERO(event
, g_model
.header
.modelId
[moduleIdx
], MAX_RX_NUM(moduleIdx
));
1004 if (checkIncDec_Ret
) {
1005 modelHeaders
[g_eeGeneral
.currModel
].modelId
[moduleIdx
] = g_model
.header
.modelId
[moduleIdx
];
1008 if (s_editMode
==0 && event
==EVT_KEY_BREAK(KEY_ENTER
)) {
1009 checkModelIdUnique(g_eeGeneral
.currModel
, moduleIdx
);
1012 lcdDrawText(MODEL_SETUP_2ND_COLUMN
+xOffsetBind
, y
, STR_MODULE_BIND
, l_posHorz
==1 ? attr
: 0);
1013 lcdDrawText(MODEL_SETUP_2ND_COLUMN
+MODEL_SETUP_RANGE_OFS
+xOffsetBind
, y
, STR_MODULE_RANGE
, l_posHorz
==2 ? attr
: 0);
1014 uint8_t newFlag
= 0;
1015 #if defined(MULTIMODULE)
1016 if (multiBindStatus
== MULTI_BIND_FINISHED
) {
1017 multiBindStatus
= MULTI_NORMAL_OPERATION
;
1021 if (attr
&& l_posHorz
>0) {
1023 if (l_posHorz
== 1) {
1024 if (IS_MODULE_R9M(moduleIdx
) || (IS_MODULE_XJT(moduleIdx
) && g_model
.moduleData
[moduleIdx
].rfProtocol
== RF_PROTO_X16
)) {
1025 if (event
== EVT_KEY_BREAK(KEY_ENTER
)) {
1026 uint8_t default_selection
;
1027 if (IS_MODULE_R9M_LBT(moduleIdx
)) {
1028 if (!IS_TELEMETRY_INTERNAL_MODULE())
1029 POPUP_MENU_ADD_ITEM(STR_BINDING_25MW_CH1_8_TELEM_ON
);
1030 POPUP_MENU_ADD_ITEM(STR_BINDING_25MW_CH1_8_TELEM_OFF
);
1031 POPUP_MENU_ADD_ITEM(STR_BINDING_500MW_CH1_8_TELEM_OFF
);
1032 POPUP_MENU_ADD_ITEM(STR_BINDING_500MW_CH9_16_TELEM_OFF
);
1033 default_selection
= 1;
1036 if (!(IS_TELEMETRY_INTERNAL_MODULE() && moduleIdx
== EXTERNAL_MODULE
))
1037 POPUP_MENU_ADD_ITEM(STR_BINDING_1_8_TELEM_ON
);
1038 POPUP_MENU_ADD_ITEM(STR_BINDING_1_8_TELEM_OFF
);
1039 if (!(IS_TELEMETRY_INTERNAL_MODULE() && moduleIdx
== EXTERNAL_MODULE
))
1040 POPUP_MENU_ADD_ITEM(STR_BINDING_9_16_TELEM_ON
);
1041 POPUP_MENU_ADD_ITEM(STR_BINDING_9_16_TELEM_OFF
);
1042 default_selection
= g_model
.moduleData
[moduleIdx
].pxx
.receiver_telem_off
+ (g_model
.moduleData
[moduleIdx
].pxx
.receiver_channel_9_16
<< 1);
1044 POPUP_MENU_SELECT_ITEM(default_selection
);
1045 POPUP_MENU_START(onBindMenu
);
1048 if (moduleFlag
[moduleIdx
] == MODULE_BIND
) {
1049 newFlag
= MODULE_BIND
;
1052 if (!popupMenuNoItems
) {
1053 s_editMode
= 0; // this is when popup is exited before a choice is made
1058 newFlag
= MODULE_BIND
;
1061 else if (l_posHorz
== 2) {
1062 newFlag
= MODULE_RANGECHECK
;
1066 moduleFlag
[moduleIdx
] = newFlag
;
1067 #if defined(MULTIMODULE)
1068 if (newFlag
== MODULE_BIND
)
1069 multiBindStatus
= MULTI_BIND_INITIATED
;
1076 case ITEM_MODEL_INTERNAL_MODULE_FAILSAFE
:
1077 case ITEM_MODEL_EXTERNAL_MODULE_FAILSAFE
:
1079 uint8_t moduleIdx
= CURRENT_MODULE_EDITED(k
);
1080 ModuleData
& moduleData
= g_model
.moduleData
[moduleIdx
];
1081 lcdDrawTextAlignedLeft(y
, STR_FAILSAFE
);
1082 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_VFAILSAFE
, moduleData
.failsafeMode
, menuHorizontalPosition
==0 ? attr
: 0);
1083 if (moduleData
.failsafeMode
== FAILSAFE_CUSTOM
) lcdDrawText(MODEL_SETUP_2ND_COLUMN
+ MODEL_SETUP_SET_FAILSAFE_OFS
, y
, STR_SET
, menuHorizontalPosition
==1 ? attr
: 0);
1085 if (moduleData
.failsafeMode
!= FAILSAFE_CUSTOM
) {
1086 menuHorizontalPosition
= 0;
1088 if (menuHorizontalPosition
== 0) {
1089 if (s_editMode
> 0) {
1090 CHECK_INCDEC_MODELVAR_ZERO(event
, moduleData
.failsafeMode
, FAILSAFE_LAST
);
1091 if (checkIncDec_Ret
) SEND_FAILSAFE_NOW(moduleIdx
);
1094 else if (menuHorizontalPosition
== 1) {
1096 if (moduleData
.failsafeMode
==FAILSAFE_CUSTOM
&& event
==EVT_KEY_FIRST(KEY_ENTER
)) {
1097 g_moduleIdx
= moduleIdx
;
1098 pushMenu(menuModelFailsafe
);
1102 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN
, y
, LCD_W
- MODEL_SETUP_2ND_COLUMN
- MENUS_SCROLLBAR_WIDTH
, 8);
1109 case ITEM_MODEL_EXTERNAL_MODULE_OPTIONS
:
1111 uint8_t moduleIdx
= CURRENT_MODULE_EDITED(k
);
1112 #if defined(MULTIMODULE)
1114 if (IS_MODULE_MULTIMODULE(moduleIdx
)) {
1115 int optionValue
= g_model
.moduleData
[moduleIdx
].multi
.optionValue
;
1117 const uint8_t multi_proto
= g_model
.moduleData
[EXTERNAL_MODULE
].getMultiProtocol(true);
1118 const mm_protocol_definition
*pdef
= getMultiProtocolDefinition(multi_proto
);
1119 if (pdef
->optionsstr
)
1120 lcdDrawText(INDENT_WIDTH
, y
, pdef
->optionsstr
);
1122 if (multi_proto
== MM_RF_PROTO_FS_AFHDS2A
)
1123 optionValue
= 50 + 5 * optionValue
;
1125 lcdDrawNumber(MODEL_SETUP_2ND_COLUMN
, y
, optionValue
, LEFT
| attr
);
1127 if (multi_proto
== MM_RF_PROTO_FS_AFHDS2A
) {
1128 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[moduleIdx
].multi
.optionValue
, 0, 70);
1130 else if (multi_proto
== MM_RF_PROTO_OLRS
) {
1131 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[moduleIdx
].multi
.optionValue
, -1, 7);
1134 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[moduleIdx
].multi
.optionValue
, -128, 127);
1139 if (IS_MODULE_R9M_FCC(moduleIdx
)) {
1140 if (IS_TELEMETRY_INTERNAL_MODULE()) {
1141 lcdDrawTextAlignedLeft(y
, STR_MODULE_TELEMETRY
);
1142 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, STR_DISABLE_INTERNAL
);
1145 g_model
.moduleData
[moduleIdx
].pxx
.sport_out
= editCheckBox(g_model
.moduleData
[EXTERNAL_MODULE
].pxx
.sport_out
, MODEL_SETUP_2ND_COLUMN
, y
, STR_MODULE_TELEMETRY
, attr
, event
);
1148 else if (IS_MODULE_R9M_LBT(moduleIdx
)) {
1149 if (IS_TELEMETRY_INTERNAL_MODULE()) {
1150 lcdDrawTextAlignedLeft(y
, STR_MODULE_TELEMETRY
);
1151 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, STR_DISABLE_INTERNAL
);
1154 lcdDrawTextAlignedLeft(y
, STR_MODULE_TELEMETRY
);
1155 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, STR_BINDING_OPTION
);
1158 else if (IS_MODULE_SBUS(moduleIdx
)) {
1159 lcdDrawTextAlignedLeft(y
, STR_WARN_BATTVOLTAGE
);
1160 putsVolts(lcdLastRightPos
, y
, getBatteryVoltage(), attr
| PREC2
| LEFT
);
1165 case ITEM_MODEL_EXTERNAL_MODULE_POWER
:
1167 uint8_t moduleIdx
= CURRENT_MODULE_EDITED(k
);
1168 if (IS_MODULE_R9M_FCC(moduleIdx
)) {
1169 // Power selection is only available on R9M FCC
1170 lcdDrawTextAlignedLeft(y
, TR_MULTI_RFPOWER
);
1171 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN
, y
, STR_R9M_FCC_POWER_VALUES
, g_model
.moduleData
[moduleIdx
].pxx
.power
, LEFT
| attr
);
1173 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[moduleIdx
].pxx
.power
, 0, R9M_FCC_POWER_MAX
);
1175 #if defined (MULTIMODULE)
1176 else if (IS_MODULE_MULTIMODULE(moduleIdx
)) {
1177 g_model
.moduleData
[EXTERNAL_MODULE
].multi
.lowPowerMode
= editCheckBox(g_model
.moduleData
[EXTERNAL_MODULE
].multi
.lowPowerMode
, MODEL_SETUP_2ND_COLUMN
, y
, STR_MULTI_LOWPOWER
, attr
, event
);
1183 #if defined (MULTIMODULE)
1184 case ITEM_MODEL_EXTERNAL_MODULE_AUTOBIND
:
1185 if (g_model
.moduleData
[EXTERNAL_MODULE
].getMultiProtocol(true) == MM_RF_PROTO_DSM2
)
1186 g_model
.moduleData
[EXTERNAL_MODULE
].multi
.autoBindMode
= editCheckBox(g_model
.moduleData
[EXTERNAL_MODULE
].multi
.autoBindMode
, MODEL_SETUP_2ND_COLUMN
, y
, STR_MULTI_DSM_AUTODTECT
, attr
, event
);
1188 g_model
.moduleData
[EXTERNAL_MODULE
].multi
.autoBindMode
= editCheckBox(g_model
.moduleData
[EXTERNAL_MODULE
].multi
.autoBindMode
, MODEL_SETUP_2ND_COLUMN
, y
, STR_MULTI_AUTOBIND
, attr
, event
);
1190 case ITEM_MODEL_EXTERNAL_MODULE_STATUS
:
1192 lcdDrawTextAlignedLeft(y
, STR_MODULE_STATUS
);
1194 char statusText
[64];
1195 multiModuleStatus
.getStatusString(statusText
);
1196 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, statusText
);
1199 case ITEM_MODEL_EXTERNAL_MODULE_SYNCSTATUS
: {
1200 lcdDrawTextAlignedLeft(y
, STR_MODULE_SYNC
);
1202 char statusText
[64];
1203 multiSyncStatus
.getRefreshString(statusText
);
1204 lcdDrawText(MODEL_SETUP_2ND_COLUMN
, y
, statusText
);
1212 if (IS_RANGECHECK_ENABLE()) {
1213 showMessageBox("RSSI: ");
1214 lcdDrawNumber(16+4*FW
, 5*FH
, TELEMETRY_RSSI(), BOLD
);
1219 void menuModelFailsafe(event_t event
)
1221 static uint8_t maxNameLen
= 4;
1222 static int8_t lastModel
= g_eeGeneral
.currModel
;
1223 const coord_t barH
= (LCD_H
- FH
) / 8 - 1;
1224 const int lim
= (g_model
.extendedLimits
? (512 * LIMIT_EXT_PERCENT
/ 100) : 512) * 2;
1225 const uint8_t channelStart
= g_model
.moduleData
[g_moduleIdx
].channelsStart
;
1228 uint8_t colW
= LCD_W
;
1230 if (lastModel
!= g_eeGeneral
.currModel
) {
1231 lastModel
= g_eeGeneral
.currModel
;
1235 if (event
== EVT_KEY_LONG(KEY_ENTER
)) {
1239 g_model
.moduleData
[g_moduleIdx
].failsafeChannels
[menuVerticalPosition
] = channelOutputs
[menuVerticalPosition
+channelStart
];
1240 storageDirty(EE_MODEL
);
1243 SEND_FAILSAFE_NOW(g_moduleIdx
);
1246 int16_t & failsafe
= g_model
.moduleData
[g_moduleIdx
].failsafeChannels
[menuVerticalPosition
];
1247 if (failsafe
< FAILSAFE_CHANNEL_HOLD
)
1248 failsafe
= FAILSAFE_CHANNEL_HOLD
;
1249 else if (failsafe
== FAILSAFE_CHANNEL_HOLD
)
1250 failsafe
= FAILSAFE_CHANNEL_NOPULSE
;
1253 storageDirty(EE_MODEL
);
1255 SEND_FAILSAFE_NOW(g_moduleIdx
);
1259 SIMPLE_SUBMENU_NOTITLE(NUM_CHANNELS(g_moduleIdx
));
1262 if (NUM_CHANNELS(g_moduleIdx
) > 8) {
1264 colW
= LCD_W
/ cols
- 1;
1266 lcdDrawSolidVerticalLine(colW
, FH
, LCD_H
- FH
);
1269 lcdDrawTextAlignedCenter(0, FAILSAFESET
);
1273 for (uint8_t col
= 0; col
< cols
; col
++) {
1276 for (uint8_t line
= 0; line
< 8; line
++) {
1277 const int32_t channelValue
= channelOutputs
[ch
+channelStart
];
1278 int32_t failsafeValue
= g_model
.moduleData
[g_moduleIdx
].failsafeChannels
[8*col
+line
];
1279 uint8_t lenLabel
= ZLEN(g_model
.limitData
[ch
+channelStart
].name
);
1280 uint8_t barW
= colW
- FW
* maxNameLen
- FWNUM
* 3; // default bar width
1282 #if defined(PPM_UNIT_PERCENT_PREC1)
1287 // Channel name if present, number if not
1289 if (lenLabel
> maxNameLen
)
1290 maxNameLen
= lenLabel
;
1291 lcdDrawSizedText(x
- colW
, y
, g_model
.limitData
[ch
+channelStart
].name
, sizeof(g_model
.limitData
[ch
+channelStart
].name
), ZCHAR
| SMLSIZE
);
1294 putsChn(x
- colW
, y
, ch
+1, SMLSIZE
);
1298 LcdFlags flags
= TINSIZE
;
1299 if (menuVerticalPosition
== ch
) {
1302 if (failsafeValue
== FAILSAFE_CHANNEL_HOLD
|| failsafeValue
== FAILSAFE_CHANNEL_NOPULSE
) {
1307 CHECK_INCDEC_MODELVAR(event
, g_model
.moduleData
[g_moduleIdx
].failsafeChannels
[8*col
+line
], -lim
, +lim
);
1312 const coord_t xValue
= x
- barW
;
1313 if (failsafeValue
== FAILSAFE_CHANNEL_HOLD
) {
1314 lcdDrawText(xValue
, y
, STR_HOLD
, RIGHT
|flags
);
1317 else if (failsafeValue
== FAILSAFE_CHANNEL_NOPULSE
) {
1318 lcdDrawText(xValue
, y
, STR_NONE
, RIGHT
|flags
);
1322 #if defined(PPM_UNIT_US)
1323 lcdDrawNumber(xValue
, y
, PPM_CH_CENTER(ch
)+failsafeValue
/2, RIGHT
|flags
);
1324 #elif defined(PPM_UNIT_PERCENT_PREC1)
1325 lcdDrawNumber(xValue
, y
, calcRESXto1000(failsafeValue
), RIGHT
|PREC1
|flags
);
1327 lcdDrawNumber(xValue
, y
, calcRESXto1000(failsafeValue
)/10, RIGHT
|flags
);
1332 lcdDrawRect(x
- barW
, y
, barW
- 1, barH
);
1333 barW
= barW
/ 2 - 1;
1334 const coord_t lenChannel
= limit
<uint8_t>(1, (abs(channelValue
) * barW
+ lim
/ 2) / lim
, barW
);
1335 const coord_t lenFailsafe
= limit
<uint8_t>(1, (abs(failsafeValue
) * barW
+ lim
/ 2) / lim
, barW
);
1336 const coord_t barX
= x
- barW
- 2;
1337 const coord_t xChannel
= (channelValue
>= 0) ? barX
: barX
- lenChannel
+ 1;
1338 const coord_t xFailsafe
= (failsafeValue
> 0) ? barX
: barX
- lenFailsafe
+ 1;
1339 lcdDrawHorizontalLine(xChannel
, y
+1, lenChannel
, DOTTED
, 0);
1340 lcdDrawHorizontalLine(xChannel
, y
+2, lenChannel
, DOTTED
, 0);
1341 lcdDrawSolidHorizontalLine(xFailsafe
, y
+3, lenFailsafe
);
1342 lcdDrawSolidHorizontalLine(xFailsafe
, y
+4, lenFailsafe
);
1344 if (++ch
>= NUM_CHANNELS(g_moduleIdx
))