Companion: Russian UI (#7180)
[opentx.git] / radio / src / telemetry / telemetry_sensors.cpp
blobdc9742b5593f6e01bdcb9ada7ec50a928cae99d3
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 "opentx.h"
22 #define _USE_MATH_DEFINES
23 #include <math.h>
25 TelemetryItem telemetryItems[MAX_TELEMETRY_SENSORS];
26 uint8_t allowNewSensors;
28 bool isFaiForbidden(source_t idx)
30 if (idx < MIXSRC_FIRST_TELEM) {
31 return false;
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) {
39 return false;
41 else if (sensor->id == BATT_ID) {
42 return false;
44 break;
46 case PROTOCOL_TELEMETRY_FRSKY_D:
47 if (sensor->id == D_RSSI_ID) {
48 return false;
50 else if (sensor->id == D_A1_ID) {
51 return false;
53 break;
55 #if defined(CROSSFIRE)
56 case PROTOCOL_TELEMETRY_CROSSFIRE:
57 if (sensor->id == RX_RSSI1_INDEX) {
58 return false;
60 else if (sensor->id == RX_RSSI2_INDEX) {
61 return false;
63 else if (sensor->id == BATT_VOLTAGE_INDEX) {
64 return false;
66 break;
67 #endif
69 return true;
72 // TODO in maths
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)
83 int32_t newVal = val;
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) {
93 clear();
94 cells.count = cellsCount;
95 // we skip this round as we are not sure we received all cells values
96 return;
99 else if (cellsCount != cells.count) {
100 clear();
101 cells.count = cellsCount;
103 cells.values[cellIndex].set(cellValue);
104 if (cellIndex+1 == cells.count) {
105 newVal = 0;
106 for (int i=0; i<cellsCount; i++) {
107 if (cells.values[i].state) {
108 newVal += cells.values[i].value;
110 else {
111 // we didn't receive all cells values
112 return;
115 newVal = sensor.getValue(newVal, UNIT_VOLTS, 2);
117 else {
118 // we didn't receive all cells values
119 return;
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);
129 else {
130 datetime.hour = (uint8_t) ((data & 0xff000000) >> 24);
131 datetime.min = (uint8_t) ((data & 0x00ff0000) >> 16);
132 datetime.sec = (uint8_t) ((data & 0x0000ff00) >> 8);
133 #if defined(RTCLOCK)
134 if (g_eeGeneral.adjustRTC) {
135 rtcAdjust(datetime.year, datetime.month, datetime.day, datetime.hour, datetime.min, datetime.sec);
137 #endif
139 newVal = 0;
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);
147 #endif
148 if (!pilotLatitude) {
149 pilotLatitude = newVal;
150 distFromEarthAxis = getDistFromEarthAxis(newVal);
152 gps.latitude = newVal;
153 setFresh();
154 return;
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;
161 #endif
162 if (!pilotLongitude) {
163 pilotLongitude = newVal;
165 gps.longitude = newVal;
166 setFresh();
167 return;
169 else if (unit == UNIT_DATETIME_YEAR) {
170 datetime.year = newVal;
171 return;
173 else if (unit == UNIT_DATETIME_DAY_MONTH) {
174 auto data = uint32_t(newVal);
175 datetime.month = data >> 8;
176 datetime.day = data & 0xFF;
177 return;
179 else if (unit == UNIT_DATETIME_HOUR_MIN) {
180 auto data = uint32_t(newVal);
181 datetime.hour = (data & 0xFF);
182 datetime.min = data >> 8;
183 return;
185 else if (unit == UNIT_DATETIME_SEC) {
186 datetime.sec = newVal & 0xFF;
187 newVal = 0;
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;
196 setFresh();
197 return;
199 else {
200 newVal = sensor.getValue(newVal, unit, prec);
201 if (sensor.autoOffset) {
202 if (!isAvailable()) {
203 std.offsetAuto = -newVal;
205 newVal += std.offsetAuto;
207 if (sensor.filter) {
208 if (!isAvailable()) {
209 for (int i=0; i<TELEMETRY_AVERAGE_COUNT; i++) {
210 std.filterValues[i] = newVal;
213 else {
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;
222 sum += tmp;
224 std.filterValues[TELEMETRY_AVERAGE_COUNT-1] = newVal;
225 sum += newVal;
226 newVal = sum/(TELEMETRY_AVERAGE_COUNT+1);
231 if (!isAvailable()) {
232 valueMin = newVal;
233 valueMax = newVal;
235 else if (newVal < valueMin) {
236 valueMin = newVal;
238 else if (newVal > valueMax) {
239 valueMax = newVal;
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);
254 value = newVal;
255 setFresh();
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()) {
266 return;
268 else if (currentItem.isOld()) {
269 setOld();
270 return;
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);
278 setFresh();
280 break;
282 default:
283 break;
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()) {
294 setOld();
296 else {
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)
303 lowest = i+1;
304 if (!highest || cellsItem.cells.values[i].value > cellsItem.cells.values[highest-1].value)
305 highest = i+1;
307 else {
308 lowest = highest = 0;
311 if (lowest) {
312 switch (index) {
313 case TELEM_CELL_INDEX_LOWEST:
314 setValue(sensor, cellsItem.cells.values[lowest-1].value, UNIT_VOLTS, 2);
315 break;
316 case TELEM_CELL_INDEX_HIGHEST:
317 setValue(sensor, cellsItem.cells.values[highest-1].value, UNIT_VOLTS, 2);
318 break;
319 case TELEM_CELL_INDEX_DELTA:
320 setValue(sensor, cellsItem.cells.values[highest-1].value - cellsItem.cells.values[lowest-1].value, UNIT_VOLTS, 2);
321 break;
325 else {
326 index -= 1;
327 if (index < cellsItem.cells.count && cellsItem.cells.values[index].state) {
328 setValue(sensor, cellsItem.cells.values[index].value, UNIT_VOLTS, 2);
333 break;
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()) {
340 return;
342 else if (gpsItem.isOld()) {
343 setOld();
344 return;
346 if (sensor.dist.alt) {
347 altItem = &telemetryItems[sensor.dist.alt-1];
348 if (!altItem->isAvailable()) {
349 return;
351 else if (altItem->isOld()) {
352 setOld();
353 return;
356 uint32_t angle = abs(gpsItem.gps.latitude - gpsItem.pilotLatitude);
357 #if defined(STM32)
358 uint32_t dist = uint64_t(EARTH_RADIUS * M_PI / 180) * angle / 1000000;
359 #else
360 // TODO search later why it breaks Sky9x
361 uint32_t dist = EARTH_RADIUS * angle / 1000000;
362 #endif
363 uint32_t result = dist * dist;
365 angle = abs(gpsItem.gps.longitude - gpsItem.pilotLongitude);
366 #if defined(STM32)
367 dist = uint64_t(gpsItem.distFromEarthAxis) * angle / 1000000;
368 #else
369 dist = gpsItem.distFromEarthAxis * angle / 1000000;
370 #endif
371 result += dist * dist;
373 // Length on ground (ignoring curvature of the earth)
374 result = isqrt32(result);
376 if (altItem) {
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);
384 break;
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) {
394 maxitems = 2;
395 value = 1;
397 for (int i=0; i<maxitems; i++) {
398 int8_t source = sensor.calc.sources[i];
399 if (source) {
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())
405 available = 1;
406 else
407 continue;
408 if (telemetryItem.isOld())
409 continue;
411 else {
412 if (!telemetryItem.isAvailable()) {
413 return;
415 else if (telemetryItem.isOld()) {
416 setOld();
417 return;
420 int32_t sensorValue = telemetryItem.value;
421 if (source < 0)
422 sensorValue = -sensorValue;
423 count += 1;
424 if (sensor.formula == TELEM_FORMULA_MULTIPLY) {
425 mulprec += telemetrySensor.prec;
426 value *= convertTelemetryValue(sensorValue, telemetrySensor.unit, 0, sensor.unit, 0);
428 else {
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));
434 else
435 value += sensorValue;
439 if (sensor.formula == TELEM_FORMULA_AVERAGE) {
440 if (count == 0) {
441 if (available)
442 setOld();
443 return;
445 else {
446 value = (value + count/2) / count;
449 else if (sensor.formula == TELEM_FORMULA_MULTIPLY) {
450 if (count == 0)
451 return;
452 value = convertTelemetryValue(value, sensor.unit, mulprec, sensor.unit, sensor.prec);
454 setValue(sensor, value, sensor.unit, sensor.prec);
455 break;
458 default:
459 break;
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()) {
475 return index;
478 return -1;
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()) {
486 return index;
489 return -1;
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);
500 sensorFound = true;
501 // we continue search here, because sensors can share the same id and instance
505 if (sensorFound || !allowNewSensors) {
506 return -1;
509 int index = availableTelemetryIndex();
510 if (index >= 0) {
511 switch (protocol) {
512 case PROTOCOL_TELEMETRY_FRSKY_SPORT:
513 frskySportSetDefault(index, id, subId, instance);
514 break;
515 case PROTOCOL_TELEMETRY_FRSKY_D:
516 frskyDSetDefault(index, id);
517 break;
518 #if defined(CROSSFIRE)
519 case PROTOCOL_TELEMETRY_CROSSFIRE:
520 crossfireSetDefault(index, id, instance);
521 break;
522 #endif
523 #if defined(MULTIMODULE)
524 case PROTOCOL_TELEMETRY_SPEKTRUM:
525 spektrumSetDefault(index, id, subId, instance);
526 break;
527 case PROTOCOL_TELEMETRY_FLYSKY_IBUS:
528 flySkySetDefault(index,id, subId, instance);
529 break;
530 case PROTOCOL_TELEMETRY_HITEC:
531 hitecSetDefault(index, id, subId, instance);
532 break;
533 case PROTOCOL_TELEMETRY_HOTT:
534 hottSetDefault(index, id, subId, instance);
535 break;
536 #endif
537 #if defined(LUA)
538 case PROTOCOL_TELEMETRY_LUA:
539 // Sensor will be initialized by calling function
540 // This drops the first value
541 return index;
542 #endif
543 default:
544 return index;
546 telemetryItems[index].setValue(g_model.telemetrySensors[index], value, unit, prec);
547 return index;
549 else {
550 POPUP_WARNING(STR_TELEMETRYFULL);
551 return -1;
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);
560 this->unit = unit;
561 if (prec > 1 && (IS_DISTANCE_UNIT(unit) || IS_SPEED_UNIT(unit))) {
562 // 2 digits precision is not needed here
563 prec = 1;
565 this->prec = prec;
566 // Log sensors by default
567 this->logs = true;
570 void TelemetrySensor::init(uint16_t id)
572 char label[4];
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);
577 init(label);
580 bool TelemetrySensor::isAvailable() const
582 return ZLEN(label) > 0;
585 PACK(typedef struct {
586 uint8_t unitFrom;
587 uint8_t unitTo;
588 int16_t multiplier;
589 int16_t divisor;
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++)
618 value *= 10;
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;
630 else {
631 const UnitConversionRule * p = unitConversionTable;
632 while (p->divisor) {
633 if (p->unitFrom == unit && p->unitTo == destUnit) {
634 value = (value * (int32_t)p->multiplier) / (int32_t)p->divisor;
635 break;
637 ++p;
641 for (int i=destPrec; i<prec; i++)
642 value /= 10;
644 return value;
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) {
651 value *= 10;
652 prec = 2;
654 else {
655 prec = 1;
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) {
665 value = 0;
669 return value;
672 bool TelemetrySensor::isConfigurable() const
674 if (type == TELEM_TYPE_CALCULATED) {
675 if (formula >= TELEM_FORMULA_CELL) {
676 return false;
679 else {
680 if (unit >= UNIT_FIRST_VIRTUAL) {
681 return false;
684 return true;
687 bool TelemetrySensor::isPrecConfigurable() const
689 if (isConfigurable()) {
690 return true;
692 else if (unit == UNIT_CELLS) {
693 return true;
695 else {
696 return false;
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;
708 return 100;
711 int32_t TelemetrySensor::getPrecDivisor() const
713 if (prec == 2) return 100;
714 if (prec == 1) return 10;
715 return 1;