mirror of
https://github.com/opentx/opentx.git
synced 2025-07-18 22:05:10 +03:00
Multi telemetry : add Hitec and improve FlySky (#6835)
* Add Hitec and update Flysky telemetry * Travis (and sky9x) doesn't like cheating ! * Cosmetics
This commit is contained in:
parent
97743ed2f6
commit
b8fa8438ba
21 changed files with 1001 additions and 152 deletions
|
@ -264,6 +264,7 @@ enum TelemetryProtocol
|
|||
PROTOCOL_TELEMETRY_CROSSFIRE,
|
||||
PROTOCOL_TELEMETRY_SPEKTRUM,
|
||||
PROTOCOL_TELEMETRY_FLYSKY_IBUS,
|
||||
PROTOCOL_TELEMETRY_HITEC,
|
||||
PROTOCOL_TELEMETRY_MULTIMODULE,
|
||||
PROTOCOL_TELEMETRY_LAST=PROTOCOL_TELEMETRY_MULTIMODULE,
|
||||
PROTOCOL_TELEMETRY_LUA
|
||||
|
|
|
@ -143,7 +143,7 @@ endif()
|
|||
|
||||
if(MULTIMODULE)
|
||||
add_definitions(-DMULTIMODULE)
|
||||
set(SRC ${SRC} pulses/multi.cpp telemetry/spektrum.cpp telemetry/flysky_ibus.cpp telemetry/multi.cpp io/multi_firmware_update.cpp)
|
||||
set(SRC ${SRC} pulses/multi.cpp telemetry/spektrum.cpp telemetry/flysky_ibus.cpp telemetry/hitec.cpp telemetry/multi.cpp io/multi_firmware_update.cpp)
|
||||
endif()
|
||||
|
||||
if(CROSSFIRE)
|
||||
|
|
|
@ -16,127 +16,253 @@
|
|||
#include "opentx.h"
|
||||
|
||||
/*
|
||||
* Telemetry format from goebish/Deviation/flysky_afhds2a_a7105.c
|
||||
*
|
||||
* format from RX:
|
||||
* AA | TXID | RXID | sensor id | sensor # | value 16 bit big endian | sensor id ......
|
||||
* max 7 sensors per packet
|
||||
*
|
||||
* TXID + RXID are already skipped in MULTI module to save memory+transmission time, format from Multi is:
|
||||
* AA | TX_RSSI | sensor ...
|
||||
*
|
||||
* OpenTX Mapping
|
||||
*
|
||||
* instance = sensor id
|
||||
*
|
||||
* Additional sensors from https://github.com/cleanflight/cleanflight/blob/master/src/main/telemetry/ibus.c
|
||||
*
|
||||
* AA or AC | TX_RSSI | sensor ...
|
||||
*/
|
||||
|
||||
|
||||
#define FLYSKY_TELEMETRY_LENGTH (2+7*4)
|
||||
#define FLYSKY_TELEMETRY_LENGTH (2+7*4) // Should it be 2+7*6???
|
||||
#define ALT_PRECISION 15
|
||||
#define R_DIV_G_MUL_10_Q15 UINT64_C(9591506)
|
||||
#define INV_LOG2_E_Q1DOT31 UINT64_C(0x58b90bfc) // Inverse log base 2 of e
|
||||
#define PRESSURE_MASK 0x7FFFF
|
||||
|
||||
struct FlySkySensor {
|
||||
struct FlySkySensor
|
||||
{
|
||||
const uint16_t id;
|
||||
const char *name;
|
||||
const char * name;
|
||||
const TelemetryUnit unit;
|
||||
const uint8_t precision;
|
||||
};
|
||||
|
||||
#define TX_RSSI_ID 300 // Pseudo id outside 1 byte range of FlySky sensors
|
||||
#define FS_ID_TEMP 0x01
|
||||
#define FS_ID_SNR 0xFA
|
||||
#define FS_ID_NOISE 0xFB
|
||||
#define FS_ID_RSSI 0xFC
|
||||
#define FS_ID_ERR 0xFE
|
||||
// telemetry sensors ID
|
||||
enum
|
||||
{
|
||||
AFHDS2A_ID_VOLTAGE = 0x00, // Internal Voltage
|
||||
AFHDS2A_ID_TEMPERATURE = 0x01, // Temperature
|
||||
AFHDS2A_ID_MOT = 0x02, // RPM
|
||||
AFHDS2A_ID_EXTV = 0x03, // External Voltage
|
||||
AFHDS2A_ID_CELL_VOLTAGE = 0x04, // Avg Cell voltage
|
||||
AFHDS2A_ID_BAT_CURR = 0x05, // battery current A * 100
|
||||
AFHDS2A_ID_FUEL = 0x06, // remaining battery percentage / mah drawn otherwise or fuel level no unit!
|
||||
AFHDS2A_ID_RPM = 0x07, // throttle value / battery capacity
|
||||
AFHDS2A_ID_CMP_HEAD = 0x08, // Heading 0..360 deg, 0=north 2bytes
|
||||
AFHDS2A_ID_CLIMB_RATE = 0x09, // 2 bytes m/s *100 signed
|
||||
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
|
||||
AFHDS2A_ID_GPS_STATUS = 0x0B, // 2 bytes
|
||||
AFHDS2A_ID_ACC_X = 0x0C, // 2 bytes m/s *100 signed
|
||||
AFHDS2A_ID_ACC_Y = 0x0D, // 2 bytes m/s *100 signed
|
||||
AFHDS2A_ID_ACC_Z = 0x0E, // 2 bytes m/s *100 signed
|
||||
AFHDS2A_ID_ROLL = 0x0F, // 2 bytes deg *100 signed
|
||||
AFHDS2A_ID_PITCH = 0x10, // 2 bytes deg *100 signed
|
||||
AFHDS2A_ID_YAW = 0x11, // 2 bytes deg *100 signed
|
||||
AFHDS2A_ID_VERTICAL_SPEED = 0x12, // 2 bytes m/s *100 signed
|
||||
AFHDS2A_ID_GROUND_SPEED = 0x13, // 2 bytes m/s *100 different unit than build-in sensor
|
||||
AFHDS2A_ID_GPS_DIST = 0x14, // 2 bytes distance from home m unsigned
|
||||
AFHDS2A_ID_ARMED = 0x15, // 2 bytes
|
||||
AFHDS2A_ID_FLIGHT_MODE = 0x16, // 2 bytes
|
||||
|
||||
const FlySkySensor flySkySensors[] = {
|
||||
AFHDS2A_ID_PRES = 0x41, // Pressure
|
||||
AFHDS2A_ID_ODO1 = 0x7C, // Odometer1
|
||||
AFHDS2A_ID_ODO2 = 0x7D, // Odometer2
|
||||
AFHDS2A_ID_SPE = 0x7E, // Speed 2 bytes km/h
|
||||
AFHDS2A_ID_TX_V = 0x7F, // TX Voltage
|
||||
|
||||
// RX Voltage (remapped, really 0x0)
|
||||
{0x100, ZSTR_A1, UNIT_VOLTS, 2},
|
||||
// Temperature
|
||||
{FS_ID_TEMP, ZSTR_TEMP1, UNIT_CELSIUS, 1},
|
||||
// RPM
|
||||
{0x02, ZSTR_RPM, UNIT_RAW, 0},
|
||||
// External voltage
|
||||
{0x03, ZSTR_A3, UNIT_VOLTS, 2},
|
||||
// RX SNR
|
||||
{FS_ID_SNR, ZSTR_RX_SNR, UNIT_DB, 0},
|
||||
// RX Noise
|
||||
{FS_ID_NOISE, ZSTR_RX_NOISE, UNIT_DB, 0},
|
||||
// RX RSSI (0xfc)
|
||||
{FS_ID_RSSI, ZSTR_RSSI, UNIT_DB, 0},
|
||||
// RX error rate
|
||||
{FS_ID_ERR, ZSTR_RX_QUALITY, UNIT_RAW, 0},
|
||||
// 0xff is an unused sensor slot
|
||||
// Pseudo sensor for TRSSI
|
||||
{TX_RSSI_ID, ZSTR_TX_RSSI, UNIT_RAW, 0},
|
||||
// sentinel
|
||||
{0x00, NULL, UNIT_RAW, 0},
|
||||
AFHDS2A_ID_GPS_LAT = 0x80, // 4bytes signed WGS84 in degrees * 1E7
|
||||
AFHDS2A_ID_GPS_LON = 0x81, // 4bytes signed WGS84 in degrees * 1E7
|
||||
AFHDS2A_ID_GPS_ALT = 0x82, // 4bytes signed!!! GPS alt m*100
|
||||
AFHDS2A_ID_ALT = 0x83, // 4bytes signed!!! Alt m*100
|
||||
AFHDS2A_ID_S84 = 0x84,
|
||||
AFHDS2A_ID_S85 = 0x85,
|
||||
AFHDS2A_ID_S86 = 0x86,
|
||||
AFHDS2A_ID_S87 = 0x87,
|
||||
AFHDS2A_ID_S88 = 0x88,
|
||||
AFHDS2A_ID_S89 = 0x89,
|
||||
AFHDS2A_ID_S8a = 0x8A,
|
||||
|
||||
AFHDS2A_ID_ALT_FLYSKY = 0xF9, // Altitude 2 bytes signed in m - used in FlySky native TX
|
||||
AFHDS2A_ID_RX_SNR = 0xFA, // SNR
|
||||
AFHDS2A_ID_RX_NOISE = 0xFB, // Noise
|
||||
AFHDS2A_ID_RX_RSSI = 0xFC, // RSSI
|
||||
AFHDS2A_ID_RX_ERR_RATE = 0xFE, // Error rate
|
||||
AFHDS2A_ID_END = 0xFF,
|
||||
|
||||
// AC type telemetry with multiple values in one packet
|
||||
AFHDS2A_ID_GPS_FULL = 0xFD,
|
||||
AFHDS2A_ID_VOLT_FULL = 0xF0,
|
||||
AFHDS2A_ID_ACC_FULL = 0xEF,
|
||||
TX_RSSI_ID = 0x200, // Pseudo id outside 1 byte range of FlySky sensors
|
||||
};
|
||||
|
||||
static void processFlySkySensor(const uint8_t *packet)
|
||||
const FlySkySensor flySkySensors[] = {
|
||||
{AFHDS2A_ID_VOLTAGE | 0x100, ZSTR_A1, UNIT_VOLTS, 2}, // RX Voltage (remapped, really 0x0)
|
||||
{AFHDS2A_ID_TEMPERATURE, ZSTR_TEMP1, UNIT_CELSIUS, 1}, // Temperature
|
||||
{AFHDS2A_ID_MOT, ZSTR_RPM, UNIT_RAW, 0}, // RPM
|
||||
{AFHDS2A_ID_EXTV, ZSTR_A3, UNIT_VOLTS, 2}, // External voltage
|
||||
{AFHDS2A_ID_CELL_VOLTAGE, ZSTR_CELLS, UNIT_VOLTS, 2}, // Avg Cell voltage
|
||||
{AFHDS2A_ID_BAT_CURR, ZSTR_CURR, UNIT_AMPS, 2}, // battery current A * 100
|
||||
{AFHDS2A_ID_FUEL, ZSTR_CAPACITY, UNIT_RAW, 0}, // remaining battery percentage / mah drawn otherwise or fuel level no unit!
|
||||
{AFHDS2A_ID_RPM, ZSTR_RPM, UNIT_RAW, 0}, // throttle value / battery capacity
|
||||
{AFHDS2A_ID_CMP_HEAD, ZSTR_HDG, UNIT_DEGREE, 0}, // Heading 0..360 deg, 0=north 2bytes
|
||||
{AFHDS2A_ID_CLIMB_RATE, ZSTR_VSPD, UNIT_METERS_PER_SECOND, 2}, // 2 bytes m/s *100
|
||||
{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
|
||||
{AFHDS2A_ID_GPS_STATUS, ZSTR_SATELLITES, UNIT_RAW, 0}, // 2 bytes
|
||||
{AFHDS2A_ID_ACC_X, ZSTR_ACCX, UNIT_METERS_PER_SECOND, 2}, // 2 bytes m/s *100 signed
|
||||
{AFHDS2A_ID_ACC_Y, ZSTR_ACCY, UNIT_METERS_PER_SECOND, 2}, // 2 bytes m/s *100 signed
|
||||
{AFHDS2A_ID_ACC_Z, ZSTR_ACCZ, UNIT_METERS_PER_SECOND, 2}, // 2 bytes m/s *100 signed
|
||||
{AFHDS2A_ID_ROLL, ZSTR_ROLL, UNIT_DEGREE, 2}, // 2 bytes deg *100 signed
|
||||
{AFHDS2A_ID_PITCH, ZSTR_PITCH, UNIT_DEGREE, 2}, // 2 bytes deg *100 signed
|
||||
{AFHDS2A_ID_YAW, ZSTR_YAW, UNIT_DEGREE, 2}, // 2 bytes deg *100 signed
|
||||
{AFHDS2A_ID_VERTICAL_SPEED, ZSTR_VSPD, UNIT_METERS_PER_SECOND, 2}, // 2 bytes m/s *100
|
||||
{AFHDS2A_ID_GROUND_SPEED, ZSTR_GSPD, UNIT_METERS_PER_SECOND, 2}, // 2 bytes m/s *100 different unit than build-in sensor
|
||||
{AFHDS2A_ID_GPS_DIST, ZSTR_DIST, UNIT_METERS, 0}, // 2 bytes dist from home m unsigned
|
||||
{AFHDS2A_ID_ARMED, ZSTR_ARM, UNIT_RAW, 0}, // 2 bytes
|
||||
{AFHDS2A_ID_FLIGHT_MODE, ZSTR_FLIGHT_MODE, UNIT_RAW, 0}, // 2 bytes index
|
||||
{AFHDS2A_ID_PRES, ZSTR_PRES, UNIT_RAW, 2}, // 4 bytes In fact Temperature + Pressure -> Altitude
|
||||
{AFHDS2A_ID_PRES | 0x100, ZSTR_TEMP2, UNIT_CELSIUS, 1}, // 2 bytes Temperature
|
||||
{AFHDS2A_ID_ODO1, ZSTR_ODO1, UNIT_METERS, 2}, // 2 bytes Odometer1 -- some magic with 330 needed
|
||||
{AFHDS2A_ID_ODO2, ZSTR_ODO2, UNIT_METERS, 2}, // 2 bytes Odometer2 -- some magic with 330 needed
|
||||
{AFHDS2A_ID_SPE, ZSTR_ASPD, UNIT_KMH, 2}, // 2 bytes Speed km/h -- some magic with 330 needed
|
||||
{AFHDS2A_ID_TX_V, ZSTR_TXV, UNIT_VOLTS, 2}, // TX Voltage
|
||||
{AFHDS2A_ID_GPS_LAT, ZSTR_GPS, UNIT_RAW, 7}, // 4 bytes signed WGS84 in degrees * 1E7
|
||||
{AFHDS2A_ID_GPS_LON, ZSTR_GPS, UNIT_RAW, 7}, // 4 bytes signed WGS84 in degrees * 1E7
|
||||
{AFHDS2A_ID_GPS_ALT, ZSTR_GPSALT, UNIT_METERS, 2}, // 4 bytes signed GPS alt m*100
|
||||
{AFHDS2A_ID_ALT, ZSTR_ALT, UNIT_METERS, 2}, // 4 bytes signed Alt m*100
|
||||
|
||||
{AFHDS2A_ID_RX_SNR, ZSTR_RX_SNR, UNIT_DB, 0}, // RX SNR
|
||||
{AFHDS2A_ID_RX_NOISE, ZSTR_RX_NOISE, UNIT_DB, 0}, // RX Noise
|
||||
{AFHDS2A_ID_RX_RSSI, ZSTR_RSSI, UNIT_DB, 0}, // RX RSSI (0xfc)
|
||||
{AFHDS2A_ID_RX_ERR_RATE, ZSTR_RX_QUALITY, UNIT_RAW, 0}, // RX error rate
|
||||
{TX_RSSI_ID, ZSTR_TX_RSSI, UNIT_RAW, 0}, // Pseudo sensor for TRSSI
|
||||
|
||||
{0x00, NULL, UNIT_RAW, 0}, // sentinel
|
||||
};
|
||||
|
||||
int32_t getALT(uint32_t value);
|
||||
|
||||
static void processFlySkySensor(const uint8_t * packet, uint8_t type)
|
||||
{
|
||||
uint8_t buffer[8];
|
||||
uint16_t id = packet[0];
|
||||
const uint8_t instance = packet[1];
|
||||
int32_t value = (packet[3] << 8) + packet[2];
|
||||
int32_t value;
|
||||
|
||||
if (id == 0xff) {
|
||||
// No sensor
|
||||
return;
|
||||
//Load most likely value
|
||||
if (type == 0xAA)
|
||||
value = (packet[3] << 8) | packet[2];
|
||||
else
|
||||
value = (packet[6] << 24) | (packet[5] << 16) | (packet[4] << 8) | packet[3];
|
||||
|
||||
if (id == 0) id = 0x100; // Some part of OpenTX does not like sensor with id and instance 0, remap to 0x100
|
||||
|
||||
if (id == AFHDS2A_ID_RX_NOISE || id == AFHDS2A_ID_RX_RSSI) {
|
||||
value = 135 - value;
|
||||
}
|
||||
|
||||
if (id == 0) {
|
||||
// Some part of OpenTX does not like sensor with id and instance 0, remap to 0x100
|
||||
id = 0x100;
|
||||
}
|
||||
|
||||
if (id == FS_ID_ERR) { // ERR RATE, displayed RQLy and used as RSSI
|
||||
else if (id == AFHDS2A_ID_RX_ERR_RATE) {
|
||||
value = 100 - value;
|
||||
telemetryData.rssi.set(value);
|
||||
if (value > 0)
|
||||
telemetryStreaming = TELEMETRY_TIMEOUT10ms;
|
||||
if (value > 0) telemetryStreaming = TELEMETRY_TIMEOUT10ms;
|
||||
}
|
||||
|
||||
for (const FlySkySensor * sensor = flySkySensors; sensor->id; sensor++) {
|
||||
// Extract value, skip header
|
||||
if (sensor->id == id) {
|
||||
// The Noise and Signal sensors that are specified in dB send the absolute value
|
||||
if (id == FS_ID_NOISE || id == FS_ID_RSSI)
|
||||
value = 135 - value;
|
||||
else if (id == FS_ID_SNR) {
|
||||
if (value > 0) {
|
||||
value += 20;
|
||||
}
|
||||
}
|
||||
else if (id == FS_ID_TEMP)
|
||||
// Temperature sensors have 40 degree offset
|
||||
value -= 400;
|
||||
else if (sensor->unit == UNIT_VOLTS)
|
||||
// Voltage types are signed 16bit integers
|
||||
value = (int16_t)value;
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS, id, 0, instance, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
else if (id == AFHDS2A_ID_PRES && value) {
|
||||
// Extract temperature to a new sensor
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS, id | 0x100, 0, instance, ((value >> 19) - 400), UNIT_CELSIUS, 1);
|
||||
// Extract alt to a new sensor
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS, AFHDS2A_ID_ALT, 0, instance, getALT(value), UNIT_METERS, 2);
|
||||
value &= PRESSURE_MASK;
|
||||
}
|
||||
else if ((id >= AFHDS2A_ID_ACC_X && id <= AFHDS2A_ID_VERTICAL_SPEED) || id == AFHDS2A_ID_CLIMB_RATE) {
|
||||
value = (int16_t) value; // Signed value
|
||||
}
|
||||
else if (id == AFHDS2A_ID_GPS_STATUS) {
|
||||
value = value >> 8;
|
||||
}
|
||||
else if (id == AFHDS2A_ID_GPS_FULL) {
|
||||
//(AC FRAME)[ID][inst][size][fix][sats][LAT]x4[LON]x4[ALT]x4
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS, AFHDS2A_ID_GPS_STATUS, 0, instance, packet[4], UNIT_RAW, 0);
|
||||
for (uint8_t sensorID = AFHDS2A_ID_GPS_LAT; sensorID <= AFHDS2A_ID_GPS_ALT; sensorID++) {
|
||||
int index = 5 + (sensorID - AFHDS2A_ID_GPS_LAT) * 4;
|
||||
buffer[0] = sensorID;
|
||||
buffer[1] = instance;
|
||||
buffer[2] = 4;
|
||||
memcpy(buffer + 3, packet + index, 4);
|
||||
processFlySkySensor(buffer, 0xAC);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (id == AFHDS2A_ID_VOLT_FULL) {
|
||||
//(AC FRAME)[ID][inst][size][ACC_X]x2[ACC_Y]x2[ACC_Z]x2[ROLL]x2[PITCH]x2[YAW]x2
|
||||
for (uint8_t sensorID = AFHDS2A_ID_EXTV; sensorID <= AFHDS2A_ID_RPM; sensorID++) {
|
||||
int index = 3 + (sensorID - AFHDS2A_ID_EXTV) * 2;
|
||||
buffer[0] = sensorID;
|
||||
buffer[1] = instance;
|
||||
buffer[2] = packet[index];
|
||||
buffer[3] = packet[index + 1];
|
||||
processFlySkySensor(buffer, 0xAA);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (id == AFHDS2A_ID_ACC_FULL) {
|
||||
//(AC FRAME)[ID][inst][size]
|
||||
for (uint8_t sensorID = AFHDS2A_ID_ACC_X; sensorID <= AFHDS2A_ID_YAW; sensorID++) {
|
||||
int index = 3 + (sensorID - AFHDS2A_ID_ACC_X) * 2;
|
||||
buffer[0] = sensorID;
|
||||
buffer[1] = instance;
|
||||
buffer[2] = packet[index];
|
||||
buffer[3] = packet[index + 1];
|
||||
processFlySkySensor(buffer, 0xAA);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const FlySkySensor * sensor = flySkySensors; sensor->id; sensor++) {
|
||||
if (sensor->id != id) continue;
|
||||
if (sensor->unit == UNIT_CELSIUS) value -= 400; // Temperature sensors have 40 degree offset
|
||||
else if (sensor->unit == UNIT_VOLTS) value = (uint16_t) value; // Voltage types are unsigned 16bit integers
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS, id, 0, instance, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
}
|
||||
//unknown
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS, id, 0, instance, value, UNIT_RAW, 0);
|
||||
}
|
||||
|
||||
void processFlySkyPacket(const uint8_t *packet)
|
||||
void processFlySkyPacket(const uint8_t * packet)
|
||||
{
|
||||
// Set TX RSSI Value, reverse MULTIs scaling
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS, TX_RSSI_ID, 0, 0, packet[0], UNIT_RAW, 0);
|
||||
|
||||
for (int sensor = 0; sensor < 7; sensor++) {
|
||||
int index = 1 + (4 * sensor);
|
||||
processFlySkySensor(packet+index);
|
||||
const uint8_t * buffer = packet + 1;
|
||||
int sesnor = 0;
|
||||
while (sesnor++ < 7) {
|
||||
if (*buffer == AFHDS2A_ID_END) break;
|
||||
processFlySkySensor(buffer, 0xAA);
|
||||
buffer += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void processFlySkyTelemetryData(uint8_t data, uint8_t* rxBuffer, uint8_t& rxBufferCount)
|
||||
void processFlySkyPacketAC(const uint8_t * packet)
|
||||
{
|
||||
if (rxBufferCount == 0 && data != 0xAA) {
|
||||
// Set TX RSSI Value, reverse MULTIs scaling
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_FLYSKY_IBUS, TX_RSSI_ID, 0, 0, packet[0], UNIT_RAW, 0);
|
||||
const uint8_t * buffer = packet + 1;
|
||||
while (buffer - packet < 26) //28 + 1(multi TX rssi) - 3(ac header)
|
||||
{
|
||||
if (*buffer == AFHDS2A_ID_END) break;
|
||||
uint8_t size = buffer[2];
|
||||
processFlySkySensor(buffer, 0xAC);
|
||||
buffer += size + 3;
|
||||
}
|
||||
}
|
||||
|
||||
void processFlySkyTelemetryData(uint8_t data, uint8_t * rxBuffer, uint8_t &rxBufferCount)
|
||||
{
|
||||
if (rxBufferCount == 0)
|
||||
return;
|
||||
|
||||
if (data == 0xAA || data == 0xAC) {
|
||||
TRACE("[IBUS] Packet 0x%02X", data);
|
||||
}
|
||||
else {
|
||||
TRACE("[IBUS] invalid start byte 0x%02X", data);
|
||||
rxBufferCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -148,24 +274,23 @@ void processFlySkyTelemetryData(uint8_t data, uint8_t* rxBuffer, uint8_t& rxBuff
|
|||
rxBufferCount = 0;
|
||||
}
|
||||
|
||||
|
||||
if (rxBufferCount >= FLYSKY_TELEMETRY_LENGTH) {
|
||||
// debug print the content of the packets
|
||||
#if 0
|
||||
debugPrintf("[IBUS] Packet 0x%02X rssi 0x%02X: ",
|
||||
rxBuffer[0], rxBuffer[1]);
|
||||
debugPrintf(", rssi 0x%02X: ", rxBuffer[1]);
|
||||
for (int i=0; i<7; i++) {
|
||||
debugPrintf("[%02X %02X %02X%02X] ", rxBuffer[i*4+2], rxBuffer[i*4 + 3],
|
||||
rxBuffer[i*4 + 4], rxBuffer[i*4 + 5]);
|
||||
}
|
||||
debugPrintf("\r\n");
|
||||
#endif
|
||||
processFlySkyPacket(rxBuffer+1);
|
||||
if (data == 0xAA) processFlySkyPacket(rxBuffer + 1);
|
||||
else if (data == 0xAC) processFlySkyPacketAC(rxBuffer + 1);
|
||||
rxBufferCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const FlySkySensor *getFlySkySensor(uint16_t id)
|
||||
const FlySkySensor * getFlySkySensor(uint16_t id)
|
||||
{
|
||||
for (const FlySkySensor * sensor = flySkySensors; sensor->id; sensor++) {
|
||||
if (id == sensor->id)
|
||||
|
@ -181,7 +306,7 @@ void flySkySetDefault(int index, uint16_t id, uint8_t subId, uint8_t instance)
|
|||
telemetrySensor.subId = subId;
|
||||
telemetrySensor.instance = instance;
|
||||
|
||||
const FlySkySensor *sensor = getFlySkySensor(id);
|
||||
const FlySkySensor * sensor = getFlySkySensor(id);
|
||||
if (sensor) {
|
||||
TelemetryUnit unit = sensor->unit;
|
||||
uint8_t prec = min<uint8_t>(2, sensor->precision);
|
||||
|
@ -198,3 +323,71 @@ void flySkySetDefault(int index, uint16_t id, uint8_t subId, uint8_t instance)
|
|||
|
||||
storageDirty(EE_MODEL);
|
||||
}
|
||||
|
||||
uint16_t ibusTempToK(int16_t tempertureIbus)
|
||||
{
|
||||
return (uint16_t) (tempertureIbus - 400) + 2731;
|
||||
}
|
||||
|
||||
int32_t log2fix(uint32_t x)
|
||||
{
|
||||
int32_t b = 1U << (ALT_PRECISION - 1);
|
||||
int32_t y = 0;
|
||||
while (x < 1U << ALT_PRECISION) {
|
||||
x <<= 1;
|
||||
y -= 1U << ALT_PRECISION;
|
||||
}
|
||||
|
||||
while (x >= 2U << ALT_PRECISION) {
|
||||
x >>= 1;
|
||||
y += 1U << ALT_PRECISION;
|
||||
}
|
||||
|
||||
uint64_t z = x;
|
||||
for (size_t i = 0; i < ALT_PRECISION; i++) {
|
||||
z = (z * z) >> ALT_PRECISION;
|
||||
if (z >= 2U << ALT_PRECISION) {
|
||||
z >>= 1;
|
||||
y += b;
|
||||
}
|
||||
b >>= 1;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
int32_t getALT(uint32_t value)
|
||||
{
|
||||
uint32_t pressurePa = value & PRESSURE_MASK;
|
||||
if (pressurePa == 0) return 0;
|
||||
uint16_t temperatureK = ibusTempToK((uint16_t) (value >> 19));
|
||||
static uint32_t initPressure = 0;
|
||||
static uint16_t initTemperature = 0;
|
||||
if (initPressure <= 0) // use current pressure for ground altitude -> 0
|
||||
{
|
||||
initPressure = pressurePa;
|
||||
initTemperature = temperatureK;
|
||||
}
|
||||
int temperature = (initTemperature + temperatureK) >> 1; //div 2
|
||||
bool tempNegative = temperature < 0;
|
||||
if (tempNegative) temperature = temperature * -1;
|
||||
uint64_t helper = R_DIV_G_MUL_10_Q15;
|
||||
helper = helper * (uint64_t) temperature;
|
||||
helper = helper >> ALT_PRECISION;
|
||||
|
||||
uint32_t po_to_p = (uint32_t)(initPressure << (ALT_PRECISION - 1));
|
||||
po_to_p = po_to_p / pressurePa;
|
||||
//shift missing bit
|
||||
po_to_p = po_to_p << 1;
|
||||
if (po_to_p == 0) return 0;
|
||||
uint64_t t = log2fix(po_to_p) * INV_LOG2_E_Q1DOT31;
|
||||
int32_t ln = t >> 31;
|
||||
|
||||
bool neg = ln < 0;
|
||||
if (neg) ln = ln * -1;
|
||||
helper = helper * (uint64_t) ln;
|
||||
helper = helper >> ALT_PRECISION;
|
||||
int result = (int) helper;
|
||||
|
||||
if (neg ^ tempNegative) result = result * -1;
|
||||
return result;
|
||||
}
|
|
@ -21,11 +21,14 @@
|
|||
#ifndef _FLYSKY_IBUS_H
|
||||
#define _FLYSKY_IBUS_H
|
||||
|
||||
void processFlySkyTelemetryData(uint8_t data, uint8_t* rxBuffer, uint8_t& rxBufferCount);
|
||||
void processFlySkyTelemetryData(uint8_t data, uint8_t * rxBuffer, uint8_t &rxBufferCount);
|
||||
|
||||
void flySkySetDefault(int index, uint16_t id, uint8_t subId, uint8_t instance);
|
||||
|
||||
// Used by multi protocol
|
||||
void processFlySkyPacket(const uint8_t *packet);
|
||||
void processFlySkyPacket(const uint8_t * packet);
|
||||
|
||||
void processFlySkyPacketAC(const uint8_t * packet);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
433
radio/src/telemetry/hitec.cpp
Normal file
433
radio/src/telemetry/hitec.cpp
Normal file
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* Copyright (C) OpenTX
|
||||
*
|
||||
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "opentx.h"
|
||||
|
||||
/* Full telemetry
|
||||
packet[0] = TX RSSI value
|
||||
packet[1] = TX LQI value
|
||||
packet[2] = frame number
|
||||
packet[3-7] telemetry data
|
||||
|
||||
The frame number takes the following values: 0x00, 0x11, 0x12, ..., 0x1C. The frames can be present or not, they also do not have to follow each others.
|
||||
Here is a description of the telemetry data for each frame number:
|
||||
- frame 0x00
|
||||
data byte 0 -> 0x00 unknown
|
||||
data byte 1 -> 0x00 unknown
|
||||
data byte 2 -> 0x00 unknown
|
||||
data byte 3 -> RX Batt Volt_H
|
||||
data byte 4 -> RX Batt Volt_L => RX Batt=(Volt_H*256+Volt_L)/28
|
||||
- frame 0x11
|
||||
data byte 0 -> 0xAF start of frame
|
||||
data byte 1 -> 0x00 unknown
|
||||
data byte 2 -> 0x2D station type 0x2D=standard station nitro or electric, 0xAC=advanced station
|
||||
data byte 3 -> RX Batt Volt_H
|
||||
data byte 4 -> RX Batt Volt_L => RX Batt=(Volt_H*256+Volt_L)/28
|
||||
- frame 0x12
|
||||
data byte 0 -> Lat_sec_H GPS : latitude second
|
||||
data byte 1 -> Lat_sec_L signed int : 1/100 of second
|
||||
data byte 2 -> Lat_deg_min_H GPS : latitude degree.minute
|
||||
data byte 3 -> Lat_deg_min_L signed int : +=North, - = south
|
||||
data byte 4 -> Time_second GPS Time
|
||||
- frame 0x13
|
||||
data byte 0 -> GPS Longitude second
|
||||
data byte 1 -> signed int : 1/100 of second
|
||||
data byte 2 -> GPS Longitude degree.minute
|
||||
data byte 3 -> signed int : +=Est, - = west
|
||||
data byte 4 -> Temp2 Temperature2=Temp2-40°C
|
||||
- frame 0x14
|
||||
data byte 0 -> Speed_H
|
||||
data byte 1 -> Speed_L GPS Speed=Speed_H*256+Speed_L km/h
|
||||
data byte 2 -> Alti_sea_H
|
||||
data byte 3 -> Alti_sea_L GPS Altitude=Alti_sea_H*256+Alti_sea_L m
|
||||
data byte 4 -> Temp1 Temperature1=Temp1-40°C
|
||||
- frame 0x15
|
||||
data byte 0 -> FUEL
|
||||
data byte 1 -> RPM1_L
|
||||
data byte 2 -> RPM1_H RPM1=RPM1_H*256+RPM1_L
|
||||
data byte 3 -> RPM2_L
|
||||
data byte 4 -> RPM2_H RPM2=RPM2_H*256+RPM2_L
|
||||
- frame 0x16
|
||||
data byte 0 -> Date_year GPS Date
|
||||
data byte 1 -> Date_month
|
||||
data byte 2 -> Date_day
|
||||
data byte 3 -> Time_hour GPS Time
|
||||
data byte 4 -> Time_min
|
||||
- frame 0x17
|
||||
data byte 0 -> COURSEH
|
||||
data byte 1 -> COURSEL GPS heading = COURSEH*256+COURSEL in degrees
|
||||
data byte 2 -> Count GPS satellites
|
||||
data byte 3 -> Temp3 Temperature3=Temp2-40°C
|
||||
data byte 4 -> Temp4 Temperature4=Temp3-40°C
|
||||
- frame 0x18
|
||||
data byte 0 -> Volt_L Volt=(Volt_H*256+Volt_L)/10 V
|
||||
data byte 1 -> Volt_H
|
||||
data byte 2 -> AMP_L
|
||||
data byte 3 -> AMP_H Amp=(AMP1_*256+AMP_L -180)/14 in signed A
|
||||
- frame 0x19 Servo sensor
|
||||
data byte 0 -> AMP_Servo1 Amp=AMP_Servo1/10 in A
|
||||
data byte 1 -> AMP_Servo2 Amp=AMP_Servo2/10 in A
|
||||
data byte 2 -> AMP_Servo3 Amp=AMP_Servo3/10 in A
|
||||
data byte 3 -> AMP_Servo4 Amp=AMP_Servo4/10 in A
|
||||
- frame 0x1A
|
||||
data byte 2 -> ASpeed_H Air speed=ASpeed_H*256+ASpeed_L km/h
|
||||
data byte 3 -> ASpeed_L
|
||||
- frame 0x1B Variometer sensor
|
||||
data byte 0 -> Alti1H
|
||||
data byte 1 -> Alti1L Altitude unfiltered
|
||||
data byte 2 -> Alti2H
|
||||
data byte 3 -> Alti2L Altitude filtered
|
||||
- frame 0x1C Unknown
|
||||
- frame 0x22 Unknown
|
||||
*/
|
||||
|
||||
#define HITEC_TELEMETRY_LENGTH 8
|
||||
|
||||
struct HitecSensor
|
||||
{
|
||||
const uint16_t id;
|
||||
const char * name;
|
||||
const TelemetryUnit unit;
|
||||
const uint8_t precision;
|
||||
};
|
||||
|
||||
// telemetry frames
|
||||
enum
|
||||
{
|
||||
HITEC_FRAME_00 = 0x00,
|
||||
HITEC_FRAME_11 = 0x11,
|
||||
HITEC_FRAME_12 = 0x12,
|
||||
HITEC_FRAME_13 = 0x13,
|
||||
HITEC_FRAME_14 = 0x14,
|
||||
HITEC_FRAME_15 = 0x15,
|
||||
HITEC_FRAME_16 = 0x16,
|
||||
HITEC_FRAME_17 = 0x17,
|
||||
HITEC_FRAME_18 = 0x18,
|
||||
HITEC_FRAME_19 = 0x19,
|
||||
HITEC_FRAME_1A = 0x1A,
|
||||
HITEC_FRAME_1B = 0x1B,
|
||||
HITEC_FRAME_1C = 0x1C,
|
||||
HITEC_FRAME_22 = 0x22,
|
||||
};
|
||||
|
||||
// telemetry sensors ID
|
||||
enum
|
||||
{
|
||||
HITEC_ID_RX_VOLTAGE = 0x0003, // RX_Batt Voltage
|
||||
HITEC_ID_GPS_LAT_LONG = 0x1200, // GPS latitude longitude
|
||||
HITEC_ID_TEMP2 = 0x1304, // Temperature sensor 2
|
||||
HITEC_ID_GPS_SPEED = 0x1400, // GPS speed
|
||||
HITEC_ID_GPS_ALTITUDE = 0x1402, // GPS altitude sea level
|
||||
HITEC_ID_TEMP1 = 0x1404, // Temperature sensor 1
|
||||
HITEC_ID_FUEL = 0x1500, // Fuel
|
||||
HITEC_ID_RPM1 = 0x1501, // RPM1
|
||||
HITEC_ID_RPM2 = 0x1503, // RPM2
|
||||
HITEC_ID_GPS_DATETIME = 0x1600, // GPS date time
|
||||
HITEC_ID_GPS_HEADING = 0x1700, // GPS heading
|
||||
HITEC_ID_GPS_COUNT = 0x1702, // GPS count
|
||||
HITEC_ID_TEMP3 = 0x1703, // Temperature sensor 3
|
||||
HITEC_ID_TEMP4 = 0x1704, // Temperature sensor 4
|
||||
HITEC_ID_VOLTAGE = 0x1800, // Voltage sensor
|
||||
HITEC_ID_AMP = 0x1802, // Amp sensor
|
||||
HITEC_ID_C50 = 0x1803, // Amp sensor C50
|
||||
HITEC_ID_C200 = 0x1804, // Amp sensor C200
|
||||
HITEC_ID_AMP_S1 = 0x1900, // Amp servo 1 sensor
|
||||
HITEC_ID_AMP_S2 = 0x1901, // Amp servo 2 sensor
|
||||
HITEC_ID_AMP_S3 = 0x1902, // Amp servo 3 sensor
|
||||
HITEC_ID_AMP_S4 = 0x1903, // Amp servo 4 sensor
|
||||
HITEC_ID_AIR_SPEED = 0x1A02, // Air speed
|
||||
HITEC_ID_VARIO = 0x1B00, // Vario
|
||||
HITEC_ID_ALT = 0x1B02, // Vario
|
||||
TX_RSSI_ID = 0xFF00, // Pseudo id outside 1 byte range of Hitec sensors
|
||||
TX_LQI_ID = 0xFF01, // Pseudo id outside 1 byte range of Hitec sensors
|
||||
};
|
||||
|
||||
const HitecSensor hitecSensors[] = {
|
||||
//frame 00
|
||||
{HITEC_ID_RX_VOLTAGE, ZSTR_BATT, UNIT_VOLTS, 2}, // RX_Batt Voltage
|
||||
//frame 11
|
||||
//frame 12
|
||||
{HITEC_ID_GPS_LAT_LONG, ZSTR_GPS, UNIT_GPS, 0}, // GPS position
|
||||
//frame 13
|
||||
{HITEC_ID_TEMP2, ZSTR_TEMP2, UNIT_CELSIUS, 0}, // Temperature sensor 2
|
||||
//frame 14
|
||||
{HITEC_ID_GPS_SPEED, ZSTR_GSPD, UNIT_KMH, 0}, // GPS speed
|
||||
{HITEC_ID_GPS_ALTITUDE, ZSTR_GPSALT, UNIT_METERS, 0}, // GPS altitude sea level
|
||||
{HITEC_ID_TEMP1, ZSTR_TEMP1, UNIT_CELSIUS, 0}, // Temperature sensor 1
|
||||
//frame 15
|
||||
{HITEC_ID_FUEL, ZSTR_FUEL, UNIT_PERCENT, 0}, // Fuel
|
||||
{HITEC_ID_RPM1, ZSTR_RPM, UNIT_RPMS, 0}, // RPM1
|
||||
{HITEC_ID_RPM2, ZSTR_RPM2, UNIT_RPMS, 0}, // RPM2
|
||||
//frame 16
|
||||
{HITEC_ID_GPS_DATETIME, ZSTR_GPS, UNIT_DATETIME, 0}, // GPS date time
|
||||
//frame 17
|
||||
{HITEC_ID_GPS_HEADING, ZSTR_HDG, UNIT_DEGREE, 0}, // GPS Heading
|
||||
{HITEC_ID_GPS_COUNT, ZSTR_SATELLITES, UNIT_RAW, 0}, // GPS count
|
||||
{HITEC_ID_TEMP3, ZSTR_TEMP3, UNIT_CELSIUS, 0}, // Temperature sensor 3
|
||||
{HITEC_ID_TEMP4, ZSTR_TEMP4, UNIT_CELSIUS, 0}, // Temperature sensor 4
|
||||
//frame 18
|
||||
{HITEC_ID_VOLTAGE, ZSTR_A1, UNIT_VOLTS, 1}, // Voltage sensor
|
||||
{HITEC_ID_AMP, ZSTR_CURR, UNIT_AMPS, 0}, // Amp sensor
|
||||
{HITEC_ID_C50, ZSTR_C50, UNIT_AMPS, 1}, // Amp sensor C50
|
||||
{HITEC_ID_C200, ZSTR_C200, UNIT_AMPS, 0}, // Amp sensor C200
|
||||
//frame 19
|
||||
{HITEC_ID_AMP_S1, ZSTR_CURR_SERVO1, UNIT_AMPS, 1}, // Amp sensor
|
||||
{HITEC_ID_AMP_S2, ZSTR_CURR_SERVO2, UNIT_AMPS, 1}, // Amp sensor
|
||||
{HITEC_ID_AMP_S3, ZSTR_CURR_SERVO3, UNIT_AMPS, 1}, // Amp sensor
|
||||
{HITEC_ID_AMP_S4, ZSTR_CURR_SERVO4, UNIT_AMPS, 1}, // Amp sensor
|
||||
//frame 1A
|
||||
{HITEC_ID_AIR_SPEED, ZSTR_ASPD, UNIT_KMH, 0}, // Air speed
|
||||
//frame 1B
|
||||
{HITEC_ID_VARIO, ZSTR_VSPD, UNIT_METERS_PER_SECOND, 1}, // Vario
|
||||
{HITEC_ID_ALT, ZSTR_ALT, UNIT_METERS, 1}, // Altitude
|
||||
|
||||
{TX_RSSI_ID, ZSTR_TX_RSSI, UNIT_RAW, 0}, // Pseudo id outside 1 byte range of Hitec sensors
|
||||
{TX_LQI_ID, ZSTR_TX_QUALITY, UNIT_RAW, 0}, // Pseudo id outside 1 byte range of Hitec sensors// Pseudo sensor for TLQI
|
||||
{0x00, NULL, UNIT_RAW, 0}, // sentinel
|
||||
};
|
||||
|
||||
const HitecSensor * getHitecSensor(uint16_t id)
|
||||
{
|
||||
for (const HitecSensor * sensor = hitecSensors; sensor->id; sensor++) {
|
||||
if (id == sensor->id)
|
||||
return sensor;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void processHitecPacket(const uint8_t * packet)
|
||||
{
|
||||
static uint16_t rssi = 0, lqi = 0;
|
||||
// Set TX RSSI Value, reverse MULTIs scaling
|
||||
rssi = ((packet[0] * 10) + (rssi * 90)) / 100; // quick filtering
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, TX_RSSI_ID, 0, 0, rssi >> 1, UNIT_RAW, 0);
|
||||
telemetryData.rssi.set(rssi >> 1);
|
||||
if (packet[0] > 0) telemetryStreaming = TELEMETRY_TIMEOUT10ms;
|
||||
// Set TX LQI Value, reverse MULTIs scaling
|
||||
lqi = ((packet[1] * 10) + (lqi * 90)) / 100; // quick filtering
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, TX_LQI_ID, 0, 0, lqi, UNIT_RAW, 0);
|
||||
|
||||
const HitecSensor * sensor;
|
||||
int32_t value, deg, min, sec, alt, amp;
|
||||
static uint8_t second = 0;
|
||||
static int32_t last_alt = 0;
|
||||
static uint16_t last_ms = 0;
|
||||
uint16_t current_ms;
|
||||
|
||||
switch (packet[2]) {
|
||||
case HITEC_FRAME_00:
|
||||
value = (((packet[6] << 8) | packet[7]) * 100) / 28;
|
||||
sensor = getHitecSensor(HITEC_ID_RX_VOLTAGE);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_RX_VOLTAGE, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_11:
|
||||
value = (((packet[6] << 8) | packet[7]) * 100) / 28;
|
||||
sensor = getHitecSensor(HITEC_ID_RX_VOLTAGE);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_RX_VOLTAGE, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_12:
|
||||
//value=(packet[5]<<24)|(packet[6]<<16)|(packet[3]<<8)|packet[4];
|
||||
min = (int16_t) ((packet[5] << 8) | packet[6]);
|
||||
deg = min / 100;
|
||||
min = min - deg * 100;
|
||||
sec = (int16_t) ((packet[3] << 8) | packet[4]);
|
||||
value = deg * 1000000 + (min * 150000 + sec * 25) / 9;
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_GPS_LAT_LONG, 0, 0, value, UNIT_GPS_LATITUDE, 0);
|
||||
second = packet[7];
|
||||
return;
|
||||
case HITEC_FRAME_13:
|
||||
//value=(packet[5]<<24)|(packet[6]<<16)|(packet[3]<<8)|packet[4];
|
||||
min = (int16_t) ((packet[5] << 8) | packet[6]);
|
||||
deg = min / 100;
|
||||
min = min - deg * 100;
|
||||
sec = (int16_t) ((packet[3] << 8) | packet[4]);
|
||||
value = deg * 1000000 + (min * 150000 + sec * 25) / 9;
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_GPS_LAT_LONG, 0, 0, value, UNIT_GPS_LONGITUDE, 0);
|
||||
value = packet[7] - 40;
|
||||
sensor = getHitecSensor(HITEC_ID_TEMP2);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_TEMP2, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_14:
|
||||
value = (packet[3] << 8) | packet[4];
|
||||
sensor = getHitecSensor(HITEC_ID_GPS_SPEED);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_GPS_SPEED, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = (packet[5] << 8) | packet[6];
|
||||
sensor = getHitecSensor(HITEC_ID_GPS_ALTITUDE);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_GPS_ALTITUDE, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = packet[7] - 40;
|
||||
sensor = getHitecSensor(HITEC_ID_TEMP1);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_TEMP1, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_15:
|
||||
value = packet[3] * 25;
|
||||
if (value > 100) value = 100;
|
||||
sensor = getHitecSensor(HITEC_ID_FUEL);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_FUEL, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = (packet[5] << 8) | packet[4];
|
||||
sensor = getHitecSensor(HITEC_ID_RPM1);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_RPM1, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = (packet[7] << 8) | packet[6];
|
||||
sensor = getHitecSensor(HITEC_ID_RPM2);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_RPM2, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_16:
|
||||
sensor = getHitecSensor(HITEC_ID_GPS_DATETIME);
|
||||
value = (packet[3] << 24) | (packet[4] << 16) | (packet[5] << 8) | 0x01;
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_GPS_DATETIME, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = (packet[6] << 24) | (packet[7] << 16) | (second << 8);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_GPS_DATETIME, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_17:
|
||||
value = (packet[3] << 8) | packet[4];
|
||||
if (value <= 359) { // Filter strange values received time to time
|
||||
sensor = getHitecSensor(HITEC_ID_GPS_HEADING);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_GPS_HEADING, 0, 0, value, sensor->unit, sensor->precision);
|
||||
}
|
||||
value = packet[5];
|
||||
sensor = getHitecSensor(HITEC_ID_GPS_COUNT);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_GPS_COUNT, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = packet[6] - 40;
|
||||
sensor = getHitecSensor(HITEC_ID_TEMP3);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_TEMP3, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = packet[7] - 40;
|
||||
sensor = getHitecSensor(HITEC_ID_TEMP4);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_TEMP4, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_18:
|
||||
value = (packet[4] << 8) | packet[3];
|
||||
if (value) value += 2; // Measured voltage seems to be 0.2V lower than real
|
||||
sensor = getHitecSensor(HITEC_ID_VOLTAGE);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_VOLTAGE, 0, 0, value, sensor->unit, sensor->precision);
|
||||
//I'm adding below 3 amp sensors but there is only one really since I don't know how to really calculate them
|
||||
value = (int16_t) ((packet[6] << 8) | packet[5]);
|
||||
sensor = getHitecSensor(HITEC_ID_AMP);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_AMP, 0, 0, value, sensor->unit, sensor->precision);
|
||||
amp = ((value + 114.875) * 1.441) + 0.5;
|
||||
sensor = getHitecSensor(HITEC_ID_C50);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_C50, 0, 0, amp, sensor->unit, sensor->precision);
|
||||
amp = value * 3 + 165;
|
||||
sensor = getHitecSensor(HITEC_ID_C200);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_C200, 0, 0, amp, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_19:
|
||||
value = packet[3];
|
||||
sensor = getHitecSensor(HITEC_ID_AMP_S1);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_AMP_S1, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = packet[4];
|
||||
sensor = getHitecSensor(HITEC_ID_AMP_S2);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_AMP_S2, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = packet[5];
|
||||
sensor = getHitecSensor(HITEC_ID_AMP_S3);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_AMP_S3, 0, 0, value, sensor->unit, sensor->precision);
|
||||
value = packet[6];
|
||||
sensor = getHitecSensor(HITEC_ID_AMP_S4);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_AMP_S4, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_1A:
|
||||
value = (packet[5] << 8) | packet[6];
|
||||
sensor = getHitecSensor(HITEC_ID_AIR_SPEED);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_AIR_SPEED, 0, 0, value, sensor->unit, sensor->precision);
|
||||
return;
|
||||
case HITEC_FRAME_1B:
|
||||
alt = (int16_t) ((packet[3] << 8) | packet[4]);
|
||||
sensor = getHitecSensor(HITEC_ID_ALT);
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_ALT, 0, 0, alt, sensor->unit, sensor->precision);
|
||||
current_ms = RTOS_GET_MS();
|
||||
sensor = getHitecSensor(HITEC_ID_VARIO);
|
||||
value = (alt - last_alt) * 100;
|
||||
if ((current_ms - last_ms) < 1000)
|
||||
value /= (int32_t) (current_ms - last_ms);
|
||||
else
|
||||
value = 0;
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, HITEC_ID_VARIO, 0, 0, value, sensor->unit, sensor->precision);
|
||||
last_alt = alt;
|
||||
last_ms = current_ms;
|
||||
return;
|
||||
case HITEC_FRAME_1C:
|
||||
case HITEC_FRAME_22:
|
||||
return;
|
||||
}
|
||||
//unknown
|
||||
value = (packet[6] << 24) | (packet[5] << 16) | (packet[4] << 8) | packet[3];
|
||||
setTelemetryValue(PROTOCOL_TELEMETRY_HITEC, packet[2], 0, 0, value, UNIT_RAW, 0);
|
||||
}
|
||||
|
||||
void processHitecTelemetryData(uint8_t data, uint8_t * rxBuffer, uint8_t &rxBufferCount)
|
||||
{
|
||||
if (rxBufferCount == 0)
|
||||
return;
|
||||
|
||||
if (data != 0xAA) {
|
||||
TRACE("[HITEC] invalid start byte 0x%02X", data);
|
||||
rxBufferCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rxBuffer[3] == HITEC_FRAME_00 || (rxBuffer[3] >= HITEC_FRAME_11 && rxBuffer[3] <= HITEC_FRAME_1C) || rxBuffer[3] == HITEC_FRAME_22) {
|
||||
TRACE("[HITEC] Frame 0x%02X", rxBuffer[3]);
|
||||
}
|
||||
else {
|
||||
TRACE("[HITEC] wrong frame 0x%02X", rxBuffer[3]);
|
||||
rxBufferCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rxBufferCount < TELEMETRY_RX_PACKET_SIZE) {
|
||||
rxBuffer[rxBufferCount++] = data;
|
||||
}
|
||||
else {
|
||||
TRACE("[HITEC] array size %d error", rxBufferCount);
|
||||
rxBufferCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rxBufferCount >= HITEC_TELEMETRY_LENGTH) {
|
||||
// debug print the content of the packets
|
||||
#if 0
|
||||
debugPrintf(" rssi 0x%02X lqi 0x%02X: ",
|
||||
rxBuffer[1], rxBuffer[2]);
|
||||
for (int i=0; i<5; i++) {
|
||||
debugPrintf("%02X ", rxBuffer[4+i]);
|
||||
}
|
||||
debugPrintf("\r\n");
|
||||
#endif
|
||||
processHitecPacket(rxBuffer + 1);
|
||||
rxBufferCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void hitecSetDefault(int index, uint16_t id, uint8_t subId, uint8_t instance)
|
||||
{
|
||||
TelemetrySensor &telemetrySensor = g_model.telemetrySensors[index];
|
||||
telemetrySensor.id = id;
|
||||
telemetrySensor.subId = subId;
|
||||
telemetrySensor.instance = instance;
|
||||
|
||||
const HitecSensor * sensor = getHitecSensor(id);
|
||||
if (sensor) {
|
||||
TelemetryUnit unit = sensor->unit;
|
||||
uint8_t prec = min<uint8_t>(2, sensor->precision);
|
||||
telemetrySensor.init(sensor->name, unit, prec);
|
||||
if (unit == UNIT_RPMS) {
|
||||
telemetrySensor.custom.ratio = 1;
|
||||
telemetrySensor.custom.offset = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
telemetrySensor.init(id);
|
||||
}
|
||||
|
||||
storageDirty(EE_MODEL);
|
||||
}
|
30
radio/src/telemetry/hitec.h
Normal file
30
radio/src/telemetry/hitec.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (C) OpenTX
|
||||
*
|
||||
* Based on code named
|
||||
* th9x - http://code.google.com/p/th9x
|
||||
* er9x - http://code.google.com/p/er9x
|
||||
* gruvin9x - http://code.google.com/p/gruvin9x
|
||||
*
|
||||
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _HITEC_H
|
||||
#define _HITEC_H
|
||||
|
||||
void processHitecTelemetryData(uint8_t data, uint8_t* rxBuffer, uint8_t& rxBufferCount);
|
||||
void hitecSetDefault(int index, uint16_t id, uint8_t subId, uint8_t instance);
|
||||
|
||||
// Used by multi protocol
|
||||
void processHitecPacket(const uint8_t *packet);
|
||||
|
||||
#endif
|
|
@ -21,7 +21,8 @@
|
|||
#include "telemetry.h"
|
||||
#include "multi.h"
|
||||
|
||||
enum MultiPacketTypes : uint8_t {
|
||||
enum MultiPacketTypes : uint8_t
|
||||
{
|
||||
MultiStatus = 1,
|
||||
FrSkySportTelemtry,
|
||||
FrSkyHubTelemetry,
|
||||
|
@ -30,10 +31,14 @@ enum MultiPacketTypes : uint8_t {
|
|||
FlyskyIBusTelemetry,
|
||||
ConfigCommand,
|
||||
InputSync,
|
||||
FrskySportPolling
|
||||
FrskySportPolling,
|
||||
HitecTelemetry,
|
||||
SpectrumScannerPacket,
|
||||
FlyskyIBusTelemetryAC
|
||||
};
|
||||
|
||||
enum MultiBufferState : uint8_t {
|
||||
enum MultiBufferState : uint8_t
|
||||
{
|
||||
NoProtocolDetected,
|
||||
MultiFirstByteReceived,
|
||||
ReceivingMultiProtocol,
|
||||
|
@ -43,24 +48,25 @@ enum MultiBufferState : uint8_t {
|
|||
FrskyTelemetryFallbackFirstByte,
|
||||
FrskyTelemetryFallbackNextBytes,
|
||||
FlyskyTelemetryFallback,
|
||||
HitecTelemetryFallback,
|
||||
MultiStatusOrFrskyData
|
||||
};
|
||||
|
||||
|
||||
#if defined(INTERNAL_MODULE_MULTI)
|
||||
|
||||
static MultiModuleStatus multiModuleStatus[NUM_MODULES] = { MultiModuleStatus(), MultiModuleStatus() };
|
||||
static MultiModuleSyncStatus multiSyncStatus[NUM_MODULES] = { MultiModuleSyncStatus(), MultiModuleSyncStatus() };
|
||||
static uint8_t multiBindStatus[NUM_MODULES] = { MULTI_NORMAL_OPERATION, MULTI_NORMAL_OPERATION };
|
||||
static MultiModuleStatus multiModuleStatus[NUM_MODULES] = {MultiModuleStatus(), MultiModuleStatus()};
|
||||
static MultiModuleSyncStatus multiSyncStatus[NUM_MODULES] = {MultiModuleSyncStatus(), MultiModuleSyncStatus()};
|
||||
static uint8_t multiBindStatus[NUM_MODULES] = {MULTI_NORMAL_OPERATION, MULTI_NORMAL_OPERATION};
|
||||
|
||||
static MultiBufferState multiTelemetryBufferState[NUM_MODULES];
|
||||
|
||||
MultiModuleStatus& getMultiModuleStatus(uint8_t module)
|
||||
MultiModuleStatus &getMultiModuleStatus(uint8_t module)
|
||||
{
|
||||
return multiModuleStatus[module];
|
||||
}
|
||||
|
||||
MultiModuleSyncStatus& getMultiSyncStatus(uint8_t module)
|
||||
MultiModuleSyncStatus &getMultiSyncStatus(uint8_t module)
|
||||
{
|
||||
return multiSyncStatus[module];
|
||||
}
|
||||
|
@ -140,9 +146,9 @@ static MultiBufferState guessProtocol(uint8_t module)
|
|||
return FrskyTelemetryFallback;
|
||||
}
|
||||
|
||||
static void processMultiStatusPacket(const uint8_t *data, uint8_t module)
|
||||
static void processMultiStatusPacket(const uint8_t * data, uint8_t module)
|
||||
{
|
||||
MultiModuleStatus& status = getMultiModuleStatus(module);
|
||||
MultiModuleStatus &status = getMultiModuleStatus(module);
|
||||
|
||||
// At least two status packets without bind flag
|
||||
bool wasBinding = status.isBinding();
|
||||
|
@ -158,9 +164,9 @@ static void processMultiStatusPacket(const uint8_t *data, uint8_t module)
|
|||
setMultiBindStatus(module, MULTI_BIND_FINISHED);
|
||||
}
|
||||
|
||||
static void processMultiSyncPacket(const uint8_t *data, uint8_t module)
|
||||
static void processMultiSyncPacket(const uint8_t * data, uint8_t module)
|
||||
{
|
||||
MultiModuleSyncStatus& status = getMultiSyncStatus(module);
|
||||
MultiModuleSyncStatus &status = getMultiSyncStatus(module);
|
||||
|
||||
status.lastUpdate = get_tmr10ms();
|
||||
status.interval = data[4];
|
||||
|
@ -175,17 +181,17 @@ static void processMultiSyncPacket(const uint8_t *data, uint8_t module)
|
|||
#if !defined(PPM_PIN_SERIAL)
|
||||
TRACE("MP ADJ: rest: %d, lag %04d, diff: %04d target: %d, interval: %d, Refresh: %d, intAdjRefresh: %d, adjRefresh %d\r\n",
|
||||
module == EXTERNAL_MODULE ? extmodulePulsesData.dsm2.rest : 0,
|
||||
status.inputLag, oldlag-status.inputLag, status.target, status.interval, status.refreshRate, status.adjustedRefreshRate/50,
|
||||
status.inputLag, oldlag - status.inputLag, status.target, status.interval, status.refreshRate, status.adjustedRefreshRate / 50,
|
||||
status.getAdjustedRefreshRate());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void processMultiTelemetryPaket(const uint8_t *packet, uint8_t module)
|
||||
static void processMultiTelemetryPaket(const uint8_t * packet, uint8_t module)
|
||||
{
|
||||
uint8_t type = packet[0];
|
||||
uint8_t len = packet[1];
|
||||
const uint8_t *data = packet + 2;
|
||||
const uint8_t * data = packet + 2;
|
||||
|
||||
// Switch type
|
||||
switch (type) {
|
||||
|
@ -215,6 +221,20 @@ static void processMultiTelemetryPaket(const uint8_t *packet, uint8_t module)
|
|||
TRACE("[MP] Received IBUS telemetry len %d < 28", len);
|
||||
break;
|
||||
|
||||
case FlyskyIBusTelemetryAC:
|
||||
if (len >= 28)
|
||||
processFlySkyPacketAC(data);
|
||||
else
|
||||
TRACE("[MP] Received IBUS telemetry AC len %d < 28", len);
|
||||
break;
|
||||
|
||||
case HitecTelemetry:
|
||||
if (len >= 8)
|
||||
processHitecPacket(data);
|
||||
else
|
||||
TRACE("[MP] Received Hitec telemetry len %d < 8", len);
|
||||
break;
|
||||
|
||||
case FrSkyHubTelemetry:
|
||||
if (len >= 4)
|
||||
frskyDProcessPacket(data);
|
||||
|
@ -257,7 +277,7 @@ static void processMultiTelemetryPaket(const uint8_t *packet, uint8_t module)
|
|||
|
||||
// sprintf does not work AVR ARM
|
||||
// use a small helper function
|
||||
static void appendInt(char *buf, uint32_t val)
|
||||
static void appendInt(char * buf, uint32_t val)
|
||||
{
|
||||
while (*buf)
|
||||
buf++;
|
||||
|
@ -277,8 +297,8 @@ void MultiModuleSyncStatus::calcAdjustedRefreshRate(uint16_t newRefreshRate, uin
|
|||
uint16_t targetRefreshRate = (uint16_t) (newRefreshRate * ((MIN_REFRESH_RATE / (newRefreshRate - 1)) + 1));
|
||||
|
||||
// Overflow, reverse sample
|
||||
if (lagDifference < -targetRefreshRate/2)
|
||||
lagDifference= -lagDifference;
|
||||
if (lagDifference < -targetRefreshRate / 2)
|
||||
lagDifference = -lagDifference;
|
||||
|
||||
|
||||
// Reset adjusted refresh if rate has changed
|
||||
|
@ -289,7 +309,7 @@ void MultiModuleSyncStatus::calcAdjustedRefreshRate(uint16_t newRefreshRate, uin
|
|||
adjustedRefreshRate /= 2;
|
||||
|
||||
// Our refresh rate in ps
|
||||
adjustedRefreshRate*=1000;
|
||||
adjustedRefreshRate *= 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -297,16 +317,16 @@ void MultiModuleSyncStatus::calcAdjustedRefreshRate(uint16_t newRefreshRate, uin
|
|||
int numsamples = interval * 10000 / targetRefreshRate;
|
||||
|
||||
// Convert lagDifference to ps
|
||||
lagDifference=lagDifference*1000;
|
||||
lagDifference = lagDifference * 1000;
|
||||
|
||||
// Calculate the time we intentionally were late/early
|
||||
if (inputLag > target*10 +30)
|
||||
lagDifference += numsamples*500;
|
||||
else if (inputLag < target*10 - 30)
|
||||
lagDifference -= numsamples*500;
|
||||
if (inputLag > target * 10 + 30)
|
||||
lagDifference += numsamples * 500;
|
||||
else if (inputLag < target * 10 - 30)
|
||||
lagDifference -= numsamples * 500;
|
||||
|
||||
// Caculate the time in ps each frame is to slow (positive), fast(negative)
|
||||
int perframeps = lagDifference*10/ numsamples;
|
||||
int perframeps = lagDifference * 10 / numsamples;
|
||||
|
||||
if (perframeps > 20000)
|
||||
perframeps = 20000;
|
||||
|
@ -314,20 +334,21 @@ void MultiModuleSyncStatus::calcAdjustedRefreshRate(uint16_t newRefreshRate, uin
|
|||
if (perframeps < -20000)
|
||||
perframeps = -20000;
|
||||
|
||||
adjustedRefreshRate =(adjustedRefreshRate + perframeps);
|
||||
adjustedRefreshRate = (adjustedRefreshRate + perframeps);
|
||||
|
||||
// Safeguards
|
||||
if (adjustedRefreshRate < 6*1000*1000)
|
||||
adjustedRefreshRate = 6*1000*1000;
|
||||
if (adjustedRefreshRate > 30*1000*1000)
|
||||
adjustedRefreshRate = 30*1000*1000;
|
||||
if (adjustedRefreshRate < 6 * 1000 * 1000)
|
||||
adjustedRefreshRate = 6 * 1000 * 1000;
|
||||
if (adjustedRefreshRate > 30 * 1000 * 1000)
|
||||
adjustedRefreshRate = 30 * 1000 * 1000;
|
||||
|
||||
inputLag = newInputLag;
|
||||
}
|
||||
|
||||
static uint8_t counter;
|
||||
|
||||
uint16_t MultiModuleSyncStatus::getAdjustedRefreshRate() {
|
||||
uint16_t MultiModuleSyncStatus::getAdjustedRefreshRate()
|
||||
{
|
||||
if (!isValid() || refreshRate == 0)
|
||||
return 18000;
|
||||
|
||||
|
@ -335,9 +356,9 @@ uint16_t MultiModuleSyncStatus::getAdjustedRefreshRate() {
|
|||
counter = (uint8_t) (counter + 1 % 10);
|
||||
uint16_t rate = (uint16_t) ((adjustedRefreshRate + counter * 50) / 500);
|
||||
// Check how far off we are from our target, positive means we are too slow, negative we are too fast
|
||||
if (inputLag > target*10 +30)
|
||||
return (uint16_t) (rate - 1);
|
||||
else if (inputLag < target*10 - 30)
|
||||
if (inputLag > target * 10 + 30)
|
||||
return (uint16_t) (rate - 1);
|
||||
else if (inputLag < target * 10 - 30)
|
||||
return (uint16_t) (rate + 1);
|
||||
else
|
||||
return rate;
|
||||
|
@ -349,17 +370,16 @@ static void prependSpaces(char * buf, int val)
|
|||
while (*buf)
|
||||
buf++;
|
||||
|
||||
int k=10000;
|
||||
while(val/k==0 && k > 0)
|
||||
{
|
||||
*buf=' ';
|
||||
int k = 10000;
|
||||
while (val / k == 0 && k > 0) {
|
||||
*buf = ' ';
|
||||
buf++;
|
||||
k/= 10;
|
||||
k /= 10;
|
||||
}
|
||||
*buf='\0';
|
||||
*buf = '\0';
|
||||
}
|
||||
|
||||
void MultiModuleSyncStatus::getRefreshString(char *statusText)
|
||||
void MultiModuleSyncStatus::getRefreshString(char * statusText)
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
|
@ -369,12 +389,12 @@ void MultiModuleSyncStatus::getRefreshString(char *statusText)
|
|||
prependSpaces(statusText, inputLag);
|
||||
appendInt(statusText, inputLag);
|
||||
strcat(statusText, "ns R ");
|
||||
prependSpaces(statusText, adjustedRefreshRate/1000);
|
||||
prependSpaces(statusText, adjustedRefreshRate / 1000);
|
||||
appendInt(statusText, (uint32_t) (adjustedRefreshRate / 1000));
|
||||
strcat(statusText, "ns");
|
||||
}
|
||||
|
||||
void MultiModuleStatus::getStatusString(char *statusText)
|
||||
void MultiModuleStatus::getStatusString(char * statusText)
|
||||
{
|
||||
if (!isValid()) {
|
||||
#if defined(PCBTARANIS) || defined(PCBHORUS)
|
||||
|
@ -384,7 +404,7 @@ void MultiModuleStatus::getStatusString(char *statusText)
|
|||
else
|
||||
#endif
|
||||
#endif
|
||||
strcpy(statusText, STR_MODULE_NO_TELEMETRY);
|
||||
strcpy(statusText, STR_MODULE_NO_TELEMETRY);
|
||||
return;
|
||||
}
|
||||
if (!protocolValid()) {
|
||||
|
@ -399,7 +419,7 @@ void MultiModuleStatus::getStatusString(char *statusText)
|
|||
strcpy(statusText, STR_MODULE_NO_INPUT);
|
||||
return;
|
||||
}
|
||||
else if(isWaitingforBind()) {
|
||||
else if (isWaitingforBind()) {
|
||||
strcpy(statusText, STR_MODULE_WAITFORBIND);
|
||||
return;
|
||||
}
|
||||
|
@ -419,7 +439,7 @@ void MultiModuleStatus::getStatusString(char *statusText)
|
|||
strcat(statusText, STR_MODULE_BINDING);
|
||||
}
|
||||
|
||||
static uint8_t* getRxBuffer(uint8_t moduleIdx)
|
||||
static uint8_t * getRxBuffer(uint8_t moduleIdx)
|
||||
{
|
||||
#if defined(INTERNAL_MODULE_MULTI)
|
||||
if (moduleIdx == INTERNAL_MODULE)
|
||||
|
@ -428,7 +448,7 @@ static uint8_t* getRxBuffer(uint8_t moduleIdx)
|
|||
return telemetryRxBuffer;
|
||||
}
|
||||
|
||||
static uint8_t& getRxBufferCount(uint8_t moduleIdx)
|
||||
static uint8_t &getRxBufferCount(uint8_t moduleIdx)
|
||||
{
|
||||
#if defined(INTERNAL_MODULE_MULTI)
|
||||
if (moduleIdx == INTERNAL_MODULE)
|
||||
|
@ -439,8 +459,8 @@ static uint8_t& getRxBufferCount(uint8_t moduleIdx)
|
|||
|
||||
static void processMultiTelemetryByte(const uint8_t data, uint8_t module)
|
||||
{
|
||||
uint8_t* rxBuffer = getRxBuffer(module);
|
||||
uint8_t& rxBufferCount = getRxBufferCount(module);
|
||||
uint8_t * rxBuffer = getRxBuffer(module);
|
||||
uint8_t &rxBufferCount = getRxBufferCount(module);
|
||||
|
||||
if (rxBufferCount < TELEMETRY_RX_PACKET_SIZE) {
|
||||
rxBuffer[rxBufferCount++] = data;
|
||||
|
@ -470,8 +490,8 @@ static void processMultiTelemetryByte(const uint8_t data, uint8_t module)
|
|||
|
||||
void processMultiTelemetryData(uint8_t data, uint8_t module)
|
||||
{
|
||||
uint8_t* rxBuffer = getRxBuffer(module);
|
||||
uint8_t& rxBufferCount = getRxBufferCount(module);
|
||||
uint8_t * rxBuffer = getRxBuffer(module);
|
||||
uint8_t &rxBufferCount = getRxBufferCount(module);
|
||||
|
||||
debugPrintf("State: %d, byte received %02X, buflen: %d\r\n", multiTelemetryBufferState, data, rxBufferCount);
|
||||
switch (getMultiTelemetryBufferState(module)) {
|
||||
|
@ -523,7 +543,7 @@ void processMultiTelemetryData(uint8_t data, uint8_t module)
|
|||
break;
|
||||
|
||||
case SpektrumTelemetryFallback:
|
||||
processSpektrumTelemetryData(module, data,rxBuffer, rxBufferCount);
|
||||
processSpektrumTelemetryData(module, data, rxBuffer, rxBufferCount);
|
||||
if (rxBufferCount == 0) {
|
||||
setMultiTelemetryBufferState(module, NoProtocolDetected);
|
||||
}
|
||||
|
@ -565,15 +585,15 @@ void processMultiTelemetryData(uint8_t data, uint8_t module)
|
|||
|
||||
case ReceivingMultiStatus:
|
||||
rxBuffer[rxBufferCount++] = data;
|
||||
if (rxBufferCount > 5 && rxBuffer[0] == rxBufferCount-1) {
|
||||
processMultiStatusPacket(rxBuffer+1, module);
|
||||
if (rxBufferCount > 5 && rxBuffer[0] == rxBufferCount - 1) {
|
||||
processMultiStatusPacket(rxBuffer + 1, module);
|
||||
rxBufferCount = 0;
|
||||
setMultiTelemetryBufferState(module, NoProtocolDetected);
|
||||
}
|
||||
if (rxBufferCount > 10) {
|
||||
// too long ignore
|
||||
TRACE("Overlong multi status packet detected ignoring, wanted %d", rxBuffer[0]);
|
||||
rxBufferCount =0;
|
||||
rxBufferCount = 0;
|
||||
setMultiTelemetryBufferState(module, NoProtocolDetected);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#if defined(MULTIMODULE)
|
||||
#include "spektrum.h"
|
||||
#include "flysky_ibus.h"
|
||||
#include "hitec.h"
|
||||
#include "multi.h"
|
||||
#endif
|
||||
|
||||
|
|
|
@ -527,6 +527,9 @@ int setTelemetryValue(TelemetryProtocol protocol, uint16_t id, uint8_t subId, ui
|
|||
case PROTOCOL_TELEMETRY_FLYSKY_IBUS:
|
||||
flySkySetDefault(index,id, subId, instance);
|
||||
break;
|
||||
case PROTOCOL_TELEMETRY_HITEC:
|
||||
hitecSetDefault(index, id, subId, instance);
|
||||
break;
|
||||
#endif
|
||||
#if defined(LUA)
|
||||
case PROTOCOL_TELEMETRY_LUA:
|
||||
|
@ -599,10 +602,10 @@ const UnitConversionRule unitConversionTable[] = {
|
|||
{ UNIT_KMH, UNIT_FEET_PER_SECOND, 911, 1000}, // 1 km/h = 0.911344415 feet per second
|
||||
|
||||
{ UNIT_MILLILITERS, UNIT_FLOZ, 100, 2957},
|
||||
|
||||
|
||||
{ UNIT_RADIANS, UNIT_DEGREE, 10000, 175}, // 1 rad = 57.29578 deg
|
||||
{ UNIT_DEGREE, UNIT_RADIANS, 175, 10000}, // 1 deg = 0,0174533 rad
|
||||
|
||||
|
||||
{ 0, 0, 0, 0} // termination
|
||||
};
|
||||
|
||||
|
|
|
@ -1166,6 +1166,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1171,6 +1171,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1172,6 +1172,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1191,6 +1191,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1179,6 +1179,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1194,6 +1194,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1187,6 +1187,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1181,6 +1181,21 @@ TR_GYR_VSRCRAW
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1187,6 +1187,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1176,6 +1176,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -1187,6 +1187,21 @@
|
|||
#define ZSTR_ALT "Alt"
|
||||
#define ZSTR_TEMP1 "Tmp1"
|
||||
#define ZSTR_TEMP2 "Tmp2"
|
||||
#define ZSTR_TEMP3 "Tmp3"
|
||||
#define ZSTR_TEMP4 "Tmp4"
|
||||
#define ZSTR_RPM2 "RPM2"
|
||||
#define ZSTR_PRES "Pres"
|
||||
#define ZSTR_ODO1 "Odo1"
|
||||
#define ZSTR_ODO2 "Odo2"
|
||||
#define ZSTR_TXV "TX_V"
|
||||
#define ZSTR_CURR_SERVO1 "CSv1"
|
||||
#define ZSTR_CURR_SERVO2 "CSv2"
|
||||
#define ZSTR_CURR_SERVO3 "CSv3"
|
||||
#define ZSTR_CURR_SERVO4 "CSv4"
|
||||
#define ZSTR_DIST "Dist"
|
||||
#define ZSTR_ARM "Arm"
|
||||
#define ZSTR_C50 "C50"
|
||||
#define ZSTR_C200 "C200"
|
||||
#define ZSTR_RPM "RPM"
|
||||
#define ZSTR_FUEL "Fuel"
|
||||
#define ZSTR_VSPD "VSpd"
|
||||
|
|
|
@ -133,4 +133,4 @@
|
|||
#define TR_DSM_PROTOCOLS "LP45""DSM2""DSMX"
|
||||
|
||||
#define LEN_MULTI_PROTOCOLS "\007"
|
||||
#define TR_MULTI_PROTOCOLS "FlySky\0""Hubsan\0""FrSky\0 ""Hisky\0 ""V2x2\0 ""DSM\0 ""Devo\0 ""YD717\0 ""KN\0 ""SymaX\0 ""SLT\0 ""CX10\0 ""CG023\0 ""Bayang\0""ESky\0 ""MT99XX\0""MJXq\0 ""Shenqi\0""FY326\0 ""SFHSS\0 ""J6 Pro\0""FQ777\0 ""Assan\0 ""Hontai\0""OpenLrs""FSky 2A""Q2x2\0 ""Walkera""Q303\0 ""GW008\0 ""DM002\0 ""Cabell\0""Esky150""H8 3D\0 ""Corona\0""CFlie\0 ""Hitec\0 ""WFly\0 ""Bugs\0 ""BugMini""Traxxas""NCC1701""E01X\0 ""V911S\0 ""GD00X\0 ""V761\0 ""KF606\0 ""Redpine""Potensi""ZSX\0 ""FlyZone"
|
||||
#define TR_MULTI_PROTOCOLS "FlySky\0""Hubsan\0""FrSky\0 ""Hisky\0 ""V2x2\0 ""DSM\0 ""Devo\0 ""YD717\0 ""KN\0 ""SymaX\0 ""SLT\0 ""CX10\0 ""CG023\0 ""Bayang\0""ESky\0 ""MT99XX\0""MJXq\0 ""Shenqi\0""FY326\0 ""SFHSS\0 ""J6 Pro\0""FQ777\0 ""Assan\0 ""Hontai\0""OpenLrs""FSky 2A""Q2x2\0 ""Walkera""Q303\0 ""GW008\0 ""DM002\0 ""Cabell\0""Esky150""H8 3D\0 ""Corona\0""CFlie\0 ""Hitec\0 ""WFly\0 ""Bugs\0 ""BugMini""Traxxas""NCC1701""E01X\0 ""V911S\0 ""GD00X\0 ""V761\0 ""KF606\0 ""Redpine""Potensi""ZSX\0 ""FlyZone"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue