Update CREDITS.txt
[opentx.git] / companion / src / firmwares / modeldata.cpp
blob9a9c7157977e6c5cb1a2d603e387ea95b051716f
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "modeldata.h"
23 #include "eeprominterface.h"
24 #include "generalsettings.h"
25 #include "macros.h"
26 #include "radiodataconversionstate.h"
29 QString removeAccents(const QString & str)
31 QString result = str;
33 // UTF-8 ASCII Table
34 const QString tA[] = { "á", "â", "ã", "à", "ä" };
35 const QString tE[] = { "é", "è", "ê", "ě" };
36 const QString tI[] = { "í" };
37 const QString tO[] = { "ó", "ô", "õ", "ö" };
38 const QString tU[] = { "ú", "ü" };
39 const QString tC[] = { "ç" };
40 const QString tY[] = { "ý" };
41 const QString tS[] = { "š" };
42 const QString tR[] = { "ř" };
44 for (unsigned int i = 0; i < DIM(tA); i++) result.replace(tA[i], "a");
45 for (unsigned int i = 0; i < DIM(tE); i++) result.replace(tE[i], "e");
46 for (unsigned int i = 0; i < DIM(tI); i++) result.replace(tI[i], "i");
47 for (unsigned int i = 0; i < DIM(tO); i++) result.replace(tO[i], "o");
48 for (unsigned int i = 0; i < DIM(tU); i++) result.replace(tU[i], "u");
49 for (unsigned int i = 0; i < DIM(tC); i++) result.replace(tC[i], "c");
50 for (unsigned int i = 0; i < DIM(tY); i++) result.replace(tY[i], "y");
51 for (unsigned int i = 0; i < DIM(tS); i++) result.replace(tS[i], "s");
52 for (unsigned int i = 0; i < DIM(tR); i++) result.replace(tR[i], "r");
54 return result;
59 * TimerData
62 void TimerData::convert(RadioDataConversionState & cstate)
64 cstate.setComponent(tr("TMR"), 1);
65 cstate.setSubComp(tr("Timer %1").arg(cstate.subCompIdx + 1));
66 mode.convert(cstate);
71 * ModelData
74 ModelData::ModelData()
76 clear();
79 ModelData::ModelData(const ModelData & src)
81 *this = src;
84 ModelData & ModelData::operator = (const ModelData & src)
86 memcpy(this, &src, sizeof(ModelData));
87 return *this;
90 ExpoData * ModelData::insertInput(const int idx)
92 memmove(&expoData[idx+1], &expoData[idx], (CPN_MAX_EXPOS-(idx+1))*sizeof(ExpoData));
93 expoData[idx].clear();
94 return &expoData[idx];
97 bool ModelData::isInputValid(const unsigned int idx) const
99 for (int i=0; i<CPN_MAX_EXPOS; i++) {
100 const ExpoData * expo = &expoData[i];
101 if (expo->mode == 0) break;
102 if (expo->chn == idx)
103 return true;
105 return false;
108 bool ModelData::hasExpos(uint8_t inputIdx) const
110 for (int i=0; i<CPN_MAX_EXPOS; i++) {
111 const ExpoData & expo = expoData[i];
112 if (expo.chn==inputIdx && expo.mode!=0) {
113 return true;
116 return false;
119 bool ModelData::hasMixes(uint8_t channelIdx) const
121 channelIdx += 1;
122 for (int i=0; i<CPN_MAX_MIXERS; i++) {
123 if (mixData[i].destCh == channelIdx) {
124 return true;
127 return false;
130 QVector<const ExpoData *> ModelData::expos(int input) const
132 QVector<const ExpoData *> result;
133 for (int i=0; i<CPN_MAX_EXPOS; i++) {
134 const ExpoData * ed = &expoData[i];
135 if ((int)ed->chn==input && ed->mode!=0) {
136 result << ed;
139 return result;
142 QVector<const MixData *> ModelData::mixes(int channel) const
144 QVector<const MixData *> result;
145 for (int i=0; i<CPN_MAX_MIXERS; i++) {
146 const MixData * md = &mixData[i];
147 if ((int)md->destCh == channel+1) {
148 result << md;
151 return result;
154 void ModelData::removeInput(const int idx)
156 unsigned int chn = expoData[idx].chn;
158 memmove(&expoData[idx], &expoData[idx+1], (CPN_MAX_EXPOS-(idx+1))*sizeof(ExpoData));
159 expoData[CPN_MAX_EXPOS-1].clear();
161 //also remove input name if removing last line for this input
162 bool found = false;
163 for (int i=0; i<CPN_MAX_EXPOS; i++) {
164 if (expoData[i].mode==0) continue;
165 if (expoData[i].chn==chn) {
166 found = true;
167 break;
170 if (!found) inputNames[chn][0] = 0;
173 void ModelData::clearInputs()
175 for (int i=0; i<CPN_MAX_EXPOS; i++)
176 expoData[i].clear();
178 //clear all input names
179 if (getCurrentFirmware()->getCapability(VirtualInputs)) {
180 for (int i=0; i<CPN_MAX_INPUTS; i++) {
181 inputNames[i][0] = 0;
186 void ModelData::clearMixes()
188 for (int i=0; i<CPN_MAX_MIXERS; i++)
189 mixData[i].clear();
192 void ModelData::clear()
194 memset(this, 0, sizeof(ModelData));
195 modelIndex = -1; // an invalid index, this is managed by the TreeView data model
196 moduleData[0].channelsCount = 8;
197 moduleData[1].channelsStart = 0;
198 moduleData[1].channelsCount = 8;
199 moduleData[0].ppm.delay = 300;
200 moduleData[1].ppm.delay = 300;
201 moduleData[2].ppm.delay = 300;
202 int board = getCurrentBoard();
203 if (IS_HORUS_OR_TARANIS(board)) {
204 moduleData[0].protocol = PULSES_PXX_XJT_X16;
205 moduleData[1].protocol = PULSES_OFF;
207 else if (IS_SKY9X(board)) {
208 moduleData[0].protocol = PULSES_PPM;
209 moduleData[1].protocol = PULSES_PPM;
211 else {
212 moduleData[0].protocol = PULSES_PPM;
213 moduleData[1].protocol = PULSES_OFF;
215 for (int i=0; i<CPN_MAX_FLIGHT_MODES; i++) {
216 flightModeData[i].clear(i);
218 for (int i=0; i<CPN_MAX_GVARS; i++) {
219 gvarData[i].clear();
221 clearInputs();
222 clearMixes();
223 for (int i=0; i<CPN_MAX_CHNOUT; i++)
224 limitData[i].clear();
225 for (int i=0; i<CPN_MAX_STICKS; i++)
226 expoData[i].clear();
227 for (int i=0; i<CPN_MAX_LOGICAL_SWITCHES; i++)
228 logicalSw[i].clear();
229 for (int i=0; i<CPN_MAX_SPECIAL_FUNCTIONS; i++)
230 customFn[i].clear();
231 for (int i=0; i<CPN_MAX_CURVES; i++)
232 curves[i].clear(5);
233 for (int i=0; i<CPN_MAX_TIMERS; i++)
234 timers[i].clear();
235 swashRingData.clear();
236 frsky.clear();
237 rssiAlarms.clear();
238 for (int i=0; i<CPN_MAX_SENSORS; i++)
239 sensorData[i].clear();
241 static const uint8_t blob[] = { 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x32, 0x50, 0x31, 0x00, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x42, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
242 memcpy(customScreenData[0], blob, sizeof(blob));
245 bool ModelData::isEmpty() const
247 return !used;
250 void ModelData::setDefaultInputs(const GeneralSettings & settings)
252 Board::Type board = getCurrentBoard();
253 if (IS_ARM(board)) {
254 for (int i=0; i<CPN_MAX_STICKS; i++) {
255 ExpoData * expo = &expoData[i];
256 expo->chn = i;
257 expo->mode = INPUT_MODE_BOTH;
258 expo->srcRaw = settings.getDefaultSource(i);
259 expo->weight = 100;
260 strncpy(inputNames[i], removeAccents(expo->srcRaw.toString(this)).toLatin1().constData(), sizeof(inputNames[i])-1);
265 void ModelData::setDefaultMixes(const GeneralSettings & settings)
267 Board::Type board = getCurrentBoard();
268 if (IS_ARM(board)) {
269 setDefaultInputs(settings);
272 for (int i=0; i<CPN_MAX_STICKS; i++) {
273 MixData * mix = &mixData[i];
274 mix->destCh = i+1;
275 mix->weight = 100;
276 if (IS_ARM(board)) {
277 mix->srcRaw = RawSource(SOURCE_TYPE_VIRTUAL_INPUT, i);
279 else {
280 mix->srcRaw = RawSource(SOURCE_TYPE_STICK, i);
285 void ModelData::setDefaultValues(unsigned int id, const GeneralSettings & settings)
287 clear();
288 used = true;
289 sprintf(name, "MODEL%02d", id+1);
290 for (int i=0; i<CPN_MAX_MODULES; i++) {
291 moduleData[i].modelId = id+1;
293 setDefaultMixes(settings);
296 int ModelData::getTrimValue(int phaseIdx, int trimIdx)
298 int result = 0;
299 for (int i=0; i<CPN_MAX_FLIGHT_MODES; i++) {
300 FlightModeData & phase = flightModeData[phaseIdx];
301 if (phase.trimMode[trimIdx] < 0) {
302 return result;
304 else {
305 if (phase.trimRef[trimIdx] == phaseIdx || phaseIdx == 0) {
306 return result + phase.trim[trimIdx];
308 else {
309 phaseIdx = phase.trimRef[trimIdx];
310 if (phase.trimMode[trimIdx] != 0)
311 result += phase.trim[trimIdx];
315 return 0;
318 bool ModelData::isGVarLinked(int phaseIdx, int gvarIdx)
320 return flightModeData[phaseIdx].gvars[gvarIdx] > 1024;
323 int ModelData::getGVarFieldValue(int phaseIdx, int gvarIdx)
325 int idx = flightModeData[phaseIdx].gvars[gvarIdx];
326 for (int i=0; idx>GVAR_MAX_VALUE && i<CPN_MAX_FLIGHT_MODES; i++) {
327 int nextPhase = idx - GVAR_MAX_VALUE - 1;
328 if (nextPhase >= phaseIdx) nextPhase += 1;
329 phaseIdx = nextPhase;
330 idx = flightModeData[phaseIdx].gvars[gvarIdx];
332 return idx;
335 void ModelData::setTrimValue(int phaseIdx, int trimIdx, int value)
337 for (uint8_t i=0; i<CPN_MAX_FLIGHT_MODES; i++) {
338 FlightModeData & phase = flightModeData[phaseIdx];
339 int mode = phase.trimMode[trimIdx];
340 int p = phase.trimRef[trimIdx];
341 int & trim = phase.trim[trimIdx];
342 if (mode < 0)
343 return;
344 if (p == phaseIdx || phaseIdx == 0) {
345 trim = value;
346 break;;
348 else if (mode == 0) {
349 phaseIdx = p;
351 else {
352 trim = value - getTrimValue(p, trimIdx);
353 if (trim < -500)
354 trim = -500;
355 if (trim > 500)
356 trim = 500;
357 break;
362 void ModelData::removeGlobalVar(int & var)
364 if (var >= 126 && var <= 130)
365 var = flightModeData[0].gvars[var-126];
366 else if (var <= -126 && var >= -130)
367 var = - flightModeData[0].gvars[-126-var];
370 ModelData ModelData::removeGlobalVars()
372 ModelData result = *this;
374 for (int i=0; i<CPN_MAX_MIXERS; i++) {
375 removeGlobalVar(mixData[i].weight);
376 removeGlobalVar(mixData[i].curve.value);
377 removeGlobalVar(mixData[i].sOffset);
380 for (int i=0; i<CPN_MAX_EXPOS; i++) {
381 removeGlobalVar(expoData[i].weight);
382 removeGlobalVar(expoData[i].curve.value);
385 return result;
388 int ModelData::getChannelsMax(bool forceExtendedLimits) const
390 if (forceExtendedLimits || extendedLimits)
391 return IS_HORUS_OR_TARANIS(getCurrentBoard()) ? 150 : 125;
392 else
393 return 100;
396 bool ModelData::isAvailable(const RawSwitch & swtch) const
398 unsigned index = abs(swtch.index) - 1;
400 if (swtch.type == SWITCH_TYPE_VIRTUAL) {
401 return logicalSw[index].func != LS_FN_OFF;
403 else if (swtch.type == SWITCH_TYPE_FLIGHT_MODE) {
404 return index == 0 || flightModeData[index].swtch.type != SWITCH_TYPE_NONE;
406 else if (swtch.type == SWITCH_TYPE_SENSOR) {
407 return strlen(sensorData[index].label) > 0;
409 else {
410 return true;
414 float ModelData::getGVarFieldValuePrec(int phaseIdx, int gvarIdx)
416 return getGVarFieldValue(phaseIdx, gvarIdx) * gvarData[gvarIdx].multiplierGet();
419 void ModelData::convert(RadioDataConversionState & cstate)
421 // Here we can add explicit conversions when moving from one board to another
423 QString origin = QString(name);
424 if (origin.isEmpty())
425 origin = QString::number(cstate.modelIdx+1);
426 cstate.setOrigin(tr("Model: ") % origin);
428 cstate.setComponent("SET", 0);
429 if (thrTraceSrc && (int)thrTraceSrc < cstate.fromBoard.getCapability(Board::Pots) + cstate.fromBoard.getCapability(Board::Sliders)) {
430 cstate.setSubComp(tr("Throttle Source"));
431 thrTraceSrc = RawSource(SOURCE_TYPE_STICK, (int)thrTraceSrc + 3).convert(cstate).index - 3;
434 for (int i=0; i<CPN_MAX_TIMERS; i++) {
435 timers[i].convert(cstate.withComponentIndex(i));
438 for (int i=0; i<CPN_MAX_MIXERS; i++) {
439 mixData[i].convert(cstate.withComponentIndex(i));
442 for (int i=0; i<CPN_MAX_EXPOS; i++) {
443 expoData[i].convert(cstate.withComponentIndex(i));
446 for (int i=0; i<CPN_MAX_LOGICAL_SWITCHES; i++) {
447 logicalSw[i].convert(cstate.withComponentIndex(i));
450 for (int i=0; i<CPN_MAX_SPECIAL_FUNCTIONS; i++) {
451 customFn[i].convert(cstate.withComponentIndex(i));
454 for (int i=0; i<CPN_MAX_FLIGHT_MODES; i++) {
455 flightModeData[i].convert(cstate.withComponentIndex(i));