duplicate emptyline removal (#14027)
[betaflight.git] / src / main / telemetry / jetiexbus.c
blob07dc7fe2e6ed4eb5e1a36a801899ec1813702682
1 /*
2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
8 * any later version.
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #include "platform.h"
28 #if defined(USE_TELEMETRY_JETIEXBUS)
30 #include "build/build_config.h"
31 #include "build/debug.h"
32 #include "fc/runtime_config.h"
33 #include "config/feature.h"
35 #include "common/utils.h"
36 #include "common/bitarray.h"
38 #include "drivers/serial.h"
39 #include "drivers/serial_uart.h"
40 #include "drivers/time.h"
42 #include "flight/position.h"
43 #include "flight/imu.h"
45 #include "io/serial.h"
46 #include "io/gps.h"
48 #include "rx/rx.h"
49 #include "rx/jetiexbus.h"
51 #include "sensors/battery.h"
52 #include "sensors/sensors.h"
53 #include "sensors/acceleration.h"
55 #include "telemetry/jetiexbus.h"
56 #include "telemetry/telemetry.h"
58 #define EXTEL_DATA_MSG (0x40)
59 #define EXTEL_UNMASK_TYPE (0x3F)
60 #define EXTEL_SYNC_LEN 1
61 #define EXTEL_CRC_LEN 1
62 #define EXTEL_HEADER_LEN 6
63 #define EXTEL_MAX_LEN 26
64 #define EXTEL_OVERHEAD (EXTEL_SYNC_LEN + EXTEL_HEADER_LEN + EXTEL_CRC_LEN)
65 #define EXTEL_MAX_PAYLOAD (EXTEL_MAX_LEN - EXTEL_OVERHEAD)
66 #define EXBUS_MAX_REQUEST_BUFFER_SIZE (EXBUS_OVERHEAD + EXTEL_MAX_LEN)
68 enum exTelHeader_e {
69 EXTEL_HEADER_SYNC = 0,
70 EXTEL_HEADER_TYPE_LEN,
71 EXTEL_HEADER_USN_LB,
72 EXTEL_HEADER_USN_HB,
73 EXTEL_HEADER_LSN_LB,
74 EXTEL_HEADER_LSN_HB,
75 EXTEL_HEADER_RES,
76 EXTEL_HEADER_ID,
77 EXTEL_HEADER_DATA
80 enum {
81 EXBUS_TRANS_ZERO = 0,
82 EXBUS_TRANS_RX_READY,
83 EXBUS_TRANS_RX,
84 EXBUS_TRANS_IS_TX_COMPLETED,
85 EXBUS_TRANS_TX
88 enum exDataType_e {
89 EX_TYPE_6b = 0, // int6_t Data type 6b (-31 ¸31)
90 EX_TYPE_14b = 1, // int14_t Data type 14b (-8191 ¸8191)
91 EX_TYPE_22b = 4, // int22_t Data type 22b (-2097151 ¸2097151)
92 EX_TYPE_DT = 5, // int22_t Special data type – time and date
93 EX_TYPE_30b = 8, // int30_t Data type 30b (-536870911 ¸536870911)
94 EX_TYPE_GPS = 9, // int30_t Special data type – GPS coordinates: lo/hi minute - lo/hi degree.
95 EX_TYPE_DES = 255 // only for devicedescription
98 const uint8_t exDataTypeLen[] = {
99 [EX_TYPE_6b] = 1,
100 [EX_TYPE_14b] = 2,
101 [EX_TYPE_22b] = 3,
102 [EX_TYPE_DT] = 3,
103 [EX_TYPE_30b] = 4,
104 [EX_TYPE_GPS] = 4
107 typedef struct exBusSensor_s {
108 const char *label;
109 const char *unit;
110 const uint8_t exDataType;
111 const uint8_t decimals;
112 } exBusSensor_t;
114 #define DECIMAL_MASK(decimals) (decimals << 5)
116 // list of telemetry messages
117 // after every 15 sensors a new header has to be inserted (e.g. "BF D2")
118 const exBusSensor_t jetiExSensors[] = {
119 {"BF D1", "", EX_TYPE_DES, 0 }, // device descripton
120 {"Voltage", "V", EX_TYPE_22b, DECIMAL_MASK(1)},
121 {"Current", "A", EX_TYPE_22b, DECIMAL_MASK(2)},
122 {"Altitude", "m", EX_TYPE_22b, DECIMAL_MASK(2)},
123 {"Capacity", "mAh", EX_TYPE_22b, DECIMAL_MASK(0)},
124 {"Power", "W", EX_TYPE_22b, DECIMAL_MASK(1)},
125 {"Roll angle", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
126 {"Pitch angle", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
127 {"Heading", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
128 {"Vario", "m/s", EX_TYPE_22b, DECIMAL_MASK(2)},
129 {"GPS Sats", "", EX_TYPE_22b, DECIMAL_MASK(0)},
130 {"GPS Long", "", EX_TYPE_GPS, DECIMAL_MASK(0)},
131 {"GPS Lat", "", EX_TYPE_GPS, DECIMAL_MASK(0)},
132 {"GPS Speed", "m/s", EX_TYPE_22b, DECIMAL_MASK(2)},
133 {"GPS H-Distance", "m", EX_TYPE_22b, DECIMAL_MASK(0)},
134 {"GPS H-Direction", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
135 {"BF D2", "", EX_TYPE_DES, 0 }, // device descripton
136 {"GPS Heading", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
137 {"GPS Altitude", "m", EX_TYPE_22b, DECIMAL_MASK(2)},
138 {"G-Force X", "", EX_TYPE_22b, DECIMAL_MASK(3)},
139 {"G-Force Y", "", EX_TYPE_22b, DECIMAL_MASK(3)},
140 {"G-Force Z", "", EX_TYPE_22b, DECIMAL_MASK(3)}
143 // after every 15 sensors increment the step by 2 (e.g. ...EX_VAL15, EX_VAL16 = 17) to skip the device description
144 enum exSensors_e {
145 EX_VOLTAGE = 1,
146 EX_CURRENT,
147 EX_ALTITUDE,
148 EX_CAPACITY,
149 EX_POWER,
150 EX_ROLL_ANGLE,
151 EX_PITCH_ANGLE,
152 EX_HEADING,
153 EX_VARIO,
154 EX_GPS_SATS,
155 EX_GPS_LONG,
156 EX_GPS_LAT,
157 EX_GPS_SPEED,
158 EX_GPS_DISTANCE_TO_HOME,
159 EX_GPS_DIRECTION_TO_HOME,
160 EX_GPS_HEADING = 17,
161 EX_GPS_ALTITUDE,
162 EX_GFORCE_X,
163 EX_GFORCE_Y,
164 EX_GFORCE_Z
167 union{
168 int32_t vInt;
169 uint16_t vWord[2];
170 char vBytes[4];
171 } exGps;
173 #define JETI_EX_SENSOR_COUNT (ARRAYLEN(jetiExSensors))
175 static uint8_t jetiExBusTelemetryFrame[40];
176 static uint8_t jetiExBusTransceiveState = EXBUS_TRANS_RX;
177 static uint8_t firstActiveSensor = 0;
178 static uint32_t exSensorEnabled = 0;
180 static uint8_t sendJetiExBusTelemetry(uint8_t packetID, uint8_t item);
181 static uint8_t getNextActiveSensor(uint8_t currentSensor);
183 // Jeti Ex Telemetry CRC calculations for a frame
184 uint8_t calcCRC8(const uint8_t *pt, uint8_t msgLen)
186 uint8_t crc=0;
187 for (uint8_t mlen = 0; mlen < msgLen; mlen++) {
188 crc ^= pt[mlen];
189 crc = crc ^ (crc << 1) ^ (crc << 2) ^ (0x0e090700 >> ((crc >> 3) & 0x18));
191 return(crc);
194 void enableGpsTelemetry(bool enable)
196 if (enable) {
197 bitArraySet(&exSensorEnabled, EX_GPS_SATS);
198 bitArraySet(&exSensorEnabled, EX_GPS_LONG);
199 bitArraySet(&exSensorEnabled, EX_GPS_LAT);
200 bitArraySet(&exSensorEnabled, EX_GPS_SPEED);
201 bitArraySet(&exSensorEnabled, EX_GPS_DISTANCE_TO_HOME);
202 bitArraySet(&exSensorEnabled, EX_GPS_DIRECTION_TO_HOME);
203 bitArraySet(&exSensorEnabled, EX_GPS_HEADING);
204 bitArraySet(&exSensorEnabled, EX_GPS_ALTITUDE);
205 } else {
206 bitArrayClr(&exSensorEnabled, EX_GPS_SATS);
207 bitArrayClr(&exSensorEnabled, EX_GPS_LONG);
208 bitArrayClr(&exSensorEnabled, EX_GPS_LAT);
209 bitArrayClr(&exSensorEnabled, EX_GPS_SPEED);
210 bitArrayClr(&exSensorEnabled, EX_GPS_DISTANCE_TO_HOME);
211 bitArrayClr(&exSensorEnabled, EX_GPS_DIRECTION_TO_HOME);
212 bitArrayClr(&exSensorEnabled, EX_GPS_HEADING);
213 bitArrayClr(&exSensorEnabled, EX_GPS_ALTITUDE);
218 * -----------------------------------------------
219 * Jeti Ex Bus Telemetry
220 * -----------------------------------------------
222 void initJetiExBusTelemetry(void)
224 // Init Ex Bus Frame header
225 jetiExBusTelemetryFrame[EXBUS_HEADER_SYNC] = 0x3B; // Startbytes
226 jetiExBusTelemetryFrame[EXBUS_HEADER_REQ] = 0x01;
227 jetiExBusTelemetryFrame[EXBUS_HEADER_DATA_ID] = 0x3A; // Ex Telemetry
229 // Init Ex Telemetry header
230 uint8_t *jetiExTelemetryFrame = &jetiExBusTelemetryFrame[EXBUS_HEADER_DATA];
232 jetiExTelemetryFrame[EXTEL_HEADER_SYNC] = 0x9F; // Startbyte
233 jetiExTelemetryFrame[EXTEL_HEADER_USN_LB] = 0x1E; // Serial Number 4 Byte
234 jetiExTelemetryFrame[EXTEL_HEADER_USN_HB] = 0xA4;
235 jetiExTelemetryFrame[EXTEL_HEADER_LSN_LB] = 0x00; // increment by telemetry count (%16) > only 15 values per device possible
236 jetiExTelemetryFrame[EXTEL_HEADER_LSN_HB] = 0x00;
237 jetiExTelemetryFrame[EXTEL_HEADER_RES] = 0x00; // reserved, by default 0x00
239 // Check which sensors are available
240 if (batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE) {
241 bitArraySet(&exSensorEnabled, EX_VOLTAGE);
243 if (batteryConfig()->currentMeterSource != CURRENT_METER_NONE) {
244 bitArraySet(&exSensorEnabled, EX_CURRENT);
246 if ((batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE) && (batteryConfig()->currentMeterSource != CURRENT_METER_NONE)) {
247 bitArraySet(&exSensorEnabled, EX_POWER);
248 bitArraySet(&exSensorEnabled, EX_CAPACITY);
250 if (sensors(SENSOR_BARO)) {
251 bitArraySet(&exSensorEnabled, EX_ALTITUDE);
252 #ifdef USE_VARIO
253 bitArraySet(&exSensorEnabled, EX_VARIO);
254 #endif
256 if (sensors(SENSOR_ACC)) {
257 bitArraySet(&exSensorEnabled, EX_ROLL_ANGLE);
258 bitArraySet(&exSensorEnabled, EX_PITCH_ANGLE);
259 bitArraySet(&exSensorEnabled, EX_GFORCE_X);
260 bitArraySet(&exSensorEnabled, EX_GFORCE_Y);
261 bitArraySet(&exSensorEnabled, EX_GFORCE_Z);
263 if (sensors(SENSOR_MAG)) {
264 bitArraySet(&exSensorEnabled, EX_HEADING);
267 enableGpsTelemetry(featureIsEnabled(FEATURE_GPS));
269 firstActiveSensor = getNextActiveSensor(0); // find the first active sensor
272 void createExTelemetryTextMessage(uint8_t *exMessage, uint8_t messageID, const exBusSensor_t *sensor)
274 uint8_t labelLength = strlen(sensor->label);
275 uint8_t unitLength = strlen(sensor->unit);
277 exMessage[EXTEL_HEADER_TYPE_LEN] = EXTEL_OVERHEAD + labelLength + unitLength;
278 exMessage[EXTEL_HEADER_LSN_LB] = messageID & 0xF0; // Device ID
279 exMessage[EXTEL_HEADER_ID] = messageID & 0x0F; // Sensor ID (%16)
280 exMessage[EXTEL_HEADER_DATA] = (labelLength << 3) + unitLength;
282 memcpy(&exMessage[EXTEL_HEADER_DATA + 1], sensor->label, labelLength);
283 memcpy(&exMessage[EXTEL_HEADER_DATA + 1 + labelLength], sensor->unit, unitLength);
285 exMessage[exMessage[EXTEL_HEADER_TYPE_LEN] + EXTEL_CRC_LEN] = calcCRC8(&exMessage[EXTEL_HEADER_TYPE_LEN], exMessage[EXTEL_HEADER_TYPE_LEN]);
288 uint32_t calcGpsDDMMmmm(int32_t value, bool isLong)
290 uint32_t absValue = abs(value);
291 uint16_t deg16 = absValue / GPS_DEGREES_DIVIDER;
292 uint16_t min16 = (absValue - deg16 * GPS_DEGREES_DIVIDER) * 6 / 1000;
294 exGps.vInt = 0;
295 exGps.vWord[0] = min16;
296 exGps.vWord[1] = deg16;
297 exGps.vWord[1] |= isLong ? 0x2000 : 0;
298 exGps.vWord[1] |= (value < 0) ? 0x4000 : 0;
300 return exGps.vInt;
303 int32_t getSensorValue(uint8_t sensor)
305 switch (sensor) {
306 case EX_VOLTAGE:
307 return getLegacyBatteryVoltage();
308 break;
310 case EX_CURRENT:
311 return getAmperage();
312 break;
314 case EX_ALTITUDE:
315 return getEstimatedAltitudeCm();
316 break;
318 case EX_CAPACITY:
319 return getMAhDrawn();
320 break;
322 case EX_POWER:
323 return (getBatteryVoltage() * getAmperage() / 1000);
324 break;
326 case EX_ROLL_ANGLE:
327 return attitude.values.roll;
328 break;
330 case EX_PITCH_ANGLE:
331 return attitude.values.pitch;
332 break;
334 case EX_HEADING:
335 return attitude.values.yaw;
336 break;
338 #ifdef USE_VARIO
339 case EX_VARIO:
340 return getEstimatedVario();
341 break;
342 #endif
344 #ifdef USE_GPS
345 case EX_GPS_SATS:
346 return gpsSol.numSat;
347 break;
349 case EX_GPS_LONG:
350 return calcGpsDDMMmmm(gpsSol.llh.lon, true);
351 break;
353 case EX_GPS_LAT:
354 return calcGpsDDMMmmm(gpsSol.llh.lat, false);
355 break;
357 case EX_GPS_SPEED:
358 return gpsSol.groundSpeed;
359 break;
361 case EX_GPS_DISTANCE_TO_HOME:
362 return GPS_distanceToHome;
363 break;
365 case EX_GPS_DIRECTION_TO_HOME:
366 return GPS_directionToHome / 10;
367 break;
369 case EX_GPS_HEADING:
370 return gpsSol.groundCourse;
371 break;
373 case EX_GPS_ALTITUDE:
374 return gpsSol.llh.altCm;
375 break;
376 #endif
378 #if defined(USE_ACC)
379 case EX_GFORCE_X:
380 return (int16_t)(((float)acc.accADC.x / acc.dev.acc_1G) * 1000);
381 break;
383 case EX_GFORCE_Y:
384 return (int16_t)(((float)acc.accADC.y / acc.dev.acc_1G) * 1000);
385 break;
387 case EX_GFORCE_Z:
388 return (int16_t)(((float)acc.accADC.z / acc.dev.acc_1G) * 1000);
389 break;
390 #endif
392 default:
393 return -1;
397 uint8_t getNextActiveSensor(uint8_t currentSensor)
399 while( ++currentSensor < JETI_EX_SENSOR_COUNT) {
400 if (bitArrayGet(&exSensorEnabled, currentSensor)) {
401 break;
404 if (currentSensor == JETI_EX_SENSOR_COUNT ) {
405 currentSensor = firstActiveSensor;
407 return currentSensor;
410 uint8_t createExTelemetryValueMessage(uint8_t *exMessage, uint8_t item)
412 uint8_t startItem = item;
413 uint8_t sensorItemMaxGroup = (item & 0xF0) + 0x10;
414 uint8_t iCount;
415 uint8_t messageSize;
416 uint32_t sensorValue;
418 exMessage[EXTEL_HEADER_LSN_LB] = item & 0xF0; // Device ID
419 uint8_t *p = &exMessage[EXTEL_HEADER_ID];
421 while (item < sensorItemMaxGroup) {
422 *p++ = ((item & 0x0F) << 4) | jetiExSensors[item].exDataType; // Sensor ID (%16) | EX Data Type
424 sensorValue = getSensorValue(item);
425 iCount = exDataTypeLen[jetiExSensors[item].exDataType];
427 while (iCount > 1) {
428 *p++ = sensorValue;
429 sensorValue = sensorValue >> 8;
430 iCount--;
432 if (jetiExSensors[item].exDataType != EX_TYPE_GPS) {
433 *p++ = (sensorValue & 0x9F) | jetiExSensors[item].decimals;
434 } else {
435 *p++ = sensorValue;
438 item = getNextActiveSensor(item);
440 if (startItem >= item) {
441 break;
444 if ((p - &exMessage[EXTEL_HEADER_ID]) + exDataTypeLen[jetiExSensors[item].exDataType] + 1 >= EXTEL_MAX_PAYLOAD) {
445 break;
448 messageSize = (EXTEL_HEADER_LEN + (p-&exMessage[EXTEL_HEADER_ID]));
449 exMessage[EXTEL_HEADER_TYPE_LEN] = EXTEL_DATA_MSG | messageSize;
450 exMessage[messageSize + EXTEL_CRC_LEN] = calcCRC8(&exMessage[EXTEL_HEADER_TYPE_LEN], messageSize);
452 return item; // return the next item
455 void createExBusMessage(uint8_t *exBusMessage, const uint8_t *exMessage, uint8_t packetID)
457 uint16_t crc16;
459 exBusMessage[EXBUS_HEADER_PACKET_ID] = packetID;
460 exBusMessage[EXBUS_HEADER_SUBLEN] = (exMessage[EXTEL_HEADER_TYPE_LEN] & EXTEL_UNMASK_TYPE) + 2; // +2: startbyte & CRC8
461 exBusMessage[EXBUS_HEADER_MSG_LEN] = EXBUS_OVERHEAD + exBusMessage[EXBUS_HEADER_SUBLEN];
463 crc16 = jetiExBusCalcCRC16(exBusMessage, exBusMessage[EXBUS_HEADER_MSG_LEN] - EXBUS_CRC_LEN);
464 exBusMessage[exBusMessage[EXBUS_HEADER_MSG_LEN] - 2] = crc16;
465 exBusMessage[exBusMessage[EXBUS_HEADER_MSG_LEN] - 1] = crc16 >> 8;
468 void checkJetiExBusTelemetryState(void)
470 return;
473 void handleJetiExBusTelemetry(void)
475 static uint16_t framesLost = 0; // only for debug
476 static uint8_t item = 0;
477 uint32_t timeDiff;
479 // Check if we shall reset frame position due to time
480 if (jetiExBusRequestState == EXBUS_STATE_RECEIVED) {
482 // to prevent timing issues from request to answer - max. 4ms
483 timeDiff = micros() - jetiTimeStampRequest;
485 if (timeDiff > 3000) { // include reserved time
486 jetiExBusRequestState = EXBUS_STATE_ZERO;
487 framesLost++;
488 return;
491 if ((jetiExBusRequestFrame[EXBUS_HEADER_DATA_ID] == EXBUS_EX_REQUEST) && (jetiExBusCalcCRC16(jetiExBusRequestFrame, jetiExBusRequestFrame[EXBUS_HEADER_MSG_LEN]) == 0)) {
492 if (serialRxBytesWaiting(jetiExBusPort) == 0) {
493 jetiExBusTransceiveState = EXBUS_TRANS_TX;
494 item = sendJetiExBusTelemetry(jetiExBusRequestFrame[EXBUS_HEADER_PACKET_ID], item);
495 jetiExBusRequestState = EXBUS_STATE_PROCESSED;
496 return;
498 } else {
499 jetiExBusRequestState = EXBUS_STATE_ZERO;
500 return;
504 // check the state if transmit is ready
505 if (jetiExBusTransceiveState == EXBUS_TRANS_IS_TX_COMPLETED) {
506 if (isSerialTransmitBufferEmpty(jetiExBusPort)) {
507 jetiExBusTransceiveState = EXBUS_TRANS_RX;
508 jetiExBusRequestState = EXBUS_STATE_ZERO;
513 uint8_t sendJetiExBusTelemetry(uint8_t packetID, uint8_t item)
515 static uint8_t sensorDescriptionCounter = 0xFF;
516 static uint8_t requestLoop = 0xFF;
517 static bool allSensorsActive = true;
518 uint8_t *jetiExTelemetryFrame = &jetiExBusTelemetryFrame[EXBUS_HEADER_DATA];
520 if (requestLoop) {
521 while( ++sensorDescriptionCounter < JETI_EX_SENSOR_COUNT) {
522 if (bitArrayGet(&exSensorEnabled, sensorDescriptionCounter) || (jetiExSensors[sensorDescriptionCounter].exDataType == EX_TYPE_DES)) {
523 break;
526 if (sensorDescriptionCounter == JETI_EX_SENSOR_COUNT ) {
527 sensorDescriptionCounter = 0;
530 createExTelemetryTextMessage(jetiExTelemetryFrame, sensorDescriptionCounter, &jetiExSensors[sensorDescriptionCounter]);
531 createExBusMessage(jetiExBusTelemetryFrame, jetiExTelemetryFrame, packetID);
532 requestLoop--;
533 if (requestLoop == 0) {
534 item = firstActiveSensor;
535 if (featureIsEnabled(FEATURE_GPS)) {
536 enableGpsTelemetry(false);
537 allSensorsActive = false;
540 } else {
541 item = createExTelemetryValueMessage(jetiExTelemetryFrame, item);
542 createExBusMessage(jetiExBusTelemetryFrame, jetiExTelemetryFrame, packetID);
544 if (!allSensorsActive) {
545 if (sensors(SENSOR_GPS)) {
546 enableGpsTelemetry(true);
547 allSensorsActive = true;
552 serialWriteBuf(jetiExBusPort, jetiExBusTelemetryFrame, jetiExBusTelemetryFrame[EXBUS_HEADER_MSG_LEN]);
553 jetiExBusTransceiveState = EXBUS_TRANS_IS_TX_COMPLETED;
555 return item;
557 #endif