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.
22 #define _USE_MATH_DEFINES
25 TelemetryItem telemetryItems
[MAX_TELEMETRY_SENSORS
];
26 uint8_t allowNewSensors
;
28 bool isFaiForbidden(source_t idx
)
30 if (idx
< MIXSRC_FIRST_TELEM
) {
34 TelemetrySensor
* sensor
= &g_model
.telemetrySensors
[(idx
-MIXSRC_FIRST_TELEM
)/3];
36 switch (telemetryProtocol
) {
37 case PROTOCOL_TELEMETRY_FRSKY_SPORT
:
38 if (sensor
->id
== RSSI_ID
) {
41 else if (sensor
->id
== BATT_ID
) {
46 case PROTOCOL_TELEMETRY_FRSKY_D
:
47 if (sensor
->id
== D_RSSI_ID
) {
50 else if (sensor
->id
== D_A1_ID
) {
55 #if defined(CROSSFIRE)
56 case PROTOCOL_TELEMETRY_CROSSFIRE
:
57 if (sensor
->id
== RX_RSSI1_INDEX
) {
60 else if (sensor
->id
== RX_RSSI2_INDEX
) {
63 else if (sensor
->id
== BATT_VOLTAGE_INDEX
) {
73 uint32_t getDistFromEarthAxis(int32_t latitude
)
75 uint32_t lat
= abs(latitude
) / 10000;
76 uint32_t angle2
= (lat
* lat
) / 10000;
77 uint32_t angle4
= angle2
* angle2
;
78 return 139*(((uint32_t)10000000 - ((angle2
*(uint32_t)123370)/81) + (angle4
/25))/12500);
81 void TelemetryItem::setValue(const TelemetrySensor
& sensor
, int32_t val
, uint32_t unit
, uint32_t prec
)
85 if (unit
== UNIT_CELLS
) {
86 uint32_t data
= uint32_t(newVal
);
87 uint8_t cellsCount
= (data
>> 24);
88 uint8_t cellIndex
= ((data
>> 16) & 0x0F);
89 uint16_t cellValue
= (data
& 0xFFFF);
90 if (cellsCount
== 0) {
91 cellsCount
= (cellIndex
>= cells
.count
? cellIndex
+ 1 : cells
.count
);
92 if (cellsCount
!= cells
.count
) {
94 cells
.count
= cellsCount
;
95 // we skip this round as we are not sure we received all cells values
99 else if (cellsCount
!= cells
.count
) {
101 cells
.count
= cellsCount
;
103 cells
.values
[cellIndex
].set(cellValue
);
104 if (cellIndex
+1 == cells
.count
) {
106 for (int i
=0; i
<cellsCount
; i
++) {
107 if (cells
.values
[i
].state
) {
108 newVal
+= cells
.values
[i
].value
;
111 // we didn't receive all cells values
115 newVal
= sensor
.getValue(newVal
, UNIT_VOLTS
, 2);
118 // we didn't receive all cells values
122 else if (unit
== UNIT_DATETIME
) {
123 auto data
= uint32_t(newVal
);
124 if (data
& 0x000000ff) {
125 datetime
.year
= (uint16_t) ((data
& 0xff000000) >> 24) + 2000; // SPORT GPS year is only two digits
126 datetime
.month
= (uint8_t) ((data
& 0x00ff0000) >> 16);
127 datetime
.day
= (uint8_t) ((data
& 0x0000ff00) >> 8);
130 datetime
.hour
= (uint8_t) ((data
& 0xff000000) >> 24);
131 datetime
.min
= (uint8_t) ((data
& 0x00ff0000) >> 16);
132 datetime
.sec
= (uint8_t) ((data
& 0x0000ff00) >> 8);
134 if (g_eeGeneral
.adjustRTC
) {
135 rtcAdjust(datetime
.year
, datetime
.month
, datetime
.day
, datetime
.hour
, datetime
.min
, datetime
.sec
);
141 else if (unit
== UNIT_GPS_LATITUDE
) {
142 #if defined(INTERNAL_GPS)
143 if (gpsData
.fix
&& gpsData
.hdop
< PILOTPOS_MIN_HDOP
) {
144 pilotLatitude
= gpsData
.latitude
;
145 distFromEarthAxis
= getDistFromEarthAxis(pilotLatitude
);
148 if (!pilotLatitude
) {
149 pilotLatitude
= newVal
;
150 distFromEarthAxis
= getDistFromEarthAxis(newVal
);
152 gps
.latitude
= newVal
;
156 else if (unit
== UNIT_GPS_LONGITUDE
) {
157 #if defined(INTERNAL_GPS)
158 if (gpsData
.fix
&& gpsData
.hdop
< PILOTPOS_MIN_HDOP
) {
159 pilotLongitude
= gpsData
.longitude
;
162 if (!pilotLongitude
) {
163 pilotLongitude
= newVal
;
165 gps
.longitude
= newVal
;
169 else if (unit
== UNIT_DATETIME_YEAR
) {
170 datetime
.year
= newVal
;
173 else if (unit
== UNIT_DATETIME_DAY_MONTH
) {
174 auto data
= uint32_t(newVal
);
175 datetime
.month
= data
>> 8;
176 datetime
.day
= data
& 0xFF;
179 else if (unit
== UNIT_DATETIME_HOUR_MIN
) {
180 auto data
= uint32_t(newVal
);
181 datetime
.hour
= (data
& 0xFF);
182 datetime
.min
= data
>> 8;
185 else if (unit
== UNIT_DATETIME_SEC
) {
186 datetime
.sec
= newVal
& 0xFF;
189 else if (unit
== UNIT_RPMS
) {
190 if (sensor
.custom
.ratio
!= 0) {
191 newVal
= (newVal
* sensor
.custom
.offset
) / sensor
.custom
.ratio
;
194 else if (unit
== UNIT_TEXT
) {
195 *((uint32_t*)&text
[prec
]) = newVal
;
200 newVal
= sensor
.getValue(newVal
, unit
, prec
);
201 if (sensor
.autoOffset
) {
202 if (!isAvailable()) {
203 std
.offsetAuto
= -newVal
;
205 newVal
+= std
.offsetAuto
;
208 if (!isAvailable()) {
209 for (int i
=0; i
<TELEMETRY_AVERAGE_COUNT
; i
++) {
210 std
.filterValues
[i
] = newVal
;
214 // Calculate the average from values[] and value,
215 // also shift readings in values [] array.
216 // There is a possibility of value overflow in `sum` but
217 // in reality no sensor value should be so big to cause it.
218 int32_t sum
= std
.filterValues
[0];
219 for (int i
=0; i
<TELEMETRY_AVERAGE_COUNT
-1; i
++) {
220 int32_t tmp
= std
.filterValues
[i
+1];
221 std
.filterValues
[i
] = tmp
;
224 std
.filterValues
[TELEMETRY_AVERAGE_COUNT
-1] = newVal
;
226 newVal
= sum
/(TELEMETRY_AVERAGE_COUNT
+1);
231 if (!isAvailable()) {
235 else if (newVal
< valueMin
) {
238 else if (newVal
> valueMax
) {
240 if (sensor
.unit
== UNIT_VOLTS
) {
241 valueMin
= newVal
; // the batt was changed
245 for (int i
=0; i
<MAX_TELEMETRY_SENSORS
; i
++) {
246 TelemetrySensor
& it
= g_model
.telemetrySensors
[i
];
247 if (it
.type
== TELEM_TYPE_CALCULATED
&& it
.formula
== TELEM_FORMULA_TOTALIZE
&& &g_model
.telemetrySensors
[it
.consumption
.source
-1] == &sensor
) {
248 TelemetryItem
& item
= telemetryItems
[i
];
249 int32_t increment
= it
.getValue(val
, unit
, prec
);
250 item
.setValue(it
, item
.value
+increment
, it
.unit
, it
.prec
);
258 void TelemetryItem::per10ms(const TelemetrySensor
& sensor
)
260 switch (sensor
.formula
) {
261 case TELEM_FORMULA_CONSUMPTION
:
262 if (sensor
.consumption
.source
) {
263 TelemetrySensor
& currentSensor
= g_model
.telemetrySensors
[sensor
.consumption
.source
-1];
264 TelemetryItem
& currentItem
= telemetryItems
[sensor
.consumption
.source
-1];
265 if (!currentItem
.isAvailable()) {
268 else if (currentItem
.isOld()) {
272 int32_t current
= convertTelemetryValue(currentItem
.value
, currentSensor
.unit
, currentSensor
.prec
, UNIT_AMPS
, 1);
273 currentItem
.consumption
.prescale
+= current
;
274 if (currentItem
.consumption
.prescale
>= 3600) {
275 currentItem
.consumption
.prescale
-= 3600;
276 setValue(sensor
, value
+1, sensor
.unit
, sensor
.prec
);
287 void TelemetryItem::eval(const TelemetrySensor
& sensor
)
289 switch (sensor
.formula
) {
290 case TELEM_FORMULA_CELL
:
291 if (sensor
.cell
.source
) {
292 TelemetryItem
& cellsItem
= telemetryItems
[sensor
.cell
.source
-1];
293 if (cellsItem
.isOld()) {
297 unsigned int index
= sensor
.cell
.index
;
298 if (index
== TELEM_CELL_INDEX_LOWEST
|| index
== TELEM_CELL_INDEX_HIGHEST
|| index
== TELEM_CELL_INDEX_DELTA
) {
299 unsigned int lowest
=0, highest
=0;
300 for (int i
=0; i
<cellsItem
.cells
.count
; i
++) {
301 if (cellsItem
.cells
.values
[i
].state
) {
302 if (!lowest
|| cellsItem
.cells
.values
[i
].value
< cellsItem
.cells
.values
[lowest
-1].value
)
304 if (!highest
|| cellsItem
.cells
.values
[i
].value
> cellsItem
.cells
.values
[highest
-1].value
)
308 lowest
= highest
= 0;
313 case TELEM_CELL_INDEX_LOWEST
:
314 setValue(sensor
, cellsItem
.cells
.values
[lowest
-1].value
, UNIT_VOLTS
, 2);
316 case TELEM_CELL_INDEX_HIGHEST
:
317 setValue(sensor
, cellsItem
.cells
.values
[highest
-1].value
, UNIT_VOLTS
, 2);
319 case TELEM_CELL_INDEX_DELTA
:
320 setValue(sensor
, cellsItem
.cells
.values
[highest
-1].value
- cellsItem
.cells
.values
[lowest
-1].value
, UNIT_VOLTS
, 2);
327 if (index
< cellsItem
.cells
.count
&& cellsItem
.cells
.values
[index
].state
) {
328 setValue(sensor
, cellsItem
.cells
.values
[index
].value
, UNIT_VOLTS
, 2);
335 case TELEM_FORMULA_DIST
:
336 if (sensor
.dist
.gps
) {
337 TelemetryItem gpsItem
= telemetryItems
[sensor
.dist
.gps
-1];
338 TelemetryItem
* altItem
= nullptr;
339 if (!gpsItem
.isAvailable()) {
342 else if (gpsItem
.isOld()) {
346 if (sensor
.dist
.alt
) {
347 altItem
= &telemetryItems
[sensor
.dist
.alt
-1];
348 if (!altItem
->isAvailable()) {
351 else if (altItem
->isOld()) {
356 uint32_t angle
= abs(gpsItem
.gps
.latitude
- gpsItem
.pilotLatitude
);
358 uint32_t dist
= uint64_t(EARTH_RADIUS
* M_PI
/ 180) * angle
/ 1000000;
360 // TODO search later why it breaks Sky9x
361 uint32_t dist
= EARTH_RADIUS
* angle
/ 1000000;
363 uint32_t result
= dist
* dist
;
365 angle
= abs(gpsItem
.gps
.longitude
- gpsItem
.pilotLongitude
);
367 dist
= uint64_t(gpsItem
.distFromEarthAxis
) * angle
/ 1000000;
369 dist
= gpsItem
.distFromEarthAxis
* angle
/ 1000000;
371 result
+= dist
* dist
;
373 // Length on ground (ignoring curvature of the earth)
374 result
= isqrt32(result
);
377 dist
= abs(altItem
->value
) / g_model
.telemetrySensors
[sensor
.dist
.alt
-1].getPrecDivisor();
378 result
= (dist
* dist
) + (result
* result
);
379 result
= isqrt32(result
);
382 setValue(sensor
, result
, UNIT_METERS
);
386 case TELEM_FORMULA_ADD
:
387 case TELEM_FORMULA_AVERAGE
:
388 case TELEM_FORMULA_MIN
:
389 case TELEM_FORMULA_MAX
:
390 case TELEM_FORMULA_MULTIPLY
:
392 int32_t value
=0, count
=0, available
=0, maxitems
=4, mulprec
=0;
393 if (sensor
.formula
== TELEM_FORMULA_MULTIPLY
) {
397 for (int i
=0; i
<maxitems
; i
++) {
398 int8_t source
= sensor
.calc
.sources
[i
];
400 unsigned int index
= abs(source
)-1;
401 TelemetrySensor
& telemetrySensor
= g_model
.telemetrySensors
[index
];
402 TelemetryItem
& telemetryItem
= telemetryItems
[index
];
403 if (sensor
.formula
== TELEM_FORMULA_AVERAGE
) {
404 if (telemetryItem
.isAvailable())
408 if (telemetryItem
.isOld())
412 if (!telemetryItem
.isAvailable()) {
415 else if (telemetryItem
.isOld()) {
420 int32_t sensorValue
= telemetryItem
.value
;
422 sensorValue
= -sensorValue
;
424 if (sensor
.formula
== TELEM_FORMULA_MULTIPLY
) {
425 mulprec
+= telemetrySensor
.prec
;
426 value
*= convertTelemetryValue(sensorValue
, telemetrySensor
.unit
, 0, sensor
.unit
, 0);
429 sensorValue
= convertTelemetryValue(sensorValue
, telemetrySensor
.unit
, telemetrySensor
.prec
, sensor
.unit
, sensor
.prec
);
430 if (sensor
.formula
== TELEM_FORMULA_MIN
)
431 value
= (count
==1 ? sensorValue
: min
<int32_t>(value
, sensorValue
));
432 else if (sensor
.formula
== TELEM_FORMULA_MAX
)
433 value
= (count
==1 ? sensorValue
: max
<int32_t>(value
, sensorValue
));
435 value
+= sensorValue
;
439 if (sensor
.formula
== TELEM_FORMULA_AVERAGE
) {
446 value
= (value
+ count
/2) / count
;
449 else if (sensor
.formula
== TELEM_FORMULA_MULTIPLY
) {
452 value
= convertTelemetryValue(value
, sensor
.unit
, mulprec
, sensor
.unit
, sensor
.prec
);
454 setValue(sensor
, value
, sensor
.unit
, sensor
.prec
);
463 void delTelemetryIndex(uint8_t index
)
465 memclear(&g_model
.telemetrySensors
[index
], sizeof(TelemetrySensor
));
466 telemetryItems
[index
].clear();
467 storageDirty(EE_MODEL
);
470 int availableTelemetryIndex()
472 for (int index
=0; index
<MAX_TELEMETRY_SENSORS
; index
++) {
473 TelemetrySensor
& telemetrySensor
= g_model
.telemetrySensors
[index
];
474 if (!telemetrySensor
.isAvailable()) {
481 int lastUsedTelemetryIndex()
483 for (int index
=MAX_TELEMETRY_SENSORS
-1; index
>=0; index
--) {
484 TelemetrySensor
& telemetrySensor
= g_model
.telemetrySensors
[index
];
485 if (telemetrySensor
.isAvailable()) {
492 int setTelemetryValue(TelemetryProtocol protocol
, uint16_t id
, uint8_t subId
, uint8_t instance
, int32_t value
, uint32_t unit
, uint32_t prec
)
494 bool sensorFound
= false;
496 for (int index
=0; index
<MAX_TELEMETRY_SENSORS
; index
++) {
497 TelemetrySensor
& telemetrySensor
= g_model
.telemetrySensors
[index
];
498 if (telemetrySensor
.type
== TELEM_TYPE_CUSTOM
&& telemetrySensor
.id
== id
&& telemetrySensor
.subId
== subId
&& (telemetrySensor
.isSameInstance(protocol
, instance
) || g_model
.ignoreSensorIds
)) {
499 telemetryItems
[index
].setValue(telemetrySensor
, value
, unit
, prec
);
501 // we continue search here, because sensors can share the same id and instance
505 if (sensorFound
|| !allowNewSensors
) {
509 int index
= availableTelemetryIndex();
512 case PROTOCOL_TELEMETRY_FRSKY_SPORT
:
513 frskySportSetDefault(index
, id
, subId
, instance
);
515 case PROTOCOL_TELEMETRY_FRSKY_D
:
516 frskyDSetDefault(index
, id
);
518 #if defined(CROSSFIRE)
519 case PROTOCOL_TELEMETRY_CROSSFIRE
:
520 crossfireSetDefault(index
, id
, instance
);
523 #if defined(MULTIMODULE)
524 case PROTOCOL_TELEMETRY_SPEKTRUM
:
525 spektrumSetDefault(index
, id
, subId
, instance
);
527 case PROTOCOL_TELEMETRY_FLYSKY_IBUS
:
528 flySkySetDefault(index
,id
, subId
, instance
);
530 case PROTOCOL_TELEMETRY_HITEC
:
531 hitecSetDefault(index
, id
, subId
, instance
);
533 case PROTOCOL_TELEMETRY_HOTT
:
534 hottSetDefault(index
, id
, subId
, instance
);
538 case PROTOCOL_TELEMETRY_LUA
:
539 // Sensor will be initialized by calling function
540 // This drops the first value
546 telemetryItems
[index
].setValue(g_model
.telemetrySensors
[index
], value
, unit
, prec
);
550 POPUP_WARNING(STR_TELEMETRYFULL
);
556 void TelemetrySensor::init(const char * label
, uint8_t unit
, uint8_t prec
)
558 memclear(this->label
, TELEM_LABEL_LEN
);
559 strncpy(this->label
, label
, TELEM_LABEL_LEN
);
561 if (prec
> 1 && (IS_DISTANCE_UNIT(unit
) || IS_SPEED_UNIT(unit
))) {
562 // 2 digits precision is not needed here
566 // Log sensors by default
570 void TelemetrySensor::init(uint16_t id
)
573 label
[0] = hex2zchar((id
& 0xf000) >> 12);
574 label
[1] = hex2zchar((id
& 0x0f00) >> 8);
575 label
[2] = hex2zchar((id
& 0x00f0) >> 4);
576 label
[3] = hex2zchar((id
& 0x000f) >> 0);
580 bool TelemetrySensor::isAvailable() const
582 return ZLEN(label
) > 0;
585 PACK(typedef struct {
590 }) UnitConversionRule
;
592 const UnitConversionRule unitConversionTable
[] = {
593 /* unitFrom unitTo multiplier divisor */
594 { UNIT_METERS
, UNIT_FEET
, 105, 32},
595 { UNIT_METERS_PER_SECOND
, UNIT_FEET_PER_SECOND
, 105, 32},
597 { UNIT_KTS
, UNIT_KMH
, 1852, 1000}, // 1 knot = 1.85200 kilometers per hour
598 { UNIT_KTS
, UNIT_MPH
, 1151, 1000}, // 1 knot = 1.15077945 miles per hour
599 { UNIT_KTS
, UNIT_METERS_PER_SECOND
, 1000, 1944}, // 1 knot = 0.514444444 meters / second (divide with 1.94384449)
600 { UNIT_KTS
, UNIT_FEET_PER_SECOND
, 1688, 1000}, // 1 knot = 1.68780986 feet per second
602 { UNIT_KMH
, UNIT_KTS
, 1000, 1852}, // 1 km/h = 0.539956803 knots (divide with 1.85200)
603 { UNIT_KMH
, UNIT_MPH
, 1000, 1609}, // 1 km/h = 0.621371192 miles per hour (divide with 1.60934400)
604 { UNIT_KMH
, UNIT_METERS_PER_SECOND
, 10, 36}, // 1 km/h = 0.277777778 meters / second (divide with 3.6)
605 { UNIT_KMH
, UNIT_FEET_PER_SECOND
, 911, 1000}, // 1 km/h = 0.911344415 feet per second
607 { UNIT_MILLILITERS
, UNIT_FLOZ
, 100, 2957},
609 { UNIT_RADIANS
, UNIT_DEGREE
, 10000, 175}, // 1 rad = 57.29578 deg
610 { UNIT_DEGREE
, UNIT_RADIANS
, 175, 10000}, // 1 deg = 0,0174533 rad
612 { 0, 0, 0, 0} // termination
615 int32_t convertTelemetryValue(int32_t value
, uint8_t unit
, uint8_t prec
, uint8_t destUnit
, uint8_t destPrec
)
617 for (int i
=prec
; i
<destPrec
; i
++)
620 if (unit
== UNIT_CELSIUS
) {
621 if (destUnit
== UNIT_FAHRENHEIT
) {
622 // T(°F) = T(°C)×1,8 + 32
623 value
= 32 + (value
*18) / 10;
625 } else if (unit
== UNIT_FAHRENHEIT
) {
626 if (destUnit
== UNIT_CELSIUS
) {
627 value
= (value
- 32) * 10/18;
631 const UnitConversionRule
* p
= unitConversionTable
;
633 if (p
->unitFrom
== unit
&& p
->unitTo
== destUnit
) {
634 value
= (value
* (int32_t)p
->multiplier
) / (int32_t)p
->divisor
;
641 for (int i
=destPrec
; i
<prec
; i
++)
647 int32_t TelemetrySensor::getValue(int32_t value
, uint8_t unit
, uint8_t prec
) const
649 if (type
== TELEM_TYPE_CUSTOM
&& custom
.ratio
) {
650 if (this->prec
== 2) {
657 value
= (custom
.ratio
* value
+ 122) / 255;
660 value
= convertTelemetryValue(value
, unit
, prec
, this->unit
, this->prec
);
662 if (type
== TELEM_TYPE_CUSTOM
) {
663 value
+= custom
.offset
;
664 if (value
< 0 && onlyPositive
) {
672 bool TelemetrySensor::isConfigurable() const
674 if (type
== TELEM_TYPE_CALCULATED
) {
675 if (formula
>= TELEM_FORMULA_CELL
) {
680 if (unit
>= UNIT_FIRST_VIRTUAL
) {
687 bool TelemetrySensor::isPrecConfigurable() const
689 if (isConfigurable()) {
692 else if (unit
== UNIT_CELLS
) {
700 int32_t TelemetrySensor::getPrecMultiplier() const
703 Important: the return type must be signed, otherwise
704 mathematic operations with a negative telemetry value won't work
706 if (prec
== 2) return 1;
707 if (prec
== 1) return 10;
711 int32_t TelemetrySensor::getPrecDivisor() const
713 if (prec
== 2) return 100;
714 if (prec
== 1) return 10;