QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
PX4FirmwareUpgradeThread.cc
Go to the documentation of this file.
2#include "Bootloader.h"
3#include "FirmwareImage.h"
5
6#include <QtCore/QThread>
7#include <QtCore/QVariantMap>
8
9QGC_LOGGING_CATEGORY(PX4FirmwareUpgradeThreadLog, "Vehicle.VehicleSetup.PX4FirmwareUpgradeThread")
10QGC_LOGGING_CATEGORY(PX4FirmwareUpgradeThreadVerboseLog, "Vehicle.VehicleSetup.PX4FirmwareUpgradeThread.Verbose")
11#include <QtCore/QTimer>
12
14 : _controller(controller)
15{
16 connect(_controller, &PX4FirmwareUpgradeThreadController::_initThreadWorker, this, &PX4FirmwareUpgradeThreadWorker::_init);
17 connect(_controller, &PX4FirmwareUpgradeThreadController::_startFindBoardLoopOnThread, this, &PX4FirmwareUpgradeThreadWorker::_startFindBoardLoop);
18 connect(_controller, &PX4FirmwareUpgradeThreadController::_flashOnThread, this, &PX4FirmwareUpgradeThreadWorker::_flash);
19 connect(_controller, &PX4FirmwareUpgradeThreadController::_rebootOnThread, this, &PX4FirmwareUpgradeThreadWorker::_reboot);
20 connect(_controller, &PX4FirmwareUpgradeThreadController::_cancel, this, &PX4FirmwareUpgradeThreadWorker::_cancel);
21 connect(_controller, &PX4FirmwareUpgradeThreadController::_setTargetPortOnThread, this, &PX4FirmwareUpgradeThreadWorker::_setTargetPort);
22}
23
28
29void PX4FirmwareUpgradeThreadWorker::_init(void)
30{
31 // We create the timers here so that they are on the right thread
32
33 _findBoardTimer = new QTimer(this);
34 _findBoardTimer->setSingleShot(true);
35 _findBoardTimer->setInterval(500);
36 connect(_findBoardTimer, &QTimer::timeout, this, &PX4FirmwareUpgradeThreadWorker::_findBoardOnce);
37}
38
39void PX4FirmwareUpgradeThreadWorker::_cancel(void)
40{
41 qCDebug(PX4FirmwareUpgradeThreadVerboseLog) << "_cancel";
42 _targetSystemLocation.clear();
43 _targetSerialNumber.clear();
44 _targetSeenAtLeastOnce = false;
45 _ignoredPorts.clear();
46 _foundBoard = false;
47 if (_bootloader) {
48 _bootloader->reboot();
49 _bootloader->close();
50 _bootloader->deleteLater();
51 _bootloader = nullptr;
52 }
53 if (_findBoardTimer && !_findBoardTimer->isActive()) {
54 _findBoardTimer->start();
55 }
56}
57
58void PX4FirmwareUpgradeThreadWorker::_startFindBoardLoop(void)
59{
60 _foundBoard = false;
61 _findBoardFirstAttempt = true;
62 _targetSystemLocation.clear();
63 _targetSerialNumber.clear();
64 _targetSeenAtLeastOnce = false;
65 _ignoredPorts.clear();
66 _findBoardOnce();
67}
68
69void PX4FirmwareUpgradeThreadWorker::_resumeDiscovery(void)
70{
71 _targetSystemLocation.clear();
72 _targetSerialNumber.clear();
73 _targetSeenAtLeastOnce = false;
74 _ignoredPorts.clear();
75 _foundBoard = false;
76 if (_findBoardTimer && !_findBoardTimer->isActive()) {
77 _findBoardTimer->start();
78 }
79}
80
81void PX4FirmwareUpgradeThreadWorker::_setTargetPort(const QString& systemLocation)
82{
83 qCDebug(PX4FirmwareUpgradeThreadLog) << "_setTargetPort" << systemLocation;
84 _targetSystemLocation = systemLocation;
85 _targetSerialNumber.clear();
86 _targetSeenAtLeastOnce = false;
87 _foundBoard = false;
88 _ignoredPorts.clear();
89
90 bool currentlyVisible = false;
91 bool currentlyInBootloader = false;
92 for (const QGCSerialPortInfo& info : QGCSerialPortInfo::availablePorts()) {
93 if (info.systemLocation() == systemLocation) {
94 currentlyVisible = true;
95 // Capture serial number so we can find the board across a path change
96 // when it re-enumerates as a bootloader on a different /dev/ttyACM*.
97 _targetSerialNumber = info.serialNumber();
98 // If the descriptor already looks like a bootloader, skip the unplug-replug
99 // dance and handshake immediately on the next poll.
100 currentlyInBootloader = info.description().contains(QStringLiteral("BL"))
101 || info.description().contains(QStringLiteral("Bootloader"), Qt::CaseInsensitive);
102 } else {
103 // Remember which ports were already there so we don't accidentally attach
104 // to one of them via the canFlash() fallback after the target disappears.
105 _ignoredPorts.insert(info.systemLocation());
106 }
107 }
108 _findBoardFirstAttempt = currentlyVisible && !currentlyInBootloader;
109}
110
111QVariantList PX4FirmwareUpgradeThreadWorker::_buildPortDescriptors() const
112{
113 QVariantList list;
114
115 for (const QGCSerialPortInfo& info : QGCSerialPortInfo::availablePorts()) {
117 QString boardName;
118 const bool recognized = info.getBoardInfo(boardType, boardName);
119
120 QVariantMap entry;
121 entry["systemLocation"] = info.systemLocation();
122 entry["portName"] = info.portName();
123 entry["description"] = info.description();
124 entry["manufacturer"] = info.manufacturer();
125 entry["boardType"] = static_cast<int>(recognized ? boardType : QGCSerialPortInfo::BoardTypeUnknown);
126 entry["boardName"] = recognized ? boardName : QString();
127 entry["recognized"] = recognized;
128
129 QString display = info.portName();
130 if (!info.description().isEmpty()) {
131 display += QStringLiteral(" — ") + info.description();
132 }
133 if (recognized && !boardName.isEmpty()) {
134 display += QStringLiteral(" [") + boardName + QStringLiteral("]");
135 }
136 entry["displayName"] = display;
137
138 list.append(entry);
139 }
140
141 return list;
142}
143
144void PX4FirmwareUpgradeThreadWorker::_findBoardOnce(void)
145{
146 qCDebug(PX4FirmwareUpgradeThreadVerboseLog) << "_findBoardOnce";
147
148 emit portsAvailable(_buildPortDescriptors());
149
150 if (_targetSystemLocation.isEmpty()) {
151 _findBoardTimer->start();
152 return;
153 }
154
155 QGCSerialPortInfo targetPort;
157 QString boardName;
158 bool foundTarget = false;
159
160 for (const QGCSerialPortInfo& info: QGCSerialPortInfo::availablePorts()) {
161 qCDebug(PX4FirmwareUpgradeThreadVerboseLog) << "Serial Port --------------";
162 qCDebug(PX4FirmwareUpgradeThreadVerboseLog) << "\tport name:" << info.portName();
163 qCDebug(PX4FirmwareUpgradeThreadVerboseLog) << "\tdescription:" << info.description();
164 qCDebug(PX4FirmwareUpgradeThreadVerboseLog) << "\tsystem location:" << info.systemLocation();
165 qCDebug(PX4FirmwareUpgradeThreadVerboseLog) << "\tvendor ID:" << info.vendorIdentifier();
166 qCDebug(PX4FirmwareUpgradeThreadVerboseLog) << "\tproduct ID:" << info.productIdentifier();
167
168 bool match = (info.systemLocation() == _targetSystemLocation);
169 if (!match && !_targetSerialNumber.isEmpty() && !info.serialNumber().isEmpty()) {
170 match = (info.serialNumber() == _targetSerialNumber);
171 }
172 // After we've seen the target at least once and it's now gone, accept any
173 // recognized flashable port that wasn't already there at click time —
174 // covers a board re-enumerating as a bootloader at a different /dev/ttyACM*
175 // path with a different VID/PID, while not attaching to other unrelated
176 // flashable devices that were already plugged in.
177 if (!match && _targetSeenAtLeastOnce && !_foundBoard
178 && info.canFlash()
179 && !_ignoredPorts.contains(info.systemLocation())) {
180 match = true;
181 }
182
183 if (match) {
184 targetPort = info;
185 info.getBoardInfo(boardType, boardName);
186 foundTarget = true;
187 break;
188 }
189 }
190
191 if (foundTarget) {
192 if (!_foundBoard) {
193 _foundBoard = true;
194 _targetSeenAtLeastOnce = true;
195 _foundBoardPortInfo = targetPort;
196 if (_targetSerialNumber.isEmpty() && !targetPort.serialNumber().isEmpty()) {
197 _targetSerialNumber = targetPort.serialNumber();
198 }
199 emit foundBoard(_findBoardFirstAttempt, targetPort, boardType, boardName);
200 if (!_findBoardFirstAttempt) {
201
202 _bootloader = new Bootloader(boardType == QGCSerialPortInfo::BoardTypeSiKRadio, this);
203 connect(_bootloader, &Bootloader::updateProgress, this, &PX4FirmwareUpgradeThreadWorker::_updateProgress);
204
205 if (_bootloader->open(targetPort.portName())) {
206 uint32_t bootloaderVersion;
207 uint32_t boardId;
208 uint32_t flashSize;
209 if (_bootloader->getBoardInfo(bootloaderVersion, boardId, flashSize)) {
210 emit foundBoardInfo(bootloaderVersion, boardId, flashSize);
211 } else {
212 emit error(_bootloader->errorString());
213 }
214 } else {
215 emit error(_bootloader->errorString());
216 }
217 return;
218 }
219 }
220 } else {
221 if (_foundBoard) {
222 _foundBoard = false;
223 qCDebug(PX4FirmwareUpgradeThreadLog) << "Target port gone";
224 emit boardGone();
225 }
226 }
227
228 _findBoardFirstAttempt = false;
229 _findBoardTimer->start();
230}
231
232void PX4FirmwareUpgradeThreadWorker::_reboot(void)
233{
234 _bootloader->reboot();
235}
236
237void PX4FirmwareUpgradeThreadWorker::_flash(void)
238{
239 qCDebug(PX4FirmwareUpgradeThreadLog) << "PX4FirmwareUpgradeThreadWorker::_flash";
240
241 if (!_bootloader) {
242 // Cancelled between download completion and the queued flash slot firing.
243 qCDebug(PX4FirmwareUpgradeThreadLog) << "_flash: bootloader gone, aborting";
244 _resumeDiscovery();
245 return;
246 }
247
248 if (!_bootloader->initFlashSequence()) {
249 emit error(_bootloader->errorString());
250 _resumeDiscovery();
251 return;
252 }
253
254 if (_erase()) {
255 emit status(tr("Programming new version..."));
256
257 if (_bootloader->program(_controller->image())) {
258 qCDebug(PX4FirmwareUpgradeThreadLog) << "Program complete";
259 } else {
260 qCDebug(PX4FirmwareUpgradeThreadLog) << "Program failed:" << _bootloader->errorString();
261 goto Error;
262 }
263
264 emit status(tr("Verifying program..."));
265
266 if (_bootloader->verify(_controller->image())) {
267 qCDebug(PX4FirmwareUpgradeThreadLog) << "Verify complete";
268 } else {
269 qCDebug(PX4FirmwareUpgradeThreadLog) << "Verify failed:" << _bootloader->errorString();
270 goto Error;
271 }
272 }
273
274 emit status(tr("Rebooting board"));
275 _reboot();
276
277 _bootloader->close();
278 _bootloader->deleteLater();
279 _bootloader = nullptr;
280
281 emit flashComplete();
282
283 _resumeDiscovery();
284 return;
285
286Error:
287 emit error(_bootloader->errorString());
288 _reboot();
289 _bootloader->close();
290 _bootloader->deleteLater();
291 _bootloader = nullptr;
292 _resumeDiscovery();
293}
294
295bool PX4FirmwareUpgradeThreadWorker::_erase(void)
296{
297 qCDebug(PX4FirmwareUpgradeThreadLog) << "PX4FirmwareUpgradeThreadWorker::_erase";
298
299 emit eraseStarted();
300 emit status(tr("Erasing previous program..."));
301
302 if (_bootloader->erase()) {
303 qCDebug(PX4FirmwareUpgradeThreadLog) << "Erase complete";
304 emit status(tr("Erase complete"));
305 emit eraseComplete();
306 return true;
307 } else {
308 qCDebug(PX4FirmwareUpgradeThreadLog) << "Erase failed:" << _bootloader->errorString();
309 emit error(_bootloader->errorString());
310 return false;
311 }
312}
313
315 QObject(parent)
316{
317 _worker = new PX4FirmwareUpgradeThreadWorker(this);
318 _workerThread = new QThread(this);
319 _worker->moveToThread(_workerThread);
320
321 connect(_worker, &PX4FirmwareUpgradeThreadWorker::updateProgress, this, &PX4FirmwareUpgradeThreadController::_updateProgress);
322 connect(_worker, &PX4FirmwareUpgradeThreadWorker::foundBoard, this, &PX4FirmwareUpgradeThreadController::_foundBoard);
323 connect(_worker, &PX4FirmwareUpgradeThreadWorker::noBoardFound, this, &PX4FirmwareUpgradeThreadController::_noBoardFound);
324 connect(_worker, &PX4FirmwareUpgradeThreadWorker::boardGone, this, &PX4FirmwareUpgradeThreadController::_boardGone);
325 connect(_worker, &PX4FirmwareUpgradeThreadWorker::foundBoardInfo, this, &PX4FirmwareUpgradeThreadController::_foundBoardInfo);
326 connect(_worker, &PX4FirmwareUpgradeThreadWorker::error, this, &PX4FirmwareUpgradeThreadController::_error);
327 connect(_worker, &PX4FirmwareUpgradeThreadWorker::status, this, &PX4FirmwareUpgradeThreadController::_status);
328 connect(_worker, &PX4FirmwareUpgradeThreadWorker::eraseStarted, this, &PX4FirmwareUpgradeThreadController::_eraseStarted);
329 connect(_worker, &PX4FirmwareUpgradeThreadWorker::eraseComplete, this, &PX4FirmwareUpgradeThreadController::_eraseComplete);
330 connect(_worker, &PX4FirmwareUpgradeThreadWorker::flashComplete, this, &PX4FirmwareUpgradeThreadController::_flashComplete);
331 connect(_worker, &PX4FirmwareUpgradeThreadWorker::portsAvailable, this, &PX4FirmwareUpgradeThreadController::_portsAvailable);
332
333 _workerThread->start();
334
335 emit _initThreadWorker();
336}
337
339{
340 _workerThread->quit();
341 _workerThread->wait();
342
343 delete _workerThread;
344}
345
347{
348 qCDebug(PX4FirmwareUpgradeThreadLog) << "PX4FirmwareUpgradeThreadController::findBoard";
350}
351
353{
354 qCDebug(PX4FirmwareUpgradeThreadLog) << "PX4FirmwareUpgradeThreadController::cancel";
355 emit _cancel();
356}
357
359{
360 _image = image;
361 emit _flashOnThread();
362}
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Bootloader Utility routines. Works with PX4 and 3DR Radio bootloaders.
Definition Bootloader.h:16
bool program(const FirmwareImage *image)
void updateProgress(int curr, int total)
Signals progress indicator for long running bootloader utility routines.
QString errorString(void)
Definition Bootloader.h:22
bool erase(void)
bool reboot(void)
bool initFlashSequence(void)
bool open(const QString portName)
Definition Bootloader.cc:21
bool verify(const FirmwareImage *image)
bool getBoardInfo(uint32_t &bootloaderVersion, uint32_t &boardID, uint32_t &flashSize)
Definition Bootloader.cc:68
void close(void)
Definition Bootloader.h:25
Support for Intel Hex firmware file.
Provides methods to interact with the bootloader. The commands themselves are signalled across to PX4...
void _setTargetPortOnThread(const QString &systemLocation)
PX4FirmwareUpgradeThreadController(QObject *parent=nullptr)
void startFindBoardLoop(void)
Begins the process of searching for a supported board connected to any serial port....
void flash(const FirmwareImage *image)
Used to run bootloader commands on a separate thread. These routines are mainly meant to to be called...
void foundBoard(bool firstAttempt, const QGCSerialPortInfo &portInfo, int type, QString boardName)
void portsAvailable(const QVariantList &ports)
void error(const QString &errorString)
void foundBoardInfo(int bootloaderVersion, int boardID, int flashSize)
void status(const QString &statusText)
PX4FirmwareUpgradeThreadWorker(PX4FirmwareUpgradeThreadController *controller)
void updateProgress(int curr, int total)
QGC's version of Qt QSerialPortInfo. It provides additional information about board types that QGC ca...
bool getBoardInfo(BoardType_t &boardType, QString &name) const
QString portName() const
Returns the name of the serial port.
QString serialNumber() const
Error
Error codes for decompression operations.