779 lines
20 KiB
C++
779 lines
20 KiB
C++
/*
|
|
* Copyright (C) EdgeTX
|
|
*
|
|
* Based on code named
|
|
* opentx - https://github.com/opentx/opentx
|
|
* 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.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "bitfield.h"
|
|
#include "definitions.h"
|
|
#include "opentx_helpers.h"
|
|
#include "storage/storage.h"
|
|
#include "globals.h"
|
|
#include "MultiProtoDefs.h"
|
|
#include "hal/module_port.h"
|
|
#include "telemetry/crossfire.h"
|
|
|
|
#if defined(MULTIMODULE)
|
|
#include "telemetry/multi.h"
|
|
#endif
|
|
|
|
#if defined(PCBNV14) && defined(AFHDS2)
|
|
extern uint32_t NV14internalModuleFwVersion;
|
|
#endif
|
|
|
|
#if defined(AFHDS3)
|
|
#include "pulses/afhds3_module.h"
|
|
#endif
|
|
|
|
#define CROSSFIRE_CHANNELS_COUNT 16
|
|
#define GHOST_CHANNELS_COUNT 16
|
|
|
|
#define IS_NATIVE_FRSKY_PROTOCOL(module) \
|
|
((moduleState[module].protocol == PROTOCOL_CHANNELS_PXX1) || \
|
|
(moduleState[module].protocol == PROTOCOL_CHANNELS_PXX2))
|
|
|
|
#if defined (MULTIMODULE)
|
|
#define IS_D16_MULTI(module) \
|
|
((g_model.moduleData[module].multi.rfProtocol == MODULE_SUBTYPE_MULTI_FRSKYX) || \
|
|
(g_model.moduleData[module].multi.rfProtocol == MODULE_SUBTYPE_MULTI_FRSKYX2))
|
|
|
|
#define IS_R9_MULTI(module) \
|
|
(g_model.moduleData[module].multi.rfProtocol == \
|
|
MODULE_SUBTYPE_MULTI_FRSKY_R9)
|
|
|
|
#define IS_HOTT_MULTI(module) \
|
|
(g_model.moduleData[module].multi.rfProtocol == MODULE_SUBTYPE_MULTI_HOTT)
|
|
|
|
#define IS_CONFIG_MULTI(module) \
|
|
(g_model.moduleData[module].multi.rfProtocol == MODULE_SUBTYPE_MULTI_CONFIG)
|
|
|
|
#define IS_DSM_MULTI(module) \
|
|
(g_model.moduleData[module].multi.rfProtocol == MODULE_SUBTYPE_MULTI_DSM2)
|
|
|
|
#define IS_RX_MULTI(module) \
|
|
((g_model.moduleData[module].multi.rfProtocol == \
|
|
MODULE_SUBTYPE_MULTI_AFHDS2A_RX) || \
|
|
(g_model.moduleData[module].multi.rfProtocol == \
|
|
MODULE_SUBTYPE_MULTI_FRSKYX_RX) || \
|
|
(g_model.moduleData[module].multi.rfProtocol == \
|
|
MODULE_SUBTYPE_MULTI_BAYANG_RX) || \
|
|
(g_model.moduleData[module].multi.rfProtocol == \
|
|
MODULE_SUBTYPE_MULTI_DSM_RX))
|
|
|
|
// When using packed, the pointer in here end up not being aligned, which clang and gcc complain about
|
|
// Keep the order of the fields that the so that the size stays small
|
|
struct mm_options_strings {
|
|
static const char* const options[];
|
|
};
|
|
|
|
const uint8_t getMaxMultiOptions();
|
|
|
|
PACK_NOT_SIMU(struct mm_protocol_definition {
|
|
uint8_t protocol;
|
|
uint8_t maxSubtype:6;
|
|
bool failsafe:1;
|
|
bool disable_ch_mapping:1;
|
|
const char* const* subTypeString;
|
|
const char* optionsstr;
|
|
});
|
|
|
|
const mm_protocol_definition *getMultiProtocolDefinition (uint8_t protocol);
|
|
|
|
inline uint8_t getMaxMultiSubtype(uint8_t moduleIdx)
|
|
{
|
|
MultiModuleStatus &status = getMultiModuleStatus(moduleIdx);
|
|
|
|
uint8_t max_pdef = 0;
|
|
auto proto = g_model.moduleData[moduleIdx].multi.rfProtocol;
|
|
const mm_protocol_definition *pdef = getMultiProtocolDefinition(proto);
|
|
if (pdef) {
|
|
max_pdef = pdef->maxSubtype;
|
|
}
|
|
|
|
uint8_t max_status = 0;
|
|
if (status.isValid()) {
|
|
max_status = (status.protocolSubNbr == 0 ? 0 : status.protocolSubNbr - 1);
|
|
}
|
|
|
|
return max(max_status, max_pdef);
|
|
}
|
|
|
|
inline bool isModuleMultimodule(uint8_t idx)
|
|
{
|
|
return g_model.moduleData[idx].type == MODULE_TYPE_MULTIMODULE;
|
|
}
|
|
|
|
inline bool isModuleMultimoduleDSM2(uint8_t idx)
|
|
{
|
|
return isModuleMultimodule(idx) &&
|
|
g_model.moduleData[idx].multi.rfProtocol ==
|
|
MODULE_SUBTYPE_MULTI_DSM2;
|
|
}
|
|
|
|
inline void resetMultiProtocolsOptions(uint8_t moduleIdx)
|
|
{
|
|
if (!isModuleMultimodule(moduleIdx))
|
|
return;
|
|
|
|
// Sensible default for DSM2 (same as for ppm): 7ch@22ms + Autodetect settings enabled
|
|
if (g_model.moduleData[moduleIdx].multi.rfProtocol == MODULE_SUBTYPE_MULTI_DSM2) {
|
|
g_model.moduleData[moduleIdx].multi.autoBindMode = 1;
|
|
}
|
|
else {
|
|
g_model.moduleData[moduleIdx].multi.autoBindMode = 0;
|
|
}
|
|
g_model.moduleData[moduleIdx].multi.optionValue = 0;
|
|
g_model.moduleData[moduleIdx].multi.disableTelemetry = 0;
|
|
g_model.moduleData[moduleIdx].multi.disableMapping = 0;
|
|
g_model.moduleData[moduleIdx].multi.lowPowerMode = 0;
|
|
g_model.moduleData[moduleIdx].failsafeMode = FAILSAFE_NOT_SET;
|
|
g_model.header.modelId[moduleIdx] = 0;
|
|
}
|
|
#else
|
|
#define IS_D16_MULTI(module) false
|
|
#define IS_R9_MULTI(module) false
|
|
#define IS_HOTT_MULTI(module) false
|
|
#define IS_CONFIG_MULTI(module) false
|
|
#define IS_DSM_MULTI(module) false
|
|
#define IS_RX_MULTI(module) false
|
|
|
|
inline bool isModuleMultimodule(uint8_t)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
inline bool isModuleMultimoduleDSM2(uint8_t)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
inline bool isModuleTypeXJT(uint8_t type)
|
|
{
|
|
return type == MODULE_TYPE_XJT_PXX1 || type == MODULE_TYPE_XJT_LITE_PXX2;
|
|
}
|
|
|
|
inline bool isModuleNone(uint8_t idx)
|
|
{
|
|
return g_model.moduleData[idx].type == MODULE_TYPE_NONE;
|
|
}
|
|
|
|
inline bool isModuleXJT(uint8_t idx)
|
|
{
|
|
return isModuleTypeXJT(g_model.moduleData[idx].type);
|
|
}
|
|
|
|
inline bool isModuleXJTD8(uint8_t idx)
|
|
{
|
|
return isModuleXJT(idx) && g_model.moduleData[idx].subType == MODULE_SUBTYPE_PXX1_ACCST_D8;
|
|
}
|
|
|
|
inline bool isModuleXJTLR12(uint8_t idx)
|
|
{
|
|
return isModuleXJT(idx) && g_model.moduleData[idx].subType == MODULE_SUBTYPE_PXX1_ACCST_LR12;
|
|
}
|
|
|
|
inline bool isModuleXJTD16(uint8_t idx)
|
|
{
|
|
return isModuleXJT(idx) && g_model.moduleData[idx].subType == MODULE_SUBTYPE_PXX1_ACCST_D16;
|
|
}
|
|
|
|
inline bool isModuleTypeISRM(uint8_t type)
|
|
{
|
|
return type == MODULE_TYPE_ISRM_PXX2;
|
|
}
|
|
|
|
inline bool isModuleISRM(uint8_t idx)
|
|
{
|
|
return isModuleTypeISRM(g_model.moduleData[idx].type);
|
|
}
|
|
|
|
inline bool isModuleISRMD16(uint8_t idx)
|
|
{
|
|
return isModuleISRM(idx) &&
|
|
g_model.moduleData[idx].subType == MODULE_SUBTYPE_ISRM_PXX2_ACCST_D16;
|
|
}
|
|
|
|
inline bool isModuleD16(uint8_t idx)
|
|
{
|
|
return isModuleXJTD16(idx) || isModuleISRMD16(idx) || IS_D16_MULTI(idx);
|
|
}
|
|
|
|
inline bool isModuleISRMAccess(uint8_t idx)
|
|
{
|
|
return isModuleISRM(idx) &&
|
|
g_model.moduleData[idx].subType == MODULE_SUBTYPE_ISRM_PXX2_ACCESS;
|
|
}
|
|
|
|
#if defined(CROSSFIRE)
|
|
inline bool isModuleCrossfire(uint8_t idx)
|
|
{
|
|
return g_model.moduleData[idx].type == MODULE_TYPE_CROSSFIRE;
|
|
}
|
|
|
|
inline bool isModuleELRS(uint8_t idx)
|
|
{
|
|
return crossfireModuleStatus[idx].isELRS;
|
|
}
|
|
|
|
inline bool isInternalModuleCrossfire()
|
|
{
|
|
return g_eeGeneral.internalModule == MODULE_TYPE_CROSSFIRE;
|
|
}
|
|
#else
|
|
inline bool isModuleCrossfire(uint8_t idx)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
inline bool isModuleELRS(uint8_t idx)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
inline bool isInternalModuleCrossfire()
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if defined(GHOST)
|
|
inline bool isModuleGhost(uint8_t idx)
|
|
{
|
|
return g_model.moduleData[idx].type == MODULE_TYPE_GHOST;
|
|
}
|
|
#else
|
|
inline bool isModuleGhost(uint8_t idx)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
inline bool isExtraModule(uint8_t)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
inline bool isModuleTypePPM(uint8_t type)
|
|
{
|
|
return type == MODULE_TYPE_PPM;
|
|
}
|
|
|
|
inline bool isModulePPM(uint8_t moduleIdx)
|
|
{
|
|
return isModuleTypePPM(g_model.moduleData[moduleIdx].type);
|
|
}
|
|
|
|
inline bool isModuleTypeR9MNonAccess(uint8_t type)
|
|
{
|
|
return type == MODULE_TYPE_R9M_PXX1 || type == MODULE_TYPE_R9M_LITE_PXX1;
|
|
}
|
|
|
|
inline bool isModuleR9MNonAccess(uint8_t idx)
|
|
{
|
|
return isModuleTypeR9MNonAccess(g_model.moduleData[idx].type);
|
|
}
|
|
|
|
inline bool isModuleTypeR9MAccess(uint8_t type)
|
|
{
|
|
return type == MODULE_TYPE_R9M_PXX2 || type == MODULE_TYPE_R9M_LITE_PXX2 || type == MODULE_TYPE_R9M_LITE_PRO_PXX2;
|
|
}
|
|
|
|
inline bool isModuleR9MAccess(uint8_t idx)
|
|
{
|
|
return isModuleTypeR9MAccess(g_model.moduleData[idx].type);
|
|
}
|
|
|
|
inline bool isModuleTypeR9M(uint8_t type)
|
|
{
|
|
return isModuleTypeR9MNonAccess(type) || isModuleTypeR9MAccess(type);
|
|
}
|
|
|
|
inline bool isModuleR9M(uint8_t idx)
|
|
{
|
|
return isModuleTypeR9M(g_model.moduleData[idx].type);
|
|
}
|
|
|
|
inline bool isModuleTypeR9MLiteNonPro(uint8_t type)
|
|
{
|
|
return type == MODULE_TYPE_R9M_LITE_PXX1 || type == MODULE_TYPE_R9M_LITE_PXX2;
|
|
}
|
|
|
|
inline bool isModuleR9MLiteNonPro(uint8_t idx)
|
|
{
|
|
return isModuleTypeR9MLiteNonPro(g_model.moduleData[idx].type);
|
|
}
|
|
|
|
inline bool isModuleTypeR9MLitePro(uint8_t type)
|
|
{
|
|
return type == MODULE_TYPE_R9M_LITE_PRO_PXX2;
|
|
}
|
|
|
|
inline bool isModuleTypeR9MLite(uint8_t type)
|
|
{
|
|
return isModuleTypeR9MLiteNonPro(type) || isModuleTypeR9MLitePro(type);
|
|
}
|
|
|
|
inline bool isModuleR9MLite(uint8_t idx)
|
|
{
|
|
return isModuleTypeR9MLite(g_model.moduleData[idx].type);
|
|
}
|
|
|
|
inline bool isModuleR9M_FCC(uint8_t idx)
|
|
{
|
|
return isModuleR9MNonAccess(idx) && g_model.moduleData[idx].subType == MODULE_SUBTYPE_R9M_FCC;
|
|
}
|
|
|
|
inline bool isModuleTypeLite(uint8_t type)
|
|
{
|
|
return isModuleTypeR9MLite(type) || type == MODULE_TYPE_XJT_LITE_PXX2;
|
|
}
|
|
|
|
inline bool isModuleR9M_LBT(uint8_t idx)
|
|
{
|
|
return isModuleR9MNonAccess(idx) && g_model.moduleData[idx].subType == MODULE_SUBTYPE_R9M_EU;
|
|
}
|
|
|
|
inline bool isModuleR9M_FCC_VARIANT(uint8_t idx)
|
|
{
|
|
return isModuleR9MNonAccess(idx) && g_model.moduleData[idx].subType != MODULE_SUBTYPE_R9M_EU;
|
|
}
|
|
|
|
inline bool isModuleR9M_EUPLUS(uint8_t idx)
|
|
{
|
|
return isModuleR9MNonAccess(idx) && g_model.moduleData[idx].subType == MODULE_SUBTYPE_R9M_EUPLUS;
|
|
}
|
|
|
|
inline bool isModuleR9M_AU_PLUS(uint8_t idx)
|
|
{
|
|
return isModuleR9MNonAccess(idx) && g_model.moduleData[idx].subType == MODULE_SUBTYPE_R9M_AUPLUS;
|
|
}
|
|
|
|
inline bool isModuleTypePXX1(uint8_t type)
|
|
{
|
|
return isModuleTypeXJT(type) || isModuleTypeR9MNonAccess(type);
|
|
}
|
|
|
|
inline bool isModulePXX1(uint8_t idx)
|
|
{
|
|
return isModuleTypePXX1(g_model.moduleData[idx].type);
|
|
}
|
|
|
|
inline bool isModuleXJTLite(uint8_t idx)
|
|
{
|
|
return g_model.moduleData[idx].type == MODULE_TYPE_XJT_LITE_PXX2;
|
|
}
|
|
|
|
inline bool isModulePXX2(uint8_t idx)
|
|
{
|
|
return isModuleISRM(idx) || isModuleR9MAccess(idx) || isModuleXJTLite(idx);
|
|
}
|
|
|
|
inline bool isModuleRFAccess(uint8_t idx)
|
|
{
|
|
if (isModuleISRM(idx)) {
|
|
return g_model.moduleData[idx].subType == MODULE_SUBTYPE_ISRM_PXX2_ACCESS;
|
|
}
|
|
else if (isModuleR9MAccess(idx)) {
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline bool isModuleDSM2(uint8_t moduleIdx)
|
|
{
|
|
return g_model.moduleData[moduleIdx].type == MODULE_TYPE_DSM2;
|
|
}
|
|
|
|
inline bool isModuleSBUS(uint8_t moduleIdx)
|
|
{
|
|
return g_model.moduleData[moduleIdx].type == MODULE_TYPE_SBUS;
|
|
}
|
|
|
|
inline bool isModuleAFHDS2A(uint8_t idx)
|
|
{
|
|
return (g_model.moduleData[idx].type == MODULE_TYPE_FLYSKY_AFHDS2A);
|
|
}
|
|
|
|
inline bool isModuleAFHDS3(uint8_t idx)
|
|
{
|
|
return (g_model.moduleData[idx].type == MODULE_TYPE_FLYSKY_AFHDS3);
|
|
}
|
|
|
|
inline bool isModuleFlySky(uint8_t idx)
|
|
{
|
|
return (isModuleAFHDS2A(idx) || isModuleAFHDS3(idx));
|
|
}
|
|
|
|
inline bool isModuleDSMP(uint8_t idx)
|
|
{
|
|
return g_model.moduleData[idx].type == MODULE_TYPE_LEMON_DSMP;
|
|
}
|
|
|
|
// See enum ModuleType in modules_constant.h
|
|
// relative to 8!
|
|
static const int8_t maxChannelsModules_M8[] = {
|
|
0, // MODULE_TYPE_NONE
|
|
8, // MODULE_TYPE_PPM
|
|
0, // MODULE_TYPE_XJT_PXX1: index NOT USED
|
|
16,// MODULE_TYPE_ISRM_PXX2
|
|
-2,// MODULE_TYPE_DSM2
|
|
CROSSFIRE_CHANNELS_COUNT - 8, // MODULE_TYPE_CROSSFIRE
|
|
8, // MODULE_TYPE_MULTIMODULE
|
|
0, // MODULE_TYPE_R9M_PXX1: index NOT USED
|
|
0, // MODULE_TYPE_R9M_PXX2: index NOT USED
|
|
0, // MODULE_TYPE_R9M_LITE_PXX1: index NOT USED
|
|
0, // MODULE_TYPE_R9M_LITE_PXX2: index NOT USED
|
|
GHOST_CHANNELS_COUNT - 8, // MODULE_TYPE_GHOST
|
|
0, // MODULE_TYPE_R9M_LITE_PRO_PXX2: index NOT USED
|
|
8, // MODULE_TYPE_SBUS
|
|
0, // MODULE_TYPE_XJT_LITE_PXX2: index NOT USED
|
|
6, // MODULE_TYPE_FLYSKY_AFHDS2A: 14 channels
|
|
10,// MODULE_TYPE_FLYSKY_AFHDS3: 18 channels
|
|
4, // MODULE_TYPE_LEMON_DSMP: 12 channels for DSMX
|
|
};
|
|
|
|
static_assert(MODULE_TYPE_COUNT == sizeof(maxChannelsModules_M8),
|
|
"MODULE_TYPE_COUNT != sizeof(maxChannelsModules_M8)");
|
|
|
|
// See enum ModuleSubtypePXX1 in modules_constant.h
|
|
// relative to 8!
|
|
static const int8_t maxChannelsXJT_M8[] = {
|
|
0, // MODULE_SUBTYPE_PXX1_OFF
|
|
8, // MODULE_SUBTYPE_PXX1_ACCST_D16
|
|
0, // MODULE_SUBTYPE_PXX1_ACCST_D8
|
|
4 // MODULE_SUBTYPE_PXX1_ACCST_LR12
|
|
};
|
|
|
|
// MODULE_SUBTYPE_PXX1_OFF == -1
|
|
static_assert(MODULE_SUBTYPE_PXX1_LAST + 2 == sizeof(maxChannelsXJT_M8),
|
|
"MODULE_SUBTYPE_PXX1_LAST + 2 != sizeof(maxChannelsXJT_M8)");
|
|
|
|
constexpr int8_t MAX_TRAINER_CHANNELS_M8 = MAX_TRAINER_CHANNELS - 8;
|
|
constexpr int8_t MAX_EXTRA_MODULE_CHANNELS_M8 = 8; // only 16ch PPM
|
|
|
|
extern int8_t maxModuleChannels_M8(uint8_t moduleIdx);
|
|
|
|
inline int8_t maxModuleChannels(uint8_t moduleIdx)
|
|
{
|
|
return maxModuleChannels_M8(moduleIdx) + 8;
|
|
}
|
|
|
|
inline int8_t minModuleChannels(uint8_t idx)
|
|
{
|
|
if (isModuleCrossfire(idx))
|
|
return CROSSFIRE_CHANNELS_COUNT;
|
|
else if (isModuleGhost(idx))
|
|
return GHOST_CHANNELS_COUNT;
|
|
else if (isModuleSBUS(idx))
|
|
return 16;
|
|
else if (isModuleDSMP(idx))
|
|
return maxModuleChannels(idx);
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
inline int8_t defaultModuleChannels_M8(uint8_t idx)
|
|
{
|
|
if (isModulePPM(idx))
|
|
return 0; // 8 channels
|
|
else
|
|
return maxModuleChannels_M8(idx);
|
|
}
|
|
|
|
inline uint8_t sentModulePXXChannels(uint8_t idx)
|
|
{
|
|
return 8 + g_model.moduleData[idx].channelsCount;
|
|
}
|
|
|
|
extern int8_t sentModuleChannels(uint8_t idx);
|
|
|
|
inline bool isDefaultModelRegistrationID()
|
|
{
|
|
return memcmp(g_model.modelRegistrationID, g_eeGeneral.ownerRegistrationID, PXX2_LEN_REGISTRATION_ID) == 0;
|
|
}
|
|
|
|
inline bool isModuleRxNumAvailable(uint8_t moduleIdx)
|
|
{
|
|
if (isModuleXJT(moduleIdx))
|
|
return g_model.moduleData[moduleIdx].subType != MODULE_SUBTYPE_PXX1_ACCST_D8;
|
|
|
|
if (isModuleR9M(moduleIdx))
|
|
return true;
|
|
|
|
if (isModuleDSM2(moduleIdx))
|
|
return true;
|
|
|
|
if (isModuleISRM(moduleIdx))
|
|
return true;
|
|
|
|
if (isModuleMultimodule(moduleIdx))
|
|
return true;
|
|
|
|
if (isModuleCrossfire(moduleIdx))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
inline bool isModuleModelIndexAvailable(uint8_t idx)
|
|
{
|
|
if (isModuleXJT(idx))
|
|
return g_model.moduleData[idx].subType != MODULE_SUBTYPE_PXX1_ACCST_D8;
|
|
|
|
if (isModuleR9M(idx))
|
|
return true;
|
|
|
|
if (isModuleDSM2(idx))
|
|
return true;
|
|
|
|
if (isModuleISRM(idx))
|
|
return true;
|
|
|
|
if (isModuleMultimodule(idx))
|
|
return true;
|
|
|
|
if (isModuleCrossfire(idx))
|
|
return true;
|
|
|
|
if (isModuleAFHDS3(idx))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
inline bool isModuleFailsafeAvailable(uint8_t moduleIdx)
|
|
{
|
|
#if defined(PXX2)
|
|
if (isModuleISRM(moduleIdx))
|
|
return true;
|
|
#endif
|
|
|
|
if (isModuleXJT(moduleIdx))
|
|
return g_model.moduleData[moduleIdx].subType == MODULE_SUBTYPE_PXX1_ACCST_D16;
|
|
|
|
#if defined(MULTIMODULE)
|
|
if (isModuleMultimodule(moduleIdx)) {
|
|
MultiModuleStatus &status = getMultiModuleStatus(moduleIdx);
|
|
if (status.isValid()) {
|
|
return status.supportsFailsafe();
|
|
}
|
|
else {
|
|
auto proto = g_model.moduleData[moduleIdx].multi.rfProtocol;
|
|
const mm_protocol_definition * pdef = getMultiProtocolDefinition(proto);
|
|
if (pdef) return pdef->failsafe;
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(AFHDS3) || defined(AFHDS2)
|
|
if (isModuleFlySky(moduleIdx))
|
|
return true;
|
|
#endif
|
|
|
|
if (isModuleR9M(moduleIdx))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
#if defined(MULTIMODULE)
|
|
constexpr int32_t MULTI_DSM_CLONE_VERSION = (1 << 24) | (3 << 16) | (3 << 8) | 30;
|
|
|
|
inline bool isMultiProtocolDSMCloneAvailable(uint8_t moduleIdx)
|
|
{
|
|
if (!isModuleMultimodule(moduleIdx))
|
|
return false;
|
|
|
|
MultiModuleStatus &status = getMultiModuleStatus(moduleIdx);
|
|
if (status.isValid() && ((status.major << 24) | (status.minor << 16) | (status.revision << 8) | status.patch) < MULTI_DSM_CLONE_VERSION) {
|
|
return false;
|
|
}
|
|
|
|
return g_model.moduleData[moduleIdx].multi.rfProtocol == MODULE_SUBTYPE_MULTI_DSM2;
|
|
}
|
|
#endif
|
|
|
|
inline bool isModuleBindRangeAvailable(uint8_t moduleIdx)
|
|
{
|
|
return isModulePXX2(moduleIdx) || isModulePXX1(moduleIdx) ||
|
|
isModuleDSM2(moduleIdx) || isModuleMultimodule(moduleIdx) ||
|
|
isModuleFlySky(moduleIdx) || isModuleDSMP(moduleIdx) ||
|
|
(isModuleELRS(moduleIdx) && (crossfireModuleStatus[moduleIdx].major >= 4 || (crossfireModuleStatus[moduleIdx].major == 3 && crossfireModuleStatus[moduleIdx].minor >= 4)));
|
|
}
|
|
|
|
inline uint32_t getNV14RfFwVersion()
|
|
{
|
|
#if defined(PCBNV14) && defined(AFHDS2)
|
|
return NV14internalModuleFwVersion;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
inline bool isModuleRangeAvailable(uint8_t moduleIdx)
|
|
{
|
|
bool ret = isModuleBindRangeAvailable(moduleIdx) && !IS_RX_MULTI(moduleIdx) && !isModuleCrossfire(moduleIdx);
|
|
#if defined(PCBNV14) && defined(AFHDS2)
|
|
ret = ret &&
|
|
(!isModuleAFHDS2A(moduleIdx) || NV14internalModuleFwVersion >= 0x1000E);
|
|
#elif defined(AFHDS3)
|
|
ret = ret && (!isModuleAFHDS3(moduleIdx));
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
constexpr uint8_t MAX_RXNUM = 63;
|
|
|
|
extern uint8_t getMaxRxNum(uint8_t idx);
|
|
|
|
inline const char * getModuleDelay(uint8_t idx)
|
|
{
|
|
if (isModuleISRMAccess(idx))
|
|
return sentModuleChannels(idx) > 16 ? "(21ms)" : (sentModuleChannels(idx) > 8 ? "(14ms)" : "(7ms)");
|
|
|
|
if (isModuleXJTD16(idx) || isModuleR9MNonAccess(idx))
|
|
return sentModuleChannels(idx) > 8 ? "(18ms)" : "(9ms)";
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
inline bool isBindCh9To16Allowed(uint8_t moduleIndex)
|
|
{
|
|
if (g_model.moduleData[moduleIndex].channelsCount <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if (isModuleR9M_LBT(moduleIndex)) {
|
|
if (isModuleR9MLite(moduleIndex))
|
|
return g_model.moduleData[moduleIndex].pxx.power != R9M_LBT_POWER_25_8CH;
|
|
else
|
|
return g_model.moduleData[moduleIndex].pxx.power != R9M_LITE_LBT_POWER_25_8CH;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
inline bool isTelemAllowedOnBind(uint8_t moduleIndex)
|
|
{
|
|
#if defined(HARDWARE_INTERNAL_MODULE)
|
|
if (moduleIndex == INTERNAL_MODULE)
|
|
return true;
|
|
|
|
if (!modulePortIsPortUsedByModule(moduleIndex, ETX_MOD_PORT_SPORT))
|
|
return false;
|
|
#endif
|
|
|
|
#if defined(HARDWARE_EXTERNAL_MODULE)
|
|
if (g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_R9M_LITE_PXX1) {
|
|
if (isModuleR9M_LBT(EXTERNAL_MODULE))
|
|
return g_model.moduleData[EXTERNAL_MODULE].pxx.power < R9M_LITE_LBT_POWER_100_16CH_NOTELEM;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
if (g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_R9M_PXX1) {
|
|
if (isModuleR9M_LBT(EXTERNAL_MODULE))
|
|
return g_model.moduleData[EXTERNAL_MODULE].pxx.power < R9M_LBT_POWER_200_16CH_NOTELEM;
|
|
else
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
inline bool isPXX2ReceiverUsed(uint8_t moduleIdx, uint8_t receiverIdx)
|
|
{
|
|
return g_model.moduleData[moduleIdx].pxx2.receivers & (1 << receiverIdx);
|
|
}
|
|
|
|
inline void setPXX2ReceiverUsed(uint8_t moduleIdx, uint8_t receiverIdx)
|
|
{
|
|
g_model.moduleData[moduleIdx].pxx2.receivers |= (1 << receiverIdx);
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
|
|
inline bool isPXX2ReceiverEmpty(uint8_t moduleIdx, uint8_t receiverIdx)
|
|
{
|
|
return is_memclear(g_model.moduleData[moduleIdx].pxx2.receiverName[receiverIdx], PXX2_LEN_RX_NAME);
|
|
}
|
|
|
|
inline void removePXX2Receiver(uint8_t moduleIdx, uint8_t receiverIdx)
|
|
{
|
|
memclear(g_model.moduleData[moduleIdx].pxx2.receiverName[receiverIdx], PXX2_LEN_RX_NAME);
|
|
g_model.moduleData[moduleIdx].pxx2.receivers &= ~(1 << receiverIdx);
|
|
storageDirty(EE_MODEL);
|
|
}
|
|
|
|
inline void removePXX2ReceiverIfEmpty(uint8_t moduleIdx, uint8_t receiverIdx)
|
|
{
|
|
if (isPXX2ReceiverEmpty(moduleIdx, receiverIdx)) {
|
|
removePXX2Receiver(moduleIdx, receiverIdx);
|
|
}
|
|
}
|
|
|
|
inline void setDefaultPpmFrameLength(uint8_t moduleIdx)
|
|
{
|
|
// channelsCount + 8 ???
|
|
g_model.moduleData[moduleIdx].ppm.frameLength =
|
|
4 * max<int>(0, g_model.moduleData[moduleIdx].channelsCount);
|
|
}
|
|
|
|
inline void setDefaultPpmFrameLengthTrainer()
|
|
{
|
|
g_model.trainerData.frameLength =
|
|
4 * max<int>(0, g_model.trainerData.channelsCount);
|
|
}
|
|
|
|
inline void resetAccessAuthenticationCount()
|
|
{
|
|
// the module will reset on mode switch, we need to reset the authentication counter
|
|
globalData.authenticationCount = 0;
|
|
}
|
|
|
|
inline void resetAfhds3Options(uint8_t moduleIdx)
|
|
{
|
|
#if defined(AFHDS3)
|
|
auto & data = g_model.moduleData[moduleIdx];
|
|
data.subType = 0;
|
|
data.afhds3.emi = 2; // FCC
|
|
data.afhds3.telemetry = 1;
|
|
data.afhds3.phyMode = 0;
|
|
#endif
|
|
}
|
|
|
|
inline void resetAfhds2AOptions(uint8_t moduleIdx)
|
|
{
|
|
#if defined(AFHDS2)
|
|
auto & data = g_model.moduleData[moduleIdx];
|
|
data.subType = 0;
|
|
data.flysky.setDefault();
|
|
#endif
|
|
}
|
|
|
|
extern void setModuleType(uint8_t moduleIdx, uint8_t moduleType);
|
|
|
|
extern bool isExternalAntennaEnabled();
|