OP-1900 have path_progress updated correctly for leg_remaining and error_below end...
[librepilot.git] / ground / openpilotgcs / src / plugins / uploader / uploadergadgetwidget.cpp
blobe218704b15491eb55c006f819e880b0a8b21e7bd
1 /**
2 ******************************************************************************
4 * @file uploadergadgetwidget.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * @addtogroup GCSPlugins GCS Plugins
7 * @{
8 * @addtogroup YModemUploader YModem Serial Uploader Plugin
9 * @{
10 * @brief The YModem protocol serial uploader plugin
11 *****************************************************************************/
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 * for more details.
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #include "uploadergadgetwidget.h"
29 #include "flightstatus.h"
30 #include "oplinkstatus.h"
31 #include "delay.h"
32 #include "devicewidget.h"
33 #include "runningdevicewidget.h"
35 #include <extensionsystem/pluginmanager.h>
36 #include <coreplugin/icore.h>
37 #include <coreplugin/coreconstants.h>
38 #include <coreplugin/connectionmanager.h>
39 #include <uavtalk/telemetrymanager.h>
41 #include <QDesktopServices>
42 #include <QMessageBox>
43 #include <QProgressBar>
44 #include <QDebug>
45 #include "rebootdialog.h"
47 #define DFU_DEBUG true
49 const int UploaderGadgetWidget::BOARD_EVENT_TIMEOUT = 20000;
50 const int UploaderGadgetWidget::AUTOUPDATE_CLOSE_TIMEOUT = 7000;
51 const int UploaderGadgetWidget::REBOOT_TIMEOUT = 20000;
52 const int UploaderGadgetWidget::ERASE_TIMEOUT = 20000;
53 const int UploaderGadgetWidget::BOOTLOADER_TIMEOUT = 20000;
55 TimedDialog::TimedDialog(const QString &title, const QString &labelText, int timeout, QWidget *parent, Qt::WindowFlags flags) :
56 QProgressDialog(labelText, tr("Cancel"), 0, timeout, parent, flags), bar(new QProgressBar(this))
58 setWindowTitle(title);
59 setAutoReset(false);
60 // open immediately...
61 setMinimumDuration(0);
62 // setup progress bar
63 bar->setRange(0, timeout);
64 bar->setFormat(tr("Timing out in %1 seconds").arg(timeout));
65 setBar(bar);
68 void TimedDialog::perform()
70 setValue(value() + 1);
71 int remaining = bar->maximum() - bar->value();
72 if (remaining > 0) {
73 bar->setFormat(tr("Timing out in %1 seconds").arg(remaining));
74 } else {
75 bar->setFormat(tr("Timed out after %1 seconds").arg(bar->maximum()));
79 ConnectionWaiter::ConnectionWaiter(int targetDeviceCount, int timeout, QWidget *parent) : QObject(parent), eventLoop(this), timer(this), timeout(timeout), elapsed(0), targetDeviceCount(targetDeviceCount), result(ConnectionWaiter::Ok)
82 int ConnectionWaiter::exec()
84 connect(USBMonitor::instance(), SIGNAL(deviceDiscovered(USBPortInfo)), this, SLOT(deviceEvent()));
85 connect(USBMonitor::instance(), SIGNAL(deviceRemoved(USBPortInfo)), this, SLOT(deviceEvent()));
87 connect(&timer, SIGNAL(timeout()), this, SLOT(perform()));
88 timer.start(1000);
90 emit timeChanged(0);
91 eventLoop.exec();
93 return result;
96 void ConnectionWaiter::cancel()
98 quit();
99 result = ConnectionWaiter::Canceled;
102 void ConnectionWaiter::quit()
104 disconnect(USBMonitor::instance(), SIGNAL(deviceDiscovered(USBPortInfo)), this, SLOT(deviceEvent()));
105 disconnect(USBMonitor::instance(), SIGNAL(deviceRemoved(USBPortInfo)), this, SLOT(deviceEvent()));
106 timer.stop();
107 eventLoop.exit();
110 void ConnectionWaiter::perform()
112 ++elapsed;
113 emit timeChanged(elapsed);
114 int remaining = timeout - elapsed * 1000;
115 if (remaining <= 0) {
116 result = ConnectionWaiter::TimedOut;
117 quit();
121 void ConnectionWaiter::deviceEvent()
123 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() == targetDeviceCount) {
124 quit();
128 int ConnectionWaiter::openDialog(const QString &title, const QString &labelText, int targetDeviceCount, int timeout, QWidget *parent, Qt::WindowFlags flags)
130 TimedDialog dlg(title, labelText, timeout / 1000, parent, flags);
131 ConnectionWaiter waiter(targetDeviceCount, timeout, parent);
133 connect(&dlg, SIGNAL(canceled()), &waiter, SLOT(cancel()));
134 connect(&waiter, SIGNAL(timeChanged(int)), &dlg, SLOT(perform()));
135 return waiter.exec();
138 UploaderGadgetWidget::UploaderGadgetWidget(QWidget *parent) : QWidget(parent)
140 m_config = new Ui_UploaderWidget();
141 m_config->setupUi(this);
142 m_currentIAPStep = IAP_STATE_READY;
143 m_resetOnly = false;
144 m_dfu = NULL;
145 m_autoUpdateClosing = false;
147 // Listen to autopilot connection events
148 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
149 TelemetryManager *telMngr = pm->getObject<TelemetryManager>();
150 connect(telMngr, SIGNAL(connected()), this, SLOT(onAutopilotConnect()));
151 connect(telMngr, SIGNAL(disconnected()), this, SLOT(onAutopilotDisconnect()));
153 Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
154 connect(cm, SIGNAL(deviceConnected(QIODevice *)), this, SLOT(onPhysicalHWConnect()));
156 connect(m_config->haltButton, SIGNAL(clicked()), this, SLOT(systemHalt()));
157 connect(m_config->resetButton, SIGNAL(clicked()), this, SLOT(systemReset()));
158 connect(m_config->bootButton, SIGNAL(clicked()), this, SLOT(systemBoot()));
159 connect(m_config->safeBootButton, SIGNAL(clicked()), this, SLOT(systemSafeBoot()));
160 connect(m_config->eraseBootButton, SIGNAL(clicked()), this, SLOT(systemEraseBoot()));
161 connect(m_config->rescueButton, SIGNAL(clicked()), this, SLOT(systemRescue()));
163 getSerialPorts();
165 connect(m_config->autoUpdateButton, SIGNAL(clicked()), this, SLOT(startAutoUpdate()));
166 connect(m_config->autoUpdateEraseButton, SIGNAL(clicked()), this, SLOT(startAutoUpdateErase()));
167 connect(m_config->autoUpdateOkButton, SIGNAL(clicked()), this, SLOT(closeAutoUpdate()));
168 m_config->autoUpdateButton->setEnabled(autoUpdateCapable());
169 m_config->autoUpdateEraseButton->setEnabled(autoUpdateCapable());
170 m_config->autoUpdateGroupBox->setVisible(false);
172 m_config->refreshPorts->setIcon(QIcon(":uploader/images/view-refresh.svg"));
174 bootButtonsSetEnable(false);
176 connect(m_config->refreshPorts, SIGNAL(clicked()), this, SLOT(getSerialPorts()));
178 connect(m_config->pbHelp, SIGNAL(clicked()), this, SLOT(openHelp()));
179 // And check whether by any chance we are not already connected
180 if (telMngr->isConnected()) {
181 onAutopilotConnect();
185 bool sortPorts(const QSerialPortInfo &s1, const QSerialPortInfo &s2)
187 return s1.portName() < s2.portName();
191 Gets the list of serial ports
193 void UploaderGadgetWidget::getSerialPorts()
195 QStringList list;
197 // Populate the telemetry combo box:
198 m_config->telemetryLink->clear();
200 list.append(QString("USB"));
201 QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
203 // sort the list by port number (nice idea from PT_Dreamer :))
204 qSort(ports.begin(), ports.end(), sortPorts);
205 foreach(QSerialPortInfo port, ports) {
206 list.append(port.portName());
209 m_config->telemetryLink->addItems(list);
213 QString UploaderGadgetWidget::getPortDevice(const QString &friendName)
215 QList<QSerialPortInfo> ports = QSerialPortInfo::availablePorts();
216 foreach(QSerialPortInfo port, ports) {
217 if (port.portName() == friendName) {
218 return port.portName();
221 return "";
224 void UploaderGadgetWidget::connectSignalSlot(QWidget *widget)
226 connect(qobject_cast<DeviceWidget *>(widget), SIGNAL(uploadStarted()), this, SLOT(uploadStarted()));
227 connect(qobject_cast<DeviceWidget *>(widget), SIGNAL(uploadEnded(bool)), this, SLOT(uploadEnded(bool)));
228 connect(qobject_cast<DeviceWidget *>(widget), SIGNAL(downloadStarted()), this, SLOT(downloadStarted()));
229 connect(qobject_cast<DeviceWidget *>(widget), SIGNAL(downloadEnded(bool)), this, SLOT(downloadEnded(bool)));
232 FlightStatus *UploaderGadgetWidget::getFlightStatus()
234 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
236 Q_ASSERT(pm);
237 UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
238 Q_ASSERT(objManager);
239 FlightStatus *status = dynamic_cast<FlightStatus *>(objManager->getObject("FlightStatus"));
240 Q_ASSERT(status);
241 return status;
244 void UploaderGadgetWidget::bootButtonsSetEnable(bool enabled)
246 m_config->bootButton->setEnabled(enabled);
247 m_config->safeBootButton->setEnabled(enabled);
249 // this feature is supported only on BL revision >= 4
250 bool isBL4 = ((m_dfu != NULL) && (m_dfu->devices[0].BL_Version >= 4));
251 m_config->eraseBootButton->setEnabled(isBL4 && enabled);
254 void UploaderGadgetWidget::onPhysicalHWConnect()
256 bootButtonsSetEnable(false);
257 m_config->rescueButton->setEnabled(false);
258 m_config->telemetryLink->setEnabled(false);
262 Enables widget buttons if autopilot connected
264 void UploaderGadgetWidget::onAutopilotConnect()
266 QTimer::singleShot(1000, this, SLOT(populate()));
269 void UploaderGadgetWidget::populate()
271 m_config->haltButton->setEnabled(true);
272 m_config->resetButton->setEnabled(true);
273 bootButtonsSetEnable(false);
274 m_config->rescueButton->setEnabled(false);
275 m_config->telemetryLink->setEnabled(false);
277 // Add a very simple widget with Board model & serial number
278 // Delete all previous tabs:
279 while (m_config->systemElements->count()) {
280 QWidget *qw = m_config->systemElements->widget(0);
281 m_config->systemElements->removeTab(0);
282 delete qw;
284 RunningDeviceWidget *dw = new RunningDeviceWidget(this);
285 dw->populate();
286 m_config->systemElements->addTab(dw, tr("Connected Device"));
290 Enables widget buttons if autopilot disconnected
292 void UploaderGadgetWidget::onAutopilotDisconnect()
294 m_config->haltButton->setEnabled(false);
295 m_config->resetButton->setEnabled(false);
296 bootButtonsSetEnable(true);
297 if (m_currentIAPStep == IAP_STATE_BOOTLOADER) {
298 m_config->rescueButton->setEnabled(false);
299 m_config->telemetryLink->setEnabled(false);
300 } else {
301 m_config->rescueButton->setEnabled(true);
302 m_config->telemetryLink->setEnabled(true);
306 static void sleep(int ms)
308 QEventLoop eventLoop;
309 QTimer::singleShot(ms, &eventLoop, SLOT(quit()));
311 eventLoop.exec();
315 Tell the mainboard to go to bootloader:
316 - Send the relevant IAP commands
317 - setup callback for MoBo acknowledge
319 void UploaderGadgetWidget::goToBootloader(UAVObject *callerObj, bool success)
321 Q_UNUSED(callerObj);
323 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
324 UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
325 UAVObject *fwIAP = dynamic_cast<UAVDataObject *>(objManager->getObject("FirmwareIAPObj"));
327 switch (m_currentIAPStep) {
328 case IAP_STATE_READY:
329 m_config->haltButton->setEnabled(false);
330 getSerialPorts(); // Useful in case a new serial port appeared since the initial list,
331 // otherwise we won't find it when we stop the board.
332 // The board is running, send the 1st IAP Reset order:
333 fwIAP->getField("Command")->setValue("1122");
334 fwIAP->getField("BoardRevision")->setDouble(0);
335 fwIAP->getField("BoardType")->setDouble(0);
336 connect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
337 m_currentIAPStep = IAP_STATE_STEP_1;
338 clearLog();
339 log("IAP Step 1");
340 fwIAP->updated();
341 emit progressUpdate(JUMP_TO_BL, QVariant(1));
342 break;
343 case IAP_STATE_STEP_1:
344 if (!success) {
345 log("Oops, failure step 1");
346 log("Reset did NOT happen");
347 m_currentIAPStep = IAP_STATE_READY;
348 disconnect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
349 m_config->haltButton->setEnabled(true);
350 emit progressUpdate(FAILURE, QVariant());
351 emit bootloaderFailed();
352 break;
354 sleep(600);
355 fwIAP->getField("Command")->setValue("2233");
356 m_currentIAPStep = IAP_STATE_STEP_2;
357 log("IAP Step 2");
358 fwIAP->updated();
359 emit progressUpdate(JUMP_TO_BL, QVariant(2));
360 break;
361 case IAP_STATE_STEP_2:
362 if (!success) {
363 log("Oops, failure step 2");
364 log("Reset did NOT happen");
365 m_currentIAPStep = IAP_STATE_READY;
366 disconnect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
367 m_config->haltButton->setEnabled(true);
368 emit progressUpdate(FAILURE, QVariant());
369 emit bootloaderFailed();
370 break;
372 sleep(600);
373 fwIAP->getField("Command")->setValue("3344");
374 m_currentIAPStep = IAP_STEP_RESET;
375 log("IAP Step 3");
376 emit progressUpdate(JUMP_TO_BL, QVariant(3));
377 fwIAP->updated();
378 break;
379 case IAP_STEP_RESET:
381 m_currentIAPStep = IAP_STATE_READY;
382 if (!success) {
383 log("Oops, failure step 3");
384 log("Reset did NOT happen");
385 disconnect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
386 m_config->haltButton->setEnabled(true);
387 emit progressUpdate(FAILURE, QVariant());
388 emit bootloaderFailed();
389 break;
392 // The board is now reset: we have to disconnect telemetry
393 Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
394 QString dli = cm->getCurrentDevice().getConName();
395 QString dlj = cm->getCurrentDevice().getConName();
396 cm->disconnectDevice();
397 sleep(200);
398 // Tell connections to stop their polling threads: otherwise it will mess up DFU
399 cm->suspendPolling();
400 sleep(200);
401 log("Board Halt");
402 emit progressUpdate(JUMP_TO_BL, QVariant(4));
403 m_config->boardStatus->setText(tr("Bootloader"));
404 if (dlj.startsWith("USB")) {
405 m_config->telemetryLink->setCurrentIndex(m_config->telemetryLink->findText("USB"));
406 } else {
407 m_config->telemetryLink->setCurrentIndex(m_config->telemetryLink->findText(dli));
410 disconnect(fwIAP, SIGNAL(transactionCompleted(UAVObject *, bool)), this, SLOT(goToBootloader(UAVObject *, bool)));
412 m_currentIAPStep = IAP_STATE_BOOTLOADER;
414 // Tell the mainboard to get into bootloader state:
415 log("Detecting devices, please wait a few seconds...");
416 if (!m_dfu) {
417 if (dlj.startsWith("USB")) {
418 m_dfu = new DFUObject(DFU_DEBUG, false, QString());
419 } else {
420 m_dfu = new DFUObject(DFU_DEBUG, true, getPortDevice(dli));
423 if (!m_dfu->ready()) {
424 log("Could not enter DFU mode.");
425 delete m_dfu;
426 m_dfu = NULL;
427 cm->resumePolling();
428 m_currentIAPStep = IAP_STATE_READY;
429 m_config->boardStatus->setText(tr("Bootloader?"));
430 m_config->haltButton->setEnabled(true);
431 emit progressUpdate(FAILURE, QVariant());
432 emit bootloaderFailed();
433 return;
435 m_dfu->AbortOperation();
436 if (!m_dfu->enterDFU(0)) {
437 log("Could not enter DFU mode.");
438 delete m_dfu;
439 m_dfu = NULL;
440 cm->resumePolling();
441 m_currentIAPStep = IAP_STATE_READY;
442 m_config->boardStatus->setText(tr("Bootloader?"));
443 emit progressUpdate(FAILURE, QVariant());
444 emit bootloaderFailed();
445 return;
448 sleep(500);
449 m_dfu->findDevices();
450 log(QString("Found %1 device(s).").arg(QString::number(m_dfu->numberOfDevices)));
451 if (m_dfu->numberOfDevices < 0 || m_dfu->numberOfDevices > 3) {
452 log("Inconsistent number of devices! Aborting");
453 delete m_dfu;
454 m_dfu = NULL;
455 cm->resumePolling();
456 emit progressUpdate(FAILURE, QVariant());
457 emit bootloaderFailed();
458 return;
460 // Delete all previous tabs:
461 while (m_config->systemElements->count()) {
462 QWidget *qw = m_config->systemElements->widget(0);
463 m_config->systemElements->removeTab(0);
464 delete qw;
466 for (int i = 0; i < m_dfu->numberOfDevices; i++) {
467 DeviceWidget *dw = new DeviceWidget(this);
468 connectSignalSlot(dw);
469 dw->setDeviceID(i);
470 dw->setDfu(m_dfu);
471 dw->populate();
472 m_config->systemElements->addTab(dw, tr("Device") + QString::number(i));
475 QApplication::processEvents();
477 // Need to re-enable in case we were not connected
478 bootButtonsSetEnable(true);
479 emit progressUpdate(JUMP_TO_BL, QVariant(5));
480 emit bootloaderSuccess();
482 if (m_resetOnly) {
483 m_resetOnly = false;
484 delay::msleep(3500);
485 systemBoot();
486 break;
489 break;
490 case IAP_STATE_BOOTLOADER:
491 // We should never end up here anyway.
492 emit progressUpdate(FAILURE, QVariant());
493 emit bootloaderFailed();
494 break;
498 void UploaderGadgetWidget::systemHalt()
500 // The board can not be halted when in armed state.
501 // If board is armed, or arming. Show message with notice.
502 FlightStatus *status = getFlightStatus();
504 if (status->getArmed() == FlightStatus::ARMED_DISARMED) {
505 goToBootloader();
506 } else {
507 cannotHaltMessageBox();
512 Tell the mainboard to reset:
513 - Send the relevant IAP commands
514 - setup callback for MoBo acknowledge
516 void UploaderGadgetWidget::systemReset()
518 FlightStatus *status = getFlightStatus();
520 // The board can not be reset when in armed state.
521 // If board is armed, or arming. Show message with notice.
522 if (status->getArmed() == FlightStatus::ARMED_DISARMED) {
523 m_resetOnly = true;
524 if (m_dfu) {
525 delete m_dfu;
526 m_dfu = NULL;
528 clearLog();
529 log("Board Reset initiated.");
530 goToBootloader();
531 } else {
532 cannotResetMessageBox();
536 void UploaderGadgetWidget::systemBoot()
538 commonSystemBoot(false, false);
541 void UploaderGadgetWidget::systemSafeBoot()
543 commonSystemBoot(true, false);
546 void UploaderGadgetWidget::systemEraseBoot()
548 switch (confirmEraseSettingsMessageBox()) {
549 case QMessageBox::Ok:
550 commonSystemBoot(true, true);
551 break;
552 case QMessageBox::Help:
553 QDesktopServices::openUrl(QUrl(tr("http://wiki.openpilot.org/display/Doc/Erase+board+settings"), QUrl::StrictMode));
554 break;
558 void UploaderGadgetWidget::rebootWithDialog()
560 RebootDialog dialog(this);
562 dialog.exec();
565 void UploaderGadgetWidget::systemReboot()
567 ResultEventLoop eventLoop;
569 connect(this, SIGNAL(bootloaderSuccess()), &eventLoop, SLOT(success()));
570 connect(this, SIGNAL(bootloaderFailed()), &eventLoop, SLOT(fail()));
572 goToBootloader();
574 if (eventLoop.run(REBOOT_TIMEOUT) != 0) {
575 emit progressUpdate(FAILURE, QVariant());
576 return;
579 disconnect(this, SIGNAL(bootloaderSuccess()), &eventLoop, SLOT(success()));
580 disconnect(this, SIGNAL(bootloaderFailed()), &eventLoop, SLOT(fail()));
582 commonSystemBoot(false, false);
584 ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
585 Q_ASSERT(pluginManager);
586 TelemetryManager *telemetryManager = pluginManager->getObject<TelemetryManager>();
587 Q_ASSERT(telemetryManager);
589 if (!telemetryManager->isConnected()) {
590 progressUpdate(BOOTING, QVariant());
591 ResultEventLoop eventLoop;
593 connect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
595 if (eventLoop.run(REBOOT_TIMEOUT) != 0) {
596 emit progressUpdate(FAILURE, QVariant());
597 return;
600 disconnect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
603 emit progressUpdate(SUCCESS, QVariant());
607 * Tells the system to boot (from Bootloader state)
608 * @param[in] safeboot Indicates whether the firmware should use the stock HWSettings
610 void UploaderGadgetWidget::commonSystemBoot(bool safeboot, bool erase)
612 clearLog();
613 bootButtonsSetEnable(false);
614 // Suspend telemety & polling in case it is not done yet
615 Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
616 cm->disconnectDevice();
617 cm->suspendPolling();
619 QString devName = m_config->telemetryLink->currentText();
620 log("Attempting to boot the system through " + devName + ".");
621 repaint();
623 if (!m_dfu) {
624 if (devName == "USB") {
625 m_dfu = new DFUObject(DFU_DEBUG, false, QString());
626 } else {
627 m_dfu = new DFUObject(DFU_DEBUG, true, getPortDevice(devName));
630 m_dfu->AbortOperation();
631 if (!m_dfu->enterDFU(0)) {
632 log("Could not enter DFU mode.");
633 delete m_dfu;
634 m_dfu = NULL;
635 bootButtonsSetEnable(true);
636 m_config->rescueButton->setEnabled(true); // Boot not possible, maybe Rescue OK?
637 emit bootFailed();
638 return;
640 log("Booting system...");
641 m_dfu->JumpToApp(safeboot, erase);
642 // Restart the polling thread
643 cm->resumePolling();
644 m_config->rescueButton->setEnabled(true);
645 m_config->telemetryLink->setEnabled(true);
646 m_config->boardStatus->setText(tr("Running"));
647 if (m_currentIAPStep == IAP_STATE_BOOTLOADER) {
648 // Freeze the tabs, they are not useful anymore and their buttons
649 // will cause segfaults or weird stuff if we use them.
650 for (int i = 0; i < m_config->systemElements->count(); i++) {
651 // OP-682 arriving here too "early" (before the devices are refreshed) was leading to a crash
652 // OP-682 the crash was due to an unchecked cast in the line below that would cast a RunningDeviceGadget to a DeviceGadget
653 DeviceWidget *qw = dynamic_cast<DeviceWidget *>(m_config->systemElements->widget(i));
654 if (qw) {
655 // OP-682 fixed a second crash by disabling *all* buttons in the device widget
656 // disabling the buttons is only half of the solution as even if the buttons are enabled
657 // the app should not crash
658 qw->freeze();
662 m_currentIAPStep = IAP_STATE_READY;
663 log("You can now reconnect telemetry...");
664 delete m_dfu; // Frees up the USB/Serial port too
665 emit bootSuccess();
666 m_dfu = NULL;
669 bool UploaderGadgetWidget::autoUpdateCapable()
671 return QDir(":/firmware").exists();
674 bool UploaderGadgetWidget::autoUpdate(bool erase)
676 if (m_oplinkwatchdog.isConnected() &&
677 m_oplinkwatchdog.opLinkType() == OPLinkWatchdog::OPLINK_MINI) {
678 emit progressUpdate(FAILURE, QVariant(tr("To upgrade the OPLinkMini board please disconnect it from the USB port, "
679 "press the Upgrade again button and follow instructions on screen.")));
680 emit autoUpdateFailed();
681 return false;
684 ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
685 Q_ASSERT(pluginManager);
686 TelemetryManager *telemetryManager = pluginManager->getObject<TelemetryManager>();
687 Q_ASSERT(telemetryManager);
689 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() > 0 &&
690 telemetryManager->connectionState() != TelemetryManager::TELEMETRY_CONNECTED) {
691 // Wait for the board to completely connect
692 ResultEventLoop eventLoop;
693 connect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
695 if (telemetryManager->connectionState() != TelemetryManager::TELEMETRY_CONNECTED
696 && eventLoop.run(REBOOT_TIMEOUT) != 0) {
697 emit progressUpdate(FAILURE, QVariant(tr("Timed out while waiting for a board to be fully connected!")));
698 emit autoUpdateFailed();
699 return false;
702 disconnect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
704 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() == 0) {
705 ConnectionWaiter waiter(1, BOARD_EVENT_TIMEOUT);
706 connect(&waiter, SIGNAL(timeChanged(int)), this, SLOT(autoUpdateConnectProgress(int)));
707 if (waiter.exec() == ConnectionWaiter::TimedOut) {
708 emit progressUpdate(FAILURE, QVariant(tr("Timed out while waiting for a board to be connected!")));
709 emit autoUpdateFailed();
710 return false;
712 } else {
713 ResultEventLoop eventLoop;
714 connect(this, SIGNAL(bootloaderSuccess()), &eventLoop, SLOT(success()));
715 connect(this, SIGNAL(bootloaderFailed()), &eventLoop, SLOT(fail()));
717 goToBootloader();
719 if (eventLoop.run(BOOTLOADER_TIMEOUT) != 0) {
720 emit progressUpdate(FAILURE, QVariant(tr("Failed to enter bootloader mode.")));
721 emit autoUpdateFailed();
722 return false;
725 disconnect(this, SIGNAL(bootloaderSuccess()), &eventLoop, SLOT(success()));
726 disconnect(this, SIGNAL(bootloaderFailed()), &eventLoop, SLOT(fail()));
729 if (m_dfu) {
730 delete m_dfu;
731 m_dfu = NULL;
734 Core::ConnectionManager *connectionManager = Core::ICore::instance()->connectionManager();
735 m_dfu = new DFUObject(DFU_DEBUG, false, QString());
736 m_dfu->AbortOperation();
737 emit progressUpdate(JUMP_TO_BL, QVariant());
739 if (!m_dfu->enterDFU(0) || !m_dfu->findDevices() ||
740 (m_dfu->numberOfDevices != 1) || m_dfu->numberOfDevices > 5) {
741 delete m_dfu;
742 m_dfu = NULL;
743 connectionManager->resumePolling();
744 emit progressUpdate(FAILURE, QVariant(tr("Failed to enter bootloader mode.")));
745 emit autoUpdateFailed();
746 return false;
749 QString filename;
750 emit progressUpdate(LOADING_FW, QVariant());
751 switch (m_dfu->devices[0].ID) {
752 case 0x301:
753 filename = "fw_oplinkmini";
754 break;
755 case 0x501:
756 filename = "fw_osd";
757 break;
758 case 0x902:
759 filename = "fw_revoproto";
760 break;
761 case 0x903:
762 filename = "fw_revolution";
763 break;
764 case 0x904:
765 filename = "fw_discoveryf4bare";
766 break;
767 case 0x905:
768 filename = "fw_revonano";
769 break;
770 default:
771 emit progressUpdate(FAILURE, QVariant(tr("Unknown board id '0x%1'").arg(QString::number(m_dfu->devices[0].ID, 16))));
772 emit autoUpdateFailed();
773 return false;
775 filename = ":/firmware/" + filename + ".opfw";
776 QByteArray firmware;
777 if (!QFile::exists(filename)) {
778 emit progressUpdate(FAILURE, QVariant(tr("Firmware image not found.")));
779 emit autoUpdateFailed();
780 return false;
782 QFile file(filename);
783 if (!file.open(QIODevice::ReadOnly)) {
784 emit progressUpdate(FAILURE, QVariant(tr("Could not open firmware image for reading.")));
785 emit autoUpdateFailed();
786 return false;
788 firmware = file.readAll();
789 QEventLoop eventLoop2;
790 connect(m_dfu, SIGNAL(progressUpdated(int)), this, SLOT(autoUpdateFlashProgress(int)));
791 connect(m_dfu, SIGNAL(uploadFinished(OP_DFU::Status)), &eventLoop2, SLOT(quit()));
792 emit progressUpdate(UPLOADING_FW, QVariant());
793 if (!m_dfu->enterDFU(0)) {
794 emit progressUpdate(FAILURE, QVariant(tr("Could not enter direct firmware upload mode.")));
795 emit autoUpdateFailed();
796 return false;
798 m_dfu->AbortOperation();
799 if (!m_dfu->UploadFirmware(filename, false, 0)) {
800 emit progressUpdate(FAILURE, QVariant(tr("Firmware upload failed.")));
801 emit autoUpdateFailed();
802 return false;
804 eventLoop2.exec();
805 QByteArray desc = firmware.right(100);
806 emit progressUpdate(UPLOADING_DESC, QVariant());
807 if (m_dfu->UploadDescription(desc) != OP_DFU::Last_operation_Success) {
808 emit progressUpdate(FAILURE, QVariant(tr("Failed to upload firmware description.")));
809 emit autoUpdateFailed();
810 return false;
813 commonSystemBoot(false, erase);
815 // Wait for board to connect to GCS again after boot and erase
816 // Theres a special case with OPLink
817 if (!telemetryManager->isConnected() && !m_oplinkwatchdog.isConnected()) {
818 progressUpdate(erase ? BOOTING_AND_ERASING : BOOTING, QVariant());
819 ResultEventLoop eventLoop;
820 connect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
821 connect(&m_oplinkwatchdog, SIGNAL(opLinkMiniConnected()), &eventLoop, SLOT(success()));
823 if (eventLoop.run(REBOOT_TIMEOUT + (erase ? ERASE_TIMEOUT : 0)) != 0) {
824 emit progressUpdate(FAILURE, QVariant(tr("Timed out while booting.")));
825 emit autoUpdateFailed();
826 return false;
829 disconnect(&m_oplinkwatchdog, SIGNAL(opLinkMiniConnected()), &eventLoop, SLOT(success()));
830 disconnect(telemetryManager, SIGNAL(connected()), &eventLoop, SLOT(success()));
833 emit progressUpdate(SUCCESS, QVariant());
834 emit autoUpdateSuccess();
835 return true;
838 void UploaderGadgetWidget::autoUpdateDisconnectProgress(int value)
840 emit progressUpdate(WAITING_DISCONNECT, value);
843 void UploaderGadgetWidget::autoUpdateConnectProgress(int value)
845 emit progressUpdate(WAITING_CONNECT, value);
848 void UploaderGadgetWidget::autoUpdateFlashProgress(int value)
850 emit progressUpdate(UPLOADING_FW, value);
854 Attempt a guided procedure to put both boards in BL mode when
855 the system is not bootable
857 void UploaderGadgetWidget::systemRescue()
859 Core::ConnectionManager *cm = Core::ICore::instance()->connectionManager();
861 cm->disconnectDevice();
862 // stop the polling thread: otherwise it will mess up DFU
863 cm->suspendPolling();
865 // Delete all previous tabs:
866 while (m_config->systemElements->count()) {
867 QWidget *qw = m_config->systemElements->widget(0);
868 m_config->systemElements->removeTab(0);
869 delete qw;
872 // Existing DFU objects will have a handle over USB and will
873 // disturb everything for the rescue process:
874 if (m_dfu) {
875 delete m_dfu;
876 m_dfu = NULL;
879 // Avoid users pressing Rescue twice.
880 m_config->rescueButton->setEnabled(false);
882 // Now we're good to go
883 clearLog();
884 log("**********************************************************");
885 log("** Follow those instructions to attempt a system rescue **");
886 log("**********************************************************");
887 log("You will be prompted to first connect USB, then system power");
889 // Check if boards are connected and, if yes, prompt user to disconnect them all
890 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() > 0) {
891 QString labelText = QString("<p align=\"left\">%1</p>").arg(tr("Please disconnect your OpenPilot board."));
892 int result = ConnectionWaiter::openDialog(tr("System Rescue"), labelText, 0, BOARD_EVENT_TIMEOUT, this);
893 switch (result) {
894 case ConnectionWaiter::Canceled:
895 m_config->rescueButton->setEnabled(true);
896 return;
898 case ConnectionWaiter::TimedOut:
899 QMessageBox::warning(this, tr("System Rescue"), tr("Timed out while waiting for all boards to be disconnected!"));
900 m_config->rescueButton->setEnabled(true);
901 return;
905 // Now prompt user to connect board
906 QString labelText = QString("<p align=\"left\">%1<br>%2</p>").arg(tr("Please connect your OpenPilot board.")).arg(tr("Board must be connected to the USB port!"));
907 int result = ConnectionWaiter::openDialog(tr("System Rescue"), labelText, 1, BOARD_EVENT_TIMEOUT, this);
908 switch (result) {
909 case ConnectionWaiter::Canceled:
910 m_config->rescueButton->setEnabled(true);
911 return;
913 case ConnectionWaiter::TimedOut:
914 QMessageBox::warning(this, tr("System Rescue"), tr("Timed out while waiting for a board to be connected!"));
915 m_config->rescueButton->setEnabled(true);
916 return;
919 log("Detecting first board...");
920 m_dfu = new DFUObject(DFU_DEBUG, false, QString());
921 m_dfu->AbortOperation();
922 if (!m_dfu->enterDFU(0)) {
923 log("Could not enter DFU mode.");
924 delete m_dfu;
925 m_dfu = NULL;
926 cm->resumePolling();
927 m_config->rescueButton->setEnabled(true);
928 return;
930 if (!m_dfu->findDevices() || (m_dfu->numberOfDevices != 1)) {
931 log("Could not detect a board, aborting!");
932 delete m_dfu;
933 m_dfu = NULL;
934 cm->resumePolling();
935 m_config->rescueButton->setEnabled(true);
936 return;
938 log(QString("Found %1 device(s).").arg(m_dfu->numberOfDevices));
940 if (m_dfu->numberOfDevices > 5) {
941 log("Inconsistent number of devices, aborting!");
942 delete m_dfu;
943 m_dfu = NULL;
944 cm->resumePolling();
945 m_config->rescueButton->setEnabled(true);
946 return;
948 for (int i = 0; i < m_dfu->numberOfDevices; i++) {
949 DeviceWidget *dw = new DeviceWidget(this);
950 connectSignalSlot(dw);
951 dw->setDeviceID(i);
952 dw->setDfu(m_dfu);
953 dw->populate();
954 m_config->systemElements->addTab(dw, tr("Device") + QString::number(i));
956 m_config->haltButton->setEnabled(false);
957 m_config->resetButton->setEnabled(false);
958 bootButtonsSetEnable(true);
959 m_config->rescueButton->setEnabled(false);
961 // So that we can boot from the GUI afterwards.
962 m_currentIAPStep = IAP_STATE_BOOTLOADER;
965 void UploaderGadgetWidget::uploadStarted()
967 m_config->haltButton->setEnabled(false);
968 bootButtonsSetEnable(false);
969 m_config->resetButton->setEnabled(false);
970 m_config->rescueButton->setEnabled(false);
973 void UploaderGadgetWidget::uploadEnded(bool succeed)
975 Q_UNUSED(succeed);
976 // device is halted so no halt
977 m_config->haltButton->setEnabled(false);
978 bootButtonsSetEnable(true);
979 // device is halted so no reset
980 m_config->resetButton->setEnabled(false);
981 m_config->rescueButton->setEnabled(true);
984 void UploaderGadgetWidget::downloadStarted()
986 m_config->haltButton->setEnabled(false);
987 bootButtonsSetEnable(false);
988 m_config->resetButton->setEnabled(false);
989 m_config->rescueButton->setEnabled(false);
992 void UploaderGadgetWidget::downloadEnded(bool succeed)
994 Q_UNUSED(succeed);
995 // device is halted so no halt
996 m_config->haltButton->setEnabled(false);
997 bootButtonsSetEnable(true);
998 // device is halted so no reset
999 m_config->resetButton->setEnabled(false);
1000 m_config->rescueButton->setEnabled(true);
1003 void UploaderGadgetWidget::startAutoUpdate()
1005 startAutoUpdate(false);
1008 void UploaderGadgetWidget::startAutoUpdateErase()
1010 startAutoUpdate(true);
1012 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
1013 UAVObjectUtilManager *utilMngr = pm->getObject<UAVObjectUtilManager>();
1014 int id = utilMngr->getBoardModel();
1016 if (id == 0x905) {
1017 systemReset();
1021 void UploaderGadgetWidget::startAutoUpdate(bool erase)
1023 m_config->autoUpdateProgressBar->setValue(0);
1024 autoUpdateStatus(uploader::JUMP_TO_BL, QVariant());
1025 m_config->buttonFrame->setEnabled(false);
1026 m_config->splitter->setEnabled(false);
1027 m_config->autoUpdateGroupBox->setVisible(true);
1028 m_config->autoUpdateOkButton->setEnabled(false);
1030 connect(this, SIGNAL(progressUpdate(uploader::ProgressStep, QVariant)), this, SLOT(autoUpdateStatus(uploader::ProgressStep, QVariant)));
1031 autoUpdate(erase);
1034 void UploaderGadgetWidget::finishAutoUpdate()
1036 disconnect(this, SIGNAL(progressUpdate(uploader::ProgressStep, QVariant)), this, SLOT(autoUpdateStatus(uploader::ProgressStep, QVariant)));
1037 m_config->autoUpdateOkButton->setEnabled(true);
1038 m_autoUpdateClosing = true;
1040 // wait a bit and "close" auto update
1041 QTimer::singleShot(AUTOUPDATE_CLOSE_TIMEOUT, this, SLOT(closeAutoUpdate()));
1044 void UploaderGadgetWidget::closeAutoUpdate()
1046 if (m_autoUpdateClosing) {
1047 m_config->autoUpdateGroupBox->setVisible(false);
1048 m_config->buttonFrame->setEnabled(true);
1049 m_config->splitter->setEnabled(true);
1051 m_autoUpdateClosing = false;
1054 void UploaderGadgetWidget::autoUpdateStatus(uploader::ProgressStep status, QVariant value)
1056 QString msg;
1057 int remaining;
1059 switch (status) {
1060 case uploader::WAITING_DISCONNECT:
1061 m_config->autoUpdateLabel->setText(tr("Waiting for all OpenPilot boards to be disconnected from USB."));
1062 m_config->autoUpdateProgressBar->setMaximum(BOARD_EVENT_TIMEOUT / 1000);
1063 m_config->autoUpdateProgressBar->setValue(value.toInt());
1064 remaining = m_config->autoUpdateProgressBar->maximum() - m_config->autoUpdateProgressBar->value();
1065 m_config->autoUpdateProgressBar->setFormat(tr("Timing out in %1 seconds").arg(remaining));
1066 break;
1067 case uploader::WAITING_CONNECT:
1068 m_config->autoUpdateLabel->setText(tr("Please connect the OpenPilot board to the USB port."));
1069 m_config->autoUpdateProgressBar->setMaximum(BOARD_EVENT_TIMEOUT / 1000);
1070 m_config->autoUpdateProgressBar->setValue(value.toInt());
1071 remaining = m_config->autoUpdateProgressBar->maximum() - m_config->autoUpdateProgressBar->value();
1072 m_config->autoUpdateProgressBar->setFormat(tr("Timing out in %1 seconds").arg(remaining));
1073 break;
1074 case uploader::JUMP_TO_BL:
1075 m_config->autoUpdateLabel->setText(tr("Bringing the board into boot loader mode. Please wait."));
1076 m_config->autoUpdateProgressBar->setFormat(tr("Step %1").arg(value.toInt()));
1077 m_config->autoUpdateProgressBar->setMaximum(5);
1078 m_config->autoUpdateProgressBar->setValue(value.toInt());
1079 break;
1080 case uploader::LOADING_FW:
1081 m_config->autoUpdateLabel->setText(tr("Preparing to upload firmware to the board."));
1082 break;
1083 case uploader::UPLOADING_FW:
1084 m_config->autoUpdateLabel->setText(tr("Uploading firmware to the board."));
1085 m_config->autoUpdateProgressBar->setFormat("%p%");
1086 m_config->autoUpdateProgressBar->setMaximum(100);
1087 m_config->autoUpdateProgressBar->setValue(value.toInt());
1088 break;
1089 case uploader::UPLOADING_DESC:
1090 m_config->autoUpdateLabel->setText(tr("Uploading description of the new firmware to the board."));
1091 break;
1092 case uploader::BOOTING:
1093 m_config->autoUpdateLabel->setText(tr("Rebooting the board. Please wait."));
1094 break;
1095 case uploader::BOOTING_AND_ERASING:
1096 m_config->autoUpdateLabel->setText(tr("Rebooting and erasing the board. Please wait."));
1097 break;
1098 case uploader::SUCCESS:
1099 m_config->autoUpdateProgressBar->setValue(m_config->autoUpdateProgressBar->maximum());
1100 msg = tr("Board was updated successfully. Press OK to finish.");
1101 m_config->autoUpdateLabel->setText(QString("<font color='green'>%1</font>").arg(msg));
1102 finishAutoUpdate();
1103 break;
1104 case uploader::FAILURE:
1105 msg = value.toString();
1106 if (msg.isEmpty()) {
1107 msg = tr("Something went wrong.");
1109 msg += tr(" Press OK to finish, you will have to manually upgrade the board.");
1111 m_config->autoUpdateLabel->setText(QString("<font color='red'>%1</font>").arg(msg));
1112 finishAutoUpdate();
1113 break;
1118 Update log entry
1120 void UploaderGadgetWidget::log(QString str)
1122 qDebug() << "UploaderGadgetWidget -" << str;
1123 m_config->textBrowser->append(str);
1126 void UploaderGadgetWidget::clearLog()
1128 m_config->textBrowser->clear();
1132 * Remove all the device widgets...
1134 UploaderGadgetWidget::~UploaderGadgetWidget()
1136 while (m_config->systemElements->count()) {
1137 QWidget *qw = m_config->systemElements->widget(0);
1138 m_config->systemElements->removeTab(0);
1139 delete qw;
1140 qw = 0;
1144 void UploaderGadgetWidget::openHelp()
1146 QDesktopServices::openUrl(QUrl(tr("http://wiki.openpilot.org/x/AoBZ"), QUrl::StrictMode));
1149 int UploaderGadgetWidget::confirmEraseSettingsMessageBox()
1151 QMessageBox msgBox(this);
1153 msgBox.setWindowTitle(tr("Confirm Settings Erase?"));
1154 msgBox.setIcon(QMessageBox::Question);
1155 msgBox.setText(tr("Do you want to erase all settings from the board?"));
1156 msgBox.setInformativeText(tr("Settings cannot be recovered after this operation.\nThe board will be restarted and all settings erased."));
1157 msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel | QMessageBox::Help);
1158 return msgBox.exec();
1161 int UploaderGadgetWidget::cannotHaltMessageBox()
1163 QMessageBox msgBox(this);
1165 msgBox.setWindowTitle(tr("Cannot Halt Board!"));
1166 msgBox.setIcon(QMessageBox::Warning);
1167 msgBox.setText(tr("The controller board is armed and can not be halted."));
1168 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."));
1169 msgBox.setStandardButtons(QMessageBox::Ok);
1170 return msgBox.exec();
1173 int UploaderGadgetWidget::cannotResetMessageBox()
1175 QMessageBox msgBox(this);
1177 msgBox.setWindowTitle(tr("Cannot Reset Board!"));
1178 msgBox.setIcon(QMessageBox::Warning);
1179 msgBox.setText(tr("The controller board is armed and can not be reset."));
1180 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."));
1181 msgBox.setStandardButtons(QMessageBox::Ok);
1182 return msgBox.exec();
1186 int ResultEventLoop::run(int millisTimout)
1188 m_timer.singleShot(millisTimout, this, SLOT(fail()));
1189 return exec();
1192 void ResultEventLoop::success()
1194 m_timer.stop();
1195 exit(0);
1198 void ResultEventLoop::fail()
1200 m_timer.stop();
1201 exit(-1);