Merged in f5soh/librepilot/update_credits (pull request #529)
[librepilot.git] / ground / gcs / src / plugins / uploader / uploadergadgetwidget.cpp
blobbfecfc154cf62ed808c8e03ea62c660735546319
1 /**
2 ******************************************************************************
4 * @file uploadergadgetwidget.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2015
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
7 * @addtogroup GCSPlugins GCS Plugins
8 * @{
9 * @addtogroup YModemUploader YModem Serial Uploader Plugin
10 * @{
11 * @brief The YModem protocol serial uploader plugin
12 *****************************************************************************/
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.
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.
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include "uploadergadgetwidget.h"
30 #include "ui_uploader.h"
32 #include <ophid/inc/ophid_usbmon.h>
34 #include "flightstatus.h"
35 #include "devicewidget.h"
36 #include "runningdevicewidget.h"
38 #include <extensionsystem/pluginmanager.h>
39 #include <coreplugin/icore.h>
40 #include <coreplugin/coreconstants.h>
41 #include <coreplugin/connectionmanager.h>
42 #include <uavtalk/telemetrymanager.h>
43 #include <uavtalk/oplinkmanager.h>
44 #include "rebootdialog.h"
46 #include <QDesktopServices>
47 #include <QMessageBox>
48 #include <QProgressBar>
49 #include <QtSerialPort/QSerialPortInfo>
50 #include <QDebug>
52 #define DFU_DEBUG true
54 const int UploaderGadgetWidget::BOARD_EVENT_TIMEOUT = 20000;
55 const int UploaderGadgetWidget::AUTOUPDATE_CLOSE_TIMEOUT = 7000;
56 const int UploaderGadgetWidget::REBOOT_TIMEOUT = 20000;
57 const int UploaderGadgetWidget::ERASE_TIMEOUT = 20000;
58 const int UploaderGadgetWidget::BOOTLOADER_TIMEOUT = 20000;
60 TimedDialog::TimedDialog(const QString &title, const QString &labelText, int timeout, QWidget *parent, Qt::WindowFlags flags) :
61 QProgressDialog(labelText, tr("Cancel"), 0, timeout, parent, flags), bar(new QProgressBar(this))
63 setWindowTitle(title);
64 setAutoReset(false);
65 // open immediately...
66 setMinimumDuration(0);
67 // setup progress bar
68 bar->setRange(0, timeout);
69 bar->setFormat(tr("Timing out in %1 seconds").arg(timeout));
70 setBar(bar);
73 void TimedDialog::perform()
75 setValue(value() + 1);
76 int remaining = bar->maximum() - bar->value();
77 if (remaining > 0) {
78 bar->setFormat(tr("Timing out in %1 seconds").arg(remaining));
79 } else {
80 bar->setFormat(tr("Timed out after %1 seconds").arg(bar->maximum()));
84 ConnectionWaiter::ConnectionWaiter(int targetDeviceCount, int timeout, QWidget *parent) : QObject(parent), eventLoop(this), timer(this), timeout(timeout), elapsed(0), targetDeviceCount(targetDeviceCount), result(ConnectionWaiter::Ok)
87 int ConnectionWaiter::exec()
89 connect(USBMonitor::instance(), SIGNAL(deviceDiscovered(USBPortInfo)), this, SLOT(deviceEvent()));
90 connect(USBMonitor::instance(), SIGNAL(deviceRemoved(USBPortInfo)), this, SLOT(deviceEvent()));
92 connect(&timer, SIGNAL(timeout()), this, SLOT(perform()));
93 timer.start(1000);
95 emit timeChanged(0);
96 eventLoop.exec();
98 return result;
101 void ConnectionWaiter::cancel()
103 quit();
104 result = ConnectionWaiter::Canceled;
107 void ConnectionWaiter::quit()
109 disconnect(USBMonitor::instance(), SIGNAL(deviceDiscovered(USBPortInfo)), this, SLOT(deviceEvent()));
110 disconnect(USBMonitor::instance(), SIGNAL(deviceRemoved(USBPortInfo)), this, SLOT(deviceEvent()));
111 timer.stop();
112 eventLoop.exit();
115 void ConnectionWaiter::perform()
117 ++elapsed;
118 emit timeChanged(elapsed);
119 int remaining = timeout - elapsed * 1000;
120 if (remaining <= 0) {
121 result = ConnectionWaiter::TimedOut;
122 quit();
126 void ConnectionWaiter::deviceEvent()
128 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() == targetDeviceCount) {
129 quit();
133 int ConnectionWaiter::openDialog(const QString &title, const QString &labelText, int targetDeviceCount, int timeout, QWidget *parent, Qt::WindowFlags flags)
135 TimedDialog dlg(title, labelText, timeout / 1000, parent, flags);
136 ConnectionWaiter waiter(targetDeviceCount, timeout, parent);
138 connect(&dlg, SIGNAL(canceled()), &waiter, SLOT(cancel()));
139 connect(&waiter, SIGNAL(timeChanged(int)), &dlg, SLOT(perform()));
140 return waiter.exec();
143 UploaderGadgetWidget::UploaderGadgetWidget(QWidget *parent) : QWidget(parent)
145 m_config = new Ui_UploaderWidget();
146 m_config->setupUi(this);
147 m_currentIAPStep = IAP_STATE_READY;
148 m_resetOnly = false;
149 m_dfu = NULL;
150 m_autoUpdateClosing = false;
152 // Listen to autopilot connection events
153 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
154 TelemetryManager *telMngr = pm->getObject<TelemetryManager>();
155 connect(telMngr, SIGNAL(connected()), this, SLOT(onAutopilotConnect()));
156 connect(telMngr, SIGNAL(disconnected()), this, SLOT(onAutopilotDisconnect()));
158 Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
159 connect(cm, SIGNAL(deviceConnected(QIODevice *)), this, SLOT(onPhysicalHWConnect()));
161 connect(m_config->haltButton, SIGNAL(clicked()), this, SLOT(systemHalt()));
162 connect(m_config->resetButton, SIGNAL(clicked()), this, SLOT(systemReset()));
163 connect(m_config->bootButton, SIGNAL(clicked()), this, SLOT(systemBoot()));
164 connect(m_config->safeBootButton, SIGNAL(clicked()), this, SLOT(systemSafeBoot()));
165 connect(m_config->eraseBootButton, SIGNAL(clicked()), this, SLOT(systemEraseBoot()));
166 connect(m_config->rescueButton, SIGNAL(clicked()), this, SLOT(systemRescue()));
168 getSerialPorts();
170 connect(m_config->autoUpdateButton, SIGNAL(clicked()), this, SLOT(startAutoUpdate()));
171 connect(m_config->autoUpdateEraseButton, SIGNAL(clicked()), this, SLOT(startAutoUpdateErase()));
172 connect(m_config->autoUpdateOkButton, SIGNAL(clicked()), this, SLOT(closeAutoUpdate()));
173 m_config->autoUpdateButton->setEnabled(autoUpdateCapable());
174 m_config->autoUpdateEraseButton->setEnabled(autoUpdateCapable());
175 m_config->autoUpdateGroupBox->setVisible(false);
177 m_config->refreshPorts->setIcon(QIcon(":uploader/images/view-refresh.svg"));
179 bootButtonsSetEnable(false);
181 connect(m_config->refreshPorts, SIGNAL(clicked()), this, SLOT(getSerialPorts()));
183 connect(m_config->pbHelp, SIGNAL(clicked()), this, SLOT(openHelp()));
184 // And check whether by any chance we are not already connected
185 if (telMngr->isConnected()) {
186 onAutopilotConnect();
190 bool sortPorts(const QSerialPortInfo &s1, const QSerialPortInfo &s2)
192 return s1.portName() < s2.portName();
196 Gets the list of serial ports
198 void UploaderGadgetWidget::getSerialPorts()
200 QStringList list;
202 // Populate the telemetry combo box:
203 m_config->telemetryLink->clear();
205 list.append(QString("USB"));
206 QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
208 // sort the list by port number (nice idea from PT_Dreamer :))
209 qSort(ports.begin(), ports.end(), sortPorts);
210 foreach(QSerialPortInfo port, ports) {
211 list.append(port.portName());
214 m_config->telemetryLink->addItems(list);
218 QString UploaderGadgetWidget::getPortDevice(const QString &friendName)
220 QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
221 foreach(QSerialPortInfo port, ports) {
222 if (port.portName() == friendName) {
223 return port.portName();
226 return "";
229 void UploaderGadgetWidget::connectSignalSlot(QWidget *widget)
231 connect(qobject_cast<DeviceWidget *>(widget), SIGNAL(uploadStarted()), this, SLOT(uploadStarted()));
232 connect(qobject_cast<DeviceWidget *>(widget), SIGNAL(uploadEnded(bool)), this, SLOT(uploadEnded(bool)));
233 connect(qobject_cast<DeviceWidget *>(widget), SIGNAL(downloadStarted()), this, SLOT(downloadStarted()));
234 connect(qobject_cast<DeviceWidget *>(widget), SIGNAL(downloadEnded(bool)), this, SLOT(downloadEnded(bool)));
237 FlightStatus *UploaderGadgetWidget::getFlightStatus()
239 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
241 Q_ASSERT(pm);
242 UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
243 Q_ASSERT(objManager);
244 FlightStatus *status = dynamic_cast<FlightStatus *>(objManager->getObject("FlightStatus"));
245 Q_ASSERT(status);
246 return status;
249 void UploaderGadgetWidget::bootButtonsSetEnable(bool enabled)
251 m_config->bootButton->setEnabled(enabled);
252 m_config->safeBootButton->setEnabled(enabled);
254 // this feature is supported only on BL revision >= 4
255 bool isBL4 = ((m_dfu != NULL) && (m_dfu->devices[0].BL_Version >= 4));
256 m_config->eraseBootButton->setEnabled(isBL4 && enabled);
259 void UploaderGadgetWidget::onPhysicalHWConnect()
261 bootButtonsSetEnable(false);
262 m_config->rescueButton->setEnabled(false);
263 m_config->telemetryLink->setEnabled(false);
267 Enables widget buttons if autopilot connected
269 void UploaderGadgetWidget::onAutopilotConnect()
271 QTimer::singleShot(1000, this, SLOT(populate()));
274 void UploaderGadgetWidget::populate()
276 m_config->haltButton->setEnabled(true);
277 m_config->resetButton->setEnabled(true);
278 bootButtonsSetEnable(false);
279 m_config->rescueButton->setEnabled(false);
280 m_config->telemetryLink->setEnabled(false);
282 // Add a very simple widget with Board model & serial number
283 // Delete all previous tabs:
284 while (m_config->systemElements->count()) {
285 QWidget *qw = m_config->systemElements->widget(0);
286 m_config->systemElements->removeTab(0);
287 delete qw;
289 RunningDeviceWidget *dw = new RunningDeviceWidget(this);
290 dw->populate();
291 m_config->systemElements->addTab(dw, tr("Connected Device"));
295 Enables widget buttons if autopilot disconnected
297 void UploaderGadgetWidget::onAutopilotDisconnect()
299 m_config->haltButton->setEnabled(false);
300 m_config->resetButton->setEnabled(false);
301 bootButtonsSetEnable(true);
302 if (m_currentIAPStep == IAP_STATE_BOOTLOADER) {
303 m_config->rescueButton->setEnabled(false);
304 m_config->telemetryLink->setEnabled(false);
305 } else {
306 m_config->rescueButton->setEnabled(true);
307 m_config->telemetryLink->setEnabled(true);
311 static void sleep(int ms)
313 QEventLoop eventLoop;
314 QTimer::singleShot(ms, &eventLoop, SLOT(quit()));
316 eventLoop.exec();
320 Tell the mainboard to go to bootloader:
321 - Send the relevant IAP commands
322 - setup callback for MoBo acknowledge
324 void UploaderGadgetWidget::goToBootloader(UAVObject *callerObj, bool success)
326 Q_UNUSED(callerObj);
328 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
329 UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
330 UAVObject *fwIAP = dynamic_cast<UAVDataObject *>(objManager->getObject("FirmwareIAPObj"));
332 switch (m_currentIAPStep) {
333 case IAP_STATE_READY:
334 m_config->haltButton->setEnabled(false);
335 getSerialPorts(); // Useful in case a new serial port appeared since the initial list,
336 // otherwise we won't find it when we stop the board.
337 // The board is running, send the 1st IAP Reset order:
338 fwIAP->getField("Command")->setValue("1122");
339 fwIAP->getField("BoardRevision")->setDouble(0);
340 fwIAP->getField("BoardType")->setDouble(0);
341 connect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
342 m_currentIAPStep = IAP_STATE_STEP_1;
343 clearLog();
344 log("IAP Step 1");
345 fwIAP->updated();
346 emit progressUpdate(JUMP_TO_BL, QVariant(1));
347 break;
348 case IAP_STATE_STEP_1:
349 if (!success) {
350 log("Oops, failure step 1");
351 log("Reset did NOT happen");
352 m_currentIAPStep = IAP_STATE_READY;
353 disconnect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
354 m_config->haltButton->setEnabled(true);
355 emit progressUpdate(FAILURE, QVariant());
356 emit bootloaderFailed();
357 break;
359 sleep(600);
360 fwIAP->getField("Command")->setValue("2233");
361 m_currentIAPStep = IAP_STATE_STEP_2;
362 log("IAP Step 2");
363 fwIAP->updated();
364 emit progressUpdate(JUMP_TO_BL, QVariant(2));
365 break;
366 case IAP_STATE_STEP_2:
367 if (!success) {
368 log("Oops, failure step 2");
369 log("Reset did NOT happen");
370 m_currentIAPStep = IAP_STATE_READY;
371 disconnect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
372 m_config->haltButton->setEnabled(true);
373 emit progressUpdate(FAILURE, QVariant());
374 emit bootloaderFailed();
375 break;
377 sleep(600);
378 fwIAP->getField("Command")->setValue("3344");
379 m_currentIAPStep = IAP_STEP_RESET;
380 log("IAP Step 3");
381 emit progressUpdate(JUMP_TO_BL, QVariant(3));
382 fwIAP->updated();
383 break;
384 case IAP_STEP_RESET:
386 m_currentIAPStep = IAP_STATE_READY;
387 if (!success) {
388 log("Oops, failure step 3");
389 log("Reset did NOT happen");
390 disconnect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
391 m_config->haltButton->setEnabled(true);
392 emit progressUpdate(FAILURE, QVariant());
393 emit bootloaderFailed();
394 break;
397 // The board is now reset: we have to disconnect telemetry
398 Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
399 QString dli = cm->getCurrentDevice().getConName();
400 QString dlj = cm->getCurrentDevice().getConName();
401 cm->disconnectDevice();
402 sleep(200);
403 // Tell connections to stop their polling threads: otherwise it will mess up DFU
404 cm->suspendPolling();
405 sleep(200);
406 log("Board Halt");
407 emit progressUpdate(JUMP_TO_BL, QVariant(4));
408 m_config->boardStatus->setText(tr("Bootloader"));
409 if (dlj.startsWith("USB")) {
410 m_config->telemetryLink->setCurrentIndex(m_config->telemetryLink->findText("USB"));
411 } else {
412 m_config->telemetryLink->setCurrentIndex(m_config->telemetryLink->findText(dli));
415 disconnect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
417 m_currentIAPStep = IAP_STATE_BOOTLOADER;
419 // Tell the mainboard to get into bootloader state:
420 log("Detecting devices, please wait a few seconds...");
421 if (!m_dfu) {
422 if (dlj.startsWith("USB")) {
423 m_dfu = new DFUObject(DFU_DEBUG, false, QString());
424 } else {
425 m_dfu = new DFUObject(DFU_DEBUG, true, getPortDevice(dli));
428 if (!m_dfu->ready()) {
429 log("Could not enter DFU mode.");
430 delete m_dfu;
431 m_dfu = NULL;
432 cm->resumePolling();
433 m_currentIAPStep = IAP_STATE_READY;
434 m_config->boardStatus->setText(tr("Bootloader?"));
435 m_config->haltButton->setEnabled(true);
436 emit progressUpdate(FAILURE, QVariant());
437 emit bootloaderFailed();
438 return;
440 m_dfu->AbortOperation();
441 if (!m_dfu->enterDFU(0)) {
442 log("Could not enter DFU mode.");
443 delete m_dfu;
444 m_dfu = NULL;
445 cm->resumePolling();
446 m_currentIAPStep = IAP_STATE_READY;
447 m_config->boardStatus->setText(tr("Bootloader?"));
448 emit progressUpdate(FAILURE, QVariant());
449 emit bootloaderFailed();
450 return;
453 sleep(500);
454 m_dfu->findDevices();
455 log(QString("Found %1 device(s).").arg(QString::number(m_dfu->numberOfDevices)));
456 if (m_dfu->numberOfDevices < 0 || m_dfu->numberOfDevices > 3) {
457 log("Inconsistent number of devices! Aborting");
458 delete m_dfu;
459 m_dfu = NULL;
460 cm->resumePolling();
461 emit progressUpdate(FAILURE, QVariant());
462 emit bootloaderFailed();
463 return;
465 // Delete all previous tabs:
466 while (m_config->systemElements->count()) {
467 QWidget *qw = m_config->systemElements->widget(0);
468 m_config->systemElements->removeTab(0);
469 delete qw;
471 for (int i = 0; i < m_dfu->numberOfDevices; i++) {
472 DeviceWidget *dw = new DeviceWidget(this);
473 connectSignalSlot(dw);
474 dw->setDeviceID(i);
475 dw->setDfu(m_dfu);
476 dw->populate();
477 m_config->systemElements->addTab(dw, tr("Device") + QString::number(i));
480 QApplication::processEvents();
482 // Need to re-enable in case we were not connected
483 bootButtonsSetEnable(true);
484 emit progressUpdate(JUMP_TO_BL, QVariant(5));
485 emit bootloaderSuccess();
487 if (m_resetOnly) {
488 m_resetOnly = false;
489 QThread::msleep(3500);
490 systemBoot();
491 break;
494 break;
495 case IAP_STATE_BOOTLOADER:
496 // We should never end up here anyway.
497 emit progressUpdate(FAILURE, QVariant());
498 emit bootloaderFailed();
499 break;
503 void UploaderGadgetWidget::systemHalt()
505 // The board can not be halted when in armed state.
506 // If board is armed, or arming. Show message with notice.
507 FlightStatus *status = getFlightStatus();
509 if (status->getArmed() == FlightStatus::ARMED_DISARMED) {
510 goToBootloader();
511 } else {
512 cannotHaltMessageBox();
517 Tell the mainboard to reset:
518 - Send the relevant IAP commands
519 - setup callback for MoBo acknowledge
521 void UploaderGadgetWidget::systemReset()
523 FlightStatus *status = getFlightStatus();
525 // The board can not be reset when in armed state.
526 // If board is armed, or arming. Show message with notice.
527 if (status->getArmed() == FlightStatus::ARMED_DISARMED) {
528 m_resetOnly = true;
529 if (m_dfu) {
530 delete m_dfu;
531 m_dfu = NULL;
533 clearLog();
534 log("Board Reset initiated.");
535 goToBootloader();
536 } else {
537 cannotResetMessageBox();
541 void UploaderGadgetWidget::systemBoot()
543 commonSystemBoot(false, false);
546 void UploaderGadgetWidget::systemSafeBoot()
548 commonSystemBoot(true, false);
551 void UploaderGadgetWidget::systemEraseBoot()
553 switch (confirmEraseSettingsMessageBox()) {
554 case QMessageBox::Ok:
555 commonSystemBoot(true, true);
556 break;
557 case QMessageBox::Help:
558 QDesktopServices::openUrl(QUrl(QString(WIKI_URL_ROOT) + QString("Erase+board+settings"),
559 QUrl::StrictMode));
560 break;
564 void UploaderGadgetWidget::rebootWithDialog()
566 RebootDialog dialog(this);
568 dialog.exec();
571 void UploaderGadgetWidget::systemReboot()
573 ResultEventLoop eventLoop;
575 connect(this, SIGNAL(bootloaderSuccess()), &eventLoop, SLOT(success()));
576 connect(this, SIGNAL(bootloaderFailed()), &eventLoop, SLOT(fail()));
578 goToBootloader();
580 if (eventLoop.run(REBOOT_TIMEOUT) != 0) {
581 emit progressUpdate(FAILURE, QVariant());
582 return;
585 disconnect(this, SIGNAL(bootloaderSuccess()), &eventLoop, SLOT(success()));
586 disconnect(this, SIGNAL(bootloaderFailed()), &eventLoop, SLOT(fail()));
588 commonSystemBoot(false, false);
590 ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
591 Q_ASSERT(pluginManager);
592 TelemetryManager *telemetryManager = pluginManager->getObject<TelemetryManager>();
593 Q_ASSERT(telemetryManager);
595 if (!telemetryManager->isConnected()) {
596 progressUpdate(BOOTING, QVariant());
597 ResultEventLoop eventLoop;
599 connect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
601 if (eventLoop.run(REBOOT_TIMEOUT) != 0) {
602 emit progressUpdate(FAILURE, QVariant());
603 return;
606 disconnect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
609 emit progressUpdate(SUCCESS, QVariant());
613 * Tells the system to boot (from Bootloader state)
614 * @param[in] safeboot Indicates whether the firmware should use the stock HWSettings
616 void UploaderGadgetWidget::commonSystemBoot(bool safeboot, bool erase)
618 clearLog();
619 bootButtonsSetEnable(false);
620 // Suspend telemety & polling in case it is not done yet
621 Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
622 cm->disconnectDevice();
623 cm->suspendPolling();
625 QString devName = m_config->telemetryLink->currentText();
626 log("Attempting to boot the system through " + devName + ".");
627 repaint();
629 if (!m_dfu) {
630 if (devName == "USB") {
631 m_dfu = new DFUObject(DFU_DEBUG, false, QString());
632 } else {
633 m_dfu = new DFUObject(DFU_DEBUG, true, getPortDevice(devName));
636 m_dfu->AbortOperation();
637 if (!m_dfu->enterDFU(0)) {
638 log("Could not enter DFU mode.");
639 delete m_dfu;
640 m_dfu = NULL;
641 bootButtonsSetEnable(true);
642 m_config->rescueButton->setEnabled(true); // Boot not possible, maybe Rescue OK?
643 emit bootFailed();
644 return;
646 log("Booting system...");
647 m_dfu->JumpToApp(safeboot, erase);
648 // Restart the polling thread
649 cm->resumePolling();
650 m_config->rescueButton->setEnabled(true);
651 m_config->telemetryLink->setEnabled(true);
652 m_config->boardStatus->setText(tr("Running"));
653 if (m_currentIAPStep == IAP_STATE_BOOTLOADER) {
654 // Freeze the tabs, they are not useful anymore and their buttons
655 // will cause segfaults or weird stuff if we use them.
656 for (int i = 0; i < m_config->systemElements->count(); i++) {
657 // OP-682 arriving here too "early" (before the devices are refreshed) was leading to a crash
658 // OP-682 the crash was due to an unchecked cast in the line below that would cast a RunningDeviceGadget to a DeviceGadget
659 DeviceWidget *qw = dynamic_cast<DeviceWidget *>(m_config->systemElements->widget(i));
660 if (qw) {
661 // OP-682 fixed a second crash by disabling *all* buttons in the device widget
662 // disabling the buttons is only half of the solution as even if the buttons are enabled
663 // the app should not crash
664 qw->freeze();
668 m_currentIAPStep = IAP_STATE_READY;
669 log("You can now reconnect telemetry...");
670 delete m_dfu; // Frees up the USB/Serial port too
671 emit bootSuccess();
672 m_dfu = NULL;
675 bool UploaderGadgetWidget::autoUpdateCapable()
677 return QDir(":/firmware").exists();
680 bool UploaderGadgetWidget::autoUpdate(bool erase)
682 ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
684 Q_ASSERT(pluginManager);
686 OPLinkManager *opLinkManager = pluginManager->getObject<OPLinkManager>();
687 Q_ASSERT(opLinkManager);
689 if (opLinkManager->isConnected() &&
690 opLinkManager->opLinkType() == OPLinkManager::OPLINK_MINI) {
691 emit progressUpdate(FAILURE, QVariant(tr("To upgrade the OPLinkMini board please disconnect it from the USB port, "
692 "press the Upgrade button again and follow instructions on screen.")));
693 emit autoUpdateFailed();
694 return false;
697 TelemetryManager *telemetryManager = pluginManager->getObject<TelemetryManager>();
698 Q_ASSERT(telemetryManager);
700 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() > 0 &&
701 telemetryManager->connectionState() != TelemetryManager::TELEMETRY_CONNECTED) {
702 // Wait for the board to completely connect
703 ResultEventLoop eventLoop;
704 connect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
706 if (telemetryManager->connectionState() != TelemetryManager::TELEMETRY_CONNECTED
707 && eventLoop.run(REBOOT_TIMEOUT) != 0) {
708 emit progressUpdate(FAILURE, QVariant(tr("Timed out while waiting for a board to be fully connected!")));
709 emit autoUpdateFailed();
710 return false;
713 disconnect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
715 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() == 0) {
716 ConnectionWaiter waiter(1, BOARD_EVENT_TIMEOUT);
717 connect(&waiter, SIGNAL(timeChanged(int)), this, SLOT(autoUpdateConnectProgress(int)));
718 if (waiter.exec() == ConnectionWaiter::TimedOut) {
719 emit progressUpdate(FAILURE, QVariant(tr("Timed out while waiting for a board to be connected!")));
720 emit autoUpdateFailed();
721 return false;
723 } else {
724 ResultEventLoop eventLoop;
725 connect(this, SIGNAL(bootloaderSuccess()), &eventLoop, SLOT(success()));
726 connect(this, SIGNAL(bootloaderFailed()), &eventLoop, SLOT(fail()));
728 goToBootloader();
730 if (eventLoop.run(BOOTLOADER_TIMEOUT) != 0) {
731 emit progressUpdate(FAILURE, QVariant(tr("Failed to enter bootloader mode.")));
732 emit autoUpdateFailed();
733 return false;
736 disconnect(this, SIGNAL(bootloaderSuccess()), &eventLoop, SLOT(success()));
737 disconnect(this, SIGNAL(bootloaderFailed()), &eventLoop, SLOT(fail()));
740 if (m_dfu) {
741 delete m_dfu;
742 m_dfu = NULL;
745 Core::ConnectionManager *connectionManager = Core::ICore::instance()->connectionManager();
746 m_dfu = new DFUObject(DFU_DEBUG, false, QString());
747 m_dfu->AbortOperation();
748 emit progressUpdate(JUMP_TO_BL, QVariant());
750 if (!m_dfu->enterDFU(0) || !m_dfu->findDevices() ||
751 (m_dfu->numberOfDevices != 1) || m_dfu->numberOfDevices > 5) {
752 delete m_dfu;
753 m_dfu = NULL;
754 connectionManager->resumePolling();
755 emit progressUpdate(FAILURE, QVariant(tr("Failed to enter bootloader mode.")));
756 emit autoUpdateFailed();
757 return false;
760 QString filename;
761 emit progressUpdate(LOADING_FW, QVariant());
762 switch (m_dfu->devices[0].ID) {
763 case 0x0301:
764 filename = "fw_oplinkmini";
765 break;
766 case 0x0401:
767 case 0x0402:
768 filename = "fw_coptercontrol";
769 break;
770 case 0x0501:
771 filename = "fw_osd";
772 break;
773 case 0x0902:
774 filename = "fw_revoproto";
775 break;
776 case 0x0903:
777 filename = "fw_revolution";
778 break;
779 case 0x0904:
780 filename = "fw_discoveryf4bare";
781 break;
782 case 0x0905:
783 filename = "fw_revonano";
784 break;
785 case 0x9201:
786 filename = "fw_sparky2";
787 break;
788 case 0x1001:
789 filename = "fw_spracingf3";
790 break;
791 case 0x1002:
792 filename = "fw_spracingf3evo";
793 break;
794 case 0x1003:
795 filename = "fw_nucleof303re";
796 break;
797 case 0x1005:
798 filename = "fw_pikoblx";
799 break;
800 case 0x1006:
801 filename = "fw_tinyfish";
802 break;
803 default:
804 emit progressUpdate(FAILURE, QVariant(tr("Unknown board id '0x%1'").arg(QString::number(m_dfu->devices[0].ID, 16))));
805 emit autoUpdateFailed();
806 return false;
808 filename = ":/firmware/" + filename + ".opfw";
809 QByteArray firmware;
810 if (!QFile::exists(filename)) {
811 emit progressUpdate(FAILURE, QVariant(tr("Firmware image not found.")));
812 emit autoUpdateFailed();
813 return false;
815 QFile file(filename);
816 if (!file.open(QIODevice::ReadOnly)) {
817 emit progressUpdate(FAILURE, QVariant(tr("Could not open firmware image for reading.")));
818 emit autoUpdateFailed();
819 return false;
821 firmware = file.readAll();
822 QEventLoop eventLoop2;
823 connect(m_dfu, SIGNAL(progressUpdated(int)), this, SLOT(autoUpdateFlashProgress(int)));
824 connect(m_dfu, SIGNAL(uploadFinished(DFU::Status)), &eventLoop2, SLOT(quit()));
825 emit progressUpdate(UPLOADING_FW, QVariant());
826 if (!m_dfu->enterDFU(0)) {
827 emit progressUpdate(FAILURE, QVariant(tr("Could not enter direct firmware upload mode.")));
828 emit autoUpdateFailed();
829 return false;
831 m_dfu->AbortOperation();
832 if (!m_dfu->UploadFirmware(filename, false, 0)) {
833 emit progressUpdate(FAILURE, QVariant(tr("Firmware upload failed.")));
834 emit autoUpdateFailed();
835 return false;
837 eventLoop2.exec();
838 QByteArray desc = firmware.right(100);
839 emit progressUpdate(UPLOADING_DESC, QVariant());
840 if (m_dfu->UploadDescription(desc) != DFU::Last_operation_Success) {
841 emit progressUpdate(FAILURE, QVariant(tr("Failed to upload firmware description.")));
842 emit autoUpdateFailed();
843 return false;
846 commonSystemBoot(false, erase);
848 // Wait for board to connect to GCS again after boot and erase
849 // For older board like CC3D this can take some time
850 // Theres a special case with OPLink
851 if (!telemetryManager->isConnected() && !opLinkManager->isConnected()) {
852 progressUpdate(erase ? BOOTING_AND_ERASING : BOOTING, QVariant());
853 ResultEventLoop eventLoop;
854 connect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
855 connect(opLinkManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
857 if (eventLoop.run(REBOOT_TIMEOUT + (erase ? ERASE_TIMEOUT : 0)) != 0) {
858 emit progressUpdate(FAILURE, QVariant(tr("Timed out while booting.")));
859 emit autoUpdateFailed();
860 return false;
863 disconnect(opLinkManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
864 disconnect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
867 emit progressUpdate(SUCCESS, QVariant());
868 emit autoUpdateSuccess();
869 return true;
872 void UploaderGadgetWidget::autoUpdateDisconnectProgress(int value)
874 emit progressUpdate(WAITING_DISCONNECT, value);
877 void UploaderGadgetWidget::autoUpdateConnectProgress(int value)
879 emit progressUpdate(WAITING_CONNECT, value);
882 void UploaderGadgetWidget::autoUpdateFlashProgress(int value)
884 emit progressUpdate(UPLOADING_FW, value);
888 Attempt a guided procedure to put both boards in BL mode when
889 the system is not bootable
891 void UploaderGadgetWidget::systemRescue()
893 Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
895 cm->disconnectDevice();
896 // stop the polling thread: otherwise it will mess up DFU
897 cm->suspendPolling();
899 // Delete all previous tabs:
900 while (m_config->systemElements->count()) {
901 QWidget *qw = m_config->systemElements->widget(0);
902 m_config->systemElements->removeTab(0);
903 delete qw;
906 // Existing DFU objects will have a handle over USB and will
907 // disturb everything for the rescue process:
908 if (m_dfu) {
909 delete m_dfu;
910 m_dfu = NULL;
913 // Avoid users pressing Rescue twice.
914 m_config->rescueButton->setEnabled(false);
916 // Now we're good to go
917 clearLog();
918 log("**********************************************************");
919 log("** Follow those instructions to attempt a system rescue **");
920 log("**********************************************************");
921 log("You will be prompted to first connect USB, then system power");
923 // Check if boards are connected and, if yes, prompt user to disconnect them all
924 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() > 0) {
925 QString labelText = QString("<p align=\"left\">%1</p>").arg(tr("Please disconnect your board."));
926 int result = ConnectionWaiter::openDialog(tr("System Rescue"), labelText, 0, BOARD_EVENT_TIMEOUT, this);
927 switch (result) {
928 case ConnectionWaiter::Canceled:
929 m_config->rescueButton->setEnabled(true);
930 return;
932 case ConnectionWaiter::TimedOut:
933 QMessageBox::warning(this, tr("System Rescue"), tr("Timed out while waiting for all boards to be disconnected!"));
934 m_config->rescueButton->setEnabled(true);
935 return;
939 // Now prompt user to connect board
940 QString labelText = QString("<p align=\"left\">%1<br>%2</p>").arg(tr("Please connect your board.")).arg(tr("Board must be connected to the USB port!"));
941 int result = ConnectionWaiter::openDialog(tr("System Rescue"), labelText, 1, BOARD_EVENT_TIMEOUT, this);
942 switch (result) {
943 case ConnectionWaiter::Canceled:
944 m_config->rescueButton->setEnabled(true);
945 return;
947 case ConnectionWaiter::TimedOut:
948 QMessageBox::warning(this, tr("System Rescue"), tr("Timed out while waiting for a board to be connected!"));
949 m_config->rescueButton->setEnabled(true);
950 return;
953 log("Detecting first board...");
954 m_dfu = new DFUObject(DFU_DEBUG, false, QString());
955 m_dfu->AbortOperation();
956 if (!m_dfu->enterDFU(0)) {
957 log("Could not enter DFU mode.");
958 delete m_dfu;
959 m_dfu = NULL;
960 cm->resumePolling();
961 m_config->rescueButton->setEnabled(true);
962 return;
964 if (!m_dfu->findDevices() || (m_dfu->numberOfDevices != 1)) {
965 log("Could not detect a board, aborting!");
966 delete m_dfu;
967 m_dfu = NULL;
968 cm->resumePolling();
969 m_config->rescueButton->setEnabled(true);
970 return;
972 log(QString("Found %1 device(s).").arg(m_dfu->numberOfDevices));
974 if (m_dfu->numberOfDevices > 5) {
975 log("Inconsistent number of devices, aborting!");
976 delete m_dfu;
977 m_dfu = NULL;
978 cm->resumePolling();
979 m_config->rescueButton->setEnabled(true);
980 return;
982 for (int i = 0; i < m_dfu->numberOfDevices; i++) {
983 DeviceWidget *dw = new DeviceWidget(this);
984 connectSignalSlot(dw);
985 dw->setDeviceID(i);
986 dw->setDfu(m_dfu);
987 dw->populate();
988 m_config->systemElements->addTab(dw, tr("Device") + QString::number(i));
990 m_config->haltButton->setEnabled(false);
991 m_config->resetButton->setEnabled(false);
992 bootButtonsSetEnable(true);
993 m_config->rescueButton->setEnabled(false);
995 // So that we can boot from the GUI afterwards.
996 m_currentIAPStep = IAP_STATE_BOOTLOADER;
999 void UploaderGadgetWidget::uploadStarted()
1001 m_config->haltButton->setEnabled(false);
1002 bootButtonsSetEnable(false);
1003 m_config->resetButton->setEnabled(false);
1004 m_config->rescueButton->setEnabled(false);
1007 void UploaderGadgetWidget::uploadEnded(bool succeed)
1009 Q_UNUSED(succeed);
1010 // device is halted so no halt
1011 m_config->haltButton->setEnabled(false);
1012 bootButtonsSetEnable(true);
1013 // device is halted so no reset
1014 m_config->resetButton->setEnabled(false);
1015 m_config->rescueButton->setEnabled(true);
1018 void UploaderGadgetWidget::downloadStarted()
1020 m_config->haltButton->setEnabled(false);
1021 bootButtonsSetEnable(false);
1022 m_config->resetButton->setEnabled(false);
1023 m_config->rescueButton->setEnabled(false);
1026 void UploaderGadgetWidget::downloadEnded(bool succeed)
1028 Q_UNUSED(succeed);
1029 // device is halted so no halt
1030 m_config->haltButton->setEnabled(false);
1031 bootButtonsSetEnable(true);
1032 // device is halted so no reset
1033 m_config->resetButton->setEnabled(false);
1034 m_config->rescueButton->setEnabled(true);
1037 void UploaderGadgetWidget::startAutoUpdate()
1039 startAutoUpdate(false);
1042 void UploaderGadgetWidget::startAutoUpdateErase()
1044 startAutoUpdate(true);
1046 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
1047 UAVObjectUtilManager *utilMngr = pm->getObject<UAVObjectUtilManager>();
1048 int id = utilMngr->getBoardModel();
1050 // reset if Nano
1051 if (id == 0x0905) {
1052 systemReset();
1056 void UploaderGadgetWidget::startAutoUpdate(bool erase)
1058 m_config->autoUpdateProgressBar->setValue(0);
1059 autoUpdateStatus(uploader::JUMP_TO_BL, QVariant());
1060 m_config->buttonFrame->setEnabled(false);
1061 m_config->splitter->setEnabled(false);
1062 m_config->autoUpdateGroupBox->setVisible(true);
1063 m_config->autoUpdateOkButton->setEnabled(false);
1065 connect(this, SIGNAL(progressUpdate(uploader::ProgressStep, QVariant)), this, SLOT(autoUpdateStatus(uploader::ProgressStep, QVariant)));
1066 autoUpdate(erase);
1069 void UploaderGadgetWidget::finishAutoUpdate()
1071 disconnect(this, SIGNAL(progressUpdate(uploader::ProgressStep, QVariant)), this, SLOT(autoUpdateStatus(uploader::ProgressStep, QVariant)));
1072 m_config->autoUpdateOkButton->setEnabled(true);
1073 m_autoUpdateClosing = true;
1075 // wait a bit and "close" auto update
1076 QTimer::singleShot(AUTOUPDATE_CLOSE_TIMEOUT, this, SLOT(closeAutoUpdate()));
1079 void UploaderGadgetWidget::closeAutoUpdate()
1081 if (m_autoUpdateClosing) {
1082 m_config->autoUpdateGroupBox->setVisible(false);
1083 m_config->buttonFrame->setEnabled(true);
1084 m_config->splitter->setEnabled(true);
1086 m_autoUpdateClosing = false;
1089 void UploaderGadgetWidget::autoUpdateStatus(uploader::ProgressStep status, QVariant value)
1091 QString msg;
1092 int remaining;
1094 switch (status) {
1095 case uploader::WAITING_DISCONNECT:
1096 m_config->autoUpdateLabel->setText(tr("Waiting for all boards to be disconnected from USB."));
1097 m_config->autoUpdateProgressBar->setMaximum(BOARD_EVENT_TIMEOUT / 1000);
1098 m_config->autoUpdateProgressBar->setValue(value.toInt());
1099 remaining = m_config->autoUpdateProgressBar->maximum() - m_config->autoUpdateProgressBar->value();
1100 m_config->autoUpdateProgressBar->setFormat(tr("Timing out in %1 seconds").arg(remaining));
1101 break;
1102 case uploader::WAITING_CONNECT:
1103 m_config->autoUpdateLabel->setText(tr("Please connect the board to the USB port."));
1104 m_config->autoUpdateProgressBar->setMaximum(BOARD_EVENT_TIMEOUT / 1000);
1105 m_config->autoUpdateProgressBar->setValue(value.toInt());
1106 remaining = m_config->autoUpdateProgressBar->maximum() - m_config->autoUpdateProgressBar->value();
1107 m_config->autoUpdateProgressBar->setFormat(tr("Timing out in %1 seconds").arg(remaining));
1108 break;
1109 case uploader::JUMP_TO_BL:
1110 m_config->autoUpdateLabel->setText(tr("Bringing the board into boot loader mode. Please wait."));
1111 m_config->autoUpdateProgressBar->setFormat(tr("Step %1").arg(value.toInt()));
1112 m_config->autoUpdateProgressBar->setMaximum(5);
1113 m_config->autoUpdateProgressBar->setValue(value.toInt());
1114 break;
1115 case uploader::LOADING_FW:
1116 m_config->autoUpdateLabel->setText(tr("Preparing to upload firmware to the board."));
1117 break;
1118 case uploader::UPLOADING_FW:
1119 m_config->autoUpdateLabel->setText(tr("Uploading firmware to the board."));
1120 m_config->autoUpdateProgressBar->setFormat("%p%");
1121 m_config->autoUpdateProgressBar->setMaximum(100);
1122 m_config->autoUpdateProgressBar->setValue(value.toInt());
1123 break;
1124 case uploader::UPLOADING_DESC:
1125 m_config->autoUpdateLabel->setText(tr("Uploading description of the new firmware to the board."));
1126 break;
1127 case uploader::BOOTING:
1128 m_config->autoUpdateLabel->setText(tr("Rebooting the board. Please wait."));
1129 break;
1130 case uploader::BOOTING_AND_ERASING:
1131 m_config->autoUpdateLabel->setText(tr("Rebooting and erasing the board. Please wait."));
1132 break;
1133 case uploader::SUCCESS:
1134 m_config->autoUpdateProgressBar->setValue(m_config->autoUpdateProgressBar->maximum());
1135 msg = tr("Board was updated successfully. Press OK to finish.");
1136 m_config->autoUpdateLabel->setText(QString("<font color='green'>%1</font>").arg(msg));
1137 finishAutoUpdate();
1138 break;
1139 case uploader::FAILURE:
1140 msg = value.toString();
1141 if (msg.isEmpty()) {
1142 msg = tr("Something went wrong.");
1144 msg += tr(" Press OK to finish, you will have to manually upgrade the board.");
1146 m_config->autoUpdateLabel->setText(QString("<font color='red'>%1</font>").arg(msg));
1147 finishAutoUpdate();
1148 break;
1153 Update log entry
1155 void UploaderGadgetWidget::log(QString str)
1157 qDebug() << "UploaderGadgetWidget -" << str;
1158 m_config->textBrowser->append(str);
1161 void UploaderGadgetWidget::clearLog()
1163 m_config->textBrowser->clear();
1167 * Remove all the device widgets...
1169 UploaderGadgetWidget::~UploaderGadgetWidget()
1171 while (m_config->systemElements->count()) {
1172 QWidget *qw = m_config->systemElements->widget(0);
1173 m_config->systemElements->removeTab(0);
1174 delete qw;
1175 qw = 0;
1179 void UploaderGadgetWidget::openHelp()
1181 QDesktopServices::openUrl(QUrl(QString(WIKI_URL_ROOT) + QString("Firmware+Tab"),
1182 QUrl::StrictMode));
1185 int UploaderGadgetWidget::confirmEraseSettingsMessageBox()
1187 QMessageBox msgBox(this);
1189 msgBox.setWindowTitle(tr("Confirm Settings Erase?"));
1190 msgBox.setIcon(QMessageBox::Question);
1191 msgBox.setText(tr("Do you want to erase all settings from the board?"));
1192 msgBox.setInformativeText(tr("Settings cannot be recovered after this operation.\nThe board will be restarted and all settings erased."));
1193 msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel | QMessageBox::Help);
1194 return msgBox.exec();
1197 int UploaderGadgetWidget::cannotHaltMessageBox()
1199 QMessageBox msgBox(this);
1201 msgBox.setWindowTitle(tr("Cannot Halt Board!"));
1202 msgBox.setIcon(QMessageBox::Warning);
1203 msgBox.setText(tr("The controller board is armed and can not be halted."));
1204 msgBox.setInformativeText(tr("Please make sure the board is not armed and then press Halt again to proceed or use Rescue to force a firmware upgrade."));
1205 msgBox.setStandardButtons(QMessageBox::Ok);
1206 return msgBox.exec();
1209 int UploaderGadgetWidget::cannotResetMessageBox()
1211 QMessageBox msgBox(this);
1213 msgBox.setWindowTitle(tr("Cannot Reset Board!"));
1214 msgBox.setIcon(QMessageBox::Warning);
1215 msgBox.setText(tr("The controller board is armed and can not be reset."));
1216 msgBox.setInformativeText(tr("Please make sure the board is not armed and then press reset again to proceed or power cycle to force a board reset."));
1217 msgBox.setStandardButtons(QMessageBox::Ok);
1218 return msgBox.exec();
1222 int ResultEventLoop::run(int millisTimout)
1224 m_timer.singleShot(millisTimout, this, SLOT(fail()));
1225 return exec();
1228 void ResultEventLoop::success()
1230 m_timer.stop();
1231 exit(0);
1234 void ResultEventLoop::fail()
1236 m_timer.stop();
1237 exit(-1);