/* * 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 "eeprominterface.h" #include "firmwares/er9x/er9xinterface.h" #include "firmwares/ersky9x/ersky9xinterface.h" #include "firmwares/opentx/opentxinterface.h" #include "firmwares/opentx/opentxeeprom.h" #include "appdata.h" #include "helpers.h" #include "wizarddata.h" #include "firmwareinterface.h" #include "macros.h" #include "multiprotocols.h" #include #include #include #include #include #include const uint8_t chout_ar[] = { // First number is 0..23 -> template setup, Second is relevant channel out 1,2,3,4 , 1,2,4,3 , 1,3,2,4 , 1,3,4,2 , 1,4,2,3 , 1,4,3,2, 2,1,3,4 , 2,1,4,3 , 2,3,1,4 , 2,3,4,1 , 2,4,1,3 , 2,4,3,1, 3,1,2,4 , 3,1,4,2 , 3,2,1,4 , 3,2,4,1 , 3,4,1,2 , 3,4,2,1, 4,1,2,3 , 4,1,3,2 , 4,2,1,3 , 4,2,3,1 , 4,3,1,2 , 4,3,2,1 }; static const char specialCharsTab[] = "_-.,"; void setEEPROMString(char *dst, const char *src, int size) { memcpy(dst, src, size); for (int i=size-1; i>=0; i--) { if (dst[i] == '\0') dst[i] = ' '; else break; } } void getEEPROMString(char *dst, const char *src, int size) { memcpy(dst, src, size); dst[size] = '\0'; for (int i=size-1; i>=0; i--) { if (dst[i] == ' ') dst[i] = '\0'; else break; } } int8_t char2idx(char c) { if (c==' ') return 0; if (c>='A' && c<='Z') return 1+c-'A'; if (c>='a' && c<='z') return -1-c+'a'; if (c>='0' && c<='9') return 27+c-'0'; for (int8_t i=0;;i++) { char cc = specialCharsTab[i]; if (cc==0) return 0; if (cc==c) return 37+i; } } #define ZCHAR_MAX 40 char idx2char(int8_t idx) { if (idx == 0) return ' '; if (idx < 0) { if (idx > -27) return 'a' - idx - 1; idx = -idx; } if (idx < 27) return 'A' + idx - 1; if (idx < 37) return '0' + idx - 27; if (idx <= ZCHAR_MAX) return specialCharsTab[idx-37]; return ' '; } void setEEPROMZString(char *dst, const char *src, int size) { for (int i=size-1; i>=0; i--) dst[i] = char2idx(src[i]); } void getEEPROMZString(char *dst, const char *src, int size) { for (int i=size-1; i>=0; i--) dst[i] = idx2char(src[i]); dst[size] = '\0'; for (int i=size-1; i>=0; i--) { if (dst[i] == ' ') dst[i] = '\0'; else break; } } float ValToTim(int value) { return ((value < -109 ? 129+value : (value < 7 ? (113+value)*5 : (53+value)*10))/10.0); } int TimToVal(float value) { int temp; if (value>60) { temp=136+round((value-60)); } else if (value>2) { temp=20+round((value-2.0)*2.0); } else { temp=round(value*10.0); } return (temp-129); } QString getSignedStr(int value) { return value > 0 ? QString("+%1").arg(value) : QString("%1").arg(value); } QString getGVarString(int16_t val, bool sign) { if (val >= -10000 && val <= 10000) { if (sign) return QString("%1%").arg(getSignedStr(val)); else return QString("%1%").arg(val); } else { if (val<0) return QObject::tr("-GV%1").arg(-val-10000); else return QObject::tr("GV%1").arg(val-10000); } } void SensorData::updateUnit() { if (type == TELEM_TYPE_CALCULATED) { if (formula == TELEM_FORMULA_CONSUMPTION) unit = UNIT_MAH; } } QString SensorData::unitString() const { switch (unit) { case UNIT_VOLTS: return QObject::tr("V"); case UNIT_AMPS: return QObject::tr("A"); case UNIT_MILLIAMPS: return QObject::tr("mA"); case UNIT_KTS: return QObject::tr("kts"); case UNIT_METERS_PER_SECOND: return QObject::tr("m/s"); case UNIT_KMH: return QObject::tr("km/h"); case UNIT_MPH: return QObject::tr("mph"); case UNIT_METERS: return QObject::tr("m"); case UNIT_FEET: return QObject::tr("f"); case UNIT_CELSIUS: return QObject::trUtf8("°C"); case UNIT_FAHRENHEIT: return QObject::trUtf8("°F"); case UNIT_PERCENT: return QObject::tr("%"); case UNIT_MAH: return QObject::tr("mAh"); case UNIT_WATTS: return QObject::tr("W"); case UNIT_MILLIWATTS: return QObject::tr("mW"); case UNIT_DB: return QObject::tr("dB"); case UNIT_RPMS: return QObject::tr("rpms"); case UNIT_G: return QObject::tr("g"); case UNIT_DEGREE: return QObject::trUtf8("°"); case UNIT_RADIANS: return QObject::trUtf8("Rad"); case UNIT_HOURS: return QObject::tr("hours"); case UNIT_MINUTES: return QObject::tr("minutes"); case UNIT_SECONDS: return QObject::tr("seconds"); case UNIT_CELLS: return QObject::tr("V"); default: return ""; } } bool RawSource::isTimeBased() const { if (IS_ARM(getCurrentBoard())) return (type == SOURCE_TYPE_SPECIAL && index > 0); else return (type==SOURCE_TYPE_TELEMETRY && (index==TELEMETRY_SOURCE_TX_TIME || index==TELEMETRY_SOURCE_TIMER1 || index==TELEMETRY_SOURCE_TIMER2 || index==TELEMETRY_SOURCE_TIMER3)); } float RawSourceRange::getValue(int value) { if (IS_ARM(getCurrentBoard())) return float(value) * step; else return min + float(value) * step; } RawSourceRange RawSource::getRange(const ModelData * model, const GeneralSettings & settings, unsigned int flags) const { RawSourceRange result; Firmware * firmware = Firmware::getCurrentVariant(); int board = firmware->getBoard(); bool singleprec = (flags & RANGE_SINGLE_PRECISION); if (!singleprec && !IS_ARM(board)) { singleprec = true; } switch (type) { case SOURCE_TYPE_TELEMETRY: if (IS_ARM(board)) { div_t qr = div(index, 3); const SensorData & sensor = model->sensorData[qr.quot]; if (sensor.prec == 2) result.step = 0.01; else if (sensor.prec == 1) result.step = 0.1; else result.step = 1; result.min = -30000 * result.step; result.max = +30000 * result.step; result.decimals = sensor.prec; result.unit = sensor.unitString(); } else { if (singleprec) { result.offset = -DBL_MAX; } switch (index) { case TELEMETRY_SOURCE_TX_BATT: result.step = 0.1; result.decimals = 1; result.max = 25.5; result.unit = QObject::tr("V"); break; case TELEMETRY_SOURCE_TX_TIME: result.step = 1; result.max = 24*60 - 1; break; case TELEMETRY_SOURCE_TIMER1: case TELEMETRY_SOURCE_TIMER2: case TELEMETRY_SOURCE_TIMER3: result.step = singleprec ? 5 : 1; result.max = singleprec ? 255*5 : 60*60; result.unit = QObject::tr("s"); break; case TELEMETRY_SOURCE_RSSI_TX: case TELEMETRY_SOURCE_RSSI_RX: result.max = 100; if (singleprec) result.offset = 128; break; case TELEMETRY_SOURCE_A1_MIN: case TELEMETRY_SOURCE_A2_MIN: case TELEMETRY_SOURCE_A3_MIN: case TELEMETRY_SOURCE_A4_MIN: if (model) result = model->frsky.channels[index-TELEMETRY_SOURCE_A1_MIN].getRange(); break; case TELEMETRY_SOURCE_A1: case TELEMETRY_SOURCE_A2: case TELEMETRY_SOURCE_A3: case TELEMETRY_SOURCE_A4: if (model) result = model->frsky.channels[index-TELEMETRY_SOURCE_A1].getRange(); break; case TELEMETRY_SOURCE_ALT: case TELEMETRY_SOURCE_ALT_MIN: case TELEMETRY_SOURCE_ALT_MAX: case TELEMETRY_SOURCE_GPS_ALT: result.step = singleprec ? 8 : 1; result.min = -500; result.max = singleprec ? 1540 : 3000; if (firmware->getCapability(Imperial) || settings.imperial) { result.step = (result.step * 105) / 32; result.min = (result.min * 105) / 32; result.max = (result.max * 105) / 32; result.unit = QObject::tr("ft"); } else { result.unit = QObject::tr("m"); } break; case TELEMETRY_SOURCE_T1: case TELEMETRY_SOURCE_T1_MAX: case TELEMETRY_SOURCE_T2: case TELEMETRY_SOURCE_T2_MAX: result.min = -30; result.max = 225; result.unit = QObject::trUtf8("°C"); break; case TELEMETRY_SOURCE_HDG: result.step = singleprec ? 2 : 1; result.max = 360; if (singleprec) result.offset = 256; result.unit = QObject::trUtf8("°"); break; case TELEMETRY_SOURCE_RPM: case TELEMETRY_SOURCE_RPM_MAX: result.step = singleprec ? 50 : 1; result.max = singleprec ? 12750 : 30000; break; case TELEMETRY_SOURCE_FUEL: result.max = 100; result.unit = QObject::tr("%"); break; case TELEMETRY_SOURCE_ASPEED: case TELEMETRY_SOURCE_ASPEED_MAX: result.decimals = 1; result.step = singleprec ? 2.0 : 0.1; result.max = singleprec ? (2*255) : 2000; if (firmware->getCapability(Imperial) || settings.imperial) { result.step *= 1.150779; result.max *= 1.150779; result.unit = QObject::tr("mph"); } else { result.step *= 1.852; result.max *= 1.852; result.unit = QObject::tr("km/h"); } break; case TELEMETRY_SOURCE_SPEED: case TELEMETRY_SOURCE_SPEED_MAX: result.step = singleprec ? 2 : 1; result.max = singleprec ? (2*255) : 2000; if (firmware->getCapability(Imperial) || settings.imperial) { result.step *= 1.150779; result.max *= 1.150779; result.unit = QObject::tr("mph"); } else { result.step *= 1.852; result.max *= 1.852; result.unit = QObject::tr("km/h"); } break; case TELEMETRY_SOURCE_VERTICAL_SPEED: result.step = 0.1; result.min = singleprec ? -12.5 : -300.0; result.max = singleprec ? 13.0 : 300.0; result.decimals = 1; result.unit = QObject::tr("m/s"); break; case TELEMETRY_SOURCE_DTE: result.max = 30000; break; case TELEMETRY_SOURCE_DIST: case TELEMETRY_SOURCE_DIST_MAX: result.step = singleprec ? 8 : 1; result.max = singleprec ? 2040 : 10000; result.unit = QObject::tr("m"); break; case TELEMETRY_SOURCE_CELL: case TELEMETRY_SOURCE_CELL_MIN: result.step = singleprec ? 0.02 : 0.01; result.max = 5.1; result.decimals = 2; result.unit = QObject::tr("V"); break; case TELEMETRY_SOURCE_CELLS_SUM: case TELEMETRY_SOURCE_CELLS_MIN: case TELEMETRY_SOURCE_VFAS: case TELEMETRY_SOURCE_VFAS_MIN: result.step = 0.1; result.max = singleprec ? 25.5 : 100.0; result.decimals = 1; result.unit = QObject::tr("V"); break; case TELEMETRY_SOURCE_CURRENT: case TELEMETRY_SOURCE_CURRENT_MAX: result.step = singleprec ? 0.5 : 0.1; result.max = singleprec ? 127.5 : 200.0; result.decimals = 1; result.unit = QObject::tr("A"); break; case TELEMETRY_SOURCE_CONSUMPTION: result.step = singleprec ? 100 : 1; result.max = singleprec ? 25500 : 30000; result.unit = QObject::tr("mAh"); break; case TELEMETRY_SOURCE_POWER: case TELEMETRY_SOURCE_POWER_MAX: result.step = singleprec ? 5 : 1; result.max = singleprec ? 1275 : 2000; result.unit = QObject::tr("W"); break; case TELEMETRY_SOURCE_ACCX: case TELEMETRY_SOURCE_ACCY: case TELEMETRY_SOURCE_ACCZ: result.step = 0.01; result.decimals = 2; result.max = singleprec ? 2.55 : 10.00; result.min = singleprec ? 0 : -10.00; result.unit = QObject::tr("g"); break; default: result.max = 125; break; } if (singleprec && result.offset==-DBL_MAX) { result.offset = result.max - (127*result.step); } if (flags & (RANGE_DELTA_FUNCTION|RANGE_DELTA_ABS_FUNCTION)) { if (singleprec) { result.offset = 0; result.min = result.step * -127; result.max = result.step * 127; } else { result.min = -result.max; } } } break; case SOURCE_TYPE_GVAR: result.max = 1024; result.min = -result.max; break; case SOURCE_TYPE_SPECIAL: if (index == 0) { //Batt result.step = 0.1; result.decimals = 1; result.max = 25.5; result.unit = QObject::tr("V"); } else if (index == 1) { //Time result.step = 1; result.max = 24*60 - 1; result.unit = QObject::tr("h:m"); } else { // Timers 1 - 3 result.step = singleprec ? 5 : 1; result.max = singleprec ? 255*5 : 60*60; result.unit = singleprec ? QObject::tr("m:s") : QObject::tr("h:m:s"); } break; default: if (model) { result.max = model->getChannelsMax(true); result.min = -result.max; } break; } if (flags & RANGE_DELTA_ABS_FUNCTION) { result.min = 0; } return result; } QString RotaryEncoderString(int index) { static const QString rotary[] = { QObject::tr("REa"), QObject::tr("REb") }; return CHECK_IN_ARRAY(rotary, index); } /* * RawSource */ QString RawSource::toString(const ModelData * model, const GeneralSettings * const generalSettings) const { static const QString trims[] = { QObject::tr("TrmR"), QObject::tr("TrmE"), QObject::tr("TrmT"), QObject::tr("TrmA"), QObject::tr("Trm5"), QObject::tr("Trm6") }; static const QString special[] = { QObject::tr("Batt"), QObject::tr("Time"), QObject::tr("Timer1"), QObject::tr("Timer2"), QObject::tr("Timer3"), }; static const QString telemetry[] = { QObject::tr("Batt"), QObject::tr("Time"), QObject::tr("Timer1"), QObject::tr("Timer2"), QObject::tr("Timer3"), QObject::tr("SWR"), QObject::tr("RSSI Tx"), QObject::tr("RSSI Rx"), QObject::tr("A1"), QObject::tr("A2"), QObject::tr("A3"), QObject::tr("A4"), QObject::tr("Alt"), QObject::tr("Rpm"), QObject::tr("Fuel"), QObject::tr("T1"), QObject::tr("T2"), QObject::tr("Speed"), QObject::tr("Dist"), QObject::tr("GPS Alt"), QObject::tr("Cell"), QObject::tr("Cells"), QObject::tr("Vfas"), QObject::tr("Curr"), QObject::tr("Cnsp"), QObject::tr("Powr"), QObject::tr("AccX"), QObject::tr("AccY"), QObject::tr("AccZ"), QObject::tr("Hdg "), QObject::tr("VSpd"), QObject::tr("AirSpeed"), QObject::tr("dTE"), QObject::tr("A1-"), QObject::tr("A2-"), QObject::tr("A3-"), QObject::tr("A4-"), QObject::tr("Alt-"), QObject::tr("Alt+"), QObject::tr("Rpm+"), QObject::tr("T1+"), QObject::tr("T2+"), QObject::tr("Speed+"), QObject::tr("Dist+"), QObject::tr("AirSpeed+"), QObject::tr("Cell-"), QObject::tr("Cells-"), QObject::tr("Vfas-"), QObject::tr("Curr+"), QObject::tr("Powr+"), QObject::tr("ACC"), QObject::tr("GPS Time"), }; if (index<0) { return QObject::tr("----"); } QString result; int genAryIdx = 0; switch (type) { case SOURCE_TYPE_VIRTUAL_INPUT: result = QObject::tr("[I%1]").arg(index+1); if (model) result += QString(model->inputNames[index]); return result; case SOURCE_TYPE_LUA_OUTPUT: return QObject::tr("LUA%1%2").arg(index/16+1).arg(QChar('a'+index%16)); case SOURCE_TYPE_STICK: if (generalSettings) { if (isPot(&genAryIdx)) result = QString(generalSettings->potName[genAryIdx]); else if (isSlider(&genAryIdx)) result = QString(generalSettings->sliderName[genAryIdx]); else if (isStick(&genAryIdx)) result = QString(generalSettings->stickName[genAryIdx]); } if (result.isEmpty()) result = getCurrentFirmware()->getAnalogInputName(index);; return result; case SOURCE_TYPE_TRIM: return CHECK_IN_ARRAY(trims, index); case SOURCE_TYPE_ROTARY_ENCODER: return RotaryEncoderString(index); case SOURCE_TYPE_MAX: return QObject::tr("MAX"); case SOURCE_TYPE_SWITCH: if (generalSettings) result = QString(generalSettings->switchName[index]); if (result.isEmpty()) result = getSwitchInfo(getCurrentBoard(), index).name; return result; case SOURCE_TYPE_CUSTOM_SWITCH: return QObject::tr("L%1").arg(index+1); case SOURCE_TYPE_CYC: return QObject::tr("CYC%1").arg(index+1); case SOURCE_TYPE_PPM: return QObject::tr("TR%1").arg(index+1); case SOURCE_TYPE_CH: return QObject::tr("CH%1").arg(index+1); case SOURCE_TYPE_SPECIAL: return CHECK_IN_ARRAY(special, index); case SOURCE_TYPE_TELEMETRY: if (IS_ARM(getCurrentBoard())) { div_t qr = div(index, 3); result = model ? QString(model->sensorData[qr.quot].label) : QString("[T%1]").arg(qr.quot+1); if (qr.rem) result += (qr.rem == 1 ? "-" : "+"); return result; } else { return CHECK_IN_ARRAY(telemetry, index); } case SOURCE_TYPE_GVAR: return QObject::tr("GV%1").arg(index+1); default: return QObject::tr("----"); } } bool RawSource::isStick(int * stickIndex) const { if (type == SOURCE_TYPE_STICK && index < getBoardCapability(getCurrentBoard(), Board::Sticks)) { if (stickIndex) *stickIndex = index; return true; } return false; } bool RawSource::isPot(int * potsIndex) const { if (type == SOURCE_TYPE_STICK && index >= getBoardCapability(getCurrentBoard(), Board::Sticks) && index < getBoardCapability(getCurrentBoard(), Board::Sticks) + getBoardCapability(getCurrentBoard(), Board::Pots)) { if (potsIndex) *potsIndex = index - getBoardCapability(getCurrentBoard(), Board::Sticks); return true; } return false; } bool RawSource::isSlider(int * sliderIndex) const { if (type == SOURCE_TYPE_STICK && index >= getBoardCapability(getCurrentBoard(), Board::Sticks) + getBoardCapability(getCurrentBoard(), Board::Pots) && index < getBoardCapability(getCurrentBoard(), Board::Sticks) + getBoardCapability(getCurrentBoard(), Board::Pots) + getBoardCapability(getCurrentBoard(), Board::Sliders)) { if (sliderIndex) *sliderIndex = index - getBoardCapability(getCurrentBoard(), Board::Sticks) - getBoardCapability(getCurrentBoard(), Board::Pots); return true; } return false; } /* * RawSwitch */ QString RawSwitch::toString(Board::Type board, const GeneralSettings * const generalSettings) const { if (board == Board::BOARD_UNKNOWN) { board = getCurrentBoard(); } static const QString switches9X[] = { QString("THR"), QString("RUD"), QString("ELE"), QString("ID0"), QString("ID1"), QString("ID2"), QString("AIL"), QString("GEA"), QString("TRN") }; static const QString flightModes[] = { QObject::tr("FM0"), QObject::tr("FM1"), QObject::tr("FM2"), QObject::tr("FM3"), QObject::tr("FM4"), QObject::tr("FM5"), QObject::tr("FM6"), QObject::tr("FM7"), QObject::tr("FM8") }; static const QString trimsSwitches[] = { QObject::tr("RudTrim Left"), QObject::tr("RudTrim Right"), QObject::tr("EleTrim Down"), QObject::tr("EleTrim Up"), QObject::tr("ThrTrim Down"), QObject::tr("ThrTrim Up"), QObject::tr("AilTrim Left"), QObject::tr("AilTrim Right"), QObject::tr("Trim 5 Down"), QObject::tr("Trim 5 Up"), QObject::tr("Trim 6 Down"), QObject::tr("Trim 6 Up") }; static const QString rotaryEncoders[] = { QObject::tr("REa"), QObject::tr("REb") }; static const QString timerModes[] = { QObject::tr("OFF"), QObject::tr("ON"), QObject::tr("THs"), QObject::tr("TH%"), QObject::tr("THt") }; const QStringList directionIndicators = QStringList() << CPN_STR_SW_INDICATOR_UP << CPN_STR_SW_INDICATOR_NEUT << CPN_STR_SW_INDICATOR_DN; if (index < 0) { return CPN_STR_SW_INDICATOR_REV % RawSwitch(type, -index).toString(board, generalSettings); } else { QString swName; div_t qr; switch(type) { case SWITCH_TYPE_SWITCH: if (IS_HORUS_OR_TARANIS(board)) { qr = div(index-1, 3); if (generalSettings) swName = QString(generalSettings->switchName[qr.quot]); if (swName.isEmpty()) swName = getSwitchInfo(board, qr.quot).name; return swName + directionIndicators.at(qr.rem > -1 && qr.rem < directionIndicators.size() ? qr.rem : 1); } else { return CHECK_IN_ARRAY(switches9X, index - 1); } case SWITCH_TYPE_VIRTUAL: return QObject::tr("L%1").arg(index); case SWITCH_TYPE_MULTIPOS_POT: if (!getCurrentFirmware()->getCapability(MultiposPotsPositions)) return QObject::tr("???"); qr = div(index - 1, getCurrentFirmware()->getCapability(MultiposPotsPositions)); if (generalSettings && qr.quot < (int)DIM(generalSettings->potConfig)) swName = QString(generalSettings->potName[qr.quot]); if (swName.isEmpty()) swName = getCurrentFirmware()->getAnalogInputName(qr.quot + getBoardCapability(board, Board::Sticks));; return swName + "_" + QString::number(qr.rem + 1); case SWITCH_TYPE_TRIM: return CHECK_IN_ARRAY(trimsSwitches, index-1); case SWITCH_TYPE_ROTARY_ENCODER: return CHECK_IN_ARRAY(rotaryEncoders, index-1); case SWITCH_TYPE_ON: return QObject::tr("ON"); case SWITCH_TYPE_OFF: return QObject::tr("OFF"); case SWITCH_TYPE_ONE: return QObject::tr("One"); case SWITCH_TYPE_FLIGHT_MODE: return CHECK_IN_ARRAY(flightModes, index-1); case SWITCH_TYPE_NONE: return QObject::tr("----"); case SWITCH_TYPE_TIMER_MODE: return CHECK_IN_ARRAY(timerModes, index); default: return QObject::tr("???"); } } } /* * CurveReference */ QString CurveReference::toString() const { if (value == 0) { return "----"; } else { switch(type) { case CURVE_REF_DIFF: return QObject::tr("Diff(%1)").arg(getGVarString(value)); case CURVE_REF_EXPO: return QObject::tr("Expo(%1)").arg(getGVarString(value)); case CURVE_REF_FUNC: return QObject::tr("Function(%1)").arg(QString("x>0" "x<0" "|x|" "f>0" "f<0" "|f|").mid(3*(value-1), 3)); default: return QString(value > 0 ? QObject::tr("Curve(%1)") : QObject::tr("!Curve(%1)")).arg(abs(value)); } } } CSFunctionFamily LogicalSwitchData::getFunctionFamily() const { if (func == LS_FN_EDGE) return LS_FAMILY_EDGE; else if (func == LS_FN_TIMER) return LS_FAMILY_TIMER; else if (func == LS_FN_STICKY) return LS_FAMILY_STICKY; else if (func < LS_FN_AND || func > LS_FN_ELESS) return LS_FAMILY_VOFS; else if (func < LS_FN_EQUAL) return LS_FAMILY_VBOOL; else return LS_FAMILY_VCOMP; } unsigned int LogicalSwitchData::getRangeFlags() const { if (func == LS_FN_DPOS) return RANGE_DELTA_FUNCTION; else if (func == LS_FN_DAPOS) return RANGE_DELTA_ABS_FUNCTION; else return 0; } QString LogicalSwitchData::funcToString() const { switch (func) { case LS_FN_OFF: return QObject::tr("---"); case LS_FN_VPOS: return QObject::tr("a>x"); case LS_FN_VNEG: return QObject::tr("ax"); case LS_FN_ANEG: return QObject::tr("|a|b"); case LS_FN_LESS: return QObject::tr("a=b"); case LS_FN_ELESS: return QObject::tr("a<=b"); case LS_FN_DPOS: return QObject::tr("d>=x"); case LS_FN_DAPOS: return QObject::tr("|d|>=x"); case LS_FN_VEQUAL: return QObject::tr("a=x"); case LS_FN_VALMOSTEQUAL: return QObject::tr("a~x"); case LS_FN_TIMER: return QObject::tr("Timer"); case LS_FN_STICKY: return QObject::tr("Sticky"); case LS_FN_EDGE: return QObject::tr("Edge"); default: return QObject::tr("Unknown"); } } void CustomFunctionData::clear() { memset(this, 0, sizeof(CustomFunctionData)); if (!getCurrentFirmware()->getCapability(SafetyChannelCustomFunction)) { func = FuncTrainer; } } QString CustomFunctionData::funcToString() const { if (func >= FuncOverrideCH1 && func <= FuncOverrideCH32) return QObject::tr("Override %1").arg(RawSource(SOURCE_TYPE_CH, func).toString()); else if (func == FuncTrainer) return QObject::tr("Trainer"); else if (func == FuncTrainerRUD) return QObject::tr("Trainer RUD"); else if (func == FuncTrainerELE) return QObject::tr("Trainer ELE"); else if (func == FuncTrainerTHR) return QObject::tr("Trainer THR"); else if (func == FuncTrainerAIL) return QObject::tr("Trainer AIL"); else if (func == FuncInstantTrim) return QObject::tr("Instant Trim"); else if (func == FuncPlaySound) return QObject::tr("Play Sound"); else if (func == FuncPlayHaptic) return QObject::tr("Haptic"); else if (func == FuncReset) return QObject::tr("Reset"); else if (func >= FuncSetTimer1 && func <= FuncSetTimer3) return QObject::tr("Set Timer %1").arg(func-FuncSetTimer1+1); else if (func == FuncVario) return QObject::tr("Vario"); else if (func == FuncPlayPrompt) return QObject::tr("Play Track"); else if (func == FuncPlayBoth) return QObject::tr("Play Both"); else if (func == FuncPlayValue) return QObject::tr("Play Value"); else if (func == FuncPlayScript) return QObject::tr("Play Script"); else if (func == FuncLogs) return QObject::tr("SD Logs"); else if (func == FuncVolume) return QObject::tr("Volume"); else if (func == FuncBacklight) return QObject::tr("Backlight"); else if (func == FuncScreenshot) return QObject::tr("Screenshot"); else if (func == FuncBackgroundMusic) return QObject::tr("Background Music"); else if (func == FuncBackgroundMusicPause) return QObject::tr("Background Music Pause"); else if (func >= FuncAdjustGV1 && func <= FuncAdjustGVLast) return QObject::tr("Adjust GV%1").arg(func-FuncAdjustGV1+1); else if (func == FuncSetFailsafeInternalModule) return QObject::tr("SetFailsafe Int. Module"); else if (func == FuncSetFailsafeExternalModule) return QObject::tr("SetFailsafe Ext. Module"); else if (func == FuncRangeCheckInternalModule) return QObject::tr("RangeCheck Int. Module"); else if (func == FuncRangeCheckExternalModule) return QObject::tr("RangeCheck Ext. Module"); else if (func == FuncBindInternalModule) return QObject::tr("Bind Int. Module"); else if (func == FuncBindExternalModule) return QObject::tr("Bind Ext. Module"); else { return QString("???"); // Highlight unknown functions with output of question marks.(BTW should not happen that we do not know what a function is) } } void CustomFunctionData::populateResetParams(const ModelData * model, QComboBox * b, unsigned int value = 0) { int val = 0; Firmware * firmware = Firmware::getCurrentVariant(); Board::Type board = firmware->getBoard(); b->addItem(QObject::tr("Timer1"), val++); b->addItem(QObject::tr("Timer2"), val++); if (IS_ARM(board)) { b->addItem( QObject::tr("Timer3"), val++); } b->addItem(QObject::tr("Flight"), val++); b->addItem(QObject::tr("Telemetry"), val++); int reCount = firmware->getCapability(RotaryEncoders); if (reCount == 1) { b->addItem(QObject::tr("Rotary Encoder"), val++); } else if (reCount == 2) { b->addItem(QObject::tr("REa"), val++); b->addItem(QObject::tr("REb"), val++); } if ((int)value < b->count()) { b->setCurrentIndex(value); } if (model && IS_ARM(board)) { for (int i=0; isensorData[i].isAvailable()) { RawSource item = RawSource(SOURCE_TYPE_TELEMETRY, 3*i); b->addItem(item.toString(model), val+i); if ((int)value == val+i) { b->setCurrentIndex(b->count()-1); } } } } } void CustomFunctionData::populatePlaySoundParams(QStringList & qs) { qs <<"Beep 1" << "Beep 2" << "Beep 3" << "Warn1" << "Warn2" << "Cheep" << "Ratata" << "Tick" << "Siren" << "Ring" ; qs << "SciFi" << "Robot" << "Chirp" << "Tada" << "Crickt" << "AlmClk" ; } void CustomFunctionData::populateHapticParams(QStringList & qs) { qs << "0" << "1" << "2" << "3"; } QString CustomFunctionData::paramToString(const ModelData * model) const { QStringList qs; if (func <= FuncInstantTrim) { return QString("%1").arg(param); } else if (func==FuncLogs) { return QString("%1").arg(param/10.0) + QObject::tr("s"); } else if (func==FuncPlaySound) { CustomFunctionData::populatePlaySoundParams(qs); if (param>=0 && param<(int)qs.count()) return qs.at(param); else return QObject::tr("Inconsistent parameter"); } else if (func==FuncPlayHaptic) { CustomFunctionData::populateHapticParams(qs); if (param>=0 && param<(int)qs.count()) return qs.at(param); else return QObject::tr("Inconsistent parameter"); } else if (func==FuncReset) { QComboBox cb; CustomFunctionData::populateResetParams(model, &cb); int pos = cb.findData(param); if (pos >= 0) return cb.itemText(pos); else return QObject::tr("Inconsistent parameter"); } else if ((func==FuncVolume)|| (func==FuncPlayValue)) { RawSource item(param); return item.toString(model); } else if ((func==FuncPlayPrompt) || (func==FuncPlayBoth)) { if ( getCurrentFirmware()->getCapability(VoicesAsNumbers)) { return QString("%1").arg(param); } else { return paramarm; } } else if ((func>=FuncAdjustGV1) && (func=FuncOverrideCH1 && func<=FuncOverrideCH32) || (func>=FuncAdjustGV1 && func<=FuncAdjustGVLast) || (func==FuncReset) || (func>=FuncSetTimer1 && func<=FuncSetTimer2) || (func==FuncVolume) || (func <= FuncInstantTrim)) { if (!enabled) { return QObject::tr("DISABLED"); } } return ""; } CurveData::CurveData() { clear(5); } void CurveData::clear(int count) { memset(this, 0, sizeof(CurveData)); this->count = count; } bool CurveData::isEmpty() const { for (int i=0; igetBoardCapability(getCurrentBoard(), Board::Pots)) return false; return potConfig[index] != Board::POT_NONE; } bool GeneralSettings::isSliderAvailable(int index) const { if (index<0 || index>getBoardCapability(getCurrentBoard(), Board::Sliders)) return false; return sliderConfig[index] != Board::SLIDER_NONE; } GeneralSettings::GeneralSettings() { memset(this, 0, sizeof(GeneralSettings)); contrast = 25; vBatWarn = 90; for (int i=0; i < CPN_MAX_ANALOGS; ++i) { calibMid[i] = 0x200; calibSpanNeg[i] = 0x180; calibSpanPos[i] = 0x180; } Firmware * firmware = Firmware::getCurrentVariant(); Board::Type board = firmware->getBoard(); for (int i=0; i= CPN_MAX_STICKS) return -1; else return chout_ar[4*templateSetup + channel] - 1; } RawSource GeneralSettings::getDefaultSource(unsigned int channel) const { int stick = getDefaultStick(channel); if (stick >= 0) return RawSource(SOURCE_TYPE_STICK, stick); else return RawSource(SOURCE_TYPE_NONE); } int GeneralSettings::getDefaultChannel(unsigned int stick) const { for (int i=0; i<4; i++){ if (getDefaultStick(i) == (int)stick) return i; } return -1; } float FrSkyChannelData::getRatio() const { if (type==0 || type==1 || type==2) return float(ratio << multiplier) / 10.0; else return ratio << multiplier; } RawSourceRange FrSkyChannelData::getRange() const { RawSourceRange result; float ratio = getRatio(); if (type==0 || type==1 || type==2) result.decimals = 2; else result.decimals = 0; result.step = ratio / 255; result.min = offset * result.step; result.max = ratio + result.min; result.unit = QObject::tr("V"); return result; } void FrSkyScreenData::clear() { memset(this, 0, sizeof(FrSkyScreenData)); if (!IS_ARM(getCurrentBoard())) { type = TELEMETRY_SCREEN_NUMBERS; } } void FrSkyData::clear() { usrProto = 0; voltsSource = 0; altitudeSource = 0; currentSource = 0; varioMin = 0; varioCenterMin = 0; // if increment in 0.2m/s = 3.0m/s max varioCenterMax = 0; varioMax = 0; mAhPersistent = 0; storedMah = 0; fasOffset = 0; rssiAlarms[0].clear(2, 45); rssiAlarms[1].clear(3, 42); for (int i=0; i<4; i++) screens[i].clear(); varioSource = 2/*VARIO*/; blades = 2; } ModelData::ModelData() { clear(); } ModelData::ModelData(const ModelData & src) { *this = src; } ModelData & ModelData::operator = (const ModelData & src) { memcpy(this, &src, sizeof(ModelData)); return *this; } ExpoData * ModelData::insertInput(const int idx) { memmove(&expoData[idx+1], &expoData[idx], (CPN_MAX_EXPOS-(idx+1))*sizeof(ExpoData)); expoData[idx].clear(); return &expoData[idx]; } bool ModelData::isInputValid(const unsigned int idx) const { for (int i=0; imode == 0) break; if (expo->chn == idx) return true; } return false; } bool ModelData::hasExpos(uint8_t inputIdx) const { for (int i=0; i ModelData::expos(int input) const { QVector result; for (int i=0; ichn==input && ed->mode!=0) { result << ed; } } return result; } QVector ModelData::mixes(int channel) const { QVector result; for (int i=0; idestCh == channel+1) { result << md; } } return result; } void ModelData::removeInput(const int idx) { unsigned int chn = expoData[idx].chn; memmove(&expoData[idx], &expoData[idx+1], (CPN_MAX_EXPOS-(idx+1))*sizeof(ExpoData)); expoData[CPN_MAX_EXPOS-1].clear(); //also remove input name if removing last line for this input bool found = false; for (int i=0; igetCapability(VirtualInputs)) { for (int i=0; igetCapability(Models)); } void ModelData::clear() { memset(this, 0, sizeof(ModelData)); modelIndex = -1; // an invalid index, this is managed by the TreeView data model moduleData[0].channelsCount = 8; moduleData[1].channelsStart = 0; moduleData[1].channelsCount = 8; moduleData[0].ppm.delay = 300; moduleData[1].ppm.delay = 300; moduleData[2].ppm.delay = 300; int board = getCurrentBoard(); if (IS_HORUS_OR_TARANIS(board)) { moduleData[0].protocol = PULSES_PXX_XJT_X16; moduleData[1].protocol = PULSES_OFF; } else if (IS_SKY9X(board)) { moduleData[0].protocol = PULSES_PPM; moduleData[1].protocol = PULSES_PPM; } else { moduleData[0].protocol = PULSES_PPM; moduleData[1].protocol = PULSES_OFF; } for (int i=0; ichn = i; expo->mode = INPUT_MODE_BOTH; expo->srcRaw = settings.getDefaultSource(i); expo->weight = 100; strncpy(inputNames[i], removeAccents(expo->srcRaw.toString(this)).toLatin1().constData(), sizeof(inputNames[i])-1); } } } void ModelData::setDefaultMixes(const GeneralSettings & settings) { Board::Type board = getCurrentBoard(); if (IS_ARM(board)) { setDefaultInputs(settings); } for (int i=0; idestCh = i+1; mix->weight = 100; if (IS_ARM(board)) { mix->srcRaw = RawSource(SOURCE_TYPE_VIRTUAL_INPUT, i); } else { mix->srcRaw = RawSource(SOURCE_TYPE_STICK, i); } } } void ModelData::setDefaultValues(unsigned int id, const GeneralSettings & settings) { clear(); used = true; sprintf(name, "MODEL%02d", id+1); for (int i=0; i 1024; } int ModelData::getGVarFieldValue(int phaseIdx, int gvarIdx) { int idx = flightModeData[phaseIdx].gvars[gvarIdx]; for (int i=0; idx>1024 && i= phaseIdx) nextPhase += 1; phaseIdx = nextPhase; idx = flightModeData[phaseIdx].gvars[gvarIdx]; } return idx; } void ModelData::setTrimValue(int phaseIdx, int trimIdx, int value) { for (uint8_t i=0; i 500) trim = 500; break; } } } void ModelData::removeGlobalVar(int & var) { if (var >= 126 && var <= 130) var = flightModeData[0].gvars[var-126]; else if (var <= -126 && var >= -130) var = - flightModeData[0].gvars[-126-var]; } ModelData ModelData::removeGlobalVars() { ModelData result = *this; for (int i=0; i eepromInterfaces; void unregisterEEpromInterfaces() { foreach(EEPROMInterface * intf, eepromInterfaces) { // qDebug() << "UnregisterEepromInterfaces(): deleting " << QString::number( reinterpret_cast(intf), 16 ); delete intf; } OpenTxEepromCleanup(); } void ShowEepromErrors(QWidget *parent, const QString &title, const QString &mainMessage, unsigned long errorsFound) { std::bitset errors((unsigned long long)errorsFound); QStringList errorsList; errorsList << QT_TRANSLATE_NOOP("EepromInterface", "Possible causes for this:"); if (errors.test(UNSUPPORTED_NEWER_VERSION)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is from a newer version of OpenTX"); } if (errors.test(NOT_OPENTX)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from OpenTX"); } if (errors.test(NOT_TH9X)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from Th9X"); } if (errors.test(NOT_GRUVIN9X)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from Gruvin9X"); } if (errors.test(NOT_ERSKY9X)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from ErSky9X"); } if (errors.test(NOT_ER9X)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is not from Er9X"); } if (errors.test(WRONG_SIZE)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom size is invalid"); } if (errors.test(WRONG_FILE_SYSTEM)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom file system is invalid"); } if (errors.test(UNKNOWN_BOARD)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is from a unknown board"); } if (errors.test(WRONG_BOARD)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom is from the wrong board"); } if (errors.test(BACKUP_NOT_SUPPORTED)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Eeprom backup not supported"); } if (errors.test(UNKNOWN_ERROR)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Something that couldn't be guessed, sorry"); } if (errors.test(HAS_WARNINGS)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "Warning:"); if (errors.test(WARNING_WRONG_FIRMWARE)) { errorsList << QT_TRANSLATE_NOOP("EepromInterface", "- Your radio probably uses a wrong firmware,\n eeprom size is 4096 but only the first 2048 are used"); } } QMessageBox msgBox(parent); msgBox.setWindowTitle(title); msgBox.setIcon(QMessageBox::Critical); msgBox.setText(mainMessage); msgBox.setInformativeText(errorsList.join("\n")); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.exec(); } void ShowEepromWarnings(QWidget *parent, const QString &title, unsigned long errorsFound) { std::bitset errors((unsigned long long)errorsFound); QStringList warningsList; if (errors.test(WARNING_WRONG_FIRMWARE)) { warningsList << QT_TRANSLATE_NOOP("EepromInterface", "- Your radio probably uses a wrong firmware,\n eeprom size is 4096 but only the first 2048 are used"); } if (errors.test(OLD_VERSION)) { warningsList << QT_TRANSLATE_NOOP("EepromInterface", "- Your eeprom is from an old version of OpenTX, upgrading!\n You should 'save as' to keep the old file as a backup."); } QMessageBox msgBox(parent); msgBox.setWindowTitle(title); msgBox.setIcon(QMessageBox::Warning); msgBox.setText(QT_TRANSLATE_NOOP("EepromInterface", "Warnings!")); msgBox.setInformativeText(warningsList.join("\n")); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.exec(); } // static QVector Firmware::registeredFirmwares; Firmware * Firmware::defaultVariant = NULL; Firmware * Firmware::currentVariant = NULL; // static Firmware * Firmware::getFirmwareForId(const QString & id) { foreach(Firmware * firmware, registeredFirmwares) { Firmware * result = firmware->getFirmwareVariant(id); if (result) { return result; } } return defaultVariant; } void Firmware::addOption(const char *option, QString tooltip, uint32_t variant) { Option options[] = { { option, tooltip, variant }, { NULL } }; addOptions(options); } unsigned int Firmware::getVariantNumber() { unsigned int result = 0; const Firmware * base = getFirmwareBase(); QStringList options = id.mid(base->getId().length()+1).split("-", QString::SkipEmptyParts); foreach(QString option, options) { foreach(QList