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.
23 struct FrSkySportSensor
{
24 const uint16_t firstId
;
25 const uint16_t lastId
;
28 const TelemetryUnit unit
;
32 const FrSkySportSensor sportSensors
[] = {
33 { RSSI_ID
, RSSI_ID
, 0, ZSTR_RSSI
, UNIT_DB
, 0 },
34 { ADC1_ID
, ADC1_ID
, 0, ZSTR_A1
, UNIT_VOLTS
, 1 },
35 { ADC2_ID
, ADC2_ID
, 0, ZSTR_A2
, UNIT_VOLTS
, 1 },
36 { A3_FIRST_ID
, A3_LAST_ID
, 0, ZSTR_A3
, UNIT_VOLTS
, 2 },
37 { A4_FIRST_ID
, A4_LAST_ID
, 0, ZSTR_A4
, UNIT_VOLTS
, 2 },
38 { BATT_ID
, BATT_ID
, 0, ZSTR_BATT
, UNIT_VOLTS
, 1 },
39 { R9_PWR_ID
, R9_PWR_ID
, 0, ZSTR_R9PW
, UNIT_MILLIWATTS
, 0 },
40 { T1_FIRST_ID
, T1_LAST_ID
, 0, ZSTR_TEMP1
, UNIT_CELSIUS
, 0 },
41 { T2_FIRST_ID
, T2_LAST_ID
, 0, ZSTR_TEMP2
, UNIT_CELSIUS
, 0 },
42 { RPM_FIRST_ID
, RPM_LAST_ID
, 0, ZSTR_RPM
, UNIT_RPMS
, 0 },
43 { FUEL_FIRST_ID
, FUEL_LAST_ID
, 0, ZSTR_FUEL
, UNIT_PERCENT
, 0 },
44 { ALT_FIRST_ID
, ALT_LAST_ID
, 0, ZSTR_ALT
, UNIT_METERS
, 2 },
45 { VARIO_FIRST_ID
, VARIO_LAST_ID
, 0, ZSTR_VSPD
, UNIT_METERS_PER_SECOND
, 2 },
46 { ACCX_FIRST_ID
, ACCX_LAST_ID
, 0, ZSTR_ACCX
, UNIT_G
, 2 },
47 { ACCY_FIRST_ID
, ACCY_LAST_ID
, 0, ZSTR_ACCY
, UNIT_G
, 2 },
48 { ACCZ_FIRST_ID
, ACCZ_LAST_ID
, 0, ZSTR_ACCZ
, UNIT_G
, 2 },
49 { CURR_FIRST_ID
, CURR_LAST_ID
, 0, ZSTR_CURR
, UNIT_AMPS
, 1 },
50 { VFAS_FIRST_ID
, VFAS_LAST_ID
, 0, ZSTR_VFAS
, UNIT_VOLTS
, 2 },
51 { AIR_SPEED_FIRST_ID
, AIR_SPEED_LAST_ID
, 0, ZSTR_ASPD
, UNIT_KTS
, 1 },
52 { GPS_SPEED_FIRST_ID
, GPS_SPEED_LAST_ID
, 0, ZSTR_GSPD
, UNIT_KTS
, 3 },
53 { CELLS_FIRST_ID
, CELLS_LAST_ID
, 0, ZSTR_CELLS
, UNIT_CELLS
, 2 },
54 { GPS_ALT_FIRST_ID
, GPS_ALT_LAST_ID
, 0, ZSTR_GPSALT
, UNIT_METERS
, 2 },
55 { GPS_TIME_DATE_FIRST_ID
, GPS_TIME_DATE_LAST_ID
, 0, ZSTR_GPSDATETIME
, UNIT_DATETIME
, 0 },
56 { GPS_LONG_LATI_FIRST_ID
, GPS_LONG_LATI_LAST_ID
, 0, ZSTR_GPS
, UNIT_GPS
, 0 },
57 { FUEL_QTY_FIRST_ID
, FUEL_QTY_LAST_ID
, 0, ZSTR_FUEL
, UNIT_MILLILITERS
, 2 },
58 { GPS_COURS_FIRST_ID
, GPS_COURS_LAST_ID
, 0, ZSTR_HDG
, UNIT_DEGREE
, 2 },
59 { RBOX_BATT1_FIRST_ID
, RBOX_BATT1_LAST_ID
, 0, ZSTR_BATT1_VOLTAGE
, UNIT_VOLTS
, 3 },
60 { RBOX_BATT2_FIRST_ID
, RBOX_BATT2_LAST_ID
, 0, ZSTR_BATT2_VOLTAGE
, UNIT_VOLTS
, 3 },
61 { RBOX_BATT1_FIRST_ID
, RBOX_BATT1_LAST_ID
, 1, ZSTR_BATT1_CURRENT
, UNIT_AMPS
, 2 },
62 { RBOX_BATT2_FIRST_ID
, RBOX_BATT2_LAST_ID
, 1, ZSTR_BATT2_CURRENT
, UNIT_AMPS
, 2 },
63 { RBOX_CNSP_FIRST_ID
, RBOX_CNSP_LAST_ID
, 0, ZSTR_BATT1_CONSUMPTION
, UNIT_MAH
, 0 },
64 { RBOX_CNSP_FIRST_ID
, RBOX_CNSP_LAST_ID
, 1, ZSTR_BATT2_CONSUMPTION
, UNIT_MAH
, 0 },
65 { RBOX_STATE_FIRST_ID
, RBOX_STATE_LAST_ID
, 0, ZSTR_CHANS_STATE
, UNIT_BITFIELD
, 0 },
66 { RBOX_STATE_FIRST_ID
, RBOX_STATE_LAST_ID
, 1, ZSTR_RB_STATE
, UNIT_BITFIELD
, 0 },
67 { SD1_FIRST_ID
, SD1_LAST_ID
, 0, ZSTR_SD1_CHANNEL
, UNIT_RAW
, 0 },
68 { ESC_POWER_FIRST_ID
, ESC_POWER_LAST_ID
, 0, ZSTR_ESC_VOLTAGE
, UNIT_VOLTS
, 2 },
69 { ESC_POWER_FIRST_ID
, ESC_POWER_LAST_ID
, 1, ZSTR_ESC_CURRENT
, UNIT_AMPS
, 2 },
70 { ESC_RPM_CONS_FIRST_ID
, ESC_RPM_CONS_LAST_ID
, 0, ZSTR_ESC_RPM
, UNIT_RPMS
, 0 },
71 { ESC_RPM_CONS_FIRST_ID
, ESC_RPM_CONS_LAST_ID
, 1, ZSTR_ESC_CONSUMPTION
, UNIT_MAH
, 0 },
72 { ESC_TEMPERATURE_FIRST_ID
, ESC_TEMPERATURE_LAST_ID
, 0, ZSTR_ESC_TEMP
, UNIT_CELSIUS
, 0 },
73 { GASSUIT_TEMP1_FIRST_ID
, GASSUIT_TEMP1_LAST_ID
, 0, ZSTR_GASSUIT_TEMP1
, UNIT_CELSIUS
, 0 },
74 { GASSUIT_TEMP2_FIRST_ID
, GASSUIT_TEMP2_LAST_ID
, 0, ZSTR_GASSUIT_TEMP2
, UNIT_CELSIUS
, 0 },
75 { GASSUIT_SPEED_FIRST_ID
, GASSUIT_SPEED_LAST_ID
, 0, ZSTR_GASSUIT_RPM
, UNIT_RPMS
, 0 },
76 { GASSUIT_RES_VOL_FIRST_ID
, GASSUIT_RES_VOL_LAST_ID
, 0, ZSTR_GASSUIT_RES_VOL
, UNIT_MILLILITERS
, 0 },
77 { GASSUIT_RES_PERC_FIRST_ID
, GASSUIT_RES_PERC_LAST_ID
, 0, ZSTR_GASSUIT_RES_PERC
, UNIT_PERCENT
, 0 },
78 { GASSUIT_FLOW_FIRST_ID
, GASSUIT_FLOW_LAST_ID
, 0, ZSTR_GASSUIT_FLOW
, UNIT_MILLILITERS_PER_MINUTE
, 0 },
79 { GASSUIT_MAX_FLOW_FIRST_ID
, GASSUIT_MAX_FLOW_LAST_ID
, 0, ZSTR_GASSUIT_MAX_FLOW
, UNIT_MILLILITERS_PER_MINUTE
, 0 },
80 { GASSUIT_AVG_FLOW_FIRST_ID
, GASSUIT_AVG_FLOW_LAST_ID
, 0, ZSTR_GASSUIT_AVG_FLOW
, UNIT_MILLILITERS_PER_MINUTE
, 0 },
81 { SBEC_POWER_FIRST_ID
, SBEC_POWER_LAST_ID
, 0, ZSTR_SBEC_VOLTAGE
, UNIT_VOLTS
, 2 },
82 { SBEC_POWER_FIRST_ID
, SBEC_POWER_LAST_ID
, 1, ZSTR_SBEC_CURRENT
, UNIT_AMPS
, 2 },
83 { 0, 0, 0, NULL
, UNIT_RAW
, 0 } // sentinel
86 const FrSkySportSensor
* getFrSkySportSensor(uint16_t id
, uint8_t subId
=0)
88 for (const FrSkySportSensor
* sensor
= sportSensors
; sensor
->firstId
; sensor
++) {
89 if (id
>= sensor
->firstId
&& id
<= sensor
->lastId
&& subId
== sensor
->subId
) {
96 bool checkSportPacket(const uint8_t * packet
)
99 for (int i
=1; i
<FRSKY_SPORT_PACKET_SIZE
; ++i
) {
100 crc
+= packet
[i
]; // 0-1FE
101 crc
+= crc
>> 8; // 0-1FF
102 crc
&= 0x00ff; // 0-FF
104 // TRACE("crc: 0x%02x", crc);
105 return (crc
== 0x00ff);
108 #define SPORT_DATA_U8(packet) (packet[4])
109 #define SPORT_DATA_S32(packet) (*((int32_t *)(packet+4)))
110 #define SPORT_DATA_U32(packet) (*((uint32_t *)(packet+4)))
111 #define HUB_DATA_U16(packet) (*((uint16_t *)(packet+4)))
113 uint16_t servosState
;
116 void sportProcessTelemetryPacket(uint16_t id
, uint8_t subId
, uint8_t instance
, uint32_t data
, TelemetryUnit unit
=UNIT_RAW
)
118 const FrSkySportSensor
* sensor
= getFrSkySportSensor(id
, subId
);
119 uint8_t precision
= 0;
121 if (unit
== UNIT_RAW
)
123 precision
= sensor
->prec
;
125 if (unit
== UNIT_CELLS
) {
126 uint8_t cellsCount
= (data
& 0xF0) >> 4;
127 uint8_t cellIndex
= (data
& 0x0F);
128 uint32_t mask
= (cellsCount
<< 24) + (cellIndex
<< 16);
129 setTelemetryValue(PROTOCOL_TELEMETRY_FRSKY_SPORT
, id
, subId
, instance
, mask
+ (((data
& 0x000FFF00) >> 8) / 5), unit
, precision
);
130 if (cellIndex
+1 < cellsCount
) {
132 setTelemetryValue(PROTOCOL_TELEMETRY_FRSKY_SPORT
, id
, subId
, instance
, mask
+ (((data
& 0xFFF00000) >> 20) / 5), unit
, precision
);
136 setTelemetryValue(PROTOCOL_TELEMETRY_FRSKY_SPORT
, id
, subId
, instance
, data
, unit
, precision
);
140 void sportProcessTelemetryPacket(const uint8_t * packet
)
142 if (!checkSportPacket(packet
)) {
143 TRACE("sportProcessTelemetryPacket(): checksum error ");
144 DUMP(packet
, FRSKY_SPORT_PACKET_SIZE
);
148 sportProcessTelemetryPacketWithoutCrc(TELEMETRY_ENDPOINT_SPORT
, packet
);
151 void sportProcessTelemetryPacketWithoutCrc(uint8_t origin
, const uint8_t * packet
)
153 uint8_t physicalId
= packet
[0] & 0x1F;
154 uint8_t primId
= packet
[1];
155 uint16_t dataId
= *((uint16_t *)(packet
+2));
156 uint32_t data
= SPORT_DATA_S32(packet
);
158 #if defined(BLUETOOTH)
159 if (g_eeGeneral
.bluetoothMode
== BLUETOOTH_TELEMETRY
&& bluetooth
.state
== BLUETOOTH_STATE_CONNECTED
) {
160 bluetooth
.forwardTelemetry(packet
);
164 if (primId
== DATA_FRAME
) {
166 if (origin
== TELEMETRY_ENDPOINT_SPORT
) {
170 uint8_t moduleIndex
= (origin
>> 2);
171 originMask
= 0x01 << moduleIndex
;
173 uint8_t instance
= physicalId
+ (origin
<< 5);
174 if (dataId
== RSSI_ID
) {
175 data
= SPORT_DATA_U8(packet
);
177 telemetryStreaming
= TELEMETRY_TIMEOUT10ms
; // reset counter only if valid packets are being detected
178 telemetryData
.telemetryValid
|= originMask
;
181 telemetryData
.telemetryValid
&= ~originMask
;
182 // one module may send RSSI(0) while the other is still streaming
183 // in this case we don't want to update telemetryData.rssi
186 if (g_model
.rssiSource
) {
187 TelemetrySensor
* sensor
= &g_model
.telemetrySensors
[g_model
.rssiSource
- 1];
188 if (sensor
->isSameInstance(PROTOCOL_TELEMETRY_FRSKY_SPORT
, instance
)) {
189 telemetryData
.rssi
.set(data
);
193 telemetryData
.rssi
.set(data
);
196 else if (dataId
== R9_PWR_ID
) {
197 uint32_t r9pwr
[] = {100, 200, 500, 1000};
198 data
= r9pwr
[SPORT_DATA_U8(packet
) & 0x03];
200 else if (dataId
== XJT_VERSION_ID
) {
201 telemetryData
.xjtVersion
= HUB_DATA_U16(packet
);
202 if (!isRasValueValid()) {
203 telemetryData
.setSwr(origin
>> 2, 0);
206 else if (dataId
== RAS_ID
) {
207 if (isRasValueValid()) {
208 telemetryData
.setSwr(origin
>> 2, SPORT_DATA_U8(packet
));
212 // here we discard the frame if it comes from an origin which has RSSI = 0 (RxBt and RSSI are sent in a loop by the module in some situations)
213 if (TELEMETRY_STREAMING() && (telemetryData
.telemetryValid
& originMask
)/* because when Rx is OFF it happens that some old A1/A2 values are sent from the XJT module*/) {
214 if ((dataId
>> 8) == 0) {
216 processHubPacket(dataId
, HUB_DATA_U16(packet
));
218 else if (!IS_HIDDEN_TELEMETRY_VALUE(dataId
)) {
219 if (dataId
== ADC1_ID
|| dataId
== ADC2_ID
|| dataId
== BATT_ID
|| dataId
== RAS_ID
) {
220 data
= SPORT_DATA_U8(packet
);
223 if (dataId
>= GPS_LONG_LATI_FIRST_ID
&& dataId
<= GPS_LONG_LATI_LAST_ID
) {
224 int32_t value
= (data
& 0x3fffffff);
225 if (data
& (1 << 30))
227 value
= (value
* 5) / 3; // min/10000 => deg/1000000
228 if (data
& (1 << 31))
229 sportProcessTelemetryPacket(dataId
, 0, instance
, value
, UNIT_GPS_LONGITUDE
);
231 sportProcessTelemetryPacket(dataId
, 0, instance
, value
, UNIT_GPS_LATITUDE
);
233 else if (dataId
>= RBOX_BATT1_FIRST_ID
&& dataId
<= RBOX_BATT2_LAST_ID
) {
234 sportProcessTelemetryPacket(dataId
, 0, instance
, data
& 0xffff);
235 sportProcessTelemetryPacket(dataId
, 1, instance
, data
>> 16);
237 else if (dataId
>= RBOX_CNSP_FIRST_ID
&& dataId
<= RBOX_CNSP_LAST_ID
) {
238 sportProcessTelemetryPacket(dataId
, 0, instance
, data
& 0xffff);
239 sportProcessTelemetryPacket(dataId
, 1, instance
, data
>> 16);
241 else if (dataId
>= RBOX_STATE_FIRST_ID
&& dataId
<= RBOX_STATE_LAST_ID
) {
242 bool static isRB10
= false;
243 uint16_t newServosState
;
245 if (servosState
== 0 && (data
& 0xff00) == 0xff00) {
249 newServosState
= data
& 0x00ff; // 8ch only RB10
252 newServosState
= data
& 0xffff;
254 if (newServosState
!= 0 && servosState
== 0) {
255 audioEvent(AU_SERVO_KO
);
257 uint16_t newRboxState
= data
>> 16;
258 if ((newRboxState
& 0x07) && (rboxState
& 0x07) == 0) {
259 audioEvent(AU_RX_OVERLOAD
);
261 servosState
= newServosState
;
262 rboxState
= newRboxState
;
263 sportProcessTelemetryPacket(dataId
, 0, instance
, servosState
);
264 sportProcessTelemetryPacket(dataId
, 1, instance
, rboxState
);
266 else if (dataId
>= ESC_POWER_FIRST_ID
&& dataId
<= ESC_POWER_LAST_ID
) {
267 sportProcessTelemetryPacket(dataId
, 0, instance
, data
& 0xffff);
268 sportProcessTelemetryPacket(dataId
, 1, instance
, data
>> 16);
270 else if (dataId
>= ESC_RPM_CONS_FIRST_ID
&& dataId
<= ESC_RPM_CONS_LAST_ID
) {
271 sportProcessTelemetryPacket(dataId
, 0, instance
, 100 * (data
& 0xffff));
272 sportProcessTelemetryPacket(dataId
, 1, instance
, data
>> 16);
274 else if (dataId
>= ESC_TEMPERATURE_FIRST_ID
&& dataId
<= ESC_TEMPERATURE_LAST_ID
) {
275 sportProcessTelemetryPacket(dataId
, 0, instance
, data
& 0x00ff);
277 else if (dataId
>= SBEC_POWER_FIRST_ID
&& dataId
<= SBEC_POWER_LAST_ID
) {
278 sportProcessTelemetryPacket(dataId
, 0, instance
, (data
& 0xffff) / 10);
279 sportProcessTelemetryPacket(dataId
, 1, instance
, (data
>> 16) / 10);
281 else if (dataId
>= DIY_STREAM_FIRST_ID
&& dataId
<= DIY_STREAM_LAST_ID
) {
283 if (luaInputTelemetryFifo
&& luaInputTelemetryFifo
->hasSpace(sizeof(SportTelemetryPacket
))) {
284 SportTelemetryPacket luaPacket
;
285 luaPacket
.physicalId
= physicalId
;
286 luaPacket
.primId
= primId
;
287 luaPacket
.dataId
= dataId
;
288 luaPacket
.value
= data
;
289 for (uint8_t i
=0; i
<sizeof(SportTelemetryPacket
); i
++) {
290 luaInputTelemetryFifo
->push(luaPacket
.raw
[i
]);
296 sportProcessTelemetryPacket(dataId
, 0, instance
, data
);
302 else if (primId
== 0x32) {
303 if (luaInputTelemetryFifo
&& luaInputTelemetryFifo
->hasSpace(sizeof(SportTelemetryPacket
))) {
304 SportTelemetryPacket luaPacket
;
305 luaPacket
.physicalId
= physicalId
;
306 luaPacket
.primId
= primId
;
307 luaPacket
.dataId
= dataId
;
308 luaPacket
.value
= data
;
309 for (uint8_t i
=0; i
<sizeof(SportTelemetryPacket
); i
++) {
310 luaInputTelemetryFifo
->push(luaPacket
.raw
[i
]);
317 void frskySportSetDefault(int index
, uint16_t id
, uint8_t subId
, uint8_t instance
)
319 TelemetrySensor
& telemetrySensor
= g_model
.telemetrySensors
[index
];
321 telemetrySensor
.id
= id
;
322 telemetrySensor
.subId
= subId
;
323 telemetrySensor
.instance
= instance
;
325 const FrSkySportSensor
* sensor
= getFrSkySportSensor(id
, subId
);
327 TelemetryUnit unit
= sensor
->unit
;
328 uint8_t prec
= min
<uint8_t>(2, sensor
->prec
);
329 telemetrySensor
.init(sensor
->name
, unit
, prec
);
330 if (id
>= ADC1_ID
&& id
<= BATT_ID
) {
331 telemetrySensor
.custom
.ratio
= 132;
332 telemetrySensor
.filter
= 1;
334 else if (id
>= CURR_FIRST_ID
&& id
<= CURR_LAST_ID
) {
335 telemetrySensor
.onlyPositive
= 1;
337 else if (id
>= ALT_FIRST_ID
&& id
<= ALT_LAST_ID
) {
338 telemetrySensor
.autoOffset
= 1;
340 if (unit
== UNIT_RPMS
) {
341 telemetrySensor
.custom
.ratio
= 1;
342 telemetrySensor
.custom
.offset
= 1;
344 else if (unit
== UNIT_METERS
) {
345 if (IS_IMPERIAL_ENABLE()) {
346 telemetrySensor
.unit
= UNIT_FEET
;
349 else if (unit
== UNIT_GPS_LATITUDE
|| unit
== UNIT_GPS_LONGITUDE
) {
350 telemetrySensor
.unit
= UNIT_GPS
;
354 telemetrySensor
.init(id
);
357 storageDirty(EE_MODEL
);