Bsongis/x7d companion support (#4028)
[opentx.git] / companion / src / eeprominterface.cpp
blobcee3ab0e3ff07e60f0f0d1098f92deaf64586f16
1 #include <stdio.h>
2 #include <list>
3 #include <float.h>
4 #include <QtWidgets>
5 #include <stdlib.h>
6 #include "eeprominterface.h"
7 #include "firmwares/er9x/er9xinterface.h"
8 #include "firmwares/th9x/th9xinterface.h"
9 #include "firmwares/gruvin9x/gruvin9xinterface.h"
10 #include "firmwares/opentx/opentxinterface.h"
11 #include "firmwares/opentx/opentxeeprom.h"
12 #include "firmwares/ersky9x/ersky9xinterface.h"
13 #include "appdata.h"
14 #include "helpers.h"
15 #include "wizarddata.h"
16 #include "firmwareinterface.h"
18 std::list<QString> EEPROMWarnings;
20 const uint8_t chout_ar[] = { // First number is 0..23 -> template setup, Second is relevant channel out
21 1,2,3,4 , 1,2,4,3 , 1,3,2,4 , 1,3,4,2 , 1,4,2,3 , 1,4,3,2,
22 2,1,3,4 , 2,1,4,3 , 2,3,1,4 , 2,3,4,1 , 2,4,1,3 , 2,4,3,1,
23 3,1,2,4 , 3,1,4,2 , 3,2,1,4 , 3,2,4,1 , 3,4,1,2 , 3,4,2,1,
24 4,1,2,3 , 4,1,3,2 , 4,2,1,3 , 4,2,3,1 , 4,3,1,2 , 4,3,2,1
27 void setEEPROMString(char *dst, const char *src, int size)
29 memcpy(dst, src, size);
30 for (int i=size-1; i>=0; i--) {
31 if (dst[i] == '\0')
32 dst[i] = ' ';
33 else
34 break;
38 void getEEPROMString(char *dst, const char *src, int size)
40 memcpy(dst, src, size);
41 dst[size] = '\0';
42 for (int i=size-1; i>=0; i--) {
43 if (dst[i] == ' ')
44 dst[i] = '\0';
45 else
46 break;
50 float ValToTim(int value)
52 return ((value < -109 ? 129+value : (value < 7 ? (113+value)*5 : (53+value)*10))/10.0);
55 int TimToVal(float value)
57 int temp;
58 if (value>60) {
59 temp=136+round((value-60));
61 else if (value>2) {
62 temp=20+round((value-2.0)*2.0);
64 else {
65 temp=round(value*10.0);
67 return (temp-129);
70 QString getSignedStr(int value)
72 return value > 0 ? QString("+%1").arg(value) : QString("%1").arg(value);
75 QString getGVarString(int16_t val, bool sign)
77 if (val >= -10000 && val <= 10000) {
78 if (sign)
79 return QString("%1%").arg(getSignedStr(val));
80 else
81 return QString("%1%").arg(val);
83 else {
84 if (val<0)
85 return QObject::tr("-GV%1").arg(-val-10000);
86 else
87 return QObject::tr("GV%1").arg(val-10000);
91 void SensorData::updateUnit()
93 if (type == TELEM_TYPE_CALCULATED) {
94 if (formula == TELEM_FORMULA_CONSUMPTION)
95 unit = UNIT_MAH;
99 QString SensorData::unitString() const
101 switch (unit) {
102 case UNIT_VOLTS:
103 return QObject::tr("V");
104 case UNIT_AMPS:
105 return QObject::tr("A");
106 case UNIT_MILLIAMPS:
107 return QObject::tr("mA");
108 case UNIT_KTS:
109 return QObject::tr("kts");
110 case UNIT_METERS_PER_SECOND:
111 return QObject::tr("m/s");
112 case UNIT_KMH:
113 return QObject::tr("km/h");
114 case UNIT_MPH:
115 return QObject::tr("mph");
116 case UNIT_METERS:
117 return QObject::tr("m");
118 case UNIT_FEET:
119 return QObject::tr("f");
120 case UNIT_CELSIUS:
121 return QObject::trUtf8("°C");
122 case UNIT_FAHRENHEIT:
123 return QObject::trUtf8("°F");
124 case UNIT_PERCENT:
125 return QObject::tr("%");
126 case UNIT_MAH:
127 return QObject::tr("mAh");
128 case UNIT_WATTS:
129 return QObject::tr("W");
130 case UNIT_DB:
131 return QObject::tr("dB");
132 case UNIT_RPMS:
133 return QObject::tr("rpms");
134 case UNIT_G:
135 return QObject::tr("g");
136 case UNIT_DEGREE:
137 return QObject::trUtf8("°");
138 case UNIT_HOURS:
139 return QObject::tr("hours");
140 case UNIT_MINUTES:
141 return QObject::tr("minutes");
142 case UNIT_SECONDS:
143 return QObject::tr("seconds");
144 case UNIT_CELLS:
145 return QObject::tr("V");
146 default:
147 return "";
151 bool RawSource::isTimeBased() const
153 if (IS_ARM(GetCurrentFirmware()->getBoard()))
154 return (type == SOURCE_TYPE_SPECIAL && index > 0);
155 else
156 return (type==SOURCE_TYPE_TELEMETRY && (index==TELEMETRY_SOURCE_TX_TIME || index==TELEMETRY_SOURCE_TIMER1 || index==TELEMETRY_SOURCE_TIMER2 || index==TELEMETRY_SOURCE_TIMER3));
159 float RawSourceRange::getValue(int value)
161 if (IS_ARM(GetCurrentFirmware()->getBoard()))
162 return float(value) * step;
163 else
164 return min + float(value) * step;
167 RawSourceRange RawSource::getRange(const ModelData * model, const GeneralSettings & settings, unsigned int flags) const
169 RawSourceRange result;
171 Firmware * firmware = GetCurrentFirmware();
172 int board = firmware->getBoard();
173 bool singleprec = (flags & RANGE_SINGLE_PRECISION);
175 if (!singleprec && !IS_ARM(board)) {
176 singleprec = true;
179 switch (type) {
180 case SOURCE_TYPE_TELEMETRY:
181 if (IS_ARM(board)) {
182 div_t qr = div(index, 3);
183 const SensorData & sensor = model->sensorData[qr.quot];
184 if (sensor.prec == 2)
185 result.step = 0.01;
186 else if (sensor.prec == 1)
187 result.step = 0.1;
188 else
189 result.step = 1;
190 result.min = -30000 * result.step;
191 result.max = +30000 * result.step;
192 result.decimals = sensor.prec;
193 result.unit = sensor.unitString();
195 else {
196 if (singleprec) {
197 result.offset = -DBL_MAX;
200 switch (index) {
201 case TELEMETRY_SOURCE_TX_BATT:
202 result.step = 0.1;
203 result.decimals = 1;
204 result.max = 25.5;
205 result.unit = QObject::tr("V");
206 break;
207 case TELEMETRY_SOURCE_TX_TIME:
208 result.step = 1;
209 result.max = 24*60 - 1;
210 break;
211 case TELEMETRY_SOURCE_TIMER1:
212 case TELEMETRY_SOURCE_TIMER2:
213 case TELEMETRY_SOURCE_TIMER3:
214 result.step = singleprec ? 5 : 1;
215 result.max = singleprec ? 255*5 : 60*60;
216 result.unit = QObject::tr("s");
217 break;
218 case TELEMETRY_SOURCE_RSSI_TX:
219 case TELEMETRY_SOURCE_RSSI_RX:
220 result.max = 100;
221 if (singleprec) result.offset = 128;
222 break;
223 case TELEMETRY_SOURCE_A1_MIN:
224 case TELEMETRY_SOURCE_A2_MIN:
225 case TELEMETRY_SOURCE_A3_MIN:
226 case TELEMETRY_SOURCE_A4_MIN:
227 if (model) result = model->frsky.channels[index-TELEMETRY_SOURCE_A1_MIN].getRange();
228 break;
229 case TELEMETRY_SOURCE_A1:
230 case TELEMETRY_SOURCE_A2:
231 case TELEMETRY_SOURCE_A3:
232 case TELEMETRY_SOURCE_A4:
233 if (model) result = model->frsky.channels[index-TELEMETRY_SOURCE_A1].getRange();
234 break;
235 case TELEMETRY_SOURCE_ALT:
236 case TELEMETRY_SOURCE_ALT_MIN:
237 case TELEMETRY_SOURCE_ALT_MAX:
238 case TELEMETRY_SOURCE_GPS_ALT:
239 result.step = singleprec ? 8 : 1;
240 result.min = -500;
241 result.max = singleprec ? 1540 : 3000;
242 if (firmware->getCapability(Imperial) || settings.imperial) {
243 result.step = (result.step * 105) / 32;
244 result.min = (result.min * 105) / 32;
245 result.max = (result.max * 105) / 32;
246 result.unit = QObject::tr("ft");
248 else {
249 result.unit = QObject::tr("m");
251 break;
252 case TELEMETRY_SOURCE_T1:
253 case TELEMETRY_SOURCE_T1_MAX:
254 case TELEMETRY_SOURCE_T2:
255 case TELEMETRY_SOURCE_T2_MAX:
256 result.min = -30;
257 result.max = 225;
258 result.unit = QObject::trUtf8("°C");
259 break;
260 case TELEMETRY_SOURCE_HDG:
261 result.step = singleprec ? 2 : 1;
262 result.max = 360;
263 if (singleprec) result.offset = 256;
264 result.unit = QObject::trUtf8("°");
265 break;
266 case TELEMETRY_SOURCE_RPM:
267 case TELEMETRY_SOURCE_RPM_MAX:
268 result.step = singleprec ? 50 : 1;
269 result.max = singleprec ? 12750 : 30000;
270 break;
271 case TELEMETRY_SOURCE_FUEL:
272 result.max = 100;
273 result.unit = QObject::tr("%");
274 break;
275 case TELEMETRY_SOURCE_ASPEED:
276 case TELEMETRY_SOURCE_ASPEED_MAX:
277 result.decimals = 1;
278 result.step = singleprec ? 2.0 : 0.1;
279 result.max = singleprec ? (2*255) : 2000;
280 if (firmware->getCapability(Imperial) || settings.imperial) {
281 result.step *= 1.150779;
282 result.max *= 1.150779;
283 result.unit = QObject::tr("mph");
285 else {
286 result.step *= 1.852;
287 result.max *= 1.852;
288 result.unit = QObject::tr("km/h");
290 break;
291 case TELEMETRY_SOURCE_SPEED:
292 case TELEMETRY_SOURCE_SPEED_MAX:
293 result.step = singleprec ? 2 : 1;
294 result.max = singleprec ? (2*255) : 2000;
295 if (firmware->getCapability(Imperial) || settings.imperial) {
296 result.step *= 1.150779;
297 result.max *= 1.150779;
298 result.unit = QObject::tr("mph");
300 else {
301 result.step *= 1.852;
302 result.max *= 1.852;
303 result.unit = QObject::tr("km/h");
305 break;
306 case TELEMETRY_SOURCE_VERTICAL_SPEED:
307 result.step = 0.1;
308 result.min = singleprec ? -12.5 : -300.0;
309 result.max = singleprec ? 13.0 : 300.0;
310 result.decimals = 1;
311 result.unit = QObject::tr("m/s");
312 break;
313 case TELEMETRY_SOURCE_DTE:
314 result.max = 30000;
315 break;
316 case TELEMETRY_SOURCE_DIST:
317 case TELEMETRY_SOURCE_DIST_MAX:
318 result.step = singleprec ? 8 : 1;
319 result.max = singleprec ? 2040 : 10000;
320 result.unit = QObject::tr("m");
321 break;
322 case TELEMETRY_SOURCE_CELL:
323 case TELEMETRY_SOURCE_CELL_MIN:
324 result.step = singleprec ? 0.02 : 0.01;
325 result.max = 5.1;
326 result.decimals = 2;
327 result.unit = QObject::tr("V");
328 break;
329 case TELEMETRY_SOURCE_CELLS_SUM:
330 case TELEMETRY_SOURCE_CELLS_MIN:
331 case TELEMETRY_SOURCE_VFAS:
332 case TELEMETRY_SOURCE_VFAS_MIN:
333 result.step = 0.1;
334 result.max = singleprec ? 25.5 : 100.0;
335 result.decimals = 1;
336 result.unit = QObject::tr("V");
337 break;
338 case TELEMETRY_SOURCE_CURRENT:
339 case TELEMETRY_SOURCE_CURRENT_MAX:
340 result.step = singleprec ? 0.5 : 0.1;
341 result.max = singleprec ? 127.5 : 200.0;
342 result.decimals = 1;
343 result.unit = QObject::tr("A");
344 break;
345 case TELEMETRY_SOURCE_CONSUMPTION:
346 result.step = singleprec ? 100 : 1;
347 result.max = singleprec ? 25500 : 30000;
348 result.unit = QObject::tr("mAh");
349 break;
350 case TELEMETRY_SOURCE_POWER:
351 case TELEMETRY_SOURCE_POWER_MAX:
352 result.step = singleprec ? 5 : 1;
353 result.max = singleprec ? 1275 : 2000;
354 result.unit = QObject::tr("W");
355 break;
356 case TELEMETRY_SOURCE_ACCX:
357 case TELEMETRY_SOURCE_ACCY:
358 case TELEMETRY_SOURCE_ACCZ:
359 result.step = 0.01;
360 result.decimals = 2;
361 result.max = singleprec ? 2.55 : 10.00;
362 result.min = singleprec ? 0 : -10.00;
363 result.unit = QObject::tr("g");
364 break;
365 default:
366 result.max = 125;
367 break;
370 if (singleprec && result.offset==-DBL_MAX) {
371 result.offset = result.max - (127*result.step);
374 if (flags & (RANGE_DELTA_FUNCTION|RANGE_DELTA_ABS_FUNCTION)) {
375 if (singleprec) {
376 result.offset = 0;
377 result.min = result.step * -127;
378 result.max = result.step * 127;
380 else {
381 result.min = -result.max;
385 break;
387 case SOURCE_TYPE_GVAR:
388 result.max = 1024;
389 result.min = -result.max;
390 break;
392 case SOURCE_TYPE_SPECIAL:
393 if (index == 0) { //Batt
394 result.step = 0.1;
395 result.decimals = 1;
396 result.max = 25.5;
397 result.unit = QObject::tr("V");
399 else if (index == 1) { //Time
400 result.step = 1;
401 result.max = 24*60 - 1;
402 result.unit = QObject::tr("h:m");
404 else { // Timers 1 - 3
405 result.step = singleprec ? 5 : 1;
406 result.max = singleprec ? 255*5 : 60*60;
407 result.unit = singleprec ? QObject::tr("m:s") : QObject::tr("h:m:s");
409 break;
411 default:
412 if (model) {
413 result.max = model->getChannelsMax(true);
414 result.min = -result.max;
416 break;
419 if (flags & RANGE_DELTA_ABS_FUNCTION) {
420 result.min = 0;
423 return result;
426 QString AnalogString(int index)
428 static const QString sticks[] = { QObject::tr("Rud"), QObject::tr("Ele"), QObject::tr("Thr"), QObject::tr("Ail") };
429 static const QString pots9X[] = { QObject::tr("P1"), QObject::tr("P2"), QObject::tr("P3") };
430 static const QString potsTaranisX9E[] = { QObject::tr("F1"), QObject::tr("F2"), QObject::tr("F3"), QObject::tr("F4"), QObject::tr("S1"), QObject::tr("S2"), QObject::tr("LS"), QObject::tr("RS") };
431 static const QString potsTaranis[] = { QObject::tr("S1"), QObject::tr("S2"), QObject::tr("S3"), QObject::tr("LS"), QObject::tr("RS") };
432 if (index < 4)
433 return CHECK_IN_ARRAY(sticks, index);
434 else if (IS_TARANIS_X9E(GetEepromInterface()->getBoard()))
435 return CHECK_IN_ARRAY(potsTaranisX9E, index-4);
436 else if (IS_TARANIS(GetEepromInterface()->getBoard()))
437 return CHECK_IN_ARRAY(potsTaranis, index-4);
438 else
439 return CHECK_IN_ARRAY(pots9X, index-4);
442 QString RotaryEncoderString(int index)
444 static const QString rotary[] = { QObject::tr("REa"), QObject::tr("REb") };
445 return CHECK_IN_ARRAY(rotary, index);
448 QString RawSource::toString(const ModelData * model) const
450 static const QString trims[] = {
451 QObject::tr("TrmR"), QObject::tr("TrmE"), QObject::tr("TrmT"), QObject::tr("TrmA")
454 static const QString special[] = {
455 QObject::tr("Batt"), QObject::tr("Time"), QObject::tr("Timer1"), QObject::tr("Timer2"), QObject::tr("Timer3"),
458 static const QString telemetry[] = {
459 QObject::tr("Batt"), QObject::tr("Time"), QObject::tr("Timer1"), QObject::tr("Timer2"), QObject::tr("Timer3"),
460 QObject::tr("SWR"), QObject::tr("RSSI Tx"), QObject::tr("RSSI Rx"),
461 QObject::tr("A1"), QObject::tr("A2"), QObject::tr("A3"), QObject::tr("A4"),
462 QObject::tr("Alt"), QObject::tr("Rpm"), QObject::tr("Fuel"), QObject::tr("T1"), QObject::tr("T2"),
463 QObject::tr("Speed"), QObject::tr("Dist"), QObject::tr("GPS Alt"),
464 QObject::tr("Cell"), QObject::tr("Cells"), QObject::tr("Vfas"), QObject::tr("Curr"), QObject::tr("Cnsp"), QObject::tr("Powr"),
465 QObject::tr("AccX"), QObject::tr("AccY"), QObject::tr("AccZ"),
466 QObject::tr("Hdg "), QObject::tr("VSpd"), QObject::tr("AirSpeed"), QObject::tr("dTE"),
467 QObject::tr("A1-"), QObject::tr("A2-"), QObject::tr("A3-"), QObject::tr("A4-"),
468 QObject::tr("Alt-"), QObject::tr("Alt+"), QObject::tr("Rpm+"), QObject::tr("T1+"), QObject::tr("T2+"), QObject::tr("Speed+"), QObject::tr("Dist+"), QObject::tr("AirSpeed+"),
469 QObject::tr("Cell-"), QObject::tr("Cells-"), QObject::tr("Vfas-"), QObject::tr("Curr+"), QObject::tr("Powr+"),
470 QObject::tr("ACC"), QObject::tr("GPS Time"),
473 if (index<0) {
474 return QObject::tr("----");
477 switch (type) {
478 case SOURCE_TYPE_VIRTUAL_INPUT:
480 QString result = QObject::tr("[I%1]").arg(index+1);
481 if (model && strlen(model->inputNames[index]) > 0) {
482 result += QString(model->inputNames[index]);
484 return result;
486 case SOURCE_TYPE_LUA_OUTPUT:
487 return QObject::tr("LUA%1%2").arg(index/16+1).arg(QChar('a'+index%16));
488 case SOURCE_TYPE_STICK:
489 return AnalogString(index);
490 case SOURCE_TYPE_TRIM:
491 return CHECK_IN_ARRAY(trims, index);
492 case SOURCE_TYPE_ROTARY_ENCODER:
493 return RotaryEncoderString(index);
494 case SOURCE_TYPE_MAX:
495 return QObject::tr("MAX");
496 case SOURCE_TYPE_SWITCH:
497 return GetCurrentFirmware()->getSwitch(index).name;
498 case SOURCE_TYPE_CUSTOM_SWITCH:
499 return QObject::tr("L%1").arg(index+1);
500 case SOURCE_TYPE_CYC:
501 return QObject::tr("CYC%1").arg(index+1);
502 case SOURCE_TYPE_PPM:
503 return QObject::tr("TR%1").arg(index+1);
504 case SOURCE_TYPE_CH:
505 return QObject::tr("CH%1").arg(index+1);
506 case SOURCE_TYPE_SPECIAL:
507 return CHECK_IN_ARRAY(special, index);
508 case SOURCE_TYPE_TELEMETRY:
509 if (IS_ARM(GetEepromInterface()->getBoard())) {
510 div_t qr = div(index, 3);
511 QString result = QString(model ? model->sensorData[qr.quot].label : QString("[T%1]").arg(qr.quot+1));
512 if (qr.rem) result += qr.rem == 1 ? "-" : "+";
513 return result;
515 else {
516 return CHECK_IN_ARRAY(telemetry, index);
518 case SOURCE_TYPE_GVAR:
519 return QObject::tr("GV%1").arg(index+1);
520 default:
521 return QObject::tr("----");
525 bool RawSource::isPot() const
527 return (type == SOURCE_TYPE_STICK &&
528 index >= NUM_STICKS &&
529 index < NUM_STICKS+GetCurrentFirmware()->getCapability(Pots));
532 bool RawSource::isSlider() const
534 return (type == SOURCE_TYPE_STICK &&
535 index >= NUM_STICKS+GetCurrentFirmware()->getCapability(Pots) &&
536 index < NUM_STICKS+GetCurrentFirmware()->getCapability(Pots)+GetCurrentFirmware()->getCapability(Sliders));
539 QString RawSwitch::toString() const
541 static const QString switches9X[] = {
542 QString("THR"), QString("RUD"), QString("ELE"),
543 QString("ID0"), QString("ID1"), QString("ID2"),
544 QString("AIL"), QString("GEA"), QString("TRN")
547 static const QString flightModes[] = {
548 QObject::tr("FM0"), QObject::tr("FM1"), QObject::tr("FM2"), QObject::tr("FM3"), QObject::tr("FM4"), QObject::tr("FM5"), QObject::tr("FM6"), QObject::tr("FM7"), QObject::tr("FM8")
551 static const QString multiposPots[] = {
552 QObject::tr("S11"), QObject::tr("S12"), QObject::tr("S13"), QObject::tr("S14"), QObject::tr("S15"), QObject::tr("S16"),
553 QObject::tr("S21"), QObject::tr("S22"), QObject::tr("S23"), QObject::tr("S24"), QObject::tr("S25"), QObject::tr("S26"),
554 QObject::tr("S31"), QObject::tr("S32"), QObject::tr("S33"), QObject::tr("S34"), QObject::tr("S35"), QObject::tr("S36")
557 static const QString trimsSwitches[] = {
558 QObject::tr("RudTrim Left"), QObject::tr("RudTrim Right"),
559 QObject::tr("EleTrim Down"), QObject::tr("EleTrim Up"),
560 QObject::tr("ThrTrim Down"), QObject::tr("ThrTrim Up"),
561 QObject::tr("AilTrim Left"), QObject::tr("AilTrim Right")
564 static const QString rotaryEncoders[] = {
565 QObject::tr("REa"), QObject::tr("REb")
568 static const QString timerModes[] = {
569 QObject::tr("OFF"), QObject::tr("ON"),
570 QObject::tr("THs"), QObject::tr("TH%"), QObject::tr("THt")
573 if (index < 0) {
574 return QString("!") + RawSwitch(type, -index).toString();
576 else {
577 switch(type) {
578 case SWITCH_TYPE_SWITCH:
579 if (IS_TARANIS(GetEepromInterface()->getBoard())) {
580 div_t qr = div(index-1, 3);
581 Firmware::Switch sw = GetCurrentFirmware()->getSwitch(qr.quot);
582 const char * positions[] = { ARROW_UP, "-", ARROW_DOWN };
583 return QString(sw.name) + QString(positions[qr.rem]);
585 else {
586 return CHECK_IN_ARRAY(switches9X, index - 1);
588 case SWITCH_TYPE_VIRTUAL:
589 return QObject::tr("L%1").arg(index);
590 case SWITCH_TYPE_MULTIPOS_POT:
591 return CHECK_IN_ARRAY(multiposPots, index-1);
592 case SWITCH_TYPE_TRIM:
593 return CHECK_IN_ARRAY(trimsSwitches, index-1);
594 case SWITCH_TYPE_ROTARY_ENCODER:
595 return CHECK_IN_ARRAY(rotaryEncoders, index-1);
596 case SWITCH_TYPE_ON:
597 return QObject::tr("ON");
598 case SWITCH_TYPE_OFF:
599 return QObject::tr("OFF");
600 case SWITCH_TYPE_ONE:
601 return QObject::tr("One");
602 case SWITCH_TYPE_FLIGHT_MODE:
603 return CHECK_IN_ARRAY(flightModes, index-1);
604 case SWITCH_TYPE_NONE:
605 return QObject::tr("----");
606 case SWITCH_TYPE_TIMER_MODE:
607 return CHECK_IN_ARRAY(timerModes, index);
608 default:
609 return QObject::tr("???");
614 QString CurveReference::toString() const
616 if (value == 0) {
617 return "----";
619 else {
620 switch(type) {
621 case CURVE_REF_DIFF:
622 return QObject::tr("Diff(%1)").arg(getGVarString(value));
623 case CURVE_REF_EXPO:
624 return QObject::tr("Expo(%1)").arg(getGVarString(value));
625 case CURVE_REF_FUNC:
626 return QObject::tr("Function(%1)").arg(QString("x>0" "x<0" "|x|" "f>0" "f<0" "|f|").mid(3*(value-1), 3));
627 default:
628 return QString(value > 0 ? QObject::tr("Curve(%1)") : QObject::tr("!Curve(%1)")).arg(abs(value));
633 CSFunctionFamily LogicalSwitchData::getFunctionFamily() const
635 if (func == LS_FN_EDGE)
636 return LS_FAMILY_EDGE;
637 else if (func == LS_FN_TIMER)
638 return LS_FAMILY_TIMER;
639 else if (func == LS_FN_STICKY)
640 return LS_FAMILY_STICKY;
641 else if (func < LS_FN_AND || func > LS_FN_ELESS)
642 return LS_FAMILY_VOFS;
643 else if (func < LS_FN_EQUAL)
644 return LS_FAMILY_VBOOL;
645 else
646 return LS_FAMILY_VCOMP;
649 unsigned int LogicalSwitchData::getRangeFlags() const
651 if (func == LS_FN_DPOS)
652 return RANGE_DELTA_FUNCTION;
653 else if (func == LS_FN_DAPOS)
654 return RANGE_DELTA_ABS_FUNCTION;
655 else
656 return 0;
659 QString LogicalSwitchData::funcToString() const
661 switch (func) {
662 case LS_FN_OFF:
663 return QObject::tr("---");
664 case LS_FN_VPOS:
665 return QObject::tr("a>x");
666 case LS_FN_VNEG:
667 return QObject::tr("a<x");
668 case LS_FN_APOS:
669 return QObject::tr("|a|>x");
670 case LS_FN_ANEG:
671 return QObject::tr("|a|<x");
672 case LS_FN_AND:
673 return QObject::tr("AND");
674 case LS_FN_OR:
675 return QObject::tr("OR");
676 case LS_FN_XOR:
677 return QObject::tr("XOR");
678 case LS_FN_EQUAL:
679 return QObject::tr("a=b");
680 case LS_FN_NEQUAL:
681 return QObject::tr("a!=b");
682 case LS_FN_GREATER:
683 return QObject::tr("a>b");
684 case LS_FN_LESS:
685 return QObject::tr("a<b");
686 case LS_FN_EGREATER:
687 return QObject::tr("a>=b");
688 case LS_FN_ELESS:
689 return QObject::tr("a<=b");
690 case LS_FN_DPOS:
691 return QObject::tr("d>=x");
692 case LS_FN_DAPOS:
693 return QObject::tr("|d|>=x");
694 case LS_FN_VEQUAL:
695 return QObject::tr("a=x");
696 case LS_FN_VALMOSTEQUAL:
697 return QObject::tr("a~x");
698 case LS_FN_TIMER:
699 return QObject::tr("Timer");
700 case LS_FN_STICKY:
701 return QObject::tr("Sticky");
702 case LS_FN_EDGE:
703 return QObject::tr("Edge");
704 default:
705 return QObject::tr("Unknown");
709 void CustomFunctionData::clear()
711 memset(this, 0, sizeof(CustomFunctionData));
712 if (!GetCurrentFirmware()->getCapability(SafetyChannelCustomFunction)) {
713 func = FuncTrainer;
717 QString CustomFunctionData::funcToString() const
719 if (func >= FuncOverrideCH1 && func <= FuncOverrideCH32)
720 return QObject::tr("Override %1").arg(RawSource(SOURCE_TYPE_CH, func).toString());
721 else if (func == FuncTrainer)
722 return QObject::tr("Trainer");
723 else if (func == FuncTrainerRUD)
724 return QObject::tr("Trainer RUD");
725 else if (func == FuncTrainerELE)
726 return QObject::tr("Trainer ELE");
727 else if (func == FuncTrainerTHR)
728 return QObject::tr("Trainer THR");
729 else if (func == FuncTrainerAIL)
730 return QObject::tr("Trainer AIL");
731 else if (func == FuncInstantTrim)
732 return QObject::tr("Instant Trim");
733 else if (func == FuncPlaySound)
734 return QObject::tr("Play Sound");
735 else if (func == FuncPlayHaptic)
736 return QObject::tr("Haptic");
737 else if (func == FuncReset)
738 return QObject::tr("Reset");
739 else if (func >= FuncSetTimer1 && func <= FuncSetTimer3)
740 return QObject::tr("Set Timer %1").arg(func-FuncSetTimer1+1);
741 else if (func == FuncVario)
742 return QObject::tr("Vario");
743 else if (func == FuncPlayPrompt)
744 return QObject::tr("Play Track");
745 else if (func == FuncPlayBoth)
746 return QObject::tr("Play Both");
747 else if (func == FuncPlayValue)
748 return QObject::tr("Play Value");
749 else if (func == FuncPlayScript)
750 return QObject::tr("Play Script");
751 else if (func == FuncLogs)
752 return QObject::tr("SD Logs");
753 else if (func == FuncVolume)
754 return QObject::tr("Volume");
755 else if (func == FuncBacklight)
756 return QObject::tr("Backlight");
757 else if (func == FuncScreenshot)
758 return QObject::tr("Screenshot");
759 else if (func == FuncBackgroundMusic)
760 return QObject::tr("Background Music");
761 else if (func == FuncBackgroundMusicPause)
762 return QObject::tr("Background Music Pause");
763 else if (func >= FuncAdjustGV1 && func <= FuncAdjustGVLast)
764 return QObject::tr("Adjust GV%1").arg(func-FuncAdjustGV1+1);
765 else if (func == FuncSetFailsafeInternalModule)
766 return QObject::tr("SetFailsafe Int. Module");
767 else if (func == FuncSetFailsafeExternalModule)
768 return QObject::tr("SetFailsafe Ext. Module");
769 else if (func == FuncRangeCheckInternalModule)
770 return QObject::tr("RangeCheck Int. Module");
771 else if (func == FuncRangeCheckExternalModule)
772 return QObject::tr("RangeCheck Ext. Module");
773 else if (func == FuncBindInternalModule)
774 return QObject::tr("Bind Int. Module");
775 else if (func == FuncBindExternalModule)
776 return QObject::tr("Bind Ext. Module");
777 else {
778 return QString("???"); // Highlight unknown functions with output of question marks.(BTW should not happen that we do not know what a function is)
782 void CustomFunctionData::populateResetParams(const ModelData * model, QComboBox * b, unsigned int value = 0)
784 int val = 0;
785 Firmware * firmware = GetCurrentFirmware();
786 BoardEnum board = GetEepromInterface()->getBoard();
788 b->addItem(QObject::tr("Timer1"), val++);
789 b->addItem(QObject::tr("Timer2"), val++);
790 if (IS_ARM(board)) {
791 b->addItem( QObject::tr("Timer3"), val++);
793 b->addItem(QObject::tr("Flight"), val++);
794 b->addItem(QObject::tr("Telemetry"), val++);
795 int reCount = firmware->getCapability(RotaryEncoders);
796 if (reCount == 1) {
797 b->addItem(QObject::tr("Rotary Encoder"), val++);
799 else if (reCount == 2) {
800 b->addItem(QObject::tr("REa"), val++);
801 b->addItem(QObject::tr("REb"), val++);
803 if ((int)value < b->count()) {
804 b->setCurrentIndex(value);
806 if (model && IS_ARM(board)) {
807 for (int i=0; i<C9X_MAX_SENSORS; ++i) {
808 if (model->sensorData[i].isAvailable()) {
809 RawSource item = RawSource(SOURCE_TYPE_TELEMETRY, 3*i);
810 b->addItem(item.toString(model), val+i);
811 if ((int)value == val+i) {
812 b->setCurrentIndex(b->count()-1);
819 void CustomFunctionData::populatePlaySoundParams(QStringList & qs)
821 qs <<"Beep 1" << "Beep 2" << "Beep 3" << "Warn1" << "Warn2" << "Cheep" << "Ratata" << "Tick" << "Siren" << "Ring" ;
822 qs << "SciFi" << "Robot" << "Chirp" << "Tada" << "Crickt" << "AlmClk" ;
825 void CustomFunctionData::populateHapticParams(QStringList & qs)
827 qs << "0" << "1" << "2" << "3";
830 QString CustomFunctionData::paramToString(const ModelData * model) const
832 QStringList qs;
833 if (func <= FuncInstantTrim) {
834 return QString("%1").arg(param);
836 else if (func==FuncLogs) {
837 return QString("%1").arg(param/10.0) + QObject::tr("s");
839 else if (func==FuncPlaySound) {
840 CustomFunctionData::populatePlaySoundParams(qs);
841 if (param>=0 && param<(int)qs.count())
842 return qs.at(param);
843 else
844 return QObject::tr("<font color=red><b>Inconsistent parameter</b></font>");
846 else if (func==FuncPlayHaptic) {
847 CustomFunctionData::populateHapticParams(qs);
848 if (param>=0 && param<(int)qs.count())
849 return qs.at(param);
850 else
851 return QObject::tr("<font color=red><b>Inconsistent parameter</b></font>");
853 else if (func==FuncReset) {
854 QComboBox cb;
855 CustomFunctionData::populateResetParams(model, &cb);
856 int pos = cb.findData(param);
857 if (pos >= 0)
858 return cb.itemText(pos);
859 else
860 return QObject::tr("<font color=red><b>Inconsistent parameter</b></font>");
862 else if ((func==FuncVolume)|| (func==FuncPlayValue)) {
863 RawSource item(param);
864 return item.toString(model);
866 else if ((func==FuncPlayPrompt) || (func==FuncPlayBoth)) {
867 if ( GetCurrentFirmware()->getCapability(VoicesAsNumbers)) {
868 return QString("%1").arg(param);
870 else {
871 return paramarm;
874 else if ((func>=FuncAdjustGV1) && (func<FuncCount)) {
875 switch (adjustMode) {
876 case FUNC_ADJUST_GVAR_CONSTANT:
877 return QObject::tr("Value ")+QString("%1").arg(param);
878 case FUNC_ADJUST_GVAR_SOURCE:
879 case FUNC_ADJUST_GVAR_GVAR:
880 return RawSource(param).toString();
881 case FUNC_ADJUST_GVAR_INCDEC:
882 if (param==0) return QObject::tr("Decr:") + " -1";
883 else return QObject::tr("Incr:") + " +1";
886 return "";
889 QString CustomFunctionData::repeatToString() const
891 if (repeatParam == -1) {
892 return QObject::tr("played once, not during startup");
894 else if (repeatParam == 0) {
895 return "";
897 else {
898 unsigned int step = IS_ARM(GetEepromInterface()->getBoard()) ? 1 : 10;
899 return QObject::tr("repeat(%1s)").arg(step*repeatParam);
903 QString CustomFunctionData::enabledToString() const
905 if ((func>=FuncOverrideCH1 && func<=FuncOverrideCH32) ||
906 (func>=FuncAdjustGV1 && func<=FuncAdjustGVLast) ||
907 (func==FuncReset) ||
908 (func>=FuncSetTimer1 && func<=FuncSetTimer2) ||
909 (func==FuncVolume) ||
910 (func <= FuncInstantTrim)) {
911 if (!enabled) {
912 return QObject::tr("DISABLED");
915 return "";
918 CurveData::CurveData()
920 clear(5);
923 void CurveData::clear(int count)
925 memset(this, 0, sizeof(CurveData));
926 this->count = count;
929 bool CurveData::isEmpty() const
931 for (int i=0; i<count; i++) {
932 if (points[i].y != 0) {
933 return false;
936 return true;
939 QString LimitData::minToString() const
941 return QString::number((qreal)min/10);
944 QString LimitData::maxToString() const
946 return QString::number((qreal)max/10);
949 QString LimitData::revertToString() const
951 return revert ? QObject::tr("INV") : QObject::tr("NOR");
954 QString LimitData::offsetToString() const
956 return QString::number((qreal)offset/10, 'f', 1);
959 void LimitData::clear()
961 memset(this, 0, sizeof(LimitData));
962 min = -1000;
963 max = +1000;
966 GeneralSettings::SwitchInfo GeneralSettings::switchInfoFromSwitchPositionTaranis(unsigned int index)
968 return SwitchInfo((index-1)/3, (index-1)%3);
971 bool GeneralSettings::switchPositionAllowedTaranis(int index) const
973 if (index == 0)
974 return true;
975 SwitchInfo info = switchInfoFromSwitchPositionTaranis(abs(index));
976 if (index < 0 && switchConfig[info.index] != Firmware::SWITCH_3POS)
977 return false;
978 else if (info.position == 1)
979 return switchConfig[info.index] == Firmware::SWITCH_3POS;
980 else
981 return switchConfig[info.index] != Firmware::SWITCH_NONE;
984 bool GeneralSettings::switchSourceAllowedTaranis(int index) const
986 return switchConfig[index] != Firmware::SWITCH_NONE;
989 bool GeneralSettings::isPotAvailable(int index) const
991 if (index<0 || index>GetCurrentFirmware()->getCapability(Pots)) return false;
992 return potConfig[index] != POT_NONE;
995 bool GeneralSettings::isSliderAvailable(int index) const
997 if (index<0 || index>GetCurrentFirmware()->getCapability(Sliders)) return false;
998 return sliderConfig[index] != SLIDER_NONE;
1001 GeneralSettings::GeneralSettings()
1003 memset(this, 0, sizeof(GeneralSettings));
1005 contrast = 25;
1006 vBatWarn = 90;
1008 for (int i=0; i<NUM_STICKS+C9X_NUM_POTS; ++i) {
1009 calibMid[i] = 0x200;
1010 calibSpanNeg[i] = 0x180;
1011 calibSpanPos[i] = 0x180;
1014 for (int i=0; i<GetCurrentFirmware()->getCapability(Switches); i++) {
1015 switchConfig[i] = GetCurrentFirmware()->getSwitch(i).type;
1018 BoardEnum board = GetEepromInterface()->getBoard();
1019 if (IS_TARANIS(board)) {
1020 potConfig[0] = POT_WITH_DETENT;
1021 potConfig[1] = POT_WITH_DETENT;
1022 sliderConfig[0] = SLIDER_WITH_DETENT;
1023 sliderConfig[1] = SLIDER_WITH_DETENT;
1025 else {
1026 for (int i=0; i<3; i++) {
1027 potConfig[i] = POT_WITHOUT_DETENT;
1031 if (IS_ARM(board)) {
1032 speakerVolume = 12;
1035 if (IS_TARANIS_X9E(board)) {
1036 strcpy(bluetoothName, "Taranis");
1039 templateSetup = g.profile[g.id()].channelOrder();
1040 stickMode = g.profile[g.id()].defaultMode();
1042 QString t_calib=g.profile[g.id()].stickPotCalib();
1043 int potsnum=GetCurrentFirmware()->getCapability(Pots);
1044 if (t_calib.isEmpty()) {
1045 return;
1047 else {
1048 QString t_trainercalib=g.profile[g.id()].trainerCalib();
1049 int8_t t_txVoltageCalibration=(int8_t)g.profile[g.id()].txVoltageCalibration();
1050 int8_t t_txCurrentCalibration=(int8_t)g.profile[g.id()].txCurrentCalibration();
1051 int8_t t_PPM_Multiplier=(int8_t)g.profile[g.id()].ppmMultiplier();
1052 uint8_t t_stickMode=(uint8_t)g.profile[g.id()].gsStickMode();
1053 uint8_t t_vBatWarn=(uint8_t)g.profile[g.id()].vBatWarn();
1054 QString t_DisplaySet=g.profile[g.id()].display();
1055 QString t_BeeperSet=g.profile[g.id()].beeper();
1056 QString t_HapticSet=g.profile[g.id()].haptic();
1057 QString t_SpeakerSet=g.profile[g.id()].speaker();
1058 QString t_CountrySet=g.profile[g.id()].countryCode();
1060 if ((t_calib.length()==(NUM_STICKS+potsnum)*12) && (t_trainercalib.length()==16)) {
1061 QString Byte;
1062 int16_t byte16;
1063 bool ok;
1064 for (int i=0; i<(NUM_STICKS+potsnum); i++) {
1065 Byte=t_calib.mid(i*12,4);
1066 byte16=(int16_t)Byte.toInt(&ok,16);
1067 if (ok)
1068 calibMid[i]=byte16;
1069 Byte=t_calib.mid(4+i*12,4);
1070 byte16=(int16_t)Byte.toInt(&ok,16);
1071 if (ok)
1072 calibSpanNeg[i]=byte16;
1073 Byte=t_calib.mid(8+i*12,4);
1074 byte16=(int16_t)Byte.toInt(&ok,16);
1075 if (ok)
1076 calibSpanPos[i]=byte16;
1078 for (int i=0; i<4; i++) {
1079 Byte=t_trainercalib.mid(i*4,4);
1080 byte16=(int16_t)Byte.toInt(&ok,16);
1081 if (ok)
1082 trainer.calib[i]=byte16;
1084 txCurrentCalibration=t_txCurrentCalibration;
1085 txVoltageCalibration=t_txVoltageCalibration;
1086 vBatWarn=t_vBatWarn;
1087 PPM_Multiplier=t_PPM_Multiplier;
1088 stickMode = t_stickMode;
1090 if ((t_DisplaySet.length()==6) && (t_BeeperSet.length()==4) && (t_HapticSet.length()==6) && (t_SpeakerSet.length()==6)) {
1091 uint8_t byte8u;
1092 int8_t byte8;
1093 bool ok;
1094 byte8=(int8_t)t_DisplaySet.mid(0,2).toInt(&ok,16);
1095 if (ok)
1096 optrexDisplay=(byte8==1 ? true : false);
1097 byte8u=(uint8_t)t_DisplaySet.mid(2,2).toUInt(&ok,16);
1098 if (ok)
1099 contrast=byte8u;
1100 byte8u=(uint8_t)t_DisplaySet.mid(4,2).toUInt(&ok,16);
1101 if (ok)
1102 backlightBright=byte8u;
1103 byte8=(int8_t)t_BeeperSet.mid(0,2).toUInt(&ok,16);
1104 if (ok)
1105 beeperMode=(BeeperMode)byte8;
1106 byte8=(int8_t)t_BeeperSet.mid(2,2).toInt(&ok,16);
1107 if (ok)
1108 beeperLength=byte8;
1109 byte8=(int8_t)t_HapticSet.mid(0,2).toUInt(&ok,16);
1110 if (ok)
1111 hapticMode=(BeeperMode)byte8;
1112 byte8=(int8_t)t_HapticSet.mid(2,2).toInt(&ok,16);
1113 if (ok)
1114 hapticStrength=byte8;
1115 byte8=(int8_t)t_HapticSet.mid(4,2).toInt(&ok,16);
1116 if (ok)
1117 hapticLength=byte8;
1118 byte8u=(uint8_t)t_SpeakerSet.mid(0,2).toUInt(&ok,16);
1119 if (ok)
1120 speakerMode=byte8u;
1121 byte8u=(uint8_t)t_SpeakerSet.mid(2,2).toUInt(&ok,16);
1122 if (ok)
1123 speakerPitch=byte8u;
1124 byte8u=(uint8_t)t_SpeakerSet.mid(4,2).toUInt(&ok,16);
1125 if (ok)
1126 speakerVolume=byte8u;
1127 if (t_CountrySet.length()==6) {
1128 byte8u=(uint8_t)t_CountrySet.mid(0,2).toUInt(&ok,16);
1129 if (ok)
1130 countryCode=byte8u;
1131 byte8u=(uint8_t)t_CountrySet.mid(2,2).toUInt(&ok,16);
1132 if (ok)
1133 imperial=byte8u;
1134 QString chars = t_CountrySet.mid(4, 2);
1135 ttsLanguage[0] = chars[0].toLatin1();
1136 ttsLanguage[1] = chars[1].toLatin1();
1142 int GeneralSettings::getDefaultStick(unsigned int channel) const
1144 if (channel >= NUM_STICKS)
1145 return -1;
1146 else
1147 return chout_ar[4*templateSetup + channel] - 1;
1150 RawSource GeneralSettings::getDefaultSource(unsigned int channel) const
1152 int stick = getDefaultStick(channel);
1153 if (stick >= 0)
1154 return RawSource(SOURCE_TYPE_STICK, stick);
1155 else
1156 return RawSource(SOURCE_TYPE_NONE);
1159 int GeneralSettings::getDefaultChannel(unsigned int stick) const
1161 for (int i=0; i<4; i++){
1162 if (getDefaultStick(i) == (int)stick)
1163 return i;
1165 return -1;
1168 float FrSkyChannelData::getRatio() const
1170 if (type==0 || type==1 || type==2)
1171 return float(ratio << multiplier) / 10.0;
1172 else
1173 return ratio << multiplier;
1176 RawSourceRange FrSkyChannelData::getRange() const
1178 RawSourceRange result;
1179 float ratio = getRatio();
1180 if (type==0 || type==1 || type==2)
1181 result.decimals = 2;
1182 else
1183 result.decimals = 0;
1184 result.step = ratio / 255;
1185 result.min = offset * result.step;
1186 result.max = ratio + result.min;
1187 result.unit = QObject::tr("V");
1188 return result;
1191 void FrSkyScreenData::clear()
1193 memset(this, 0, sizeof(FrSkyScreenData));
1194 if (!IS_ARM(GetCurrentFirmware()->getBoard())) {
1195 type = TELEMETRY_SCREEN_NUMBERS;
1199 void FrSkyData::clear()
1201 usrProto = 0;
1202 voltsSource = 0;
1203 altitudeSource = 0;
1204 currentSource = 0;
1205 varioMin = 0;
1206 varioCenterMin = 0; // if increment in 0.2m/s = 3.0m/s max
1207 varioCenterMax = 0;
1208 varioMax = 0;
1209 mAhPersistent = 0;
1210 storedMah = 0;
1211 fasOffset = 0;
1212 rssiAlarms[0].clear(2, 45);
1213 rssiAlarms[1].clear(3, 42);
1214 for (int i=0; i<4; i++)
1215 screens[i].clear();
1216 varioSource = 2/*VARIO*/;
1217 blades = 2;
1220 ModelData::ModelData()
1222 clear();
1225 ModelData::ModelData(const ModelData & src)
1227 *this = src;
1230 ModelData & ModelData::operator = (const ModelData & src)
1232 memcpy(this, &src, sizeof(ModelData));
1233 return *this;
1236 ExpoData * ModelData::insertInput(const int idx)
1238 memmove(&expoData[idx+1], &expoData[idx], (C9X_MAX_EXPOS-(idx+1))*sizeof(ExpoData));
1239 expoData[idx].clear();
1240 return &expoData[idx];
1243 bool ModelData::isInputValid(const unsigned int idx) const
1245 for (int i=0; i<C9X_MAX_EXPOS; i++) {
1246 const ExpoData * expo = &expoData[i];
1247 if (expo->mode == 0) break;
1248 if (expo->chn == idx)
1249 return true;
1251 return false;
1254 bool ModelData::hasExpos(uint8_t inputIdx) const
1256 for (int i=0; i<C9X_MAX_EXPOS; i++) {
1257 const ExpoData & expo = expoData[i];
1258 if (expo.chn==inputIdx && expo.mode!=0) {
1259 return true;
1262 return false;
1265 bool ModelData::hasMixes(uint8_t channelIdx) const
1267 channelIdx += 1;
1268 for (int i=0; i<C9X_MAX_MIXERS; i++) {
1269 if (mixData[i].destCh == channelIdx) {
1270 return true;
1273 return false;
1276 QVector<const ExpoData *> ModelData::expos(int input) const
1278 QVector<const ExpoData *> result;
1279 for (int i=0; i<C9X_MAX_EXPOS; i++) {
1280 const ExpoData * ed = &expoData[i];
1281 if ((int)ed->chn==input && ed->mode!=0) {
1282 result << ed;
1285 return result;
1288 QVector<const MixData *> ModelData::mixes(int channel) const
1290 QVector<const MixData *> result;
1291 for (int i=0; i<C9X_MAX_MIXERS; i++) {
1292 const MixData * md = &mixData[i];
1293 if ((int)md->destCh == channel+1) {
1294 result << md;
1297 return result;
1300 void ModelData::removeInput(const int idx)
1302 unsigned int chn = expoData[idx].chn;
1304 memmove(&expoData[idx], &expoData[idx+1], (C9X_MAX_EXPOS-(idx+1))*sizeof(ExpoData));
1305 expoData[C9X_MAX_EXPOS-1].clear();
1307 //also remove input name if removing last line for this input
1308 bool found = false;
1309 for (int i=0; i<C9X_MAX_EXPOS; i++) {
1310 if (expoData[i].mode==0) continue;
1311 if (expoData[i].chn==chn) {
1312 found = true;
1313 break;
1316 if (!found) inputNames[chn][0] = 0;
1319 void ModelData::clearInputs()
1321 for (int i=0; i<C9X_MAX_EXPOS; i++)
1322 expoData[i].clear();
1324 //clear all input names
1325 if (GetCurrentFirmware()->getCapability(VirtualInputs)) {
1326 for (int i=0; i<C9X_MAX_INPUTS; i++) {
1327 inputNames[i][0] = 0;
1332 void ModelData::clearMixes()
1334 for (int i=0; i<C9X_MAX_MIXERS; i++)
1335 mixData[i].clear();
1338 void ModelData::clear()
1340 memset(this, 0, sizeof(ModelData));
1341 moduleData[0].channelsCount = 8;
1342 moduleData[1].channelsStart = 0;
1343 moduleData[1].channelsCount = 8;
1344 moduleData[0].ppm.delay = 300;
1345 moduleData[1].ppm.delay = 300;
1346 moduleData[2].ppm.delay = 300;
1347 int board = GetEepromInterface()->getBoard();
1348 if (IS_TARANIS(board)) {
1349 moduleData[0].protocol = PULSES_PXX_XJT_X16;
1350 moduleData[1].protocol = PULSES_OFF;
1352 else if (IS_SKY9X(board)) {
1353 moduleData[0].protocol = PULSES_PPM;
1354 moduleData[1].protocol = PULSES_PPM;
1356 else {
1357 moduleData[0].protocol = PULSES_PPM;
1358 moduleData[1].protocol = PULSES_OFF;
1360 for (int i=0; i<C9X_MAX_FLIGHT_MODES; i++) {
1361 flightModeData[i].clear(i);
1363 clearInputs();
1364 clearMixes();
1365 for (int i=0; i<C9X_NUM_CHNOUT; i++)
1366 limitData[i].clear();
1367 for (int i=0; i<NUM_STICKS; i++)
1368 expoData[i].clear();
1369 for (int i=0; i<C9X_NUM_CSW; i++)
1370 logicalSw[i].clear();
1371 for (int i=0; i<C9X_MAX_CUSTOM_FUNCTIONS; i++)
1372 customFn[i].clear();
1373 for (int i=0; i<C9X_MAX_CURVES; i++)
1374 curves[i].clear(5);
1375 for (int i=0; i<C9X_MAX_TIMERS; i++)
1376 timers[i].clear();
1377 swashRingData.clear();
1378 frsky.clear();
1379 for (int i=0; i<C9X_MAX_SENSORS; i++)
1380 sensorData[i].clear();
1383 bool ModelData::isEmpty() const
1385 return !used;
1388 QString removeAccents(const QString & str)
1390 QString result = str;
1392 // UTF-8 ASCII Table
1393 const QString tA[] = { "á", "â", "ã", "à", "ä" };
1394 const QString tE[] = { "é", "è", "ê", "ě" };
1395 const QString tI[] = { "í" };
1396 const QString tO[] = { "ó", "ô", "õ", "ö" };
1397 const QString tU[] = { "ú", "ü" };
1398 const QString tC[] = { "ç" };
1399 const QString tY[] = { "ý" };
1400 const QString tS[] = { "š" };
1401 const QString tR[] = { "ř" };
1403 for (unsigned int i = 0; i < DIM(tA); i++) result.replace(tA[i], "a");
1404 for (unsigned int i = 0; i < DIM(tE); i++) result.replace(tE[i], "e");
1405 for (unsigned int i = 0; i < DIM(tI); i++) result.replace(tI[i], "i");
1406 for (unsigned int i = 0; i < DIM(tO); i++) result.replace(tO[i], "o");
1407 for (unsigned int i = 0; i < DIM(tU); i++) result.replace(tU[i], "u");
1408 for (unsigned int i = 0; i < DIM(tC); i++) result.replace(tC[i], "c");
1409 for (unsigned int i = 0; i < DIM(tY); i++) result.replace(tY[i], "y");
1410 for (unsigned int i = 0; i < DIM(tS); i++) result.replace(tS[i], "s");
1411 for (unsigned int i = 0; i < DIM(tR); i++) result.replace(tR[i], "r");
1413 return result;
1416 void ModelData::setDefaultInputs(const GeneralSettings & settings)
1418 if (IS_TARANIS(GetEepromInterface()->getBoard())) {
1419 for (int i=0; i<NUM_STICKS; i++) {
1420 ExpoData * expo = &expoData[i];
1421 expo->chn = i;
1422 expo->mode = INPUT_MODE_BOTH;
1423 expo->srcRaw = settings.getDefaultSource(i);
1424 expo->weight = 100;
1425 strncpy(inputNames[i], removeAccents(expo->srcRaw.toString(this)).toLatin1().constData(), sizeof(inputNames[i])-1);
1430 void ModelData::setDefaultMixes(const GeneralSettings & settings)
1432 if (IS_TARANIS(GetEepromInterface()->getBoard())) {
1433 setDefaultInputs(settings);
1436 for (int i=0; i<NUM_STICKS; i++) {
1437 MixData * mix = &mixData[i];
1438 mix->destCh = i+1;
1439 mix->weight = 100;
1440 if (IS_TARANIS(GetEepromInterface()->getBoard())) {
1441 mix->srcRaw = RawSource(SOURCE_TYPE_VIRTUAL_INPUT, i);
1443 else {
1444 mix->srcRaw = RawSource(SOURCE_TYPE_STICK, i);
1449 void ModelData::setDefaultValues(unsigned int id, const GeneralSettings & settings)
1451 clear();
1452 used = true;
1453 sprintf(name, "MODEL%02d", id+1);
1454 for (int i=0; i<C9X_NUM_MODULES; i++) {
1455 moduleData[i].modelId = id+1;
1457 setDefaultMixes(settings);
1460 int ModelData::getTrimValue(int phaseIdx, int trimIdx)
1462 int result = 0;
1463 for (int i=0; i<C9X_MAX_FLIGHT_MODES; i++) {
1464 FlightModeData & phase = flightModeData[phaseIdx];
1465 if (phase.trimMode[trimIdx] < 0) {
1466 return result;
1468 else {
1469 if (phase.trimRef[trimIdx] == phaseIdx || phaseIdx == 0) {
1470 return result + phase.trim[trimIdx];
1472 else {
1473 phaseIdx = phase.trimRef[trimIdx];
1474 if (phase.trimMode[trimIdx] != 0)
1475 result += phase.trim[trimIdx];
1479 return 0;
1482 bool ModelData::isGVarLinked(int phaseIdx, int gvarIdx)
1484 return flightModeData[phaseIdx].gvars[gvarIdx] > 1024;
1487 int ModelData::getGVarFieldValue(int phaseIdx, int gvarIdx)
1489 int idx = flightModeData[phaseIdx].gvars[gvarIdx];
1490 for (int i=0; idx>1024 && i<C9X_MAX_FLIGHT_MODES; i++) {
1491 int nextPhase = idx - 1025;
1492 if (nextPhase >= phaseIdx) nextPhase += 1;
1493 phaseIdx = nextPhase;
1494 idx = flightModeData[phaseIdx].gvars[gvarIdx];
1496 return idx;
1499 void ModelData::setTrimValue(int phaseIdx, int trimIdx, int value)
1501 for (uint8_t i=0; i<C9X_MAX_FLIGHT_MODES; i++) {
1502 FlightModeData & phase = flightModeData[phaseIdx];
1503 int mode = phase.trimMode[trimIdx];
1504 int p = phase.trimRef[trimIdx];
1505 int & trim = phase.trim[trimIdx];
1506 if (mode < 0)
1507 return;
1508 if (p == phaseIdx || phaseIdx == 0) {
1509 trim = value;
1510 break;;
1512 else if (mode == 0) {
1513 phaseIdx = p;
1515 else {
1516 trim = value - getTrimValue(p, trimIdx);
1517 if (trim < -500)
1518 trim = -500;
1519 if (trim > 500)
1520 trim = 500;
1521 break;
1526 void ModelData::removeGlobalVar(int & var)
1528 if (var >= 126 && var <= 130)
1529 var = flightModeData[0].gvars[var-126];
1530 else if (var <= -126 && var >= -130)
1531 var = - flightModeData[0].gvars[-126-var];
1534 ModelData ModelData::removeGlobalVars()
1536 ModelData result = *this;
1538 for (int i=0; i<C9X_MAX_MIXERS; i++) {
1539 removeGlobalVar(mixData[i].weight);
1540 removeGlobalVar(mixData[i].curve.value);
1541 removeGlobalVar(mixData[i].sOffset);
1544 for (int i=0; i<C9X_MAX_EXPOS; i++) {
1545 removeGlobalVar(expoData[i].weight);
1546 removeGlobalVar(expoData[i].curve.value);
1549 return result;
1552 int ModelData::getChannelsMax(bool forceExtendedLimits) const
1554 if (forceExtendedLimits || extendedLimits)
1555 return IS_TARANIS(GetCurrentFirmware()->getBoard()) ? 150 : 125;
1556 else
1557 return 100;
1560 QList<EEPROMInterface *> eepromInterfaces;
1561 void registerEEpromInterfaces()
1563 eepromInterfaces.push_back(new OpenTxEepromInterface(BOARD_STOCK));
1564 eepromInterfaces.push_back(new OpenTxEepromInterface(BOARD_M128));
1565 eepromInterfaces.push_back(new OpenTxEepromInterface(BOARD_GRUVIN9X));
1566 eepromInterfaces.push_back(new OpenTxEepromInterface(BOARD_SKY9X));
1567 eepromInterfaces.push_back(new OpenTxEepromInterface(BOARD_9XRPRO));
1568 eepromInterfaces.push_back(new OpenTxEepromInterface(BOARD_TARANIS_X9D));
1569 eepromInterfaces.push_back(new OpenTxEepromInterface(BOARD_TARANIS_X9DP));
1570 eepromInterfaces.push_back(new OpenTxEepromInterface(BOARD_TARANIS_X9E));
1571 eepromInterfaces.push_back(new Gruvin9xInterface(BOARD_STOCK));
1572 eepromInterfaces.push_back(new Gruvin9xInterface(BOARD_GRUVIN9X));
1573 eepromInterfaces.push_back(new Ersky9xInterface());
1574 eepromInterfaces.push_back(new Th9xInterface());
1575 eepromInterfaces.push_back(new Er9xInterface());
1578 void unregisterEEpromInterfaces()
1580 foreach(EEPROMInterface * intf, eepromInterfaces) {
1581 // qDebug() << "UnregisterEepromInterfaces(): deleting " << QString::number( reinterpret_cast<uint64_t>(intf), 16 );
1582 delete intf;
1584 OpenTxEepromCleanup();
1587 QList<Firmware *> firmwares;
1588 Firmware * default_firmware_variant;
1589 Firmware * current_firmware_variant;
1591 void ShowEepromErrors(QWidget *parent, const QString &title, const QString &mainMessage, unsigned long errorsFound)
1593 std::bitset<NUM_ERRORS> errors((unsigned long long)errorsFound);
1594 QStringList errorsList;
1596 errorsList << QT_TRANSLATE_NOOP("EepromInterface", "Possible causes for this:");
1598 if (errors.test(UNSUPPORTED_NEWER_VERSION)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is from a newer version of OpenTX"); }
1599 if (errors.test(NOT_OPENTX)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from OpenTX"); }
1600 if (errors.test(NOT_TH9X)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from Th9X"); }
1601 if (errors.test(NOT_GRUVIN9X)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from Gruvin9X"); }
1602 if (errors.test(NOT_ERSKY9X)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from ErSky9X"); }
1603 if (errors.test(NOT_ER9X)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from Er9X"); }
1604 if (errors.test(WRONG_SIZE)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom size is invalid"); }
1605 if (errors.test(WRONG_FILE_SYSTEM)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom file system is invalid"); }
1606 if (errors.test(UNKNOWN_BOARD)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is from a unknown board"); }
1607 if (errors.test(WRONG_BOARD)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is from the wrong board"); }
1608 if (errors.test(BACKUP_NOT_SUPPORTED)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom backup not supported"); }
1610 if (errors.test(UNKNOWN_ERROR)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Something that couldn't be guessed, sorry"); }
1612 if (errors.test(HAS_WARNINGS)) {
1613 errorsList << QT_TRANSLATE_NOOP("EepromInterface", "Warning:");
1614 if (errors.test(WARNING_WRONG_FIRMWARE)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Your radio probably uses a wrong firmware,\n eeprom size is 4096 but only the first 2048 are used"); }
1617 QMessageBox msgBox(parent);
1618 msgBox.setWindowTitle(title);
1619 msgBox.setIcon(QMessageBox::Critical);
1620 msgBox.setText(mainMessage);
1621 msgBox.setInformativeText(errorsList.join("\n"));
1622 msgBox.setStandardButtons(QMessageBox::Ok);
1623 msgBox.exec();
1626 void ShowEepromWarnings(QWidget *parent, const QString &title, unsigned long errorsFound)
1628 std::bitset<NUM_ERRORS> errors((unsigned long long)errorsFound);
1629 QStringList warningsList;
1630 if (errors.test(WARNING_WRONG_FIRMWARE)) { warningsList << QT_TRANSLATE_NOOP("EepromInterface", "- Your radio probably uses a wrong firmware,\n eeprom size is 4096 but only the first 2048 are used"); }
1631 if (errors.test(OLD_VERSION)) { warningsList << QT_TRANSLATE_NOOP("EepromInterface", "- Your eeprom is from an old version of OpenTX, upgrading!\n You should 'save as' to keep the old file as a backup."); }
1633 QMessageBox msgBox(parent);
1634 msgBox.setWindowTitle(title);
1635 msgBox.setIcon(QMessageBox::Warning);
1636 msgBox.setText(QT_TRANSLATE_NOOP("EepromInterface", "Warnings!"));
1637 msgBox.setInformativeText(warningsList.join("\n"));
1638 msgBox.setStandardButtons(QMessageBox::Ok);
1639 msgBox.exec();
1642 unsigned long LoadEeprom(RadioData &radioData, const uint8_t *eeprom, const int size)
1644 std::bitset<NUM_ERRORS> errors;
1646 foreach(EEPROMInterface *eepromInterface, eepromInterfaces) {
1647 std::bitset<NUM_ERRORS> result((unsigned long long)eepromInterface->load(radioData, eeprom, size));
1648 if (result.test(ALL_OK)) {
1649 return result.to_ulong();
1651 else {
1652 errors |= result;
1656 if (errors.none()) {
1657 errors.set(UNKNOWN_ERROR);
1659 return errors.to_ulong();
1662 unsigned long LoadBackup(RadioData & radioData, uint8_t * eeprom, int size, int index)
1664 std::bitset<NUM_ERRORS> errors;
1666 foreach(EEPROMInterface *eepromInterface, eepromInterfaces) {
1667 std::bitset<NUM_ERRORS> result((unsigned long long)eepromInterface->loadBackup(radioData, eeprom, size, index));
1668 if (result.test(ALL_OK)) {
1669 return result.to_ulong();
1671 else {
1672 errors |= result;
1676 if (errors.none()) {
1677 errors.set(UNKNOWN_ERROR);
1679 return errors.to_ulong();
1683 unsigned long LoadEepromXml(RadioData & radioData, QDomDocument & doc)
1685 std::bitset<NUM_ERRORS> errors;
1687 foreach(EEPROMInterface * eepromInterface, eepromInterfaces) {
1688 std::bitset<NUM_ERRORS> result((unsigned long long)eepromInterface->loadxml(radioData, doc));
1689 if (result.test(ALL_OK)) {
1690 return result.to_ulong();
1692 else {
1693 errors |= result;
1697 if (errors.none()) {
1698 errors.set(UNKNOWN_ERROR);
1700 return errors.to_ulong();
1703 const int Firmware::getFlashSize()
1705 switch (board) {
1706 case BOARD_STOCK:
1707 return FSIZE_STOCK;
1708 case BOARD_M128:
1709 return FSIZE_M128;
1710 case BOARD_MEGA2560:
1711 case BOARD_GRUVIN9X:
1712 return FSIZE_GRUVIN9X;
1713 case BOARD_SKY9X:
1714 return FSIZE_SKY9X;
1715 case BOARD_9XRPRO:
1716 case BOARD_AR9X:
1717 return FSIZE_9XRPRO;
1718 case BOARD_TARANIS_X9D:
1719 case BOARD_TARANIS_X9DP:
1720 case BOARD_TARANIS_X9E:
1721 case BOARD_FLAMENCO:
1722 return FSIZE_TARANIS;
1723 case BOARD_HORUS:
1724 return FSIZE_HORUS;
1725 default:
1726 return 0;
1730 Firmware * GetFirmware(QString id)
1732 foreach(Firmware * firmware, firmwares) {
1733 Firmware * result = firmware->getFirmwareVariant(id);
1734 if (result) {
1735 return result;
1739 return default_firmware_variant;
1742 void Firmware::addOption(const char *option, QString tooltip, uint32_t variant)
1744 Option options[] = { { option, tooltip, variant }, { NULL } };
1745 addOptions(options);
1748 unsigned int Firmware::getVariantNumber()
1750 unsigned int result = 0;
1751 const Firmware * base = getFirmwareBase();
1752 QStringList options = id.mid(base->getId().length()+1).split("-", QString::SkipEmptyParts);
1753 foreach(QString option, options) {
1754 foreach(QList<Option> group, base->opts) {
1755 foreach(Option opt, group) {
1756 if (opt.name == option) {
1757 result += opt.variant;
1762 return result;
1765 void Firmware::addLanguage(const char *lang)
1767 languages.push_back(lang);
1770 void Firmware::addTTSLanguage(const char *lang)
1772 ttslanguages.push_back(lang);
1775 void Firmware::addOptions(Option options[])
1777 QList<Option> opts;
1778 for (int i=0; options[i].name; i++) {
1779 opts.push_back(options[i]);
1781 this->opts.push_back(opts);
1784 SimulatorInterface *GetCurrentFirmwareSimulator()
1786 QString firmwareId = GetCurrentFirmware()->getId();
1787 SimulatorFactory *factory = getSimulatorFactory(firmwareId);
1788 if (factory)
1789 return factory->create();
1790 else
1791 return NULL;
1794 unsigned int getNumSubtypes(MultiModuleRFProtocols type) {
1795 switch (type) {
1796 case MM_RF_PROTO_HISKY:
1797 case MM_RF_PROTO_SYMAX:
1798 case MM_RF_PROTO_KN:
1799 return 2;
1801 case MM_RF_PROTO_CG023:
1802 case MM_RF_PROTO_MT99XX:
1803 return 3;
1805 case MM_RF_PROTO_FRSKY:
1806 case MM_RF_PROTO_FLYSKY:
1807 case MM_RF_PROTO_DSM2:
1808 case MM_RF_PROTO_AFHDS2A:
1809 return 4;
1811 case MM_RF_PROTO_MJXQ:
1812 case MM_RF_PROTO_YD717:
1813 return 5;
1815 case MM_RF_PROTO_CX10:
1816 return 8;
1817 default:
1818 return 1;
1823 void FlightModeData::clear(const int phase)
1825 memset(this, 0, sizeof(FlightModeData));
1826 if (phase != 0) {
1827 for (int idx=0; idx<C9X_MAX_GVARS; idx++) {
1828 gvars[idx] = 1025;
1830 for (int idx=0; idx<C9X_MAX_ENCODERS; idx++) {
1831 rotaryEncoders[idx] = 1025;