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
9 * @addtogroup YModemUploader YModem Serial Uploader Plugin
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
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>
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
);
65 // open immediately...
66 setMinimumDuration(0);
68 bar
->setRange(0, timeout
);
69 bar
->setFormat(tr("Timing out in %1 seconds").arg(timeout
));
73 void TimedDialog::perform()
75 setValue(value() + 1);
76 int remaining
= bar
->maximum() - bar
->value();
78 bar
->setFormat(tr("Timing out in %1 seconds").arg(remaining
));
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()));
101 void ConnectionWaiter::cancel()
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()));
115 void ConnectionWaiter::perform()
118 emit
timeChanged(elapsed
);
119 int remaining
= timeout
- elapsed
* 1000;
120 if (remaining
<= 0) {
121 result
= ConnectionWaiter::TimedOut
;
126 void ConnectionWaiter::deviceEvent()
128 if (USBMonitor::instance()->availableDevices(0x20a0, -1, -1, -1).length() == targetDeviceCount
) {
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
;
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()));
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()
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();
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();
242 UAVObjectManager
*objManager
= pm
->getObject
<UAVObjectManager
>();
243 Q_ASSERT(objManager
);
244 FlightStatus
*status
= dynamic_cast<FlightStatus
*>(objManager
->getObject("FlightStatus"));
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);
289 RunningDeviceWidget
*dw
= new RunningDeviceWidget(this);
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);
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()));
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
)
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
;
346 emit
progressUpdate(JUMP_TO_BL
, QVariant(1));
348 case IAP_STATE_STEP_1
:
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();
360 fwIAP
->getField("Command")->setValue("2233");
361 m_currentIAPStep
= IAP_STATE_STEP_2
;
364 emit
progressUpdate(JUMP_TO_BL
, QVariant(2));
366 case IAP_STATE_STEP_2
:
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();
378 fwIAP
->getField("Command")->setValue("3344");
379 m_currentIAPStep
= IAP_STEP_RESET
;
381 emit
progressUpdate(JUMP_TO_BL
, QVariant(3));
386 m_currentIAPStep
= IAP_STATE_READY
;
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();
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();
403 // Tell connections to stop their polling threads: otherwise it will mess up DFU
404 cm
->suspendPolling();
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"));
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...");
422 if (dlj
.startsWith("USB")) {
423 m_dfu
= new DFUObject(DFU_DEBUG
, false, QString());
425 m_dfu
= new DFUObject(DFU_DEBUG
, true, getPortDevice(dli
));
428 if (!m_dfu
->ready()) {
429 log("Could not enter DFU mode.");
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();
440 m_dfu
->AbortOperation();
441 if (!m_dfu
->enterDFU(0)) {
442 log("Could not enter DFU mode.");
446 m_currentIAPStep
= IAP_STATE_READY
;
447 m_config
->boardStatus
->setText(tr("Bootloader?"));
448 emit
progressUpdate(FAILURE
, QVariant());
449 emit
bootloaderFailed();
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");
461 emit
progressUpdate(FAILURE
, QVariant());
462 emit
bootloaderFailed();
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);
471 for (int i
= 0; i
< m_dfu
->numberOfDevices
; i
++) {
472 DeviceWidget
*dw
= new DeviceWidget(this);
473 connectSignalSlot(dw
);
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();
489 QThread::msleep(3500);
495 case IAP_STATE_BOOTLOADER
:
496 // We should never end up here anyway.
497 emit
progressUpdate(FAILURE
, QVariant());
498 emit
bootloaderFailed();
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
) {
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
) {
534 log("Board Reset initiated.");
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);
557 case QMessageBox::Help
:
558 QDesktopServices::openUrl(QUrl(QString(WIKI_URL_ROOT
) + QString("Erase+board+settings"),
564 void UploaderGadgetWidget::rebootWithDialog()
566 RebootDialog
dialog(this);
571 void UploaderGadgetWidget::systemReboot()
573 ResultEventLoop eventLoop
;
575 connect(this, SIGNAL(bootloaderSuccess()), &eventLoop
, SLOT(success()));
576 connect(this, SIGNAL(bootloaderFailed()), &eventLoop
, SLOT(fail()));
580 if (eventLoop
.run(REBOOT_TIMEOUT
) != 0) {
581 emit
progressUpdate(FAILURE
, QVariant());
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());
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
)
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
+ ".");
630 if (devName
== "USB") {
631 m_dfu
= new DFUObject(DFU_DEBUG
, false, QString());
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.");
641 bootButtonsSetEnable(true);
642 m_config
->rescueButton
->setEnabled(true); // Boot not possible, maybe Rescue OK?
646 log("Booting system...");
647 m_dfu
->JumpToApp(safeboot
, erase
);
648 // Restart the polling thread
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
));
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
668 m_currentIAPStep
= IAP_STATE_READY
;
669 log("You can now reconnect telemetry...");
670 delete m_dfu
; // Frees up the USB/Serial port too
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();
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();
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();
724 ResultEventLoop eventLoop
;
725 connect(this, SIGNAL(bootloaderSuccess()), &eventLoop
, SLOT(success()));
726 connect(this, SIGNAL(bootloaderFailed()), &eventLoop
, SLOT(fail()));
730 if (eventLoop
.run(BOOTLOADER_TIMEOUT
) != 0) {
731 emit
progressUpdate(FAILURE
, QVariant(tr("Failed to enter bootloader mode.")));
732 emit
autoUpdateFailed();
736 disconnect(this, SIGNAL(bootloaderSuccess()), &eventLoop
, SLOT(success()));
737 disconnect(this, SIGNAL(bootloaderFailed()), &eventLoop
, SLOT(fail()));
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) {
754 connectionManager
->resumePolling();
755 emit
progressUpdate(FAILURE
, QVariant(tr("Failed to enter bootloader mode.")));
756 emit
autoUpdateFailed();
761 emit
progressUpdate(LOADING_FW
, QVariant());
762 switch (m_dfu
->devices
[0].ID
) {
764 filename
= "fw_oplinkmini";
768 filename
= "fw_coptercontrol";
774 filename
= "fw_revoproto";
777 filename
= "fw_revolution";
780 filename
= "fw_discoveryf4bare";
783 filename
= "fw_revonano";
786 filename
= "fw_sparky2";
789 filename
= "fw_spracingf3";
792 filename
= "fw_spracingf3evo";
795 filename
= "fw_nucleof303re";
798 filename
= "fw_pikoblx";
801 filename
= "fw_tinyfish";
804 emit
progressUpdate(FAILURE
, QVariant(tr("Unknown board id '0x%1'").arg(QString::number(m_dfu
->devices
[0].ID
, 16))));
805 emit
autoUpdateFailed();
808 filename
= ":/firmware/" + filename
+ ".opfw";
810 if (!QFile::exists(filename
)) {
811 emit
progressUpdate(FAILURE
, QVariant(tr("Firmware image not found.")));
812 emit
autoUpdateFailed();
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();
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();
831 m_dfu
->AbortOperation();
832 if (!m_dfu
->UploadFirmware(filename
, false, 0)) {
833 emit
progressUpdate(FAILURE
, QVariant(tr("Firmware upload failed.")));
834 emit
autoUpdateFailed();
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();
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();
863 disconnect(opLinkManager
, SIGNAL(connected()), &eventLoop
, SLOT(success()));
864 disconnect(telemetryManager
, SIGNAL(connected()), &eventLoop
, SLOT(success()));
867 emit
progressUpdate(SUCCESS
, QVariant());
868 emit
autoUpdateSuccess();
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);
906 // Existing DFU objects will have a handle over USB and will
907 // disturb everything for the rescue process:
913 // Avoid users pressing Rescue twice.
914 m_config
->rescueButton
->setEnabled(false);
916 // Now we're good to go
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);
928 case ConnectionWaiter::Canceled
:
929 m_config
->rescueButton
->setEnabled(true);
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);
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);
943 case ConnectionWaiter::Canceled
:
944 m_config
->rescueButton
->setEnabled(true);
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);
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.");
961 m_config
->rescueButton
->setEnabled(true);
964 if (!m_dfu
->findDevices() || (m_dfu
->numberOfDevices
!= 1)) {
965 log("Could not detect a board, aborting!");
969 m_config
->rescueButton
->setEnabled(true);
972 log(QString("Found %1 device(s).").arg(m_dfu
->numberOfDevices
));
974 if (m_dfu
->numberOfDevices
> 5) {
975 log("Inconsistent number of devices, aborting!");
979 m_config
->rescueButton
->setEnabled(true);
982 for (int i
= 0; i
< m_dfu
->numberOfDevices
; i
++) {
983 DeviceWidget
*dw
= new DeviceWidget(this);
984 connectSignalSlot(dw
);
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
)
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
)
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();
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
)));
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
)
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
));
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
));
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());
1115 case uploader::LOADING_FW
:
1116 m_config
->autoUpdateLabel
->setText(tr("Preparing to upload firmware to the board."));
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());
1124 case uploader::UPLOADING_DESC
:
1125 m_config
->autoUpdateLabel
->setText(tr("Uploading description of the new firmware to the board."));
1127 case uploader::BOOTING
:
1128 m_config
->autoUpdateLabel
->setText(tr("Rebooting the board. Please wait."));
1130 case uploader::BOOTING_AND_ERASING
:
1131 m_config
->autoUpdateLabel
->setText(tr("Rebooting and erasing the board. Please wait."));
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
));
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
));
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);
1179 void UploaderGadgetWidget::openHelp()
1181 QDesktopServices::openUrl(QUrl(QString(WIKI_URL_ROOT
) + QString("Firmware+Tab"),
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()));
1228 void ResultEventLoop::success()
1234 void ResultEventLoop::fail()