diff --git a/src/config/config.c b/src/config/config.c
new file mode 100644
index 0000000000..98a4810d61
--- /dev/null
+++ b/src/config/config.c
@@ -0,0 +1,291 @@
+/*
+ This project is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Deviation is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Deviation. If not, see .
+ */
+
+#include "common.h"
+#include "gui/gui.h"
+#include "config.h"
+#include "display.h"
+#include "mixer.h"
+#include "tx.h"
+
+#include
+#include
+
+typedef const char* StringCallback(int value);
+typedef const char* StringCallback2(char* buffer, int value);
+
+extern u8 FONT_GetFromString(const char *);
+
+static u16 get_color(const char *value) {
+ u8 r, g, b;
+ u32 color = strtol(value, NULL, 16);
+ r = 0xff & (color >> 16);
+ g = 0xff & (color >> 8);
+ b = 0xff & (color >> 0);
+ return RGB888_to_RGB565(r, g, b);
+}
+
+static u8 get_button(const char *value)
+{
+ u8 i;
+ for (i = 0; i <= NUM_TX_BUTTONS; i++) {
+ if (strcasecmp(INPUT_ButtonName(i), value) == 0) {
+ return i;
+ }
+ }
+ printf("Could not parse Button %s\n", value);
+ return 0;
+}
+
+
+static s8 mapstrcasecmp(const char *s1, const char *s2)
+{
+ int i = 0;
+ while (1) {
+ if (s1[i] == s2[i]
+ || (s2[i] >= 'a' && s1[i] + ('a'-'A') == s2[i])
+ || (s1[i] >= 'a' && s2[i] + ('a'-'A') == s1[i])
+ || (s2[i] == ' ' && s1[i] == '_')
+ || (s1[i] == ' ' && s2[i] == '_'))
+ {
+ if (s1[i] == '\0')
+ return 0;
+ i++;
+ continue;
+ }
+ return (s1[i] < s2[i] ? -1 : 1);
+ }
+}
+
+u8 get_source(const char *value)
+{
+ unsigned i;
+ unsigned val;
+ const char *ptr = (value[0] == '!') ? value + 1 : value;
+ const char *tmp;
+ char cmp[10];
+ for (i = 0; i <= NUM_SOURCES; i++) {
+ if (mapstrcasecmp(INPUT_SourceNameReal(cmp, i), ptr) == 0) {
+ #if defined(HAS_SWITCHES_NOSTOCK) && HAS_SWITCHES_NOSTOCK
+ #define SWITCH_NOSTOCK ((1 << INP_HOLD0) | (1 << INP_HOLD1) | \
+ (1 << INP_FMOD0) | (1 << INP_FMOD1))
+ if ((Transmitter.ignore_src & SWITCH_NOSTOCK) == SWITCH_NOSTOCK) {
+ if (mapstrcasecmp("FMODE0", ptr) == 0 ||
+ mapstrcasecmp("FMODE1", ptr) == 0 ||
+ mapstrcasecmp("HOLD0", ptr) == 0 ||
+ mapstrcasecmp("HOLD1", ptr) == 0)
+ break;
+ }
+ #endif // HAS_SWITCHES_NOSTOCK
+ return ((ptr == value) ? 0 : 0x80) | i;
+ }
+ }
+ for (i = 0; i < 4; i++) {
+ if (mapstrcasecmp(tx_stick_names[i], ptr) == 0) {
+ return ((ptr == value) ? 0 : 0x80) | (i + 1);
+ }
+ }
+ i = 0;
+ while ((tmp = INPUT_MapSourceName(i++, &val))) {
+ if (mapstrcasecmp(tmp, ptr) == 0) {
+ return ((ptr == value) ? 0 : 0x80) | val;
+ }
+ }
+ printf("Could not parse Source %s\n", value);
+ return 0;
+}
+
+int assign_int(void* ptr, const struct struct_map *map, int map_size, const char* name, const char* value) {
+ for (int i = 0; i < map_size; i++) {
+ if (strcasecmp(name, map[i].str) == 0) {
+ int size = map[i].offset >> 12;
+ int offset = map[i].offset & 0xFFF;
+ switch (size) {
+ case TYPE_S8:
+ *((s8 *)((u8*)ptr + offset)) = atoi(value); break;
+ case TYPE_S16:
+ *((s16 *)((u8*)ptr + offset)) = atoi(value); break;
+ case TYPE_S32:
+ *((s32 *)((u8*)ptr + offset)) = atoi(value); break;
+
+ case TYPE_U8:
+ *((u8 *)((u8*)ptr + offset)) = atoi(value); break;
+ case TYPE_U16:
+ *((u16 *)((u8*)ptr + offset)) = atoi(value); break;
+ case TYPE_U32:
+ *((u32 *)((u8*)ptr + offset)) = atoi(value); break;
+
+ case TYPE_COLOR:
+ *((u16 *)((u8*)ptr + offset)) = get_color(value); break;
+ case TYPE_FONT:
+ *((u8 *)((u8*)ptr + offset)) = FONT_GetFromString(value); break;
+ case TYPE_BUTTON:
+ *((u8 *)((u8*)ptr + offset)) = get_button(value); break;
+ case TYPE_SOURCE:
+ *((u8 *)((u8*)ptr + offset)) = get_source(value); break;
+
+ case TYPE_STR_LIST: {
+ // get the list
+ i++; // next entry is additional info for string list
+ const char* const *list = (const char* const *)map[i].str;
+ unsigned length = map[i].offset;
+ for (unsigned j = 0; j < length; j++) {
+ if (list[j] && strcasecmp(value, list[j]) == 0) {
+ *((u8 *)((u8*)ptr + offset)) = j;
+ return 1;
+ }
+ }
+ printf("unknow value %s for %s\n", value, name);
+ break;
+ }
+ case TYPE_STR_CALL: {
+ i++;
+ unsigned j;
+ StringCallback *callback = (StringCallback*)map[i].str;
+
+ // If the length is larger than 256, upper part is start index
+ if (map[i].offset & 0xFF00) {
+ j = (map[i].offset & 0xFF00) >> 8;
+ } else {
+ j = 0;
+ }
+
+ for (; j < (map[i].offset & 0xFF); j++)
+ {
+ if (mapstrcasecmp(value, callback(j)) == 0) {
+ *((u8 *)((u8*)ptr + offset)) = j;
+ return 1;
+ }
+ }
+ printf("unknow value %s for %s\n", value, name);
+ break;
+ }
+ case TYPE_STR_CALL2: {
+ i++;
+ char strbuf[30];
+ unsigned j;
+ StringCallback2 *callback = (StringCallback2*)map[i].str;
+ // If the length is larger than 256, upper part is start index
+ if (map[i].offset & 0xFF00) {
+ j = (map[i].offset & 0xFF00) >> 8;
+ } else {
+ j = 0;
+ }
+ for (; j < (map[i].offset & 0xFF); j++)
+ {
+ if (mapstrcasecmp(value, callback(strbuf, j)) == 0) {
+ *((u8 *)((u8*)ptr + offset)) = j;
+ return 1;
+ }
+ }
+ printf("unknow value %s for %s\n", value, name);
+ break;
+ }
+ default:
+ printf("Unknown type: %d\n", size);
+ break;
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int write_int2(void* ptr, const struct struct_map *map, int map_size,
+ const u16* defaults, int defaults_size, FILE* fh) {
+ for (int i = 0; i < map_size; i++) {
+ int size = map[i].offset >> 12;
+ int offset = map[i].offset & 0xFFF;
+ int value;
+
+ switch (size) {
+ case TYPE_S8:
+ value = *((s8 *)((u8*)ptr + offset)); break;
+ case TYPE_S16:
+ value = *((s16 *)((u8*)ptr + offset)); break;
+ case TYPE_S32:
+ value = *((s32 *)((u8*)ptr + offset)); break;
+
+ case TYPE_U8:
+ value = *((u8 *)((u8*)ptr + offset)); break;
+ case TYPE_U16:
+ value = *((u16 *)((u8*)ptr + offset)); break;
+ case TYPE_U32:
+ value = *((u32 *)((u8*)ptr + offset)); break;
+
+ case TYPE_STR_LIST:
+ case TYPE_STR_CALL:
+ case TYPE_STR_CALL2:
+ i++; // next entry is additional info for string list
+ value = *((u8 *)((u8*)ptr + offset));
+ break;
+
+ case TYPE_BUTTON:
+ case TYPE_SOURCE:
+ value = *((u8 *)((u8*)ptr + offset)); break;
+
+ default:
+ continue; // unsupported type
+ }
+
+ if (defaults == DEFAULTS_ZERO) {
+ if (value == 0) continue;
+ } else if (defaults == DEFAULTS_ALWAYS) {
+ // always write out
+ } else if (defaults != NULL) {
+ if (i < defaults_size && value == defaults[i]) continue;
+ }
+
+ switch (size)
+ {
+ case TYPE_STR_LIST: {
+ const char* const *list = (const char* const *)map[i].str;
+ fprintf(fh, "%s=%s\n", map[i - 1].str, list[value]);
+ break;
+ }
+ case TYPE_BUTTON:
+ fprintf(fh, "%s=%s\n", map[i].str, INPUT_ButtonName(value));
+ break;
+ case TYPE_SOURCE: {
+ char tmpstr[20];
+ fprintf(fh, "%s=%s\n", map[i].str, INPUT_SourceNameReal(tmpstr, value));
+ break;
+ }
+ case TYPE_STR_CALL: {
+ StringCallback *callback = (StringCallback*)map[i].str;
+ fprintf(fh, "%s=%s\n", map[i - 1].str, callback(value));
+ break;
+ }
+ case TYPE_STR_CALL2: {
+ char strbuf[30];
+ StringCallback2 *callback = (StringCallback2*)map[i].str;
+ fprintf(fh, "%s=%s\n", map[i - 1].str, callback(strbuf, value));
+ break;
+ }
+ default:
+ fprintf(fh, "%s=%d\n", map[i].str, value);
+ }
+ }
+
+ return 1;
+}
+
+int write_int(void* ptr, const struct struct_map *map, int map_size, FILE* fh) {
+ return write_int2(ptr, map, map_size, DEFAULTS_ALWAYS, 0, fh);
+}
+
+#define TESTNAME config
+#include "tests.h"
+
diff --git a/src/config/config.h b/src/config/config.h
new file mode 100644
index 0000000000..fdc0838328
--- /dev/null
+++ b/src/config/config.h
@@ -0,0 +1,64 @@
+/*
+ This project is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Deviation is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Deviation. If not, see .
+ */
+
+#ifndef __CONFIG_H_
+#define __CONFIG_H_
+
+#include
+
+enum {
+ TYPE_U8 = 0,
+ TYPE_U16 = 1,
+ TYPE_U32 = 3,
+
+ TYPE_S8 = 4,
+ TYPE_S16 = 5,
+ TYPE_S32 = 7,
+
+ TYPE_STR_LIST = 8,
+ TYPE_STR_CALL = 9,
+ TYPE_STR_CALL2 = 10,
+
+ TYPE_COLOR = 11,
+ TYPE_FONT = 12,
+ TYPE_SOURCE = 13,
+ TYPE_BUTTON = 14,
+};
+
+struct struct_map {const char *str; u16 offset;};
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+#define OFFSET(s, v) (offsetof(s, v) | ((sizeof(((s*)0)->v) - 1) << 12))
+#define OFFSETS(s, v) (offsetof(s, v) | ((sizeof(((s*)0)->v) + 3) << 12))
+#define OFFSET_COL(s, v) (offsetof(s, v) | (TYPE_COLOR << 12))
+#define OFFSET_FON(s, v) (offsetof(s, v) | (TYPE_FONT << 12))
+#define OFFSET_STRLIST(s, v, StrList, StrListSize) \
+ (offsetof(s, v) | (TYPE_STR_LIST << 12)) }, { (const char*)StrList, StrListSize
+#define OFFSET_STRCALL(s, v, StrCallback, N) \
+ (offsetof(s, v) | (TYPE_STR_CALL << 12)) }, { (const char*)StrCallback, N
+#define OFFSET_STRCALL2(s, v, StrCallback, N) \
+ (offsetof(s, v) | (TYPE_STR_CALL2 << 12)) }, { (const char*)StrCallback, N
+#define OFFSET_SRC(s, v) (offsetof(s, v) | (TYPE_SOURCE << 12))
+#define OFFSET_BUT(s, v) (offsetof(s, v) | (TYPE_BUTTON << 12))
+
+int assign_int(void* ptr, const struct struct_map *map, int map_size,
+ const char* name, const char* value);
+int write_int(void* ptr, const struct struct_map *map, int map_size, FILE* fh);
+
+#define DEFAULTS_ZERO (const u16*)0x01
+#define DEFAULTS_ALWAYS (const u16*)0x02
+int write_int2(void* ptr, const struct struct_map *map, int map_size,
+ const u16* defaults, int defaults_size, FILE* fh);
+
+#endif
diff --git a/src/config/display.c b/src/config/display.c
index 7b6e0f033d..723c5c9771 100644
--- a/src/config/display.c
+++ b/src/config/display.c
@@ -15,6 +15,7 @@
#include "common.h"
#include "gui/gui.h"
+#include "config.h"
#include "display.h"
#include
#include
@@ -86,132 +87,83 @@ static const char * const ALIGN_VAL[] = {
#define MATCH_START(x,y) strncasecmp(x, y, sizeof(y)-1) == 0
#define MATCH_KEY(s) strcasecmp(name, s) == 0
#define MATCH_VALUE(s) strcasecmp(value, s) == 0
-#define NUM_STR_ELEMS(s) (sizeof(s) / sizeof(char *))
#define SET_FLAG(var, value, flag) ((value) ? ((var) | (flag)) : ((var) & ~(flag)))
-extern u8 FONT_GetFromString(const char *);
-struct display_settings Display;
-u16 get_color(const char *value) {
- u8 r, g, b;
- u32 color = strtol(value, NULL, 16);
- r = 0xff & (color >> 16);
- g = 0xff & (color >> 8);
- b = 0xff & (color >> 0);
- return RGB888_to_RGB565(r, g, b);
-}
+struct display_settings Display;
-static int handle_label(struct LabelDesc *label, const char *name, const char *value)
+static const struct struct_map _seclabel[] =
{
- if(MATCH_KEY(FONT)) {
- label->font = FONT_GetFromString(value);
- return 1;
- }
- if(MATCH_KEY(FONT_COLOR)) {
- label->font_color = get_color(value);
- return 1;
- }
- if(MATCH_KEY(BG_COLOR)) {
- label->fill_color = get_color(value);
- return 1;
- }
- if(MATCH_KEY(OUTLINE_COLOR)) {
- label->outline_color = get_color(value);
- return 1;
- }
- if(MATCH_KEY(BOX)) {
- u8 idx;
- for (idx = 0; idx < NUM_STR_ELEMS(BOX_VAL); idx++) {
- if(BOX_VAL[idx] && MATCH_VALUE(BOX_VAL[idx])) {
- label->style = idx;
- // For compatibility reasons,
- // use the old alignment values as default
- // if the new one is not yet set:
- if(label->align == 0) {
- switch(idx) {
- case LABEL_CENTER: label->align = ALIGN_CENTER; break;
- case LABEL_LEFT: label->align = ALIGN_LEFT; break;
- case LABEL_RIGHT: label->align = ALIGN_RIGHT; break;
- }
- }
- }
- }
- }
- if(MATCH_KEY(ALIGN)) {
- u8 idx;
- for (idx = 0; idx < NUM_STR_ELEMS(ALIGN_VAL); idx++) {
- if(ALIGN_VAL[idx] && MATCH_VALUE(ALIGN_VAL[idx])) {
- label->align = idx;
- }
- }
- }
- return 0;
-}
+ {FONT, OFFSET_FON(struct LabelDesc, font)},
+ {FONT_COLOR, OFFSET_COL(struct LabelDesc, font_color)},
+ {BG_COLOR, OFFSET_COL(struct LabelDesc, fill_color)},
+ {OUTLINE_COLOR, OFFSET_COL(struct LabelDesc, outline_color)},
+ {ALIGN, OFFSET_STRLIST(struct LabelDesc, align, ALIGN_VAL, ARRAYSIZE(ALIGN_VAL))},
+ {BOX, OFFSET_STRLIST(struct LabelDesc, style, BOX_VAL, ARRAYSIZE(BOX_VAL))}
+};
-struct struct_map {const char *str; u16 offset;};
-#define MAPSIZE(x) (sizeof(x) / sizeof(struct struct_map))
-#define OFFSET(s,v) (((long)(&s.v) - (long)(&s)) | ((sizeof(s.v)-1) << 13))
-#define OFFSETS(s,v) (((long)(&s.v) - (long)(&s)) | ((sizeof(s.v)+3) << 13))
-#define OFFSET_COL(s,v) (((long)(&s.v) - (long)(&s)) | (2 << 13))
-#define OFFSET_FON(s,v) (((long)(&s.v) - (long)(&s)) | (6 << 13))
static const struct struct_map _secgeneral[] =
{
- {"header_height", OFFSET(Display.metrics, header_height)},
- {"header_widget_height", OFFSET(Display.metrics, header_widget_height)},
- {"line_height", OFFSET(Display.metrics, line_height)},
- {"line_space", OFFSET(Display.metrics, line_space)},
+ {"header_height", OFFSET(struct disp_metrics, header_height)},
+ {"header_widget_height", OFFSET(struct disp_metrics, header_widget_height)},
+ {"line_height", OFFSET(struct disp_metrics, line_height)},
+ {"line_space", OFFSET(struct disp_metrics, line_space)},
};
static const struct struct_map _secselect[] =
{
- {"width", OFFSET(Display, select_width)},
- {COLOR, OFFSET_COL(Display, select_color)},
+ {"width", OFFSET(struct display_settings, select_width)},
+ {COLOR, OFFSET_COL(struct display_settings, select_color)},
};
static const struct struct_map _seckeybd[] =
{
- {FONT, OFFSET_FON(Display.keyboard, font)},
- {"bg_key1", OFFSET_COL(Display.keyboard, bg_key1)},
- {"fg_key1", OFFSET_COL(Display.keyboard, fg_key1)},
- {"bg_key2", OFFSET_COL(Display.keyboard, bg_key2)},
- {"fg_key2", OFFSET_COL(Display.keyboard, fg_key2)},
- {"bg_key3", OFFSET_COL(Display.keyboard, bg_key3)},
- {"fg_key3", OFFSET_COL(Display.keyboard, fg_key3)},
- {BG_COLOR, OFFSET_COL(Display.keyboard, fill_color)},
+ {FONT, OFFSET_FON(struct disp_keyboard, font)},
+ {"bg_key1", OFFSET_COL(struct disp_keyboard, bg_key1)},
+ {"fg_key1", OFFSET_COL(struct disp_keyboard, fg_key1)},
+ {"bg_key2", OFFSET_COL(struct disp_keyboard, bg_key2)},
+ {"fg_key2", OFFSET_COL(struct disp_keyboard, fg_key2)},
+ {"bg_key3", OFFSET_COL(struct disp_keyboard, bg_key3)},
+ {"fg_key3", OFFSET_COL(struct disp_keyboard, fg_key3)},
+ {BG_COLOR, OFFSET_COL(struct disp_keyboard, fill_color)},
};
static const struct struct_map _seclistbox[] =
{
- {FONT, OFFSET_FON(Display.listbox, font)},
- {BG_COLOR, OFFSET_COL(Display.listbox, bg_color)},
- {FG_COLOR, OFFSET_COL(Display.listbox, fg_color)},
- {"bg_select", OFFSET_COL(Display.listbox, bg_select)},
- {"fg_select", OFFSET_COL(Display.listbox, fg_select)},
+ {FONT, OFFSET_FON(struct disp_listbox, font)},
+ {BG_COLOR, OFFSET_COL(struct disp_listbox, bg_color)},
+ {FG_COLOR, OFFSET_COL(struct disp_listbox, fg_color)},
+ {"bg_select", OFFSET_COL(struct disp_listbox, bg_select)},
+ {"fg_select", OFFSET_COL(struct disp_listbox, fg_select)},
};
static const struct struct_map _secscroll[] =
{
- {BG_COLOR, OFFSET_COL(Display.scrollbar, bg_color)},
- {FG_COLOR, OFFSET_COL(Display.scrollbar, fg_color)},
+ {BG_COLOR, OFFSET_COL(struct disp_scrollbar, bg_color)},
+ {FG_COLOR, OFFSET_COL(struct disp_scrollbar, fg_color)},
};
static const struct struct_map _secxygraph[] =
{
- {BG_COLOR, OFFSET_COL(Display.xygraph, bg_color)},
- {FG_COLOR, OFFSET_COL(Display.xygraph, fg_color)},
- {XY_AXIS_COLOR, OFFSET_COL(Display.xygraph, axis_color)},
- {XY_GRID_COLOR, OFFSET_COL(Display.xygraph, grid_color)},
- {XY_POINT_COLOR, OFFSET_COL(Display.xygraph, point_color)},
- {OUTLINE_COLOR, OFFSET_COL(Display.xygraph, outline_color)},
+ {BG_COLOR, OFFSET_COL(struct disp_xygraph, bg_color)},
+ {FG_COLOR, OFFSET_COL(struct disp_xygraph, fg_color)},
+ {XY_AXIS_COLOR, OFFSET_COL(struct disp_xygraph, axis_color)},
+ {XY_GRID_COLOR, OFFSET_COL(struct disp_xygraph, grid_color)},
+ {XY_POINT_COLOR, OFFSET_COL(struct disp_xygraph, point_color)},
+ {OUTLINE_COLOR, OFFSET_COL(struct disp_xygraph, outline_color)},
};
static const struct struct_map _secbargraph[] =
{
- {BG_COLOR, OFFSET_COL(Display.bargraph, bg_color)},
- {FG_COLOR_POS, OFFSET_COL(Display.bargraph, fg_color_pos)},
- {FG_COLOR_NEG, OFFSET_COL(Display.bargraph, fg_color_neg)},
- {FG_COLOR_ZERO, OFFSET_COL(Display.bargraph, fg_color_zero)},
- {OUTLINE_COLOR, OFFSET_COL(Display.bargraph, outline_color)},
+ {BG_COLOR, OFFSET_COL(struct disp_bargraph, bg_color)},
+ {FG_COLOR_POS, OFFSET_COL(struct disp_bargraph, fg_color_pos)},
+ {FG_COLOR_NEG, OFFSET_COL(struct disp_bargraph, fg_color_neg)},
+ {FG_COLOR_ZERO, OFFSET_COL(struct disp_bargraph, fg_color_zero)},
+ {OUTLINE_COLOR, OFFSET_COL(struct disp_bargraph, outline_color)},
+};
+static const struct struct_map _secbargraph_legacy[] =
+{
+ {FG_COLOR, OFFSET_COL(struct disp_bargraph, fg_color_pos)},
};
#if (LCD_WIDTH == 480) || (LCD_WIDTH == 320)
static const struct struct_map _secbackground[] =
{
- {"drawn_background", OFFSET(Display.background, drawn_background)},
- {"bg_color", OFFSET_COL(Display.background, bg_color)},
- {"hd_color", OFFSET_COL(Display.background, hd_color)},
+ {"drawn_background", OFFSET(struct disp_background, drawn_background)},
+ {"bg_color", OFFSET_COL(struct disp_background, bg_color)},
+ {"hd_color", OFFSET_COL(struct disp_background, hd_color)},
};
#endif
@@ -221,35 +173,10 @@ static int ini_handler(void* user, const char* section, const char* name, const
struct display_settings *d = (struct display_settings *)user;
int value_int = atoi(value);
- int assign_int(void* ptr, const struct struct_map *map, int map_size)
- {
- for(int i = 0; i < map_size; i++) {
- if(MATCH_KEY(map[i].str)) {
- int size = map[i].offset >> 13;
- int offset = map[i].offset & 0x1FFF;
- switch(size) {
- case 0:
- *((u8 *)((long)ptr + offset)) = value_int; break;
- case 1:
- *((u16 *)((long)ptr + offset)) = value_int; break;
- case 2:
- *((u16 *)((long)ptr + offset)) = get_color(value); break;
- case 3:
- *((u32 *)((long)ptr + offset)) = value_int; break;
- case 6:
- *((u8 *)((long)ptr + offset)) = FONT_GetFromString(value);
- }
- return 1;
- }
- }
- return 0;
- }
-
if(MATCH_START(section, FONT) && strlen(section) > sizeof(FONT)) {
- for (idx = 0; idx < NUM_STR_ELEMS(FONT_VAL); idx++) {
+ for (idx = 0; idx < ARRAYSIZE(FONT_VAL); idx++) {
if (FONT_VAL[idx] && 0 == strcasecmp(section + sizeof(FONT), FONT_VAL[idx])) {
- handle_label(&d->font[idx], name, value);
- return 1;
+ return assign_int(&d->font[idx], _seclabel, ARRAYSIZE(_seclabel), name, value);
}
}
printf("Couldn't parse font: %s\n", section);
@@ -266,36 +193,36 @@ static int ini_handler(void* user, const char* section, const char* name, const
#endif
return 1;
}
- if(assign_int(&d->metrics, _secgeneral, MAPSIZE(_secgeneral)))
+ if (assign_int(&d->metrics, _secgeneral, ARRAYSIZE(_secgeneral), name, value))
return 1;
}
if(MATCH_START(section, "select")) {
- if(assign_int(d, _secselect, MAPSIZE(_secselect)))
+ if (assign_int(d, _secselect, ARRAYSIZE(_secselect), name, value))
return 1;
}
if(MATCH_START(section, "keyboard")) {
- if(assign_int(&d->keyboard, _seckeybd, MAPSIZE(_seckeybd)))
+ if (assign_int(&d->keyboard, _seckeybd, ARRAYSIZE(_seckeybd), name, value))
return 1;
}
if(MATCH_START(section, "listbox")) {
- if(assign_int(&d->listbox, _seclistbox, MAPSIZE(_seclistbox)))
+ if (assign_int(&d->listbox, _seclistbox, ARRAYSIZE(_seclistbox), name, value))
return 1;
}
if(MATCH_START(section, "scrollbar")) {
- if(assign_int(&d->scrollbar, _secscroll, MAPSIZE(_secscroll)))
+ if (assign_int(&d->scrollbar, _secscroll, ARRAYSIZE(_secscroll), name, value))
return 1;
}
if(MATCH_SECTION("xygraph")) {
- if(assign_int(&d->xygraph, _secxygraph, MAPSIZE(_secxygraph)))
+ if (assign_int(&d->xygraph, _secxygraph, ARRAYSIZE(_secxygraph), name, value))
return 1;
}
#if (LCD_WIDTH == 480) || (LCD_WIDTH == 320)
if(MATCH_SECTION("background")) {
- if(assign_int(&d->background, _secbackground, MAPSIZE(_secbackground)))
+ if (assign_int(&d->background, _secbackground, ARRAYSIZE(_secbackground), name, value))
return 1;
}
#endif
- for (idx = 0; idx < NUM_STR_ELEMS(BARGRAPH_VAL); idx++) {
+ for (idx = 0; idx < ARRAYSIZE(BARGRAPH_VAL); idx++) {
if(MATCH_SECTION(BARGRAPH_VAL[idx])) {
struct disp_bargraph *graph;
enum DispFlags flag;
@@ -310,11 +237,15 @@ static int ini_handler(void* user, const char* section, const char* name, const
d->flags = SET_FLAG(d->flags, value_int, flag);
return 1;
}
- if(MATCH_KEY(FG_COLOR)) {
- graph->fg_color_pos = graph->fg_color_neg = graph->fg_color_zero = get_color(value);
+
+ if (assign_int(graph, _secbargraph, ARRAYSIZE(_secbargraph), name, value))
+ return 1;
+
+ if (assign_int(graph, _secbargraph_legacy, ARRAYSIZE(_secbargraph_legacy), name, value)) {
+ graph->fg_color_neg = graph->fg_color_zero = graph->fg_color_pos;
return 1;
}
- assign_int(graph, _secbargraph, MAPSIZE(_secbargraph));
+
return 1;
}
}
@@ -322,6 +253,24 @@ static int ini_handler(void* user, const char* section, const char* name, const
return 1;
}
+static void convert_legacy()
+{
+ for (int i = 0; i < NUM_LABELS; i++) {
+ struct LabelDesc *label = &Display.font[i];
+ // For compatibility reasons,
+ // use the old alignment values as default
+ // if the new one is not yet set:
+ if (label->align == 0) {
+ switch (label->style) {
+ case LABEL_CENTER: label->align = ALIGN_CENTER; break;
+ case LABEL_LEFT: label->align = ALIGN_LEFT; break;
+ case LABEL_RIGHT: label->align = ALIGN_RIGHT; break;
+ default: break;
+ }
+ }
+ }
+}
+
u8 CONFIG_ReadDisplay()
{
memset(&Display, 0, sizeof(Display));
@@ -342,5 +291,10 @@ u8 CONFIG_ReadDisplay()
#else
char filename[] = "media/config.ini";
#endif
- return CONFIG_IniParse(filename, ini_handler, (void *)&Display);
+ if (CONFIG_IniParse(filename, ini_handler, (void *)&Display)) {
+ convert_legacy();
+ return 1;
+ }
+
+ return 0;
}
diff --git a/src/config/model.c b/src/config/model.c
index df598122d8..6d410301aa 100644
--- a/src/config/model.c
+++ b/src/config/model.c
@@ -20,15 +20,19 @@
#include "music.h"
#include "extended_audio.h"
+#include "config.h"
+
#include
#include
+
+extern u8 get_source(const char *value);
extern const u8 EATRG0[PROTO_MAP_LEN];
struct Model Model;
/*set this to write all model data even if it is the same as the default */
static u32 crc32;
-const char * const MODEL_TYPE_VAL[MODELTYPE_LAST] = { "heli", "plane", "multi" };
+static const char * const MODEL_TYPE_VAL[MODELTYPE_LAST] = { "heli", "plane", "multi" };
const u8 RADIO_TX_POWER_COUNT[TX_MODULE_LAST] = { // number of power settings
8, // CYRF6936,
7, // A7105,
@@ -252,55 +256,6 @@ s8 mapstrcasecmp(const char *s1, const char *s2)
return(s1[i] < s2[i] ? -1 : 1);
}
}
-static u8 get_source(const char *section, const char *value)
-{
- unsigned i;
- unsigned val;
- const char *ptr = (value[0] == '!') ? value + 1 : value;
- const char *tmp;
- char cmp[10];
- for (i = 0; i <= NUM_SOURCES; i++) {
- if(mapstrcasecmp(INPUT_SourceNameReal(cmp, i), ptr) == 0) {
- #if defined(HAS_SWITCHES_NOSTOCK) && HAS_SWITCHES_NOSTOCK
- #define SWITCH_NOSTOCK ((1 << INP_HOLD0) | (1 << INP_HOLD1) | \
- (1 << INP_FMOD0) | (1 << INP_FMOD1))
- if ((Transmitter.ignore_src & SWITCH_NOSTOCK) == SWITCH_NOSTOCK) {
- if(mapstrcasecmp("FMODE0", ptr) == 0 ||
- mapstrcasecmp("FMODE1", ptr) == 0 ||
- mapstrcasecmp("HOLD0", ptr) == 0 ||
- mapstrcasecmp("HOLD1", ptr) == 0)
- break;
- }
- #endif //HAS_SWITCHES_NOSTOCK
- return ((ptr == value) ? 0 : 0x80) | i;
- }
- }
- for (i = 0; i < 4; i++) {
- if(mapstrcasecmp(tx_stick_names[i], ptr) == 0) {
- return ((ptr == value) ? 0 : 0x80) | (i + 1);
- }
- }
- i = 0;
- while((tmp = INPUT_MapSourceName(i++, &val))) {
- if(mapstrcasecmp(tmp, ptr) == 0) {
- return ((ptr == value) ? 0 : 0x80) | val;
- }
- }
- printf("%s: Could not parse Source %s\n", section, value);
- return 0;
-}
-
-static u8 get_button(const char *section, const char *value)
-{
- u8 i;
- for (i = 0; i <= NUM_TX_BUTTONS; i++) {
- if(strcasecmp(INPUT_ButtonName(i), value) == 0) {
- return i;
- }
- }
- printf("%s: Could not parse Button %s\n", section, value);
- return 0;
-}
static int handle_proto_opts(struct Model *m, const char* key, const char* value, const char **opts)
{
@@ -515,7 +470,7 @@ static int layout_ini_handler(void* user, const char* section, const char* name,
}
}
if (src == -1) {
- u8 newsrc = get_source(section, ptr);
+ u8 newsrc = get_source(ptr);
if(newsrc >= NUM_INPUTS) {
src = newsrc - (NUM_INPUTS + 1 - (NUM_RTC + NUM_TIMERS + NUM_TELEM + 1));
}
@@ -529,7 +484,7 @@ static int layout_ini_handler(void* user, const char* section, const char* name,
{
if (count != 3)
return 1;
- u8 src = get_source(section, ptr);
+ u8 src = get_source(ptr);
if (src < NUM_INPUTS)
src = 0;
data[5] = src - NUM_INPUTS;
@@ -553,101 +508,94 @@ static int layout_ini_handler(void* user, const char* section, const char* name,
return 1;
}
-struct struct_map {const char *str; u16 offset; u16 defval;};
-#define MAPSIZE(x) (sizeof(x) / sizeof(struct struct_map))
-#define OFFSET(s,v) (((long)(&s.v) - (long)(&s)) | ((sizeof(s.v)-1) << 13))
-#define OFFSETS(s,v) (((long)(&s.v) - (long)(&s)) | ((sizeof(s.v)+3) << 13))
-#define OFFSET_SRC(s,v) (((long)(&s.v) - (long)(&s)) | (2 << 13))
-#define OFFSET_BUT(s,v) (((long)(&s.v) - (long)(&s)) | (6 << 13))
-#if HAS_PERMANENT_TIMER
static const struct struct_map _secnone[] =
{
- {PERMANENT_TIMER, OFFSET(Model, permanent_timer), 0},
-};
+#if HAS_PERMANENT_TIMER
+ {PERMANENT_TIMER, OFFSET(struct Model, permanent_timer)},
#endif
+ {MODEL_TYPE, OFFSET_STRLIST(struct Model, type, MODEL_TYPE_VAL, ARRAYSIZE(MODEL_TYPE_VAL))},
+ {MODEL_MIXERMODE, OFFSET_STRCALL(struct Model, mixer_mode, STDMIXER_ModeName, 0x0103)},
+};
static const struct struct_map _secradio[] = {
- {RADIO_NUM_CHANNELS, OFFSET(Model, num_channels), 0},
- {RADIO_FIXED_ID, OFFSET(Model, fixed_id), 0},
+ {RADIO_NUM_CHANNELS, OFFSET(struct Model, num_channels)},
+ {RADIO_FIXED_ID, OFFSET(struct Model, fixed_id)},
#if HAS_VIDEO
- {RADIO_VIDEOSRC, OFFSET_SRC(Model, videosrc), 0},
- {RADIO_VIDEOCH, OFFSET(Model, videoch), 0},
- {RADIO_VIDEOCONTRAST,OFFSET(Model, video_contrast), 0},
- {RADIO_VIDEOBRIGHTNESS,OFFSET(Model, video_brightness), 0},
+ {RADIO_VIDEOSRC, OFFSET_SRC(struct Model, videosrc)},
+ {RADIO_VIDEOCH, OFFSET(struct Model, videoch)},
+ {RADIO_VIDEOCONTRAST, OFFSET(struct Model, video_contrast)},
+ {RADIO_VIDEOBRIGHTNESS, OFFSET(struct Model, video_brightness)},
#endif
#if HAS_EXTENDED_TELEMETRY
- {RADIO_GROUND_LEVEL, OFFSET(Model, ground_level), 0},
+ {RADIO_GROUND_LEVEL, OFFSET(struct Model, ground_level)},
#endif
+ {RADIO_PROTOCOL, OFFSET_STRCALL(struct Model, protocol, PROTOCOL_GetName, PROTOCOL_COUNT)},
};
static const struct struct_map _secmixer[] = {
- {MIXER_SWITCH, OFFSET_SRC(Model.mixers[0], sw), 0},
- {MIXER_SCALAR, OFFSETS(Model.mixers[0], scalar), 100},
- {MIXER_OFFSET, OFFSETS(Model.mixers[0], offset), 0},
+ {MIXER_SCALAR, OFFSETS(struct Mixer, scalar)},
+ {MIXER_SWITCH, OFFSET_SRC(struct Mixer, sw)},
+ {MIXER_OFFSET, OFFSETS(struct Mixer, offset)},
+};
+static const u16 _secmixer_defaults[] = {
+ 100, 0, 0
};
static const struct struct_map _seclimit[] = {
- {CHAN_LIMIT_SAFETYSW, OFFSET_SRC(Model.limits[0], safetysw), 0},
- {CHAN_LIMIT_SAFETYVAL, OFFSETS(Model.limits[0], safetyval), 0},
- {CHAN_LIMIT_MAX, OFFSET(Model.limits[0], max), DEFAULT_SERVO_LIMIT},
- {CHAN_LIMIT_SPEED, OFFSET(Model.limits[0], speed), 0},
- {CHAN_SCALAR, OFFSET(Model.limits[0], servoscale), 100},
- {CHAN_SCALAR_NEG, OFFSET(Model.limits[0], servoscale_neg), 0},
- {CHAN_SUBTRIM, OFFSETS(Model.limits[0], subtrim), 0},
- {CHAN_DISPLAY_SCALE, OFFSETS(Model.limits[0], displayscale), DEFAULT_DISPLAY_SCALE},
+ {CHAN_LIMIT_SAFETYSW, OFFSET_SRC(struct Limit, safetysw)},
+ {CHAN_LIMIT_SAFETYVAL, OFFSETS(struct Limit, safetyval)},
+ {CHAN_LIMIT_MAX, OFFSET(struct Limit, max)},
+ {CHAN_LIMIT_SPEED, OFFSET(struct Limit, speed)},
+ {CHAN_SCALAR, OFFSET(struct Limit, servoscale)},
+ {CHAN_SCALAR_NEG, OFFSET(struct Limit, servoscale_neg)},
+ {CHAN_SUBTRIM, OFFSETS(struct Limit, subtrim)},
+ {CHAN_DISPLAY_SCALE, OFFSETS(struct Limit, displayscale)},
+};
+static const u16 _seclimit_defaults[] = {
+ 0, 0, DEFAULT_SERVO_LIMIT, 0, 100, 0, 0, DEFAULT_DISPLAY_SCALE
};
static const struct struct_map _sectrim[] = {
- {TRIM_SOURCE, OFFSET_SRC(Model.trims[0], src), 0xFFFF},
- {TRIM_POS, OFFSET_BUT(Model.trims[0], pos), 0},
- {TRIM_NEG, OFFSET_BUT(Model.trims[0], neg), 0},
- {TRIM_STEP, OFFSET(Model.trims[0], step), 1},
+ {TRIM_STEP, OFFSET(struct Trim, step)},
+ {TRIM_POS, OFFSET_BUT(struct Trim, pos)},
+ {TRIM_NEG, OFFSET_BUT(struct Trim, neg)},
+};
+static const u16 _sectrim_defaults[] = {
+ 1, 0, 0
+};
+static const struct struct_map _sectrim_rdonly[] = {
+ {TRIM_SOURCE, OFFSET_SRC(struct Trim, src)},
};
+
static const struct struct_map _secswash[] = {
- {SWASH_AILMIX, OFFSET(Model, swashmix[0]), 60},
- {SWASH_ELEMIX, OFFSET(Model, swashmix[1]), 60},
- {SWASH_COLMIX, OFFSET(Model, swashmix[2]), 60},
+ {SWASH_AILMIX, OFFSET(struct Model, swashmix[0])},
+ {SWASH_ELEMIX, OFFSET(struct Model, swashmix[1])},
+ {SWASH_COLMIX, OFFSET(struct Model, swashmix[2])},
+ {SWASH_TYPE, OFFSET_STRCALL(struct Model, swash_type, MIXER_SwashType, SWASH_TYPE_LAST)},
+};
+static const u16 _secswash_defaults[] = {
+ 60, 60, 60, 0
};
+
static const struct struct_map _sectimer[] = {
- {TIMER_TIME, OFFSET(Model.timer[0], timer), 0xFFFF},
- {TIMER_VAL, OFFSET(Model.timer[0], val), 0xFFFF},
- {TIMER_SOURCE, OFFSET_SRC(Model.timer[0], src), 0},
- {TIMER_RESETSRC, OFFSET_SRC(Model.timer[0], resetsrc), 0},
+ {TIMER_SOURCE, OFFSET_SRC(struct Timer, src)},
+ {TIMER_RESETSRC, OFFSET_SRC(struct Timer, resetsrc)},
+ {TIMER_TYPE, OFFSET_STRLIST(struct Timer, type, TIMER_TYPE_VAL, ARRAYSIZE(TIMER_TYPE_VAL))}
};
+static const struct struct_map _sectimer_rdonly[] = {
+ {TIMER_TIME, OFFSET(struct Timer, timer)},
+ {TIMER_VAL, OFFSET(struct Timer, val)},
+};
+
static const struct struct_map _secppm[] = {
- {PPMIN_CENTERPW, OFFSET(Model, ppmin_centerpw), 0},
- {PPMIN_DELTAPW, OFFSET(Model, ppmin_deltapw), 0},
- {PPMIN_SWITCH, OFFSET_SRC(Model, train_sw), 0xFFFF},
+ {PPMIN_CENTERPW, OFFSET(struct Model, ppmin_centerpw)},
+ {PPMIN_DELTAPW, OFFSET(struct Model, ppmin_deltapw)},
};
+static const struct struct_map _secppm_rdonly[] = {
+ {PPMIN_SWITCH, OFFSET_SRC(struct Model, train_sw)},
+};
+
static int ini_handler(void* user, const char* section, const char* name, const char* value)
{
int value_int = atoi(value);
struct Model *m = (struct Model *)user;
-int assign_int(void* ptr, const struct struct_map *map, int map_size)
-{
- for(int i = 0; i < map_size; i++) {
- if(MATCH_KEY(map[i].str)) {
- int size = map[i].offset >> 13;
- int offset = map[i].offset & 0x1FFF;
- switch(size) {
- case 0:
- *((u8 *)((long)ptr + offset)) = value_int; break;
- case 1:
- *((u16 *)((long)ptr + offset)) = value_int; break;
- case 2:
- *((u8 *)((long)ptr + offset)) = get_source(section, value); break;
- case 3:
- *((u32 *)((long)ptr + offset)) = value_int; break;
- case 4:
- *((s8 *)((long)ptr + offset)) = value_int; break;
- case 5:
- *((s16 *)((long)ptr + offset)) = value_int; break;
- case 6:
- *((u8 *)((long)ptr + offset)) = get_button(section, value); break;
- case 7:
- *((s32 *)((long)ptr + offset)) = value_int; break;
- }
- return 1;
- }
- }
- return 0;
-}
+
CLOCK_ResetWatchdog();
unsigned i;
if (MATCH_SECTION("")) {
@@ -663,45 +611,15 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
CONFIG_ParseIconName(m->icon, value);
return 1;
}
-#if HAS_PERMANENT_TIMER
- if(assign_int(&Model, _secnone, MAPSIZE(_secnone)))
- return 1;
-#endif
- if (MATCH_KEY(MODEL_TYPE)) {
- for (i = 0; i < NUM_STR_ELEMS(MODEL_TYPE_VAL); i++) {
- if (MATCH_VALUE(MODEL_TYPE_VAL[i])) {
- m->type = i;
- return 1;
- }
- }
- return 0;
- }
if (MATCH_KEY(MODEL_AUTOMAP)) {
auto_map = atoi(value);
return 1;
}
- if (MATCH_KEY(MODEL_MIXERMODE)) {
- for(i = 1; i < 3; i++) {
- if(MATCH_VALUE(STDMIXER_ModeName(i)))
- m->mixer_mode = i;
- }
+ if (assign_int(&Model, _secnone, ARRAYSIZE(_secnone), name, value))
return 1;
- }
}
if (MATCH_SECTION(SECTION_RADIO)) {
- if (MATCH_KEY(RADIO_PROTOCOL)) {
- for (i = 0; i < PROTOCOL_COUNT; i++) {
- if (MATCH_VALUE(PROTOCOL_GetName(i))) {
- m->protocol = i;
- m->radio = PROTOCOL_GetRadio(i);
- PROTOCOL_Load(1);
- return 1;
- }
- }
- printf("Unknown protocol: %s\n", value);
- return 1;
- }
- if(assign_int(&Model, _secradio, MAPSIZE(_secradio)))
+ if (assign_int(&Model, _secradio, ARRAYSIZE(_secradio), name, value))
return 1;
if (MATCH_KEY(RADIO_TX_POWER)) {
if (m->radio == TX_MODULE_LAST) {
@@ -738,15 +656,15 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
printf("%s: Only %d mixers are supported\n", section, NUM_MIXERS);
return 1;
}
- m->mixers[idx].src = get_source(section, value);
+ m->mixers[idx].src = get_source(value);
return 1;
}
idx--;
if (MATCH_KEY(MIXER_DEST)) {
- m->mixers[idx].dest = get_source(section, value) - NUM_INPUTS - 1;
+ m->mixers[idx].dest = get_source(value) - NUM_INPUTS - 1;
return 1;
}
- if(assign_int(&m->mixers[idx], _secmixer, MAPSIZE(_secmixer)))
+ if (assign_int(&m->mixers[idx], _secmixer, ARRAYSIZE(_secmixer), name, value))
return 1;
if (MATCH_KEY(MIXER_USETRIM)) {
MIXER_SET_APPLY_TRIM(&m->mixers[idx], value_int);
@@ -819,7 +737,7 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
return 1;
}
- if(assign_int(&m->limits[idx], _seclimit, MAPSIZE(_seclimit)))
+ if (assign_int(&m->limits[idx], _seclimit, ARRAYSIZE(_seclimit), name, value))
return 1;
if (MATCH_KEY(CHAN_LIMIT_MIN)) {
m->limits[idx].min = -value_int;
@@ -877,7 +795,9 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
return 1;
}
idx--;
- if(assign_int(&m->trims[idx], _sectrim, MAPSIZE(_sectrim)))
+ if (assign_int(&m->trims[idx], _sectrim, ARRAYSIZE(_sectrim), name, value))
+ return 1;
+ if (assign_int(&m->trims[idx], _sectrim_rdonly, ARRAYSIZE(_sectrim_rdonly), name, value))
return 1;
if (MATCH_KEY(TRIM_SWITCH)) {
for (int i = 0; i <= NUM_SOURCES; i++) {
@@ -897,16 +817,6 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
return 0;
}
if (MATCH_SECTION(SECTION_SWASH)) {
- if (MATCH_KEY(SWASH_TYPE)) {
- for (i = SWASH_TYPE_NONE; i <= SWASH_TYPE_90; i++) {
- if(strcasecmp(MIXER_SwashType(i), value) == 0) {
- m->swash_type = i;
- return 1;
- }
- }
- printf("%s: Unknown swash_type: %s\n", section, value);
- return 1;
- }
if (MATCH_KEY(SWASH_ELE_INV)) {
if (value_int)
m->swash_invert |= 0x01;
@@ -922,7 +832,7 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
m->swash_invert |= 0x04;
return 1;
}
- if(assign_int(m, _secswash, MAPSIZE(_secswash)))
+ if (assign_int(m, _secswash, ARRAYSIZE(_secswash), name, value))
return 1;
}
if (MATCH_START(section, SECTION_TIMER)) {
@@ -946,7 +856,9 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
printf("%s: Unknown timer type: %s\n", section, value);
return 1;
}
- if(assign_int(&m->timer[idx], _sectimer, MAPSIZE(_sectimer)))
+ if (assign_int(&m->timer[idx], _sectimer, ARRAYSIZE(_sectimer), name, value))
+ return 1;
+ if (assign_int(&m->timer[idx], _sectimer_rdonly, ARRAYSIZE(_sectimer_rdonly), name, value))
return 1;
}
if (MATCH_START(section, SECTION_TELEMALARM)) {
@@ -991,7 +903,7 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
#if HAS_DATALOG
if (MATCH_SECTION(SECTION_DATALOG)) {
if (MATCH_KEY(DATALOG_SWITCH)) {
- m->datalog.enable = get_source(section, value);
+ m->datalog.enable = get_source(value);
} else if (MATCH_KEY(DATALOG_RATE)) {
for (i = 0; i < DLOG_RATE_LAST; i++) {
if(mapstrcasecmp(DATALOG_RateString(i), value) == 0) {
@@ -1018,7 +930,7 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
src = 0;
found = 1;
} else {
- src = get_source(section, name);
+ src = get_source(name);
}
if(found || src) {
u32 i;
@@ -1047,12 +959,14 @@ int assign_int(void* ptr, const struct struct_map *map, int map_size)
}
return 1;
}
- if(assign_int(m, _secppm, MAPSIZE(_secppm)))
+ if (assign_int(m, _secppm, ARRAYSIZE(_secppm), name, value))
+ return 1;
+ if (assign_int(m, _secppm_rdonly, ARRAYSIZE(_secppm_rdonly), name, value))
return 1;
if (MATCH_START(name, PPMIN_MAP)) {
u8 idx = atoi(name + sizeof(PPMIN_MAP)-1) -1;
if (idx < MAX_PPM_IN_CHANNELS) {
- m->ppm_map[idx] = get_source(section, value);
+ m->ppm_map[idx] = get_source(value);
if (PPMin_Mode() == PPM_IN_TRAIN1) {
m->ppm_map[idx] = (m->ppm_map[idx] <= NUM_INPUTS)
? -1
@@ -1129,36 +1043,6 @@ static void get_model_file(char *file, u8 model_num)
sprintf(file, "models/model%d.ini", model_num);
}
-static void write_int(FILE *fh, void* ptr, const struct struct_map *map, int map_size)
-{
- char tmpstr[20];
- for(int i = 0; i < map_size; i++) {
- int size = map[i].offset >> 13;
- int offset = map[i].offset & 0x1FFF;
- int value;
- if (map[i].defval == 0xffff)
- continue;
- switch(size) {
- case 0:
- case 2: //SRC
- case 6: //BUTTON
- value = *((u8 *)((long)ptr + offset)); break;
- case 1: value = *((u16 *)((long)ptr + offset)); break;
- case 3: value = *((u32 *)((long)ptr + offset)); break;
- case 4: value = *((s8 *)((long)ptr + offset)); break;
- case 5: value = *((s16 *)((long)ptr + offset)); break;
- case 7: value = *((s32 *)((long)ptr + offset)); break;
- default: continue;
- }
- if(WRITE_FULL_MODEL || value != map[i].defval) {
- if (2 == (size & 0x03)) //2, 6
- fprintf(fh, "%s=%s\n", map[i].str, size == 2 ? INPUT_SourceNameReal(tmpstr, value) : INPUT_ButtonName(value));
- else
- fprintf(fh, "%s=%d\n", map[i].str, value);
- }
- }
-}
-
static u8 write_mixer(FILE *fh, struct Model *m, u8 channel)
{
int idx;
@@ -1172,7 +1056,8 @@ static u8 write_mixer(FILE *fh, struct Model *m, u8 channel)
fprintf(fh, "[%s]\n", SECTION_MIXER);
fprintf(fh, "%s=%s\n", MIXER_SOURCE, INPUT_SourceNameReal(tmpstr, m->mixers[idx].src));
fprintf(fh, "%s=%s\n", MIXER_DEST, INPUT_SourceNameReal(tmpstr, m->mixers[idx].dest + NUM_INPUTS + 1));
- write_int(fh, &m->mixers[idx], _secmixer, MAPSIZE(_secmixer));
+ write_int2(&m->mixers[idx], _secmixer, ARRAYSIZE(_secmixer),
+ _secmixer_defaults, ARRAYSIZE(_secmixer_defaults), fh);
if(WRITE_FULL_MODEL || ! MIXER_APPLY_TRIM(&m->mixers[idx]))
fprintf(fh, "%s=%d\n", MIXER_USETRIM, MIXER_APPLY_TRIM(&m->mixers[idx]) ? 1 : 0);
if(WRITE_FULL_MODEL || MIXER_MUX(&m->mixers[idx]))
@@ -1237,17 +1122,13 @@ u8 CONFIG_WriteModel(u8 model_num) {
}
CONFIG_EnableLanguage(0);
fprintf(fh, "%s=%s\n", MODEL_NAME, m->name);
-#if HAS_PERMANENT_TIMER
- write_int(fh, m, _secnone, MAPSIZE(_secnone));
-#endif
- fprintf(fh, "%s=%s\n", MODEL_MIXERMODE, STDMIXER_ModeName(m->mixer_mode));
+ write_int2(m, _secnone, ARRAYSIZE(_secnone), DEFAULTS_ZERO, 0, fh);
if(m->icon[0] != 0)
fprintf(fh, "%s=%s\n", MODEL_ICON, m->icon + 9);
if(WRITE_FULL_MODEL || m->type != 0)
fprintf(fh, "%s=%s\n", MODEL_TYPE, MODEL_TYPE_VAL[m->type]);
fprintf(fh, "[%s]\n", SECTION_RADIO);
- fprintf(fh, "%s=%s\n", RADIO_PROTOCOL, PROTOCOL_GetName(m->protocol));
- write_int(fh, m, _secradio, MAPSIZE(_secradio));
+ write_int2(m, _secradio, ARRAYSIZE(_secradio), DEFAULTS_ZERO, 0, fh);
fprintf(fh, "%s=%s\n", RADIO_TX_POWER, radio_tx_power_val(m->radio, m->tx_power));
fprintf(fh, "\n");
write_proto_opts(fh, m);
@@ -1266,7 +1147,7 @@ u8 CONFIG_WriteModel(u8 model_num) {
fprintf(fh, "[%s%d]\n", SECTION_CHANNEL, idx+1);
if(WRITE_FULL_MODEL || (m->limits[idx].flags & CH_REVERSE))
fprintf(fh, "%s=%d\n", CHAN_LIMIT_REVERSE, (m->limits[idx].flags & CH_REVERSE) ? 1 : 0);
- write_int(fh, &m->limits[idx], _seclimit, MAPSIZE(_seclimit));
+ write_int2(&m->limits[idx], _seclimit, ARRAYSIZE(_seclimit), _seclimit_defaults, ARRAYSIZE(_seclimit_defaults), fh);
if(WRITE_FULL_MODEL || (m->limits[idx].flags & CH_FAILSAFE_EN)) {
if(m->limits[idx].flags & CH_FAILSAFE_EN) {
fprintf(fh, "%s=%d\n", CHAN_LIMIT_FAILSAFE, m->limits[idx].failsafe);
@@ -1301,7 +1182,7 @@ u8 CONFIG_WriteModel(u8 model_num) {
if (PPMin_Mode() != PPM_IN_SOURCE) {
fprintf(fh, "%s=%s\n", PPMIN_SWITCH, INPUT_SourceNameReal(file, m->train_sw));
}
- write_int(fh, m, _secppm, MAPSIZE(_secppm));
+ write_int2(m, _secppm, ARRAYSIZE(_secppm), DEFAULTS_ZERO, 0, fh);
//fprintf(fh, "%s=%d\n", PPMIN_CENTERPW, m->ppmin_centerpw);
//fprintf(fh, "%s=%d\n", PPMIN_DELTAPW, m->ppmin_deltapw);
if (PPMin_Mode() != PPM_IN_SOURCE) {
@@ -1322,7 +1203,7 @@ u8 CONFIG_WriteModel(u8 model_num) {
m->trims[idx].src >= 1 && m->trims[idx].src <= 4
? tx_stick_names[m->trims[idx].src-1]
: INPUT_SourceNameReal(file, m->trims[idx].src));
- write_int(fh, &m->trims[idx], _sectrim, MAPSIZE(_sectrim));
+ write_int2(&m->trims[idx], _sectrim, ARRAYSIZE(_sectrim), _sectrim_defaults, ARRAYSIZE(_sectrim_defaults), fh);
if(WRITE_FULL_MODEL || m->trims[idx].sw)
fprintf(fh, "%s=%s\n", TRIM_SWITCH, INPUT_SourceNameAbbrevSwitchReal(file, m->trims[idx].sw));
if(WRITE_FULL_MODEL || m->trims[idx].value[0] || m->trims[idx].value[1] || m->trims[idx].value[2]
@@ -1333,22 +1214,19 @@ u8 CONFIG_WriteModel(u8 model_num) {
}
if (WRITE_FULL_MODEL || m->swash_type) {
fprintf(fh, "[%s]\n", SECTION_SWASH);
- fprintf(fh, "%s=%s\n", SWASH_TYPE, MIXER_SwashType(m->swash_type));
if (WRITE_FULL_MODEL || m->swash_invert & 0x01)
fprintf(fh, "%s=1\n", SWASH_ELE_INV);
if (WRITE_FULL_MODEL || m->swash_invert & 0x02)
fprintf(fh, "%s=1\n", SWASH_AIL_INV);
if (WRITE_FULL_MODEL || m->swash_invert & 0x04)
fprintf(fh, "%s=1\n", SWASH_COL_INV);
- write_int(fh, m, _secswash, MAPSIZE(_secswash));
+ write_int2(m, _secswash, ARRAYSIZE(_secswash), _secswash_defaults, ARRAYSIZE(_secswash_defaults), fh);
}
for(idx = 0; idx < NUM_TIMERS; idx++) {
if (! WRITE_FULL_MODEL && m->timer[idx].src == 0 && m->timer[idx].type == TIMER_STOPWATCH)
continue;
fprintf(fh, "[%s%d]\n", SECTION_TIMER, idx+1);
- if (WRITE_FULL_MODEL || m->timer[idx].type != TIMER_STOPWATCH)
- fprintf(fh, "%s=%s\n", TIMER_TYPE, TIMER_TYPE_VAL[m->timer[idx].type]);
- write_int(fh, &m->timer[idx], _sectimer, MAPSIZE(_sectimer));
+ write_int(&m->timer[idx], _sectimer, ARRAYSIZE(_sectimer), fh);
if (WRITE_FULL_MODEL || ((m->timer[idx].type == TIMER_COUNTDOWN || m->timer[idx].type == TIMER_COUNTDOWN_PROP) && m->timer[idx].timer))
fprintf(fh, "%s=%d\n", TIMER_TIME, m->timer[idx].timer);
if (WRITE_FULL_MODEL || (m->timer[idx].val != 0 && m->timer[idx].type == TIMER_PERMANENT))
@@ -1502,6 +1380,8 @@ u8 CONFIG_ReadModel(u8 model_num) {
CONFIG_ReadLayout("layout/default.ini");
if(! PROTOCOL_HasPowerAmp(Model.protocol))
Model.tx_power = TXPOWER_150mW;
+ Model.radio = PROTOCOL_GetRadio(Model.protocol);
+ PROTOCOL_Load(1);
MIXER_SetMixers(NULL, 0);
if(auto_map)
RemapChannelsForProtocol(EATRG0);
diff --git a/src/config/model.h b/src/config/model.h
index 3a2c28bf18..ec9c6f8bd7 100644
--- a/src/config/model.h
+++ b/src/config/model.h
@@ -20,7 +20,9 @@
extern const char MODEL_NAME[];
extern const char MODEL_ICON[];
extern const char MODEL_TYPE[];
+extern const char MODEL_MIXERMODE[];
extern const char MODEL_TEMPLATE[];
+extern const char MODEL_AUTOMAP[];
#define UNKNOWN_ICON ("media/noicon" IMG_EXT)
//This cannot be computed, and must be manually updated
diff --git a/src/config/tx.c b/src/config/tx.c
index d4cbfe7850..69449105c6 100644
--- a/src/config/tx.c
+++ b/src/config/tx.c
@@ -15,6 +15,7 @@
#include "common.h"
#include "target.h"
+#include "config.h"
#include "gui/gui.h"
#include "tx.h"
#include "rtc.h"
@@ -87,80 +88,70 @@ static const char TELEM_ALERT_INTERVAL[] ="alertinterval";
#define MATCH_VALUE(s) strcasecmp(value, s) == 0
#define NUM_STR_ELEMS(s) (sizeof(s) / sizeof(char *))
+static const struct struct_map _secmodel[] =
+{
+ {CURRENT_MODEL, OFFSET(struct Transmitter, current_model)},
+ {LANGUAGE, OFFSET(struct Transmitter, language)},
+ {MUSIC_SHUTD, OFFSET(struct Transmitter, music_shutdown)},
+ {MODE, OFFSET(struct Transmitter, mode)},
+ {BRIGHTNESS, OFFSET(struct Transmitter, backlight)},
+ {CONTRAST, OFFSET(struct Transmitter, contrast)},
+ {VOLUME, OFFSET(struct Transmitter, volume)},
+ {VIBRATION, OFFSET(struct Transmitter, vibration_state)},
+ {POWER_ALARM, OFFSET(struct Transmitter, power_alarm)},
+ {BATT_ALARM, OFFSET(struct Transmitter, batt_alarm)},
+ {BATT_CRITICAL, OFFSET(struct Transmitter, batt_critical)},
+ {BATT_WARNING_INTERVAL, OFFSET(struct Transmitter, batt_warning_interval)},
+ {SPLASH_DELAY, OFFSET(struct Transmitter, splash_delay)},
+
+#if HAS_RTC
+ {TIME_FORMAT, OFFSET(struct Transmitter, rtc_timeformat)},
+ {DATE_FORMAT, OFFSET(struct Transmitter, rtc_dateformat)},
+#endif
+
+#if HAS_EXTENDED_AUDIO
+ {AUDIO_VOL, OFFSET(struct Transmitter, audio_vol)},
+#endif
+};
+
+static const struct struct_map _seccalibrate[] =
+{
+ {CALIBRATE_MAX, OFFSET(struct StickCalibration, max)},
+ {CALIBRATE_MIN, OFFSET(struct StickCalibration, min)},
+ {CALIBRATE_ZERO, OFFSET(struct StickCalibration, zero)},
+};
+
+#if HAS_TOUCH
+static const struct struct_map _sectouch[] =
+{
+ {TOUCH_XSCALE, OFFSET(struct TouchCalibration, xscale)},
+ {TOUCH_YSCALE, OFFSET(struct TouchCalibration, yscale)},
+ {TOUCH_XOFFSET, OFFSET(struct TouchCalibration, xoffset)},
+ {TOUCH_YOFFSET, OFFSET(struct TouchCalibration, yoffset)},
+};
+#endif
+
+static const struct struct_map _secautodimmer[] =
+{
+ {AUTODIMMER_TIME, OFFSET(struct AutoDimmer, timer)},
+ {AUTODIMMER_DIMVALUE, OFFSET(struct AutoDimmer, backlight_dim_value)},
+};
+
+static const struct struct_map _sectimer[] =
+{
+ {TIMERSETTINGS_PREALERT_TIME, OFFSET(struct CountDownTimerSettings, prealert_time)},
+ {TIMERSETTINGS_PREALERT_INTERVAL, OFFSET(struct CountDownTimerSettings, prealert_interval)},
+ {TIMERSETTINGS_TIMEUP_INTERVAL, OFFSET(struct CountDownTimerSettings, timeup_interval)},
+};
+
static int ini_handler(void* user, const char* section, const char* name, const char* value)
{
struct Transmitter *t = (struct Transmitter *)user;
s32 value_int = atoi(value);
if (section[0] == '\0') {
- if (MATCH_KEY(CURRENT_MODEL)) {
- t->current_model = value_int;
- return 1;
- }
- if (MATCH_KEY(LANGUAGE)) {
- t->language = value_int;
- return 1;
- }
- if (MATCH_KEY(MUSIC_SHUTD)) {
- t->music_shutdown = atoi(value);
- return 1;
- }
- if (MATCH_KEY(MODE)) {
- t->mode = atoi(value);
- return 1;
- }
- if (MATCH_KEY(BRIGHTNESS)) {
- t->backlight = atoi(value);
- return 1;
- }
- if (MATCH_KEY(CONTRAST)) {
- t->contrast = atoi(value);
- return 1;
- }
- if (MATCH_KEY(VOLUME)) {
- t->volume = atoi(value);
- return 1;
- }
-#if HAS_EXTENDED_AUDIO
- if (MATCH_KEY(AUDIO_VOL)) {
- t->audio_vol = atoi(value);
- return 1;
- }
-#endif
- if (MATCH_KEY(VIBRATION)) {
- t->vibration_state = atoi(value);
- return 1;
- }
- if (MATCH_KEY(POWER_ALARM)) {
- t->power_alarm = atoi(value);
- return 1;
- }
- if (MATCH_KEY(BATT_ALARM)) {
- t->batt_alarm = atoi(value);
+ if (assign_int(t, _secmodel, ARRAYSIZE(_secmodel), name, value))
return 1;
- }
- if (MATCH_KEY(BATT_CRITICAL)) {
- t->batt_critical = atoi(value);
- return 1;
- }
- if (MATCH_KEY(BATT_WARNING_INTERVAL)) {
- t->batt_warning_interval = atoi(value);
- return 1;
- }
- if (MATCH_KEY(SPLASH_DELAY)) {
- t->splash_delay = atoi(value);
- return 1;
- }
- #if HAS_RTC
- if (MATCH_KEY(TIME_FORMAT)) {
- t->rtc_timeformat = atoi(value);
- return 1;
- }
- if (MATCH_KEY(DATE_FORMAT)) {
- t->rtc_dateformat = atoi(value);
- return 1;
- }
- #endif
}
if(MATCH_START(section, SECTION_CALIBRATE) && strlen(section) >= sizeof(SECTION_CALIBRATE)) {
u8 idx = atoi(section + sizeof(SECTION_CALIBRATE)-1);
@@ -173,62 +164,22 @@ static int ini_handler(void* user, const char* section, const char* name, const
return 1;
}
idx--;
- if (MATCH_KEY(CALIBRATE_MAX)) {
- t->calibration[idx].max = value_int;
- return 1;
- }
- if (MATCH_KEY(CALIBRATE_MIN)) {
- t->calibration[idx].min = value_int;
- return 1;
- }
- if (MATCH_KEY(CALIBRATE_ZERO)) {
- t->calibration[idx].zero = value_int;
+ if (assign_int(&t->calibration[idx], _seccalibrate, ARRAYSIZE(_seccalibrate), name, value))
return 1;
- }
}
- if (HAS_TOUCH) {
- if (MATCH_SECTION(SECTION_TOUCH)) {
- if (MATCH_KEY(TOUCH_XSCALE)) {
- t->touch.xscale = value_int;
- return 1;
- }
- if (MATCH_KEY(TOUCH_YSCALE)) {
- t->touch.yscale = value_int;
- return 1;
- }
- if (MATCH_KEY(TOUCH_XOFFSET)) {
- t->touch.xoffset = value_int;
- return 1;
- }
- if (MATCH_KEY(TOUCH_YOFFSET)) {
- t->touch.yoffset = value_int;
- return 1;
- }
- }
+#if HAS_TOUCH
+ if (MATCH_SECTION(SECTION_TOUCH)) {
+ if (assign_int(&t->touch, _sectouch, ARRAYSIZE(_sectouch), name, value))
+ return 1;
}
+#endif
if (MATCH_SECTION(SECTION_AUTODIMMER)) {
- if (MATCH_KEY(AUTODIMMER_TIME)) {
- t->auto_dimmer.timer = value_int;
+ if (assign_int(&t->auto_dimmer, _secautodimmer, ARRAYSIZE(_secautodimmer), name, value))
return 1;
- }
- if (MATCH_KEY(AUTODIMMER_DIMVALUE)) {
- t->auto_dimmer.backlight_dim_value = value_int;
- return 1;
- }
}
if (MATCH_SECTION(SECTION_TIMERSETTINGS)) {
- if (MATCH_KEY(TIMERSETTINGS_PREALERT_TIME)) {
- t->countdown_timer_settings.prealert_time = value_int;
- return 1;
- }
- if (MATCH_KEY(TIMERSETTINGS_PREALERT_INTERVAL)) {
- t->countdown_timer_settings.prealert_interval = value_int;
+ if (assign_int(&t->countdown_timer_settings, _sectimer, ARRAYSIZE(_sectimer), name, value))
return 1;
- }
- if (MATCH_KEY(TIMERSETTINGS_TIMEUP_INTERVAL)) {
- t->countdown_timer_settings.timeup_interval = value_int;
- return 1;
- }
}
if (MATCH_SECTION(SECTION_TELEMETRY)) {
if (MATCH_KEY(TELEM_TEMP)) {
@@ -260,54 +211,30 @@ void CONFIG_WriteTx()
printf("Couldn't open tx.ini\n");
return;
}
- CONFIG_EnableLanguage(0);
- fprintf(fh, "%s=%d\n", CURRENT_MODEL, Transmitter.current_model);
- fprintf(fh, "%s=%d\n", LANGUAGE, Transmitter.language);
- fprintf(fh, "%s=%d\n", MUSIC_SHUTD, Transmitter.music_shutdown);
- fprintf(fh, "%s=%d\n", MODE, Transmitter.mode);
- fprintf(fh, "%s=%d\n", BRIGHTNESS, Transmitter.backlight);
- fprintf(fh, "%s=%d\n", CONTRAST, Transmitter.contrast);
- fprintf(fh, "%s=%d\n", VOLUME, Transmitter.volume);
-#if HAS_EXTENDED_AUDIO
- fprintf(fh, "%s=%d\n", AUDIO_VOL, Transmitter.audio_vol);
-#endif
- fprintf(fh, "%s=%d\n", VIBRATION, Transmitter.vibration_state);
- fprintf(fh, "%s=%d\n", POWER_ALARM, Transmitter.power_alarm);
- fprintf(fh, "%s=%d\n", BATT_ALARM, Transmitter.batt_alarm);
- fprintf(fh, "%s=%d\n", BATT_CRITICAL, Transmitter.batt_critical);
- fprintf(fh, "%s=%d\n", BATT_WARNING_INTERVAL, Transmitter.batt_warning_interval);
- fprintf(fh, "%s=%d\n", SPLASH_DELAY, Transmitter.splash_delay);
-#if HAS_RTC
- fprintf(fh, "%s=%d\n", TIME_FORMAT, Transmitter.rtc_timeformat);
- fprintf(fh, "%s=%d\n", DATE_FORMAT, Transmitter.rtc_dateformat);
-#endif
+
+ write_int(&Transmitter, _secmodel, ARRAYSIZE(_secmodel), fh);
+
for(i = 0; i < INP_HAS_CALIBRATION; i++) {
fprintf(fh, "[%s%d]\n", SECTION_CALIBRATE, i+1);
- fprintf(fh, " %s=%d\n", CALIBRATE_MAX, t->calibration[i].max);
- fprintf(fh, " %s=%d\n", CALIBRATE_MIN, t->calibration[i].min);
- fprintf(fh, " %s=%d\n", CALIBRATE_ZERO, t->calibration[i].zero);
- }
- if (HAS_TOUCH) {
- fprintf(fh, "[%s]\n", SECTION_TOUCH);
- fprintf(fh, " %s=%d\n", TOUCH_XSCALE, (int)t->touch.xscale);
- fprintf(fh, " %s=%d\n", TOUCH_YSCALE, (int)t->touch.yscale);
- fprintf(fh, " %s=%d\n", TOUCH_XOFFSET, (int)t->touch.xoffset);
- fprintf(fh, " %s=%d\n", TOUCH_YOFFSET, (int)t->touch.yoffset);
+ write_int(&t->calibration[i], _seccalibrate, ARRAYSIZE(_seccalibrate), fh);
}
+
+#if HAS_TOUCH
+ fprintf(fh, "[%s]\n", SECTION_TOUCH);
+ write_int(&t->touch, _sectouch, ARRAYSIZE(_sectouch), fh);
+#endif
+
fprintf(fh, "[%s]\n", SECTION_AUTODIMMER);
- fprintf(fh, "%s=%u\n", AUTODIMMER_TIME, (unsigned int)t->auto_dimmer.timer);
- fprintf(fh, "%s=%u\n", AUTODIMMER_DIMVALUE, t->auto_dimmer.backlight_dim_value);
+ write_int(&t->auto_dimmer, _secautodimmer, ARRAYSIZE(_secautodimmer), fh);
+
fprintf(fh, "[%s]\n", SECTION_TIMERSETTINGS);
- fprintf(fh, "%s=%u\n", TIMERSETTINGS_PREALERT_TIME, (unsigned int)t->countdown_timer_settings.prealert_time);
- fprintf(fh, "%s=%u\n", TIMERSETTINGS_PREALERT_INTERVAL, t->countdown_timer_settings.prealert_interval);
- fprintf(fh, "%s=%u\n", TIMERSETTINGS_TIMEUP_INTERVAL, t->countdown_timer_settings.timeup_interval);
+ write_int(&t->countdown_timer_settings, _sectimer, ARRAYSIZE(_sectimer), fh);
fprintf(fh, "[%s]\n", SECTION_TELEMETRY);
fprintf(fh, "%s=%s\n", TELEM_TEMP, TELEM_TEMP_VAL[(t->telem & TELEMUNIT_FAREN) ? 1 : 0]);
fprintf(fh, "%s=%s\n", TELEM_LENGTH, TELEM_LENGTH_VAL[(t->telem & TELEMUNIT_FEET) ? 1 : 0]);
fprintf(fh, "%s=%u\n", TELEM_ALERT_INTERVAL, t->telem_alert_interval);
- CONFIG_EnableLanguage(1);
fclose(fh);
}
diff --git a/src/target/tx/other/test/old_model.c b/src/target/tx/other/test/old_model.c
new file mode 100644
index 0000000000..d1feb298bd
--- /dev/null
+++ b/src/target/tx/other/test/old_model.c
@@ -0,0 +1,1475 @@
+/*
+ This project is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Deviation is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Deviation. If not, see .
+ */
+
+#include "common.h"
+#include "config/model.h"
+#include "telemetry.h"
+#include "config/tx.h"
+#include "music.h"
+#include "extended_audio.h"
+
+#include
+#include
+extern const u8 EATRG0[PROTO_MAP_LEN];
+
+extern struct Model Model;
+/*set this to write all model data even if it is the same as the default */
+static u32 crc32;
+static const char * const MODEL_TYPE_VAL[MODELTYPE_LAST] = { "heli", "plane", "multi" };
+
+#define MATCH_SECTION(s) (strcasecmp(section, s) == 0)
+#define MATCH_START(x,y) (strncasecmp(x, y, sizeof(y)-1) == 0)
+#define MATCH_KEY(s) (strcasecmp(name, s) == 0)
+#define MATCH_VALUE(s) (strcasecmp(value, s) == 0)
+#define NUM_STR_ELEMS(s) (sizeof(s) / sizeof(char *))
+
+#define WRITE_FULL_MODEL 0
+static u8 auto_map;
+
+/* Section: Radio */
+static const char SECTION_RADIO[] = "radio";
+
+static const char RADIO_PROTOCOL[] = "protocol";
+#if HAS_VIDEO
+static const char RADIO_VIDEOSRC[] = "videosrc";
+static const char RADIO_VIDEOCH[] = "videoch";
+static const char RADIO_VIDEOCONTRAST[] = "videocontrast";
+static const char RADIO_VIDEOBRIGHTNESS[] = "videobrightness";
+#endif
+
+static const char RADIO_NUM_CHANNELS[] = "num_channels";
+static const char RADIO_FIXED_ID[] = "fixed_id";
+
+static const char RADIO_TX_POWER[] = "tx_power";
+#if HAS_EXTENDED_TELEMETRY
+static const char RADIO_GROUND_LEVEL[] = "ground_level";
+#endif // HAS_EXTENDED_TELEMETRY
+
+static const char SECTION_PROTO_OPTS[] = "protocol_opts";
+/* Section: Mixer */
+static const char SECTION_MIXER[] = "mixer";
+
+static const char MIXER_SOURCE[] = "src";
+static const char MIXER_DEST[] = "dest";
+static const char MIXER_SWITCH[] = "switch";
+static const char MIXER_SCALAR[] = "scalar";
+static const char MIXER_OFFSET[] = "offset";
+static const char MIXER_USETRIM[] = "usetrim";
+
+static const char MIXER_MUXTYPE[] = "muxtype";
+static const char * const MIXER_MUXTYPE_VAL[MUX_LAST] = {
+ "replace", "multiply", "add", "max", "min", "delay",
+#if HAS_EXTENDED_AUDIO
+ "beep","voice",
+#endif
+};
+
+static const char MIXER_CURVETYPE[] = "curvetype";
+static const char * const MIXER_CURVETYPE_VAL[CURVE_MAX+1] = {
+ "none", "fixed", "min/max", "zero/max", "greater-than-0", "less-than-0", "absval",
+ "expo", "deadband", "3point", "5point", "7point", "9point", "11point", "13point" };
+static const char MIXER_CURVE_POINTS[] = "points";
+static const char MIXER_CURVE_SMOOTH[] = "smooth";
+
+/* Section: Channel */
+static const char SECTION_CHANNEL[] = "channel";
+
+static const char CHAN_DISPLAY_FORMAT[] = "display-format";
+static const char CHAN_DISPLAY_SCALE[] = "display-scale";
+static const char CHAN_LIMIT_REVERSE[] = "reverse";
+static const char CHAN_LIMIT_SAFETYSW[] = "safetysw";
+static const char CHAN_LIMIT_SAFETYVAL[] = "safetyval";
+static const char CHAN_LIMIT_FAILSAFE[] = "failsafe";
+static const char CHAN_LIMIT_MAX[] = "max";
+static const char CHAN_LIMIT_MIN[] = "min";
+static const char CHAN_LIMIT_SPEED[] = "speed";
+static const char CHAN_SUBTRIM[] = "subtrim";
+static const char CHAN_SCALAR_NEG[] = "scalar-";
+#define CHAN_SCALAR MIXER_SCALAR
+#define CHAN_TEMPLATE MODEL_TEMPLATE
+static const char * const CHAN_TEMPLATE_VAL[MIXERTEMPLATE_MAX+1] =
+ { "none", "simple", "expo_dr", "complex", "cyclic1", "cyclic2", "cyclic3" };
+
+/* Section: Virtual Channel */
+static const char SECTION_VIRTCHAN[] = "virtchan";
+#define VCHAN_TEMPLATE CHAN_TEMPLATE
+#define VCHAN_TEMPLATE_VAL CHAN_TEMPLATE_VAL
+#define VCHAN_NAME MODEL_NAME
+
+/* Section: PPM-In */
+static const char SECTION_PPMIN[] = "ppm-in";
+static const char PPMIN_MAP[] = "map";
+static const char PPMIN_MODE[] = "mode";
+static const char * const PPMIN_MODE_VALUE[4] = {"none", "channel", "stick", "extend"};
+static const char PPMIN_CENTERPW[] = "centerpw";
+static const char PPMIN_DELTAPW[] = "deltapw";
+#define PPMIN_NUM_CHANNELS RADIO_NUM_CHANNELS
+#define PPMIN_SWITCH MIXER_SWITCH
+
+/* Section: Trim */
+static const char SECTION_TRIM[] = "trim";
+
+#define TRIM_SOURCE MIXER_SOURCE
+static const char TRIM_POS[] = "pos";
+static const char TRIM_NEG[] = "neg";
+static const char TRIM_STEP[] = "step";
+static const char TRIM_VALUE[] = "value";
+#define TRIM_SWITCH MIXER_SWITCH
+
+/* Section: Heli */
+static const char SECTION_SWASH[] = "swash";
+#define SWASH_TYPE MODEL_TYPE
+static const char SWASH_AIL_INV[] = "ail_inv";
+static const char SWASH_ELE_INV[] = "ele_inv";
+static const char SWASH_COL_INV[] = "col_inv";
+static const char SWASH_AILMIX[] = "ail_mix";
+static const char SWASH_ELEMIX[] = "ele_mix";
+static const char SWASH_COLMIX[] = "col_mix";
+
+/* Section: Timer */
+static const char SECTION_TIMER[] = "timer";
+
+#define TIMER_SOURCE MIXER_SOURCE
+#define TIMER_TYPE MODEL_TYPE
+static const char * const TIMER_TYPE_VAL[TIMER_LAST] = {
+ [TIMER_STOPWATCH] = "stopwatch",
+ [TIMER_STOPWATCH_PROP] = "stop-prop",
+ [TIMER_COUNTDOWN] = "countdown",
+ [TIMER_COUNTDOWN_PROP] = "cntdn-prop",
+ [TIMER_PERMANENT] = "permanent",
+ };
+static const char TIMER_TIME[] = "time";
+static const char TIMER_RESETSRC[] = "resetsrc";
+#if HAS_PERMANENT_TIMER
+static const char PERMANENT_TIMER[] = "permanent_timer";
+#endif
+static const char TIMER_VAL[] = "val";
+
+/* Section: Safety */
+static const char SECTION_SAFETY[] = "safety";
+static const char * const SAFETY_VAL[SAFE_MAX+1] = { "none", "min", "zero", "max" };
+
+/* Section: Telemetry */
+static const char SECTION_TELEMALARM[] = "telemalarm";
+static const char TELEM_SRC[] = "source";
+static const char TELEM_ABOVE[] = "above";
+static const char TELEM_VALUE[] = "value";
+static const char TELEM_THRESHOLD[] ="threshold";
+
+#if HAS_DATALOG
+/* Section: Datalog */
+static const char SECTION_DATALOG[] = "datalog";
+static const char DATALOG_RATE[] = "rate";
+#define DATALOG_SWITCH MIXER_SWITCH
+#define DATALOG_SOURCE TELEM_SRC
+#endif
+
+/* Section: Gui-QVGA */
+#define STRINGIFY(x) _STRINGIFY(x)
+#define _STRINGIFY(x) #x
+static const char SECTION_GUI[] = "gui-" STRINGIFY(LCD_WIDTH) "x" STRINGIFY(LCD_HEIGHT);
+static const char GUI_QUICKPAGE[] = "quickpage";
+
+#if HAS_EXTENDED_AUDIO
+/* Section: Music */
+static const char SECTION_VOICE[] = "voice";
+static const char * const VOICE_TELEMALARM[TELEM_NUM_ALARMS] =
+ { "telemalarm1", "telemalarm2", "telemalarm3", "telemalarm4", "telemalarm5", "telemalarm6" };
+static const char * const VOICE_TIMER[NUM_TIMERS] =
+ { "timer1", "timer2", "timer3", "timer4" };
+
+#endif
+
+
+static s8 mapstrcasecmp(const char *s1, const char *s2)
+{
+ int i = 0;
+ while(1) {
+ if(s1[i] == s2[i]
+ || (s2[i] >= 'a' && s1[i] + ('a'-'A') == s2[i])
+ || (s1[i] >= 'a' && s2[i] + ('a'-'A') == s1[i])
+ || (s2[i] == ' ' && s1[i] == '_')
+ || (s1[i] == ' ' && s2[i] == '_'))
+ {
+ if(s1[i] == '\0')
+ return 0;
+ i++;
+ continue;
+ }
+ return(s1[i] < s2[i] ? -1 : 1);
+ }
+}
+static u8 get_source(const char *section, const char *value)
+{
+ unsigned i;
+ unsigned val;
+ const char *ptr = (value[0] == '!') ? value + 1 : value;
+ const char *tmp;
+ char cmp[10];
+ for (i = 0; i <= NUM_SOURCES; i++) {
+ if(mapstrcasecmp(INPUT_SourceNameReal(cmp, i), ptr) == 0) {
+ #if defined(HAS_SWITCHES_NOSTOCK) && HAS_SWITCHES_NOSTOCK
+ #define SWITCH_NOSTOCK ((1 << INP_HOLD0) | (1 << INP_HOLD1) | \
+ (1 << INP_FMOD0) | (1 << INP_FMOD1))
+ if ((Transmitter.ignore_src & SWITCH_NOSTOCK) == SWITCH_NOSTOCK) {
+ if(mapstrcasecmp("FMODE0", ptr) == 0 ||
+ mapstrcasecmp("FMODE1", ptr) == 0 ||
+ mapstrcasecmp("HOLD0", ptr) == 0 ||
+ mapstrcasecmp("HOLD1", ptr) == 0)
+ break;
+ }
+ #endif //HAS_SWITCHES_NOSTOCK
+ return ((ptr == value) ? 0 : 0x80) | i;
+ }
+ }
+ for (i = 0; i < 4; i++) {
+ if(mapstrcasecmp(tx_stick_names[i], ptr) == 0) {
+ return ((ptr == value) ? 0 : 0x80) | (i + 1);
+ }
+ }
+ i = 0;
+ while((tmp = INPUT_MapSourceName(i++, &val))) {
+ if(mapstrcasecmp(tmp, ptr) == 0) {
+ return ((ptr == value) ? 0 : 0x80) | val;
+ }
+ }
+ printf("%s: Could not parse Source %s\n", section, value);
+ return 0;
+}
+
+static u8 get_button(const char *section, const char *value)
+{
+ u8 i;
+ for (i = 0; i <= NUM_TX_BUTTONS; i++) {
+ if(strcasecmp(INPUT_ButtonName(i), value) == 0) {
+ return i;
+ }
+ }
+ printf("%s: Could not parse Button %s\n", section, value);
+ return 0;
+}
+
+static int handle_proto_opts(struct Model *m, const char* key, const char* value, const char **opts)
+{
+ const char **popts = opts;
+ int idx = 0;
+ while(*popts) {
+ if(mapstrcasecmp(*popts, key) == 0) {
+ popts++;
+ int start = exact_atoi(popts[0]);
+ int end = exact_atoi(popts[1]);
+ int is_num = ((start != 0 || end != 0) && (popts[2] == 0 || (popts[3] == 0 && exact_atoi(popts[2]) != 0))) ? 1 : 0;
+ if(is_num) {
+ m->proto_opts[idx] = atoi(value);
+ return 1;
+ }
+ int val = 0;
+ while(popts[val]) {
+ if(mapstrcasecmp(popts[val], value) == 0) {
+ m->proto_opts[idx] = val;
+ return 1;
+ }
+ val++;
+ }
+ printf("Unknown protocol option '%s' for '%s'\n", value, key);
+ return 1;
+ }
+ //Find end of options
+ while(*popts) {
+ popts++;
+ }
+ popts++; //Go to next option
+ idx++;
+ }
+ return 0;
+}
+
+enum {
+ S8,
+ U8,
+ S16
+};
+static const char * parse_partial_int_list(const char *ptr, void *vals, int *max_count, int type)
+{
+ //const char *origptr = ptr;
+ int value_int = 0;
+ int idx = 0;
+ int sign = 0;
+
+ while(1) {
+ if(*ptr == ',' || *ptr == '\0') {
+ value_int = value_int * (sign ? -1 : 1);
+ if (type == S8) {
+ if (value_int > 127)
+ value_int = 127;
+ else if (value_int < -127)
+ value_int = -127;
+ ((s8 *)vals)[idx] = value_int;
+ } else if (type == U8) {
+ if (value_int > 255)
+ value_int = 255;
+ else if (value_int < 0)
+ value_int = 0;
+ ((u8 *)vals)[idx] = value_int;
+ } else {
+ ((s16 *)vals)[idx] = value_int;
+ }
+ sign = 0;
+ value_int = 0;
+ idx++;
+ --*max_count;
+ if (*max_count == 0 || *ptr == '\0')
+ return ptr;
+ } else if(*ptr == '-') {
+ sign = 1;
+ } else if (*ptr >= '0' && *ptr <= '9') {
+ value_int = value_int * 10 + (*ptr - '0');
+ } else {
+ //printf("Bad value '%c' in '%s'\n", *ptr, origptr);
+ return ptr;
+ }
+ ptr++;
+ }
+}
+
+static int parse_int_list(const char *ptr, void *vals, int max_count, int type)
+{
+ int count = max_count;
+ parse_partial_int_list(ptr, vals, &count, type);
+ return max_count - count;
+}
+
+static void create_element(struct elem *elem, int type, s16 *data)
+{
+ //int x, int y, int src, int e0, int e1, int e2)
+ ELEM_SET_X(*elem, data[0]);
+ ELEM_SET_Y(*elem, data[1]);
+ ELEM_SET_TYPE(*elem, type);
+ elem->src = data[5];
+ elem->extra[0] = data[2];
+ elem->extra[1] = data[3];
+ elem->extra[2] = data[4];
+}
+
+static int layout_ini_handler(void* user, const char* section, const char* name, const char* value)
+{
+ struct Model *m = (struct Model *)user;
+ u16 i;
+ int offset_x = 0, offset_y = 0;
+ CLOCK_ResetWatchdog();
+ int idx;
+ if (MATCH_START(name, GUI_QUICKPAGE)) {
+ u8 idx = name[9] - '1';
+ if (idx >= NUM_QUICKPAGES) {
+ printf("%s: Only %d quickpages are supported\n", section, NUM_QUICKPAGES);
+ return 1;
+ }
+ int max = PAGE_GetNumPages();
+ for(i = 0; i < max; i++) {
+ if(mapstrcasecmp(PAGE_GetName(i), value) == 0) {
+ m->pagecfg2.quickpage[idx] = i;
+ return 1;
+ }
+ }
+ printf("%s: Unknown page '%s' for quickpage%d\n", section, value, idx+1);
+ return 1;
+ }
+#ifdef ENABLE_320x240_GUI
+ static u8 seen_res = 0;
+ enum {
+ LOWRES = 1,
+ HIRES,
+ };
+ if (! MATCH_SECTION(SECTION_GUI)) {
+ if(MATCH_SECTION("gui-320x240")
+ && (! ELEM_USED(Model.pagecfg2.elem[0]) || seen_res != HIRES))
+ {
+ seen_res = LOWRES;
+ offset_x = (LCD_WIDTH - 320) / 2;
+ offset_y = (LCD_HEIGHT - 240) / 2;
+ } else
+ return 1;
+ } else {
+ if (seen_res == LOWRES) {
+ memset(&Model.pagecfg2.elem, 0, sizeof(Model.pagecfg2.elem));
+ }
+ seen_res = HIRES;
+ }
+#else
+ if (! MATCH_SECTION(SECTION_GUI))
+ return 1;
+#endif
+ for (idx = 0; idx < NUM_ELEMS; idx++) {
+ if (! ELEM_USED(Model.pagecfg2.elem[idx]))
+ break;
+ }
+
+ if (idx == NUM_ELEMS) {
+ printf("No free element available (max = %d)\n", NUM_ELEMS);
+ return 1;
+ }
+ int type;
+ for (type = 0; type < ELEM_LAST; type++)
+ if(mapstrcasecmp(name, GetElemName(type)) == 0)
+ break;
+ if (type == ELEM_LAST)
+ return 1;
+ int count = 5;
+ s16 data[6] = {0};
+ const char *ptr = parse_partial_int_list(value, data, &count, S16);
+ data[0] += offset_x;
+ data[1] += offset_y;
+ if (count > 3) {
+ printf("Could not parse coordinates from %s=%s\n", name,value);
+ return 1;
+ }
+ switch(type) {
+ //case ELEM_MODEL: //x, y
+ case ELEM_VTRIM: //x, y, src
+ case ELEM_HTRIM: //x, y, src
+ data[5] = data[2];
+ data[2] = 0;
+ break;
+ case ELEM_SMALLBOX: //x, y, src
+ case ELEM_BIGBOX: //x. y. src
+ {
+ s16 src = -1;
+ char str[20];
+ if (count != 3)
+ return 1;
+#if HAS_RTC
+ for(i = 0; i < NUM_RTC; i++) {
+ if(mapstrcasecmp(ptr, RTC_Name(str, i)) == 0) {
+ src = i + 1;
+ break;
+ }
+ }
+#endif
+ if (src == -1) {
+ for(i = 0; i < NUM_TIMERS; i++) {
+ if(mapstrcasecmp(ptr, TIMER_Name(str, i)) == 0) {
+ src = i + 1 + NUM_RTC;
+ break;
+ }
+ }
+ }
+ if (src == -1) {
+ for(i = 0; i < NUM_TELEM; i++) {
+ if(mapstrcasecmp(ptr, TELEMETRY_Name(str, i+1)) == 0) {
+ src = i + 1 + NUM_RTC + NUM_TIMERS;
+ break;
+ }
+ }
+ }
+ if (src == -1) {
+ u8 newsrc = get_source(section, ptr);
+ if(newsrc >= NUM_INPUTS) {
+ src = newsrc - (NUM_INPUTS + 1 - (NUM_RTC + NUM_TIMERS + NUM_TELEM + 1));
+ }
+ }
+ if (src == -1)
+ src = 0;
+ data[5] = src;
+ break;
+ }
+ case ELEM_BAR: //x, y, src
+ {
+ if (count != 3)
+ return 1;
+ u8 src = get_source(section, ptr);
+ if (src < NUM_INPUTS)
+ src = 0;
+ data[5] = src - NUM_INPUTS;
+ break;
+ }
+ case ELEM_TOGGLE: //x, y, tgl0, tgl1, tgl2, src
+ {
+ if(count)
+ return 1;
+ for (int j = 0; j <= NUM_SOURCES; j++) {
+ char cmp[10];
+ if(mapstrcasecmp(INPUT_SourceNameAbbrevSwitchReal(cmp, j), ptr+1) == 0) {
+ data[5] = j;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ create_element(&m->pagecfg2.elem[idx], type, data);
+ return 1;
+}
+
+struct struct_map {const char *str; u16 offset; u16 defval;};
+#define MAPSIZE(x) (sizeof(x) / sizeof(struct struct_map))
+#define OFFSET(s,v) (((long)(&s.v) - (long)(&s)) | ((sizeof(s.v)-1) << 13))
+#define OFFSETS(s,v) (((long)(&s.v) - (long)(&s)) | ((sizeof(s.v)+3) << 13))
+#define OFFSET_SRC(s,v) (((long)(&s.v) - (long)(&s)) | (2 << 13))
+#define OFFSET_BUT(s,v) (((long)(&s.v) - (long)(&s)) | (6 << 13))
+#if HAS_PERMANENT_TIMER
+static const struct struct_map _secnone[] =
+{
+ {PERMANENT_TIMER, OFFSET(Model, permanent_timer), 0},
+};
+#endif
+static const struct struct_map _secradio[] = {
+ {RADIO_NUM_CHANNELS, OFFSET(Model, num_channels), 0},
+ {RADIO_FIXED_ID, OFFSET(Model, fixed_id), 0},
+#if HAS_VIDEO
+ {RADIO_VIDEOSRC, OFFSET_SRC(Model, videosrc), 0},
+ {RADIO_VIDEOCH, OFFSET(Model, videoch), 0},
+ {RADIO_VIDEOCONTRAST,OFFSET(Model, video_contrast), 0},
+ {RADIO_VIDEOBRIGHTNESS,OFFSET(Model, video_brightness), 0},
+#endif
+#if HAS_EXTENDED_TELEMETRY
+ {RADIO_GROUND_LEVEL, OFFSET(Model, ground_level), 0},
+#endif
+};
+static const struct struct_map _secmixer[] = {
+ {MIXER_SWITCH, OFFSET_SRC(Model.mixers[0], sw), 0},
+ {MIXER_SCALAR, OFFSETS(Model.mixers[0], scalar), 100},
+ {MIXER_OFFSET, OFFSETS(Model.mixers[0], offset), 0},
+};
+static const struct struct_map _seclimit[] = {
+ {CHAN_LIMIT_SAFETYSW, OFFSET_SRC(Model.limits[0], safetysw), 0},
+ {CHAN_LIMIT_SAFETYVAL, OFFSETS(Model.limits[0], safetyval), 0},
+ {CHAN_LIMIT_MAX, OFFSET(Model.limits[0], max), DEFAULT_SERVO_LIMIT},
+ {CHAN_LIMIT_SPEED, OFFSET(Model.limits[0], speed), 0},
+ {CHAN_SCALAR, OFFSET(Model.limits[0], servoscale), 100},
+ {CHAN_SCALAR_NEG, OFFSET(Model.limits[0], servoscale_neg), 0},
+ {CHAN_SUBTRIM, OFFSETS(Model.limits[0], subtrim), 0},
+ {CHAN_DISPLAY_SCALE, OFFSETS(Model.limits[0], displayscale), DEFAULT_DISPLAY_SCALE},
+};
+static const struct struct_map _sectrim[] = {
+ {TRIM_SOURCE, OFFSET_SRC(Model.trims[0], src), 0xFFFF},
+ {TRIM_POS, OFFSET_BUT(Model.trims[0], pos), 0},
+ {TRIM_NEG, OFFSET_BUT(Model.trims[0], neg), 0},
+ {TRIM_STEP, OFFSET(Model.trims[0], step), 1},
+};
+static const struct struct_map _secswash[] = {
+ {SWASH_AILMIX, OFFSET(Model, swashmix[0]), 60},
+ {SWASH_ELEMIX, OFFSET(Model, swashmix[1]), 60},
+ {SWASH_COLMIX, OFFSET(Model, swashmix[2]), 60},
+};
+static const struct struct_map _sectimer[] = {
+ {TIMER_TIME, OFFSET(Model.timer[0], timer), 0xFFFF},
+ {TIMER_VAL, OFFSET(Model.timer[0], val), 0xFFFF},
+ {TIMER_SOURCE, OFFSET_SRC(Model.timer[0], src), 0},
+ {TIMER_RESETSRC, OFFSET_SRC(Model.timer[0], resetsrc), 0},
+};
+static const struct struct_map _secppm[] = {
+ {PPMIN_CENTERPW, OFFSET(Model, ppmin_centerpw), 0},
+ {PPMIN_DELTAPW, OFFSET(Model, ppmin_deltapw), 0},
+ {PPMIN_SWITCH, OFFSET_SRC(Model, train_sw), 0xFFFF},
+};
+static int ini_handler(void* user, const char* section, const char* name, const char* value)
+{
+ int value_int = atoi(value);
+ struct Model *m = (struct Model *)user;
+int assign_int(void* ptr, const struct struct_map *map, int map_size)
+{
+ for(int i = 0; i < map_size; i++) {
+ if(MATCH_KEY(map[i].str)) {
+ int size = map[i].offset >> 13;
+ int offset = map[i].offset & 0x1FFF;
+ switch(size) {
+ case 0:
+ *((u8 *)((long)ptr + offset)) = value_int; break;
+ case 1:
+ *((u16 *)((long)ptr + offset)) = value_int; break;
+ case 2:
+ *((u8 *)((long)ptr + offset)) = get_source(section, value); break;
+ case 3:
+ *((u32 *)((long)ptr + offset)) = value_int; break;
+ case 4:
+ *((s8 *)((long)ptr + offset)) = value_int; break;
+ case 5:
+ *((s16 *)((long)ptr + offset)) = value_int; break;
+ case 6:
+ *((u8 *)((long)ptr + offset)) = get_button(section, value); break;
+ case 7:
+ *((s32 *)((long)ptr + offset)) = value_int; break;
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+ CLOCK_ResetWatchdog();
+ unsigned i;
+ if (MATCH_SECTION("")) {
+ if(MATCH_KEY(MODEL_NAME)) {
+ strlcpy(m->name, value, sizeof(m->name)-1);
+ return 1;
+ }
+ if(MATCH_KEY(MODEL_TEMPLATE)) {
+ //A dummy rule
+ return 1;
+ }
+ if (MATCH_KEY(MODEL_ICON)) {
+ CONFIG_ParseIconName(m->icon, value);
+ return 1;
+ }
+#if HAS_PERMANENT_TIMER
+ if(assign_int(&Model, _secnone, MAPSIZE(_secnone)))
+ return 1;
+#endif
+ if (MATCH_KEY(MODEL_TYPE)) {
+ for (i = 0; i < NUM_STR_ELEMS(MODEL_TYPE_VAL); i++) {
+ if (MATCH_VALUE(MODEL_TYPE_VAL[i])) {
+ m->type = i;
+ return 1;
+ }
+ }
+ return 0;
+ }
+ if (MATCH_KEY(MODEL_AUTOMAP)) {
+ auto_map = atoi(value);
+ return 1;
+ }
+ if (MATCH_KEY(MODEL_MIXERMODE)) {
+ for(i = 1; i < 3; i++) {
+ if(MATCH_VALUE(STDMIXER_ModeName(i)))
+ m->mixer_mode = i;
+ }
+ return 1;
+ }
+ }
+ if (MATCH_SECTION(SECTION_RADIO)) {
+ if (MATCH_KEY(RADIO_PROTOCOL)) {
+ for (i = 0; i < PROTOCOL_COUNT; i++) {
+ if (MATCH_VALUE(PROTOCOL_GetName(i))) {
+ m->protocol = i;
+ m->radio = PROTOCOL_GetRadio(i);
+ PROTOCOL_Load(1);
+ return 1;
+ }
+ }
+ printf("Unknown protocol: %s\n", value);
+ return 1;
+ }
+ if(assign_int(&Model, _secradio, MAPSIZE(_secradio)))
+ return 1;
+ if (MATCH_KEY(RADIO_TX_POWER)) {
+ if (m->radio == TX_MODULE_LAST) {
+ m->tx_power = TXPOWER_150mW;
+ return 1;
+ }
+ for (i = 0; i < RADIO_TX_POWER_COUNT[m->radio]; i++) {
+ if (MATCH_VALUE(radio_tx_power_val(m->radio, i))) {
+ m->tx_power = i;
+ return 1;
+ }
+ }
+ printf("Unknown Tx power: %s\n", value);
+ m->tx_power = RADIO_TX_POWER_COUNT[m->radio]-1; // default to radio maximum
+ return 1;
+ }
+ printf("Unknown Radio Key: %s\n", name);
+ return 0;
+ }
+ if (MATCH_SECTION(SECTION_PROTO_OPTS)) {
+ const char **opts = PROTOCOL_GetOptions();
+ if (!opts || ! *opts)
+ return 1;
+ return handle_proto_opts(m, name, value, opts);
+ }
+ if (MATCH_START(section, SECTION_MIXER)) {
+ int idx;
+ for (idx = 0; idx < NUM_MIXERS; idx++) {
+ if(m->mixers[idx].src == 0)
+ break;
+ }
+ if (MATCH_KEY(MIXER_SOURCE)) {
+ if (idx == NUM_MIXERS) {
+ printf("%s: Only %d mixers are supported\n", section, NUM_MIXERS);
+ return 1;
+ }
+ m->mixers[idx].src = get_source(section, value);
+ return 1;
+ }
+ idx--;
+ if (MATCH_KEY(MIXER_DEST)) {
+ m->mixers[idx].dest = get_source(section, value) - NUM_INPUTS - 1;
+ return 1;
+ }
+ if(assign_int(&m->mixers[idx], _secmixer, MAPSIZE(_secmixer)))
+ return 1;
+ if (MATCH_KEY(MIXER_USETRIM)) {
+ MIXER_SET_APPLY_TRIM(&m->mixers[idx], value_int);
+ return 1;
+ }
+ if (MATCH_KEY(MIXER_MUXTYPE)) {
+ for (i = 0; i < NUM_STR_ELEMS(MIXER_MUXTYPE_VAL); i++) {
+ if (MATCH_VALUE(MIXER_MUXTYPE_VAL[i])) {
+ MIXER_SET_MUX(&m->mixers[idx], i);
+ return 1;
+ }
+ }
+ printf("%s: Unknown Mux type: %s\n", section, value);
+ return 1;
+ }
+ if (MATCH_KEY(MIXER_CURVETYPE)) {
+ for (i = 0; i < NUM_STR_ELEMS(MIXER_CURVETYPE_VAL); i++) {
+ if (MATCH_VALUE(MIXER_CURVETYPE_VAL[i])) {
+ CURVE_SET_TYPE(&m->mixers[idx].curve, i);
+ return 1;
+ }
+ }
+ printf("%s: Unknown Curve type: %s\n", section, value);
+ return 1;
+ }
+ if (MATCH_KEY(MIXER_CURVE_POINTS)) {
+ int count = parse_int_list(value, m->mixers[idx].curve.points, MAX_POINTS, S8);
+ if (count > MAX_POINTS) {
+ printf("%s: Too many points (max points = %d\n", section, MAX_POINTS);
+ return 0;
+ }
+ return 1;
+ }
+ if (MATCH_KEY(MIXER_CURVE_SMOOTH)) {
+ CURVE_SET_SMOOTHING(&m->mixers[idx].curve, value_int);
+ return 1;
+ }
+ printf("%s: Couldn't parse key: %s\n", section, name);
+ return 0;
+ }
+ if (MATCH_START(section, SECTION_CHANNEL)) {
+ u8 idx = atoi(section + sizeof(SECTION_CHANNEL)-1);
+ if (idx == 0) {
+ printf("Unknown Channel: %s\n", section);
+ return 0;
+ }
+ if (idx > NUM_OUT_CHANNELS) {
+ printf("%s: Only %d channels are supported\n", section, NUM_OUT_CHANNELS);
+ return 1;
+ }
+ idx--;
+ if (MATCH_KEY(CHAN_LIMIT_REVERSE)) {
+ if (value_int)
+ m->limits[idx].flags |= CH_REVERSE;
+ else
+ m->limits[idx].flags &= ~CH_REVERSE;
+ return 1;
+ }
+ if (MATCH_KEY(CHAN_LIMIT_FAILSAFE)) {
+ if(strcasecmp("off", value) == 0) {
+ m->limits[idx].flags &= ~CH_FAILSAFE_EN;
+ } else {
+ m->limits[idx].failsafe = value_int;
+ m->limits[idx].flags |= CH_FAILSAFE_EN;
+ }
+ return 1;
+ }
+ if (MATCH_KEY(CHAN_DISPLAY_FORMAT)) {
+ strcpy(m->limits[idx].displayformat, value);
+ return 1;
+ }
+
+ if(assign_int(&m->limits[idx], _seclimit, MAPSIZE(_seclimit)))
+ return 1;
+ if (MATCH_KEY(CHAN_LIMIT_MIN)) {
+ m->limits[idx].min = -value_int;
+ return 1;
+ }
+ if (MATCH_KEY(CHAN_TEMPLATE)) {
+ for (i = 0; i < NUM_STR_ELEMS(CHAN_TEMPLATE_VAL); i++) {
+ if (MATCH_VALUE(CHAN_TEMPLATE_VAL[i])) {
+ m->templates[idx] = i;
+ return 1;
+ }
+ }
+ printf("%s: Unknown template: %s\n", section, value);
+ return 1;
+ }
+ printf("%s: Unknown key: %s\n", section, name);
+ return 0;
+ }
+ if (MATCH_START(section, SECTION_VIRTCHAN)) {
+ u8 idx = atoi(section + sizeof(SECTION_VIRTCHAN)-1);
+ if (idx == 0) {
+ printf("Unknown Virtual Channel: %s\n", section);
+ return 0;
+ }
+ if (idx > NUM_VIRT_CHANNELS) {
+ printf("%s: Only %d virtual channels are supported\n", section, NUM_VIRT_CHANNELS);
+ return 1;
+ }
+ idx = idx + NUM_OUT_CHANNELS - 1;
+ if (MATCH_KEY(VCHAN_TEMPLATE)) {
+ for (i = 0; i < NUM_STR_ELEMS(VCHAN_TEMPLATE_VAL); i++) {
+ if (MATCH_VALUE(VCHAN_TEMPLATE_VAL[i])) {
+ m->templates[idx] = i;
+ return 1;
+ }
+ }
+ printf("%s: Unknown template: %s\n", section, value);
+ return 1;
+ }
+ if (MATCH_KEY(VCHAN_NAME)) {
+ strlcpy(m->virtname[idx - NUM_OUT_CHANNELS], value, sizeof(m->virtname[0]));
+ return 1;
+ }
+ printf("%s: Unknown key: %s\n", section, name);
+ return 0;
+ }
+ if (MATCH_START(section, SECTION_TRIM)) {
+ u8 idx = atoi(section + sizeof(SECTION_TRIM)-1);
+ if (idx == 0) {
+ printf("Unknown Trim: %s\n", section);
+ return 0;
+ }
+ if (idx > NUM_TRIMS) {
+ printf("%s: Only %d trims are supported\n", section, NUM_TRIMS);
+ return 1;
+ }
+ idx--;
+ if(assign_int(&m->trims[idx], _sectrim, MAPSIZE(_sectrim)))
+ return 1;
+ if (MATCH_KEY(TRIM_SWITCH)) {
+ for (int i = 0; i <= NUM_SOURCES; i++) {
+ char cmp[10];
+ if(mapstrcasecmp(INPUT_SourceNameAbbrevSwitchReal(cmp, i), value) == 0) {
+ m->trims[idx].sw = i;
+ return 1;
+ }
+ }
+ return 1;
+ }
+ if (MATCH_KEY(TRIM_VALUE)) {
+ parse_int_list(value, m->trims[idx].value, 6, S8);
+ return 1;
+ }
+ printf("%s: Unknown trim setting: %s\n", section, name);
+ return 0;
+ }
+ if (MATCH_SECTION(SECTION_SWASH)) {
+ if (MATCH_KEY(SWASH_TYPE)) {
+ for (i = SWASH_TYPE_NONE; i <= SWASH_TYPE_90; i++) {
+ if(strcasecmp(MIXER_SwashType(i), value) == 0) {
+ m->swash_type = i;
+ return 1;
+ }
+ }
+ printf("%s: Unknown swash_type: %s\n", section, value);
+ return 1;
+ }
+ if (MATCH_KEY(SWASH_ELE_INV)) {
+ if (value_int)
+ m->swash_invert |= 0x01;
+ return 1;
+ }
+ if (MATCH_KEY(SWASH_AIL_INV)) {
+ if (value_int)
+ m->swash_invert |= 0x02;
+ return 1;
+ }
+ if (MATCH_KEY(SWASH_COL_INV)) {
+ if (value_int)
+ m->swash_invert |= 0x04;
+ return 1;
+ }
+ if(assign_int(m, _secswash, MAPSIZE(_secswash)))
+ return 1;
+ }
+ if (MATCH_START(section, SECTION_TIMER)) {
+ u8 idx = atoi(section + sizeof(SECTION_TIMER)-1);
+ if (idx == 0) {
+ printf("Unknown Timer: %s\n", section);
+ return 0;
+ }
+ if (idx > NUM_TIMERS) {
+ printf("%s: Only %d timers are supported\n", section, NUM_TIMERS);
+ return 1;
+ }
+ idx--;
+ if (MATCH_KEY(TIMER_TYPE)) {
+ for (i = 0; i < NUM_STR_ELEMS(TIMER_TYPE_VAL); i++) {
+ if (MATCH_VALUE(TIMER_TYPE_VAL[i])) {
+ m->timer[idx].type = i;
+ return 1;
+ }
+ }
+ printf("%s: Unknown timer type: %s\n", section, value);
+ return 1;
+ }
+ if(assign_int(&m->timer[idx], _sectimer, MAPSIZE(_sectimer)))
+ return 1;
+ }
+ if (MATCH_START(section, SECTION_TELEMALARM)) {
+ u8 idx = atoi(section + sizeof(SECTION_TELEMALARM)-1);
+ if (idx == 0) {
+ printf("Unknown Telem-alarm: %s\n", section);
+ return 0;
+ }
+ if (idx > TELEM_NUM_ALARMS) {
+ printf("%s: Only %d timers are supported\n", section, TELEM_NUM_ALARMS);
+ return 1;
+ }
+ struct TelemetryAlarm *alarm = &Model.alarms[idx - 1]; // idx is 1 based
+ if (MATCH_KEY(TELEM_SRC)) {
+ char str[20];
+ unsigned last = TELEMETRY_GetNumTelemSrc();
+ for(i = 1; i <= last; i++) {
+ if (strcasecmp(TELEMETRY_ShortName(str, i), value) == 0) {
+ alarm->src = i;
+ return 1;
+ }
+ }
+ printf("%s: Unknown telemetry src: %s\n", section, value);
+ return 0;
+ }
+ if (MATCH_KEY(TELEM_ABOVE)) {
+ if (atoi(value))
+ alarm->above = 1;
+ else
+ alarm->above = 0;
+ return 1;
+ }
+ if (MATCH_KEY(TELEM_VALUE)) {
+ alarm->value = atoi(value);
+ return 1;
+ }
+ if (MATCH_KEY(TELEM_THRESHOLD)) {
+ alarm->threshold = atoi(value);
+ return 1;
+ }
+ }
+#if HAS_DATALOG
+ if (MATCH_SECTION(SECTION_DATALOG)) {
+ if (MATCH_KEY(DATALOG_SWITCH)) {
+ m->datalog.enable = get_source(section, value);
+ } else if (MATCH_KEY(DATALOG_RATE)) {
+ for (i = 0; i < DLOG_RATE_LAST; i++) {
+ if(mapstrcasecmp(DATALOG_RateString(i), value) == 0) {
+ m->datalog.rate = i;
+ break;
+ }
+ }
+ } else if (MATCH_KEY(DATALOG_SOURCE)) {
+ char cmp[10];
+ for (i = 0; i < DLOG_LAST; i++) {
+ if(mapstrcasecmp(DATALOG_Source(cmp, i), value) == 0) {
+ m->datalog.source[DATALOG_BYTE(i)] |= 1 << DATALOG_POS(i);
+ break;
+ }
+ }
+ }
+ return 1;
+ }
+#endif //HAS_DATALOG
+ if (MATCH_START(section, SECTION_SAFETY)) {
+ int found = 0;
+ u8 src;
+ if (MATCH_KEY("auto")) {
+ src = 0;
+ found = 1;
+ } else {
+ src = get_source(section, name);
+ }
+ if(found || src) {
+ u32 i;
+ for (i = 0; i < NUM_STR_ELEMS(SAFETY_VAL); i++) {
+ if (MATCH_VALUE(SAFETY_VAL[i])) {
+ m->safety[src] = i;
+ return 1;
+ }
+ }
+ }
+ }
+ if (MATCH_START(section, "gui-")) {
+ return layout_ini_handler(user, section, name, value);
+ }
+ if (MATCH_SECTION(SECTION_PPMIN)) {
+ if (MATCH_KEY(PPMIN_NUM_CHANNELS)) {
+ m->num_ppmin_channels = atoi(value);
+ return 1;
+ }
+ if (MATCH_KEY(PPMIN_MODE)) {
+ for(i = 0; i < 4; i++) {
+ if(mapstrcasecmp(PPMIN_MODE_VALUE[i], value) == 0) {
+ m->ppmin_mode = i;
+ return 1;
+ }
+ }
+ return 1;
+ }
+ if(assign_int(m, _secppm, MAPSIZE(_secppm)))
+ return 1;
+ if (MATCH_START(name, PPMIN_MAP)) {
+ u8 idx = atoi(name + sizeof(PPMIN_MAP)-1) -1;
+ if (idx < MAX_PPM_IN_CHANNELS) {
+ m->ppm_map[idx] = get_source(section, value);
+ if (PPMin_Mode() == PPM_IN_TRAIN1) {
+ m->ppm_map[idx] = (m->ppm_map[idx] <= NUM_INPUTS)
+ ? -1
+ : m->ppm_map[idx] - (NUM_INPUTS + 1);
+ }
+ }
+ return 1;
+ }
+ }
+#if HAS_EXTENDED_AUDIO
+ char src_name[20];
+
+ if (MATCH_SECTION(SECTION_VOICE)) {
+ u16 val = atoi(value);
+ if(val>MAX_VOICEMAP_ENTRIES-1 || voice_map[val].duration == 0 || val < CUSTOM_ALARM_ID) {
+ printf("%s: Music %s not found in voice.ini or below ID %d\n", section, value, CUSTOM_ALARM_ID);
+ return 0;
+ }
+ for (int i = INP_HAS_CALIBRATION+1; i <= NUM_INPUTS; i++) {
+ INPUT_SourceName(src_name, i);
+ if (MATCH_KEY(src_name)) {
+ m->voice.switches[i - INP_HAS_CALIBRATION - 1].music = val;
+ return 1;
+ }
+ }
+#if NUM_AUX_KNOBS
+ for (int i = 0; i < NUM_AUX_KNOBS; i++) {
+ INPUT_SourceName(src_name, i + NUM_STICKS + 1);
+ strcat(src_name, "_UP");
+ if (MATCH_KEY(src_name)) {
+ m->voice.aux[i * 2 + 1].music = val;
+ return 1;
+ }
+ INPUT_SourceName(src_name, i + NUM_STICKS + 1);
+ strcat(src_name, "_DOWN");
+ if (MATCH_KEY(src_name)) {
+ m->voice.aux[i * 2].music = val;
+ return 1;
+ }
+ }
+#endif
+ for (int i = 0; i < NUM_TIMERS; i++) {
+ if (MATCH_KEY(VOICE_TIMER[i])) {
+ m->voice.timer[i].music = val;
+ return 1;
+ }
+ }
+ for (int i = 0; i < TELEM_NUM_ALARMS; i++) {
+ if (MATCH_KEY(VOICE_TELEMALARM[i])) {
+ m->voice.telemetry[i].music = val;
+ return 1;
+ }
+ }
+ for (int i = 0; i < (NUM_OUT_CHANNELS + NUM_VIRT_CHANNELS); i++) {
+ INPUT_SourceNameReal(src_name, i + NUM_INPUTS + 1);
+ if (MATCH_KEY(src_name)) {
+ m->voice.mixer[i].music = val;
+ return 1;
+ }
+ }
+ printf("%s: unknown source name '%s'\n", section, name);
+ return 0;
+ }
+#endif
+ printf("Unknown Section: '%s'\n", section);
+ return 0;
+}
+
+static void get_model_file(char *file, u8 model_num)
+{
+ if (model_num == 0)
+ sprintf(file, "models/default.ini");
+ else
+ sprintf(file, "models/model%d.ini", model_num);
+}
+
+static void write_int(FILE *fh, void* ptr, const struct struct_map *map, int map_size)
+{
+ char tmpstr[20];
+ for(int i = 0; i < map_size; i++) {
+ int size = map[i].offset >> 13;
+ int offset = map[i].offset & 0x1FFF;
+ int value;
+ if (map[i].defval == 0xffff)
+ continue;
+ switch(size) {
+ case 0:
+ case 2: //SRC
+ case 6: //BUTTON
+ value = *((u8 *)((long)ptr + offset)); break;
+ case 1: value = *((u16 *)((long)ptr + offset)); break;
+ case 3: value = *((u32 *)((long)ptr + offset)); break;
+ case 4: value = *((s8 *)((long)ptr + offset)); break;
+ case 5: value = *((s16 *)((long)ptr + offset)); break;
+ case 7: value = *((s32 *)((long)ptr + offset)); break;
+ default: continue;
+ }
+ if(WRITE_FULL_MODEL || value != map[i].defval) {
+ if (2 == (size & 0x03)) //2, 6
+ fprintf(fh, "%s=%s\n", map[i].str, size == 2 ? INPUT_SourceNameReal(tmpstr, value) : INPUT_ButtonName(value));
+ else
+ fprintf(fh, "%s=%d\n", map[i].str, value);
+ }
+ }
+}
+
+static u8 write_mixer(FILE *fh, struct Model *m, u8 channel)
+{
+ int idx;
+ int i;
+ char tmpstr[20];
+ u8 changed = 0;
+ for(idx = 0; idx < NUM_MIXERS; idx++) {
+ if (! WRITE_FULL_MODEL && (m->mixers[idx].src == 0 || m->mixers[idx].dest != channel))
+ continue;
+ changed = 1;
+ fprintf(fh, "[%s]\n", SECTION_MIXER);
+ fprintf(fh, "%s=%s\n", MIXER_SOURCE, INPUT_SourceNameReal(tmpstr, m->mixers[idx].src));
+ fprintf(fh, "%s=%s\n", MIXER_DEST, INPUT_SourceNameReal(tmpstr, m->mixers[idx].dest + NUM_INPUTS + 1));
+ write_int(fh, &m->mixers[idx], _secmixer, MAPSIZE(_secmixer));
+ if(WRITE_FULL_MODEL || ! MIXER_APPLY_TRIM(&m->mixers[idx]))
+ fprintf(fh, "%s=%d\n", MIXER_USETRIM, MIXER_APPLY_TRIM(&m->mixers[idx]) ? 1 : 0);
+ if(WRITE_FULL_MODEL || MIXER_MUX(&m->mixers[idx]))
+ fprintf(fh, "%s=%s\n", MIXER_MUXTYPE, MIXER_MUXTYPE_VAL[MIXER_MUX(&m->mixers[idx])]);
+ if(WRITE_FULL_MODEL || CURVE_TYPE(&m->mixers[idx].curve)) {
+ fprintf(fh, "%s=%s\n", MIXER_CURVETYPE, MIXER_CURVETYPE_VAL[CURVE_TYPE(&m->mixers[idx].curve)]);
+ u8 num_points = CURVE_NumPoints(&m->mixers[idx].curve);
+ if (num_points > 0) {
+ fprintf(fh, "%s=", MIXER_CURVE_POINTS);
+ for (i = 0; i < num_points; i++) {
+ fprintf(fh, "%d", m->mixers[idx].curve.points[i]);
+ if (i != num_points - 1)
+ fprintf(fh, ",");
+ }
+ fprintf(fh, "\n");
+ }
+ if (CURVE_SMOOTHING(&m->mixers[idx].curve))
+ fprintf(fh, "%s=%d\n", MIXER_CURVE_SMOOTH, CURVE_SMOOTHING(&m->mixers[idx].curve) ? 1 : 0);
+ }
+ }
+ return changed;
+}
+
+static void write_proto_opts(FILE *fh, struct Model *m)
+{
+ const char **opts = PROTOCOL_GetOptions();
+ if (!opts || ! *opts) // bug fix: must check NULL ptr
+ return;
+ int idx = 0;
+ fprintf(fh, "[%s]\n", SECTION_PROTO_OPTS);
+ while(*opts) {
+ int start = exact_atoi(opts[1]);
+ int end = exact_atoi(opts[2]);
+ int is_num = ((start != 0 || end != 0) && (opts[3] == 0 || (opts[4] == 0 && exact_atoi(opts[3]) != 0))) ? 1 : 0;
+ if (is_num) {
+ fprintf(fh, "%s=%d\n",*opts, m->proto_opts[idx]);
+ } else {
+ fprintf(fh, "%s=%s\n",*opts, opts[m->proto_opts[idx]+1]);
+ }
+ opts++;
+ while(*opts) {
+ opts++;
+ }
+ opts++;
+ idx++;
+ }
+ fprintf(fh, "\n");
+}
+
+u8 CONFIG_WriteModel_old(u8 model_num) {
+ char file[20];
+ FILE *fh;
+ u8 idx;
+ struct Model *m = &Model;
+
+
+ get_model_file(file, model_num);
+ fh = fopen(file, "w");
+ if (! fh) {
+ printf("Couldn't open file: %s\n", file);
+ return 0;
+ }
+ CONFIG_EnableLanguage(0);
+ fprintf(fh, "%s=%s\n", MODEL_NAME, m->name);
+#if HAS_PERMANENT_TIMER
+ write_int(fh, m, _secnone, MAPSIZE(_secnone));
+#endif
+ fprintf(fh, "%s=%s\n", MODEL_MIXERMODE, STDMIXER_ModeName(m->mixer_mode));
+ if(m->icon[0] != 0)
+ fprintf(fh, "%s=%s\n", MODEL_ICON, m->icon + 9);
+ if(WRITE_FULL_MODEL || m->type != 0)
+ fprintf(fh, "%s=%s\n", MODEL_TYPE, MODEL_TYPE_VAL[m->type]);
+ fprintf(fh, "[%s]\n", SECTION_RADIO);
+ fprintf(fh, "%s=%s\n", RADIO_PROTOCOL, PROTOCOL_GetName(m->protocol));
+ write_int(fh, m, _secradio, MAPSIZE(_secradio));
+ fprintf(fh, "%s=%s\n", RADIO_TX_POWER, radio_tx_power_val(m->radio, m->tx_power));
+ fprintf(fh, "\n");
+ write_proto_opts(fh, m);
+ struct Limit default_limit;
+ memset(&default_limit, 0, sizeof(default_limit));
+ MIXER_SetDefaultLimit(&default_limit);
+ for(idx = 0; idx < NUM_OUT_CHANNELS; idx++) {
+ if(!WRITE_FULL_MODEL
+ && memcmp(&m->limits[idx], &default_limit, sizeof(default_limit)) == 0
+ && m->templates[idx] == 0)
+ {
+ if (write_mixer(fh, m, idx))
+ fprintf(fh, "\n");
+ continue;
+ }
+ fprintf(fh, "[%s%d]\n", SECTION_CHANNEL, idx+1);
+ if(WRITE_FULL_MODEL || (m->limits[idx].flags & CH_REVERSE))
+ fprintf(fh, "%s=%d\n", CHAN_LIMIT_REVERSE, (m->limits[idx].flags & CH_REVERSE) ? 1 : 0);
+ write_int(fh, &m->limits[idx], _seclimit, MAPSIZE(_seclimit));
+ if(WRITE_FULL_MODEL || (m->limits[idx].flags & CH_FAILSAFE_EN)) {
+ if(m->limits[idx].flags & CH_FAILSAFE_EN) {
+ fprintf(fh, "%s=%d\n", CHAN_LIMIT_FAILSAFE, m->limits[idx].failsafe);
+ } else {
+ fprintf(fh, "%s=Off\n", CHAN_LIMIT_FAILSAFE);
+ }
+ }
+ if(WRITE_FULL_MODEL || m->limits[idx].min != DEFAULT_SERVO_LIMIT)
+ fprintf(fh, "%s=%d\n", CHAN_LIMIT_MIN, -(int)m->limits[idx].min);
+ if(WRITE_FULL_MODEL || strcmp(m->limits[idx].displayformat, DEFAULT_DISPLAY_FORMAT) != 0)
+ fprintf(fh, "%s=%s\n", CHAN_DISPLAY_FORMAT, m->limits[idx].displayformat);
+ if(WRITE_FULL_MODEL || m->templates[idx] != 0)
+ fprintf(fh, "%s=%s\n", CHAN_TEMPLATE, CHAN_TEMPLATE_VAL[m->templates[idx]]);
+ write_mixer(fh, m, idx);
+ fprintf(fh, "\n");
+ }
+ for(idx = 0; idx < NUM_VIRT_CHANNELS; idx++) {
+ if(WRITE_FULL_MODEL || m->templates[idx+NUM_OUT_CHANNELS] != 0 || m->virtname[idx][0]) {
+ fprintf(fh, "[%s%d]\n", SECTION_VIRTCHAN, idx+1);
+ if(m->virtname[idx][0])
+ fprintf(fh, "%s=%s\n", VCHAN_NAME, m->virtname[idx]);
+ if(WRITE_FULL_MODEL || m->templates[idx+NUM_OUT_CHANNELS] != 0)
+ fprintf(fh, "%s=%s\n", VCHAN_TEMPLATE, VCHAN_TEMPLATE_VAL[m->templates[idx+NUM_OUT_CHANNELS]]);
+ }
+ if (write_mixer(fh, m, idx+NUM_OUT_CHANNELS))
+ fprintf(fh, "\n");
+ }
+ if (PPMin_Mode()) {
+ fprintf(fh, "[%s]\n", SECTION_PPMIN);
+ fprintf(fh, "%s=%s\n", PPMIN_MODE, PPMIN_MODE_VALUE[PPMin_Mode()]);
+ fprintf(fh, "%s=%d\n", PPMIN_NUM_CHANNELS, m->num_ppmin_channels);
+ if (PPMin_Mode() != PPM_IN_SOURCE) {
+ fprintf(fh, "%s=%s\n", PPMIN_SWITCH, INPUT_SourceNameReal(file, m->train_sw));
+ }
+ write_int(fh, m, _secppm, MAPSIZE(_secppm));
+ //fprintf(fh, "%s=%d\n", PPMIN_CENTERPW, m->ppmin_centerpw);
+ //fprintf(fh, "%s=%d\n", PPMIN_DELTAPW, m->ppmin_deltapw);
+ if (PPMin_Mode() != PPM_IN_SOURCE) {
+ int offset = (PPMin_Mode() == PPM_IN_TRAIN1) ? NUM_INPUTS + 1: 0;
+ for(idx = 0; idx < MAX_PPM_IN_CHANNELS; idx++) {
+ if (m->ppm_map[idx] == -1)
+ continue;
+ fprintf(fh, "%s%d=%s\n", PPMIN_MAP, idx + 1, INPUT_SourceNameReal(file, m->ppm_map[idx] + offset));
+ }
+ }
+ fprintf(fh, "\n");
+ }
+ for(idx = 0; idx < NUM_TRIMS; idx++) {
+ if (! WRITE_FULL_MODEL && m->trims[idx].src == 0)
+ continue;
+ fprintf(fh, "[%s%d]\n", SECTION_TRIM, idx+1);
+ fprintf(fh, "%s=%s\n", TRIM_SOURCE,
+ m->trims[idx].src >= 1 && m->trims[idx].src <= 4
+ ? tx_stick_names[m->trims[idx].src-1]
+ : INPUT_SourceNameReal(file, m->trims[idx].src));
+ write_int(fh, &m->trims[idx], _sectrim, MAPSIZE(_sectrim));
+ if(WRITE_FULL_MODEL || m->trims[idx].sw)
+ fprintf(fh, "%s=%s\n", TRIM_SWITCH, INPUT_SourceNameAbbrevSwitchReal(file, m->trims[idx].sw));
+ if(WRITE_FULL_MODEL || m->trims[idx].value[0] || m->trims[idx].value[1] || m->trims[idx].value[2]
+ || m->trims[idx].value[3] || m->trims[idx].value[4] || m->trims[idx].value[5])
+ fprintf(fh, "%s=%d,%d,%d,%d,%d,%d\n", TRIM_VALUE,
+ m->trims[idx].value[0], m->trims[idx].value[1], m->trims[idx].value[2],
+ m->trims[idx].value[3], m->trims[idx].value[4], m->trims[idx].value[5]);
+ }
+ if (WRITE_FULL_MODEL || m->swash_type) {
+ fprintf(fh, "[%s]\n", SECTION_SWASH);
+ fprintf(fh, "%s=%s\n", SWASH_TYPE, MIXER_SwashType(m->swash_type));
+ if (WRITE_FULL_MODEL || m->swash_invert & 0x01)
+ fprintf(fh, "%s=1\n", SWASH_ELE_INV);
+ if (WRITE_FULL_MODEL || m->swash_invert & 0x02)
+ fprintf(fh, "%s=1\n", SWASH_AIL_INV);
+ if (WRITE_FULL_MODEL || m->swash_invert & 0x04)
+ fprintf(fh, "%s=1\n", SWASH_COL_INV);
+ write_int(fh, m, _secswash, MAPSIZE(_secswash));
+ }
+ for(idx = 0; idx < NUM_TIMERS; idx++) {
+ if (! WRITE_FULL_MODEL && m->timer[idx].src == 0 && m->timer[idx].type == TIMER_STOPWATCH)
+ continue;
+ fprintf(fh, "[%s%d]\n", SECTION_TIMER, idx+1);
+ if (WRITE_FULL_MODEL || m->timer[idx].type != TIMER_STOPWATCH)
+ fprintf(fh, "%s=%s\n", TIMER_TYPE, TIMER_TYPE_VAL[m->timer[idx].type]);
+ write_int(fh, &m->timer[idx], _sectimer, MAPSIZE(_sectimer));
+ if (WRITE_FULL_MODEL || ((m->timer[idx].type == TIMER_COUNTDOWN || m->timer[idx].type == TIMER_COUNTDOWN_PROP) && m->timer[idx].timer))
+ fprintf(fh, "%s=%d\n", TIMER_TIME, m->timer[idx].timer);
+ if (WRITE_FULL_MODEL || (m->timer[idx].val != 0 && m->timer[idx].type == TIMER_PERMANENT))
+ fprintf(fh, "%s=%d\n", TIMER_VAL, m->timer[idx].val);
+ }
+ for(idx = 0; idx < TELEM_NUM_ALARMS; idx++) {
+ struct TelemetryAlarm *alarm = &m->alarms[idx];
+ if (!WRITE_FULL_MODEL && !alarm->src)
+ continue;
+ fprintf(fh, "[%s%d]\n", SECTION_TELEMALARM, idx+1);
+ fprintf(fh, "%s=%s\n", TELEM_SRC, TELEMETRY_ShortName(file, alarm->src));
+ if (WRITE_FULL_MODEL || alarm->above)
+ fprintf(fh, "%s=%d\n", TELEM_ABOVE, alarm->above);
+ fprintf(fh, "%s=%d\n", TELEM_VALUE, alarm->value);
+ fprintf(fh, "%s=%d\n", TELEM_THRESHOLD, alarm->threshold);
+ }
+#if HAS_DATALOG
+ fprintf(fh, "[%s]\n", SECTION_DATALOG);
+ fprintf(fh, "%s=%s\n", DATALOG_SWITCH, INPUT_SourceNameReal(file, m->datalog.enable));
+ fprintf(fh, "%s=%s\n", DATALOG_RATE, DATALOG_RateString(m->datalog.rate));
+ for(idx = 0; idx < DLOG_LAST; idx++) {
+ if(m->datalog.source[DATALOG_BYTE(idx)] & (1 << DATALOG_POS(idx)))
+ fprintf(fh, "%s=%s\n", DATALOG_SOURCE, DATALOG_Source(file, idx));
+ }
+#endif //HAS_DATALOG
+ fprintf(fh, "[%s]\n", SECTION_SAFETY);
+ for(int i = 0; i < NUM_SOURCES + 1; i++) {
+ if (WRITE_FULL_MODEL || m->safety[i]) {
+ fprintf(fh, "%s=%s\n", i == 0 ? "Auto" : INPUT_SourceNameReal(file, i), SAFETY_VAL[m->safety[i]]);
+ }
+ }
+ fprintf(fh, "[%s]\n", SECTION_GUI);
+ for(idx = 0; idx < NUM_ELEMS; idx++) {
+ if (! ELEM_USED(Model.pagecfg2.elem[idx]))
+ break;
+ int src = Model.pagecfg2.elem[idx].src;
+ int x = ELEM_X(Model.pagecfg2.elem[idx]);
+ int y = ELEM_Y(Model.pagecfg2.elem[idx]);
+ int type = ELEM_TYPE(Model.pagecfg2.elem[idx]);
+ const char *elename = GetElemName(type);
+ switch(type) {
+ case ELEM_SMALLBOX:
+ case ELEM_BIGBOX:
+ fprintf(fh, "%s=%d,%d,%s\n", elename, x, y, GetBoxSourceReal(file, src));
+ break;
+ case ELEM_BAR:
+ src += NUM_INPUTS;
+ fprintf(fh, "%s=%d,%d,%s\n", elename, x, y, INPUT_SourceNameReal(file, src));
+ break;
+ case ELEM_TOGGLE:
+ fprintf(fh, "%s=%d,%d,%d,%d,%d,%s\n", elename, x, y,
+ Model.pagecfg2.elem[idx].extra[0],
+ Model.pagecfg2.elem[idx].extra[1],
+ INPUT_NumSwitchPos(src) == 2 ? 0 : Model.pagecfg2.elem[idx].extra[2],
+ INPUT_SourceNameAbbrevSwitchReal(file, src));
+ break;
+ case ELEM_HTRIM:
+ case ELEM_VTRIM:
+ fprintf(fh, "%s=%d,%d,%d\n", elename, x, y, src);
+ break;
+ default:
+ fprintf(fh, "%s=%d,%d\n", elename, x, y);
+ break;
+ }
+ }
+ for(idx = 0; idx < NUM_QUICKPAGES; idx++) {
+ if (WRITE_FULL_MODEL || m->pagecfg2.quickpage[idx]) {
+ u8 val = m->pagecfg2.quickpage[idx];
+ fprintf(fh, "%s%d=%s\n", GUI_QUICKPAGE, idx+1, PAGE_GetName(val));
+ }
+ }
+#if HAS_EXTENDED_AUDIO
+ fprintf(fh, "[%s]\n", SECTION_VOICE);
+ for (idx = 0; idx < NUM_SWITCHES; idx++) {
+ if (m->voice.switches[idx].music)
+ fprintf(fh, "%s=%d\n", INPUT_SourceName(file,idx + INP_HAS_CALIBRATION + 1), m->voice.switches[idx].music);
+ }
+#if NUM_AUX_KNOBS
+ for (idx = 0; idx < NUM_AUX_KNOBS * 2; idx++) {
+ if (m->voice.aux[idx].music) {
+ if (idx % 2)
+ fprintf(fh, "%s_UP=%d\n", INPUT_SourceName(tempstring,(idx-1) / 2 + NUM_STICKS + 1), m->voice.aux[idx].music);
+ else
+ fprintf(fh, "%s_DOWN=%d\n", INPUT_SourceName(tempstring,idx / 2 + NUM_STICKS + 1), m->voice.aux[idx].music);
+ }
+ }
+#endif
+ for (idx = 0; idx < NUM_TIMERS; idx++) {
+ if (m->voice.timer[idx].music)
+ fprintf(fh, "timer%d=%d\n", idx + 1, m->voice.timer[idx].music);
+ }
+ for (idx = 0; idx < TELEM_NUM_ALARMS; idx++) {
+ if (m->voice.telemetry[idx].music)
+ fprintf(fh, "telemalarm%d=%d\n", idx + 1, m->voice.telemetry[idx].music);
+ }
+ for (idx = 0; idx < (NUM_OUT_CHANNELS + NUM_VIRT_CHANNELS); idx++) {
+ if (m->voice.mixer[idx].music)
+ fprintf(fh, "%s=%d\n", INPUT_SourceNameReal(tempstring, idx + NUM_INPUTS + 1), m->voice.mixer[idx].music);
+ }
+#endif
+ CONFIG_EnableLanguage(1);
+ fclose(fh);
+ return 1;
+}
+
+static void clear_model(u8 full)
+{
+ u8 i;
+ if (full) {
+ memset(&Model, 0, sizeof(Model));
+ } else {
+ memset(Model.mixers, 0, sizeof(Model.mixers));
+ memset(Model.templates, 0, sizeof(Model.templates));
+ memset(Model.trims, 0, sizeof(Model.trims));
+ Model.swash_type = SWASH_TYPE_NONE;
+ Model.swash_invert = 0;
+ }
+ Model.mixer_mode = MIXER_ADVANCED;
+ Model.swashmix[0] = 60;
+ Model.swashmix[1] = 60;
+ Model.swashmix[2] = 60;
+ for(i = 0; i < NUM_MIXERS; i++) {
+ Model.mixers[i].scalar = 100;
+ MIXER_SET_APPLY_TRIM(&Model.mixers[i], 1);
+ }
+ for(i = 0; i < NUM_OUT_CHANNELS; i++) {
+ MIXER_SetDefaultLimit(&Model.limits[i]);
+ }
+ for (i = 0; i < NUM_TRIMS; i++) {
+ Model.trims[i].step = 1;
+ }
+ for (i = 0; i < MAX_PPM_IN_CHANNELS; i++) {
+ Model.ppm_map[i] = -1;
+ }
+ Model.ppmin_centerpw = 1500;
+ Model.ppmin_deltapw = 400;
+}
+
+u8 CONFIG_ReadLayout_old(const char *filename) {
+ memset(&Model.pagecfg2, 0, sizeof(Model.pagecfg2));
+ if (CONFIG_IniParse(filename, layout_ini_handler, &Model)) {
+ printf("Failed to parse Layout file: %s\n", filename);
+ return 0;
+ }
+ return 1;
+}
+
+u8 CONFIG_ReadModel_old(const char* file) {
+ clear_model(1);
+
+ auto_map = 0;
+ if (CONFIG_IniParse(file, ini_handler, &Model)) {
+ printf("Failed to parse Model file: %s\n", file);
+ }
+ if (! ELEM_USED(Model.pagecfg2.elem[0]))
+ CONFIG_ReadLayout_old("layout/default.ini");
+ if(! PROTOCOL_HasPowerAmp(Model.protocol))
+ Model.tx_power = TXPOWER_150mW;
+ MIXER_SetMixers(NULL, 0);
+ if(auto_map)
+ RemapChannelsForProtocol(EATRG0);
+ if(! Model.name[0])
+ sprintf(Model.name, "Model%d", 1);
+ return 1;
+}
diff --git a/src/tests/models/280qav.ini b/src/tests/models/280qav.ini
new file mode 100644
index 0000000000..060ff47855
--- /dev/null
+++ b/src/tests/models/280qav.ini
@@ -0,0 +1,100 @@
+name=280qav
+mixermode=Advanced
+type=multi
+[radio]
+protocol=DSMX
+num_channels=7
+tx_power=150mW
+
+[channel1]
+template=simple
+[mixer]
+src=THR
+dest=Ch1
+curvetype=expo
+points=0,0
+
+[channel2]
+reverse=1
+template=simple
+[mixer]
+src=AIL
+dest=Ch2
+curvetype=expo
+points=0,0
+
+[channel3]
+template=simple
+[mixer]
+src=ELE
+dest=Ch3
+curvetype=expo
+points=0,0
+
+[channel4]
+reverse=1
+template=simple
+[mixer]
+src=RUD
+dest=Ch4
+curvetype=expo
+points=0,0
+
+[channel5]
+template=expo_dr
+[mixer]
+src=AIL
+dest=Ch5
+scalar=125
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=SW A1
+scalar=50
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=SW A2
+scalar=0
+curvetype=fixed
+
+[channel6]
+template=expo_dr
+[mixer]
+src=SW B1
+dest=Ch6
+scalar=125
+curvetype=fixed
+[mixer]
+src=SW B1
+dest=Ch6
+switch=SW B1
+scalar=50
+curvetype=fixed
+[mixer]
+src=SW B1
+dest=Ch6
+switch=SW B0
+scalar=0
+curvetype=fixed
+
+[safety]
+[gui-128x64]
+V-trim=59,10,1
+H-trim=5,59,3
+V-trim=65,10,2
+H-trim=74,59,4
+Small-box=2,22,Ch1
+Small-box=2,31,Timer1
+Small-box=2,40,Timer2
+Model=75,20
+Battery=102,1
+Toggle=4,10,0,3,0,None
+Toggle=13,10,0,5,0,None
+Toggle=22,10,0,4,0,None
+Toggle=31,10,0,0,0,None
+Toggle=40,10,0,0,0,None
+TxPower=102,7
+quickpage1=Telemetry monitor
\ No newline at end of file
diff --git a/src/tests/models/4g6s.ini b/src/tests/models/4g6s.ini
new file mode 100644
index 0000000000..e8207b802c
--- /dev/null
+++ b/src/tests/models/4g6s.ini
@@ -0,0 +1,169 @@
+name=4G6
+mixermode=Advanced
+icon=4G6S.BMP
+[radio]
+protocol=WK2601
+num_channels=7
+tx_power=30mW
+
+[protocol_opts]
+Chan mode=6+1
+COL Inv=Normal
+COL Limit=0
+
+[channel1]
+template=cyclic1
+
+[channel2]
+reverse=1
+template=cyclic2
+
+[channel3]
+template=complex
+[mixer]
+src=THR
+dest=Ch3
+usetrim=0
+curvetype=3point
+points=-100,48,100
+[mixer]
+src=THR
+dest=Ch3
+switch=FMODE1
+usetrim=0
+curvetype=3point
+points=100,50,100
+[mixer]
+src=THR
+dest=Ch3
+switch=FMODE2
+usetrim=0
+curvetype=3point
+points=100,75,100
+[mixer]
+src=THR
+dest=Ch3
+switch=GEAR1
+usetrim=0
+curvetype=3point
+points=-100,-100,-100
+
+[channel4]
+reverse=1
+template=simple
+[mixer]
+src=RUD
+dest=Ch4
+usetrim=0
+curvetype=expo
+points=0,0
+
+[channel6]
+reverse=1
+template=cyclic3
+
+[channel7]
+template=expo_dr
+[mixer]
+src=Ch7
+dest=Ch7
+scalar=82
+usetrim=0
+curvetype=fixed
+[mixer]
+src=Ch7
+dest=Ch7
+switch=FMODE1
+scalar=82
+usetrim=0
+curvetype=fixed
+[mixer]
+src=Ch7
+dest=Ch7
+switch=FMODE2
+scalar=82
+usetrim=0
+curvetype=fixed
+
+[virtchan1]
+template=expo_dr
+[mixer]
+src=AIL
+dest=Virt1
+usetrim=0
+curvetype=expo
+points=40,40
+
+[virtchan2]
+template=expo_dr
+[mixer]
+src=ELE
+dest=Virt2
+usetrim=0
+curvetype=expo
+points=40,40
+
+[virtchan3]
+template=expo_dr
+[mixer]
+src=THR
+dest=Virt3
+usetrim=0
+curvetype=3point
+points=-25,0,100
+[mixer]
+src=THR
+dest=Virt3
+switch=FMODE1
+usetrim=0
+[mixer]
+src=THR
+dest=Virt3
+switch=FMODE2
+usetrim=0
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+value=-100
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[swash]
+type=120
+ail_inv=1
+[timer2]
+type=countdown
+time=10
+[safety]
+Auto=min
+Ch3=min
+[gui-qvga]
+trim=4in
+barsize=half
+box1=Ch3
+box2=Timer1
+box3=Timer2
+bar1=Ch1
+bar2=Ch2
+bar3=Ch3
+bar4=Ch4
+toggle1=ELE DR
+tglico1=0,1,0
+toggle2=AIL DR
+tglico2=0,0,0
+toggle3=RUD DR
+tglico3=0,2,0
+toggle4=GEAR
+tglico4=0,4,0
+quickpage1=Telemetry monitor
\ No newline at end of file
diff --git a/src/tests/models/apm.ini b/src/tests/models/apm.ini
new file mode 100644
index 0000000000..773e57fadc
--- /dev/null
+++ b/src/tests/models/apm.ini
@@ -0,0 +1,185 @@
+name=APM
+mixermode=Advanced
+icon=V212.BMP
+type=plane
+[radio]
+protocol=DSM2
+num_channels=8
+fixed_id=636699
+tx_power=100mW
+
+[protocol_opts]
+Telemetry=Off
+
+[channel1]
+max=100
+min=-100
+template=complex
+[mixer]
+src=THR
+dest=Ch1
+switch=RUD DR1
+[mixer]
+src=AUX4
+dest=Ch1
+switch=RUD DR0
+scalar=-100
+usetrim=0
+curvetype=fixed
+
+[channel2]
+reverse=1
+template=simple
+[mixer]
+src=AIL
+dest=Ch2
+
+[channel3]
+reverse=1
+template=simple
+[mixer]
+src=ELE
+dest=Ch3
+
+[channel4]
+reverse=1
+template=complex
+[mixer]
+src=RUD
+dest=Ch4
+switch=RUD DR1
+
+[channel5]
+max=100
+min=-100
+template=complex
+[mixer]
+src=AUX5
+dest=Ch5
+switch=FMODE0
+scalar=-125
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=FMODE1
+scalar=-40
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=FMODE2
+scalar=-20
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=GEAR1
+scalar=10
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=MIX1
+scalar=40
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=MIX2
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=RUD DR0
+scalar=-100
+usetrim=0
+curvetype=fixed
+
+[channel6]
+max=100
+min=-100
+template=complex
+[mixer]
+src=AUX5
+dest=Ch6
+usetrim=0
+curvetype=expo
+points=0,0
+
+[channel7]
+max=100
+min=-100
+template=complex
+[mixer]
+src=ELE DR0
+dest=Ch7
+switch=ELE DR0
+scalar=-100
+usetrim=0
+[mixer]
+src=AIL
+dest=Ch7
+switch=ELE DR1
+usetrim=0
+curvetype=fixed
+
+[channel8]
+max=100
+min=-100
+template=complex
+[mixer]
+src=AUX4
+dest=Ch8
+usetrim=0
+curvetype=expo
+points=0,0
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[timer1]
+type=countdown
+src=Ch1
+resetsrc=AIL DR1
+time=420
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-128x64]
+V-trim=59,10,1
+H-trim=5,59,3
+V-trim=65,10,2
+H-trim=74,59,4
+Small-box=2,22,Ch1
+Small-box=2,31,Timer1
+Model=75,20
+Battery=102,1
+Toggle=42,10,72,0,0,RUD DR
+Toggle=12,10,0,68,0,ELE DR
+Toggle=2,10,0,193,194,FMODE
+Toggle=32,10,0,196,197,MIX
+Toggle=22,10,0,71,0,GEAR
+TxPower=102,7
+quickpage1=Telemetry monitor
\ No newline at end of file
diff --git a/src/tests/models/ardrone2.ini b/src/tests/models/ardrone2.ini
new file mode 100644
index 0000000000..e65157d97d
--- /dev/null
+++ b/src/tests/models/ardrone2.ini
@@ -0,0 +1,108 @@
+name=ArDrone2
+mixermode=Advanced
+icon=DRONE2-2.BMP
+type=plane
+[radio]
+protocol=DSM2
+num_channels=7
+fixed_id=123456
+tx_power=150mW
+
+[protocol_opts]
+Telemetry=Off
+
+[channel1]
+template=simple
+[mixer]
+src=THR
+dest=Ch1
+
+[channel2]
+reverse=1
+template=simple
+[mixer]
+src=AIL
+dest=Ch2
+scalar=45
+curvetype=expo
+points=0,0
+
+[channel3]
+reverse=1
+template=simple
+[mixer]
+src=ELE
+dest=Ch3
+scalar=45
+curvetype=expo
+points=0,0
+
+[channel4]
+reverse=1
+template=simple
+[mixer]
+src=RUD
+dest=Ch4
+
+[channel5]
+reverse=1
+template=simple
+[mixer]
+src=GEAR0
+dest=Ch5
+curvetype=min/max
+points=0
+
+[virtchan1]
+name=Virt1
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[trim5]
+src=GEAR0
+pos=None
+neg=None
+switch=GEAR
+[trim6]
+src=MIX0
+pos=None
+neg=None
+[timer1]
+type=countdown
+src=GEAR1
+time=600
+[timer2]
+type=stop-prop
+src=THR
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-320x240]
+V-trim=131,75,1
+H-trim=6,220,3
+V-trim=181,75,2
+H-trim=191,220,4
+Big-box=9,90,Timer1
+Small-box=9,150,Timer2
+Bargraph=205,150,Ch1
+Bargraph=235,150,Ch2
+Bargraph=265,150,Ch3
+Bargraph=295,150,Ch4
+Model=206,40
+Toggle=144,44,68,5,0,GEAR
+Big-box=9,37,Ch1
\ No newline at end of file
diff --git a/src/tests/models/bixler2.ini b/src/tests/models/bixler2.ini
new file mode 100644
index 0000000000..55d514e79f
--- /dev/null
+++ b/src/tests/models/bixler2.ini
@@ -0,0 +1,241 @@
+name=Bixler 2
+mixermode=Advanced
+icon=BIXLER2.BMP
+type=plane
+[radio]
+protocol=DEVO
+num_channels=10
+fixed_id=870249
+tx_power=150mW
+
+[protocol_opts]
+Telemetry=Off
+
+[channel1]
+subtrim=-165
+template=complex
+[mixer]
+src=ELE
+dest=Ch1
+switch=FMODE0
+scalar=80
+curvetype=expo
+points=20,20
+[mixer]
+src=ELE
+dest=Ch1
+switch=FMODE1
+curvetype=expo
+points=20,20
+[mixer]
+src=AIL
+dest=Ch1
+switch=MIX1
+scalar=20
+muxtype=add
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch1
+switch=MIX2
+scalar=20
+muxtype=add
+curvetype=fixed
+[mixer]
+src=ELE
+dest=Ch1
+switch=FMODE2
+curvetype=expo
+points=20,20
+[mixer]
+src=ELE
+dest=Ch1
+switch=FMODE2
+curvetype=expo
+points=20,20
+
+[channel2]
+template=complex
+[mixer]
+src=AIL
+dest=Ch2
+switch=FMODE0
+scalar=80
+curvetype=expo
+points=20,20
+[mixer]
+src=AIL
+dest=Ch2
+switch=FMODE1
+curvetype=expo
+points=20,20
+[mixer]
+src=AIL
+dest=Ch2
+switch=FMODE2
+curvetype=expo
+points=20,20
+
+[channel3]
+safetysw=RUD DR0
+failsafe=-125
+safetyval=-150
+template=simple
+[mixer]
+src=THR
+dest=Ch3
+
+[channel4]
+template=complex
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE0
+scalar=80
+curvetype=expo
+points=20,20
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE1
+curvetype=expo
+points=20,20
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE2
+curvetype=expo
+points=20,20
+[mixer]
+src=AIL
+dest=Ch4
+switch=GEAR1
+curvetype=expo
+points=20,20
+
+[channel5]
+template=complex
+[mixer]
+src=AIL
+dest=Ch5
+switch=FMODE0
+scalar=80
+curvetype=expo
+points=20,20
+[mixer]
+src=AIL
+dest=Ch5
+switch=FMODE1
+usetrim=0
+curvetype=expo
+points=20,20
+[mixer]
+src=AIL
+dest=Ch5
+switch=FMODE2
+usetrim=0
+curvetype=expo
+points=20,20
+
+[channel6]
+template=complex
+[mixer]
+src=ELE
+dest=Ch6
+switch=MIX0
+scalar=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch6
+switch=MIX1
+scalar=40
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch6
+switch=MIX2
+scalar=60
+usetrim=0
+curvetype=fixed
+[mixer]
+src=!AIL
+dest=Ch6
+switch=FMODE2
+usetrim=0
+
+[channel7]
+template=complex
+[mixer]
+src=MIX0
+dest=Ch7
+switch=MIX0
+scalar=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch7
+switch=MIX1
+scalar=-40
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch7
+switch=MIX2
+scalar=-60
+usetrim=0
+curvetype=fixed
+[mixer]
+src=!AIL
+dest=Ch7
+switch=FMODE2
+usetrim=0
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+step=10
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+step=10
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+step=10
+[timer1]
+type=countdown
+src=Ch3
+resetsrc=AIL DR1
+time=630
+[timer2]
+type=permanent
+src=Ch3
+val=2709794
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-128x64]
+V-trim=59,10,1
+H-trim=5,59,3
+V-trim=65,10,2
+H-trim=74,59,4
+Small-box=2,22,Ch3
+Small-box=2,31,Timer1
+Small-box=2,39,Timer2
+Model=75,20
+Toggle=2,11,0,193,194,FMODE
+Toggle=23,11,0,196,197,MIX
+Toggle=42,11,72,0,0,RUD DR
+quickpage1=Mixer
\ No newline at end of file
diff --git a/src/tests/models/blade130x.ini b/src/tests/models/blade130x.ini
new file mode 100644
index 0000000000..d4968f48a5
--- /dev/null
+++ b/src/tests/models/blade130x.ini
@@ -0,0 +1,198 @@
+name=Blade130X
+mixermode=Advanced
+icon=HELI.BMP
+[radio]
+protocol=DSMX
+num_channels=7
+tx_power=100mW
+
+[protocol_opts]
+Telemetry=Off
+
+[channel1]
+safetysw=RUD DR1
+safetyval=-100
+template=complex
+[mixer]
+src=THR
+dest=Ch1
+curvetype=7point
+points=-100,-20,22,41,48,50,50
+[mixer]
+src=THR
+dest=Ch1
+switch=FMODE1
+curvetype=7point
+points=100,87,73,60,73,87,100
+[mixer]
+src=THR
+dest=Ch1
+switch=FMODE2
+curvetype=fixed
+
+[channel2]
+template=expo_dr
+[mixer]
+src=AIL
+dest=Ch2
+curvetype=expo
+points=30,30
+[mixer]
+src=AIL
+dest=Ch2
+switch=AIL DR1
+scalar=125
+curvetype=expo
+points=0,0
+
+[channel3]
+template=expo_dr
+[mixer]
+src=ELE
+dest=Ch3
+curvetype=expo
+points=30,30
+[mixer]
+src=ELE
+dest=Ch3
+switch=ELE DR1
+scalar=125
+curvetype=expo
+points=0,0
+
+[channel4]
+template=simple
+[mixer]
+src=RUD
+dest=Ch4
+curvetype=expo
+points=-6,-6
+
+[channel6]
+template=complex
+[mixer]
+src=THR
+dest=Ch6
+usetrim=0
+curvetype=7point
+points=-40,-26,-13,0,33,66,100
+[mixer]
+src=THR
+dest=Ch6
+switch=FMODE1
+usetrim=0
+curvetype=7point
+points=-100,-66,-33,0,33,66,100
+[mixer]
+src=THR
+dest=Ch6
+switch=FMODE2
+usetrim=0
+curvetype=7point
+points=-100,-66,-33,0,33,66,100
+[mixer]
+src=AIL
+dest=Ch6
+switch=RUD DR1
+scalar=0
+usetrim=0
+curvetype=fixed
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[timer1]
+type=countdown
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-480x272]
+V-trim=213,91,1
+H-trim=86,236,3
+V-trim=263,91,2
+H-trim=271,236,4
+Big-box=89,56,Ch1
+Big-box=89,106,Timer1
+Small-box=89,166,Timer2
+Small-box=89,197,Clock
+Bargraph=285,166,Ch1
+Bargraph=298,166,Ch2
+Bargraph=312,166,Ch3
+Bargraph=326,166,Ch4
+Bargraph=340,166,Ch5
+Bargraph=354,166,Ch6
+Bargraph=368,166,Ch7
+Bargraph=382,166,Ch8
+Toggle=210,54,1,64,128,FMODE
+Toggle=248,54,2,65,129,MIX
+Toggle=227,92,8,71,0,GEAR
+Toggle=227,131,5,132,68,ELE DR
+Toggle=227,169,4,131,67,AIL DR
+Toggle=227,208,3,130,66,RUD DR
+Model=290,56
+
+[gui-320x240]
+V-trim=129,75,1
+H-trim=4,220,3
+V-trim=181,75,2
+H-trim=191,220,4
+Big-box=8,40,Ch1
+Small-box=8,95,Timer1
+Small-box=8,133,Timer2
+Small-box=8,172,Timer3
+Bargraph=200,150,Ch1
+Bargraph=214,150,Ch2
+Bargraph=228,150,Ch3
+Bargraph=242,150,Ch4
+Bargraph=256,150,Ch5
+Bargraph=270,150,Ch6
+Bargraph=284,150,Ch7
+Bargraph=298,150,Ch8
+Toggle=144,36,1,64,128,FMODE
+Toggle=144,69,2,65,129,MIX
+Toggle=144,102,5,68,0,ELE DR
+Toggle=144,135,4,67,0,AIL DR
+Toggle=144,168,3,66,0,RUD DR
+Toggle=144,201,8,71,0,GEAR
+Model=207,40
+
+[gui-128x64]
+V-trim=55,10,1
+H-trim=1,59,3
+V-trim=69,10,2
+H-trim=78,59,4
+Big-box=2,12,Ch1
+Small-box=2,28,Timer1
+Small-box=2,38,Timer2
+Small-box=2,48,Timer3
+Bargraph=79,30,Ch1
+Bargraph=85,30,Ch2
+Bargraph=91,30,Ch3
+Bargraph=97,30,Ch4
+Bargraph=103,30,Ch5
+Bargraph=109,30,Ch6
+Bargraph=115,30,Ch7
+Bargraph=121,30,Ch8
+Toggle=75,13,1,64,128,FMODE
+Toggle=84,13,2,65,129,MIX
+Toggle=93,13,0,5,0,ELE DR
+Toggle=102,13,0,4,0,AIL DR
+Toggle=111,13,0,8,0,GEAR
+Toggle=120,13,0,3,0,RUD DR
+Battery=102,1
+TxPower=75,1
\ No newline at end of file
diff --git a/src/tests/models/deltaray.ini b/src/tests/models/deltaray.ini
new file mode 100644
index 0000000000..67fe54b38b
--- /dev/null
+++ b/src/tests/models/deltaray.ini
@@ -0,0 +1,116 @@
+name=DeltaRay 1
+mixermode=Advanced
+icon=DELTARAY.BMP
+type=plane
+[radio]
+protocol=DSM2
+num_channels=7
+tx_power=150mW
+
+[protocol_opts]
+Telemetry=Off
+
+[channel1]
+template=simple
+[mixer]
+src=THR
+dest=Ch1
+
+[channel2]
+template=simple
+[mixer]
+src=AIL
+dest=Ch2
+curvetype=expo
+points=0,0
+
+[channel3]
+template=simple
+[mixer]
+src=ELE
+dest=Ch3
+curvetype=expo
+points=0,0
+
+[channel4]
+template=simple
+[mixer]
+src=RUD
+dest=Ch4
+curvetype=expo
+points=40,40
+
+[channel5]
+max=100
+min=-100
+template=expo_dr
+[mixer]
+src=FMODE0
+dest=Ch5
+curvetype=fixed
+[mixer]
+src=FMODE0
+dest=Ch5
+switch=FMODE1
+scalar=0
+curvetype=fixed
+[mixer]
+src=FMODE0
+dest=Ch5
+switch=FMODE2
+scalar=-100
+curvetype=fixed
+
+[channel6]
+reverse=1
+template=expo_dr
+[mixer]
+src=RUD DR0
+dest=Ch6
+curvetype=fixed
+[mixer]
+src=RUD DR0
+dest=Ch6
+switch=RUD DR1
+scalar=-100
+curvetype=fixed
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+value=-18
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[timer1]
+src=THR
+[timer2]
+type=countdown
+src=THR
+time=585
+[safety]
+Ch1=min
+[gui-qvga]
+trim=4in
+barsize=half
+box1=Timer1
+box2=Timer2
+box3=Timer2
+bar1=Ch1
+bar2=Ch2
+bar3=Ch3
+bar4=Ch4
+toggle1=FMODE
+tglico1=3,1,2
+toggle2=RUD DR
+tglico2=3,2,0
\ No newline at end of file
diff --git a/src/tests/models/fx071.ini b/src/tests/models/fx071.ini
new file mode 100644
index 0000000000..9f116f199e
--- /dev/null
+++ b/src/tests/models/fx071.ini
@@ -0,0 +1,219 @@
+name=FX071-Kiwi.Craig
+mixermode=Advanced
+icon=FX071.BMP
+[radio]
+protocol=KN
+num_channels=10
+fixed_id=123456
+tx_power=100mW
+
+[protocol_opts]
+Re-bind=No
+1Mbps=No
+
+[channel1]
+safetysw=RUD DR1
+safetyval=-100
+scalar-=100
+failsafe=-100
+template=simple
+[mixer]
+src=THR
+dest=Ch1
+curvetype=5point
+points=-100,-50,0,50,100
+smooth=1
+
+[channel2]
+scalar-=100
+template=complex
+[mixer]
+src=AIL
+dest=Ch2
+scalar=30
+curvetype=expo
+points=10,10
+[mixer]
+src=AIL
+dest=Ch2
+switch=MIX0
+scalar=5
+muxtype=add
+curvetype=expo
+points=0,0
+[mixer]
+src=AIL
+dest=Ch2
+switch=FMODE1
+scalar=55
+curvetype=expo
+points=15,15
+[mixer]
+src=AIL
+dest=Ch2
+switch=MIX0
+scalar=5
+muxtype=add
+curvetype=expo
+points=0,0
+[mixer]
+src=AIL
+dest=Ch2
+switch=FMODE2
+scalar=70
+curvetype=expo
+points=30,30
+[mixer]
+src=AIL
+dest=Ch2
+switch=MIX0
+scalar=10
+muxtype=add
+curvetype=expo
+points=0,0
+
+[channel3]
+scalar-=100
+template=complex
+[mixer]
+src=ELE
+dest=Ch3
+scalar=30
+curvetype=expo
+points=10,10
+[mixer]
+src=ELE
+dest=Ch3
+switch=MIX0
+scalar=5
+muxtype=add
+curvetype=expo
+points=0,0
+[mixer]
+src=ELE
+dest=Ch3
+switch=FMODE1
+scalar=55
+curvetype=expo
+points=15,15
+[mixer]
+src=ELE
+dest=Ch3
+switch=MIX0
+scalar=5
+muxtype=add
+curvetype=expo
+points=0,0
+[mixer]
+src=ELE
+dest=Ch3
+switch=FMODE2
+scalar=70
+curvetype=expo
+points=30,30
+[mixer]
+src=ELE
+dest=Ch3
+switch=MIX0
+scalar=10
+muxtype=add
+curvetype=expo
+points=0,0
+
+[channel4]
+safetysw=RUD DR1
+safetyval=-100
+failsafe=-100
+template=complex
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE0
+scalar=80
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE1
+curvetype=expo
+points=10,10
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE2
+scalar=120
+curvetype=expo
+points=20,20
+
+[channel5]
+template=simple
+[mixer]
+src=GEAR0
+dest=Ch5
+curvetype=expo
+points=0,0
+
+[channel6]
+template=expo_dr
+[mixer]
+src=RUD DR1
+dest=Ch6
+curvetype=min/max
+points=0
+
+[channel8]
+template=simple
+[mixer]
+src=MIX1
+dest=Ch8
+curvetype=expo
+points=0,0
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+step=2
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+step=2
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+step=2
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+step=2
+[timer1]
+type=countdown
+src=THR
+resetsrc=AIL DR1
+time=390
+[timer2]
+type=countdown
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-128x64]
+V-trim=59,10,1
+H-trim=5,59,3
+V-trim=65,10,2
+H-trim=74,59,4
+Small-box=2,22,Ch1
+Small-box=2,31,Timer1
+Small-box=2,40,None
+Model=75,20
+Battery=102,1
+Toggle=4,10,9,72,0,RUD DR
+Toggle=13,10,8,71,0,MIX
+Toggle=22,10,2,65,129,FMODE
+Toggle=31,10,6,69,0,GEAR
+Toggle=40,10,0,0,0,None
+TxPower=102,7
+quickpage1=Telemetry monitor
\ No newline at end of file
diff --git a/src/tests/models/geniuscp.ini b/src/tests/models/geniuscp.ini
new file mode 100644
index 0000000000..72e6d85caa
--- /dev/null
+++ b/src/tests/models/geniuscp.ini
@@ -0,0 +1,149 @@
+name=Genius CP V2
+mixermode=Standard
+[radio]
+protocol=DEVO
+num_channels=7
+tx_power=150mW
+
+[protocol_opts]
+Telemetry=On
+
+[channel1]
+template=cyclic2
+
+[channel2]
+template=cyclic1
+
+[channel3]
+safetysw=!HOLD0
+failsafe=-125
+safetyval=-110
+template=complex
+[mixer]
+src=THR
+dest=Ch3
+curvetype=9point
+points=-100,-50,-4,23,40,58,74,87,100
+[mixer]
+src=THR
+dest=Ch3
+switch=FMODE1
+curvetype=9point
+points=100,84,69,58,50,58,69,84,100
+
+[channel4]
+template=expo_dr
+[mixer]
+src=RUD
+dest=Ch4
+curvetype=expo
+points=0,0
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE1
+curvetype=expo
+points=0,0
+
+[channel6]
+template=cyclic3
+
+[channel7]
+template=expo_dr
+[mixer]
+src=FMODE0
+dest=Ch7
+scalar=50
+curvetype=fixed
+[mixer]
+src=FMODE0
+dest=Ch7
+switch=FMODE1
+scalar=0
+curvetype=fixed
+
+[virtchan1]
+template=expo_dr
+[mixer]
+src=AIL
+dest=Virt1
+scalar=90
+curvetype=expo
+points=0,0
+[mixer]
+src=AIL
+dest=Virt1
+switch=FMODE1
+curvetype=expo
+points=0,0
+
+[virtchan2]
+template=expo_dr
+[mixer]
+src=ELE
+dest=Virt2
+scalar=90
+curvetype=expo
+points=0,0
+[mixer]
+src=ELE
+dest=Virt2
+switch=FMODE1
+curvetype=expo
+points=0,0
+
+[virtchan3]
+template=complex
+[mixer]
+src=THR
+dest=Virt3
+curvetype=9point
+points=-20,-10,0,10,20,29,38,47,56
+[mixer]
+src=THR
+dest=Virt3
+switch=FMODE1
+curvetype=9point
+points=-60,-45,-30,-15,0,15,30,45,60
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[timer1]
+type=countdown
+src=Ch3
+time=180
+[timer2]
+src=Ch3
+[safety]
+Auto=min
+[gui-128x64]
+V-trim=59,10,1
+H-trim=5,59,3
+V-trim=65,10,2
+H-trim=74,59,4
+Small-box=2,22,Ch3
+Small-box=2,31,Timer1
+Small-box=2,39,Timer2
+Model=75,20
+Battery=102,1
+Toggle=4,10,0,3,0,None
+Toggle=13,10,0,5,0,None
+Toggle=22,10,0,4,0,None
+Toggle=31,10,0,0,0,None
+Toggle=40,10,0,0,0,None
+TxPower=102,7
+quickpage1=Telemetry monitor
\ No newline at end of file
diff --git a/src/tests/models/nazath.ini b/src/tests/models/nazath.ini
new file mode 100644
index 0000000000..4424cd4098
--- /dev/null
+++ b/src/tests/models/nazath.ini
@@ -0,0 +1,135 @@
+name=Naza TH
+mixermode=Advanced
+icon=HUBSANX4.BMP
+[radio]
+protocol=DEVO
+num_channels=10
+fixed_id=611642
+tx_power=150mW
+
+[protocol_opts]
+Telemetry=Off
+
+[channel1]
+template=simple
+[mixer]
+src=ELE
+dest=Ch1
+curvetype=expo
+points=0,0
+
+[channel2]
+template=simple
+[mixer]
+src=AIL
+dest=Ch2
+curvetype=expo
+points=0,0
+
+[channel3]
+template=simple
+[mixer]
+src=THR
+dest=Ch3
+curvetype=expo
+points=0,0
+
+[channel4]
+template=simple
+[mixer]
+src=RUD
+dest=Ch4
+curvetype=expo
+points=0,0
+
+[channel5]
+template=expo_dr
+[mixer]
+src=MIX0
+dest=Ch5
+scalar=95
+curvetype=absval
+points=0
+[mixer]
+src=MIX0
+dest=Ch5
+switch=MIX1
+scalar=5
+curvetype=absval
+points=0
+[mixer]
+src=MIX0
+dest=Ch5
+switch=MIX2
+scalar=-80
+curvetype=absval
+points=0
+
+[channel6]
+template=simple
+[mixer]
+src=PPM5
+dest=Ch6
+curvetype=expo
+points=0,0
+
+[channel7]
+template=simple
+[mixer]
+src=PPM7
+dest=Ch7
+curvetype=expo
+points=0,0
+
+[channel8]
+template=simple
+[mixer]
+src=PPM8
+dest=Ch8
+curvetype=expo
+points=0,0
+
+[ppm-in]
+mode=extend
+num_channels=8
+centerpw=1500
+deltapw=400
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-128x64]
+V-trim=59,10,1
+H-trim=5,59,3
+V-trim=65,10,2
+H-trim=74,59,4
+Small-box=2,22,Ch3
+Small-box=2,31,Timer1
+Small-box=2,39,Timer2
+Model=75,20
+Battery=102,1
+Toggle=4,10,0,3,0,RUD DR
+Toggle=13,10,0,5,0,ELE DR
+Toggle=22,10,0,4,0,AIL DR
+Toggle=31,10,0,0,0,None
+Toggle=40,10,0,0,0,None
+TxPower=102,7
+quickpage1=Telemetry monitor
\ No newline at end of file
diff --git a/src/tests/models/trex150dfc.ini b/src/tests/models/trex150dfc.ini
new file mode 100644
index 0000000000..37842b8cb3
--- /dev/null
+++ b/src/tests/models/trex150dfc.ini
@@ -0,0 +1,242 @@
+name=TREX150DFC
+mixermode=Advanced
+icon=HELI.BMP
+[radio]
+protocol=DSMX
+num_channels=7
+tx_power=100mW
+
+[protocol_opts]
+Telemetry=Off
+
+[channel1]
+safetysw=RUD DR1
+safetyval=-100
+template=complex
+[mixer]
+src=THR
+dest=Ch1
+curvetype=7point
+points=-100,-40,0,8,13,17,20
+[mixer]
+src=THR
+dest=Ch1
+switch=FMODE1
+curvetype=7point
+points=40,36,33,28,33,36,40
+[mixer]
+src=THR
+dest=Ch1
+switch=FMODE2
+curvetype=7point
+points=60,55,50,46,50,55,60
+
+[channel2]
+reverse=1
+template=expo_dr
+[mixer]
+src=AIL
+dest=Ch2
+scalar=50
+curvetype=expo
+points=30,30
+[mixer]
+src=AIL
+dest=Ch2
+switch=FMODE1
+scalar=70
+curvetype=expo
+points=30,30
+[mixer]
+src=AIL
+dest=Ch2
+switch=FMODE2
+scalar=125
+
+[channel3]
+reverse=1
+template=expo_dr
+[mixer]
+src=ELE
+dest=Ch3
+scalar=50
+curvetype=expo
+points=30,30
+[mixer]
+src=ELE
+dest=Ch3
+switch=FMODE1
+scalar=70
+curvetype=expo
+points=30,30
+[mixer]
+src=ELE
+dest=Ch3
+switch=FMODE2
+scalar=125
+
+[channel4]
+reverse=1
+template=expo_dr
+[mixer]
+src=RUD
+dest=Ch4
+curvetype=expo
+points=15,15
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE1
+curvetype=expo
+points=15,15
+[mixer]
+src=RUD
+dest=Ch4
+switch=FMODE2
+
+[channel5]
+template=complex
+[mixer]
+src=AIL
+dest=Ch5
+switch=MIX0
+scalar=30
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=MIX1
+scalar=40
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch5
+switch=MIX2
+scalar=50
+curvetype=fixed
+
+[channel6]
+reverse=1
+template=complex
+[mixer]
+src=THR
+dest=Ch6
+scalar=60
+usetrim=0
+curvetype=7point
+points=0,0,0,0,33,66,100
+[mixer]
+src=THR
+dest=Ch6
+switch=FMODE1
+scalar=70
+usetrim=0
+curvetype=7point
+points=-30,-20,-10,0,33,66,100
+[mixer]
+src=THR
+dest=Ch6
+switch=FMODE2
+scalar=75
+usetrim=0
+curvetype=7point
+points=-100,-66,-33,0,33,66,100
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[timer1]
+type=countdown
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-480x272]
+V-trim=213,91,1
+H-trim=86,236,3
+V-trim=263,91,2
+H-trim=271,236,4
+Big-box=89,56,Ch1
+Big-box=89,106,Timer1
+Small-box=89,166,Timer2
+Small-box=89,197,Clock
+Bargraph=285,166,Ch1
+Bargraph=298,166,Ch2
+Bargraph=312,166,Ch3
+Bargraph=326,166,Ch4
+Bargraph=340,166,Ch5
+Bargraph=354,166,Ch6
+Bargraph=368,166,Ch7
+Bargraph=382,166,Ch8
+Toggle=210,54,1,64,128,FMODE
+Toggle=248,54,2,65,129,MIX
+Toggle=227,92,8,71,0,GEAR
+Toggle=227,131,5,132,68,ELE DR
+Toggle=227,169,4,131,67,AIL DR
+Toggle=227,208,3,130,66,RUD DR
+Model=290,56
+
+[gui-320x240]
+V-trim=129,75,1
+H-trim=4,220,3
+V-trim=181,75,2
+H-trim=191,220,4
+Big-box=8,40,Ch1
+Small-box=8,95,Timer1
+Small-box=8,133,Timer2
+Small-box=8,172,Timer3
+Bargraph=200,150,Ch1
+Bargraph=214,150,Ch2
+Bargraph=228,150,Ch3
+Bargraph=242,150,Ch4
+Bargraph=256,150,Ch5
+Bargraph=270,150,Ch6
+Bargraph=284,150,Ch7
+Bargraph=298,150,Ch8
+Toggle=144,36,1,64,128,FMODE
+Toggle=144,69,2,65,129,MIX
+Toggle=144,102,5,68,0,ELE DR
+Toggle=144,135,4,67,0,AIL DR
+Toggle=144,168,3,66,0,RUD DR
+Toggle=144,201,8,71,0,GEAR
+Model=207,40
+
+[gui-128x64]
+V-trim=55,10,1
+H-trim=1,59,3
+V-trim=69,10,2
+H-trim=78,59,4
+Big-box=2,12,Ch1
+Small-box=2,28,Timer1
+Small-box=2,38,Timer2
+Small-box=2,48,Timer3
+Bargraph=79,30,Ch1
+Bargraph=85,30,Ch2
+Bargraph=91,30,Ch3
+Bargraph=97,30,Ch4
+Bargraph=103,30,Ch5
+Bargraph=109,30,Ch6
+Bargraph=115,30,Ch7
+Bargraph=121,30,Ch8
+Toggle=75,13,1,64,128,FMODE
+Toggle=84,13,2,65,129,MIX
+Toggle=93,13,0,5,0,ELE DR
+Toggle=102,13,0,4,0,AIL DR
+Toggle=111,13,0,8,0,GEAR
+Toggle=120,13,0,3,0,RUD DR
+Battery=102,1
+TxPower=75,1
\ No newline at end of file
diff --git a/src/tests/models/wltoys931.ini b/src/tests/models/wltoys931.ini
new file mode 100644
index 0000000000..26520b706c
--- /dev/null
+++ b/src/tests/models/wltoys931.ini
@@ -0,0 +1,206 @@
+name=V931
+mixermode=Advanced
+icon=V931.BMP
+type=plane
+[radio]
+protocol=KN
+num_channels=11
+tx_power=100mW
+
+[protocol_opts]
+Re-bind=No
+1Mbps=Yes
+Format=WLToys
+
+[channel1]
+template=complex
+[mixer]
+src=THR
+dest=Ch1
+switch=FMODE1
+scalar=95
+[mixer]
+src=THR
+dest=Ch1
+switch=!FMODE1
+usetrim=0
+
+[channel2]
+template=expo_dr
+[mixer]
+src=AIL
+dest=Ch2
+scalar=60
+curvetype=expo
+points=50,50
+[mixer]
+src=AIL
+dest=Ch2
+switch=MIX1
+scalar=80
+curvetype=expo
+points=25,25
+[mixer]
+src=AIL
+dest=Ch2
+switch=MIX2
+curvetype=expo
+points=0,0
+
+[channel3]
+template=expo_dr
+[mixer]
+src=ELE
+dest=Ch3
+scalar=60
+curvetype=expo
+points=50,50
+[mixer]
+src=ELE
+dest=Ch3
+switch=MIX1
+scalar=80
+curvetype=expo
+points=25,25
+[mixer]
+src=ELE
+dest=Ch3
+switch=MIX2
+curvetype=expo
+points=0,0
+
+[channel4]
+template=simple
+[mixer]
+src=RUD
+dest=Ch4
+
+[channel5]
+template=simple
+[mixer]
+src=!AIL DR0
+dest=Ch5
+curvetype=min/max
+points=0
+
+[channel6]
+template=simple
+[mixer]
+src=RUD DR1
+dest=Ch6
+curvetype=min/max
+points=0
+
+[channel7]
+template=complex
+[mixer]
+src=FMODE0
+dest=Ch7
+switch=FMODE0
+scalar=-100
+usetrim=0
+curvetype=fixed
+[mixer]
+src=AIL
+dest=Ch7
+switch=!FMODE0
+usetrim=0
+curvetype=fixed
+
+[channel8]
+template=simple
+[mixer]
+src=!GEAR0
+dest=Ch8
+curvetype=min/max
+points=0
+
+[channel9]
+template=simple
+
+[channel10]
+template=simple
+
+[channel11]
+template=simple
+
+[virtchan1]
+template=simple
+[mixer]
+src=Ch1
+dest=Virt1
+scalar=50
+offset=50
+
+[virtchan2]
+template=complex
+[mixer]
+src=THR
+dest=Virt2
+curvetype=expo
+points=0,0
+[mixer]
+src=THR
+dest=Virt2
+switch=RUD DR1
+scalar=-100
+curvetype=fixed
+
+[trim1]
+src=Ch9
+pos=TRIMLV+
+neg=TRIMLV-
+step=10
+switch=GEAR
+value=52,-18,0
+[trim2]
+src=Ch11
+pos=TRIMRV+
+neg=TRIMRV-
+step=10
+switch=GEAR
+value=26,0,0
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+switch=GEAR
+[trim4]
+src=Ch10
+pos=TRIMRH+
+neg=TRIMRH-
+step=10
+switch=GEAR
+value=33,0,0
+[timer1]
+src=Virt2
+[timer2]
+type=permanent
+src=Virt2
+val=1733598
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-320x240]
+V-trim=133,75,1
+H-trim=6,220,3
+V-trim=183,75,2
+H-trim=191,220,4
+Big-box=9,40,Ch1
+Big-box=9,90,Timer1
+Small-box=9,140,Timer2
+Bargraph=205,150,Ch2
+Bargraph=235,150,Ch3
+Bargraph=265,150,Ch1
+Bargraph=295,150,Ch4
+Model=206,40
+Toggle=130,38,1,64,128,None
+Toggle=168,38,2,65,129,None
+Toggle=147,76,0,66,0,RUD DR
+Toggle=147,113,0,67,0,AIL DR
+Toggle=147,153,0,68,0,ELE DR
+Toggle=147,192,8,71,0,None
+Big-box=9,177,Virt1
+quickpage1=Telemetry monitor
\ No newline at end of file
diff --git a/src/tests/models/yacht.ini b/src/tests/models/yacht.ini
new file mode 100644
index 0000000000..385779e5d2
--- /dev/null
+++ b/src/tests/models/yacht.ini
@@ -0,0 +1,81 @@
+name=Joysway Orion
+mixermode=Advanced
+icon=YACHT.BMP
+type=multi
+[radio]
+protocol=DSM2
+num_channels=2
+fixed_id=859231
+tx_power=100mW
+
+[protocol_opts]
+Telemetry=On
+
+[channel1]
+reverse=1
+template=complex
+[mixer]
+src=THR
+dest=Ch1
+[mixer]
+src=AIL
+dest=Ch1
+switch=AIL DR1
+scalar=20
+usetrim=0
+muxtype=add
+curvetype=fixed
+
+[channel2]
+template=complex
+[mixer]
+src=RUD
+dest=Ch2
+[mixer]
+src=AIL
+dest=Ch2
+usetrim=0
+muxtype=add
+
+[trim1]
+src=LEFT_V
+pos=TRIMLV+
+neg=TRIMLV-
+[trim2]
+src=RIGHT_V
+pos=TRIMRV+
+neg=TRIMRV-
+[trim3]
+src=LEFT_H
+pos=TRIMLH+
+neg=TRIMLH-
+value=1,0,0
+[trim4]
+src=RIGHT_H
+pos=TRIMRH+
+neg=TRIMRH-
+[timer1]
+src=Ch3
+resetsrc=ELE DR1
+[timer2]
+type=permanent
+src=Ch3
+val=19187434
+[datalog]
+switch=None
+rate=1 sec
+[safety]
+Auto=min
+[gui-128x64]
+V-trim=59,10,1
+H-trim=5,59,3
+Small-box=2,35,Timer1
+Small-box=2,45,Timer2
+Model=75,20
+Battery=102,1
+Toggle=22,10
+Switch=198,AIL DR1
+TxPower=102,7
+Big-box=2,21,Volt1
+quickpage1=Channel monitor
+quickpage2=Telemetry monitor
\ No newline at end of file
diff --git a/src/tests/test_config.c b/src/tests/test_config.c
new file mode 100644
index 0000000000..a3a3224fd8
--- /dev/null
+++ b/src/tests/test_config.c
@@ -0,0 +1,110 @@
+#include "CuTest.h"
+
+struct config_values {
+ u8 u8_val;
+ u16 u16_val;
+ u32 u32_val;
+
+ s8 s8_val;
+ s16 s16_val;
+ s32 s32_val;
+
+ u16 color_val;
+ u8 str_index_val;
+ u8 font_val;
+
+ u8 mixer;
+} TestConfig;
+
+enum {
+ STR_NONE = 0,
+ STR_CENTER,
+ STR_LEFT,
+ STR_RIGHT
+};
+
+static const char * const ALIGN_VAL[] = {
+ [STR_CENTER] = "center",
+ [STR_LEFT] = "left",
+ [STR_RIGHT] = "right",
+ };
+
+static const char *string_values(int i) {
+ if (i == 0)
+ return "standard";
+ else
+ return "advanced";
+}
+
+static const struct struct_map _secgeneral[] =
+{
+ {"u8val", OFFSET(struct config_values, u8_val)},
+ {"u16val", OFFSET(struct config_values, u16_val)},
+ {"u32val", OFFSET(struct config_values, u32_val)},
+ {"s8val", OFFSETS(struct config_values, s8_val)},
+ {"s16val", OFFSETS(struct config_values, s16_val)},
+ {"s32val", OFFSETS(struct config_values, s32_val)},
+ {"index", OFFSET_STRLIST(struct config_values, str_index_val, ALIGN_VAL, ARRAYSIZE(ALIGN_VAL))},
+ {"color", OFFSET_COL(struct config_values, color_val)},
+ {"font", OFFSET_FON(struct config_values, font_val)},
+ {"mixer", OFFSET_STRCALL(struct config_values, mixer, string_values, 2)},
+};
+
+void TestConfigBasic(CuTest* t) {
+ memset(&TestConfig, 0, sizeof(TestConfig));
+
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "u8val", "5"));
+ CuAssertIntEquals(t, 5, TestConfig.u8_val);
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "s8val", "-5"));
+ CuAssertIntEquals(t, -5, TestConfig.s8_val);
+
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "u16val", "5000"));
+ CuAssertIntEquals(t, 5000, TestConfig.u16_val);
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "s16val", "-6005"));
+ CuAssertIntEquals(t, -6005, TestConfig.s16_val);
+
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "u32val", "755350"));
+ CuAssertIntEquals(t, 755350, TestConfig.u32_val);
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "s32val", "-655350"));
+ CuAssertIntEquals(t, -655350, TestConfig.s32_val);
+
+ CuAssertTrue(t, !assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "notfound", "0"));
+
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "color", "000000"));
+ CuAssertIntEquals(t, 0, TestConfig.color_val);
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "color", "FEDCBA"));
+ CuAssertIntEquals(t, 0xfef7, TestConfig.color_val);
+}
+
+void TestConfigStringList(CuTest* t)
+{
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "index", "center"));
+ CuAssertIntEquals(t, STR_CENTER, TestConfig.str_index_val);
+
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "index", "left"));
+ CuAssertIntEquals(t, STR_LEFT, TestConfig.str_index_val);
+
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "index", "right"));
+ CuAssertIntEquals(t, STR_RIGHT, TestConfig.str_index_val);
+
+ TestConfig.str_index_val = 254;
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "index", "invalid"));
+ CuAssertIntEquals(t, 254, TestConfig.str_index_val);
+}
+
+void TestConfigFont(CuTest* t)
+{
+ int fontindex = FONT_GetFromString("font1");
+
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "font", "font1"));
+ CuAssertIntEquals(t, fontindex, TestConfig.font_val);
+}
+
+void TestConfigStringCallback(CuTest* t)
+{
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "mixer", "standard"));
+ CuAssertIntEquals(t, 0, TestConfig.mixer);
+
+ CuAssertTrue(t, assign_int(&TestConfig, _secgeneral, ARRAYSIZE(_secgeneral), "mixer", "advanced"));
+ CuAssertIntEquals(t, 1, TestConfig.mixer);
+}
diff --git a/src/tests/test_model.c b/src/tests/test_model.c
index f5884c35db..a4ec040959 100644
--- a/src/tests/test_model.c
+++ b/src/tests/test_model.c
@@ -1,5 +1,72 @@
#include "CuTest.h"
+extern u8 CONFIG_ReadModel_old(const char* file);
+extern u8 CONFIG_WriteModel_old(u8 model_num);
+
+u8 CONFIG_ReadModel_new(const char* file) {
+ clear_model(1);
+
+ auto_map = 0;
+ if (CONFIG_IniParse(file, ini_handler, &Model)) {
+ printf("Failed to parse Model file: %s\n", file);
+ }
+ if (! ELEM_USED(Model.pagecfg2.elem[0]))
+ CONFIG_ReadLayout("layout/default.ini");
+ if(! PROTOCOL_HasPowerAmp(Model.protocol))
+ Model.tx_power = TXPOWER_150mW;
+ Model.radio = PROTOCOL_GetRadio(Model.protocol);
+ MIXER_SetMixers(NULL, 0);
+ if(auto_map)
+ RemapChannelsForProtocol(EATRG0);
+ if(! Model.name[0])
+ sprintf(Model.name, "Model%d", 1);
+ return 1;
+}
+
+const char* const names[] = {
+// "../../tests/models/geniuscp.ini",
+
+"../../tests/models/fx071.ini",
+
+"../../tests/models/280qav.ini",
+"../../tests/models/bixler2.ini",
+
+"../../tests/models/yacht.ini",
+
+"../../tests/models/4g6s.ini",
+"../../tests/models/blade130x.ini",
+"../../tests/models/nazath.ini",
+
+"../../tests/models/apm.ini",
+"../../tests/models/deltaray.ini",
+"../../tests/models/trex150dfc.ini",
+
+"../../tests/models/ardrone2.ini",
+"../../tests/models/wltoys931.ini",
+};
+
+void TestNewAndOld(CuTest *t)
+{
+ struct Model ValidateModel;
+
+ for (unsigned i = 0; i < ARRAYSIZE(names); i++) {
+ const char *filename = names[i];
+ printf("Test model: %s\n", filename);
+ CuAssertTrue(t, CONFIG_ReadModel_old(filename));
+ memcpy(&ValidateModel, &Model, sizeof(Model));
+ CuAssertTrue(t, CONFIG_ReadModel_new(filename));
+ printf("\tRead successfully\n", filename);
+
+ CuAssertTrue(t, memcmp(&ValidateModel, &Model, sizeof(Model)) == 0);
+ printf("\tRead result is identical\n", filename);
+
+ CONFIG_WriteModel(1);
+ CONFIG_ReadModel(1);
+ CuAssertTrue(t, memcmp(&ValidateModel, &Model, sizeof(Model)) == 0);
+ printf("\tWrite result is identical\n", filename);
+ }
+}
+
void TestModelLoadSave(CuTest *t)
{
struct Model ValidateModel;