Companion: Russian UI (#7180)
[opentx.git] / companion / src / firmwares / rawsource.cpp
blob47171c29f1bb7ec5a03ddfeeb119cfbcae9b8bf9
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "rawsource.h"
23 #include "eeprominterface.h"
24 #include "radiodata.h"
25 #include "radiodataconversionstate.h"
27 #include <float.h>
30 * RawSourceRange
33 float RawSourceRange::getValue(int value)
35 if (IS_ARM(getCurrentBoard()))
36 return float(value) * step;
37 else
38 return min + float(value) * step;
43 * RawSource
46 RawSourceRange RawSource::getRange(const ModelData * model, const GeneralSettings & settings, unsigned int flags) const
48 RawSourceRange result;
50 Firmware * firmware = Firmware::getCurrentVariant();
51 Board::Type board = firmware->getBoard();
53 switch (type) {
54 case SOURCE_TYPE_TELEMETRY:
55 if (IS_ARM(board)) {
56 div_t qr = div(index, 3);
57 const SensorData & sensor = model->sensorData[qr.quot];
58 if (sensor.prec == 2)
59 result.step = 0.01;
60 else if (sensor.prec == 1)
61 result.step = 0.1;
62 else
63 result.step = 1;
64 result.min = -30000 * result.step;
65 result.max = +30000 * result.step;
66 result.decimals = sensor.prec;
67 result.unit = sensor.unitString();
69 else {
70 result.offset = -DBL_MAX;
72 switch (index) {
73 case TELEMETRY_SOURCE_TX_BATT:
74 result.step = 0.1;
75 result.decimals = 1;
76 result.max = 25.5;
77 result.unit = tr("V");
78 break;
79 case TELEMETRY_SOURCE_TX_TIME:
80 result.step = 60;
81 result.max = 24 * 60 * result.step - 60; // 23:59:00 with 1-minute resolution
82 result.unit = tr("s");
83 break;
84 case TELEMETRY_SOURCE_TIMER1:
85 case TELEMETRY_SOURCE_TIMER2:
86 case TELEMETRY_SOURCE_TIMER3:
87 result.step = 5;
88 result.max = 255 * result.step;
89 result.unit = tr("s");
90 break;
91 case TELEMETRY_SOURCE_RSSI_TX:
92 case TELEMETRY_SOURCE_RSSI_RX:
93 result.max = 100;
94 result.offset = 128;
95 break;
96 case TELEMETRY_SOURCE_A1_MIN:
97 case TELEMETRY_SOURCE_A2_MIN:
98 case TELEMETRY_SOURCE_A3_MIN:
99 case TELEMETRY_SOURCE_A4_MIN:
100 if (model) result = model->frsky.channels[index-TELEMETRY_SOURCE_A1_MIN].getRange();
101 break;
102 case TELEMETRY_SOURCE_A1:
103 case TELEMETRY_SOURCE_A2:
104 case TELEMETRY_SOURCE_A3:
105 case TELEMETRY_SOURCE_A4:
106 if (model) result = model->frsky.channels[index-TELEMETRY_SOURCE_A1].getRange();
107 break;
108 case TELEMETRY_SOURCE_ALT:
109 case TELEMETRY_SOURCE_ALT_MIN:
110 case TELEMETRY_SOURCE_ALT_MAX:
111 case TELEMETRY_SOURCE_GPS_ALT:
112 result.step = 8;
113 result.min = -500;
114 result.max = 1540;
115 if (firmware->getCapability(Imperial) || settings.imperial) {
116 result.step = (result.step * 105) / 32;
117 result.min = (result.min * 105) / 32;
118 result.max = (result.max * 105) / 32;
119 result.unit = tr("ft");
121 else {
122 result.unit = tr("m");
124 break;
125 case TELEMETRY_SOURCE_T1:
126 case TELEMETRY_SOURCE_T1_MAX:
127 case TELEMETRY_SOURCE_T2:
128 case TELEMETRY_SOURCE_T2_MAX:
129 result.min = -30;
130 result.max = 225;
131 result.unit = tr("°C");
132 break;
133 case TELEMETRY_SOURCE_HDG:
134 result.step = 2;
135 result.max = 360;
136 result.offset = 256;
137 result.unit = tr("°");
138 break;
139 case TELEMETRY_SOURCE_RPM:
140 case TELEMETRY_SOURCE_RPM_MAX:
141 result.step = 50;
142 result.max = 12750;
143 break;
144 case TELEMETRY_SOURCE_FUEL:
145 result.max = 100;
146 result.unit = tr("%");
147 break;
148 case TELEMETRY_SOURCE_ASPEED:
149 case TELEMETRY_SOURCE_ASPEED_MAX:
150 result.decimals = 1;
151 result.step = 2.0;
152 result.max = (2*255);
153 if (firmware->getCapability(Imperial) || settings.imperial) {
154 result.step *= 1.150779;
155 result.max *= 1.150779;
156 result.unit = tr("mph");
158 else {
159 result.step *= 1.852;
160 result.max *= 1.852;
161 result.unit = tr("km/h");
163 break;
164 case TELEMETRY_SOURCE_SPEED:
165 case TELEMETRY_SOURCE_SPEED_MAX:
166 result.step = 2;
167 result.max = (2*255);
168 if (firmware->getCapability(Imperial) || settings.imperial) {
169 result.step *= 1.150779;
170 result.max *= 1.150779;
171 result.unit = tr("mph");
173 else {
174 result.step *= 1.852;
175 result.max *= 1.852;
176 result.unit = tr("km/h");
178 break;
179 case TELEMETRY_SOURCE_VERTICAL_SPEED:
180 result.step = 0.1;
181 result.min = -12.5;
182 result.max = 13.0;
183 result.decimals = 1;
184 result.unit = tr("m/s");
185 break;
186 case TELEMETRY_SOURCE_DTE:
187 result.max = 30000;
188 break;
189 case TELEMETRY_SOURCE_DIST:
190 case TELEMETRY_SOURCE_DIST_MAX:
191 result.step = 8;
192 result.max = 2040;
193 result.unit = tr("m");
194 break;
195 case TELEMETRY_SOURCE_CELL:
196 case TELEMETRY_SOURCE_CELL_MIN:
197 result.step = 0.02;
198 result.max = 5.1;
199 result.decimals = 2;
200 result.unit = tr("V");
201 break;
202 case TELEMETRY_SOURCE_CELLS_SUM:
203 case TELEMETRY_SOURCE_CELLS_MIN:
204 case TELEMETRY_SOURCE_VFAS:
205 case TELEMETRY_SOURCE_VFAS_MIN:
206 result.step = 0.1;
207 result.max = 25.5;
208 result.decimals = 1;
209 result.unit = tr("V");
210 break;
211 case TELEMETRY_SOURCE_CURRENT:
212 case TELEMETRY_SOURCE_CURRENT_MAX:
213 result.step = 0.5;
214 result.max = 127.5;
215 result.decimals = 1;
216 result.unit = tr("A");
217 break;
218 case TELEMETRY_SOURCE_CONSUMPTION:
219 result.step = 100;
220 result.max = 25500;
221 result.unit = tr("mAh");
222 break;
223 case TELEMETRY_SOURCE_POWER:
224 case TELEMETRY_SOURCE_POWER_MAX:
225 result.step = 5;
226 result.max = 1275;
227 result.unit = tr("W");
228 break;
229 case TELEMETRY_SOURCE_ACCX:
230 case TELEMETRY_SOURCE_ACCY:
231 case TELEMETRY_SOURCE_ACCZ:
232 result.step = 0.01;
233 result.decimals = 2;
234 result.max = 2.55;
235 result.min = 0;
236 result.unit = tr("g");
237 break;
238 default:
239 result.max = 125;
240 break;
243 if (result.offset == -DBL_MAX) {
244 result.offset = result.max - (127*result.step);
247 if (flags & (RANGE_DELTA_FUNCTION | RANGE_ABS_FUNCTION)) {
248 result.offset = 0;
249 result.min = result.step * -127;
250 result.max = result.step * 127;
253 break;
255 case SOURCE_TYPE_LUA_OUTPUT:
256 result.max = 30000;
257 result.min = -result.max;
258 break;
260 case SOURCE_TYPE_TRIM:
261 result.max = (model && model->extendedTrims ? firmware->getCapability(ExtendedTrimsRange) : firmware->getCapability(TrimsRange));
262 result.min = -result.max;
263 break;
265 case SOURCE_TYPE_GVAR: {
266 GVarData gv = model->gvarData[index];
267 result.step = gv.multiplierGet();
268 result.decimals = gv.prec;
269 result.max = gv.getMaxPrec();
270 result.min = gv.getMinPrec();
271 result.unit = gv.unitToString();
272 break;
275 case SOURCE_TYPE_SPECIAL:
276 if (index == 0) { //Batt
277 result.step = 0.1;
278 result.decimals = 1;
279 result.max = 25.5;
280 result.unit = tr("V");
282 else if (index == 1) { //Time
283 result.step = 60;
284 result.max = 24 * 60 * result.step - 60; // 23:59:00 with 1-minute resolution
285 result.unit = tr("s");
287 else { // Timers 1 - 3
288 result.step = 1;
289 result.max = 9 * 60 * 60 - 1; // 8:59:59 (to match firmware)
290 result.min = -result.max;
291 result.unit = tr("s");
293 break;
295 case SOURCE_TYPE_CH:
296 result.max = model->getChannelsMax(false);
297 result.min = -result.max;
298 break;
300 default:
301 result.max = 100;
302 result.min = -result.max;
303 break;
306 if (flags & RANGE_ABS_FUNCTION) {
307 result.min = 0;
310 return result;
313 QString RawSource::toString(const ModelData * model, const GeneralSettings * const generalSettings, Board::Type board) const
315 if (board == Board::BOARD_UNKNOWN) {
316 board = getCurrentBoard();
319 static const QString trims[] = {
320 tr("TrmR"), tr("TrmE"), tr("TrmT"), tr("TrmA"), tr("Trm5"), tr("Trm6")
323 static const QString trims2[] = {
324 tr("TrmH"), tr("TrmV")
327 static const QString special[] = {
328 tr("Batt"), tr("Time"), tr("Timer1"), tr("Timer2"), tr("Timer3"),
331 static const QString telemetry[] = {
332 tr("Batt"), tr("Time"), tr("Timer1"), tr("Timer2"), tr("Timer3"),
333 tr("RAS"), tr("RSSI Tx"), tr("RSSI Rx"),
334 tr("A1"), tr("A2"), tr("A3"), tr("A4"),
335 tr("Alt"), tr("Rpm"), tr("Fuel"), tr("T1"), tr("T2"),
336 tr("Speed"), tr("Dist"), tr("GPS Alt"),
337 tr("Cell"), tr("Cells"), tr("Vfas"), tr("Curr"), tr("Cnsp"), tr("Powr"),
338 tr("AccX"), tr("AccY"), tr("AccZ"),
339 tr("Hdg "), tr("VSpd"), tr("AirSpeed"), tr("dTE"),
340 tr("A1-"), tr("A2-"), tr("A3-"), tr("A4-"),
341 tr("Alt-"), tr("Alt+"), tr("Rpm+"), tr("T1+"), tr("T2+"), tr("Speed+"), tr("Dist+"), tr("AirSpeed+"),
342 tr("Cell-"), tr("Cells-"), tr("Vfas-"), tr("Curr+"), tr("Powr+"),
343 tr("ACC"), tr("GPS Time"),
346 static const QString rotary[] = { tr("REa"), tr("REb") };
348 if (index<0) {
349 return tr("???");
352 QString result;
353 int genAryIdx = 0;
354 switch (type) {
355 case SOURCE_TYPE_NONE:
356 return tr("----");
358 case SOURCE_TYPE_VIRTUAL_INPUT:
360 const char * name = nullptr;
361 if (model)
362 name = model->inputNames[index];
363 return RadioData::getElementName(tr("I", "as in Input"), index + 1, name);
366 case SOURCE_TYPE_LUA_OUTPUT:
367 return tr("LUA%1%2").arg(index/16+1).arg(QChar('a'+index%16));
369 case SOURCE_TYPE_STICK:
370 if (generalSettings) {
371 if (isPot(&genAryIdx))
372 result = QString(generalSettings->potName[genAryIdx]);
373 else if (isSlider(&genAryIdx))
374 result = QString(generalSettings->sliderName[genAryIdx]);
375 else if (isStick(&genAryIdx))
376 result = QString(generalSettings->stickName[genAryIdx]);
378 if (result.isEmpty())
379 result = Boards::getAnalogInputName(board, index);
380 return result;
382 case SOURCE_TYPE_TRIM:
383 return (Boards::getCapability(board, Board::NumTrims) == 2 ? CHECK_IN_ARRAY(trims2, index) : CHECK_IN_ARRAY(trims, index));
385 case SOURCE_TYPE_ROTARY_ENCODER:
386 return CHECK_IN_ARRAY(rotary, index);
388 case SOURCE_TYPE_MAX:
389 return tr("MAX");
391 case SOURCE_TYPE_SWITCH:
392 if (generalSettings)
393 result = QString(generalSettings->switchName[index]);
394 if (result.isEmpty())
395 result = Boards::getSwitchInfo(board, index).name;
396 return result;
398 case SOURCE_TYPE_CUSTOM_SWITCH:
399 return RawSwitch(SWITCH_TYPE_VIRTUAL, index+1).toString();
401 case SOURCE_TYPE_CYC:
402 return tr("CYC%1").arg(index+1);
404 case SOURCE_TYPE_PPM:
405 return RadioData::getElementName(tr("TR", "as in Trainer"), index + 1);
407 case SOURCE_TYPE_CH:
408 if (model)
409 return model->limitData[index].nameToString(index);
410 else
411 return LimitData().nameToString(index);
413 case SOURCE_TYPE_SPECIAL:
414 return CHECK_IN_ARRAY(special, index);
416 case SOURCE_TYPE_TELEMETRY:
417 if (IS_ARM(board)) {
418 div_t qr = div(index, 3);
419 if (model)
420 result = model->sensorData[qr.quot].nameToString(qr.quot);
421 else
422 result = SensorData().nameToString(qr.quot);
423 if (qr.rem)
424 result += (qr.rem == 1 ? "-" : "+");
425 return result;
427 else {
428 return CHECK_IN_ARRAY(telemetry, index);
431 case SOURCE_TYPE_GVAR:
432 if (model)
433 return model->gvarData[index].nameToString(index);
434 else
435 return GVarData().nameToString(index);
437 default:
438 return tr("???");
442 bool RawSource::isStick(int * stickIndex, Board::Type board) const
444 if (board == Board::BOARD_UNKNOWN)
445 board = getCurrentBoard();
447 if (type == SOURCE_TYPE_STICK && index < Boards::getCapability(board, Board::Sticks)) {
448 if (stickIndex)
449 *stickIndex = index;
450 return true;
452 return false;
455 bool RawSource::isPot(int * potsIndex, Board::Type board) const
457 if (board == Board::BOARD_UNKNOWN)
458 board = getCurrentBoard();
460 Boards b(board);
461 if (type == SOURCE_TYPE_STICK &&
462 index >= b.getCapability(Board::Sticks) &&
463 index < b.getCapability(Board::Sticks) + b.getCapability(Board::Pots)) {
464 if (potsIndex)
465 *potsIndex = index - b.getCapability(Board::Sticks);
466 return true;
468 return false;
471 bool RawSource::isSlider(int * sliderIndex, Board::Type board) const
473 if (board == Board::BOARD_UNKNOWN)
474 board = getCurrentBoard();
476 Boards b(board);
477 if (type == SOURCE_TYPE_STICK &&
478 index >= b.getCapability(Board::Sticks) + b.getCapability(Board::Pots) &&
479 index < b.getCapability(Board::Sticks) + b.getCapability(Board::Pots) + b.getCapability(Board::Sliders)) {
480 if (sliderIndex)
481 *sliderIndex = index - b.getCapability(Board::Sticks) - b.getCapability(Board::Pots);
482 return true;
484 return false;
487 bool RawSource::isTimeBased(Board::Type board) const
489 if (board == Board::BOARD_UNKNOWN)
490 board = getCurrentBoard();
492 if (IS_ARM(board))
493 return (type == SOURCE_TYPE_SPECIAL && index > 0);
494 else
495 return (type==SOURCE_TYPE_TELEMETRY && (index==TELEMETRY_SOURCE_TX_TIME || index==TELEMETRY_SOURCE_TIMER1 || index==TELEMETRY_SOURCE_TIMER2 || index==TELEMETRY_SOURCE_TIMER3));
498 bool RawSource::isAvailable(const ModelData * const model, const GeneralSettings * const gs, Board::Type board) const
500 if (board == Board::BOARD_UNKNOWN)
501 board = getCurrentBoard();
503 Boards b(board);
505 if (type == SOURCE_TYPE_STICK && index >= b.getCapability(Board::MaxAnalogs))
506 return false;
508 if (type == SOURCE_TYPE_SWITCH && index >= b.getCapability(Board::Switches))
509 return false;
511 if (model) {
512 if (type == SOURCE_TYPE_VIRTUAL_INPUT && !model->isInputValid(index))
513 return false;
515 if (type == SOURCE_TYPE_CUSTOM_SWITCH && model->logicalSw[index].isEmpty())
516 return false;
518 if (type == SOURCE_TYPE_TELEMETRY) {
519 if (IS_ARM(board) && !model->sensorData[div(index, 3).quot].isAvailable()) {
520 return false;
522 else if (!IS_ARM(board)) {
523 Firmware * fw = getCurrentFirmware();
524 if (type == (int)TELEMETRY_SOURCE_TX_TIME && !fw->getCapability(RtcTime))
525 return false;
527 if (type == (int)TELEMETRY_SOURCE_RAS && !fw->getCapability(SportTelemetry))
528 return false;
530 if (type == (int)TELEMETRY_SOURCE_TIMER3 && fw->getCapability(Timers) < 3)
531 return false;
536 if (gs) {
537 int gsIdx = 0;
538 if (type == SOURCE_TYPE_STICK && ((isPot(&gsIdx) && !gs->isPotAvailable(gsIdx)) || (isSlider(&gsIdx) && !gs->isSliderAvailable(gsIdx))))
539 return false;
541 if (type == SOURCE_TYPE_SWITCH && IS_HORUS_OR_TARANIS(board) && !gs->switchSourceAllowedTaranis(index))
542 return false;
545 if (type == SOURCE_TYPE_TRIM && index >= b.getCapability(Board::NumTrims))
546 return false;
548 return true;
551 RawSource RawSource::convert(RadioDataConversionState & cstate)
553 cstate.setItemType(tr("SRC"), 1);
554 RadioDataConversionState::EventType evt = RadioDataConversionState::EVT_NONE;
555 RadioDataConversionState::LogField oldData(index, toString(cstate.fromModel(), cstate.fromGS(), cstate.fromType));
557 if (type == SOURCE_TYPE_STICK) {
558 if (cstate.toBoard.getCapability(Board::Sliders)) {
559 if (index >= cstate.fromBoard.getCapability(Board::Sticks) + cstate.fromBoard.getCapability(Board::Pots)) {
560 // 1st slider alignment
561 index += cstate.toBoard.getCapability(Board::Pots) - cstate.fromBoard.getCapability(Board::Pots);
564 if (isSlider(0, cstate.fromType)) {
565 // LS and RS sliders are after 2 aux sliders on X12 and X9E
566 if ((IS_HORUS_X12S(cstate.toType) || IS_TARANIS_X9E(cstate.toType)) && !IS_HORUS_X12S(cstate.fromType) && !IS_TARANIS_X9E(cstate.fromType)) {
567 if (index >= 7) {
568 index += 2; // LS/RS to LS/RS
571 else if (!IS_TARANIS_X9E(cstate.toType) && !IS_HORUS_X12S(cstate.toType) && (IS_HORUS_X12S(cstate.fromType) || IS_TARANIS_X9E(cstate.fromType))) {
572 if (index >= 7 && index <= 8) {
573 index += 2; // aux sliders to spare analogs (which may not exist, this is validated later)
574 evt = RadioDataConversionState::EVT_CVRT;
576 else if (index >= 9 && index <= 10) {
577 index -= 2; // LS/RS to LS/RS
583 if (IS_TARANIS(cstate.toType) && IS_HORUS(cstate.fromType)) {
584 if (index == 6)
585 index = 5; // pot S2 to S2
586 else if (index == 5)
587 index = -1; // 6P on Horus doesn't exist on Taranis
589 else if (IS_HORUS(cstate.toType) && IS_TARANIS(cstate.fromType) && index == 5)
591 index = 6; // pot S2 to S2
594 } // SOURCE_TYPE_STICK
596 if (type == SOURCE_TYPE_SWITCH) {
597 // SWI to SWR don't exist on !X9E board
598 if (!IS_TARANIS_X9E(cstate.toType) && IS_TARANIS_X9E(cstate.fromType)) {
599 if (index >= 8) {
600 index = index % 8;
601 evt = RadioDataConversionState::EVT_CVRT;
605 if (IS_TARANIS_X7(cstate.toType) && (IS_TARANIS_X9(cstate.fromType) || IS_HORUS(cstate.fromType))) {
606 // No SE and SG on X7 board
607 if (index == 4 || index == 6) {
608 index = 3; // SG and SE to SD
609 evt = RadioDataConversionState::EVT_CVRT;
611 else if (index == 5) {
612 index = 4; // SF to SF
614 else if (index == 7) {
615 index = 5; // SH to SH
618 else if (IS_JUMPER_T12(cstate.toType) && (IS_TARANIS_X9(cstate.fromType) || IS_HORUS(cstate.fromType))) {
619 // No SE and SG on T12 board
620 if (index == 4 || index == 6) {
621 index = 3; // SG and SE to SD
622 evt = RadioDataConversionState::EVT_CVRT;
624 else if (index == 5) {
625 index = 4; // SF to SF
627 else if (index == 7) {
628 index = 5; // SH to SH
631 // Compensate for SE and SG on X9/Horus board if converting from X7
632 else if ((IS_TARANIS_X9(cstate.toType) || IS_HORUS(cstate.toType)) && IS_TARANIS_X7(cstate.fromType)) {
633 if (index == 4) {
634 index = 5; // SF to SF
636 else if (index == 5) {
637 index = 7; // SH to SH
640 else if ((IS_TARANIS_X9(cstate.toType) || IS_HORUS(cstate.toType)) && IS_JUMPER_T12(cstate.fromType)) {
641 if (index == 4) {
642 index = 5; // SF to SF
644 else if (index == 5) {
645 index = 7; // SH to SH
648 else if ((IS_TARANIS_X9(cstate.toType) || IS_HORUS(cstate.toType)) && IS_JUMPER_T12(cstate.fromType)) {
649 if (index == 4) {
650 index = 5; // SF to SF
652 else if (index == 5) {
653 index = 7; // SH to SH
656 } // SOURCE_TYPE_SWITCH
658 // final validation (we do not pass model to isAvailable() because we don't know what has or hasn't been converted)
659 if (!isAvailable(NULL, cstate.toGS(), cstate.toType)) {
660 cstate.setInvalid(oldData);
661 index = -1; // TODO: better way to flag invalid sources?
662 type = MAX_SOURCE_TYPE;
664 else if (evt == RadioDataConversionState::EVT_CVRT) {
665 cstate.setConverted(oldData, RadioDataConversionState::LogField(index, toString(cstate.toModel(), cstate.toGS(), cstate.toType)));
667 else if (oldData.id != index) {
668 // provide info by default if anything changed
669 cstate.setMoved(oldData, RadioDataConversionState::LogField(index, toString(cstate.toModel(), cstate.toGS(), cstate.toType)));
672 return *this;