dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
uploadergadgetwidget.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  * Additional note on redistribution: The copyright and license notices above
28  * must be maintained in each individual source file that is a derivative work
29  * of this source file; otherwise redistribution is prohibited.
30  */
31 
32 #include <QFileDialog>
33 #include <QMessageBox>
34 #include <QDesktopServices>
35 #include <QHttpPart>
36 #include <QHttpMultiPart>
37 #include <QNetworkAccessManager>
38 #include <QNetworkRequest>
39 #include <QNetworkReply>
40 #include <QUrl>
41 
42 #include "uploadergadgetwidget.h"
43 #include "firmwareiapobj.h"
45 #include <coreplugin/icore.h>
46 #include <coreplugin/modemanager.h>
48 #include "rawhid/rawhidplugin.h"
49 #include "ui_uploader.h"
51 
52 using namespace uploader;
53 
59  : QWidget(parent)
60  , telemetryConnected(false)
61  , iapUpdated(false)
62 {
63  m_widget = new Ui_UploaderWidget();
64  m_widget->setupUi(this);
65  setUploaderStatus(uploader::DISCONNECTED);
66  m_widget->partitionBrowserTW->setSelectionMode(QAbstractItemView::SingleSelection);
67  m_widget->partitionBrowserTW->setSelectionBehavior(QAbstractItemView::SelectRows);
68 
69  // these widgets will be set to retain their position in the layout even when hidden
70  QList<QWidget *> hideable = { m_widget->progressBar };
71  foreach (QWidget *widget, hideable) {
72  QSizePolicy sp = widget->sizePolicy();
73  sp.setRetainSizeWhenHidden(true);
74  widget->setSizePolicy(sp);
75  }
76 
77  // Setup partition manager actions
78  QAction *action = new QAction("Save to file", this);
79  connect(action, &QAction::triggered, this, &UploaderGadgetWidget::onPartitionSave);
80  m_widget->partitionBrowserTW->addAction(action);
81  action = new QAction("Flash from file", this);
82  connect(action, &QAction::triggered, this, &UploaderGadgetWidget::onPartitionFlash);
83  m_widget->partitionBrowserTW->addAction(action);
84  action = new QAction("Erase", this);
85  connect(action, &QAction::triggered, this, &UploaderGadgetWidget::onPartitionErase);
86  m_widget->partitionBrowserTW->addAction(action);
87  m_widget->partitionBrowserTW->setContextMenuPolicy(Qt::ActionsContextMenu);
88 
89  // Clear widgets to defaults
90  FirmwareOnDeviceClear(true);
91  FirmwareLoadedClear(true);
92 
93  PartitionBrowserClear();
94  DeviceInformationClear();
95 
96  pm = ExtensionSystem::PluginManager::instance();
97  telMngr = pm->getObject<TelemetryManager>();
98  utilMngr = pm->getObject<UAVObjectUtilManager>();
99  importMngr = pm->getObject<UAVSettingsImportExportManager>();
100 
101  netMngr = new QNetworkAccessManager(this);
102 
103  UAVObjectManager *obm = pm->getObject<UAVObjectManager>();
104 
105  /* These (and firmwareIAP) are queued connections. We may do things
106  * that ultimately stop the telemetry manager, so we don't want to be
107  * invoked from its components and return into it.
108  */
109  connect(telMngr, &TelemetryManager::connected, this, &UploaderGadgetWidget::onAutopilotConnect,
110  Qt::QueuedConnection);
111  connect(telMngr, &TelemetryManager::disconnected, this,
112  &UploaderGadgetWidget::onAutopilotDisconnect, Qt::QueuedConnection);
113  firmwareIap = FirmwareIAPObj::GetInstance(obm);
114 
115  connect(firmwareIap, &UAVObject::objectUpdated, this, &UploaderGadgetWidget::onIAPUpdated,
116  Qt::QueuedConnection);
117 
118  // Connect button signals to slots
119  connect(m_widget->openButton, &QAbstractButton::clicked, this,
120  &UploaderGadgetWidget::onLoadFirmwareButtonClick);
121  connect(m_widget->rescueButton, &QAbstractButton::clicked, this,
122  &UploaderGadgetWidget::onRescueButtonClick);
123  connect(m_widget->flashButton, &QAbstractButton::clicked, this,
124  &UploaderGadgetWidget::onFlashButtonClick);
125  connect(m_widget->bootButton, &QAbstractButton::clicked, this,
126  &UploaderGadgetWidget::onBootButtonClick);
127  connect(m_widget->safeBootButton, &QAbstractButton::clicked, this,
128  &UploaderGadgetWidget::onBootButtonClick);
129  connect(m_widget->exportConfigButton, &QAbstractButton::clicked, this,
130  &UploaderGadgetWidget::onExportButtonClick);
131 
132  connect(m_widget->pbHelp, &QAbstractButton::clicked, this, &UploaderGadgetWidget::openHelp);
134 
135  // Setup usb discovery signals for boards in bl state
136  usbFilterBL = new USBSignalFilter(brdMgr->getKnownVendorIDs(), -1, -1, USBMonitor::Bootloader);
137  usbFilterUP = new USBSignalFilter(brdMgr->getKnownVendorIDs(), -1, -1, USBMonitor::Upgrader);
138 
139  connect(usbFilterBL, &USBSignalFilter::deviceRemoved, this,
140  &UploaderGadgetWidget::onBootloaderRemoved, Qt::QueuedConnection);
141 
142  connect(usbFilterBL, &USBSignalFilter::deviceDiscovered, this,
143  &UploaderGadgetWidget::onBootloaderDetected, Qt::QueuedConnection);
144 
145  connect(usbFilterUP, &USBSignalFilter::deviceRemoved, this,
146  &UploaderGadgetWidget::onBootloaderRemoved, Qt::QueuedConnection);
147 
148  connect(usbFilterUP, &USBSignalFilter::deviceDiscovered, this,
149  &UploaderGadgetWidget::onBootloaderDetected, Qt::QueuedConnection);
150 
151  connect(&dfu, &tl_dfu::DFUObject::operationProgress, this,
152  &UploaderGadgetWidget::onStatusUpdate);
153 
155 
156  /* Enter the loader if it's available */
157  setUploaderStatus(uploader::ENTERING_LOADER);
158  onBootloaderDetected();
159 
160  /* Else begin our normal actions waitin' for a board */
161  if (getUploaderStatus() != uploader::BL_SITTING) {
162  setUploaderStatus(uploader::DISCONNECTED);
163  setStatusInfo(tr("GCS ready for flight board connection..."), uploader::STATUSICON_OK);
164  }
165 }
166 
171 {
172  // TODO remove this when addObject removed from factory
173  ExtensionSystem::PluginManager::instance()->removeObject(this);
174 }
175 
181 void UploaderGadgetWidget::FirmwareOnDeviceClear(bool clear)
182 {
183  QRegExp rx("*OD_lbl");
184  rx.setPatternSyntax(QRegExp::Wildcard);
185  foreach (QLabel *l, findChildren<QLabel *>(rx)) {
186  if (clear)
187  l->clear();
188  l->setVisible(!clear);
189  }
190  m_widget->firmwareOnDevicePic->setVisible(!clear);
191  m_widget->noInfoOnDevice->setVisible(clear);
192  m_widget->onDeviceGB->setEnabled(!clear);
193 }
194 
200 void UploaderGadgetWidget::FirmwareLoadedClear(bool clear)
201 {
202  QRegExp rx("*LD_lbl");
203  rx.setPatternSyntax(QRegExp::Wildcard);
204  foreach (QLabel *l, findChildren<QLabel *>(rx)) {
205  if (clear)
206  l->clear();
207  l->setVisible(!clear);
208  }
209  m_widget->firmwareLoadedPic->setVisible(!clear);
210  m_widget->noFirmwareLoaded->setVisible(clear);
211  m_widget->loadedGB->setEnabled(!clear);
212  if (clear)
213  m_widget->userDefined_LD_lbl->clear();
214  m_widget->userDefined_LD_lbl->setVisible(!clear);
215 }
216 
220 void UploaderGadgetWidget::PartitionBrowserClear()
221 {
222  m_widget->partitionBrowserTW->clearContents();
223  m_widget->partitionBrowserGB->setEnabled(false);
224 }
225 
226 void UploaderGadgetWidget::DeviceInformationClear()
227 {
228  m_widget->deviceInformationMainLayout->setVisible(false);
229  m_widget->deviceInformationNoInfo->setVisible(true);
230  m_widget->boardName_lbl->setVisible(false);
231  currentBoard.board = NULL;
232 }
233 
238 void UploaderGadgetWidget::DeviceInformationUpdate(deviceInfo board)
239 {
240  if (!board.board)
241  return;
242  currentBoard = board;
243  m_widget->boardName_lbl->setText(board.board->boardDescription());
244  m_widget->devName_lbl->setText(
245  board.board->getBoardNameFromID(board.board->getBoardType() << 8));
246  m_widget->hwRev_lbl->setText(board.hw_revision);
247  m_widget->blVer_lbl->setText(board.bl_version);
248  bool bl_version_ok;
249  int bl_version = board.bl_version.toInt(&bl_version_ok, 16);
250  m_widget->blVer_lbl->setStyleSheet(
251  bl_version_ok && bl_version < board.board->minBootLoaderVersion() ? "color: red;" : "");
252  m_widget->maxCode_lbl->setText(board.max_code_size);
253  m_widget->deviceInformationMainLayout->setVisible(true);
254  m_widget->deviceInformationNoInfo->setVisible(false);
255  m_widget->boardPic->setPixmap(board.board->getBoardPicture());
256  FirmwareLoadedUpdate(loadedFile);
257 }
258 
264 void UploaderGadgetWidget::FirmwareOnDeviceUpdate(deviceDescriptorStruct firmware, QString crc)
265 {
266  m_widget->builtForOD_lbl->setText(Core::IBoardType::getBoardNameFromID(firmware.boardID()));
267  m_widget->crcOD_lbl->setText(crc);
268  m_widget->gitHashOD_lbl->setText(firmware.gitHash);
269  m_widget->ancestorHashOD_lbl->setText(firmware.nextAncestor);
270  m_widget->firmwareDateOD_lbl->setText(firmware.gitDate);
271  m_widget->firmwareTagOD_lbl->setText(firmware.gitTag);
272  m_widget->uavosSHA_OD_lbl->setText(firmware.uavoHash.toHex().toUpper());
273  m_widget->userDefined_OD_lbl->setText(firmware.userDefined);
274 
275  if (firmware.certified) {
276  QPixmap pix = QPixmap(QString(":uploader/images/application-certificate.svg"));
277  m_widget->firmwareOnDevicePic->setPixmap(pix);
278  m_widget->firmwareOnDevicePic->setToolTip(tr("Tagged officially released firmware build"));
279  } else {
280  QPixmap pix = QPixmap(QString(":uploader/images/warning.svg"));
281  m_widget->firmwareOnDevicePic->setPixmap(pix);
282  m_widget->firmwareOnDevicePic->setToolTip(tr("Custom Firmware Build"));
283  }
284  FirmwareOnDeviceClear(false);
285 }
286 
291 void UploaderGadgetWidget::FirmwareLoadedUpdate(QByteArray firmwareArray)
292 {
293  if (firmwareArray.isEmpty())
294  return;
295  deviceDescriptorStruct firmware;
296  if (!utilMngr->descriptionToStructure(firmwareArray.right(100), firmware)) {
297  setStatusInfo(tr("Error parsing file metadata"), uploader::STATUSICON_FAIL);
298  QPixmap pix = QPixmap(QString(":uploader/images/error.svg"));
299  m_widget->firmwareLoadedPic->setPixmap(pix);
300  m_widget->firmwareLoadedPic->setToolTip(
301  tr("Error unrecognized file format, proceed at your own risk"));
302  m_widget->gitHashLD_lbl->setText(
303  tr("Error unrecognized file format, proceed at your own risk"));
304  FirmwareLoadedClear(false);
305  m_widget->userDefined_LD_lbl->setVisible(false);
306  return;
307  }
308  setStatusInfo(tr("File loaded successfully"), uploader::STATUSICON_INFO);
309  m_widget->builtForLD_lbl->setText(Core::IBoardType::getBoardNameFromID(firmware.boardID()));
310  bool ok;
311  currentBoard.max_code_size.toLong(&ok);
312  if (!ok)
313  m_widget->crcLD_lbl->setText("Not Available");
314  else if (firmwareArray.length() > currentBoard.max_code_size.toLong()) {
315  m_widget->crcLD_lbl->setText("Not Available");
316  } else {
317  quint32 crc = dfu.CRCFromQBArray(firmwareArray, currentBoard.max_code_size.toLong());
318  m_widget->crcLD_lbl->setText(QString::number(crc));
319  }
320  m_widget->gitHashLD_lbl->setText(firmware.gitHash);
321  m_widget->ancestorHashLD_lbl->setText(firmware.nextAncestor);
322  m_widget->firmwareDateLD_lbl->setText(firmware.gitDate);
323  m_widget->firmwareTagLD_lbl->setText(firmware.gitTag);
324  m_widget->uavosSHA_LD_lbl->setText(firmware.uavoHash.toHex().toUpper());
325  m_widget->userDefined_LD_lbl->setText(firmware.userDefined);
326  if (currentBoard.board && (currentBoard.board->getBoardType() != firmware.boardType)) {
327  QPixmap pix = QPixmap(QString(":uploader/images/error.svg"));
328  m_widget->firmwareLoadedPic->setPixmap(pix);
329  m_widget->firmwareLoadedPic->setToolTip(
330  tr("Error firmware loaded firmware doesn't match connected board"));
331  } else if (firmware.certified) {
332  QPixmap pix = QPixmap(QString(":uploader/images/application-certificate.svg"));
333  m_widget->firmwareLoadedPic->setPixmap(pix);
334  m_widget->firmwareLoadedPic->setToolTip(tr("Tagged officially released firmware build"));
335  } else {
336  QPixmap pix = QPixmap(QString(":uploader/images/warning.svg"));
337  m_widget->firmwareLoadedPic->setPixmap(pix);
338  m_widget->firmwareLoadedPic->setToolTip(tr("Custom Firmware Build"));
339  }
340  FirmwareLoadedClear(false);
341 }
342 
346 void UploaderGadgetWidget::onAutopilotConnect()
347 {
348  telemetryConnected = true;
349  CheckAutopilotReady();
350 }
351 
355 void UploaderGadgetWidget::onAutopilotDisconnect()
356 {
357  FirmwareOnDeviceClear(true);
358  DeviceInformationClear();
359  PartitionBrowserClear();
360  telemetryConnected = false;
361  iapUpdated = false;
362  if (getUploaderStatus() == uploader::ENTERING_LOADER)
363  return;
364  if (getUploaderStatus() == uploader::UPGRADING_CATCHLOADER)
365  return;
366  setUploaderStatus(uploader::DISCONNECTED);
367  setStatusInfo(tr("Telemetry disconnected"), uploader::STATUSICON_INFO);
368 }
369 
374 void UploaderGadgetWidget::onAutopilotReady()
375 {
376  setUploaderStatus(uploader::CONNECTED_TO_TELEMETRY);
377  setStatusInfo(tr("Telemetry connected"), uploader::STATUSICON_INFO);
378  deviceInfo board;
379  board.bl_version = tr("Not available");
380  board.board = utilMngr->getBoardType();
381  board.cpu_serial = utilMngr->getBoardCPUSerial().toHex();
382  board.hw_revision = QString::number(utilMngr->getBoardRevision());
383  board.max_code_size = tr("Not available");
384  DeviceInformationUpdate(board);
385  deviceDescriptorStruct device;
386  if (utilMngr->getBoardDescriptionStruct(device)) {
387  FirmwareOnDeviceUpdate(device, QString::number(utilMngr->getFirmwareCRC()));
388  if (FirmwareCheckForUpdate(device)) {
389  onRescueButtonClick();
390 
392  // Set the status, so when we enter the loader we cue the
393  // upgrade state machine/actions.
394  setUploaderStatus(uploader::UPGRADING);
395  }
396  }
397  emit newBoardSeen(board, device);
398 }
399 
403 void UploaderGadgetWidget::onIAPUpdated()
404 {
405  if ((getUploaderStatus() == uploader::ENTERING_LOADER)
406  || (getUploaderStatus() == uploader::UPGRADING))
407  return;
408  iapUpdated = true;
409  CheckAutopilotReady();
410 }
411 
416 void UploaderGadgetWidget::onLoadFirmwareButtonClick()
417 {
418  QString board;
419  if (currentBoard.board)
420  board = currentBoard.board->shortName().toLower();
421  QString filename = LoadFirmwareFileDialog(board);
422  if (filename.isEmpty()) {
423  setStatusInfo(tr("Open cancelled"), uploader::STATUSICON_FAIL);
424  return;
425  }
426  QFile file(filename);
427  if (!file.open(QIODevice::ReadOnly)) {
428  setStatusInfo(tr("Could not open file"), uploader::STATUSICON_FAIL);
429  return;
430  }
431  loadedFile = file.readAll();
432 
433  FirmwareLoadedClear(true);
434  FirmwareLoadedUpdate(loadedFile);
435  setUploaderStatus(getUploaderStatus());
436 }
437 
438 // Would be nice to do the work here required to constify firmwareImage
439 bool UploaderGadgetWidget::flashFirmware(QByteArray &firmwareImage)
440 {
441  QEventLoop loop;
442 
443  QTimer timeout;
444 
445  connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
446 
447  timeout.setSingleShot(true);
448  timeout.start(120000); // Very long out of caution. Current Brain
449  // FW takes 36-37 seconds on some machines to
450  // flash.
451 
452  tl_dfu::Status operationSuccess = DFUidle;
453 
454  setStatusInfo("", uploader::STATUSICON_RUNNING);
455  setUploaderStatus(uploader::BL_BUSY);
456  onStatusUpdate(QString("Starting upload..."), 0); // set progress bar to 0 while erasing
457  dfu.UploadPartitionThreaded(firmwareImage, DFU_PARTITION_FW,
458  currentBoard.max_code_size.toInt());
459 
460  /* disconnects when loop comes out of scope */
461  connect(&dfu, &DFUObject::uploadFinished, &loop, [&](tl_dfu::Status status) {
462  operationSuccess = status;
463  loop.exit();
464  });
465 
466  loop.exec(); /* Wait for timeout or download complete */
467 
468  timeout.stop();
469 
470  if (operationSuccess != Last_operation_Success) {
471  setUploaderStatus(uploader::BL_SITTING);
472  setStatusInfo(tr("Firmware upload failed"), uploader::STATUSICON_FAIL);
473 
474  return false;
475  }
476 
477  setStatusInfo(tr("Firmware upload success"), uploader::STATUSICON_OK);
478 
479  if ((!firmwareImage.right(100).startsWith("TlFw"))
480  && (!firmwareImage.right(100).startsWith("OpFw"))) {
481  setUploaderStatus(uploader::BL_SITTING);
482 
483  return true;
484  }
485 
486  tempArray.clear();
487  tempArray.append(firmwareImage.right(100));
488  tempArray.chop(12);
489  QString user(" ");
490  user = user.replace(0, m_widget->userDefined_LD_lbl->text().length(),
491  m_widget->userDefined_LD_lbl->text());
492  tempArray.append(user.toLatin1());
493  setStatusInfo(tr("Starting firmware metadata upload"), uploader::STATUSICON_INFO);
494  dfu.UploadPartitionThreaded(tempArray, DFU_PARTITION_DESC, 100);
495 
496  operationSuccess = DFUidle;
497 
498  timeout.start(10000);
499  loop.exec();
500  timeout.stop();
501 
502  if (operationSuccess != Last_operation_Success) {
503  setStatusInfo(tr("Firmware metadata upload failed"), uploader::STATUSICON_FAIL);
504 
505  setUploaderStatus(uploader::BL_SITTING);
506 
507  return false;
508  }
509 
510  setUploaderStatus(uploader::BL_SITTING);
511  setStatusInfo(tr("Firmware and firmware metadata upload success"), uploader::STATUSICON_OK);
512 
513  // uploaded succeeded so we can assume the loaded file is on the board
514  deviceDescriptorStruct descStructure;
515  if (UAVObjectUtilManager::descriptionToStructure(tempArray, descStructure)) {
516  quint32 crc = dfu.CRCFromQBArray(firmwareImage, currentBoard.max_code_size.toLong());
517  FirmwareOnDeviceUpdate(descStructure, QString::number(crc));
518  }
519  setUploaderStatus(uploader::BL_SITTING);
520 
521  return true;
522 }
523 
528 void UploaderGadgetWidget::onFlashButtonClick()
529 {
530  if (flashFirmware(loadedFile)) {
531  this->activateWindow();
532  m_widget->bootButton->setFocus();
533  }
534 }
535 
536 void UploaderGadgetWidget::haltOrReset(bool halting)
537 {
538  if (!firmwareIap->getIsPresentOnHardware())
539  return;
540 
541  if (halting) {
542  setUploaderStatus(uploader::ENTERING_LOADER);
543  } else {
544  setUploaderStatus(uploader::DISCONNECTED);
545  }
546 
547  QEventLoop loop;
548  QTimer timeout;
549  timeout.setSingleShot(true);
550  firmwareIap->setBoardRevision(0);
551  firmwareIap->setBoardType(0);
552  connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
553  connect(firmwareIap, QOverload<UAVObject *, bool>::of(&FirmwareIAPObj::transactionCompleted),
554  &loop, &QEventLoop::quit);
555  int magicValue = 1122;
556  int magicStep = 1111;
557  for (int i = 0; i < 3; ++i) {
558  // Firmware IAP module specifies that the timing between iap commands must be
559  // between 500 and 5000ms
560  timeout.start(600);
561  loop.exec();
562  firmwareIap->setCommand(magicValue);
563  magicValue += magicStep;
564  if ((!halting) && (magicValue == 3344))
565  magicValue = 4455;
566 
567  setStatusInfo(QString(tr("Sending IAP Step %0").arg(i + 1)), uploader::STATUSICON_INFO);
568  firmwareIap->updated();
569  timeout.start(1000);
570  loop.exec();
571  if (!timeout.isActive()) {
572  setStatusInfo(QString(tr("Sending IAP Step %0 failed").arg(i + 1)),
574  return;
575  }
576  timeout.stop();
577  }
578 
579  Core::IConnection *conn = conMngr->getCurrentDevice().connection;
580  if (conn && conn->shortName() == "USB") {
581  conMngr->disconnectDevice();
582  timeout.start(200);
583  loop.exec();
584  timeout.stop();
585  conMngr->suspendPolling();
586  startRescue();
587  } else {
588  setUploaderStatus(uploader::DISCONNECTED);
589  conMngr->disconnectDevice();
590  }
591 }
592 
597 void UploaderGadgetWidget::onRescueButtonClick()
598 {
599  if (telMngr->isConnected()) {
600  // This means "halt" to enter the loader.
601  haltOrReset(true);
602 
603  return;
604  }
605 
606  // Otherwise, this means "begin rescue".
607  conMngr->suspendPolling();
608  setUploaderStatus(uploader::ENTERING_LOADER);
609  setStatusInfo(tr("Please connect the board with USB with no external power applied"),
611  startRescue();
612 }
613 
614 bool UploaderGadgetWidget::downloadSettings()
615 {
616  QEventLoop loop;
617 
618  QTimer timeout;
619  timeout.setSingleShot(true);
620 
621  bool operationSuccess = false;
622 
623  connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
624 
625  /* disconnects when loop comes out of scope */
626  connect(&dfu, &DFUObject::downloadFinished, &loop, [&](bool status) {
627  qDebug() << "downloadFinished in dSettings: " << status;
628  operationSuccess = status;
629  loop.exit();
630  });
631 
632  timeout.start(100000); /* 100 secs is a long time; unfortunately
633  * revo settings part is HUUUUGE and takes
634  * forever to download. */
635 
636  triggerPartitionDownload(DFU_PARTITION_SETTINGS);
637  loop.exec(); /* Wait for timeout or download complete */
638  timeout.stop();
639 
640  return operationSuccess;
641 }
642 
643 bool UploaderGadgetWidget::askIfShouldContinue()
644 {
645  QMessageBox msgBox(this);
646  msgBox.setText(tr("Proceed without saving a configuration backup?"));
647  msgBox.setInformativeText(tr("It is strongly encouraged that you save a backup of the "
648  "configuration before proceeding with upgrade."));
649  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
650  msgBox.setDefaultButton(QMessageBox::No);
651 
652  int val = msgBox.exec();
653 
654  if (val == QMessageBox::Yes) {
655  return true;
656  }
657 
658  return false;
659 }
660 
661 bool UploaderGadgetWidget::saveSettings(const QByteArray &settingsDump)
662 {
663  /* save dialog for XML config */
664  QString filename = QFileDialog::getSaveFileName(this, tr("Save Settings Backup"),
665  "cloud_exported.uav", "*.uav");
666  if (filename.isEmpty()) {
667  setStatusInfo(tr("Save cancelled."), uploader::STATUSICON_FAIL);
668  setUploaderStatus(uploader::BL_SITTING);
669  return false;
670  }
671 
672  if (!filename.endsWith(".uav", Qt::CaseInsensitive))
673  filename.append(".uav");
674 
675  QFile file(filename);
676  if (!file.open(QIODevice::WriteOnly)) {
677  setStatusInfo(tr("Error could not open file for save"), uploader::STATUSICON_FAIL);
678 
679  setUploaderStatus(uploader::BL_SITTING);
680  return false;
681  }
682 
683  file.write(settingsDump);
684  file.close();
685  setStatusInfo(tr("Dump of configuration saved to file!"), uploader::STATUSICON_OK);
686 
687  return true;
688 }
689 
690 // Returns negative (<0) if we can't get to the cloud.
691 // Returns false (0) if the cloud doesn't have our release
692 // Returns true (>0) if the cloud has our release and would be glad to
693 // upgrade us.
694 int UploaderGadgetWidget::isCloudReleaseAvailable(QString srcRelease)
695 {
696  QUrl url(hasRevUrl.arg(srcRelease));
697  QNetworkRequest request(url);
698 
699  QNetworkReply *reply = netMngr->head(request);
700 
701  QEventLoop loop;
702 
703  QTimer timeout;
704  timeout.setSingleShot(true);
705 
706  connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
707  connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
708  timeout.start(64000); /* 64 seconds */
709  loop.exec();
710 
711  timeout.stop();
712 
713  if (!reply->isFinished()) {
714  setStatusInfo(tr("Timeout communicating with cloud service"), uploader::STATUSICON_FAIL);
715  return -1;
716  }
717 
718  QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
719 
720  int code = statusCode.toInt();
721 
722  if (code == 200)
723  return 1;
724 
725  if (code == 404)
726  return 0;
727 
728  return -1; // Unexpected error code
729 }
730 
731 bool UploaderGadgetWidget::tradeSettingsWithCloud(QString srcRelease, QString ancestor,
732  bool upgrading, QByteArray *settingsOut)
733 {
734  /* post to cloud service */
735  QUrl url(exportUrl);
736  QNetworkRequest request(url);
737 
738  QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
739 
740  if (settingsOut != NULL) {
741  settingsOut->clear();
742  }
743 
744  // ZLib compress the data
745  QByteArray compressed = qCompress(tempArray, 8);
746 
747  // Remove the first 4 bytes, because Qt prepends expected length
748  // (not compatible with zlib)
749  compressed.remove(0, 4);
750 
751  QHttpPart githash, ancestorPart, adaptTo, datafile;
752 
753  githash.setHeader(QNetworkRequest::ContentDispositionHeader,
754  QVariant("form-data; name=\"githash\""));
755  githash.setBody(srcRelease.toLatin1());
756 
757  ancestorPart.setHeader(QNetworkRequest::ContentDispositionHeader,
758  QVariant("form-data; name=\"ancestor\""));
759  ancestorPart.setBody(ancestor.toLatin1());
760 
761  const QString gcsRev(Core::Constants::GCS_REVISION_STR);
762  adaptTo.setHeader(QNetworkRequest::ContentDispositionHeader,
763  QVariant("form-data; name=\"adaptTo\""));
764  adaptTo.setBody(gcsRev.toLatin1());
765 
766  datafile.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
767  datafile.setHeader(QNetworkRequest::ContentDispositionHeader,
768  QVariant("form-data; filename=\"datafile\"; name=\"datafile\""));
769  datafile.setBody(compressed);
770 
771  multiPart->append(githash);
772  multiPart->append(adaptTo);
773  multiPart->append(ancestorPart);
774  multiPart->append(datafile);
775 
776  QNetworkReply *reply = netMngr->post(request, multiPart);
777 
778  QEventLoop loop;
779 
780  QTimer timeout;
781  timeout.setSingleShot(true);
782 
783  connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
784  connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
785  timeout.start(20000); /* 20 seconds */
786  loop.exec();
787 
788  timeout.stop();
789 
790  if (!reply->isFinished()) {
791  setStatusInfo(tr("Timeout communicating with cloud service"), uploader::STATUSICON_FAIL);
792  return false;
793  }
794 
795  QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
796 
797  int code = statusCode.toInt();
798 
799  if (code != 200) {
800  setStatusInfo(tr("Received status code %1 from cloud").arg(code),
802  return false;
803  }
804 
805  setStatusInfo(tr("Retrieved dump of configuration from cloud"), uploader::STATUSICON_OK);
806 
807  settingsDump = reply->readAll();
808 
809  if (settingsOut != NULL) {
810  *settingsOut = settingsDump;
811  }
812 
813  if (upgrading) {
814  return true;
815  }
816 
817  return saveSettings(settingsDump);
818 }
819 
820 void UploaderGadgetWidget::upgradeError(QString why)
821 {
822  upgraderActive = false;
823 
824  Q_UNUSED(why);
825 
826  m_dialog.hide();
827 
828  qDebug() << "Upgrade assistant failed: " + why;
829 
830  setUploaderStatus(uploader::DISCONNECTED);
831 
832  /* Pop up a proper error dialog */
833  QMessageBox msgBox(this);
834 
835  msgBox.setIcon(QMessageBox::Critical);
836  msgBox.setText(tr("Automatic upgrade failed."));
837  msgBox.setInformativeText(why);
838 
839  msgBox.setStandardButtons(QMessageBox::Ok);
840  msgBox.exec();
841 }
842 
843 void UploaderGadgetWidget::stepChangeAndDelay(QEventLoop &loop, int delayMs,
845 {
846  m_dialog.onStepChanged(step);
847 
848  QTimer delay;
849 
850  delay.setSingleShot(true);
851  connect(&delay, &QTimer::timeout, &loop, &QEventLoop::quit);
852 
853  delay.start(delayMs);
854  loop.exec();
855  delay.stop();
856 }
857 
858 void UploaderGadgetWidget::doUpgradeOperation(bool blankFC, tl_dfu::device &dev)
859 {
860  upgraderActive = true;
861 
863 
865  m_dialog.setOperatingMode(true, blankFC);
866 
867  QEventLoop loop;
868  QTimer timeout;
869 
870  timeout.setSingleShot(true);
871 
872  bool aborted = false;
873  bool entLoader = false;
874 
875  int ret;
876 
877  struct deviceInfo board = currentBoard;
878 
879  connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
880  connect(&m_dialog, &UpgradeAssistantDialog::finished, &loop, [&](int result) {
881  (void)result;
882  aborted = true;
883  loop.exit();
884  });
885 
886  connect(this, &UploaderGadgetWidget::enteredLoader, &loop, [&]() {
887  entLoader = true;
888  loop.exit();
889  });
890 
891  m_dialog.open();
892 
893  if (uploaderStatus != uploader::BL_SITTING) {
894  upgradeError(tr("Not in expected bootloader state!"));
895  return;
896  }
897 
898  /* Save the version to convert from, so we can tell the cloud service */
899  QString upgradingFrom = m_widget->gitHashOD_lbl->text();
900  QString ancestor = m_widget->ancestorHashOD_lbl->text();
901 
902  qDebug() << "Upgrading from " << upgradingFrom;
903 
904  /* Infer what operations we need to do -- first, do they need a new
905  * bootloader? */
906  bool upgradingLoader = false;
907 
908  /* If no settings part known, new loader needed. */
909  upgradingLoader = !haveSettingsPart();
910 
911  int requiredLoader = board.board->minBootLoaderVersion();
912 
913  if (requiredLoader > dev.BL_Version) {
914  upgradingLoader = true;
915  }
916 
917  m_dialog.setOperatingMode(upgradingLoader, blankFC);
918 
919  if (!blankFC) {
920  stepChangeAndDelay(loop, 400, UpgradeAssistantDialog::STEP_CHECKCLOUD);
921 
922  int available = isCloudReleaseAvailable(upgradingFrom);
923 
924  if (available == 0) {
925  available = isCloudReleaseAvailable(ancestor);
926  }
927 
928  if (available < 0) {
929  // Cloud service missing. Pop up a dialog and ask user what to
930  // do.
931 
932  int val =
933  QMessageBox::critical(this, tr("Cloud upgrade service not available"),
934  tr("The cloud upgrade service is not reachable. "
935  "You may continue without migrating settings, "
936  "or cancel the upgrade."),
937  QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel);
938 
939  if (val != QMessageBox::Ok) {
940  aborted = true;
941  } else {
942  blankFC = true;
943  }
944  } else if (available == 0) {
945  // Cloud service doesn't know about source release. Pop up a
946  // dialog and ask what to do.
947 
948  int val = QMessageBox::critical(this, tr("Cloud upgrade service: release unknown"),
949  tr("The cloud upgrade service does not know about "
950  "the firmware on the flight controller. "
951  "You may discard the existing settings, "
952  "cancel the upgrade, or ignore and attempt to "
953  "proceed with migration anyways."),
954  QMessageBox::Discard | QMessageBox::Cancel
955  | QMessageBox::Ignore,
956  QMessageBox::Cancel);
957 
958  if (val == QMessageBox::Discard) {
959  blankFC = true;
960  } else if (val != QMessageBox::Ignore) {
961  aborted = true;
962  }
963  }
964  }
965 
966  // Set this again, as we could have decided to treat the board as blank.
967  m_dialog.setOperatingMode(upgradingLoader, blankFC);
968 
969  if (aborted) {
970  upgradeError(tr("Aborted!"));
971 
972  return;
973  }
974 
975  /* gather up bootupdater and firmware images as needed,
976  * so we spot errors before we do destructive things */
977 
978  QByteArray bootUpdateFile, firmwareFile;
979 
980  if (upgradingLoader) {
981  if (!FirmwareLoadFromFile(getImagePath(board.board->shortName(), "bu"), &bootUpdateFile)) {
982  upgradeError(tr("Unable to load bootloader update image for board!"));
983 
984  return;
985  }
986  }
987 
988  if (!FirmwareLoadFromFile(getImagePath(board.board->shortName(), "fw"), &firmwareFile)) {
989  upgradeError(tr("Unable to load firmware image for board!"));
990 
991  return;
992  }
993 
994  if (upgradingLoader) {
995  stepChangeAndDelay(loop, 400, UpgradeAssistantDialog::STEP_UPGRADEBOOTLOADER);
996 
997  if (aborted) {
998  upgradeError(tr("Aborted!"));
999 
1000  return;
1001  }
1002 
1003  /* program the boot upgrader tool */
1004  FirmwareLoadedClear(true);
1005  FirmwareLoadedUpdate(bootUpdateFile);
1006  setUploaderStatus(getUploaderStatus());
1007 
1008  if (!flashFirmware(bootUpdateFile)) {
1009  upgradeError(tr("Unable to flash upgrader image to board!"));
1010 
1011  return;
1012  }
1013 
1014  entLoader = false;
1015  setUploaderStatus(uploader::UPGRADING_CATCHLOADER);
1016  dfu.JumpToApp(false);
1017  dfu.CloseBootloaderComs();
1018 
1019  /* Wait for / detect main loader */
1020  timeout.start(25000);
1021  loop.exec();
1022  timeout.stop();
1023 
1024  if (!entLoader) {
1025  upgradeError(tr("Unable to enter bootloader after bootloader upgrade!"));
1026  }
1027  }
1028 
1029  QByteArray xmlDump;
1030 
1031  if (!blankFC) {
1032  stepChangeAndDelay(loop, 400, UpgradeAssistantDialog::STEP_DOWNLOADSETTINGS);
1033 
1034  if (aborted) {
1035  upgradeError(tr("Aborted!"));
1036 
1037  return;
1038  }
1039 
1040  /* download the settings partition from the board */
1041  if (!downloadSettings()) {
1042  upgradeError(tr("Unable to pull settings partition!"));
1043 
1044  return;
1045  }
1046 
1047  stepChangeAndDelay(loop, 100, UpgradeAssistantDialog::STEP_TRANSLATESETTINGS);
1048 
1049  if (aborted) {
1050  upgradeError(tr("Aborted!"));
1051 
1052  return;
1053  }
1054 
1055  /* translate the settings using the cloud service */
1056  if (!tradeSettingsWithCloud(upgradingFrom, ancestor, true, &xmlDump)) {
1057  upgradeError(tr("Unable to use cloud services to translate settings!"));
1058 
1059  return;
1060  }
1061 
1062  bool done = false;
1063 
1064  while (!done) {
1065  if (aborted) {
1066  upgradeError(tr("Aborted!"));
1067 
1068  return;
1069  }
1070 
1071  ret = m_dialog.PromptUser(tr("Obtained settings from cloud."),
1072  tr("It is recommended that you save a backup of the settings "
1073  "before proceeding with upgrade."),
1074  QStringList() << tr("Continue without saving")
1075  << tr("Save settings backup"));
1076 
1077  switch (ret) {
1078  case 0: /* continue */
1079  done = true;
1080  break;
1081  case 1: /* save settings backup */
1082  /* try to save. if successful, set done */
1083  done = saveSettings(xmlDump);
1084  break;
1085  default:
1086  break;
1087  }
1088  }
1089  }
1090 
1091  stepChangeAndDelay(loop, 400, UpgradeAssistantDialog::STEP_ERASESETTINGS);
1092 
1093  if (aborted) {
1094  upgradeError(tr("Aborted!"));
1095 
1096  return;
1097  }
1098 
1099  /* wipe the board setting partition in prepation of upgrade */
1100  if (!dfu.WipePartition(DFU_PARTITION_SETTINGS)) {
1101  upgradeError(tr("Failed to erase settings partition!"));
1102  }
1103 
1104  stepChangeAndDelay(loop, 400, UpgradeAssistantDialog::STEP_FLASHFIRMWARE);
1105 
1106  if (aborted) {
1107  upgradeError(tr("Aborted!"));
1108 
1109  return;
1110  }
1111 
1112  /* flash the appropriate main firmware image */
1113  FirmwareLoadedClear(true);
1114  FirmwareLoadedUpdate(firmwareFile);
1115  setUploaderStatus(getUploaderStatus());
1116 
1117  loop.processEvents();
1118  if (aborted) {
1119  upgradeError(tr("Aborted!"));
1120 
1121  return;
1122  }
1123 
1124  if (!flashFirmware(firmwareFile)) {
1125  upgradeError(tr("Unable to flash firmware image to board!"));
1126 
1127  return;
1128  }
1129 
1130  if (aborted) {
1131  upgradeError(tr("Aborted!"));
1132 
1133  return;
1134  }
1135 
1136  /* Set ignoredrev to what we just programmed, so if we programmed
1137  * an outdated rev in a developer environment we don't immediately bop
1138  * up the message */
1139  ignoredRev = m_widget->gitHashOD_lbl->text();
1140 
1141  stepChangeAndDelay(loop, 400, UpgradeAssistantDialog::STEP_BOOT);
1142 
1143  if (aborted) {
1144  upgradeError(tr("Aborted!"));
1145 
1146  return;
1147  }
1148 
1149  bool firmwareConnected = false;
1150 
1151  /* start firmware and wait for telemetry connection */
1152  connect(telMngr, &TelemetryManager::connected, &loop, [&]() {
1153  firmwareConnected = true;
1154  loop.exit();
1155  });
1156 
1157  dfu.JumpToApp(false);
1158  dfu.CloseBootloaderComs();
1159  setUploaderStatus(uploader::DISCONNECTED);
1160 
1161  for (int tries = 0; tries < 2; tries++) {
1162  if (aborted) {
1163  upgradeError(tr("Aborted!"));
1164 
1165  return;
1166  }
1167 
1168  timeout.start(25000);
1169 
1170  if (!firmwareConnected) {
1171  loop.exec();
1172  }
1173 
1174  timeout.stop();
1175 
1176  if (aborted) {
1177  upgradeError(tr("Aborted!"));
1178 
1179  return;
1180  }
1181 
1182  if (!firmwareConnected) {
1183  if (tries == 0) {
1184  ret = m_dialog.PromptUser(tr("Unable to automatically reset board"),
1185  tr("Please remove power and USB from the flight board, "
1186  " plug it back in, and select \"Continue.\""),
1187  QStringList() << tr("Continue"));
1188  } else {
1189  upgradeError(tr("Unable to connect to new firmware!"));
1190 
1191  return;
1192  }
1193  } else {
1194  break;
1195  }
1196  }
1197 
1198  if (!blankFC) {
1199  stepChangeAndDelay(loop, 400, UpgradeAssistantDialog::STEP_IMPORT);
1200 
1201  while (true) {
1202  if (aborted) {
1203  upgradeError(tr("Aborted!"));
1204 
1205  return;
1206  }
1207 
1208  ret = m_dialog.PromptUser(tr("Please review the imported settings and save to board."),
1209  tr("No settings are currently saved. Select the "
1210  "\"Review and Save\" button to import the settings."),
1211  QStringList() << tr("Review and Save"));
1212 
1213  if (ret == 0) {
1214  if (importMngr->importUAVSettings(xmlDump, true)) {
1215  break;
1216  }
1217  }
1218  }
1219  }
1220 
1222 
1223  while (!aborted) {
1224  loop.exec();
1225  }
1226 
1227  upgraderActive = false;
1228 }
1229 
1235 void UploaderGadgetWidget::onExportButtonClick()
1236 {
1237  if (telMngr->isConnected()) {
1238  /* select the UAV-oriented export thing */
1240 
1241  if (!am) {
1242  qWarning() << "Could not get ActionManager instance!";
1243  return;
1244  }
1245 
1246  Core::Command *cmd = am->command("UAVSettingsImportExportPlugin.UAVSettingsExport");
1247 
1248  if (cmd)
1249  cmd->action()->trigger();
1250  else
1251  qWarning() << "Could find UAVSettingsExport action!";
1252 
1253  return;
1254  }
1255 
1256  /* make sure there's a setting partition */
1257  if (!haveSettingsPart()) {
1258  QMessageBox msgBox(this);
1259 
1260  msgBox.setText(tr("No settings partition accessible; can't export."));
1261  msgBox.setInformativeText(tr("Please upgrade your bootloader."));
1262  msgBox.setStandardButtons(QMessageBox::Ok);
1263  msgBox.setDefaultButton(QMessageBox::Ok);
1264 
1265  msgBox.exec();
1266 
1267  return;
1268  }
1269 
1270  /* get confirmation from user that using the cloud service is OK */
1271  QMessageBox msgBox(this);
1272  msgBox.setText(tr("Do you wish to export the settings partition as an XML settings file?"));
1273  msgBox.setInformativeText(tr("This will send the raw configuration information to a dRonin "
1274  "cloud service for translation."));
1275  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
1276  msgBox.setDefaultButton(QMessageBox::Yes);
1277 
1278  int val = msgBox.exec();
1279 
1280  if (val != QMessageBox::Yes) {
1281  return;
1282  }
1283 
1284  /* pull down settings partition to ram */
1285  if (!downloadSettings()) {
1286  setStatusInfo(tr("Error, unable to pull settings partition"), uploader::STATUSICON_FAIL);
1287 
1288  return;
1289  }
1290 
1291  setStatusInfo(tr("Retrieved settings; contacting cloud..."), uploader::STATUSICON_FAIL);
1292 
1293  QString upgradingFrom = m_widget->gitHashOD_lbl->text();
1294  QString ancestor = m_widget->ancestorHashOD_lbl->text();
1295 
1296  tradeSettingsWithCloud(upgradingFrom, ancestor);
1297 }
1298 
1303 void UploaderGadgetWidget::onBootloaderDetected()
1304 {
1306  QList<USBPortInfo> devices;
1307 
1308  // If we are already connected to a bootloader, skip
1309  if (uploaderStatus == uploader::BL_SITTING) {
1310  return;
1311  }
1312 
1313  foreach (int vendorID, brdMgr->getKnownVendorIDs()) {
1314  devices.append(
1315  USBMonitor::instance()->availableDevices(vendorID, -1, -1, USBMonitor::Upgrader));
1316  devices.append(
1317  USBMonitor::instance()->availableDevices(vendorID, -1, -1, USBMonitor::Bootloader));
1318  }
1319 
1320  if (devices.length() > 1) {
1321  setStatusInfo(tr("More than one device was detected in bootloader state"),
1323  // return;
1324  } else if (devices.length() == 0) {
1325  setStatusInfo("No devices in bootloader state detected", uploader::STATUSICON_FAIL);
1326  return;
1327  }
1328 
1329  USBPortInfo device = devices.first();
1330 
1331  bool triggerUpgrading = false;
1332  bool inUpgrader = false;
1333  bool validDescription = false;
1334  bool blankFC = false;
1335 
1336  if (device.getRunState() == USBMonitor::Upgrader) {
1337  inUpgrader = true;
1338  }
1339 
1340  if (dfu.OpenBootloaderComs(device)) {
1341  tl_dfu::device dev = dfu.findCapabilities();
1342 
1343  QByteArray description = dfu.DownloadDescriptionAsByteArray(dev.SizeOfDesc);
1344  deviceDescriptorStruct descStructure;
1345  if (!UAVObjectUtilManager::descriptionToStructure(description, descStructure))
1346  setStatusInfo(tr("Could not parse firmware metadata"), uploader::STATUSICON_INFO);
1347  else {
1348  FirmwareOnDeviceUpdate(descStructure, QString::number((dev.FW_CRC)));
1349  validDescription = true;
1350  }
1351 
1352  if (!inUpgrader) {
1353  switch (uploaderStatus) {
1354  case uploader::UPGRADING:
1355  triggerUpgrading = true;
1358  break;
1359  case uploader::DISCONNECTED: {
1360  // look for completed bootloader update (last 2 chars of TlFw string are nulled)
1361  if (QString(description.left(4)) == "Tl")
1362  break;
1363 
1364  if (validDescription) {
1365  if (FirmwareCheckForUpdate(descStructure)) {
1366  triggerUpgrading = true;
1367  break;
1368  }
1369  } else {
1370  QMessageBox msgBox(this);
1371  msgBox.setText(tr("There appears to be no valid firmware on your device.."));
1372  msgBox.setInformativeText(tr("Do you want to install firmware?"));
1373  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
1374  msgBox.setDefaultButton(QMessageBox::Yes);
1375 
1376  int val = msgBox.exec();
1377 
1378  if (val == QMessageBox::Yes) {
1379  triggerUpgrading = true;
1380  blankFC = true;
1381  }
1382 
1383  break; /* Don't boot in any case */
1384  }
1385  }
1386  Q_FALLTHROUGH();
1387  default:
1388  dfu.JumpToApp(false);
1389  dfu.CloseBootloaderComs();
1390  return;
1391  }
1392  }
1393 
1394  // Bootloader has new cap extensions, query partitions and fill out browser
1395  if (dev.CapExt) {
1396  QTableWidgetItem *label;
1397  QTableWidgetItem *size;
1398  int index = 0;
1399  int row_count = 0;
1400 
1401  QString name;
1402 
1403  foreach (int i, dev.PartitionSizes) {
1404  if (i <= 0) {
1405  /* PartitionSizes is now sparse; there may be 0 sized parts
1406  * in the middle.
1407  */
1408 
1409  ++index;
1410  continue;
1411  }
1412 
1413  switch (index) {
1414  case DFU_PARTITION_FW:
1415  name = "Firmware";
1416  break;
1417  case DFU_PARTITION_DESC:
1418  name = "Metadata";
1419  break;
1420  case DFU_PARTITION_BL:
1421  name = "Bootloader";
1422  break;
1424  name = "Settings";
1425  break;
1427  name = "AutoTune";
1428  break;
1429  case DFU_PARTITION_LOG:
1430  name = "Log";
1431  break;
1433  name = "Extension";
1434  break;
1435  default:
1436  name = QString::number(index);
1437  break;
1438  }
1439  label = new QTableWidgetItem(name);
1440  size = new QTableWidgetItem(QString::number(i));
1441 
1442  label->setData(Qt::UserRole, index);
1443  size->setData(Qt::UserRole, index);
1444 
1445  size->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
1446 
1447  m_widget->partitionBrowserTW->setRowCount(row_count + 1);
1448 
1449  m_widget->partitionBrowserTW->setItem(row_count, 0, label);
1450  m_widget->partitionBrowserTW->setItem(row_count, 1, size);
1451  ++index;
1452  ++row_count;
1453  }
1454 
1455  m_widget->partitionBrowserGB->setEnabled(true);
1456  } else {
1457  m_widget->partitionBrowserTW->setRowCount(0);
1458  }
1459 
1460  deviceInfo info;
1461  info.board = brdMgr->getBoard(dev.ID >> 8);
1462  if (!info.board) {
1463  setStatusInfo(tr("Board not supported!"), uploader::STATUSICON_FAIL);
1464  return;
1465  }
1466  info.bl_version = QString::number(dev.BL_Version, 16);
1467  info.cpu_serial = "Not Available";
1468  info.hw_revision = QString::number(dev.HW_Rev);
1469  info.max_code_size = QString::number(dev.SizeOfCode);
1470  DeviceInformationUpdate(info);
1471 
1472  iapUpdated = false;
1473 
1474  if (!inUpgrader) {
1475  setStatusInfo(tr("Connection to bootloader successful"), uploader::STATUSICON_OK);
1476 
1477  if (FirmwareLoadFromFile(getImagePath(info.board->shortName()), &loadedFile)) {
1478  FirmwareLoadedClear(true);
1479  FirmwareLoadedUpdate(loadedFile);
1480  setStatusInfo(tr("Ready to flash firmware"), uploader::STATUSICON_OK);
1481  this->activateWindow();
1482  m_widget->flashButton->setFocus();
1483  }
1484 
1485  setUploaderStatus(uploader::BL_SITTING);
1486  } else {
1487  setStatusInfo(tr("Connected to upgrader-loader"), uploader::STATUSICON_OK);
1488  setUploaderStatus(uploader::BL_SITTING);
1489  }
1490 
1491  if (triggerUpgrading) {
1492  if (currentBoard.board) {
1493  bool canBeUpgraded = currentBoard.board->queryCapabilities(
1495 
1496  if (canBeUpgraded) {
1497  doUpgradeOperation(blankFC, dev);
1498  return;
1499  }
1500  }
1501  }
1502 
1503  emit enteredLoader();
1504  } else {
1505  setStatusInfo(tr("Could not open coms with the bootloader"), uploader::STATUSICON_FAIL);
1506  setUploaderStatus((uploader::DISCONNECTED));
1507  conMngr->resumePolling();
1508  }
1509 }
1510 
1515 void UploaderGadgetWidget::onBootloaderRemoved()
1516 {
1517  conMngr->resumePolling();
1518  setStatusInfo(tr("Bootloader disconnection detected"), uploader::STATUSICON_INFO);
1519  DeviceInformationClear();
1520  FirmwareOnDeviceClear(true);
1521  PartitionBrowserClear();
1522 
1523  if (uploaderStatus != uploader::UPGRADING_CATCHLOADER) {
1524  setUploaderStatus(uploader::DISCONNECTED);
1525  }
1526 }
1527 
1528 void UploaderGadgetWidget::startRescue()
1529 {
1530  setStatusInfo(tr("Trying to detect bootloader"), uploader::STATUSICON_INFO);
1531  m_widget->progressBar->setValue(100);
1532 
1533  connect(&rescueTimer, &QTimer::timeout, this, &UploaderGadgetWidget::onRescueTimer,
1534  Qt::UniqueConnection);
1535  rescueTimer.start(200);
1536 
1537  connect(usbFilterBL, &USBSignalFilter::deviceDiscovered, this,
1538  &UploaderGadgetWidget::onDeviceDiscovered, Qt::UniqueConnection);
1539  connect(usbFilterUP, &USBSignalFilter::deviceDiscovered, this,
1540  &UploaderGadgetWidget::onDeviceDiscovered, Qt::UniqueConnection);
1541 }
1542 
1547 void UploaderGadgetWidget::onRescueTimer()
1548 {
1549  m_widget->progressBar->setValue(m_widget->progressBar->value() - 1);
1550 
1551  if (m_widget->progressBar->value() <= 0) {
1552  disconnect(usbFilterBL, &USBSignalFilter::deviceDiscovered, this,
1553  &UploaderGadgetWidget::onDeviceDiscovered);
1554  disconnect(usbFilterUP, &USBSignalFilter::deviceDiscovered, this,
1555  &UploaderGadgetWidget::onDeviceDiscovered);
1556  rescueTimer.disconnect();
1557  setStatusInfo(tr("Failed to detect bootloader"), uploader::STATUSICON_FAIL);
1558  setUploaderStatus(uploader::DISCONNECTED);
1559  conMngr->resumePolling();
1560  }
1561 }
1562 
1563 void UploaderGadgetWidget::onDeviceDiscovered()
1564 {
1565  rescueTimer.stop();
1566  m_widget->progressBar->setValue(0);
1567  disconnect(usbFilterBL, &USBSignalFilter::deviceDiscovered, this,
1568  &UploaderGadgetWidget::onDeviceDiscovered);
1569  disconnect(usbFilterUP, &USBSignalFilter::deviceDiscovered, this,
1570  &UploaderGadgetWidget::onDeviceDiscovered);
1571 }
1572 
1578 void UploaderGadgetWidget::onStatusUpdate(QString text, int progress)
1579 {
1580  if (!text.isEmpty())
1581  setStatusInfo(text, uploader::STATUSICON_RUNNING);
1582  if (progress != -1)
1583  m_widget->progressBar->setValue(progress);
1584 }
1585 
1586 void UploaderGadgetWidget::triggerPartitionDownload(int index)
1587 {
1588  if (!CheckInBootloaderState())
1589  return;
1590 
1591  tl_dfu::device dev = dfu.findCapabilities();
1592  int size = dev.PartitionSizes[index];
1593 
1594  setStatusInfo("", uploader::STATUSICON_RUNNING);
1595  setUploaderStatus(uploader::BL_BUSY);
1596  tempArray.clear();
1597  dfu.DownloadPartitionThreaded(&tempArray, (dfu_partition_label)index, size);
1598 }
1599 
1603 void UploaderGadgetWidget::onPartitionSave()
1604 {
1605  int index = m_widget->partitionBrowserTW->selectedItems().first()->data(Qt::UserRole).toInt();
1606 
1607  QEventLoop loop;
1608 
1609  bool operationSuccess = false;
1610 
1611  /* disconnects when loop comes out of scope */
1612  connect(&dfu, &DFUObject::downloadFinished, &loop, [&](bool status) {
1613  operationSuccess = status;
1614  loop.exit();
1615  });
1616 
1617  triggerPartitionDownload(index);
1618 
1619  loop.exec();
1620 
1621  if (operationSuccess) {
1622  setStatusInfo(tr("Partition download success"), uploader::STATUSICON_OK);
1623  QString filename =
1624  QFileDialog::getSaveFileName(this, tr("Save File"), QDir::homePath(), "*.bin");
1625  if (filename.isEmpty()) {
1626  setStatusInfo(tr("Save cancelled"), uploader::STATUSICON_FAIL);
1627  setUploaderStatus(uploader::BL_SITTING);
1628  return;
1629  }
1630 
1631  if (!filename.endsWith(".bin", Qt::CaseInsensitive))
1632  filename.append(".bin");
1633  QFile file(filename);
1634  if (file.open(QIODevice::WriteOnly)) {
1635  file.write(tempArray);
1636  file.close();
1637  setStatusInfo(tr("Partition written to file"), uploader::STATUSICON_OK);
1638  } else
1639  setStatusInfo(tr("Error could not open file for save"), uploader::STATUSICON_FAIL);
1640  } else {
1641  setStatusInfo(tr("Partition download failed"), uploader::STATUSICON_FAIL);
1642  }
1643 
1644  setUploaderStatus(uploader::BL_SITTING);
1645 }
1646 
1650 void UploaderGadgetWidget::onPartitionFlash()
1651 {
1652  if (!CheckInBootloaderState())
1653  return;
1654  QString filename =
1655  QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), "*.bin");
1656  if (filename.isEmpty()) {
1657  setStatusInfo(tr("Flash cancelled"), uploader::STATUSICON_FAIL);
1658  return;
1659  }
1660  QFile file(filename);
1661  if (!file.open(QIODevice::ReadOnly)) {
1662  setStatusInfo(tr("Could not open file for read"), uploader::STATUSICON_FAIL);
1663  return;
1664  }
1665  tempArray.clear();
1666  tempArray = file.readAll();
1667 
1668  int index = m_widget->partitionBrowserTW->selectedItems().first()->data(Qt::UserRole).toInt();
1669  tl_dfu::device dev = dfu.findCapabilities();
1670  int size = dev.PartitionSizes[index];
1671 
1672  if (tempArray.length() > size) {
1673  setStatusInfo(tr("File bigger than partition size"), uploader::STATUSICON_FAIL);
1674  }
1675  if (QMessageBox::warning(this, tr("Warning"),
1676  tr("Are you sure you want to flash the selected partition?"),
1677  QMessageBox::Yes, QMessageBox::No)
1678  != QMessageBox::Yes)
1679  return;
1680  setStatusInfo("", uploader::STATUSICON_RUNNING);
1681  setUploaderStatus(uploader::BL_BUSY);
1682 
1683  tl_dfu::Status operationSuccess = DFUidle;
1684 
1685  QEventLoop loop;
1686 
1687  /* disconnects when loop comes out of scope */
1688  connect(&dfu, &DFUObject::uploadFinished, &loop, [&](tl_dfu::Status status) {
1689  operationSuccess = status;
1690  loop.exit();
1691  });
1692 
1693  dfu.UploadPartitionThreaded(tempArray, (dfu_partition_label)index, size);
1694 
1695  loop.exec();
1696 
1697  if (operationSuccess == Last_operation_Success)
1698  setStatusInfo(tr("Partition upload success"), uploader::STATUSICON_OK);
1699  else
1700  setStatusInfo(tr("Partition upload failed"), uploader::STATUSICON_FAIL);
1701  setUploaderStatus(uploader::BL_SITTING);
1702 }
1703 
1707 void UploaderGadgetWidget::onPartitionErase()
1708 {
1709  int index = m_widget->partitionBrowserTW->selectedItems().first()->data(Qt::UserRole).toInt();
1710 
1711  if (QMessageBox::warning(this, tr("Warning"),
1712  tr("Are you sure you want erase the selected partition?"),
1713  QMessageBox::Yes, QMessageBox::No)
1714  != QMessageBox::Yes)
1715  return;
1716  if (dfu.WipePartition((dfu_partition_label)index))
1717  setStatusInfo(tr("Partition erased"), uploader::STATUSICON_OK);
1718  else
1719  setStatusInfo(tr("Partition erasure failed"), uploader::STATUSICON_FAIL);
1720 }
1721 
1726 void UploaderGadgetWidget::onBootButtonClick()
1727 {
1728  bool safeboot = (sender() == m_widget->safeBootButton);
1729 
1730  if (telMngr->isConnected()) {
1731  haltOrReset(false);
1732  return;
1733  }
1734 
1735  if (!CheckInBootloaderState())
1736  return;
1737 
1738  dfu.JumpToApp(safeboot);
1739  dfu.CloseBootloaderComs();
1740 }
1741 
1745 void UploaderGadgetWidget::CheckAutopilotReady()
1746 {
1747  if (telemetryConnected && iapUpdated) {
1748  onAutopilotReady();
1749  }
1750 }
1751 
1756 bool UploaderGadgetWidget::CheckInBootloaderState()
1757 {
1758  if (uploaderStatus != uploader::BL_SITTING) {
1759  setStatusInfo(tr("Cannot perform the selected operation if not in bootloader state"),
1761  return false;
1762  } else
1763  return true;
1764 }
1765 
1770 QString UploaderGadgetWidget::LoadFirmwareFileDialog(QString boardName)
1771 {
1772  QFileDialog::Options options;
1773  QString selectedFilter;
1774  boardName = boardName.toLower();
1775 
1776  QString fwDirectoryStr = getImagePath(boardName);
1777 
1778  QString fileName = QFileDialog::getOpenFileName(
1779  this, tr("Select firmware file"), fwDirectoryStr, tr("Firmware (fw_*.tlfw);;"
1780  "Bootloader Update (bu_*.tlfw);;"
1781  "All (*.tlfw *.opfw *.bin)"),
1782  &selectedFilter, options);
1783  return fileName;
1784 }
1785 
1791 void UploaderGadgetWidget::setStatusInfo(QString str, uploader::StatusIcon ic)
1792 {
1793  qDebug() << str;
1794 
1795  QPixmap px;
1796  m_widget->statusLabel->setText(str);
1797  switch (ic) {
1799  px.load(QString(":/uploader/images/system-run.svg"));
1800  break;
1802  px.load(QString(":/uploader/images/dialog-apply.svg"));
1803  break;
1805  px.load(QString(":/uploader/images/process-stop.svg"));
1806  break;
1807  default:
1808  px.load(QString(":/uploader/images/gtk-info.svg"));
1809  }
1810  m_widget->statusPic->setPixmap(px);
1811 }
1812 
1816 uploader::UploaderStatus UploaderGadgetWidget::getUploaderStatus() const
1817 {
1818  return uploaderStatus;
1819 }
1820 
1821 bool UploaderGadgetWidget::haveSettingsPart() const
1822 {
1823  /* Would be nice to determine this in a nicer way */
1824  if (m_widget->partitionBrowserTW->rowCount() >= DFU_PARTITION_SETTINGS) {
1825  return true;
1826  }
1827 
1828  return false;
1829 }
1830 
1836 void UploaderGadgetWidget::setUploaderStatus(const uploader::UploaderStatus &value)
1837 {
1838  uploaderStatus = value;
1839  switch (uploaderStatus) {
1841  m_widget->progressBar->setVisible(false);
1842 
1843  m_widget->rescueButton->setEnabled(true);
1844  m_widget->rescueButton->setText(tr("Rescue"));
1845  m_widget->bootButton->setText(tr("Boot"));
1846 
1847  m_widget->openButton->setEnabled(true);
1848  m_widget->bootButton->setEnabled(false);
1849  m_widget->safeBootButton->setEnabled(false);
1850  m_widget->flashButton->setEnabled(false);
1851  m_widget->exportConfigButton->setEnabled(false);
1852  m_widget->partitionBrowserTW->setContextMenuPolicy(Qt::NoContextMenu);
1853  break;
1855  m_widget->progressBar->setVisible(true);
1856 
1857  m_widget->rescueButton->setText(tr("Rescue"));
1858  m_widget->bootButton->setText(tr("Boot"));
1859 
1860  m_widget->rescueButton->setEnabled(false);
1861  m_widget->openButton->setEnabled(true);
1862  m_widget->bootButton->setEnabled(false);
1863  m_widget->safeBootButton->setEnabled(false);
1864  m_widget->flashButton->setEnabled(false);
1865  m_widget->exportConfigButton->setEnabled(false);
1866  m_widget->partitionBrowserTW->setContextMenuPolicy(Qt::NoContextMenu);
1867  break;
1868  case uploader::BL_SITTING:
1869  m_widget->progressBar->setVisible(false);
1870 
1871  m_widget->rescueButton->setText(tr("Rescue"));
1872  m_widget->bootButton->setText(tr("Boot"));
1873 
1874  m_widget->rescueButton->setEnabled(false);
1875  m_widget->openButton->setEnabled(true);
1876  m_widget->bootButton->setEnabled(true);
1877  m_widget->safeBootButton->setEnabled(true);
1878  if (!loadedFile.isEmpty())
1879  m_widget->flashButton->setEnabled(true);
1880  else
1881  m_widget->flashButton->setEnabled(false);
1882 
1883  m_widget->exportConfigButton->setEnabled(haveSettingsPart());
1884 
1885  m_widget->partitionBrowserTW->setContextMenuPolicy(Qt::ActionsContextMenu);
1886  break;
1888  m_widget->progressBar->setVisible(false);
1889 
1890  m_widget->rescueButton->setText(tr("Enter bootloader"));
1891  m_widget->bootButton->setText(tr("Reboot"));
1892 
1893  m_widget->rescueButton->setEnabled(true);
1894  m_widget->openButton->setEnabled(true);
1895  m_widget->bootButton->setEnabled(true);
1896  m_widget->safeBootButton->setEnabled(false);
1897  m_widget->flashButton->setEnabled(false);
1898  m_widget->exportConfigButton->setEnabled(true);
1899 
1900  m_widget->partitionBrowserTW->setContextMenuPolicy(Qt::NoContextMenu);
1901  break;
1902  case uploader::UPGRADING:
1904  case uploader::BL_BUSY:
1905  m_widget->progressBar->setVisible(true);
1906 
1907  m_widget->rescueButton->setEnabled(false);
1908  m_widget->openButton->setEnabled(false);
1909  m_widget->bootButton->setEnabled(false);
1910  m_widget->safeBootButton->setEnabled(false);
1911  m_widget->flashButton->setEnabled(false);
1912  m_widget->exportConfigButton->setEnabled(false);
1913  m_widget->partitionBrowserTW->setContextMenuPolicy(Qt::NoContextMenu);
1914  break;
1915  default:
1916  break;
1917  }
1918 }
1919 
1923 void UploaderGadgetWidget::openHelp()
1924 {
1925  QDesktopServices::openUrl(QUrl(
1926  "https://github.com/d-ronin/dRonin/wiki/OnlineHelp:-Uploader-Plugin", QUrl::StrictMode));
1927 }
1928 
1929 QString UploaderGadgetWidget::getImagePath(QString boardName, QString imageType)
1930 {
1931  QString imageName = QString("%1_%2").arg(imageType).arg(boardName.toLower());
1932  QString imageNameWithSuffix = QString("%1.tlfw").arg(imageName);
1933 
1934  QStringList paths = QStringList()
1935  /* Relative paths first; try in application bundle or build dir */
1936  << "." // uh, right here?
1937  << "../firmware" // windows installed path
1938  /* Added ../build to these to make sure our assumptions are right
1939  * about it being a build tree */
1940  << "../../../../build" // windows / linux build
1941  << "../../../../../../../build" // OSX build
1942  << "../Resources/firmware" // OSX app bundle
1943  << "/usr/local/" GCS_PROJECT_BRANDING_PRETTY "/firmware"; // leenucks deb
1944 
1945  foreach (QString path, paths) {
1946  QDir pathDir = QDir(QCoreApplication::applicationDirPath());
1947 
1948  if (pathDir.cd(path)) {
1949  /* Two things to try. */
1950  QString perTargetPath = QString("%1/%2/%3")
1951  .arg(pathDir.absolutePath())
1952  .arg(imageName)
1953  .arg(imageNameWithSuffix);
1954 
1955  if (QFile::exists(perTargetPath)) {
1956  return perTargetPath;
1957  }
1958 
1959  QString directPath =
1960  QString("%1/%2").arg(pathDir.absolutePath()).arg(imageNameWithSuffix);
1961 
1962  if (QFile::exists(directPath)) {
1963  return directPath;
1964  }
1965  }
1966  }
1967 
1968  /* This is sane for file dialogs, too. */
1969  return QString("");
1970 }
1971 
1972 bool UploaderGadgetWidget::FirmwareLoadFromFile(QString filename, QByteArray *contents)
1973 {
1974  QFileInfo fileinfo = QFileInfo(filename);
1975 
1976  if (!fileinfo.exists())
1977  return false;
1978 
1979  QFile file(fileinfo.filePath());
1980  if (!file.open(QIODevice::ReadOnly))
1981  return false;
1982  *contents = file.readAll();
1983 
1984  return true;
1985 }
1986 
1987 bool UploaderGadgetWidget::FirmwareCheckForUpdate(deviceDescriptorStruct device)
1988 {
1989  if (currentBoard.board) {
1990  bool canBeUpgraded =
1991  currentBoard.board->queryCapabilities(Core::IBoardType::BOARD_CAPABILITIES_UPGRADEABLE);
1992 
1993  if (!canBeUpgraded) {
1994  return false;
1995  }
1996  }
1997 
1998  const QString gcsRev(GCS_REVISION);
1999  if (gcsRev.contains(':')) {
2000  QString gcsShort = gcsRev.mid(gcsRev.indexOf(':') + 1, 8);
2001  if ((gcsShort != device.gitHash) && (ignoredRev != device.gitHash)) {
2002  QMessageBox msgBox;
2003  msgBox.setText(
2004  tr("The firmware version on your board does not match this version of GCS."));
2005  msgBox.setInformativeText(
2006  tr("Do you want to upgrade the firmware to a compatible version?"));
2007  msgBox.setDetailedText(QString("Firmware git hash: %1\nGCS git hash: %2")
2008  .arg(device.gitHash)
2009  .arg(gcsShort));
2010  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Ignore);
2011  msgBox.setDefaultButton(QMessageBox::Yes);
2012 
2013  int val = msgBox.exec();
2014 
2015  if (val == QMessageBox::Yes)
2016  return true;
2017  else if (val == QMessageBox::Ignore)
2018  ignoredRev = device.gitHash;
2019  }
2020  }
2021  return false;
2022 }
2023 
2025 {
2026  switch (uploaderStatus) {
2027  case DISCONNECTED:
2029  return upgraderActive;
2030  case ENTERING_LOADER:
2031  case BL_SITTING:
2032  case BL_BUSY:
2033  case UPGRADING:
2034  case UPGRADING_CATCHLOADER:
2035  break;
2036  }
2037  return true;
2038 }
bool importUAVSettings(const QByteArray &settings, bool quiet=false)
int PromptUser(QString promptText, QString detailText, QStringList buttonText)
void newBoardSeen(deviceInfo board, deviceDescriptorStruct device)
quint8 BL_Version
Definition: tl_dfu.h:66
void operationProgress(QString status, int progress)
static ModeManager * instance()
Definition: modemanager.h:62
int SizeOfDesc
Definition: tl_dfu.h:67
quint32 SizeOfCode
Definition: tl_dfu.h:68
virtual QAction * action() const =0
QList< int > getKnownVendorIDs()
getKnownVendorIDs Get all USB VendorIDs known by the board manager. This can be used by any plugin wh...
QString bl_version
quint32 FW_CRC
Definition: tl_dfu.h:65
bool CapExt
Definition: tl_dfu.h:73
virtual ActionManager * actionManager() const =0
Returns the application's action manager.
for i
Definition: OPPlots.m:140
~UploaderGadgetWidget()
Class destructor, nothing needs to be destructed ATM.
QString max_code_size
virtual ConnectionManager * connectionManager() const =0
void objectUpdated(UAVObject *obj)
Signal sent whenever any field of the object is updated.
virtual Command * command(const QString &id) const =0
Returns the Command object that is known to the system under the given string id. ...
dfu_partition_label
Definition: bl_messages.h:96
Parse log file
void activateModeByWorkspaceName(const QString &id)
Status
Definition: tl_dfu.h:44
void setOperatingMode(bool upgradingBootloader, bool blankFC)
static ICore * instance()
Definition: coreimpl.cpp:46
int HW_Rev
Definition: tl_dfu.h:72
UploaderGadgetWidget(QWidget *parent=nullptr)
Class constructor, sets signal to slot connections, creates actions, creates utility classes instance...
static bool descriptionToStructure(QByteArray desc, deviceDescriptorStruct &struc)
virtual BoardManager * boardManager() const =0
IBoardType * getBoard(int type)
Find a board from it's type.
void deviceRemoved()
const char *const GCS_REVISION_STR
Definition: coreconstants.h:45
static USBMonitor * instance()
Definition: usbmonitor.cpp:176
IConnection * connection
The action manager is responsible for registration of menus and menu items and keyboard shortcuts...
Definition: actionmanager.h:47
QVector< quint32 > PartitionSizes
Definition: tl_dfu.h:71
QPointer< Core::IBoardType > board
static QString getBoardNameFromID(int id)
Definition: iboardtype.cpp:75
void(NAME)
Definition: icore.h:39
QString hw_revision
void deviceDiscovered()
unsigned char getRunState()
Definition: usbmonitor.h:54
quint16 ID
Definition: tl_dfu.h:64
bool getBoardDescriptionStruct(deviceDescriptorStruct &device)
Core::IBoardType * getBoardType()
Get the IBoardType corresponding to the connected board.
void onStepChanged(UpgradeAssistantStep step)
virtual QString shortName()
Definition: iconnection.h:71
function crc
The class Command represents an action like a menu item, tool button, or shortcut. You don't create Command objects directly, instead use {ActionManager::registerAction()} to register an action and retrieve a Command. The Command object represents the user visible action and its properties. If multiple actions are registered with the same ID (but different contexts) the returned Command is the shared one between these actions.
Definition: command.h:43
int getBoardRevision()
Get the connected board hardware revision.
bool isConnected() const
QString cpu_serial