Files
edgetx-gx12/radio/src/telemetry/multi.cpp
T

481 lines
14 KiB
C++

/*
* 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.
*/
#include "opentx.h"
#include "telemetry.h"
#include "multi.h"
MultiModuleStatus multiModuleStatus;
MultiModuleSyncStatus multiSyncStatus;
uint8_t multiBindStatus = MULTI_NORMAL_OPERATION;
enum MultiPacketTypes : uint8_t {
MultiStatus = 1,
FrSkySportTelemtry,
FrSkyHubTelemetry,
SpektrumTelemetry,
DSMBindPacket,
FlyskyIBusTelemetry,
ConfigCommand,
InputSync,
FrskySportPolling
};
enum MultiBufferState : uint8_t {
NoProtocolDetected,
MultiFirstByteReceived,
ReceivingMultiProtocol,
ReceivingMultiStatus,
SpektrumTelemetryFallback,
FrskyTelemetryFallback,
FrskyTelemetryFallbackFirstByte,
FrskyTelemetryFallbackNextBytes,
FlyskyTelemetryFallback,
MultiStatusOrFrskyData
};
MultiBufferState guessProtocol()
{
if (g_model.moduleData[EXTERNAL_MODULE].getMultiProtocol(false) == MODULE_SUBTYPE_MULTI_DSM2)
return SpektrumTelemetryFallback;
else if (g_model.moduleData[EXTERNAL_MODULE].getMultiProtocol(false) == MODULE_SUBTYPE_MULTI_FS_AFHDS2A)
return FlyskyTelemetryFallback;
else
return FrskyTelemetryFallback;
}
static void processMultiStatusPacket(const uint8_t *data)
{
// At least two status packets without bind flag
bool wasBinding = (multiModuleStatus.isBinding());
multiModuleStatus.flags = data[0];
multiModuleStatus.major = data[1];
multiModuleStatus.minor = data[2];
multiModuleStatus.revision = data[3];
multiModuleStatus.patch = data[4];
multiModuleStatus.lastUpdate = get_tmr10ms();
if (multiModuleStatus.requiresFailsafeCheck == true) {
multiModuleStatus.requiresFailsafeCheck = false;
if (multiModuleStatus.supportsFailsafe() && g_model.moduleData[EXTERNAL_MODULE].failsafeMode == FAILSAFE_NOT_SET)
POPUP_WARNING(STR_NO_FAILSAFE);
}
if (wasBinding && !multiModuleStatus.isBinding() && multiBindStatus == MULTI_BIND_INITIATED)
multiBindStatus = MULTI_BIND_FINISHED;
}
static void processMultiSyncPacket(const uint8_t *data)
{
multiSyncStatus.lastUpdate = get_tmr10ms();
multiSyncStatus.interval = data[4];
multiSyncStatus.target = data[5];
#if !defined(PPM_PIN_SERIAL)
auto oldlag = multiSyncStatus.inputLag;
(void) oldlag;
#endif
multiSyncStatus.calcAdjustedRefreshRate(data[0] << 8 | data[1], data[2] << 8 | data[3]);
#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", extmodulePulsesData.dsm2.rest,
multiSyncStatus.inputLag, oldlag-multiSyncStatus.inputLag, multiSyncStatus.target, multiSyncStatus.interval, multiSyncStatus.refreshRate, multiSyncStatus.adjustedRefreshRate/50,
multiSyncStatus.getAdjustedRefreshRate());
#endif
}
static void processMultiTelemetryPaket(const uint8_t *packet)
{
uint8_t type = packet[0];
uint8_t len = packet[1];
const uint8_t *data = packet + 2;
// Switch type
switch (type) {
case MultiStatus:
if (len >= 5)
processMultiStatusPacket(data);
break;
case DSMBindPacket:
if (len >= 10)
processDSMBindPacket(data);
break;
case SpektrumTelemetry:
// processSpektrumPacket expects data[0] to be the telemetry indicator 0xAA but does not check it,
// just send one byte of our header instead
if (len >= 17)
processSpektrumPacket(data - 1);
else
TRACE("[MP] Received spektrum telemetry len %d < 17", len);
break;
case FlyskyIBusTelemetry:
if (len >= 28)
processFlySkyPacket(data);
else
TRACE("[MP] Received IBUS telemetry len %d < 28", len);
break;
case FrSkyHubTelemetry:
if (len >= 4)
frskyDProcessPacket(data);
else
TRACE("[MP] Received Frsky HUB telemetry len %d < 4", len);
break;
case FrSkySportTelemtry:
if (len >= 4)
sportProcessTelemetryPacket(data);
else
TRACE("[MP] Received sport telemetry len %d < 4", len);
break;
case InputSync:
if (len >= 6)
processMultiSyncPacket(data);
else
TRACE("[MP] Received input sync len %d < 6", len);
break;
case ConfigCommand:
// Just an ack to our command, ignore for now
break;
#if defined(LUA)
case FrskySportPolling:
if (len >= 1 && outputTelemetryBuffer.destination == TELEMETRY_ENDPOINT_SPORT && data[0] == outputTelemetryBuffer.sport.physicalId) {
TRACE("MP Sending sport data out.");
sportSendBuffer(outputTelemetryBuffer.data, outputTelemetryBuffer.size);
}
break;
#endif
default:
TRACE("[MP] Unkown multi packet type 0x%02X, len %d", type, len);
break;
}
}
// sprintf does not work AVR ARM
// use a small helper function
static void appendInt(char *buf, uint32_t val)
{
while (*buf)
buf++;
strAppendUnsigned(buf, val);
}
#define MIN_REFRESH_RATE 7000
void MultiModuleSyncStatus::calcAdjustedRefreshRate(uint16_t newRefreshRate, uint16_t newInputLag)
{
// Check how far off we are from our target, positive means we are too slow, negative we are too fast
int lagDifference = newInputLag - inputLag;
// The refresh rate that we target
// Below is least common multiple of MIN_REFRESH_RATE and requested rate
uint16_t targetRefreshRate = (uint16_t) (newRefreshRate * ((MIN_REFRESH_RATE / (newRefreshRate - 1)) + 1));
// Overflow, reverse sample
if (lagDifference < -targetRefreshRate/2)
lagDifference= -lagDifference;
// Reset adjusted refresh if rate has changed
if (newRefreshRate != refreshRate) {
refreshRate = newRefreshRate;
adjustedRefreshRate = targetRefreshRate;
if (adjustedRefreshRate >= 30000)
adjustedRefreshRate /= 2;
// Our refresh rate in ps
adjustedRefreshRate*=1000;
return;
}
// Caluclate how many samples went into the reported input Lag (*10)
int numsamples = interval * 10000 / targetRefreshRate;
// Convert lagDifference to ps
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;
// Caculate the time in ps each frame is to slow (positive), fast(negative)
int perframeps = lagDifference*10/ numsamples;
if (perframeps > 20000)
perframeps = 20000;
if (perframeps < -20000)
perframeps = -20000;
adjustedRefreshRate =(adjustedRefreshRate + perframeps);
// Safeguards
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() {
if (!isValid() || refreshRate == 0)
return 18000;
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)
return (uint16_t) (rate + 1);
else
return rate;
}
static void prependSpaces(char * buf, int val)
{
while (*buf)
buf++;
int k=10000;
while(val/k==0 && k > 0)
{
*buf=' ';
buf++;
k/= 10;
}
*buf='\0';
}
void MultiModuleSyncStatus::getRefreshString(char *statusText)
{
if (!isValid()) {
return;
}
strcpy(statusText, "L ");
prependSpaces(statusText, inputLag);
appendInt(statusText, inputLag);
strcat(statusText, "ns R ");
prependSpaces(statusText, adjustedRefreshRate/1000);
appendInt(statusText, (uint32_t) (adjustedRefreshRate / 1000));
strcat(statusText, "ns");
}
void MultiModuleStatus::getStatusString(char *statusText)
{
if (!isValid()) {
#if defined(PCBTARANIS) || defined(PCBHORUS)
if (IS_INTERNAL_MODULE_ENABLED())
strcpy(statusText, STR_DISABLE_INTERNAL);
else
#endif
strcpy(statusText, STR_MODULE_NO_TELEMETRY);
return;
}
if (!protocolValid()) {
strcpy(statusText, STR_PROTOCOL_INVALID);
return;
}
else if (!serialMode()) {
strcpy(statusText, STR_MODULE_NO_SERIAL_MODE);
return;
}
else if (!inputDetected()) {
strcpy(statusText, STR_MODULE_NO_INPUT);
return;
}
else if(isWaitingforBind()) {
strcpy(statusText, STR_MODULE_WAITFORBIND);
return;
}
strcpy(statusText, "V");
appendInt(statusText, major);
strcat(statusText, ".");
appendInt(statusText, minor);
strcat(statusText, ".");
appendInt(statusText, revision);
strcat(statusText, ".");
appendInt(statusText, patch);
strcat(statusText, " ");
if (isBinding())
strcat(statusText, STR_MODULE_BINDING);
}
static MultiBufferState multiTelemetryBufferState;
static void processMultiTelemetryByte(const uint8_t data)
{
if (telemetryRxBufferCount < TELEMETRY_RX_PACKET_SIZE) {
telemetryRxBuffer[telemetryRxBufferCount++] = data;
}
else {
TRACE("[MP] array size %d error", telemetryRxBufferCount);
multiTelemetryBufferState = NoProtocolDetected;
}
// Length field does not count the header
if (telemetryRxBufferCount >= 2 && telemetryRxBuffer[1] == telemetryRxBufferCount - 2) {
// debug print the content of the packet
#if 0
debugPrintf("[MP] Packet type %02X len 0x%02X: ",
telemetryRxBuffer[0], telemetryRxBuffer[1]);
for (int i=0; i<(telemetryRxBufferCount+3)/4; i++) {
debugPrintf("[%02X%02X %02X%02X] ", telemetryRxBuffer[i*4+2], telemetryRxBuffer[i*4 + 3],
telemetryRxBuffer[i*4 + 4], telemetryRxBuffer[i*4 + 5]);
}
debugPrintf("\r\n");
#endif
// Packet is complete, process it
processMultiTelemetryPaket(telemetryRxBuffer);
multiTelemetryBufferState = NoProtocolDetected;
}
}
void processMultiTelemetryData(const uint8_t data)
{
// debugPrintf("State: %d, byte received %02X, buflen: %d\r\n", multiTelemetryBufferState, data, telemetryRxBufferCount);
switch (multiTelemetryBufferState) {
case NoProtocolDetected:
if (data == 'M') {
multiTelemetryBufferState = MultiFirstByteReceived;
}
else if (data == 0xAA || data == 0x7e) {
multiTelemetryBufferState = guessProtocol();
// Process the first byte by the protocol
processMultiTelemetryData(data);
}
else {
TRACE("[MP] invalid start byte 0x%02X", data);
}
break;
case FrskyTelemetryFallback:
multiTelemetryBufferState = FrskyTelemetryFallbackFirstByte;
processFrskyTelemetryData(data);
break;
case FrskyTelemetryFallbackFirstByte:
if (data == 'M') {
multiTelemetryBufferState = MultiStatusOrFrskyData;
}
else {
processFrskyTelemetryData(data);
if (data != 0x7e)
multiTelemetryBufferState = FrskyTelemetryFallbackNextBytes;
}
break;
case FrskyTelemetryFallbackNextBytes:
processFrskyTelemetryData(data);
if (data == 0x7e)
// end of packet or start of new packet
multiTelemetryBufferState = FrskyTelemetryFallbackFirstByte;
break;
case FlyskyTelemetryFallback:
processFlySkyTelemetryData(data);
if (telemetryRxBufferCount == 0)
multiTelemetryBufferState = NoProtocolDetected;
break;
case SpektrumTelemetryFallback:
processSpektrumTelemetryData(data);
if (telemetryRxBufferCount == 0)
multiTelemetryBufferState = NoProtocolDetected;
break;
case MultiFirstByteReceived:
telemetryRxBufferCount = 0;
if (data == 'P') {
multiTelemetryBufferState = ReceivingMultiProtocol;
}
else if (data >= 5 && data <= 10) {
// Protocol indented for er9x/ersky9, accept only 5-10 as packet length to have
// a bit of validation
multiTelemetryBufferState = ReceivingMultiStatus;
processMultiTelemetryData(data);
}
else {
TRACE("[MP] invalid second byte 0x%02X", data);
multiTelemetryBufferState = NoProtocolDetected;
}
break;
case ReceivingMultiProtocol:
processMultiTelemetryByte(data);
break;
case MultiStatusOrFrskyData:
// Check len byte if it makes sense for multi
if (data >= 5 && data <= 10) {
multiTelemetryBufferState = ReceivingMultiStatus;
telemetryRxBufferCount = 0;
}
else {
multiTelemetryBufferState = FrskyTelemetryFallbackNextBytes;
processMultiTelemetryData('M');
}
processMultiTelemetryData(data);
break;
case ReceivingMultiStatus:
telemetryRxBuffer[telemetryRxBufferCount++] = data;
if (telemetryRxBufferCount > 5 && telemetryRxBuffer[0] == telemetryRxBufferCount-1) {
processMultiStatusPacket(telemetryRxBuffer+1);
telemetryRxBufferCount = 0;
multiTelemetryBufferState = NoProtocolDetected;
}
if (telemetryRxBufferCount > 10) {
// too long ignore
TRACE("Overlong multi status packet detected ignoring, wanted %d", telemetryRxBuffer[0]);
telemetryRxBufferCount =0;
multiTelemetryBufferState = NoProtocolDetected;
}
}
}