4 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
19 * TXID + RXID are already skipped in MULTI module to save memory+transmission time, format from Multi is:
20 * AA or AC | TX_RSSI | sensor ...
24 #define FLYSKY_TELEMETRY_LENGTH (2+7*4) // Should it be 2+7*6???
25 #define ALT_PRECISION 15
26 #define R_DIV_G_MUL_10_Q15 (uint64_t)9591506
27 #define INV_LOG2_E_Q1DOT31 (uint64_t)0x58b90bfc // Inverse log base 2 of e
28 #define PRESSURE_MASK 0x7FFFF
34 const TelemetryUnit unit
;
35 const uint8_t precision
;
38 // telemetry sensors ID
41 AFHDS2A_ID_VOLTAGE
= 0x00, // Internal Voltage
42 AFHDS2A_ID_TEMPERATURE
= 0x01, // Temperature
43 AFHDS2A_ID_MOT
= 0x02, // RPM
44 AFHDS2A_ID_EXTV
= 0x03, // External Voltage
45 AFHDS2A_ID_CELL_VOLTAGE
= 0x04, // Avg Cell voltage
46 AFHDS2A_ID_BAT_CURR
= 0x05, // battery current A * 100
47 AFHDS2A_ID_FUEL
= 0x06, // remaining battery percentage / mah drawn otherwise or fuel level no unit!
48 AFHDS2A_ID_RPM
= 0x07, // throttle value / battery capacity
49 AFHDS2A_ID_CMP_HEAD
= 0x08, // Heading 0..360 deg, 0=north 2bytes
50 AFHDS2A_ID_CLIMB_RATE
= 0x09, // 2 bytes m/s *100 signed
51 AFHDS2A_ID_COG
= 0x0A, // 2 bytes Course over ground(NOT heading, but direction of movement) in degrees * 100, 0.0..359.99 degrees. unknown max uint
52 AFHDS2A_ID_GPS_STATUS
= 0x0B, // 2 bytes
53 AFHDS2A_ID_ACC_X
= 0x0C, // 2 bytes m/s *100 signed
54 AFHDS2A_ID_ACC_Y
= 0x0D, // 2 bytes m/s *100 signed
55 AFHDS2A_ID_ACC_Z
= 0x0E, // 2 bytes m/s *100 signed
56 AFHDS2A_ID_ROLL
= 0x0F, // 2 bytes deg *100 signed
57 AFHDS2A_ID_PITCH
= 0x10, // 2 bytes deg *100 signed
58 AFHDS2A_ID_YAW
= 0x11, // 2 bytes deg *100 signed
59 AFHDS2A_ID_VERTICAL_SPEED
= 0x12, // 2 bytes m/s *100 signed
60 AFHDS2A_ID_GROUND_SPEED
= 0x13, // 2 bytes m/s *100 different unit than build-in sensor
61 AFHDS2A_ID_GPS_DIST
= 0x14, // 2 bytes distance from home m unsigned
62 AFHDS2A_ID_ARMED
= 0x15, // 2 bytes
63 AFHDS2A_ID_FLIGHT_MODE
= 0x16, // 2 bytes
65 AFHDS2A_ID_PRES
= 0x41, // Pressure
66 AFHDS2A_ID_ODO1
= 0x7C, // Odometer1
67 AFHDS2A_ID_ODO2
= 0x7D, // Odometer2
68 AFHDS2A_ID_SPE
= 0x7E, // Speed 2 bytes km/h
69 AFHDS2A_ID_TX_V
= 0x7F, // TX Voltage
71 AFHDS2A_ID_GPS_LAT
= 0x80, // 4bytes signed WGS84 in degrees * 1E7
72 AFHDS2A_ID_GPS_LON
= 0x81, // 4bytes signed WGS84 in degrees * 1E7
73 AFHDS2A_ID_GPS_ALT
= 0x82, // 4bytes signed!!! GPS alt m*100
74 AFHDS2A_ID_ALT
= 0x83, // 4bytes signed!!! Alt m*100
75 AFHDS2A_ID_S84
= 0x84,
76 AFHDS2A_ID_S85
= 0x85,
77 AFHDS2A_ID_S86
= 0x86,
78 AFHDS2A_ID_S87
= 0x87,
79 AFHDS2A_ID_S88
= 0x88,
80 AFHDS2A_ID_S89
= 0x89,
81 AFHDS2A_ID_S8a
= 0x8A,
83 AFHDS2A_ID_ALT_FLYSKY
= 0xF9, // Altitude 2 bytes signed in m - used in FlySky native TX - SIGNED value
84 AFHDS2A_ID_RX_SNR
= 0xFA, // SNR
85 AFHDS2A_ID_RX_NOISE
= 0xFB, // Noise
86 AFHDS2A_ID_RX_RSSI
= 0xFC, // RSSI
87 AFHDS2A_ID_RX_ERR_RATE
= 0xFE, // Error rate
88 AFHDS2A_ID_END
= 0xFF,
90 // AC type telemetry with multiple values in one packet
91 AFHDS2A_ID_GPS_FULL
= 0xFD,
92 AFHDS2A_ID_VOLT_FULL
= 0xF0,
93 AFHDS2A_ID_ACC_FULL
= 0xEF,
94 AFHDS2A_ID_TX_RSSI
= 0x200, // Pseudo id outside 1 byte range of FlySky sensors
97 const FlySkySensor flySkySensors
[] = {
98 {AFHDS2A_ID_VOLTAGE
| 0x100, ZSTR_A1
, UNIT_VOLTS
, 2}, // RX Voltage (remapped, really 0x0)
99 {AFHDS2A_ID_TEMPERATURE
, ZSTR_TEMP1
, UNIT_CELSIUS
, 1}, // Temperature
100 {AFHDS2A_ID_MOT
, ZSTR_RPM
, UNIT_RAW
, 0}, // RPM
101 {AFHDS2A_ID_EXTV
, ZSTR_A3
, UNIT_VOLTS
, 2}, // External voltage
102 {AFHDS2A_ID_CELL_VOLTAGE
, ZSTR_CELLS
, UNIT_VOLTS
, 2}, // Avg Cell voltage
103 {AFHDS2A_ID_BAT_CURR
, ZSTR_CURR
, UNIT_AMPS
, 2}, // battery current A * 100
104 {AFHDS2A_ID_FUEL
, ZSTR_CAPACITY
, UNIT_RAW
, 0}, // remaining battery percentage / mah drawn otherwise or fuel level no unit!
105 {AFHDS2A_ID_RPM
, ZSTR_RPM
, UNIT_RAW
, 0}, // throttle value / battery capacity
106 {AFHDS2A_ID_CMP_HEAD
, ZSTR_HDG
, UNIT_DEGREE
, 0}, // Heading 0..360 deg, 0=north 2bytes
107 {AFHDS2A_ID_CLIMB_RATE
, ZSTR_VSPD
, UNIT_METERS_PER_SECOND
, 2}, // 2 bytes m/s *100
108 {AFHDS2A_ID_COG
, ZSTR_HDG
, UNIT_DEGREE
, 2}, // 2 bytes Course over ground(NOT heading, but direction of movement) in degrees * 100, 0.0..359.99 degrees. unknown max uint
109 {AFHDS2A_ID_GPS_STATUS
, ZSTR_SATELLITES
, UNIT_RAW
, 0}, // 2 bytes
110 {AFHDS2A_ID_ACC_X
, ZSTR_ACCX
, UNIT_METERS_PER_SECOND
, 2}, // 2 bytes m/s *100 signed
111 {AFHDS2A_ID_ACC_Y
, ZSTR_ACCY
, UNIT_METERS_PER_SECOND
, 2}, // 2 bytes m/s *100 signed
112 {AFHDS2A_ID_ACC_Z
, ZSTR_ACCZ
, UNIT_METERS_PER_SECOND
, 2}, // 2 bytes m/s *100 signed
113 {AFHDS2A_ID_ROLL
, ZSTR_ROLL
, UNIT_DEGREE
, 2}, // 2 bytes deg *100 signed
114 {AFHDS2A_ID_PITCH
, ZSTR_PITCH
, UNIT_DEGREE
, 2}, // 2 bytes deg *100 signed
115 {AFHDS2A_ID_YAW
, ZSTR_YAW
, UNIT_DEGREE
, 2}, // 2 bytes deg *100 signed
116 {AFHDS2A_ID_VERTICAL_SPEED
, ZSTR_VSPD
, UNIT_METERS_PER_SECOND
, 2}, // 2 bytes m/s *100
117 {AFHDS2A_ID_GROUND_SPEED
, ZSTR_GSPD
, UNIT_METERS_PER_SECOND
, 2}, // 2 bytes m/s *100 different unit than build-in sensor
118 {AFHDS2A_ID_GPS_DIST
, ZSTR_DIST
, UNIT_METERS
, 0}, // 2 bytes dist from home m unsigned
119 {AFHDS2A_ID_ARMED
, ZSTR_ARM
, UNIT_RAW
, 0}, // 2 bytes
120 {AFHDS2A_ID_FLIGHT_MODE
, ZSTR_FLIGHT_MODE
, UNIT_RAW
, 0}, // 2 bytes index
121 {AFHDS2A_ID_PRES
, ZSTR_PRES
, UNIT_RAW
, 2}, // 4 bytes In fact Temperature + Pressure -> Altitude
122 {AFHDS2A_ID_PRES
| 0x100, ZSTR_TEMP2
, UNIT_CELSIUS
, 1}, // 2 bytes Temperature
123 {AFHDS2A_ID_ODO1
, ZSTR_ODO1
, UNIT_METERS
, 2}, // 2 bytes Odometer1 -- some magic with 330 needed
124 {AFHDS2A_ID_ODO2
, ZSTR_ODO2
, UNIT_METERS
, 2}, // 2 bytes Odometer2 -- some magic with 330 needed
125 {AFHDS2A_ID_SPE
, ZSTR_ASPD
, UNIT_KMH
, 2}, // 2 bytes Speed km/h -- some magic with 330 needed
126 {AFHDS2A_ID_TX_V
, ZSTR_TXV
, UNIT_VOLTS
, 2}, // TX Voltage
127 {AFHDS2A_ID_GPS_LAT
, ZSTR_GPS
, UNIT_RAW
, 7}, // 4 bytes signed WGS84 in degrees * 1E7
128 {AFHDS2A_ID_GPS_LON
, ZSTR_GPS
, UNIT_RAW
, 7}, // 4 bytes signed WGS84 in degrees * 1E7
129 {AFHDS2A_ID_GPS_ALT
, ZSTR_GPSALT
, UNIT_METERS
, 2}, // 4 bytes signed GPS alt m*100
130 {AFHDS2A_ID_ALT
, ZSTR_ALT
, UNIT_METERS
, 2}, // 4 bytes signed Alt m*100
132 {AFHDS2A_ID_RX_SNR
, ZSTR_RX_SNR
, UNIT_DB
, 0}, // RX SNR
133 {AFHDS2A_ID_RX_NOISE
, ZSTR_RX_NOISE
, UNIT_DB
, 0}, // RX Noise
134 {AFHDS2A_ID_RX_RSSI
, ZSTR_RSSI
, UNIT_DB
, 0}, // RX RSSI (0xfc)
135 {AFHDS2A_ID_RX_ERR_RATE
, ZSTR_RX_QUALITY
, UNIT_RAW
, 0}, // RX error rate
136 {AFHDS2A_ID_TX_RSSI
, ZSTR_TX_RSSI
, UNIT_RAW
, 0}, // Pseudo sensor for TRSSI
138 {0x00, NULL
, UNIT_RAW
, 0}, // sentinel
141 int32_t getALT(uint32_t value
);
143 static void processFlySkySensor(const uint8_t * packet
, uint8_t type
)
146 uint16_t id
= packet
[0];
147 const uint8_t instance
= packet
[1];
150 //Load most likely value
152 value
= (packet
[3] << 8) | packet
[2];
154 value
= (packet
[6] << 24) | (packet
[5] << 16) | (packet
[4] << 8) | packet
[3];
156 if (id
== 0) id
= 0x100; // Some part of OpenTX does not like sensor with id and instance 0, remap to 0x100
158 if (id
== AFHDS2A_ID_RX_NOISE
|| id
== AFHDS2A_ID_RX_RSSI
) {
161 else if (id
== AFHDS2A_ID_RX_ERR_RATE
) {
163 telemetryData
.rssi
.set(value
);
164 if (value
> 0) telemetryStreaming
= TELEMETRY_TIMEOUT10ms
;
166 else if (id
== AFHDS2A_ID_PRES
&& value
) {
167 // Extract temperature to a new sensor
168 setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS
, id
| 0x100, 0, instance
, ((value
>> 19) - 400), UNIT_CELSIUS
, 1);
169 // Extract alt to a new sensor
170 setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS
, AFHDS2A_ID_ALT
, 0, instance
, getALT(value
), UNIT_METERS
, 2);
171 value
&= PRESSURE_MASK
;
173 else if ((id
>= AFHDS2A_ID_ACC_X
&& id
<= AFHDS2A_ID_VERTICAL_SPEED
) || id
== AFHDS2A_ID_CLIMB_RATE
|| id
== AFHDS2A_ID_ALT_FLYSKY
) {
174 value
= (int16_t) value
; // Signed value
176 else if (id
== AFHDS2A_ID_GPS_STATUS
) {
179 else if (id
== AFHDS2A_ID_GPS_FULL
) {
180 //(AC FRAME)[ID][inst][size][fix][sats][LAT]x4[LON]x4[ALT]x4
181 setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS
, AFHDS2A_ID_GPS_STATUS
, 0, instance
, packet
[4], UNIT_RAW
, 0);
182 for (uint8_t sensorID
= AFHDS2A_ID_GPS_LAT
; sensorID
<= AFHDS2A_ID_GPS_ALT
; sensorID
++) {
183 int index
= 5 + (sensorID
- AFHDS2A_ID_GPS_LAT
) * 4;
184 buffer
[0] = sensorID
;
185 buffer
[1] = instance
;
187 memcpy(buffer
+ 3, packet
+ index
, 4);
188 processFlySkySensor(buffer
, 0xAC);
192 else if (id
== AFHDS2A_ID_VOLT_FULL
) {
193 //(AC FRAME)[ID][inst][size][ACC_X]x2[ACC_Y]x2[ACC_Z]x2[ROLL]x2[PITCH]x2[YAW]x2
194 for (uint8_t sensorID
= AFHDS2A_ID_EXTV
; sensorID
<= AFHDS2A_ID_RPM
; sensorID
++) {
195 int index
= 3 + (sensorID
- AFHDS2A_ID_EXTV
) * 2;
196 buffer
[0] = sensorID
;
197 buffer
[1] = instance
;
198 buffer
[2] = packet
[index
];
199 buffer
[3] = packet
[index
+ 1];
200 processFlySkySensor(buffer
, 0xAA);
204 else if (id
== AFHDS2A_ID_ACC_FULL
) {
205 //(AC FRAME)[ID][inst][size]
206 for (uint8_t sensorID
= AFHDS2A_ID_ACC_X
; sensorID
<= AFHDS2A_ID_YAW
; sensorID
++) {
207 int index
= 3 + (sensorID
- AFHDS2A_ID_ACC_X
) * 2;
208 buffer
[0] = sensorID
;
209 buffer
[1] = instance
;
210 buffer
[2] = packet
[index
];
211 buffer
[3] = packet
[index
+ 1];
212 processFlySkySensor(buffer
, 0xAA);
216 for (const FlySkySensor
* sensor
= flySkySensors
; sensor
->id
; sensor
++) {
217 if (sensor
->id
!= id
) continue;
218 if (sensor
->unit
== UNIT_CELSIUS
) value
-= 400; // Temperature sensors have 40 degree offset
219 else if (sensor
->unit
== UNIT_VOLTS
) value
= (uint16_t) value
; // Voltage types are unsigned 16bit integers
220 setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS
, id
, 0, instance
, value
, sensor
->unit
, sensor
->precision
);
224 setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS
, id
, 0, instance
, value
, UNIT_RAW
, 0);
227 void processFlySkyPacket(const uint8_t * packet
)
229 // Set TX RSSI Value, reverse MULTIs scaling
230 setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS
, AFHDS2A_ID_TX_RSSI
, 0, 0, packet
[0], UNIT_RAW
, 0);
232 const uint8_t * buffer
= packet
+ 1;
234 while (sesnor
++ < 7) {
235 if (*buffer
== AFHDS2A_ID_END
) break;
236 processFlySkySensor(buffer
, 0xAA);
241 void processFlySkyPacketAC(const uint8_t * packet
)
243 // Set TX RSSI Value, reverse MULTIs scaling
244 setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS
, AFHDS2A_ID_TX_RSSI
, 0, 0, packet
[0], UNIT_RAW
, 0);
245 const uint8_t * buffer
= packet
+ 1;
246 while (buffer
- packet
< 26) //28 + 1(multi TX rssi) - 3(ac header)
248 if (*buffer
== AFHDS2A_ID_END
) break;
249 uint8_t size
= buffer
[2];
250 processFlySkySensor(buffer
, 0xAC);
255 void processFlySkyTelemetryData(uint8_t data
, uint8_t * rxBuffer
, uint8_t &rxBufferCount
)
257 if (rxBufferCount
== 0)
260 if (data
== 0xAA || data
== 0xAC) {
261 TRACE("[IBUS] Packet 0x%02X", data
);
264 TRACE("[IBUS] invalid start byte 0x%02X", data
);
269 if (rxBufferCount
< TELEMETRY_RX_PACKET_SIZE
) {
270 rxBuffer
[rxBufferCount
++] = data
;
273 TRACE("[IBUS] array size %d error", rxBufferCount
);
277 if (rxBufferCount
>= FLYSKY_TELEMETRY_LENGTH
) {
278 // debug print the content of the packets
280 debugPrintf(", rssi 0x%02X: ", rxBuffer
[1]);
281 for (int i
=0; i
<7; i
++) {
282 debugPrintf("[%02X %02X %02X%02X] ", rxBuffer
[i
*4+2], rxBuffer
[i
*4 + 3],
283 rxBuffer
[i
*4 + 4], rxBuffer
[i
*4 + 5]);
287 if (data
== 0xAA) processFlySkyPacket(rxBuffer
+ 1);
288 else if (data
== 0xAC) processFlySkyPacketAC(rxBuffer
+ 1);
293 const FlySkySensor
* getFlySkySensor(uint16_t id
)
295 for (const FlySkySensor
* sensor
= flySkySensors
; sensor
->id
; sensor
++) {
296 if (id
== sensor
->id
)
302 void flySkySetDefault(int index
, uint16_t id
, uint8_t subId
, uint8_t instance
)
304 TelemetrySensor
&telemetrySensor
= g_model
.telemetrySensors
[index
];
305 telemetrySensor
.id
= id
;
306 telemetrySensor
.subId
= subId
;
307 telemetrySensor
.instance
= instance
;
309 const FlySkySensor
* sensor
= getFlySkySensor(id
);
311 TelemetryUnit unit
= sensor
->unit
;
312 uint8_t prec
= min
<uint8_t>(2, sensor
->precision
);
313 telemetrySensor
.init(sensor
->name
, unit
, prec
);
315 if (unit
== UNIT_RPMS
) {
316 telemetrySensor
.custom
.ratio
= 1;
317 telemetrySensor
.custom
.offset
= 1;
321 telemetrySensor
.init(id
);
324 storageDirty(EE_MODEL
);
327 uint16_t ibusTempToK(int16_t tempertureIbus
)
329 return (uint16_t) (tempertureIbus
- 400) + 2731;
332 int32_t log2fix(uint32_t x
)
334 int32_t b
= 1U << (ALT_PRECISION
- 1);
336 while (x
< 1U << ALT_PRECISION
) {
338 y
-= 1U << ALT_PRECISION
;
341 while (x
>= 2U << ALT_PRECISION
) {
343 y
+= 1U << ALT_PRECISION
;
347 for (size_t i
= 0; i
< ALT_PRECISION
; i
++) {
348 z
= (z
* z
) >> ALT_PRECISION
;
349 if (z
>= 2U << ALT_PRECISION
) {
358 int32_t getALT(uint32_t value
)
360 uint32_t pressurePa
= value
& PRESSURE_MASK
;
361 if (pressurePa
== 0) return 0;
362 uint16_t temperatureK
= ibusTempToK((uint16_t) (value
>> 19));
363 static uint32_t initPressure
= 0;
364 static uint16_t initTemperature
= 0;
365 if (initPressure
<= 0) // use current pressure for ground altitude -> 0
367 initPressure
= pressurePa
;
368 initTemperature
= temperatureK
;
370 int temperature
= (initTemperature
+ temperatureK
) >> 1; //div 2
371 bool tempNegative
= temperature
< 0;
372 if (tempNegative
) temperature
= temperature
* -1;
373 uint64_t helper
= R_DIV_G_MUL_10_Q15
;
374 helper
= helper
* (uint64_t) temperature
;
375 helper
= helper
>> ALT_PRECISION
;
377 uint32_t po_to_p
= (uint32_t)(initPressure
<< (ALT_PRECISION
- 1));
378 po_to_p
= po_to_p
/ pressurePa
;
380 po_to_p
= po_to_p
<< 1;
381 if (po_to_p
== 0) return 0;
382 uint64_t t
= log2fix(po_to_p
) * INV_LOG2_E_Q1DOT31
;
383 int32_t ln
= t
>> 31;
386 if (neg
) ln
= ln
* -1;
387 helper
= helper
* (uint64_t) ln
;
388 helper
= helper
>> ALT_PRECISION
;
389 int result
= (int) helper
;
391 if (neg
^ tempNegative
) result
= result
* -1;