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"
33 float RawSourceRange::getValue(int value
)
35 if (IS_ARM(getCurrentBoard()))
36 return float(value
) * step
;
38 return min
+ float(value
) * step
;
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();
54 case SOURCE_TYPE_TELEMETRY
:
56 div_t qr
= div(index
, 3);
57 const SensorData
& sensor
= model
->sensorData
[qr
.quot
];
60 else if (sensor
.prec
== 1)
64 result
.min
= -30000 * result
.step
;
65 result
.max
= +30000 * result
.step
;
66 result
.decimals
= sensor
.prec
;
67 result
.unit
= sensor
.unitString();
70 result
.offset
= -DBL_MAX
;
73 case TELEMETRY_SOURCE_TX_BATT
:
77 result
.unit
= tr("V");
79 case TELEMETRY_SOURCE_TX_TIME
:
81 result
.max
= 24 * 60 * result
.step
- 60; // 23:59:00 with 1-minute resolution
82 result
.unit
= tr("s");
84 case TELEMETRY_SOURCE_TIMER1
:
85 case TELEMETRY_SOURCE_TIMER2
:
86 case TELEMETRY_SOURCE_TIMER3
:
88 result
.max
= 255 * result
.step
;
89 result
.unit
= tr("s");
91 case TELEMETRY_SOURCE_RSSI_TX
:
92 case TELEMETRY_SOURCE_RSSI_RX
:
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();
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();
108 case TELEMETRY_SOURCE_ALT
:
109 case TELEMETRY_SOURCE_ALT_MIN
:
110 case TELEMETRY_SOURCE_ALT_MAX
:
111 case TELEMETRY_SOURCE_GPS_ALT
:
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");
122 result
.unit
= tr("m");
125 case TELEMETRY_SOURCE_T1
:
126 case TELEMETRY_SOURCE_T1_MAX
:
127 case TELEMETRY_SOURCE_T2
:
128 case TELEMETRY_SOURCE_T2_MAX
:
131 result
.unit
= tr("°C");
133 case TELEMETRY_SOURCE_HDG
:
137 result
.unit
= tr("°");
139 case TELEMETRY_SOURCE_RPM
:
140 case TELEMETRY_SOURCE_RPM_MAX
:
144 case TELEMETRY_SOURCE_FUEL
:
146 result
.unit
= tr("%");
148 case TELEMETRY_SOURCE_ASPEED
:
149 case TELEMETRY_SOURCE_ASPEED_MAX
:
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");
159 result
.step
*= 1.852;
161 result
.unit
= tr("km/h");
164 case TELEMETRY_SOURCE_SPEED
:
165 case TELEMETRY_SOURCE_SPEED_MAX
:
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");
174 result
.step
*= 1.852;
176 result
.unit
= tr("km/h");
179 case TELEMETRY_SOURCE_VERTICAL_SPEED
:
184 result
.unit
= tr("m/s");
186 case TELEMETRY_SOURCE_DTE
:
189 case TELEMETRY_SOURCE_DIST
:
190 case TELEMETRY_SOURCE_DIST_MAX
:
193 result
.unit
= tr("m");
195 case TELEMETRY_SOURCE_CELL
:
196 case TELEMETRY_SOURCE_CELL_MIN
:
200 result
.unit
= tr("V");
202 case TELEMETRY_SOURCE_CELLS_SUM
:
203 case TELEMETRY_SOURCE_CELLS_MIN
:
204 case TELEMETRY_SOURCE_VFAS
:
205 case TELEMETRY_SOURCE_VFAS_MIN
:
209 result
.unit
= tr("V");
211 case TELEMETRY_SOURCE_CURRENT
:
212 case TELEMETRY_SOURCE_CURRENT_MAX
:
216 result
.unit
= tr("A");
218 case TELEMETRY_SOURCE_CONSUMPTION
:
221 result
.unit
= tr("mAh");
223 case TELEMETRY_SOURCE_POWER
:
224 case TELEMETRY_SOURCE_POWER_MAX
:
227 result
.unit
= tr("W");
229 case TELEMETRY_SOURCE_ACCX
:
230 case TELEMETRY_SOURCE_ACCY
:
231 case TELEMETRY_SOURCE_ACCZ
:
236 result
.unit
= tr("g");
243 if (result
.offset
== -DBL_MAX
) {
244 result
.offset
= result
.max
- (127*result
.step
);
247 if (flags
& (RANGE_DELTA_FUNCTION
| RANGE_ABS_FUNCTION
)) {
249 result
.min
= result
.step
* -127;
250 result
.max
= result
.step
* 127;
255 case SOURCE_TYPE_LUA_OUTPUT
:
257 result
.min
= -result
.max
;
260 case SOURCE_TYPE_TRIM
:
261 result
.max
= (model
&& model
->extendedTrims
? firmware
->getCapability(ExtendedTrimsRange
) : firmware
->getCapability(TrimsRange
));
262 result
.min
= -result
.max
;
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();
275 case SOURCE_TYPE_SPECIAL
:
276 if (index
== 0) { //Batt
280 result
.unit
= tr("V");
282 else if (index
== 1) { //Time
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
289 result
.max
= 9 * 60 * 60 - 1; // 8:59:59 (to match firmware)
290 result
.min
= -result
.max
;
291 result
.unit
= tr("s");
296 result
.max
= model
->getChannelsMax(false);
297 result
.min
= -result
.max
;
302 result
.min
= -result
.max
;
306 if (flags
& RANGE_ABS_FUNCTION
) {
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") };
355 case SOURCE_TYPE_NONE
:
358 case SOURCE_TYPE_VIRTUAL_INPUT
:
360 const char * name
= nullptr;
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
);
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
:
391 case SOURCE_TYPE_SWITCH
:
393 result
= QString(generalSettings
->switchName
[index
]);
394 if (result
.isEmpty())
395 result
= Boards::getSwitchInfo(board
, index
).name
;
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);
409 return model
->limitData
[index
].nameToString(index
);
411 return LimitData().nameToString(index
);
413 case SOURCE_TYPE_SPECIAL
:
414 return CHECK_IN_ARRAY(special
, index
);
416 case SOURCE_TYPE_TELEMETRY
:
418 div_t qr
= div(index
, 3);
420 result
= model
->sensorData
[qr
.quot
].nameToString(qr
.quot
);
422 result
= SensorData().nameToString(qr
.quot
);
424 result
+= (qr
.rem
== 1 ? "-" : "+");
428 return CHECK_IN_ARRAY(telemetry
, index
);
431 case SOURCE_TYPE_GVAR
:
433 return model
->gvarData
[index
].nameToString(index
);
435 return GVarData().nameToString(index
);
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
)) {
455 bool RawSource::isPot(int * potsIndex
, Board::Type board
) const
457 if (board
== Board::BOARD_UNKNOWN
)
458 board
= getCurrentBoard();
461 if (type
== SOURCE_TYPE_STICK
&&
462 index
>= b
.getCapability(Board::Sticks
) &&
463 index
< b
.getCapability(Board::Sticks
) + b
.getCapability(Board::Pots
)) {
465 *potsIndex
= index
- b
.getCapability(Board::Sticks
);
471 bool RawSource::isSlider(int * sliderIndex
, Board::Type board
) const
473 if (board
== Board::BOARD_UNKNOWN
)
474 board
= getCurrentBoard();
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
)) {
481 *sliderIndex
= index
- b
.getCapability(Board::Sticks
) - b
.getCapability(Board::Pots
);
487 bool RawSource::isTimeBased(Board::Type board
) const
489 if (board
== Board::BOARD_UNKNOWN
)
490 board
= getCurrentBoard();
493 return (type
== SOURCE_TYPE_SPECIAL
&& index
> 0);
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();
505 if (type
== SOURCE_TYPE_STICK
&& index
>= b
.getCapability(Board::MaxAnalogs
))
508 if (type
== SOURCE_TYPE_SWITCH
&& index
>= b
.getCapability(Board::Switches
))
512 if (type
== SOURCE_TYPE_VIRTUAL_INPUT
&& !model
->isInputValid(index
))
515 if (type
== SOURCE_TYPE_CUSTOM_SWITCH
&& model
->logicalSw
[index
].isEmpty())
518 if (type
== SOURCE_TYPE_TELEMETRY
) {
519 if (IS_ARM(board
) && !model
->sensorData
[div(index
, 3).quot
].isAvailable()) {
522 else if (!IS_ARM(board
)) {
523 Firmware
* fw
= getCurrentFirmware();
524 if (type
== (int)TELEMETRY_SOURCE_TX_TIME
&& !fw
->getCapability(RtcTime
))
527 if (type
== (int)TELEMETRY_SOURCE_RAS
&& !fw
->getCapability(SportTelemetry
))
530 if (type
== (int)TELEMETRY_SOURCE_TIMER3
&& fw
->getCapability(Timers
) < 3)
538 if (type
== SOURCE_TYPE_STICK
&& ((isPot(&gsIdx
) && !gs
->isPotAvailable(gsIdx
)) || (isSlider(&gsIdx
) && !gs
->isSliderAvailable(gsIdx
))))
541 if (type
== SOURCE_TYPE_SWITCH
&& IS_HORUS_OR_TARANIS(board
) && !gs
->switchSourceAllowedTaranis(index
))
545 if (type
== SOURCE_TYPE_TRIM
&& index
>= b
.getCapability(Board::NumTrims
))
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
)) {
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
)) {
585 index
= 5; // pot S2 to S2
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
)) {
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
)) {
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
)) {
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
)) {
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
)));