Merge companion and firmware notes, and get them from the server (#5530)
[opentx.git] / radio / src / gui / 212x64 / model_setup.cpp
blob45a632b8f4ba1e6e67fb88f723c1158ecfb37345
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
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.
21 #include "opentx.h"
23 uint8_t g_moduleIdx;
25 enum MenuModelSetupItems {
26 ITEM_MODEL_NAME,
27 ITEM_MODEL_BITMAP,
28 ITEM_MODEL_TIMER1,
29 ITEM_MODEL_TIMER1_NAME,
30 ITEM_MODEL_TIMER1_PERSISTENT,
31 ITEM_MODEL_TIMER1_MINUTE_BEEP,
32 ITEM_MODEL_TIMER1_COUNTDOWN_BEEP,
33 #if TIMERS > 1
34 ITEM_MODEL_TIMER2,
35 ITEM_MODEL_TIMER2_NAME,
36 ITEM_MODEL_TIMER2_PERSISTENT,
37 ITEM_MODEL_TIMER2_MINUTE_BEEP,
38 ITEM_MODEL_TIMER2_COUNTDOWN_BEEP,
39 #endif
40 #if TIMERS > 2
41 ITEM_MODEL_TIMER3,
42 ITEM_MODEL_TIMER3_NAME,
43 ITEM_MODEL_TIMER3_PERSISTENT,
44 ITEM_MODEL_TIMER3_MINUTE_BEEP,
45 ITEM_MODEL_TIMER3_COUNTDOWN_BEEP,
46 #endif
47 #if defined(PCBX9E)
48 ITEM_MODEL_TOP_LCD_TIMER,
49 #endif
50 ITEM_MODEL_EXTENDED_LIMITS,
51 ITEM_MODEL_EXTENDED_TRIMS,
52 ITEM_MODEL_DISPLAY_TRIMS,
53 ITEM_MODEL_TRIM_INC,
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,
62 #if defined(PCBX9E)
63 ITEM_MODEL_SWITCHES_WARNING2,
64 ITEM_MODEL_SWITCHES_WARNING3,
65 #endif
66 ITEM_MODEL_POTS_WARNING,
67 #if defined(PCBX9E)
68 ITEM_MODEL_POTS_WARNING2,
69 #endif
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,
82 #endif
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,
89 #endif
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,
95 ITEM_MODEL_SETUP_MAX
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;
148 else {
149 return;
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);
159 else
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);
170 else {
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) {
190 case 0:
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)
198 if (s_editMode>0) {
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);
206 #endif
207 break;
209 case 1:
210 qr.quot = checkIncDec(event, qr.quot, 0, 1439, EE_MODEL | NO_INCDEC_MARKS); // 23h59
211 timer.start = qr.rem + qr.quot*60;
212 break;
213 case 2:
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
218 break;
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) {
234 case 0:
235 CHECK_INCDEC_MODELVAR(event, timer.countdownBeep, COUNTDOWN_SILENT, COUNTDOWN_COUNT - 1);
236 break;
237 case 1:
238 timer.countdownStart = -checkIncDecModel(event, -timer.countdownStart, -1, +2);
239 break;
244 int getSwitchWarningsCount()
246 int count = 0;
247 for (int i=0; i<NUM_SWITCHES; ++i) {
248 if (SWITCH_WARNING_ALLOWED(i)) {
249 ++count;
252 return count;
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
258 #else
259 #define INTERNAL_MODULE_MODE_ROWS 0 // (OFF / RF protocols)
260 #endif
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)
269 #else
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)
272 #endif
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
278 #if TIMERS == 1
279 #define TIMERS_ROWS TIMER_ROWS(0)
280 #elif TIMERS == 2
281 #define TIMERS_ROWS TIMER_ROWS(0), TIMER_ROWS(1)
282 #elif TIMERS == 3
283 #define TIMERS_ROWS TIMER_ROWS(0), TIMER_ROWS(1), TIMER_ROWS(2)
284 #endif
285 #if defined(PCBX9E)
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,
289 #else
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)
292 #define TOPLCD_ROWS
293 #endif
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});
316 #else
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});
335 #endif
336 MENU_CHECK(STR_MENUSETUP, menuTabModel, MENU_MODEL_SETUP, ITEM_MODEL_SETUP_MAX);
338 #if (defined(DSM2) || defined(PXX))
339 if (menuEvent) {
340 moduleFlag[0] = 0;
341 moduleFlag[1] = 0;
343 #endif
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)
352 k++;
355 LcdFlags blink = ((s_editMode>0) ? BLINK|INVERS : INVERS);
356 LcdFlags attr = (sub == k ? blink : 0);
358 switch(k) {
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));
362 break;
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);
368 else
369 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VCSWFUNC, 0, attr);
370 if (attr && event==EVT_KEY_BREAK(KEY_ENTER) && READ_ONLY_UNLOCKED()) {
371 s_editMode = 0;
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);
375 else {
376 POPUP_WARNING(STR_NO_BITMAPS_ON_SD);
379 break;
381 case ITEM_MODEL_TIMER1:
382 editTimerMode(0, y, attr, event);
383 break;
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);
387 break;
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);
391 break;
393 case ITEM_MODEL_TIMER1_COUNTDOWN_BEEP:
394 editTimerCountdown(0, y, attr, event);
395 break;
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);
399 break;
401 #if TIMERS > 1
402 case ITEM_MODEL_TIMER2:
403 editTimerMode(1, y, attr, event);
404 break;
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);
408 break;
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);
412 break;
414 case ITEM_MODEL_TIMER2_COUNTDOWN_BEEP:
415 editTimerCountdown(1, y, attr, event);
416 break;
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);
420 break;
421 #endif
423 #if TIMERS > 2
424 case ITEM_MODEL_TIMER3:
425 editTimerMode(2, y, attr, event);
426 break;
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);
430 break;
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);
434 break;
436 case ITEM_MODEL_TIMER3_COUNTDOWN_BEEP:
437 editTimerCountdown(2, y, attr, event);
438 break;
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);
442 break;
443 #endif
445 #if defined(PCBX9E)
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);
449 if (attr) {
450 g_model.toplcdTimer = checkIncDec(event, g_model.toplcdTimer, 0, TIMERS-1, EE_MODEL);
452 break;
453 #endif
455 case ITEM_MODEL_EXTENDED_LIMITS:
456 ON_OFF_MENU_ITEM(g_model.extendedLimits, MODEL_SETUP_2ND_COLUMN, y, STR_ELIMITS, attr, event);
457 break;
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) {
463 s_editMode = 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);
470 AUDIO_WARNING1();
473 break;
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);
477 break;
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);
481 break;
483 case ITEM_MODEL_THROTTLE_LABEL:
484 lcdDrawTextAlignedLeft(y, STR_THROTTLE_LABEL);
485 break;
487 case ITEM_MODEL_THROTTLE_REVERSED:
488 ON_OFF_MENU_ITEM(g_model.throttleReversed, MODEL_SETUP_2ND_COLUMN, y, STR_THROTTLEREVERSE, attr, event ) ;
489 break;
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)
497 idx += 1;
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);
501 break;
504 case ITEM_MODEL_THROTTLE_TRIM:
505 ON_OFF_MENU_ITEM(g_model.thrTrim, MODEL_SETUP_2ND_COLUMN, y, STR_TTRIM, attr, event);
506 break;
508 case ITEM_MODEL_PREFLIGHT_LABEL:
509 lcdDrawTextAlignedLeft(y, STR_PREFLIGHT);
510 break;
512 case ITEM_MODEL_CHECKLIST_DISPLAY:
513 ON_OFF_MENU_ITEM(g_model.displayChecklist, MODEL_SETUP_2ND_COLUMN, y, STR_CHECKLIST, attr, event);
514 break;
516 case ITEM_MODEL_THROTTLE_WARNING:
517 g_model.disableThrottleWarning = !editCheckBox(!g_model.disableThrottleWarning, MODEL_SETUP_2ND_COLUMN, y, STR_THROTTLEWARNING, attr, event);
518 break;
520 #if defined(PCBX9E)
521 case ITEM_MODEL_SWITCHES_WARNING2:
522 case ITEM_MODEL_SWITCHES_WARNING3:
523 case ITEM_MODEL_POTS_WARNING2:
524 if (i==0) {
525 if (CURSOR_MOVED_LEFT(event))
526 menuVerticalOffset--;
527 else
528 menuVerticalOffset++;
530 break;
531 #endif
533 case ITEM_MODEL_SWITCHES_WARNING:
535 #if defined(PCBX9E)
536 if (i>=NUM_BODY_LINES-2 && getSwitchWarningsCount() > 8*(NUM_BODY_LINES-i)) {
537 if (CURSOR_MOVED_LEFT(event))
538 menuVerticalOffset--;
539 else
540 menuVerticalOffset++;
541 break;
543 #endif
544 lcdDrawTextAlignedLeft(y, STR_SWITCHWARNING);
545 swarnstate_t states = g_model.switchWarningState;
546 char c;
547 if (attr) {
548 s_editMode = 0;
549 if (!READ_ONLY()) {
550 switch (event) {
551 case EVT_KEY_BREAK(KEY_ENTER):
552 break;
554 case EVT_KEY_LONG(KEY_ENTER):
555 if (menuHorizontalPosition < 0) {
556 START_NO_HIGHLIGHT();
557 getMovedSwitch();
558 g_model.switchWarningState = switches_states;
559 AUDIO_WARNING1();
560 storageDirty(EE_MODEL);
562 killEvents(event);
563 break;
568 LcdFlags line = attr;
570 int current = 0;
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);
582 ++current;
584 states >>= 2;
586 if (attr && menuHorizontalPosition < 0) {
587 #if defined(PCBX9E)
588 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-1, y-1, 8*(2*FW+1), 1+FH*((current+7)/8));
589 #else
590 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-1, y-1, current*(2*FW+1), FH+1);
591 #endif
593 break;
596 case ITEM_MODEL_POTS_WARNING:
597 #if defined(PCBX9E)
598 if (i==NUM_BODY_LINES-1 && g_model.potsWarnMode) {
599 if (CURSOR_MOVED_LEFT(event))
600 menuVerticalOffset--;
601 else
602 menuVerticalOffset++;
603 break;
605 #endif
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);
614 if (attr) {
615 if (menuHorizontalPosition > 0) s_editMode = 0;
616 if (!READ_ONLY() && menuHorizontalPosition > 0) {
617 switch (event) {
618 case EVT_KEY_LONG(KEY_ENTER):
619 killEvents(event);
620 if (g_model.potsWarnMode == POTS_WARN_MANUAL) {
621 SAVE_POT_POSITION(menuHorizontalPosition-1);
622 AUDIO_WARNING1();
623 storageDirty(EE_MODEL);
625 break;
626 case EVT_KEY_BREAK(KEY_ENTER):
627 g_model.potsWarnEnabled ^= (1 << (menuHorizontalPosition-1));
628 storageDirty(EE_MODEL);
629 break;
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();
639 else {
640 #if defined(PCBX9E)
641 if (i == NUM_XPOTS) {
642 y += FH;
643 x = MODEL_SETUP_2ND_COLUMN;
645 #endif
646 LcdFlags flags = ((menuHorizontalPosition==i+1) && attr) ? BLINK : 0;
647 if ((!attr || menuHorizontalPosition >= 0) && !(g_model.potsWarnEnabled & (1 << i))) {
648 flags |= INVERS;
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);
653 x = lcdNextPos+3;
657 if (attr && menuHorizontalPosition < 0) {
658 #if defined(PCBX9E)
659 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-1, y-FH-1, LCD_W-MODEL_SETUP_2ND_COLUMN-MENUS_SCROLLBAR_WIDTH+1, 2*FH+1);
660 #else
661 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-1, y-1, LCD_W-MODEL_SETUP_2ND_COLUMN-MENUS_SCROLLBAR_WIDTH+1, FH+1);
662 #endif
664 break;
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();
673 continue;
675 lcdDrawTextAtIndex(x, y, STR_RETA123, i, ((menuHorizontalPosition==i) && attr) ? BLINK|INVERS : (((g_model.beepANACenter & ((BeepANACenter)1<<i)) || (attr && CURSOR_ON_LINE())) ? INVERS : 0 ) );
676 x += FW;
678 if (attr && CURSOR_ON_CELL) {
679 if (event==EVT_KEY_BREAK(KEY_ENTER)) {
680 if (READ_ONLY_UNLOCKED()) {
681 s_editMode = 0;
682 g_model.beepANACenter ^= ((BeepANACenter)1<<menuHorizontalPosition);
683 storageDirty(EE_MODEL);
687 break;
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);
694 break;
696 case ITEM_MODEL_INTERNAL_MODULE_LABEL:
697 lcdDrawTextAlignedLeft(y, TR_INTERNALRF);
698 break;
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) {
708 case 0:
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);;
715 break;
716 case 1:
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);
724 break;
725 #else
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);
729 if (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;
740 break;
741 #endif
742 case ITEM_MODEL_TRAINER_MODE:
743 lcdDrawTextAlignedLeft(y, STR_MODE);
744 lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VTRAINERMODES, g_model.trainerMode, attr);
745 if (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;
753 #endif
754 break;
756 case ITEM_MODEL_EXTERNAL_MODULE_LABEL:
757 lcdDrawTextAlignedLeft(y, TR_EXTERNALRF);
758 break;
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);
779 else {
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);
787 #endif
788 if (attr && s_editMode>0) {
789 switch (menuHorizontalPosition) {
790 case 0:
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;
799 break;
800 case 1:
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;
818 else {
819 g_model.moduleData[EXTERNAL_MODULE].multi.autoBindMode = 0;
821 g_model.moduleData[EXTERNAL_MODULE].multi.optionValue = 0;
824 #endif
825 else {
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);
832 break;
833 #if defined(MULTIMODULE)
834 case 2:
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));
837 break;
839 else {
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);
844 break;
845 case 3:
846 // Custom protocol, third column is subtype
847 CHECK_INCDEC_MODELVAR(event, g_model.moduleData[EXTERNAL_MODULE].subType, 0, 7);
848 break;
849 #endif
852 break;
854 case ITEM_MODEL_TRAINER_LABEL:
855 lcdDrawTextAlignedLeft(y, STR_TRAINER);
856 break;
858 #if defined(BLUETOOTH) && defined(USEHORUSBT)
859 case ITEM_MODEL_TRAINER_LINE1:
860 if (g_model.trainerMode == TRAINER_MODE_MASTER_BLUETOOTH) {
861 if (attr) {
862 s_editMode = 0;
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);
870 else {
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;
878 else {
879 bluetoothState = BLUETOOTH_STATE_BIND_REQUESTED;
883 else {
884 lcdDrawText(INDENT_WIDTH, y, "---");
885 if (bluetoothState < BLUETOOTH_STATE_IDLE)
886 lcdDrawText(MODEL_SETUP_2ND_COLUMN, y, BUTTON("Init"), attr);
887 else
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;
892 else
893 bluetoothState = BLUETOOTH_STATE_DISCOVER_REQUESTED;
896 break;
898 // no break
899 #else
900 case ITEM_MODEL_TRAINER_LINE1:
901 #endif
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)");
916 else
917 lcdDrawText(lcdLastRightPos+5, y, "(9ms)");
919 if (attr && s_editMode>0) {
920 switch (menuHorizontalPosition) {
921 case 0:
922 CHECK_INCDEC_MODELVAR_ZERO(event, moduleData.channelsStart, 32-8-moduleData.channelsCount);
923 break;
924 case 1:
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);
930 #else
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);
934 #endif
935 break;
939 break;
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) {
958 case 0:
959 CHECK_INCDEC_MODELVAR(event, moduleData.ppm.frameLength, -20, 35);
960 break;
961 case 1:
962 CHECK_INCDEC_MODELVAR(event, moduleData.ppm.delay, -4, 10);
963 break;
964 case 2:
965 CHECK_INCDEC_MODELVAR_ZERO(event, moduleData.ppm.pulsePol, 1);
966 break;
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) {
978 case 0:
979 CHECK_INCDEC_MODELVAR(event, moduleData.ppm.frameLength, -33, 35);
980 break;
981 case 1:
982 CHECK_INCDEC_MODELVAR_ZERO(event, moduleData.sbus.noninverted, 1);
983 break;
987 else {
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) {
991 xOffsetBind = 0;
992 lcdDrawTextAlignedLeft(y, STR_RECEIVER);
993 if (attr) l_posHorz += 1;
995 else {
996 lcdDrawTextAlignedLeft(y, STR_RECEIVER_NUM);
998 if (IS_MODULE_PXX(moduleIdx) || IS_MODULE_DSM2(moduleIdx) || IS_MODULE_MULTIMODULE(moduleIdx)) {
999 if (xOffsetBind)
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) {
1002 if (s_editMode>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;
1018 s_editMode = 0;
1020 #endif
1021 if (attr && l_posHorz>0) {
1022 if (s_editMode>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;
1035 else {
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);
1046 continue;
1048 if (moduleFlag[moduleIdx] == MODULE_BIND) {
1049 newFlag = MODULE_BIND;
1051 else {
1052 if (!popupMenuNoItems) {
1053 s_editMode = 0; // this is when popup is exited before a choice is made
1057 else {
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;
1070 #endif
1073 break;
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);
1084 if (attr) {
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) {
1095 s_editMode = 0;
1096 if (moduleData.failsafeMode==FAILSAFE_CUSTOM && event==EVT_KEY_FIRST(KEY_ENTER)) {
1097 g_moduleIdx = moduleIdx;
1098 pushMenu(menuModelFailsafe);
1101 else {
1102 lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN, y, LCD_W - MODEL_SETUP_2ND_COLUMN - MENUS_SCROLLBAR_WIDTH, 8);
1105 break;
1107 break;
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);
1126 if (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);
1133 else {
1134 CHECK_INCDEC_MODELVAR(event, g_model.moduleData[moduleIdx].multi.optionValue, -128, 127);
1138 #endif
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);
1144 else {
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);
1153 else {
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);
1163 break;
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);
1172 if (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);
1179 #endif
1180 break;
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);
1187 else
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);
1189 break;
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);
1197 break;
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);
1205 break;
1207 #endif
1211 #if defined(PXX)
1212 if (IS_RANGECHECK_ENABLE()) {
1213 showMessageBox("RSSI: ");
1214 lcdDrawNumber(16+4*FW, 5*FH, TELEMETRY_RSSI(), BOLD);
1216 #endif
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;
1226 uint8_t ch = 0;
1227 uint8_t cols = 1;
1228 uint8_t colW = LCD_W;
1230 if (lastModel != g_eeGeneral.currModel) {
1231 lastModel = g_eeGeneral.currModel;
1232 maxNameLen = 4;
1235 if (event == EVT_KEY_LONG(KEY_ENTER)) {
1236 killEvents(event);
1237 event = 0;
1238 if (s_editMode) {
1239 g_model.moduleData[g_moduleIdx].failsafeChannels[menuVerticalPosition] = channelOutputs[menuVerticalPosition+channelStart];
1240 storageDirty(EE_MODEL);
1241 AUDIO_WARNING1();
1242 s_editMode = 0;
1243 SEND_FAILSAFE_NOW(g_moduleIdx);
1245 else {
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;
1251 else
1252 failsafe = 0;
1253 storageDirty(EE_MODEL);
1254 AUDIO_WARNING1();
1255 SEND_FAILSAFE_NOW(g_moduleIdx);
1259 SIMPLE_SUBMENU_NOTITLE(NUM_CHANNELS(g_moduleIdx));
1260 SET_SCROLLBAR_X(0);
1262 if (NUM_CHANNELS(g_moduleIdx) > 8) {
1263 cols = 2;
1264 colW = LCD_W / cols - 1;
1265 // Column separator
1266 lcdDrawSolidVerticalLine(colW, FH, LCD_H - FH);
1269 lcdDrawTextAlignedCenter(0, FAILSAFESET);
1270 lcdInvertLine(0);
1272 coord_t x = colW;
1273 for (uint8_t col = 0; col < cols; col++) {
1275 coord_t y = FH + 1;
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)
1283 barW -= FWNUM + 1;
1284 #endif
1285 barW += (barW % 2);
1287 // Channel name if present, number if not
1288 if (lenLabel > 0) {
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);
1293 else {
1294 putsChn(x - colW, y, ch+1, SMLSIZE);
1297 // Value
1298 LcdFlags flags = TINSIZE;
1299 if (menuVerticalPosition == ch) {
1300 flags |= INVERS;
1301 if (s_editMode) {
1302 if (failsafeValue == FAILSAFE_CHANNEL_HOLD || failsafeValue == FAILSAFE_CHANNEL_NOPULSE) {
1303 s_editMode = 0;
1305 else {
1306 flags |= BLINK;
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);
1315 failsafeValue = 0;
1317 else if (failsafeValue == FAILSAFE_CHANNEL_NOPULSE) {
1318 lcdDrawText(xValue, y, STR_NONE, RIGHT|flags);
1319 failsafeValue = 0;
1321 else {
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);
1326 #else
1327 lcdDrawNumber(xValue, y, calcRESXto1000(failsafeValue)/10, RIGHT|flags);
1328 #endif
1331 // Gauge
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))
1345 break;
1347 y += barH + 1;
1349 } // channels
1351 x += colW + 2;
1353 } // columns