dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
configinputwidget.cpp
Go to the documentation of this file.
1 
14 /*
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful, but
21  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23  * for more details.
24  *
25  * You should have received a copy of the GNU General Public License along
26  * with this program; if not, see <http://www.gnu.org/licenses/>
27  *
28  * Additional note on redistribution: The copyright and license notices above
29  * must be maintained in each individual source file that is a derivative work
30  * of this source file; otherwise redistribution is prohibited.
31  */
32 
33 #include "configinputwidget.h"
34 
36 
37 #include <QDebug>
38 #include <QStringList>
39 #include <QWidget>
40 #include <QTextEdit>
41 #include <QVBoxLayout>
42 #include <QPushButton>
43 #include <QDesktopServices>
44 #include <QUrl>
45 #include <QMessageBox>
46 
49 
50 #include "actuatorcommand.h"
51 #include "actuatorsettings.h"
52 #include "stabilizationsettings.h"
53 #include "systemsettings.h"
54 
55 // The fraction of range to place the neutral position in
56 #define THROTTLE_NEUTRAL_FRACTION 0.02
57 #define ARMING_NEUTRAL_FRACTION 0.62
58 
59 #define ACCESS_MIN_MOVE -3
60 #define ACCESS_MAX_MOVE 3
61 #define STICK_MIN_MOVE -8
62 #define STICK_MAX_MOVE 8
63 
64 #define MIN_SANE_CHANNEL_VALUE 15
65 #define MAX_SANE_CHANNEL_VALUE 32768
66 #define MIN_SANE_RANGE 125
67 
68 // Should exceed "MIN_MEANINGFUL_RANGE" in transmitter_control.c (40)
69 // Needed to avoid failsafe during input wizard which confounds the stick
70 // movements. Also, life the universe and everything.
71 #define INITIAL_OFFSET 42
72 
73 const QVector<ConfigInputWidget::ArmingMethod> ConfigInputWidget::armingMethods = {
74  { ConfigInputWidget::ARM_ALWAYS_DISARMED, "always disarmed", "always disarmed",
75  "always disarmed", "Always Disarmed", false, false, true },
76  { ConfigInputWidget::ARM_SWITCH, "switch", "a switch", "a switch", "Switch", true, false,
77  false },
78  { ConfigInputWidget::ARM_ROLL_LEFT, "roll left", "roll left", "roll right",
79  "Roll Left+Throttle", false, true, false },
80  { ConfigInputWidget::ARM_ROLL_RIGHT, "roll right", "roll right", "roll left",
81  "Roll Right+Throttle", false, true, false },
82  { ConfigInputWidget::ARM_YAW_LEFT, "yaw left", "yaw left", "yaw right", "Yaw Left+Throttle",
83  false, true, false },
84  { ConfigInputWidget::ARM_YAW_RIGHT, "yaw right", "yaw right", "yaw left", "Yaw Right+Throttle",
85  false, true, false },
86  { ConfigInputWidget::ARM_CORNERS, "corners", "roll left and yaw right",
87  "roll right and yaw left", "Corners+Throttle", false, true, false },
88 };
89 
91  : ConfigTaskWidget(parent)
92  , wizardStep(wizardNone)
93  , transmitterType(heli)
94  , loop(NULL)
95  , skipflag(false)
96  , cbArmingOption(nullptr)
97  , lastArmingMethod(ARM_INVALID)
98  , armingConfigUpdating(false)
99 {
100  manualCommandObj = ManualControlCommand::GetInstance(getObjectManager());
101  manualSettingsObj = ManualControlSettings::GetInstance(getObjectManager());
102  flightStatusObj = FlightStatus::GetInstance(getObjectManager());
103  receiverActivityObj = ReceiverActivity::GetInstance(getObjectManager());
104  m_config = new Ui_InputWidget();
105  m_config->setupUi(this);
106 
107  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
108  Q_ASSERT(pm);
109  // Get telemetry manager and make sure it is valid
110  telMngr = pm->getObject<TelemetryManager>();
111  Q_ASSERT(telMngr);
112 
113  // Generate the rows of buttons in the input channel form GUI
114  int index = 0;
115  foreach (QString name, manualSettingsObj->getField("ChannelNumber")->getElementNames()) {
116  Q_ASSERT(index < static_cast<int>(ManualControlSettings::CHANNELGROUPS_NUMELEM));
117  inputChannelForm *inpForm = new inputChannelForm(this, index == 0, true);
118  m_config->channelSettings->layout()->addWidget(inpForm); // Add the row to the UI
119  inpForm->setName(name);
120  addUAVObjectToWidgetRelation("ManualControlSettings", "ChannelGroups",
121  inpForm->ui->channelGroup, index);
122  addUAVObjectToWidgetRelation("ManualControlSettings", "ChannelNumber",
123  inpForm->ui->channelNumber, index);
124  addUAVObjectToWidgetRelation("ManualControlSettings", "ChannelMin", inpForm->ui->channelMin,
125  index);
126  addUAVObjectToWidgetRelation("ManualControlSettings", "ChannelMax", inpForm->ui->channelMax,
127  index);
128  addUAVObjectToWidgetRelation("ManualControlSettings", "ChannelNeutral",
129  inpForm->ui->channelNeutral, index);
130 
131  // we warn the user if collective is assigned on multirotors (it's a rare config)
132  if (index == ManualControlSettings::CHANNELGROUPS_COLLECTIVE) {
133  connect(inpForm, &inputChannelForm::assignmentChanged,
134  this, &ConfigInputWidget::checkCollective);
135  }
136 
137  int index2 = manualCommandObj->getField("Channel")->getElementNames().indexOf(name);
138  if (index2 >= 0) {
139  addUAVObjectToWidgetRelation("ManualControlCommand", "Channel",
140  inpForm->ui->channelCurrent, index2);
141  addUAVObjectToWidgetRelation("ManualControlCommand", "Channel",
142  inpForm->sbChannelCurrent, index2);
143  }
144  ++index;
145  }
146 
147  // RSSI
148  inputChannelForm *inpForm =
149  new inputChannelForm(this, false, false, inputChannelForm::CHANNELFUNC_RSSI);
150  m_config->channelSettings->layout()->addWidget(inpForm);
151  QString name = "RSSI";
152  inpForm->setName(name);
153  addUAVObjectToWidgetRelation("ManualControlSettings", "RssiType", inpForm->ui->channelGroup, 0);
154  addUAVObjectToWidgetRelation("ManualControlSettings", "RssiChannelNumber",
155  inpForm->ui->channelNumber, 0);
156  addUAVObjectToWidgetRelation("ManualControlSettings", "RssiMin", inpForm->ui->channelMin, 0);
157  addUAVObjectToWidgetRelation("ManualControlSettings", "RssiMax", inpForm->ui->channelMax, 0);
158  addUAVObjectToWidgetRelation("ManualControlCommand", "RawRssi", inpForm->ui->channelCurrent, 0);
159 
160  connect(m_config->configurationWizard, &QAbstractButton::clicked, this,
161  &ConfigInputWidget::goToWizard);
162  connect(m_config->runCalibration, &QAbstractButton::toggled, this,
163  &ConfigInputWidget::simpleCalibration);
164 
165  connect(m_config->wzNext, &QAbstractButton::clicked, this, &ConfigInputWidget::wzNext);
166  connect(m_config->wzCancel, &QAbstractButton::clicked, this, &ConfigInputWidget::wzCancel);
167  connect(m_config->wzBack, &QAbstractButton::clicked, this, &ConfigInputWidget::wzBack);
168 
169  m_config->stackedWidget->setCurrentIndex(0);
170 
171  // connect this before the widgets are populated to ensure it always fires
172  connect(ManualControlCommand::GetInstance(getObjectManager()), &UAVObject::objectUpdated, this,
173  &ConfigInputWidget::moveFMSlider);
174  connect(ManualControlSettings::GetInstance(getObjectManager()), &UAVObject::objectUpdated, this,
175  &ConfigInputWidget::updatePositionSlider);
176 
177  // check reprojection settings
178  const QStringList axes({ "Roll", "Pitch", "Yaw", "Rep" });
179  for (int i = 1; i <= 3; i++) {
180  foreach (const QString &axis, axes) {
181  QComboBox *child =
182  this->findChild<QComboBox *>(QString("fmsSsPos%1%2").arg(i).arg(axis));
183  if (child)
184  connect(child, &QComboBox::currentTextChanged, this,
185  &ConfigInputWidget::checkReprojection);
186  }
187  }
188 
189  // check things that affect arming config
190  fillArmingComboBox();
191  ActuatorSettings *actuatorSettings =
192  qobject_cast<ActuatorSettings *>(getObjectManager()->getObject(ActuatorSettings::NAME));
193  connect(actuatorSettings, &UAVObject::objectUpdated, this,
194  &ConfigInputWidget::checkArmingConfig);
195  connect(m_config->cbArmMethod, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
196  &ConfigInputWidget::checkArmingConfig);
197  connect(m_config->cbThrottleCheck, &QCheckBox::stateChanged, this,
198  &ConfigInputWidget::checkArmingConfig);
199  connect(m_config->sbThrottleTimeout, QOverload<int>::of(&QSpinBox::valueChanged), this,
200  &ConfigInputWidget::checkArmingConfig);
201  connect(m_config->sbFailsafeTimeout, QOverload<int>::of(&QSpinBox::valueChanged), this,
202  &ConfigInputWidget::checkArmingConfig);
203  connect(m_config->cbFailsafeTimeout, &QCheckBox::stateChanged, this,
204  &ConfigInputWidget::timeoutCheckboxChanged);
205  connect(m_config->cbThrottleTimeout, &QCheckBox::stateChanged, this,
206  &ConfigInputWidget::timeoutCheckboxChanged);
207  connect(ManualControlSettings::GetInstance(getObjectManager()), &UAVObject::objectUpdated, this,
208  &ConfigInputWidget::updateArmingConfig);
209 
210  // create a hidden widget so uavo->widget relation can take care of some of the work
211  cbArmingOption = new QComboBox(this);
212  cbArmingOption->setVisible(false);
213  cbArmingOption->setProperty("objrelation",
214  QStringList{ "objname:ManualControlSettings", "fieldname:Arming" });
215  m_config->gbArming->layout()->addWidget(cbArmingOption);
216 
217  enableControls(false);
218 
219  autoLoadWidgets();
220 
221  populateWidgets();
223 
224  m_config->graphicsView->setScene(new QGraphicsScene(this));
225  m_config->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
226  m_renderer = new QSvgRenderer();
227  QGraphicsScene *l_scene = m_config->graphicsView->scene();
228  if (QFile::exists(":/configgadget/images/TX2.svg")
229  && m_renderer->load(QString(":/configgadget/images/TX2.svg")) && m_renderer->isValid()) {
230  l_scene->clear(); // Deletes all items contained in the scene as well.
231 
232  m_txBackground = new QGraphicsSvgItem();
233  // All other items will be clipped to the shape of the background
234  m_txBackground->setFlags(QGraphicsItem::ItemClipsChildrenToShape
235  | QGraphicsItem::ItemClipsToShape);
236  m_txBackground->setSharedRenderer(m_renderer);
237  m_txBackground->setElementId("background");
238  l_scene->addItem(m_txBackground);
239 
240  m_txMainBody = new QGraphicsSvgItem();
241  m_txMainBody->setParentItem(m_txBackground);
242  m_txMainBody->setSharedRenderer(m_renderer);
243  m_txMainBody->setElementId("body");
244 
245  m_txLeftStick = new QGraphicsSvgItem();
246  m_txLeftStick->setParentItem(m_txBackground);
247  m_txLeftStick->setSharedRenderer(m_renderer);
248  m_txLeftStick->setElementId("ljoy");
249 
250  m_txRightStick = new QGraphicsSvgItem();
251  m_txRightStick->setParentItem(m_txBackground);
252  m_txRightStick->setSharedRenderer(m_renderer);
253  m_txRightStick->setElementId("rjoy");
254 
255  m_txAccess0 = new QGraphicsSvgItem();
256  m_txAccess0->setParentItem(m_txBackground);
257  m_txAccess0->setSharedRenderer(m_renderer);
258  m_txAccess0->setElementId("access0");
259 
260  m_txAccess1 = new QGraphicsSvgItem();
261  m_txAccess1->setParentItem(m_txBackground);
262  m_txAccess1->setSharedRenderer(m_renderer);
263  m_txAccess1->setElementId("access1");
264 
265  m_txAccess2 = new QGraphicsSvgItem();
266  m_txAccess2->setParentItem(m_txBackground);
267  m_txAccess2->setSharedRenderer(m_renderer);
268  m_txAccess2->setElementId("access2");
269 
270  m_txFlightMode = new QGraphicsSvgItem();
271  m_txFlightMode->setParentItem(m_txBackground);
272  m_txFlightMode->setSharedRenderer(m_renderer);
273  m_txFlightMode->setElementId("flightModeCenter");
274  m_txFlightMode->setZValue(-10);
275 
276  m_txArming = new QGraphicsSvgItem();
277  m_txArming->setParentItem(m_txBackground);
278  m_txArming->setSharedRenderer(m_renderer);
279  m_txArming->setElementId("armedswitchleft");
280  m_txArming->setZValue(-10);
281 
282  m_txArrows = new QGraphicsSvgItem();
283  m_txArrows->setParentItem(m_txBackground);
284  m_txArrows->setSharedRenderer(m_renderer);
285  m_txArrows->setElementId("arrows");
286  m_txArrows->setVisible(false);
287 
288  QRectF orig = m_renderer->boundsOnElement("ljoy");
289  QMatrix Matrix = m_renderer->matrixForElement("ljoy");
290  orig = Matrix.mapRect(orig);
291  m_txLeftStickOrig.translate(orig.x(), orig.y());
292  m_txLeftStick->setTransform(m_txLeftStickOrig, false);
293 
294  orig = m_renderer->boundsOnElement("arrows");
295  Matrix = m_renderer->matrixForElement("arrows");
296  orig = Matrix.mapRect(orig);
297  m_txArrowsOrig.translate(orig.x(), orig.y());
298  m_txArrows->setTransform(m_txArrowsOrig, false);
299 
300  orig = m_renderer->boundsOnElement("body");
301  Matrix = m_renderer->matrixForElement("body");
302  orig = Matrix.mapRect(orig);
303  m_txMainBodyOrig.translate(orig.x(), orig.y());
304  m_txMainBody->setTransform(m_txMainBodyOrig, false);
305 
306  orig = m_renderer->boundsOnElement("flightModeCenter");
307  Matrix = m_renderer->matrixForElement("flightModeCenter");
308  orig = Matrix.mapRect(orig);
309  m_txFlightModeCOrig.translate(orig.x(), orig.y());
310  m_txFlightMode->setTransform(m_txFlightModeCOrig, false);
311 
312  orig = m_renderer->boundsOnElement("flightModeLeft");
313  Matrix = m_renderer->matrixForElement("flightModeLeft");
314  orig = Matrix.mapRect(orig);
315  m_txFlightModeLOrig.translate(orig.x(), orig.y());
316 
317  orig = m_renderer->boundsOnElement("flightModeRight");
318  Matrix = m_renderer->matrixForElement("flightModeRight");
319  orig = Matrix.mapRect(orig);
320  m_txFlightModeROrig.translate(orig.x(), orig.y());
321 
322  orig = m_renderer->boundsOnElement("armedswitchright");
323  Matrix = m_renderer->matrixForElement("armedswitchright");
324  orig = Matrix.mapRect(orig);
325  m_txArmingSwitchOrigR.translate(orig.x(), orig.y());
326 
327  orig = m_renderer->boundsOnElement("armedswitchleft");
328  Matrix = m_renderer->matrixForElement("armedswitchleft");
329  orig = Matrix.mapRect(orig);
330  m_txArmingSwitchOrigL.translate(orig.x(), orig.y());
331 
332  orig = m_renderer->boundsOnElement("rjoy");
333  Matrix = m_renderer->matrixForElement("rjoy");
334  orig = Matrix.mapRect(orig);
335  m_txRightStickOrig.translate(orig.x(), orig.y());
336  m_txRightStick->setTransform(m_txRightStickOrig, false);
337 
338  orig = m_renderer->boundsOnElement("access0");
339  Matrix = m_renderer->matrixForElement("access0");
340  orig = Matrix.mapRect(orig);
341  m_txAccess0Orig.translate(orig.x(), orig.y());
342  m_txAccess0->setTransform(m_txAccess0Orig, false);
343 
344  orig = m_renderer->boundsOnElement("access1");
345  Matrix = m_renderer->matrixForElement("access1");
346  orig = Matrix.mapRect(orig);
347  m_txAccess1Orig.translate(orig.x(), orig.y());
348  m_txAccess1->setTransform(m_txAccess1Orig, false);
349 
350  orig = m_renderer->boundsOnElement("access2");
351  Matrix = m_renderer->matrixForElement("access2");
352  orig = Matrix.mapRect(orig);
353  m_txAccess2Orig.translate(orig.x(), orig.y());
354  m_txAccess2->setTransform(m_txAccess2Orig, true);
355  }
356  m_config->graphicsView->fitInView(m_txMainBody, Qt::KeepAspectRatio);
357  m_config->graphicsView->setStyleSheet("background: transparent");
358  animate = new QTimer(this);
359  connect(animate, &QTimer::timeout, this, &ConfigInputWidget::moveTxControls);
360 
361  heliChannelOrder << ManualControlSettings::CHANNELGROUPS_COLLECTIVE
362  << ManualControlSettings::CHANNELGROUPS_THROTTLE
363  << ManualControlSettings::CHANNELGROUPS_ROLL
364  << ManualControlSettings::CHANNELGROUPS_PITCH
365  << ManualControlSettings::CHANNELGROUPS_YAW
366  << ManualControlSettings::CHANNELGROUPS_FLIGHTMODE
367  << ManualControlSettings::CHANNELGROUPS_ACCESSORY0
368  << ManualControlSettings::CHANNELGROUPS_ACCESSORY1
369  << ManualControlSettings::CHANNELGROUPS_ACCESSORY2
370  << ManualControlSettings::CHANNELGROUPS_ARMING;
371 
372  acroChannelOrder << ManualControlSettings::CHANNELGROUPS_THROTTLE
373  << ManualControlSettings::CHANNELGROUPS_ROLL
374  << ManualControlSettings::CHANNELGROUPS_PITCH
375  << ManualControlSettings::CHANNELGROUPS_YAW
376  << ManualControlSettings::CHANNELGROUPS_FLIGHTMODE
377  << ManualControlSettings::CHANNELGROUPS_ACCESSORY0
378  << ManualControlSettings::CHANNELGROUPS_ACCESSORY1
379  << ManualControlSettings::CHANNELGROUPS_ACCESSORY2
380  << ManualControlSettings::CHANNELGROUPS_ARMING;
381 
382  // force the initial tab
383  m_config->tabWidget->setCurrentIndex(0);
384 }
385 void ConfigInputWidget::resetTxControls()
386 {
387 
388  m_txLeftStick->setTransform(m_txLeftStickOrig, false);
389  m_txRightStick->setTransform(m_txRightStickOrig, false);
390  m_txAccess0->setTransform(m_txAccess0Orig, false);
391  m_txAccess1->setTransform(m_txAccess1Orig, false);
392  m_txAccess2->setTransform(m_txAccess2Orig, false);
393  m_txFlightMode->setElementId("flightModeCenter");
394  m_txFlightMode->setTransform(m_txFlightModeCOrig, false);
395  m_txArming->setElementId("armedswitchleft");
396  m_txArming->setTransform(m_txArmingSwitchOrigL, false);
397  m_txArrows->setVisible(false);
398 }
399 
401 
402 void ConfigInputWidget::resizeEvent(QResizeEvent *event)
403 {
404  QWidget::resizeEvent(event);
405  m_config->graphicsView->fitInView(m_txBackground, Qt::KeepAspectRatio);
406 }
407 
408 void ConfigInputWidget::goToWizard()
409 {
410  // Monitor for connection loss to reset wizard safely
411  connect(telMngr, &TelemetryManager::disconnected, this, &ConfigInputWidget::wzCancel);
412 
413  QMessageBox msgBox(this);
414  msgBox.setText(tr("Arming Settings will be set to Always Disarmed for your safety."));
415  msgBox.setDetailedText(tr("You will have to reconfigure the arming settings manually "
416  "when the wizard is finished. After the last step of the "
417  "wizard you will be taken to the Arming Settings screen."));
418  msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
419  msgBox.setDefaultButton(QMessageBox::Cancel);
420  int ret = msgBox.exec();
421 
422  switch (ret) {
423  case QMessageBox::Ok:
424  // Set correct tab visible before starting wizard.
425  if (m_config->tabWidget->currentIndex() != 0) {
426  m_config->tabWidget->setCurrentIndex(0);
427  }
428  wizardSetUpStep(wizardWelcome);
429  m_config->graphicsView->fitInView(m_txBackground, Qt::KeepAspectRatio);
430  break;
431  case QMessageBox::Cancel:
432  break;
433  default:
434  break;
435  }
436 }
437 
438 void ConfigInputWidget::wzCancel()
439 {
440  disconnect(telMngr, &TelemetryManager::disconnected, this, &ConfigInputWidget::wzCancel);
441 
442  dimOtherControls(false);
443  manualCommandObj->setMetadata(manualCommandObj->getDefaultMetadata());
444  m_config->stackedWidget->setCurrentIndex(0);
445 
446  if (wizardStep != wizardNone)
447  wizardTearDownStep(wizardStep);
448  wizardStep = wizardNone;
449  m_config->stackedWidget->setCurrentIndex(0);
450 
451  // Load manual settings back from beginning of wizard
452  manualSettingsObj->setData(previousManualSettingsData);
453 
454  // Load original metadata
455  restoreMdata();
456 }
457 
458 void ConfigInputWidget::wzNext()
459 {
460  // In identify sticks mode the next button can indicate
461  // channel advance
462  if (wizardStep != wizardNone && wizardStep != wizardIdentifySticks)
463  wizardTearDownStep(wizardStep);
464 
465  // State transitions for next button
466  switch (wizardStep) {
467  case wizardWelcome:
468  wizardSetUpStep(wizardChooseMode);
469  break;
470  case wizardChooseMode:
471  wizardSetUpStep(wizardChooseType);
472  break;
473  case wizardChooseType:
474  wizardSetUpStep(wizardIdentifySticks);
475  break;
477  nextChannel();
478  if (currentChannelNum == -1) { // Gone through all channels
479  wizardTearDownStep(wizardIdentifySticks);
480  wizardSetUpStep(wizardIdentifyCenter);
481  }
482  break;
484  wizardSetUpStep(wizardIdentifyLimits);
485  break;
487  wizardSetUpStep(wizardIdentifyInverted);
488  break;
490  wizardSetUpStep(wizardVerifyFailsafe);
491  break;
493  wizardSetUpStep(wizardFinish);
494  break;
495  case wizardFinish:
496  disconnect(telMngr, &TelemetryManager::disconnected, this, &ConfigInputWidget::wzCancel);
497 
498  wizardStep = wizardNone;
499  m_config->stackedWidget->setCurrentIndex(0);
500  m_config->tabWidget->setCurrentIndex(2);
501  break;
502  default:
503  Q_ASSERT(0);
504  }
505 }
506 
507 void ConfigInputWidget::wzBack()
508 {
509  if (wizardStep != wizardNone && wizardStep != wizardIdentifySticks)
510  wizardTearDownStep(wizardStep);
511 
512  // State transitions for next button
513  switch (wizardStep) {
514  case wizardChooseMode:
515  wizardSetUpStep(wizardWelcome);
516  break;
517  case wizardChooseType:
518  wizardSetUpStep(wizardChooseMode);
519  break;
521  prevChannel();
522  if (currentChannelNum == -1) {
523  wizardTearDownStep(wizardIdentifySticks);
524  wizardSetUpStep(wizardChooseType);
525  }
526  break;
528  wizardSetUpStep(wizardIdentifySticks);
529  break;
531  wizardSetUpStep(wizardIdentifyCenter);
532  break;
534  wizardSetUpStep(wizardIdentifyLimits);
535  break;
537  wizardSetUpStep(wizardIdentifyInverted);
538  break;
539  case wizardFinish:
540  wizardSetUpStep(wizardVerifyFailsafe);
541  break;
542  default:
543  Q_ASSERT(0);
544  }
545 }
546 
547 void ConfigInputWidget::wizardSetUpStep(enum wizardSteps step)
548 {
549  switch (step) {
550  case wizardWelcome:
551  foreach (QPointer<QWidget> wd, extraWidgets) {
552  if (!wd.isNull())
553  delete wd;
554  }
555  extraWidgets.clear();
556  m_config->graphicsView->setVisible(false);
557  setTxMovement(nothing);
558 
559  // Store the previous settings, although set to always disarmed for safety
560  manualSettingsData = manualSettingsObj->getData();
561  manualSettingsData.Arming = ManualControlSettings::ARMING_ALWAYSDISARMED;
562  previousManualSettingsData = manualSettingsData;
563 
564  // Now clear all the previous channel settings
565  for (uint i = 0; i < ManualControlSettings::CHANNELNUMBER_NUMELEM; i++) {
566  manualSettingsData.ChannelNumber[i] = 0;
567  manualSettingsData.ChannelMin[i] = 0;
568  manualSettingsData.ChannelNeutral[i] = 0;
569  manualSettingsData.ChannelMax[i] = 0;
570  manualSettingsData.ChannelGroups[i] = ManualControlSettings::CHANNELGROUPS_NONE;
571  }
572 
573  manualSettingsObj->setData(manualSettingsData);
574  m_config->wzText->setText(
575  tr("Welcome to the inputs configuration wizard.\n"
576  "Please follow the instructions on the screen and move controls when asked to.\n"
577  "Make sure you have already configured your hardware settings on the proper tab and "
578  "restarted your board.\n"
579  "Note: if you are using RSSI injection to S.Bus, HSUM or PPM, please configure that "
580  "on the input screen and save before proceeding with this wizard.\n"));
581  m_config->stackedWidget->setCurrentIndex(1);
582  m_config->wzBack->setEnabled(false);
583  m_config->wzNext->setEnabled(true);
584  m_config->bypassFailsafeGroup->setVisible(false);
585  break;
586  case wizardChooseMode: {
587  m_config->graphicsView->setVisible(true);
588  m_config->graphicsView->fitInView(m_txBackground, Qt::KeepAspectRatio);
589  setTxMovement(nothing);
590  m_config->wzText->setText(tr("Please choose your transmitter type.\n"
591  "Mode 1 means your throttle stick is on the right.\n"
592  "Mode 2 means your throttle stick is on the left.\n"));
593  m_config->wzBack->setEnabled(true);
594  QRadioButton *mode1 = new QRadioButton(tr("Mode 1"), this);
595  QRadioButton *mode2 = new QRadioButton(tr("Mode 2"), this);
596  mode2->setChecked(true);
597  extraWidgets.clear();
598  extraWidgets.append(mode1);
599  extraWidgets.append(mode2);
600  m_config->checkBoxesLayout->layout()->addWidget(mode1);
601  m_config->checkBoxesLayout->layout()->addWidget(mode2);
602  } break;
603  case wizardChooseType: {
604  m_config->wzText->setText(
605  tr("Please choose your transmitter mode.\n"
606  "Acro means normal transmitter.\n"
607  "Heli means there is a collective pitch and throttle input.\n"
608  "If you are using a heli transmitter please engage throttle hold now.\n"));
609  m_config->wzBack->setEnabled(true);
610  QRadioButton *typeAcro = new QRadioButton(tr("Acro"), this);
611  QRadioButton *typeHeli = new QRadioButton(tr("Heli"), this);
612  typeAcro->setChecked(true);
613  typeHeli->setChecked(false);
614  extraWidgets.clear();
615  extraWidgets.append(typeAcro);
616  extraWidgets.append(typeHeli);
617  m_config->checkBoxesLayout->layout()->addWidget(typeAcro);
618  m_config->checkBoxesLayout->layout()->addWidget(typeHeli);
619  wizardStep = wizardChooseType;
620  } break;
622  usedChannels.clear();
623  currentChannelNum = -1;
624  nextChannel();
625  manualSettingsData = manualSettingsObj->getData();
626  connect(receiverActivityObj, &UAVObject::objectUpdated, this,
627  &ConfigInputWidget::identifyControls);
628  fastMdata();
629  m_config->wzNext->setEnabled(false);
630  break;
632  setTxMovement(centerAll);
633  m_config->wzText->setText(
634  QString(tr("Please center all controls and press next when ready (if your FlightMode "
635  "switch has only two positions, leave it in either position).")));
636  fastMdata();
637  break;
638  case wizardIdentifyLimits: {
639  setTxMovement(nothing);
640  m_config->wzText->setText(QString(
641  tr("Please move all controls <b>(including switches)</b> to their maximum extents in "
642  "all directions. (You may continue when all controls have moved)")));
643  fastMdata();
644  manualSettingsData = manualSettingsObj->getData();
645  for (quint8 i = 0; i < ManualControlSettings::CHANNELMAX_NUMELEM; ++i) {
646  // Preserve the inverted status
647  if (manualSettingsData.ChannelMin[i] <= manualSettingsData.ChannelMax[i]) {
648  manualSettingsData.ChannelMin[i] =
649  manualSettingsData.ChannelNeutral[i] - INITIAL_OFFSET;
650  manualSettingsData.ChannelMax[i] =
651  manualSettingsData.ChannelNeutral[i] + INITIAL_OFFSET;
652  } else {
653  manualSettingsData.ChannelMin[i] =
654  manualSettingsData.ChannelNeutral[i] + INITIAL_OFFSET;
655  manualSettingsData.ChannelMax[i] =
656  manualSettingsData.ChannelNeutral[i] - INITIAL_OFFSET;
657  }
658  }
659  connect(manualCommandObj, &UAVObject::objectUpdated, this,
660  &ConfigInputWidget::identifyLimits);
661  connect(manualCommandObj, &UAVObject::objectUpdated, this, &ConfigInputWidget::moveSticks);
662 
663  // Disable next. When range on all channels is sufficient, enable it
664  m_config->wzNext->setEnabled(false);
665  } break;
667  dimOtherControls(false);
668  setTxMovement(nothing);
669  extraWidgets.clear();
670 
671  for (int index = 0;
672  index < manualSettingsObj->getField("ChannelMax")->getElementNames().length();
673  index++) {
674  QString name = manualSettingsObj->getField("ChannelMax")->getElementNames().at(index);
675  if (!name.contains("Access") && !name.contains("Flight")) {
676  QCheckBox *cb = new QCheckBox(name, this);
677  // Make sure checked status matches current one
678  cb->setChecked(manualSettingsData.ChannelMax[index]
679  < manualSettingsData.ChannelMin[index]);
680 
681  extraWidgets.append(cb);
682  m_config->checkBoxesLayout->layout()->addWidget(cb);
683 
684  connect(cb, &QAbstractButton::toggled, this, &ConfigInputWidget::invertControls);
685  }
686  }
687  connect(manualCommandObj, &UAVObject::objectUpdated, this, &ConfigInputWidget::moveSticks);
688  m_config->wzText->setText(
689  QString(tr("Please check the picture below and correct all the sticks which show an "
690  "inverted movement, press next when ready.")));
691  fastMdata();
692  break;
694  restoreMdata(); // make sure other updates do not clobber failsafe
695  dimOtherControls(false);
696  setTxMovement(nothing);
697  extraWidgets.clear();
698  flightStatusObj->requestUpdate();
699  detectFailsafe();
701  connect(flightStatusObj, &UAVObject::objectUpdated, this,
702  &ConfigInputWidget::detectFailsafe);
703  m_config->wzNext->setEnabled(false);
704  m_config->graphicsView->setVisible(false);
705  m_config->bypassFailsafeGroup->setVisible(true);
706  connect(m_config->cbBypassFailsafe, &QAbstractButton::toggled, this,
707  &ConfigInputWidget::detectFailsafe);
708  break;
709  case wizardFinish:
710  dimOtherControls(false);
711  connect(manualCommandObj, &UAVObject::objectUpdated, this, &ConfigInputWidget::moveSticks);
712  m_config->wzText->setText(QString(tr(
713  "You have completed this wizard, please check below if the picture mimics your sticks "
714  "movement.\n"
715  "These new settings aren't saved to the board yet, after pressing next you will go to "
716  "the Arming Settings "
717  "screen where you can set your desired arming sequence and save the configuration.")));
718  fastMdata();
719 
720  manualSettingsData.ChannelNeutral[ManualControlSettings::CHANNELNEUTRAL_THROTTLE] =
721  manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_THROTTLE]
722  + ((manualSettingsData.ChannelMax[ManualControlSettings::CHANNELMAX_THROTTLE]
723  - manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_THROTTLE])
724  * THROTTLE_NEUTRAL_FRACTION);
725 
726  if ((abs(manualSettingsData.ChannelMax[ManualControlSettings::CHANNELMAX_FLIGHTMODE]
727  - manualSettingsData
728  .ChannelNeutral[ManualControlSettings::CHANNELNEUTRAL_FLIGHTMODE])
729  < 100)
730  || (abs(manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_FLIGHTMODE]
731  - manualSettingsData
732  .ChannelNeutral[ManualControlSettings::CHANNELNEUTRAL_FLIGHTMODE])
733  < 100)) {
734  manualSettingsData.ChannelNeutral[ManualControlSettings::CHANNELNEUTRAL_FLIGHTMODE] =
735  manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_FLIGHTMODE]
736  + (manualSettingsData.ChannelMax[ManualControlSettings::CHANNELMAX_FLIGHTMODE]
737  - manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_FLIGHTMODE])
738  / 2;
739  }
740 
741  manualSettingsData.ChannelNeutral[ManualControlSettings::CHANNELNEUTRAL_ARMING] =
742  manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_ARMING]
743  + ((manualSettingsData.ChannelMax[ManualControlSettings::CHANNELMAX_ARMING]
744  - manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_ARMING])
745  * ARMING_NEUTRAL_FRACTION);
746 
747  manualSettingsObj->setData(manualSettingsData);
748  break;
749  default:
750  Q_ASSERT(0);
751  }
752  wizardStep = step;
753 }
754 
755 void ConfigInputWidget::wizardTearDownStep(enum wizardSteps step)
756 {
757  QRadioButton *mode, *type;
758  Q_ASSERT(step == wizardStep);
759  switch (step) {
760  case wizardWelcome:
761  break;
762  case wizardChooseMode:
763  mode = qobject_cast<QRadioButton *>(extraWidgets.at(0));
764  if (mode->isChecked())
765  transmitterMode = mode1;
766  else
767  transmitterMode = mode2;
768  delete extraWidgets.at(0);
769  delete extraWidgets.at(1);
770  extraWidgets.clear();
771  break;
772  case wizardChooseType:
773  type = qobject_cast<QRadioButton *>(extraWidgets.at(0));
774  if (type->isChecked())
775  transmitterType = acro;
776  else
777  transmitterType = heli;
778  delete extraWidgets.at(0);
779  delete extraWidgets.at(1);
780  extraWidgets.clear();
781  break;
783  disconnect(receiverActivityObj, &UAVObject::objectUpdated, this,
784  &ConfigInputWidget::identifyControls);
785  m_config->wzNext->setEnabled(true);
786  setTxMovement(nothing);
787  break;
789  manualCommandData = manualCommandObj->getData();
790  manualSettingsData = manualSettingsObj->getData();
791  for (unsigned int i = 0; i < ManualControlCommand::CHANNEL_NUMELEM; ++i) {
792  manualSettingsData.ChannelNeutral[i] = manualCommandData.Channel[i];
793  minSeen[i] = manualCommandData.Channel[i];
794  maxSeen[i] = manualCommandData.Channel[i];
795  }
796 
797  // If user skipped flight mode then force the number of flight modes to 1
798  // for valid connection
799  if (manualSettingsData.ChannelGroups[ManualControlSettings::CHANNELMIN_FLIGHTMODE]
800  == ManualControlSettings::CHANNELGROUPS_NONE) {
801  manualSettingsData.FlightModeNumber = 1;
802  }
803 
804  manualSettingsObj->setData(manualSettingsData);
805  setTxMovement(nothing);
806  break;
808  disconnect(manualCommandObj, &UAVObject::objectUpdated, this,
809  &ConfigInputWidget::identifyLimits);
810  disconnect(manualCommandObj, &UAVObject::objectUpdated, this,
811  &ConfigInputWidget::moveSticks);
812  manualSettingsObj->setData(manualSettingsData);
813  setTxMovement(nothing);
814  break;
816  dimOtherControls(false);
817  foreach (QWidget *wd, extraWidgets) {
818  QCheckBox *cb = qobject_cast<QCheckBox *>(wd);
819  if (cb) {
820  disconnect(cb, &QAbstractButton::toggled, this, &ConfigInputWidget::invertControls);
821  delete cb;
822  }
823  }
824  extraWidgets.clear();
825  disconnect(manualCommandObj, &UAVObject::objectUpdated, this,
826  &ConfigInputWidget::moveSticks);
827  break;
829  dimOtherControls(false);
830  extraWidgets.clear();
831  disconnect(flightStatusObj, &UAVObject::objectUpdated, this,
832  &ConfigInputWidget::detectFailsafe);
833  m_config->graphicsView->setVisible(true);
834  m_config->bypassFailsafeGroup->setVisible(false);
835  disconnect(m_config->cbBypassFailsafe, &QAbstractButton::toggled, this,
836  &ConfigInputWidget::detectFailsafe);
837  break;
838  case wizardFinish:
839  dimOtherControls(false);
840  setTxMovement(nothing);
841  disconnect(manualCommandObj, &UAVObject::objectUpdated, this,
842  &ConfigInputWidget::moveSticks);
843  restoreMdata();
844  break;
845  default:
846  Q_ASSERT(0);
847  }
848 }
849 
853 void ConfigInputWidget::fastMdata()
854 {
855  // Check that the metadata hasn't already been saved.
856  if (!originalMetaData.empty())
857  return;
858 
859  // Store original metadata
861  originalMetaData = utilMngr->readAllNonSettingsMetadata();
862 
863  // Update data rates
864  quint16 fastUpdate = 150; // in [ms]
865 
866  // Iterate over list of UAVObjects, configuring all dynamic data metadata objects.
867  UAVObjectManager *objManager = getObjectManager();
868  QVector<QVector<UAVDataObject *>> objList = objManager->getDataObjectsVector();
869  foreach (QVector<UAVDataObject *> list, objList) {
870  foreach (UAVDataObject *obj, list) {
871  if (!obj->isSettings()) {
872  UAVObject::Metadata mdata = obj->getMetadata();
874 
875  switch (obj->getObjID()) {
876  case ReceiverActivity::OBJID:
877  case FlightStatus::OBJID:
879  break;
880  case ManualControlCommand::OBJID:
882  mdata.flightTelemetryUpdatePeriod = fastUpdate;
883  break;
884  default:
885  break;
886  }
887 
888  // Set the metadata
889  obj->setMetadata(mdata);
890  }
891  }
892  }
893 }
894 
898 void ConfigInputWidget::restoreMdata()
899 {
900  foreach (QString objName, originalMetaData.keys()) {
901  UAVObject *obj = getObjectManager()->getObject(objName);
902  obj->setMetadata(originalMetaData.value(objName));
903  }
904  originalMetaData.clear();
905 }
906 
910 void ConfigInputWidget::setChannel(int newChan)
911 {
912  if (newChan == ManualControlSettings::CHANNELGROUPS_COLLECTIVE)
913  m_config->wzText->setText(QString(
914  tr("Please enable the throttle hold mode and move the collective pitch stick.")));
915  else if (newChan == ManualControlSettings::CHANNELGROUPS_FLIGHTMODE)
916  m_config->wzText->setText(QString(tr("Please toggle the flight mode switch. For switches "
917  "you may have to repeat this rapidly.")));
918  else if (newChan == ManualControlSettings::CHANNELGROUPS_ARMING)
919  m_config->wzText->setText(QString(tr(
920  "Please toggle the arming switch. For switches you may have to repeat this rapidly.")));
921  else if ((transmitterType == heli)
922  && (newChan == ManualControlSettings::CHANNELGROUPS_THROTTLE))
923  m_config->wzText->setText(
924  QString(tr("Please disable throttle hold mode and move the throttle stick.")));
925  else
926  m_config->wzText->setText(
927  QString(tr("Please move each control once at a time according to the instructions and "
928  "picture below.\n\n"
929  "Move the %1 stick"))
930  .arg(manualSettingsObj->getField("ChannelGroups")->getElementNames().at(newChan)));
931 
932  if (manualSettingsObj->getField("ChannelGroups")
933  ->getElementNames()
934  .at(newChan)
935  .contains("Accessory")
936  || manualSettingsObj->getField("ChannelGroups")
937  ->getElementNames()
938  .at(newChan)
939  .contains("FlightMode")
940  || manualSettingsObj->getField("ChannelGroups")
941  ->getElementNames()
942  .at(newChan)
943  .contains("Arming")) {
944  m_config->wzNext->setEnabled(true);
945  m_config->wzText->setText(m_config->wzText->text()
946  + tr(" or click next to skip this channel."));
947  } else
948  m_config->wzNext->setEnabled(false);
949 
950  setMoveFromCommand(newChan);
951 
952  currentChannelNum = newChan;
953  channelDetected = false;
954 }
955 
960 void ConfigInputWidget::nextChannel()
961 {
962  QList<int> order = (transmitterType == heli) ? heliChannelOrder : acroChannelOrder;
963 
964  if (currentChannelNum == -1) {
965  setChannel(order[0]);
966  return;
967  }
968  for (int i = 0; i < order.length() - 1; i++) {
969  if (order[i] == currentChannelNum) {
970  setChannel(order[i + 1]);
971  return;
972  }
973  }
974  currentChannelNum = -1; // hit end of list
975 }
976 
981 void ConfigInputWidget::prevChannel()
982 {
983  QList<int> order = transmitterType == heli ? heliChannelOrder : acroChannelOrder;
984 
985  // No previous from unset channel or next state
986  if (currentChannelNum == -1)
987  return;
988 
989  for (int i = 1; i < order.length(); i++) {
990  if (order[i] == currentChannelNum) {
991  setChannel(order[i - 1]);
992  usedChannels.removeLast();
993  return;
994  }
995  }
996  currentChannelNum = -1; // hit end of list
997 }
998 
1005 void ConfigInputWidget::identifyControls()
1006 {
1007  static int debounce = 0;
1008 
1009  receiverActivityData = receiverActivityObj->getData();
1010  if (receiverActivityData.ActiveChannel == 255)
1011  return;
1012 
1013  if (channelDetected)
1014  return;
1015  else {
1016  receiverActivityData = receiverActivityObj->getData();
1017  currentChannel.group = receiverActivityData.ActiveGroup;
1018  currentChannel.number = receiverActivityData.ActiveChannel;
1019  if (currentChannel == lastChannel)
1020  ++debounce;
1021  lastChannel.group = currentChannel.group;
1022  lastChannel.number = currentChannel.number;
1023 
1024  // If this channel isn't already allocated, and the debounce passes the threshold, set
1025  // the input channel as detected.
1026  if (!usedChannels.contains(lastChannel) && debounce > DEBOUNCE_THRESHOLD_COUNT) {
1027  channelDetected = true;
1028  debounce = 0;
1029  usedChannels.append(lastChannel);
1030  manualSettingsData = manualSettingsObj->getData();
1031  manualSettingsData.ChannelGroups[currentChannelNum] = currentChannel.group;
1032  manualSettingsData.ChannelNumber[currentChannelNum] = currentChannel.number;
1033  manualSettingsObj->setData(manualSettingsData);
1034  } else
1035  return;
1036  }
1037 
1038  // At this point in the code, the channel has been successfully identified
1039  m_config->wzText->clear();
1040  setTxMovement(nothing);
1041 
1042  // Wait to ensure that the user is no longer moving the sticks. Empricially,
1043  // this delay works well across a broad range of users and transmitters.
1044  QTimer::singleShot(CHANNEL_IDENTIFICATION_WAIT_TIME_MS, this, &ConfigInputWidget::wzNext);
1045 }
1046 
1047 void ConfigInputWidget::identifyLimits()
1048 {
1049  bool allSane = true;
1050 
1051  manualCommandData = manualCommandObj->getData();
1052 
1053  for (quint8 i = 0; i < ManualControlSettings::CHANNELMAX_NUMELEM; ++i) {
1054  uint16_t channelVal = manualCommandData.Channel[i];
1055 
1056  // Don't mess up the range based on failsafe, etc.
1057  if ((channelVal > MIN_SANE_CHANNEL_VALUE) && (channelVal < MAX_SANE_CHANNEL_VALUE)) {
1058  if (channelVal < minSeen[i]) {
1059  minSeen[i] = channelVal;
1060  }
1061 
1062  if (channelVal > maxSeen[i]) {
1063  maxSeen[i] = channelVal;
1064  }
1065  }
1066 
1067  switch (i) {
1068  case ManualControlSettings::CHANNELNUMBER_THROTTLE:
1069  // Keep the throttle neutral position near the minimum value so that
1070  // the stick visualization keeps working consistently (it expects this
1071  // ratio between + and - range.
1072 
1073  // Preserve channel inversion. Currently no channels will be
1074  // inverted at this time but input wizard may detect this in the
1075  // future.
1076  if (manualSettingsData.ChannelMin[i] <= manualSettingsData.ChannelMax[i]) {
1077  manualSettingsData.ChannelNeutral[i] =
1078  minSeen[i] + (maxSeen[i] - minSeen[i]) * THROTTLE_NEUTRAL_FRACTION;
1079  } else {
1080  manualSettingsData.ChannelNeutral[i] =
1081  minSeen[i] + (maxSeen[i] - minSeen[i]) * (1.0f - THROTTLE_NEUTRAL_FRACTION);
1082  }
1083 
1084  break;
1085 
1086  case ManualControlSettings::CHANNELNUMBER_ARMING:
1087  case ManualControlSettings::CHANNELGROUPS_FLIGHTMODE:
1088  // Keep switches near the middle.
1089  manualSettingsData.ChannelNeutral[i] = (maxSeen[i] + minSeen[i]) / 2;
1090  break;
1091 
1092  default:
1093  break;
1094  }
1095 
1096  if (manualSettingsData.ChannelMin[i] <= manualSettingsData.ChannelMax[i]) {
1097  // Non inverted channel
1098 
1099  // Always update the throttle based on the low part of the range.
1100  // Otherwise, if the low part of the range will be "wider" than what
1101  // we initially created-- either because of movement in minimum
1102  // observed or the neutral value--- update it.
1103  //
1104  // This tolerates cases when channels we recalculate neutral for--
1105  // switches, throttle-- are not initially centered.
1106  if ((i == ManualControlSettings::CHANNELNUMBER_THROTTLE)
1107  || ((manualSettingsData.ChannelNeutral[i] - minSeen[i]) > INITIAL_OFFSET)) {
1108  manualSettingsData.ChannelMin[i] = minSeen[i];
1109  }
1110 
1111  if ((maxSeen[i] - manualSettingsData.ChannelNeutral[i]) > INITIAL_OFFSET) {
1112  manualSettingsData.ChannelMax[i] = maxSeen[i];
1113  }
1114  } else {
1115  // Inverted channel
1116 
1117  if ((i == ManualControlSettings::CHANNELNUMBER_THROTTLE)
1118  || ((maxSeen[i] - manualSettingsData.ChannelNeutral[i]) > INITIAL_OFFSET)) {
1119  manualSettingsData.ChannelMin[i] = maxSeen[i];
1120  }
1121 
1122  if ((manualSettingsData.ChannelNeutral[i] - minSeen[i]) > INITIAL_OFFSET) {
1123  manualSettingsData.ChannelMax[i] = minSeen[i];
1124  }
1125  }
1126 
1127  // If this is a used channel, make sure we get a valid range.
1128  if (manualSettingsData.ChannelGroups[i] != ManualControlSettings::CHANNELGROUPS_NONE) {
1129  int diff = maxSeen[i] - minSeen[i];
1130 
1131  if (diff < MIN_SANE_RANGE) {
1132  allSane = false;
1133  }
1134  }
1135  }
1136 
1137  manualSettingsObj->setData(manualSettingsData);
1138 
1139  if (allSane) {
1140  m_config->wzText->setText(
1141  QString(tr("Please move all controls <b>(including switches)</b> to their maximum "
1142  "extents in all directions. You may press next when finished.")));
1143  m_config->wzNext->setEnabled(true);
1144  }
1145 }
1146 void ConfigInputWidget::setMoveFromCommand(int command)
1147 {
1148  if (command == ManualControlSettings::CHANNELNUMBER_ROLL) {
1149  setTxMovement(moveRightHorizontalStick);
1150  } else if (command == ManualControlSettings::CHANNELNUMBER_PITCH) {
1151  if (transmitterMode == mode2)
1152  setTxMovement(moveRightVerticalStick);
1153  else
1154  setTxMovement(moveLeftVerticalStick);
1155  } else if (command == ManualControlSettings::CHANNELNUMBER_YAW) {
1156  setTxMovement(moveLeftHorizontalStick);
1157  } else if (command == ManualControlSettings::CHANNELNUMBER_THROTTLE) {
1158  if (transmitterMode == mode2)
1159  setTxMovement(moveLeftVerticalStick);
1160  else
1161  setTxMovement(moveRightVerticalStick);
1162  } else if (command == ManualControlSettings::CHANNELNUMBER_COLLECTIVE) {
1163  if (transmitterMode == mode2)
1164  setTxMovement(moveLeftVerticalStick);
1165  else
1166  setTxMovement(moveRightVerticalStick);
1167  } else if (command == ManualControlSettings::CHANNELNUMBER_FLIGHTMODE) {
1168  setTxMovement(moveFlightMode);
1169  } else if (command == ManualControlSettings::CHANNELNUMBER_ARMING) {
1170  setTxMovement(armingSwitch);
1171  } else if (command == ManualControlSettings::CHANNELNUMBER_ACCESSORY0) {
1172  setTxMovement(moveAccess0);
1173  } else if (command == ManualControlSettings::CHANNELNUMBER_ACCESSORY1) {
1174  setTxMovement(moveAccess1);
1175  } else if (command == ManualControlSettings::CHANNELNUMBER_ACCESSORY2) {
1176  setTxMovement(moveAccess2);
1177  }
1178 }
1179 
1180 void ConfigInputWidget::setTxMovement(txMovements movement)
1181 {
1182  resetTxControls();
1183  switch (movement) {
1184  case moveLeftVerticalStick:
1185  movePos = 0;
1186  growing = true;
1187  currentMovement = moveLeftVerticalStick;
1188  animate->start(100);
1189  break;
1191  movePos = 0;
1192  growing = true;
1193  currentMovement = moveRightVerticalStick;
1194  animate->start(100);
1195  break;
1197  movePos = 0;
1198  growing = true;
1199  currentMovement = moveLeftHorizontalStick;
1200  animate->start(100);
1201  break;
1203  movePos = 0;
1204  growing = true;
1205  currentMovement = moveRightHorizontalStick;
1206  animate->start(100);
1207  break;
1208  case moveAccess0:
1209  movePos = 0;
1210  growing = true;
1211  currentMovement = moveAccess0;
1212  animate->start(100);
1213  break;
1214  case moveAccess1:
1215  movePos = 0;
1216  growing = true;
1217  currentMovement = moveAccess1;
1218  animate->start(100);
1219  break;
1220  case moveAccess2:
1221  movePos = 0;
1222  growing = true;
1223  currentMovement = moveAccess2;
1224  animate->start(100);
1225  break;
1226  case moveFlightMode:
1227  movePos = 0;
1228  growing = true;
1229  currentMovement = moveFlightMode;
1230  animate->start(1000);
1231  break;
1232  case armingSwitch:
1233  movePos = 0;
1234  growing = true;
1235  currentMovement = armingSwitch;
1236  animate->start(1000);
1237  break;
1238  case centerAll:
1239  movePos = 0;
1240  currentMovement = centerAll;
1241  animate->start(1000);
1242  break;
1243  case moveAll:
1244  movePos = 0;
1245  growing = true;
1246  currentMovement = moveAll;
1247  animate->start(50);
1248  break;
1249  case nothing:
1250  movePos = 0;
1251  animate->stop();
1252  break;
1253  default:
1254  break;
1255  }
1256 }
1257 
1258 void ConfigInputWidget::moveTxControls()
1259 {
1260  QTransform trans;
1261  QGraphicsItem *item = NULL;
1262  txMovementType move = vertical;
1263  int limitMax = 0;
1264  int limitMin = 0;
1265  static bool auxFlag = false;
1266  switch (currentMovement) {
1267  case moveLeftVerticalStick:
1268  item = m_txLeftStick;
1269  trans = m_txLeftStickOrig;
1270  limitMax = STICK_MAX_MOVE;
1271  limitMin = STICK_MIN_MOVE;
1272  move = vertical;
1273  break;
1275  item = m_txRightStick;
1276  trans = m_txRightStickOrig;
1277  limitMax = STICK_MAX_MOVE;
1278  limitMin = STICK_MIN_MOVE;
1279  move = vertical;
1280  break;
1282  item = m_txLeftStick;
1283  trans = m_txLeftStickOrig;
1284  limitMax = STICK_MAX_MOVE;
1285  limitMin = STICK_MIN_MOVE;
1286  move = horizontal;
1287  break;
1289  item = m_txRightStick;
1290  trans = m_txRightStickOrig;
1291  limitMax = STICK_MAX_MOVE;
1292  limitMin = STICK_MIN_MOVE;
1293  move = horizontal;
1294  break;
1295  case moveAccess0:
1296  item = m_txAccess0;
1297  trans = m_txAccess0Orig;
1298  limitMax = ACCESS_MAX_MOVE;
1299  limitMin = ACCESS_MIN_MOVE;
1300  move = horizontal;
1301  break;
1302  case moveAccess1:
1303  item = m_txAccess1;
1304  trans = m_txAccess1Orig;
1305  limitMax = ACCESS_MAX_MOVE;
1306  limitMin = ACCESS_MIN_MOVE;
1307  move = horizontal;
1308  break;
1309  case moveAccess2:
1310  item = m_txAccess2;
1311  trans = m_txAccess2Orig;
1312  limitMax = ACCESS_MAX_MOVE;
1313  limitMin = ACCESS_MIN_MOVE;
1314  move = horizontal;
1315  break;
1316  case moveFlightMode:
1317  item = m_txFlightMode;
1318  move = jump;
1319  break;
1320  case armingSwitch:
1321  item = m_txArming;
1322  move = jump;
1323  break;
1324  case centerAll:
1325  item = m_txArrows;
1326  move = jump;
1327  break;
1328  case moveAll:
1329  limitMax = STICK_MAX_MOVE;
1330  limitMin = STICK_MIN_MOVE;
1331  move = mix;
1332  break;
1333  default:
1334  break;
1335  }
1336  if (move == vertical)
1337  item->setTransform(trans.translate(0, movePos * 10), false);
1338  else if (move == horizontal)
1339  item->setTransform(trans.translate(movePos * 10, 0), false);
1340  else if (move == jump) {
1341  if (item == m_txArrows) {
1342  m_txArrows->setVisible(!m_txArrows->isVisible());
1343  } else if (item == m_txFlightMode) {
1344  QGraphicsSvgItem *svg;
1345  svg = static_cast<QGraphicsSvgItem *>(item);
1346  if (svg) {
1347  if (svg->elementId() == "flightModeCenter") {
1348  if (growing) {
1349  svg->setElementId("flightModeRight");
1350  m_txFlightMode->setTransform(m_txFlightModeROrig, false);
1351  } else {
1352  svg->setElementId("flightModeLeft");
1353  m_txFlightMode->setTransform(m_txFlightModeLOrig, false);
1354  }
1355  } else if (svg->elementId() == "flightModeRight") {
1356  growing = false;
1357  svg->setElementId("flightModeCenter");
1358  m_txFlightMode->setTransform(m_txFlightModeCOrig, false);
1359  } else if (svg->elementId() == "flightModeLeft") {
1360  growing = true;
1361  svg->setElementId("flightModeCenter");
1362  m_txFlightMode->setTransform(m_txFlightModeCOrig, false);
1363  }
1364  }
1365  } else if (item == m_txArming) {
1366  QGraphicsSvgItem *svg;
1367  svg = static_cast<QGraphicsSvgItem *>(item);
1368  if (svg) {
1369  if (svg->elementId() == "armedswitchleft") {
1370  svg->setElementId("armedswitchright");
1371  m_txArming->setTransform(m_txArmingSwitchOrigR, false);
1372  } else {
1373  svg->setElementId("armedswitchleft");
1374  m_txArming->setTransform(m_txArmingSwitchOrigL, false);
1375  }
1376  }
1377  }
1378  } else if (move == mix) {
1379  trans = m_txAccess0Orig;
1380  m_txAccess0->setTransform(
1381  trans.translate(movePos * 10 * ACCESS_MAX_MOVE / STICK_MAX_MOVE, 0), false);
1382  trans = m_txAccess1Orig;
1383  m_txAccess1->setTransform(
1384  trans.translate(movePos * 10 * ACCESS_MAX_MOVE / STICK_MAX_MOVE, 0), false);
1385  trans = m_txAccess2Orig;
1386  m_txAccess2->setTransform(
1387  trans.translate(movePos * 10 * ACCESS_MAX_MOVE / STICK_MAX_MOVE, 0), false);
1388 
1389  if (auxFlag) {
1390  trans = m_txLeftStickOrig;
1391  m_txLeftStick->setTransform(trans.translate(0, movePos * 10), false);
1392  trans = m_txRightStickOrig;
1393  m_txRightStick->setTransform(trans.translate(0, movePos * 10), false);
1394  } else {
1395  trans = m_txLeftStickOrig;
1396  m_txLeftStick->setTransform(trans.translate(movePos * 10, 0), false);
1397  trans = m_txRightStickOrig;
1398  m_txRightStick->setTransform(trans.translate(movePos * 10, 0), false);
1399  }
1400 
1401  if (movePos == 0) {
1402  m_txFlightMode->setElementId("flightModeCenter");
1403  m_txFlightMode->setTransform(m_txFlightModeCOrig, false);
1404  } else if (movePos == ACCESS_MAX_MOVE / 2) {
1405  m_txFlightMode->setElementId("flightModeRight");
1406  m_txFlightMode->setTransform(m_txFlightModeROrig, false);
1407  } else if (movePos == ACCESS_MIN_MOVE / 2) {
1408  m_txFlightMode->setElementId("flightModeLeft");
1409  m_txFlightMode->setTransform(m_txFlightModeLOrig, false);
1410  }
1411  }
1412  if (move == horizontal || move == vertical || move == mix) {
1413  if (movePos == 0 && growing)
1414  auxFlag = !auxFlag;
1415  if (growing)
1416  ++movePos;
1417  else
1418  --movePos;
1419  if (movePos > limitMax) {
1420  movePos = movePos - 2;
1421  growing = false;
1422  }
1423  if (movePos < limitMin) {
1424  movePos = movePos + 2;
1425  growing = true;
1426  }
1427  }
1428 }
1429 
1434 void ConfigInputWidget::detectFailsafe()
1435 {
1436  FlightStatus::DataFields flightStatusData = flightStatusObj->getData();
1437  switch (failsafeDetection) {
1439  if (flightStatusData.ControlSource == FlightStatus::CONTROLSOURCE_TRANSMITTER) {
1440  m_config->wzText->setText(
1441  QString(tr("To verify that the failsafe mode on your receiver is safe, please turn "
1442  "off your transmitter now.\n")));
1443  failsafeDetection = FS_AWAITING_FAILSAFE; // Now wait for it to go away
1444  } else {
1445  m_config->wzText->setText(QString(tr("Unable to detect transmitter to verify failsafe. "
1446  "Please ensure it is turned on.\n")));
1447  }
1448  break;
1449  case FS_AWAITING_FAILSAFE:
1450  if (flightStatusData.ControlSource == FlightStatus::CONTROLSOURCE_FAILSAFE) {
1451  m_config->wzText->setText(
1452  QString(tr("Failsafe mode detected. Please turn on your transmitter again.\n")));
1453  failsafeDetection = FS_AWAITING_RECONNECT; // Now wait for it to go away
1454  }
1455  break;
1456  case FS_AWAITING_RECONNECT:
1457  if (flightStatusData.ControlSource == FlightStatus::CONTROLSOURCE_TRANSMITTER) {
1458  m_config->wzText->setText(QString(
1459  tr("Congratulations. Failsafe detection appears to be working reliably.\n")));
1460  m_config->wzNext->setEnabled(true);
1461  }
1462  break;
1463  }
1464  if (m_config->cbBypassFailsafe->checkState()) {
1465  m_config->wzText->setText(
1466  QString(tr("You are selecting to bypass failsafe detection. If this is not working, "
1467  "then the flight controller is likely to fly away. Please check on the "
1468  "forums how to configure this properly.\n")));
1469  m_config->wzNext->setEnabled(true);
1470  }
1471 }
1472 
1473 void ConfigInputWidget::moveSticks()
1474 {
1475  QTransform trans;
1476  manualCommandData = manualCommandObj->getData();
1477 
1478  // 0 for throttle is THROTTLE_NEUTRAL_FRACTION of the total range
1479  // here we map it from [-1,0,1] -> [0,THROTTLE_NEUTRAL_FRACTION,1]
1480  double throttlePosition = manualCommandData.Throttle < 0
1481  ? 0 + THROTTLE_NEUTRAL_FRACTION * (1 - manualCommandData.Throttle)
1482  : THROTTLE_NEUTRAL_FRACTION + manualCommandData.Throttle * (1 - THROTTLE_NEUTRAL_FRACTION);
1483  // now map [0,1] -> [-1,1] for consistence with other channels
1484  throttlePosition = 2 * throttlePosition - 1;
1485  if (transmitterMode == mode2) {
1486  trans = m_txLeftStickOrig;
1487  m_txLeftStick->setTransform(trans.translate(manualCommandData.Yaw * STICK_MAX_MOVE * 10,
1488  -throttlePosition * STICK_MAX_MOVE * 10),
1489  false);
1490  trans = m_txRightStickOrig;
1491  m_txRightStick->setTransform(trans.translate(manualCommandData.Roll * STICK_MAX_MOVE * 10,
1492  manualCommandData.Pitch * STICK_MAX_MOVE * 10),
1493  false);
1494  } else {
1495  trans = m_txRightStickOrig;
1496  m_txRightStick->setTransform(trans.translate(manualCommandData.Roll * STICK_MAX_MOVE * 10,
1497  -throttlePosition * STICK_MAX_MOVE * 10),
1498  false);
1499  trans = m_txLeftStickOrig;
1500  m_txLeftStick->setTransform(trans.translate(manualCommandData.Yaw * STICK_MAX_MOVE * 10,
1501  manualCommandData.Pitch * STICK_MAX_MOVE * 10),
1502  false);
1503  }
1504  switch (scaleSwitchChannel(ManualControlSettings::CHANNELMIN_FLIGHTMODE,
1505  manualSettingsObj->getData().FlightModeNumber)) {
1506  case 0:
1507  m_txFlightMode->setElementId("flightModeLeft");
1508  m_txFlightMode->setTransform(m_txFlightModeLOrig, false);
1509  break;
1510  case 1:
1511  m_txFlightMode->setElementId("flightModeCenter");
1512  m_txFlightMode->setTransform(m_txFlightModeCOrig, false);
1513  break;
1514  case 2:
1515  default:
1516  m_txFlightMode->setElementId("flightModeRight");
1517  m_txFlightMode->setTransform(m_txFlightModeROrig, false);
1518  break;
1519  }
1520  switch (scaleSwitchChannel(ManualControlSettings::CHANNELMIN_ARMING, 2)) {
1521  case 0:
1522  m_txArming->setElementId("armedswitchleft");
1523  m_txArming->setTransform(m_txArmingSwitchOrigL, false);
1524  break;
1525  case 1:
1526  m_txArming->setElementId("armedswitchright");
1527  m_txArming->setTransform(m_txArmingSwitchOrigR, false);
1528  break;
1529  default:
1530  break;
1531  }
1532  m_txAccess0->setTransform(
1533  QTransform(m_txAccess0Orig)
1534  .translate(manualCommandData.Accessory[0] * ACCESS_MAX_MOVE * 10, 0),
1535  false);
1536  m_txAccess1->setTransform(
1537  QTransform(m_txAccess1Orig)
1538  .translate(manualCommandData.Accessory[1] * ACCESS_MAX_MOVE * 10, 0),
1539  false);
1540  m_txAccess2->setTransform(
1541  QTransform(m_txAccess2Orig)
1542  .translate(manualCommandData.Accessory[2] * ACCESS_MAX_MOVE * 10, 0),
1543  false);
1544 }
1545 
1546 void ConfigInputWidget::dimOtherControls(bool value)
1547 {
1548  qreal opac;
1549  if (value)
1550  opac = 0.1;
1551  else
1552  opac = 1;
1553  m_txAccess0->setOpacity(opac);
1554  m_txAccess1->setOpacity(opac);
1555  m_txAccess2->setOpacity(opac);
1556  m_txFlightMode->setOpacity(opac);
1557  m_txArming->setOpacity(opac);
1558 }
1559 
1561 {
1562  m_config->configurationWizard->setEnabled(enable);
1563  m_config->runCalibration->setEnabled(enable);
1564 
1566 }
1567 
1568 void ConfigInputWidget::invertControls()
1569 {
1570  manualSettingsData = manualSettingsObj->getData();
1571  foreach (QWidget *wd, extraWidgets) {
1572  QCheckBox *cb = qobject_cast<QCheckBox *>(wd);
1573  if (cb) {
1574  int index =
1575  manualSettingsObj->getField("ChannelNumber")->getElementNames().indexOf(cb->text());
1576  if ((cb->isChecked()
1577  && (manualSettingsData.ChannelMax[index] > manualSettingsData.ChannelMin[index]))
1578  || (!cb->isChecked()
1579  && (manualSettingsData.ChannelMax[index]
1580  < manualSettingsData.ChannelMin[index]))) {
1581  qint16 aux;
1582  aux = manualSettingsData.ChannelMax[index];
1583  manualSettingsData.ChannelMax[index] = manualSettingsData.ChannelMin[index];
1584  manualSettingsData.ChannelMin[index] = aux;
1585  if (index == ManualControlSettings::CHANNELNEUTRAL_THROTTLE) {
1586  // Keep the throttle neutral position near the minimum value so that
1587  // the stick visualization keeps working consistently (it expects this
1588  // ratio between + and - range.
1589  manualSettingsData.ChannelNeutral[index] = manualSettingsData.ChannelMin[index]
1590  + (manualSettingsData.ChannelMax[index]
1591  - manualSettingsData.ChannelMin[index])
1592  * THROTTLE_NEUTRAL_FRACTION;
1593  }
1594  }
1595  }
1596  }
1597  manualSettingsObj->setData(manualSettingsData);
1598 }
1605 quint8 ConfigInputWidget::scaleSwitchChannel(quint8 channelNumber, quint8 switchPositions)
1606 {
1607  if (channelNumber > (ManualControlSettings::CHANNELMIN_NUMELEM - 1))
1608  return 0;
1609  ManualControlSettings::DataFields manualSettingsDataPriv = manualSettingsObj->getData();
1610  ManualControlCommand::DataFields manualCommandDataPriv = manualCommandObj->getData();
1611 
1612  float valueScaled;
1613  int chMin = manualSettingsDataPriv.ChannelMin[channelNumber];
1614  int chMax = manualSettingsDataPriv.ChannelMax[channelNumber];
1615  int chNeutral = manualSettingsDataPriv.ChannelNeutral[channelNumber];
1616 
1617  int value = manualCommandDataPriv.Channel[channelNumber];
1618  if ((chMax > chMin && value >= chNeutral) || (chMin > chMax && value <= chNeutral)) {
1619  if (chMax != chNeutral)
1620  valueScaled = (float)(value - chNeutral) / (float)(chMax - chNeutral);
1621  else
1622  valueScaled = 0;
1623  } else {
1624  if (chMin != chNeutral)
1625  valueScaled = (float)(value - chNeutral) / (float)(chNeutral - chMin);
1626  else
1627  valueScaled = 0;
1628  }
1629 
1630  if (valueScaled < -1.0)
1631  valueScaled = -1.0;
1632  else if (valueScaled > 1.0)
1633  valueScaled = 1.0;
1634 
1635  // Convert channel value into the switch position in the range [0..N-1]
1636  // This uses the same optimized computation as flight code to be consistent
1637  quint8 pos = ((qint16)(valueScaled * 256) + 256) * switchPositions >> 9;
1638  if (pos >= switchPositions)
1639  pos = switchPositions - 1;
1640  return pos;
1641 }
1642 
1643 void ConfigInputWidget::moveFMSlider()
1644 {
1645  m_config->fmsSlider->setValue(
1646  scaleSwitchChannel(ManualControlSettings::CHANNELMIN_FLIGHTMODE,
1647  manualSettingsObj->getData().FlightModeNumber));
1648 }
1649 
1650 void ConfigInputWidget::updatePositionSlider()
1651 {
1652  ManualControlSettings::DataFields manualSettingsDataPriv = manualSettingsObj->getData();
1653 
1654  switch (manualSettingsDataPriv.FlightModeNumber) {
1655  default:
1656  case 6:
1657  m_config->fmsModePos6->setEnabled(true);
1658  Q_FALLTHROUGH();
1659  case 5:
1660  m_config->fmsModePos5->setEnabled(true);
1661  Q_FALLTHROUGH();
1662  case 4:
1663  m_config->fmsModePos4->setEnabled(true);
1664  Q_FALLTHROUGH();
1665  case 3:
1666  m_config->fmsModePos3->setEnabled(true);
1667  Q_FALLTHROUGH();
1668  case 2:
1669  m_config->fmsModePos2->setEnabled(true);
1670  Q_FALLTHROUGH();
1671  case 1:
1672  m_config->fmsModePos1->setEnabled(true);
1673  Q_FALLTHROUGH();
1674  case 0:
1675  break;
1676  }
1677 
1678  switch (manualSettingsDataPriv.FlightModeNumber) {
1679  case 0:
1680  m_config->fmsModePos1->setEnabled(false);
1681  Q_FALLTHROUGH();
1682  case 1:
1683  m_config->fmsModePos2->setEnabled(false);
1684  Q_FALLTHROUGH();
1685  case 2:
1686  m_config->fmsModePos3->setEnabled(false);
1687  Q_FALLTHROUGH();
1688  case 3:
1689  m_config->fmsModePos4->setEnabled(false);
1690  Q_FALLTHROUGH();
1691  case 4:
1692  m_config->fmsModePos5->setEnabled(false);
1693  Q_FALLTHROUGH();
1694  case 5:
1695  m_config->fmsModePos6->setEnabled(false);
1696  Q_FALLTHROUGH();
1697  case 6:
1698  default:
1699  break;
1700  }
1701 }
1702 
1703 void ConfigInputWidget::updateCalibration()
1704 {
1705  manualCommandData = manualCommandObj->getData();
1706  for (quint8 i = 0; i < ManualControlSettings::CHANNELMAX_NUMELEM; ++i) {
1707  if ((!reverse[i] && manualSettingsData.ChannelMin[i] > manualCommandData.Channel[i])
1708  || (reverse[i] && manualSettingsData.ChannelMin[i] < manualCommandData.Channel[i]))
1709  manualSettingsData.ChannelMin[i] = manualCommandData.Channel[i];
1710  if ((!reverse[i] && manualSettingsData.ChannelMax[i] < manualCommandData.Channel[i])
1711  || (reverse[i] && manualSettingsData.ChannelMax[i] > manualCommandData.Channel[i]))
1712  manualSettingsData.ChannelMax[i] = manualCommandData.Channel[i];
1713  manualSettingsData.ChannelNeutral[i] = manualCommandData.Channel[i];
1714  }
1715 
1716  manualSettingsObj->setData(manualSettingsData);
1717  manualSettingsObj->updated();
1718 }
1719 
1720 void ConfigInputWidget::simpleCalibration(bool enable)
1721 {
1722  if (enable) {
1723  m_config->configurationWizard->setEnabled(false);
1724 
1725  QMessageBox msgBox;
1726  msgBox.setText(tr("Arming Settings are now set to Always Disarmed for your safety."));
1727  msgBox.setDetailedText(tr("You will have to reconfigure the arming settings manually when "
1728  "the wizard is finished."));
1729  msgBox.setStandardButtons(QMessageBox::Ok);
1730  msgBox.setDefaultButton(QMessageBox::Ok);
1731  msgBox.exec();
1732 
1733  manualCommandData = manualCommandObj->getData();
1734 
1735  manualSettingsData = manualSettingsObj->getData();
1736  manualSettingsData.Arming = ManualControlSettings::ARMING_ALWAYSDISARMED;
1737  manualSettingsObj->setData(manualSettingsData);
1738 
1739  for (unsigned int i = 0; i < ManualControlCommand::CHANNEL_NUMELEM; i++) {
1740  reverse[i] = manualSettingsData.ChannelMax[i] < manualSettingsData.ChannelMin[i];
1741  manualSettingsData.ChannelMin[i] = manualCommandData.Channel[i];
1742  manualSettingsData.ChannelNeutral[i] = manualCommandData.Channel[i];
1743  manualSettingsData.ChannelMax[i] = manualCommandData.Channel[i];
1744  }
1745 
1746  fastMdata();
1747 
1748  connect(manualCommandObj, &UAVObject::objectUnpacked, this,
1749  &ConfigInputWidget::updateCalibration);
1750  } else {
1751  m_config->configurationWizard->setEnabled(true);
1752 
1753  manualCommandData = manualCommandObj->getData();
1754  manualSettingsData = manualSettingsObj->getData();
1755 
1756  restoreMdata();
1757 
1758  for (unsigned int i = 0; i < ManualControlCommand::CHANNEL_NUMELEM; i++)
1759  manualSettingsData.ChannelNeutral[i] = manualCommandData.Channel[i];
1760 
1761  // Force switches to middle
1762  manualSettingsData.ChannelNeutral[ManualControlSettings::CHANNELNUMBER_FLIGHTMODE] =
1763  (manualSettingsData.ChannelMax[ManualControlSettings::CHANNELNUMBER_FLIGHTMODE]
1764  + manualSettingsData.ChannelMin[ManualControlSettings::CHANNELNUMBER_FLIGHTMODE])
1765  / 2;
1766 
1767  manualSettingsData.ChannelNeutral[ManualControlSettings::CHANNELNUMBER_ARMING] =
1768  (manualSettingsData.ChannelMax[ManualControlSettings::CHANNELNUMBER_ARMING]
1769  + manualSettingsData.ChannelMin[ManualControlSettings::CHANNELNUMBER_ARMING])
1770  / 2;
1771 
1772  // Force throttle to be near min
1773  manualSettingsData.ChannelNeutral[ManualControlSettings::CHANNELNEUTRAL_THROTTLE] =
1774  manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_THROTTLE]
1775  + (manualSettingsData.ChannelMax[ManualControlSettings::CHANNELMAX_THROTTLE]
1776  - manualSettingsData.ChannelMin[ManualControlSettings::CHANNELMIN_THROTTLE])
1777  * THROTTLE_NEUTRAL_FRACTION;
1778 
1779  manualSettingsObj->setData(manualSettingsData);
1780 
1781  disconnect(manualCommandObj, &UAVObject::objectUnpacked, this,
1782  &ConfigInputWidget::updateCalibration);
1783  }
1784 }
1785 
1786 void ConfigInputWidget::clearMessages(QWidget *widget, const QString type)
1787 {
1788  QLabel *lbl = findChild<QLabel *>("lblMessage" + type);
1789  if (lbl) {
1790  widget->layout()->removeWidget(lbl);
1791  delete lbl;
1792  }
1793 }
1794 
1795 void ConfigInputWidget::addMessage(QWidget *target, const QString type, const QString msg)
1796 {
1797  QLabel *lbl = findChild<QLabel *>("lblMessage" + type);
1798  if (!lbl) {
1799  lbl = new QLabel(this);
1800  lbl->setObjectName("lblMessage" + type);
1801  lbl->setWordWrap(true);
1802  target->layout()->addWidget(lbl);
1803  }
1804 
1805  lbl->setText(lbl->text() + msg + "\n");
1806 }
1807 
1808 void ConfigInputWidget::addWarning(QWidget *target, QWidget *cause, const QString msg)
1809 {
1810  cause->setProperty("hasWarning", true);
1811  addMessage(target, "Warning", msg);
1812 }
1813 
1814 void ConfigInputWidget::clearWarnings(QWidget *target, QWidget *causesParent)
1815 {
1816  foreach (QWidget *wid, causesParent->findChildren<QFrame *>())
1817  wid->setProperty("hasWarning", false);
1818  clearMessages(target, "Warning");
1819 }
1820 
1821 void ConfigInputWidget::checkReprojection()
1822 {
1823  const QString prefix = sender()->objectName().left(9); // this is a little fragile
1824  QComboBox *rep = findChild<QComboBox *>(prefix + "Rep");
1825  Q_ASSERT(rep);
1826 
1827  clearMessages(m_config->gbxFMMessages, prefix);
1828 
1829  QStringList axes;
1830  QStringList allowed_modes;
1831  if (rep->currentText() == "CameraAngle") {
1832  axes << "Roll"
1833  << "Yaw";
1834  allowed_modes << "AxisLock"
1835  << "Rate"
1836  << "WeakLevelling"
1837  << "AcroPlus"
1838  << "LQG";
1839  } else if (rep->currentText() == "HeadFree") {
1840  axes << "Roll"
1841  << "Pitch";
1842  allowed_modes << "AxisLock"
1843  << "Rate"
1844  << "WeakLevelling"
1845  << "AcroPlus"
1846  << "Attitude"
1847  << "Horizon"
1848  << "LQG"
1849  << "AttitudeLQG";
1850  } else {
1851  return;
1852  }
1853 
1854  QString selected_mode;
1855  foreach (const QString axis_name, axes) {
1856  QComboBox *axis = findChild<QComboBox *>(prefix + axis_name);
1857  Q_ASSERT(axis);
1858 
1859  // make sure only allowed modes are used
1860  if (!allowed_modes.contains(axis->currentText()))
1861  addMessage(m_config->gbxFMMessages, prefix,
1862  QString("Stabilized%0: When using %1 reprojection, only the following "
1863  "stabilization modes are allowed on axes %2: %3")
1864  .arg(prefix.right(1))
1865  .arg(rep->currentText())
1866  .arg(axes.join(", "))
1867  .arg(allowed_modes.join(", ")));
1868 
1869  // axes must match
1870  if (selected_mode.length() < 1)
1871  selected_mode = axis->currentText();
1872  if (selected_mode != axis->currentText())
1873  addMessage(m_config->gbxFMMessages, prefix,
1874  QString("Stabilized%0: When using %1 reprojection, stabilization modes must "
1875  "match for %2 axes!")
1876  .arg(prefix.right(1))
1877  .arg(rep->currentText())
1878  .arg(axes.join(", ")));
1879  }
1880 }
1881 
1883 ConfigInputWidget::armingMethodFromArmName(const QString &name)
1884 {
1885  foreach (const ArmingMethod &method, armingMethods) {
1886  if (method.armName == name)
1887  return method;
1888  }
1889  Q_ASSERT(false);
1890  // default to always disarmed
1891  return armingMethods.at(0);
1892 }
1893 
1895 ConfigInputWidget::armingMethodFromUavoOption(const QString &option)
1896 {
1897  // ignore stuff after plus to make switch arming cases work
1898  QString opt = option.split('+', QString::SkipEmptyParts).at(0);
1899  foreach (const ArmingMethod &method, armingMethods) {
1900  if (method.uavoOption.split('+', QString::SkipEmptyParts).at(0) == opt)
1901  return method;
1902  }
1903  Q_ASSERT(false);
1904  // default to always disarmed
1905  return armingMethods.at(0);
1906 }
1907 
1908 void ConfigInputWidget::checkArmingConfig()
1909 {
1910  if (armingConfigUpdating)
1911  return;
1912 
1913  // clear existing warnings
1914  clearWarnings(m_config->gbWarnings, m_config->saArmingSettings);
1915 
1916  SystemSettings *sys =
1917  qobject_cast<SystemSettings *>(getObjectManager()->getObject(SystemSettings::NAME));
1918  Q_ASSERT(sys);
1919  const quint8 vehicle = sys->getAirframeType();
1920 
1921  const ArmingMethod arming = armingMethodFromArmName(m_config->cbArmMethod->currentText());
1922  m_config->lblDisarmMethod->setText((arming.isStick ? tr("throttle low and ") : "")
1923  + arming.disarmName);
1924 
1925  if (lastArmingMethod != arming.method && lastArmingMethod != ARM_INVALID) {
1926  if (arming.method == ARM_SWITCH) {
1927  m_config->cbThrottleCheck->setChecked(true);
1928  }
1929  }
1930  lastArmingMethod = arming.method;
1931 
1932  m_config->frStickArmTime->setVisible(arming.isStick);
1933  m_config->frSwitchArmTime->setVisible(arming.isSwitch);
1934  m_config->cbSwitchArmTime->setEnabled(true);
1935  // TODO: consider throttle check for initial arm with always armed
1936  m_config->frThrottleCheck->setVisible(arming.isSwitch);
1937 
1938  m_config->frStickDisarmTime->setVisible(arming.isStick);
1939  m_config->frThrottleTimeout->setVisible(!arming.isFixed);
1940  m_config->frAutoDisarm->setVisible(!arming.isFixed);
1941 
1942  m_config->gbDisarming->setVisible(!arming.isFixed);
1943 
1944  // check hangtime, recommend switch arming if enabled
1945  bool hangtime = false;
1946  StabilizationSettings *stabilizationSettings = qobject_cast<StabilizationSettings *>(
1948  Q_ASSERT(stabilizationSettings);
1949  if (stabilizationSettings)
1950  hangtime = stabilizationSettings->getLowPowerStabilizationMaxTime() > 0;
1951 
1952  if (hangtime) {
1953  m_config->lblRecommendedArm->setText(tr("Switch"));
1954  if (arming.method != ARM_ALWAYS_DISARMED && !arming.isSwitch)
1955  addWarning(m_config->gbWarnings, m_config->frArmMethod,
1956  tr("Switch arming is recommended when hangtime is enabled."));
1957  } else {
1958  switch (vehicle) {
1959  case SystemSettings::AIRFRAMETYPE_HELICP:
1960  m_config->lblRecommendedArm->setText(tr("Switch"));
1961  break;
1962  case SystemSettings::AIRFRAMETYPE_HEXA:
1963  case SystemSettings::AIRFRAMETYPE_HEXACOAX:
1964  case SystemSettings::AIRFRAMETYPE_HEXAX:
1965  case SystemSettings::AIRFRAMETYPE_OCTO:
1966  case SystemSettings::AIRFRAMETYPE_OCTOCOAXP:
1967  case SystemSettings::AIRFRAMETYPE_OCTOCOAXX:
1968  case SystemSettings::AIRFRAMETYPE_OCTOV:
1969  case SystemSettings::AIRFRAMETYPE_QUADP:
1970  case SystemSettings::AIRFRAMETYPE_QUADX:
1971  case SystemSettings::AIRFRAMETYPE_TRI:
1972  case SystemSettings::AIRFRAMETYPE_VTOL:
1973  m_config->lblRecommendedArm->setText(tr("Switch, roll or yaw"));
1974  break;
1975  default:
1976  m_config->lblRecommendedArm->setText(tr("Any"));
1977  }
1978  }
1979 
1980  if (!m_config->frThrottleCheck->isHidden() && !m_config->cbThrottleCheck->isChecked())
1981  addWarning(m_config->gbWarnings, m_config->frThrottleCheck,
1982  tr("Your vehicle can be armed while throttle is not low, be careful!"));
1983 
1984  if (!m_config->frFailsafeTimeout->isHidden() && !m_config->frAutoDisarm->isHidden()
1985  && !m_config->sbFailsafeTimeout->value())
1986  addWarning(
1987  m_config->gbWarnings, m_config->frFailsafeTimeout,
1988  tr("Your vehicle will remain armed indefinitely when receiver is disconnected!"));
1989 
1990  if (!m_config->frThrottleTimeout->isHidden() && !m_config->sbThrottleTimeout->value())
1991  addWarning(
1992  m_config->gbWarnings, m_config->frThrottleTimeout,
1993  tr("Your vehicle will remain armed indefinitely while throttle is low, be careful!"));
1994 
1995  // re-add stylesheet to force it to be recompiled and applied
1996  m_config->saArmingSettings->setStyleSheet(m_config->saArmingSettings->styleSheet());
1997 
1998  QString armOption = arming.uavoOption;
1999  switch (arming.method) {
2000  case ARM_SWITCH:
2001  if (m_config->cbThrottleCheck->isChecked())
2002  armOption += "+Throttle";
2003  break;
2004  default:
2005  break;
2006  }
2007  if (cbArmingOption->currentText() != armOption)
2008  cbArmingOption->setCurrentText(armOption);
2009 }
2010 
2011 void ConfigInputWidget::timeoutCheckboxChanged()
2012 {
2013  m_config->sbThrottleTimeout->setEnabled(m_config->cbThrottleTimeout->isChecked());
2014  if (!m_config->cbThrottleTimeout->isChecked())
2015  m_config->sbThrottleTimeout->setValue(0);
2016  else if (!m_config->sbThrottleTimeout->value())
2017  resetWidgetToDefault(m_config->sbThrottleTimeout);
2018 
2019  m_config->sbFailsafeTimeout->setEnabled(m_config->cbFailsafeTimeout->isChecked());
2020  if (!m_config->cbFailsafeTimeout->isChecked())
2021  m_config->sbFailsafeTimeout->setValue(0);
2022  else if (!m_config->sbFailsafeTimeout->value())
2023  resetWidgetToDefault(m_config->sbFailsafeTimeout);
2024 
2025  checkArmingConfig();
2026 }
2027 
2028 void ConfigInputWidget::updateArmingConfig(UAVObject *obj)
2029 {
2030  armingConfigUpdating = true;
2031 
2032  ManualControlSettings *man = qobject_cast<ManualControlSettings *>(obj);
2033  Q_ASSERT(man);
2034  if (!man)
2035  return;
2036 
2037  // don't want to mark the widget dirty by any changes from the UAVO
2038  bool wasDirty = isDirty();
2039 
2040  m_config->saArmingSettings->setEnabled(man->getIsPresentOnHardware());
2041  m_config->cbThrottleTimeout->setChecked(man->getArmedTimeout() > 0);
2042  m_config->cbFailsafeTimeout->setChecked(man->getInvalidRXArmedTimeout() > 0);
2043 
2044  const QString &armingOption = man->getField("Arming")->getValue().toString();
2045  ArmingMethod arming = armingMethodFromUavoOption(armingOption);
2046  if (m_config->cbArmMethod->currentText() != arming.armName)
2047  m_config->cbArmMethod->setCurrentText(arming.armName);
2048  m_config->cbThrottleCheck->setChecked(armingOption.contains("Throttle"));
2049 
2050  setDirty(wasDirty);
2051 
2052  armingConfigUpdating = false;
2053 
2054  checkArmingConfig();
2055 }
2056 
2057 void ConfigInputWidget::fillArmingComboBox()
2058 {
2059  m_config->cbArmMethod->clear();
2060  foreach (const ArmingMethod &method, armingMethods)
2061  m_config->cbArmMethod->addItem(method.armName, method.method);
2062 }
2063 
2064 void ConfigInputWidget::checkCollective()
2065 {
2066  auto inputForm = qobject_cast<inputChannelForm *>(sender());
2067  if (!inputForm) {
2068  qWarning() << "invalid sender";
2069  Q_ASSERT(false);
2070  return;
2071  }
2072 
2073  clearWarnings(m_config->gbChannelWarnings, inputForm);
2074 
2075  if (!inputForm->assigned())
2076  return;
2077 
2078  SystemSettings *sys =
2079  qobject_cast<SystemSettings *>(getObjectManager()->getObject(SystemSettings::NAME));
2080  Q_ASSERT(sys);
2081  const quint8 vehicle = sys->getAirframeType();
2082 
2083  if (vehicle != SystemSettings::AIRFRAMETYPE_HELICP) {
2084  addWarning(m_config->gbChannelWarnings, inputForm,
2085  tr("Collective is normally not used for this vehicle type, are you sure?"));
2086  }
2087 }
virtual void setMetadata(const Metadata &mdata)=0
void setDirty(bool value)
void addUAVObjectToWidgetRelation(QString object, QString field, QWidget *widget, int index=0, double scale=1, bool isLimited=false, bool useUnits=false, QList< int > *defaultReloadGroups=nullptr, quint32 instID=0, bool oneWayBind=false)
Add an UAVObject field to widget relation to the management system Note: This is the instance called ...
UAVObjectUtilManager * utilMngr
void clearMessages(QWidget *widget, const QString type)
bool resetWidgetToDefault(QWidget *widget)
resetWidgetToDefault Resets the widget to the default value for the associated field ...
void resizeEvent(QResizeEvent *event)
void setMetadata(const Metadata &mdata)
diff
Definition: OPPlots.m:68
static void SetFlightAccess(Metadata &meta, AccessMode mode)
Definition: uavobject.cpp:387
Core plugin system that manages the plugins, their life cycle and their registered objects...
Definition: pluginmanager.h:53
void objectUnpacked(UAVObject *obj)
objectUnpacked: triggered whenever an object is unpacked (i.e. arrives from the telemetry link) ...
void clearWarnings(QWidget *target, QWidget *causesParent)
for i
Definition: OPPlots.m:140
void objectUpdated(UAVObject *obj)
Signal sent whenever any field of the object is updated.
QMap< QString, UAVObject::Metadata > readAllNonSettingsMetadata()
UAVObjectUtilManager::readAllNonSettingsMetadata Convenience function for calling readMetadata...
static void SetFlightTelemetryUpdateMode(Metadata &meta, UpdateMode val)
Definition: uavobject.cpp:468
void addMessage(QWidget *target, const QString type, const QString msg)
void assignmentChanged()
quint32 getObjID()
Definition: uavobject.cpp:107
() NAME()
void setName(QString &name)
void addWarning(QWidget *target, QWidget *cause, const QString msg)
virtual void enableControls(bool enable)
static const QVector< ArmingMethod > armingMethods
virtual void populateWidgets()
Metadata getMetadata()
virtual void enableControls(bool enable)
virtual void refreshWidgetsValues(UAVObject *obj=NULL)
UAVObject * getObject(const QString &name, quint32 instId=0)
UAVObjectUtilManager * getObjectUtilManager()
ConfigTaskWidget::getObjectUtilManager Utility function to get a pointer to the object manager utilit...
QVector< QVector< UAVDataObject * > > getDataObjectsVector()
ConfigInputWidget(QWidget *parent=nullptr)
UAVObjectManager * getObjectManager()
ConfigTaskWidget::getObjectManager Utility function to get a pointer to the object manager...