dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
configautotunewidget.cpp
Go to the documentation of this file.
1 
13 /*
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful, but
20  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  * for more details.
23  *
24  * You should have received a copy of the GNU General Public License along
25  * with this program; if not, see <http://www.gnu.org/licenses/>
26  */
27 
28 #define _USE_MATH_DEFINES
29 
30 #include <cmath>
31 #include <algorithm>
32 #include <numeric>
33 
34 #include "ffft/FFTReal.h"
35 
36 #include "configautotunewidget.h"
37 
40 
41 #include "altitudeholdsettings.h"
42 #include "manualcontrolsettings.h"
43 #include "mixersettings.h"
44 #include "modulesettings.h"
45 #include "sensorsettings.h"
46 #include "stabilizationsettings.h"
47 #include "systemident.h"
48 #include "systemsettings.h"
51 #include "coreplugin/iboardtype.h"
52 
53 #include "physical_constants.h"
54 
55 #include <QtAlgorithms>
56 #include <QChartView>
57 #include <QClipboard>
58 #include <QCryptographicHash>
59 #include <QDebug>
60 #include <QDesktopServices>
61 #include <QFileDialog>
62 #include <QList>
63 #include <QPushButton>
64 #include <QStringList>
65 #include <QTextEdit>
66 #include <QUrl>
67 #include <QValueAxis>
68 #include <QVBoxLayout>
69 #include <QVector>
70 #include <QWidget>
71 #include <QWizard>
72 #include <QtNetwork/QNetworkAccessManager>
73 #include <QtNetwork/QNetworkRequest>
74 
75 //#define CONF_ATUNE_DEBUG
76 #ifdef CONF_ATUNE_DEBUG
77 #define CONF_ATUNE_QXTLOG_DEBUG(...) qDebug() << __VA_ARGS__
78 #else // CONF_ATUNE_DEBUG
79 #define CONF_ATUNE_QXTLOG_DEBUG(...)
80 #endif // CONF_ATUNE_DEBUG
81 
82 const QString ConfigAutotuneWidget::databaseUrl =
83  QString("http://dronin-autotown.appspot.com/storeTune");
84 
86  : ConfigTaskWidget(parent)
87 {
88  parentConfigWidget = parent;
89  m_autotune = new Ui_AutotuneWidget();
90  m_autotune->setupUi(this);
91 
92  // Connect automatic signals
95 
97 
98  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
99  utilMngr = pm->getObject<UAVObjectUtilManager>();
100 
101  connect(this, &ConfigTaskWidget::autoPilotConnected, this, &ConfigAutotuneWidget::atConnected,
102  Qt::QueuedConnection);
103  connect(this, &ConfigTaskWidget::autoPilotDisconnected, this,
104  &ConfigAutotuneWidget::atDisconnected, Qt::QueuedConnection);
105  connect(m_autotune->adjustTune, &QPushButton::pressed, this,
106  QOverload<>::of(&ConfigAutotuneWidget::openAutotuneDialog));
107  connect(m_autotune->fromDataFileBtn, &QPushButton::pressed, this,
108  &ConfigAutotuneWidget::openAutotuneFile);
109 
110  m_autotune->adjustTune->setEnabled(isAutopilotConnected());
111 }
112 
113 void ConfigAutotuneWidget::atConnected()
114 {
115  m_autotune->adjustTune->setEnabled(true);
116  checkNewAutotune();
117 }
118 
119 void ConfigAutotuneWidget::atDisconnected()
120 {
121  m_autotune->adjustTune->setEnabled(false);
122 }
123 
124 void ConfigAutotuneWidget::checkNewAutotune()
125 {
126  SystemIdent *systemIdent = SystemIdent::GetInstance(getObjectManager());
127 
128  SystemIdent::DataFields systemIdentData = systemIdent->getData();
129 
130  if (systemIdentData.NewTune) {
131  // There's a new tune! Let's talk to the user about it.
132 
133  // First though, cue resetting this flag to false.
134  systemIdentData.NewTune = false;
135 
136  systemIdent->setData(systemIdentData);
137 
138  systemIdent->updated();
139 
140  // And persist it, so we're good for next time.
141  saveObjectToSD(systemIdent);
142 
143  // Now let's get that dialog open.
144  openAutotuneDialog(true);
145  }
146 }
147 
152 QJsonDocument ConfigAutotuneWidget::getResultsJson(AutotuneFinalPage *autotuneShareForm,
153  AutotunedValues *tuneState)
154 {
155  deviceDescriptorStruct firmware;
156  utilMngr->getBoardDescriptionStruct(firmware);
157 
158  QJsonObject rawSettings;
159 
160  QJsonObject json;
161  json["dataVersion"] = 3;
162  json["uniqueId"] =
163  QString(QCryptographicHash::hash(utilMngr->getBoardCPUSerial(), QCryptographicHash::Sha256)
164  .toHex());
165 
166  QJsonObject vehicle, fw;
167  Core::IBoardType *board = utilMngr->getBoardType();
168  if (board) {
169  fw["board"] = board->shortName();
170  }
171 
172  fw["tag"] = firmware.gitTag;
173  fw["commit"] = firmware.gitHash;
174  QDateTime fwDate = QDateTime::fromString(firmware.gitDate, "yyyyMMdd hh:mm");
175  fwDate.setTimeSpec(Qt::UTC); // this makes it append a Z to the string indicating UTC
176  fw["date"] = fwDate.toString(Qt::ISODate);
177  vehicle["firmware"] = fw;
178 
179  SystemSettings *sysSettings = SystemSettings::GetInstance(getObjectManager());
180 
181  rawSettings[sysSettings->getName()] = sysSettings->getJsonRepresentation();
182 
183  ActuatorSettings *actSettings = ActuatorSettings::GetInstance(getObjectManager());
184  rawSettings[actSettings->getName()] = actSettings->getJsonRepresentation();
185 
186  StabilizationSettings *stabSettings = StabilizationSettings::GetInstance(getObjectManager());
187  rawSettings[stabSettings->getName()] = stabSettings->getJsonRepresentation();
188 
189  SystemIdent *systemIdent = SystemIdent::GetInstance(getObjectManager());
190  rawSettings[systemIdent->getName()] = systemIdent->getJsonRepresentation();
191 
192  SensorSettings *senSettings = SensorSettings::GetInstance(getObjectManager());
193  rawSettings[senSettings->getName()] = senSettings->getJsonRepresentation();
194 
195  ManualControlSettings *manSettings = ManualControlSettings::GetInstance(getObjectManager());
196  rawSettings[manSettings->getName()] = manSettings->getJsonRepresentation();
197 
198  MixerSettings *mixSettings = MixerSettings::GetInstance(getObjectManager());
199  rawSettings[mixSettings->getName()] = mixSettings->getJsonRepresentation();
200 
201  // Query the board plugin for the connected board to get the specific
202  // hw settings object
203  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
204  if (pm != NULL) {
205  UAVObjectUtilManager *uavoUtilManager = pm->getObject<UAVObjectUtilManager>();
206  Core::IBoardType *board = uavoUtilManager->getBoardType();
207  if (board != NULL) {
208  QString hwSettingsName = board->getHwUAVO();
209 
210  UAVObject *hwSettings = getObjectManager()->getObject(hwSettingsName);
211  rawSettings[hwSettings->getName()] = hwSettings->getJsonRepresentation();
212  }
213  }
214 
215  vehicle["type"] = autotuneShareForm->acType->currentText();
216  vehicle["size"] = autotuneShareForm->acVehicleSize->text();
217  vehicle["weight"] = autotuneShareForm->acWeight->text();
218  vehicle["batteryCells"] = autotuneShareForm->acBatteryCells->currentText();
219  vehicle["esc"] = autotuneShareForm->acEscs->text();
220  vehicle["motor"] = autotuneShareForm->acMotors->text();
221  vehicle["props"] = autotuneShareForm->acProps->text();
222  json["vehicle"] = vehicle;
223 
224  json["userObservations"] = autotuneShareForm->teObservations->toPlainText();
225 
226  QJsonObject identification;
227  QJsonObject roll_ident;
228  roll_ident["gain"] = tuneState->beta[0];
229  roll_ident["bias"] = tuneState->bias[0];
230  roll_ident["noise"] = tuneState->noise[0];
231  roll_ident["tau"] = tuneState->tau[0];
232  identification["roll"] = roll_ident;
233 
234  QJsonObject pitch_ident;
235  pitch_ident["gain"] = tuneState->beta[1];
236  pitch_ident["bias"] = tuneState->bias[1];
237  pitch_ident["noise"] = tuneState->noise[1];
238  pitch_ident["tau"] = tuneState->tau[1];
239  identification["pitch"] = pitch_ident;
240 
241  QJsonObject yaw_ident;
242  yaw_ident["gain"] = tuneState->beta[2];
243  yaw_ident["bias"] = tuneState->bias[2];
244  yaw_ident["noise"] = tuneState->noise[2];
245  yaw_ident["tau"] = tuneState->tau[2];
246  identification["yaw"] = yaw_ident;
247 
248  identification["tau"] = tuneState->tau[0];
249  json["identification"] = identification;
250 
251  QJsonObject tuning, parameters, computed, misc;
252  parameters["damping"] = tuneState->damping;
253  parameters["noiseSensitivity"] = tuneState->noiseSens;
254 
255  tuning["parameters"] = parameters;
256  computed["naturalFrequency"] = tuneState->naturalFreq;
257  computed["derivativeCutoff"] = tuneState->derivativeCutoff;
258  computed["converged"] = tuneState->converged;
259  computed["iterations"] = tuneState->iterations;
260 
261  QJsonObject gains;
262  QJsonObject roll_gain, pitch_gain, yaw_gain, outer_gain, vert_gain;
263  roll_gain["kp"] = tuneState->kp[0];
264  roll_gain["ki"] = tuneState->ki[0];
265  roll_gain["kd"] = tuneState->kd[0];
266  gains["roll"] = roll_gain;
267  pitch_gain["kp"] = tuneState->kp[1];
268  pitch_gain["ki"] = tuneState->ki[1];
269  pitch_gain["kd"] = tuneState->kd[1];
270  gains["pitch"] = pitch_gain;
271  yaw_gain["kp"] = tuneState->kp[2];
272  yaw_gain["ki"] = tuneState->ki[2];
273  yaw_gain["kd"] = tuneState->kd[2];
274  gains["yaw"] = yaw_gain;
275  outer_gain["kp"] = tuneState->outerKp;
276  outer_gain["ki"] = tuneState->outerKi;
277  gains["outer"] = outer_gain;
278  vert_gain["kp"] = tuneState->vertSpeedKp;
279  vert_gain["ki"] = tuneState->vertSpeedKi;
280  vert_gain["poskp"] = tuneState->vertPosKp;
281  computed["gains"] = gains;
282  tuning["computed"] = computed;
283  json["tuning"] = tuning;
284 
285  json["rawSettings"] = rawSettings;
286 
287  QByteArray compressedData = qCompress(tuneState->data, 9);
288  json["rawTuneData"] = QString(compressedData.toBase64());
289 
290  return QJsonDocument(json);
291 }
292 
293 void ConfigAutotuneWidget::persistShareForm(AutotuneFinalPage *autotuneShareForm)
294 {
295  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
297  settings->setObservations(autotuneShareForm->teObservations->toPlainText());
298  settings->setBoardType(autotuneShareForm->acBoard->text());
299  settings->setMotors(autotuneShareForm->acMotors->text());
300  settings->setESCs(autotuneShareForm->acEscs->text());
301  settings->setProps(autotuneShareForm->acProps->text());
302  settings->setWeight(autotuneShareForm->acWeight->text().toInt());
303  settings->setVehicleSize(autotuneShareForm->acVehicleSize->text().toInt());
304  settings->setVehicleType(autotuneShareForm->acType->currentText());
305  settings->setBatteryCells(autotuneShareForm->acBatteryCells->currentText().toInt());
306 }
307 
308 void ConfigAutotuneWidget::stuffShareForm(AutotuneFinalPage *autotuneShareForm)
309 {
310  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
312 
313  autotuneShareForm->teObservations->setText(settings->getObservations());
314  autotuneShareForm->acBoard->setText(settings->getBoardType());
315  autotuneShareForm->acMotors->setText(settings->getMotors());
316  autotuneShareForm->acEscs->setText(settings->getESCs());
317  autotuneShareForm->acProps->setText(settings->getProps());
318  autotuneShareForm->acWeight->setText(QString::number(settings->getWeight()));
319  autotuneShareForm->acVehicleSize->setText(QString::number(settings->getVehicleSize()));
320  autotuneShareForm->acType->setCurrentText(settings->getVehicleType());
321  autotuneShareForm->acBatteryCells->setCurrentText(QString::number(settings->getBatteryCells()));
322 
323  SystemSettings *sysSettings = SystemSettings::GetInstance(getObjectManager());
324  if (sysSettings) {
325  UAVObjectField *frameType = sysSettings->getField("AirframeType");
326  if (frameType) {
327  autotuneShareForm->acType->clear();
328  autotuneShareForm->acType->addItems(frameType->getOptions());
329 
330  QString currentFrameType;
331  currentFrameType = frameType->getValue().toString();
332  if (!currentFrameType.isNull()) {
333  autotuneShareForm->acType->setEditable(false);
334  autotuneShareForm->acType->setCurrentText(currentFrameType);
335  }
336  }
337  }
338 
339  Core::IBoardType *board = utilMngr->getBoardType();
340 
341  if (board) {
342  autotuneShareForm->acBoard->setText(board->shortName());
343  autotuneShareForm->acBoard->setEnabled(false);
344  }
345 }
346 
347 void ConfigAutotuneWidget::openAutotuneFile()
348 {
349  QString fileName =
350  QFileDialog::getOpenFileName(this, tr("Open autotune partition"), "",
351  tr("Partition image Files (*.bin) ;; All files (*.*)"));
352 
353  if (fileName.isEmpty()) {
354  return;
355  }
356 
357  QFile file(fileName);
358  if (!file.open(QIODevice::ReadOnly)) {
359  /* XXX error dialog */
360  return;
361  }
362 
363  AutotunedValues vals = { };
364  vals.data = file.readAll();
365 
366  openAutotuneDialog(false, &vals);
367 }
368 
369 void ConfigAutotuneWidget::openAutotuneDialog()
370 {
371  openAutotuneDialog(false);
372 }
373 
374 void ConfigAutotuneWidget::openAutotuneDialog(bool autoOpened,
375  AutotunedValues *precalc_vals)
376 {
377  QWizard wizard(NULL, Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint
378  | Qt::WindowCloseButtonHint);
379 
380  wizard.setPixmap(QWizard::BackgroundPixmap, QPixmap(":/configgadget/images/autotunebg.png"));
381 
382  // Used to track state across the entire wizard path.
383  AutotunedValues av = {};
384 
385  if (precalc_vals) {
386  av = *precalc_vals;
387  }
388 
389  av.converged = false;
390 
391  // The first page is simple; construct it from scratch.
392  QWizardPage *beginning = new AutotuneBeginningPage(NULL, autoOpened, &av);
393 
394  // Keep a cancel button, even on OS X.
395  wizard.setOption(QWizard::NoCancelButton, false);
396 
397  wizard.setMinimumSize(735, 600);
398 
399  wizard.addPage(beginning);
400 
401  AutotuneFinalPage *pg = new AutotuneFinalPage(NULL);
402 
403  wizard.addPage(new AutotuneMeasuredPropertiesPage(NULL, &av));
404  wizard.addPage(new AutotuneSlidersPage(NULL, &av));
405 
406  stuffShareForm(pg);
407 
408  wizard.addPage(pg);
409 
410  wizard.setWindowTitle("Autotune Wizard");
411  wizard.exec();
412 
413  if (av.valid && (wizard.result() == QDialog::Accepted) && av.converged) {
414  // Apply and save data to board.
415  StabilizationSettings *stabilizationSettings =
416  StabilizationSettings::GetInstance(getObjectManager());
417  Q_ASSERT(stabilizationSettings);
418  if (!stabilizationSettings) {
419  qWarning() << "Abandoning autotune commit because no StabSettings";
420  return;
421  }
422 
423  StabilizationSettings::DataFields stabData = stabilizationSettings->getData();
424 
425  stabData.RollRatePID[StabilizationSettings::ROLLRATEPID_KP] = av.kp[0];
426  stabData.RollRatePID[StabilizationSettings::ROLLRATEPID_KI] = av.ki[0];
427  stabData.RollRatePID[StabilizationSettings::ROLLRATEPID_KD] = av.kd[0];
428  stabData.PitchRatePID[StabilizationSettings::PITCHRATEPID_KP] = av.kp[1];
429  stabData.PitchRatePID[StabilizationSettings::PITCHRATEPID_KI] = av.ki[1];
430  stabData.PitchRatePID[StabilizationSettings::PITCHRATEPID_KD] = av.kd[1];
431  if (av.kp[2] > 0) {
432  stabData.YawRatePID[StabilizationSettings::YAWRATEPID_KP] = av.kp[2];
433  stabData.YawRatePID[StabilizationSettings::YAWRATEPID_KI] = av.ki[2];
434  stabData.YawRatePID[StabilizationSettings::YAWRATEPID_KD] = av.kd[2];
435  }
436 
437  stabData.DerivativeCutoff = av.derivativeCutoff;
438 
439  stabData.RollPI[StabilizationSettings::ROLLPI_KP] = av.outerKp;
440  stabData.RollPI[StabilizationSettings::ROLLPI_KI] = av.outerKi;
441  stabData.PitchPI[StabilizationSettings::PITCHPI_KP] = av.outerKp;
442  stabData.PitchPI[StabilizationSettings::PITCHPI_KI] = av.outerKi;
443 
444  stabilizationSettings->setData(stabData);
445  stabilizationSettings->updated();
446 
447  saveObjectToSD(stabilizationSettings);
448 
449 
450  SystemIdent *systemIdent = SystemIdent::GetInstance(getObjectManager());
451  if (systemIdent) {
452  SystemIdent::DataFields systemIdentData = systemIdent->getData();
453 
454  for (int i = 0; i < 3; i++) {
455  systemIdentData.Tau[i] = av.tau[i];
456  systemIdentData.Beta[i] = av.beta[i];
457  }
458 
459  systemIdent->setData(systemIdentData);
460  systemIdent->updated();
461 
462  saveObjectToSD(systemIdent);
463  }
464 
465  if (av.vertSpeedKp > 0) {
466  AltitudeHoldSettings *altSettings =
467  AltitudeHoldSettings::GetInstance(getObjectManager());
468 
469  if (!altSettings) {
470  qWarning() << "Not committing alt hold data because obj not present";
471  } else {
472  altSettings->setPositionKp(av.vertPosKp);
473  altSettings->setVelocityKp(av.vertSpeedKp);
474  altSettings->setVelocityKi(av.vertSpeedKi);
475  altSettings->updated();
476  saveObjectToSD(altSettings);
477  }
478  }
479 
480  if (pg->shareBox->isChecked()) {
481  persistShareForm(pg);
482 
483  // share to autotown
484 
485  QJsonDocument json = getResultsJson(pg, &av);
486 
487  // Do this in the background, completely async.
488  QUrl url(databaseUrl);
489  QNetworkRequest request(url);
490  request.setHeader(QNetworkRequest::ContentTypeHeader,
491  "application/json; charset=utf-8");
492  QNetworkAccessManager *manager = new QNetworkAccessManager(this);
493  QNetworkReply *reply = manager->post(request, json.toJson());
494  connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
495  }
496  }
497 }
498 
500  AutotunedValues *autoValues)
501  : QWizardPage(parent)
502 {
503  setupUi(this);
504 
505  this->tuneState = autoValues;
506 }
507 
508 QChart *AutotuneMeasuredPropertiesPage::makeChart(int axis) {
509  QChart *chart;
510 
511  chart = new QChart();
512 
513  tuneState->actual[axis]->setName(tr("Actual"));
514  chart->addSeries(tuneState->actual[axis]);
515  tuneState->model[axis]->setName(tr("Predicted"));
516  chart->addSeries(tuneState->model[axis]);
517 
518  chart->createDefaultAxes();
519 
520  chart->axisX()->setTitleText(tr("Time"));
521  dynamic_cast<QValueAxis *>(chart->axisX())->setLabelFormat("%.0f ms");
522  chart->axisY()->setTitleText(tr("Angular acceleration"));
523  chart->axisY()->setLabelsVisible(false);
524 
525  return chart;
526 }
527 
529 {
530  measuredRollGain->setText(QString::number(tuneState->beta[0], 'f', 2));
531  measuredPitchGain->setText(QString::number(tuneState->beta[1], 'f', 2));
532  measuredYawGain->setText(QString::number(tuneState->beta[2], 'f', 2));
533 
534  measuredRollBias->setText(QString::number(tuneState->bias[0], 'f', 3));
535  measuredPitchBias->setText(QString::number(tuneState->bias[1], 'f', 3));
536  measuredYawBias->setText(QString::number(tuneState->bias[2], 'f', 3));
537 
538  rollTau->setText(QString::number(tuneState->tau[0], 'f', 4));
539  pitchTau->setText(QString::number(tuneState->tau[1], 'f', 4));
540  yawTau->setText(QString::number(tuneState->tau[2], 'f', 4));
541 
542  measuredRollNoise->setText(QString::number(tuneState->noise[0], 'f', 2));
543  measuredPitchNoise->setText(QString::number(tuneState->noise[1], 'f', 2));
544  measuredYawNoise->setText(QString::number(tuneState->noise[2], 'f', 2));
545 
546  rollChartView->setRenderHint(QPainter::Antialiasing);
547  rollChartView->setChart(makeChart(0));
548 
549  pitchChartView->setRenderHint(QPainter::Antialiasing);
550  pitchChartView->setChart(makeChart(1));
551 
552  yawChartView->setRenderHint(QPainter::Antialiasing);
553  yawChartView->setChart(makeChart(2));
554 }
555 
557  : QWizardPage(parent)
558 {
559  setupUi(this);
560 
561  tuneState = autoValues;
562 
563  // connect sliders to computation
564  connect(rateDamp, &QAbstractSlider::valueChanged, this, &AutotuneSlidersPage::compute);
565  connect(rateNoise, &QAbstractSlider::valueChanged, this, &AutotuneSlidersPage::compute);
566  connect(cbUseYaw, &QAbstractButton::toggled, this, &AutotuneSlidersPage::compute);
567  connect(cbUseOuterKi, &QAbstractButton::toggled, this, &AutotuneSlidersPage::compute);
568  connect(cbTuneAlt, &QAbstractButton::toggled, this, &AutotuneSlidersPage::computeThrust);
569 
570  connect(btnResetSliders, &QAbstractButton::pressed, this, &AutotuneSlidersPage::resetSliders);
571 }
572 
573 
575 {
576  resetSliders();
577 }
578 
579 void AutotuneSlidersPage::resetSliders()
580 {
581  rateDamp->setValue(105);
582  rateNoise->setValue(10);
583 
584  compute();
585  computeThrust();
586 }
587 
589 {
590  return tuneState->converged;
591 }
592 
593 void AutotuneSlidersPage::setText(QLabel *lbl, double value, int precision)
594 {
595  if (value < 0) {
596  lbl->setText("–");
597  } else {
598  lbl->setText(QString::number(value, 'f', precision));
599  }
600 }
601 
602 void AutotuneSlidersPage::computeThrust()
603 {
604  const double baseKp = 0.5;
605  const double tauDerate = 0.025;
606  const double kiAdjust = 1.5;
607  const double outerSlowdown = 0.125;
608  const double okpCeiling = 0.9 * GRAVITY / 4.0;
609 
610  double hoverThrust = 0;
611  double thrustTau = (tuneState->tau[0] + tuneState->tau[1]) / 2 +
612  tauDerate;
613  /*
614  * (Thrust tau is not guaranteed to be axis tau, but it's a pretty
615  * good approximation. We derate it, mostly because we don't trust
616  * cfvert to be fast.)
617  */
618  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
619 
620  UAVObjectManager *objMngr = nullptr;
621  if (pm) {
622  objMngr = pm->getObject<UAVObjectManager>();
623  }
624 
625  SystemIdent *systemIdent = nullptr;
626 
627  if (objMngr) {
628  systemIdent = SystemIdent::GetInstance(objMngr);
629  }
630 
631  if (systemIdent) {
632  hoverThrust = systemIdent->getHoverThrottle();
633  }
634 
635  if ((hoverThrust < 0.05) ||
636  (hoverThrust > 0.6) ||
637  (thrustTau < 0.005) ||
638  (thrustTau > 0.200)) {
639  cbTuneAlt->setChecked(false);
640  cbTuneAlt->setEnabled(false);
641  }
642 
643  if (cbTuneAlt->isChecked()) {
644  tuneState->vertSpeedKp = (baseKp * hoverThrust / GRAVITY) /
645  thrustTau;
646  tuneState->vertSpeedKi = tuneState->vertSpeedKp /
647  (kiAdjust * 2 * M_PI * thrustTau);
648  tuneState->vertPosKp = outerSlowdown / thrustTau;
649 
650  if (tuneState->vertPosKp > okpCeiling) {
651  tuneState->vertPosKp = okpCeiling;
652  }
653  } else {
654  tuneState->vertSpeedKp = -1;
655  tuneState->vertSpeedKi = -1;
656  tuneState->vertPosKp = -1;
657  }
658 
659  setText(lblAltVelKp, tuneState->vertSpeedKp, 3);
660  setText(lblAltVelKi, tuneState->vertSpeedKi, 3);
661  setText(lblAltPosKp, tuneState->vertSpeedKi, 3);
662 }
663 
664 void AutotuneSlidersPage::compute()
665 {
666  // These three parameters define the desired response properties
667  // - rate scale in the fraction of the natural speed of the system
668  // to strive for.
669  // - damp is the amount of damping in the system. higher values
670  // make oscillations less likely
671  // - ghf is the amount of high frequency gain and limits the influence
672  // of noise
673 
674  const double ghf = rateNoise->value() / 1000.0;
675  const double damp = rateDamp->value() / 100.0;
676 
677  tuneState->damping = damp;
678  tuneState->noiseSens = ghf;
679 
680  /* Average roll and pitch tau for now. */
681  double tau = (tuneState->tau[0] + tuneState->tau[1]) / 2.0;
682  double beta_roll = tuneState->beta[0];
683  double beta_pitch = tuneState->beta[1];
684  double beta_yaw = tuneState->beta[2];
685 
686  // First clear out warnings..
687  lblWarnings->setText("");
688 
689  if (beta_yaw < 6.8) {
690  lblWarnings->setText(tr("Unable to auto-calculate yaw gains for this craft."));
691  cbUseYaw->setChecked(false);
692  cbUseYaw->setEnabled(false);
693  }
694 
695  bool doYaw = cbUseYaw->isChecked();
696  bool doOuterKi = cbUseOuterKi->isChecked();
697 
698  double wn = 1 / tau, wn_last = 1 / tau + 10;
699  double tau_d = 0, tau_d_last = 1000;
700 
701  const int iteration_limit = 100, stability_limit = 5;
702  bool converged = false;
703  int iterations = 0;
704  int stable_iterations = 0;
705 
706  while (!converged && (++iterations <= iteration_limit)) {
707  double tau_d_roll =
708  (2 * damp * tau * wn - 1) / (4 * tau * damp * damp * wn * wn - 2 * damp * wn
709  - tau * wn * wn + exp(beta_roll) * ghf);
710  double tau_d_pitch =
711  (2 * damp * tau * wn - 1) / (4 * tau * damp * damp * wn * wn - 2 * damp * wn
712  - tau * wn * wn + exp(beta_pitch) * ghf);
713 
714  // Select the slowest filter property
715  tau_d = (tau_d_roll > tau_d_pitch) ? tau_d_roll : tau_d_pitch;
716  wn = (tau + tau_d) / (tau * tau_d) / (2 * damp + 2);
717 
718  // check for convergence
719  if (fabs(tau_d - tau_d_last) <= 0.00001 && fabs(wn - wn_last) <= 0.00001) {
720  if (++stable_iterations >= stability_limit)
721  converged = true;
722  } else {
723  stable_iterations = 0;
724  }
725  tau_d_last = tau_d;
726  wn_last = wn;
727  }
728 
729  tuneState->iterations = iterations;
730  tuneState->converged = converged;
731 
732  tuneState->derivativeCutoff = 1 / (2 * M_PI * tau_d);
733  tuneState->naturalFreq = wn / 2 / M_PI;
734 
735  // Set the real pole position. The first pole is quite slow, which
736  // prevents the integral being too snappy and driving too much
737  // overshoot.
738  const double a = ((tau + tau_d) / tau / tau_d - 2 * damp * wn) / 25.0;
739  const double b = ((tau + tau_d) / tau / tau_d - 2 * damp * wn - a);
740 
741  CONF_ATUNE_QXTLOG_DEBUG("ghf: ", ghf);
742  CONF_ATUNE_QXTLOG_DEBUG("wn: ", wn, "tau_d: ", tau_d);
743  CONF_ATUNE_QXTLOG_DEBUG("a: ", a, " b: ", b);
744 
745  // Calculate the gain for the outer loop by approximating the
746  // inner loop as a single order lpf. Set the outer loop to be
747  // critically damped;
748  const double zeta_o = 1.3;
749  tuneState->outerKp = 1 / 4.0 / (zeta_o * zeta_o) / (1 / wn);
750 
751  // Except, if this is very high, we may be slew rate limited and pick
752  // up oscillation that way. Fix it with very soft clamping.
753  //
754  // When we come up with outer KP's of less than 10, things seem safe
755  // no matter what. So beyond 7, start to fade our response.
756  //
757  // Chosen to have derivative of 1 at 7, and .5 at 10, and never to have
758  // derivative change sign.
759  if (tuneState->outerKp > 7.0) {
760  tuneState->outerKp = 3 * log(tuneState->outerKp - 4) +
761  7.0 - 3 * log(3);
762  }
763 
764  if (doOuterKi) {
765  tuneState->outerKp *= 0.95f; // Pick up some margin.
766  // Add a zero at 1/15th the innermost bandwidth.
767  tuneState->outerKi = 0.75 * tuneState->outerKp / (2 * M_PI * tau * 15.0);
768  } else {
769  tuneState->outerKi = 0;
770  }
771 
772  for (int i = 0; i < 3; i++) {
773  double beta = exp(tuneState->beta[i]);
774 
775  double ki;
776  double kp;
777  double kd;
778 
779  ki = a * b * wn * wn * tau * tau_d / beta;
780  kp = tau * tau_d * ((a + b) * wn * wn + 2 * a * b * damp * wn) / beta - ki * tau_d;
781  kd = (tau * tau_d * (a * b + wn * wn + (a + b) * 2 * damp * wn) - 1) / beta - kp * tau_d;
782 
783  tuneState->kp[i] = kp;
784  tuneState->ki[i] = ki;
785  tuneState->kd[i] = kd;
786  }
787 
788  if (!doYaw) {
789  tuneState->kp[2] = -1;
790  tuneState->ki[2] = -1;
791  tuneState->kd[2] = -1;
792  }
793 
794  // handle non-convergence case. Takes precedence over all else.
795  if (!converged) {
796  lblWarnings->setText(tr("<span style=\"color: red\">Error:</span> Tune didn't converge! "
797  "Check noise and damping sliders."));
798  }
799 
800  emit completeChanged();
801 
802  setText(rollRateKp, tuneState->kp[0], 5);
803  setText(rollRateKi, tuneState->ki[0], 5);
804  setText(rollRateKd, tuneState->kd[0], 6);
805 
806  setText(pitchRateKp, tuneState->kp[1], 5);
807  setText(pitchRateKi, tuneState->ki[1], 5);
808  setText(pitchRateKd, tuneState->kd[1], 6);
809 
810  setText(yawRateKp, tuneState->kp[2], 5);
811  setText(yawRateKi, tuneState->ki[2], 5);
812  setText(yawRateKd, tuneState->kd[2], 6);
813 
814  setText(lblOuterKp, tuneState->outerKp, 2);
815  setText(lblOuterKi, tuneState->outerKi, 2);
816  setText(derivativeCutoff, tuneState->derivativeCutoff, 1);
817  setText(this->wn, tuneState->naturalFreq, 1);
818  setText(lblDamp, damp, 2);
819  lblNoise->setText(QString::number(ghf * 100, 'f', 1) + " %");
820 }
821 
823  : QWizardPage(parent)
824 {
825  setupUi(this);
826 
827 #ifdef Q_OS_MAC
828  lblCongrats->setText(lblCongrats->text().replace(tr("\"Finish\""), tr("\"Done\"")));
829 #endif
830 }
831 
833  bool autoOpened, AutotunedValues *autoValues)
834  : QWizardPage(parent)
835 {
836  tuneState = autoValues;
837  this->autoOpened = autoOpened;
838  dataValid = false;
839  setupUi(this);
840 }
841 
842 QString AutotuneBeginningPage::tuneValid(bool *okToContinue) const
843 {
844  if ((!tuneState->valid) || (tuneState->tau[0] == 0)) {
845  // Invalid / no tune.
846 
847  *okToContinue = false;
848  return tr("<span style=\"color: red\">It doesn't appear an autotune was successfully "
849  "completed and saved; we are unable to continue.</span>");
850  }
851 
852  QString retVal;
853 
854  *okToContinue = true;
855 
856  if (tuneState->tau[0] < 0.005) {
857  // Too low of tau to be plausible. (5ms)
858  retVal.append(tr("Error: Autotune did not measure valid values for this craft (low tau). "
859  "Consider slightly lowering the starting roll/pitch rate P values or "
860  "slightly decreasing Motor Input/Output Curve Fit on the output pane."));
861  retVal.append("<br/>");
862  *okToContinue = false;
863  } else if (tuneState->tau[0] < 0.0074) {
864  // Probably too low to be real-- 7.4ms-- warn!
865  retVal.append(tr("Warning: The tau value measured for this craft is very low."));
866  retVal.append("<br/>");
867  } else if (tuneState->tau[0] > .240) {
868  // Too high of a tau to be plausible / accurate (240ms)-- warn!
869  retVal.append(tr("Warning: The tau value measured for this craft is very high."));
870  retVal.append("<br/>");
871  }
872 
873  // Lowest valid gains seen have been 7.9, with most values in the range 9..11
874  if (tuneState->beta[0] < 7.25) {
875  retVal.append(
876  tr("Error: Autotune did not measure valid values for this craft (low roll gain)."));
877  retVal.append("<br/>");
878  *okToContinue = false;
879  }
880 
881  if (tuneState->beta[0] < 7.25) {
882  retVal.append(
883  tr("Error: Autotune did not measure valid values for this craft (low pitch gain)."));
884  retVal.append("<br/>");
885  *okToContinue = false;
886  }
887 
888  retVal.replace(QRegExp("(\\w+:)"), "<span style=\"color: red\"><b>\\1</b></span>");
889 
890  if (*okToContinue) {
891  if (retVal.isEmpty()) {
892  retVal.append(tr("Everything checks out, and we're ready to proceed!"));
893  } else {
894  retVal.append(
895  tr("<br/>These warnings may result in an invalid tune. Proceed with caution."));
896  }
897  } else {
898  retVal.append(
899  tr("<br/>Unable to complete the autotune process because of the above error(s)."));
900  }
901 
902  return retVal;
903 }
904 
905 void AutotuneBeginningPage::doDownloadAndProcess()
906 {
907  if (!tuneState->valid) {
908  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
909 
910  TelemetryManager *telMngr = pm->getObject<TelemetryManager>();
911 
912  if (tuneState->data.isEmpty()) {
913  QByteArray *data = telMngr->downloadFile(4, 32768, [&](quint32 progress) {
914  int fraction = (100 * progress) / 32768.0;
915 
916  if (fraction > 80) {
917  fraction = 80;
918  }
919 
920  progressBar->setValue(fraction);
921  });
922 
923  if (data) {
924  tuneState->data = *data;
925 
926  delete data;
927  }
928  }
929  }
930 
931  progressBar->setValue(90);
932 
933  processAutotuneData();
934 
935  progressBar->setValue(100);
936 
937  QString initialWarnings = tuneValid(&dataValid);
938 
939  status->setText(initialWarnings);
940 
941  emit completeChanged();
942 }
943 
945 {
946  setTitle(tr("Preparing autotune..."));
947 
948  if (autoOpened) {
949  subtitle->setText(tr("It looks like you have run a new autotune since you last connected to the flight "
950  "controller. This wizard will assist you in applying a set of autotune "
951  "measurements to your aircraft."));
952  } else {
953  subtitle->setText(tr("This wizard will assist you in applying a set of autotune "
954  "measurements to your aircraft."));
955  }
956 
957  progressBar->setRange(0, 100);
958  progressBar->setValue(0);
959 
960  QMetaObject::invokeMethod(this, "doDownloadAndProcess", Qt::QueuedConnection);
961 }
962 
964 {
965  return tuneState->valid && dataValid;
966 }
967 
968 /* Run a Butterworth biquad filter on a circular buffer. First go around once
969  * to "prime" the filter, then actually filter in place. This is derived from
970  * @glowtape's excellent flight implementation
971  */
972 void AutotuneBeginningPage::biquadFilter(float cutoff, int pts,
973  QVector<float> &data)
974 {
975  float f = 1.0f / tan(M_PI * cutoff);
976  float q = 1.4142f;
977 
978  float y2 = 0, y1 = 0, x2 = 0, x1 = 0;
979 
980  float b0 = 1.0f / (1.0f + q * f + f * f);
981  float a1 = 2.0f * (f * f - 1.0f) * b0;
982  float a2 = -(1.0f - q * f + f * f) * b0;
983 
984  for (int i = 0; i < pts; i++) {
985  float y = b0 * (data[i] + 2.0f * x1 + x2) + a1 * y1 + a2 * y2;
986 
987  y2 = y1;
988  y1 = y;
989 
990  x2 = x1;
991  x1 = data[i];
992  }
993 
994  for (int i = 0; i < pts; i++) {
995  float y = b0 * (data[i] + 2.0f * x1 + x2) + a1 * y1 + a2 * y2;
996 
997  y2 = y1;
998  y1 = y;
999 
1000  x2 = x1;
1001  x1 = data[i];
1002 
1003  data[i] = y;
1004  }
1005 }
1006 
1007 /* Returns number of samples of delay between series */
1008 float AutotuneBeginningPage::getSampleDelay(int pts,
1009  const QVector<float> &delayed, const QVector<float> &orig,
1010  int seriesCutoff)
1011 {
1012  ffft::FFTReal<float> fft(pts);
1013 
1014  /* Convert to frequency domain */
1015  QVector<float> delayed_fft(pts);
1016  fft.do_fft(delayed_fft.data(), delayed.data());
1017 
1018  QVector<float> orig_fft(pts);
1019  fft.do_fft(orig_fft.data(), orig.data());
1020 
1021  /* Now perform a correlation by multiplying -orig_fft* by delayed_fft.
1022  * The types are all floats here, so we need to do the heavy lifting
1023  * ourselves. gfft = x+yi, dfft = u+vi, dfft* = u-vi,
1024  * -dfft* = -u + vi
1025  *
1026  * -dfft* x gfft = (-ux - vy) + (vx - uy)i
1027  */
1028 
1029  QVector<float> product(pts);
1030 
1031  int fpts = pts / 2;
1032 
1033  // Memory layout here is annoyin'. All reals, then all imaginaries
1034  for (int i = 0; i < fpts; i++) {
1035  float x = delayed_fft[i];
1036  float y = delayed_fft[i + fpts];
1037  float u = orig_fft[i];
1038  float v = orig_fft[i + fpts];
1039 
1040  product[i] = -(u * x) - (v * y);
1041  product[i + fpts] = (v * x) - (u * y);
1042  }
1043 
1044  /* Inverse FFT converts this to the time domain */
1045  QVector<float> prod_time(pts);
1046 
1047  fft.do_ifft(product.data(), prod_time.data());
1048 
1049  /* And we take magnitudes to find tau. */
1050  QVector<float> mags(fpts);
1051 
1052  int max_idx = 0;
1053  float max_val = 0;
1054 
1055  for (int i = 0; i < fpts / seriesCutoff; i++) {
1056  float real = prod_time[i];
1057  float imag = prod_time[i + fpts];
1058  mags[i] = sqrt(real * real + imag * imag);
1059 
1060  if (mags[i] > max_val) {
1061  max_val = mags[i];
1062  max_idx = i;
1063  }
1064 
1065  // cout << i << "\t" << mags[i] << "\n";
1066  }
1067 
1068  // cout << "Maximum mags[" << max_idx << "] = " << max_val << "\n";
1069 
1070  // TODO / optional: interpolate/find a better peak around max_idx.
1071 
1072  return max_idx;
1073 }
1074 
1075 bool AutotuneBeginningPage::processAutotuneData()
1076 {
1077  QByteArray &loadedFile = tuneState->data;
1078 
1079  const at_flash *flash_data = reinterpret_cast<const at_flash *>((const void *)loadedFile);
1080 
1081  unsigned int size = loadedFile.size();
1082 
1083  /* Determine whether we have a sane amount of data, etc. */
1084  if ((size < sizeof(at_flash)) || (flash_data->hdr.magic != ATFLASH_MAGIC)) {
1085  return false;
1086  }
1087 
1088  unsigned int size_expected = sizeof(at_flash)
1089  + sizeof(at_measurement) * flash_data->hdr.wiggle_points + flash_data->hdr.aux_data_len;
1090 
1091  if (size < size_expected) {
1092  return false;
1093  }
1094 
1095  float duration = (float)flash_data->hdr.wiggle_points / flash_data->hdr.sample_rate;
1096 
1097  if ((duration < 0.25f) || (duration > 5.0f)) {
1098  return false;
1099  }
1100 
1101  int pts = flash_data->hdr.wiggle_points;
1102 
1103  for (int axis = 0; axis < 3; axis++) {
1104  QVector<float> gyro_deriv(pts);
1105  QVector<float> actu_desired(pts);
1106 
1107  for (int i = 0; i < pts; i++) {
1108  actu_desired[i] = flash_data->data[i].u[axis];
1109  }
1110 
1111  // Differentiate the gyro data
1112  for (int i = 1; i < pts; i++) {
1113  gyro_deriv[i] = flash_data->data[i].y[axis] - flash_data->data[i - 1].y[axis];
1114  }
1115 
1116  gyro_deriv[0] = flash_data->data[0].y[axis] - flash_data->data[pts - 1].y[axis];
1117 
1118  float sample_tau = getSampleDelay(pts, gyro_deriv, actu_desired,
1119  (axis == 2) ? 8 : 4);
1120 
1121  float tau = sample_tau / flash_data->hdr.sample_rate;
1122 
1123  biquadFilter(1 / (sample_tau * M_PI * 1.414), pts, actu_desired);
1124 
1125  QVector<float> gyro_sorted = gyro_deriv;
1126  QVector<float> actu_sorted = actu_desired;
1127 
1128  std::sort(gyro_sorted.begin(), gyro_sorted.end());
1129  std::sort(actu_sorted.begin(), actu_sorted.end());
1130 
1131  int low_idx = pts * 0.05 + 0.5;
1132  int high_idx = pts - 1 - low_idx;
1133 
1134  float gyro_span = gyro_sorted[high_idx] - gyro_sorted[low_idx];
1135  float actu_span = actu_sorted[high_idx] - actu_sorted[low_idx];
1136 
1137  float gain = gyro_span / actu_span * flash_data->hdr.sample_rate;
1138 
1139  float avg = std::accumulate(gyro_deriv.begin(), gyro_deriv.end(), 0.0f) / pts;
1140 
1141  for (int i = 0; i < pts; i++) {
1142  gyro_deriv[i] = gyro_deriv[i] - avg;
1143  }
1144 
1145  float avg_act = std::accumulate(actu_desired.begin(), actu_desired.end(), 0.0f) / pts;
1146 
1147  for (int i = 0; i < pts; i++) {
1148  actu_desired[i] = (actu_desired[i] - avg_act) * (gain / flash_data->hdr.sample_rate);
1149  }
1150 
1151  tuneState->model[axis] = new QLineSeries(this);
1152  tuneState->actual[axis] = new QLineSeries(this);
1153 
1154  for (int i = 0; i < pts; i++) {
1155  int tm = (i * 1000) / flash_data->hdr.sample_rate;
1156 
1157  tuneState->model[axis]->append(tm, actu_desired[i]);
1158  tuneState->actual[axis]->append(tm, gyro_deriv[i]);
1159  }
1160 
1161  float bias = avg - avg_act * (gain / flash_data->hdr.sample_rate);
1162 
1163  double noise = 0;
1164 
1165  for (int i = 0; i < pts; i++) {
1166  noise += (actu_desired[i] - gyro_deriv[i]) * (actu_desired[i] - gyro_deriv[i]);
1167  }
1168 
1169  noise = sqrt(noise / pts);
1170 
1171  qDebug() << "Series " << axis << ": tau=" << tau << "; gain=" << gain << " (" << log(gain)
1172  << "); bias=" << bias << " noise=" << noise << "";
1173 
1174  tuneState->tau[axis] = tau;
1175  tuneState->beta[axis] = log(gain);
1176  tuneState->bias[axis] = bias;
1177  tuneState->noise[axis] = noise;
1178  }
1179 
1180  tuneState->valid = true;
1181 
1182  return true;
1183 }
void autoPilotConnected()
AutotuneSlidersPage(QWidget *parent, AutotunedValues *autoValues)
Core plugin system that manages the plugins, their life cycle and their registered objects...
Definition: pluginmanager.h:53
QVariant getValue(int index=0) const
for i
Definition: OPPlots.m:140
QLineSeries * model[3]
QJsonObject getJsonRepresentation()
Definition: uavobject.cpp:204
DataFields data
end a
Definition: OPPlots.m:98
QStringList getOptions() const
Parse log file
virtual QString getHwUAVO()=0
void autoPilotDisconnected()
QByteArray * downloadFile(quint32 fileId, quint32 maxSize, std::function< void(quint32)>progressCb)
bool isAutopilotConnected()
ConfigTaskWidget::isAutopilotConnected Checks if the autopilot is connected.
void saveObjectToSD(UAVObject *obj)
AutotuneFinalPage(QWidget *parent)
QString getName()
Definition: uavobject.cpp:131
ConfigAutotuneWidget(ConfigGadgetWidget *parent=nullptr)
x
Definition: OPPlots.m:100
virtual QString shortName()=0
bool getBoardDescriptionStruct(deviceDescriptorStruct &device)
AutotuneBeginningPage(QWidget *parent, bool autoOpened, AutotunedValues *autoValues)
UAVObject * getObject(const QString &name, quint32 instId=0)
Core::IBoardType * getBoardType()
Get the IBoardType corresponding to the connected board.
UAVObjectManager * getObjectManager()
ConfigTaskWidget::getObjectManager Utility function to get a pointer to the object manager...
AutotuneMeasuredPropertiesPage(QWidget *parent, AutotunedValues *autoValues)
QLineSeries * actual[3]
y
Definition: OPPlots.m:101