QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
OnboardLogController.cc
Go to the documentation of this file.
2#include "AppSettings.h"
3#include "FTPManager.h"
4#include "OnboardLogEntry.h"
5#include "MAVLinkLib.h"
6#include "MAVLinkProtocol.h"
8#include "ParameterManager.h"
9#include "QGCFormat.h"
10#include "QGCLoggingCategory.h"
11#include "QmlObjectListModel.h"
12#include "SettingsManager.h"
13#include "Vehicle.h"
14#include "VehicleLinkManager.h"
15
16#include <algorithm>
17
18#include <QtCore/QApplicationStatic>
19#include <QtCore/QDir>
20#include <QtCore/QTimeZone>
21#include <QtCore/QTimer>
22
23QGC_LOGGING_CATEGORY(OnboardLogControllerLog, "AnalyzeView.OnboardLogController")
24
25// MAVLink FTP defines "@MAV_LOG" as the virtual log directory.
26// Older firmware that doesn't implement the alias requires the physical path
27// instead — which is firmware-specific.
28static constexpr const char *kMavlinkLogRoot = "@MAV_LOG";
29static constexpr const char *kPx4LogRootFallback = "/fs/microsd/log";
30static constexpr const char *kApmLogRootFallback = "/APM/LOGS";
31
33 : QObject(parent)
34 , _timer(new QTimer(this))
35 , _logEntriesModel(new QmlObjectListModel(this))
36{
37 qCDebug(OnboardLogControllerLog) << this;
38
39 (void) connect(MultiVehicleManager::instance(), &MultiVehicleManager::activeVehicleChanged, this, &OnboardLogController::_setActiveVehicle);
40 (void) connect(_timer, &QTimer::timeout, this, &OnboardLogController::_processDownload);
41
42 _timer->setSingleShot(false);
43
44 _setActiveVehicle(MultiVehicleManager::instance()->activeVehicle());
45}
46
48{
49 qCDebug(OnboardLogControllerLog) << this;
50}
51
52void OnboardLogController::download(const QString &path)
53{
54 const QString dir = path.isEmpty() ? SettingsManager::instance()->appSettings()->logSavePath() : path;
55 if (_transport == Transport::Ftp) {
56 _ftpDownloadToDirectory(dir);
57 } else {
58 _downloadToDirectory(dir);
59 }
60}
61
62void OnboardLogController::_downloadToDirectory(const QString &dir)
63{
64 _receivedAllEntries();
65
66 _downloadData.reset();
67
68 _downloadPath = dir;
69 if (_downloadPath.isEmpty()) {
70 return;
71 }
72
73 if (!_downloadPath.endsWith(QDir::separator())) {
74 _downloadPath += QDir::separator();
75 }
76
77 QGCOnboardLogEntry *const log = _getNextSelected();
78 if (log) {
79 log->setStatus(tr("Waiting"));
80 }
81
82 _setDownloading(true);
83 _receivedAllData();
84}
85
86void OnboardLogController::_processDownload()
87{
88 if (_requestingLogEntries) {
89 _findMissingEntries();
90 } else if (_downloadingLogs) {
91 _findMissingData();
92 }
93}
94
95void OnboardLogController::_findMissingEntries()
96{
97 const int num_logs = _logEntriesModel->count();
98 int start = -1;
99 int end = -1;
100 for (int i = 0; i < num_logs; i++) {
101 const QGCOnboardLogEntry *const entry = _logEntriesModel->value<const QGCOnboardLogEntry*>(i);
102 if (!entry) {
103 continue;
104 }
105
106 if (!entry->received()) {
107 if (start < 0) {
108 start = i;
109 } else {
110 end = i;
111 }
112 } else if (start >= 0) {
113 break;
114 }
115 }
116
117 if (start < 0) {
118 _receivedAllEntries();
119 return;
120 }
121
122 if (_retries++ > 2) {
123 for (int i = 0; i < num_logs; i++) {
124 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
125 if (entry && !entry->received()) {
126 entry->setStatus(tr("Error"));
127 }
128 }
129
130 _receivedAllEntries();
131 qCWarning(OnboardLogControllerLog) << "Too many errors retreiving log list. Giving up.";
132 return;
133 }
134
135 if (end < 0) {
136 end = start;
137 }
138
139 start += _apmOffset;
140 end += _apmOffset;
141
142 _requestLogList(static_cast<uint32_t>(start), static_cast<uint32_t>(end));
143}
144
145void OnboardLogController::_setActiveVehicle(Vehicle *vehicle)
146{
147 if (vehicle == _vehicle) {
148 return;
149 }
150
151 if (_vehicle) {
152 // Tear down any in-progress messages-transport activity before the entries
153 // it references are deleted with the model
154 _timer->stop();
155 if (_downloadData) {
156 if (_downloadData->file.exists()) {
157 (void) _downloadData->file.remove();
158 }
159 _downloadData.reset();
160 }
161 _setDownloading(false);
162 _setListing(false);
163
164 _logEntriesModel->clearAndDeleteContents();
165 (void) disconnect(_vehicle, &Vehicle::logEntry, this, &OnboardLogController::_logEntry);
166 (void) disconnect(_vehicle, &Vehicle::logData, this, &OnboardLogController::_logData);
167
168 FTPManager *const ftp = _vehicle->ftpManager();
169 (void) disconnect(ftp, &FTPManager::listDirectoryComplete, this, &OnboardLogController::_ftpListDirComplete);
170 (void) disconnect(ftp, &FTPManager::downloadComplete, this, &OnboardLogController::_ftpDownloadComplete);
171 (void) disconnect(ftp, &FTPManager::commandProgress, this, &OnboardLogController::_ftpDownloadProgress);
172 (void) disconnect(ftp, &FTPManager::deleteComplete, this, &OnboardLogController::_ftpDeleteComplete);
173
174 _ftpListState = FtpListState::Idle;
175 _ftpDirsToList.clear();
176 _ftpLogIdCounter = 0;
177 _ftpDownloadQueue.clear();
178 _ftpDeleteQueue.clear();
179 _ftpDeleting = false;
180 _ftpCurrentDownloadEntry = nullptr;
181 _ftpDisabled = false;
182 _ftpDownloadHadError = false;
183 _setTransport(Transport::Messages);
184 }
185
186 _vehicle = vehicle;
187
188 if (_vehicle) {
189 (void) connect(_vehicle, &Vehicle::logEntry, this, &OnboardLogController::_logEntry);
190 (void) connect(_vehicle, &Vehicle::logData, this, &OnboardLogController::_logData);
191
192 FTPManager *const ftp = _vehicle->ftpManager();
193 (void) connect(ftp, &FTPManager::listDirectoryComplete, this, &OnboardLogController::_ftpListDirComplete);
194 (void) connect(ftp, &FTPManager::downloadComplete, this, &OnboardLogController::_ftpDownloadComplete);
195 (void) connect(ftp, &FTPManager::commandProgress, this, &OnboardLogController::_ftpDownloadProgress);
196 (void) connect(ftp, &FTPManager::deleteComplete, this, &OnboardLogController::_ftpDeleteComplete);
197 }
198}
199
200void OnboardLogController::_logEntry(uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t last_log_num)
201{
202 Q_UNUSED(last_log_num);
203
204 if (!_requestingLogEntries || (_transport != Transport::Messages)) {
205 return;
206 }
207
208 if ((_logEntriesModel->count() == 0) && (num_logs > 0)) {
209 if (_vehicle->firmwareType() == MAV_AUTOPILOT_ARDUPILOTMEGA) {
210 // APM ID starts at 1
211 _apmOffset = 1;
212 }
213
214 for (int i = 0; i < num_logs; i++) {
215 QGCOnboardLogEntry *const entry = new QGCOnboardLogEntry(i);
217 _logEntriesModel->append(entry);
218 }
219 }
220
221 if (num_logs > 0) {
222 if ((size > 0) || (_vehicle->firmwareType() != MAV_AUTOPILOT_ARDUPILOTMEGA)) {
223 id -= _apmOffset;
224 if (id < _logEntriesModel->count()) {
225 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(id);
226 entry->setSize(size);
227 entry->setTime(QDateTime::fromSecsSinceEpoch(time_utc));
228 entry->setReceived(true);
229 entry->setStatus(tr("Available"));
230 } else {
231 qCWarning(OnboardLogControllerLog) << "Received onboard log entry for out-of-bound index:" << id;
232 }
233 }
234 } else {
235 _receivedAllEntries();
236 }
237
238 _retries = 0;
239
240 if (_entriesComplete()) {
241 _receivedAllEntries();
242 } else {
243 _timer->start(kTimeOutMs);
244 }
245}
246
247void OnboardLogController::_receivedAllEntries()
248{
249 _timer->stop();
250 _setListing(false);
251}
252
253bool OnboardLogController::_entriesComplete() const
254{
255 const int num_logs = _logEntriesModel->count();
256 for (int i = 0; i < num_logs; i++) {
257 const QGCOnboardLogEntry *const entry = _logEntriesModel->value<const QGCOnboardLogEntry*>(i);
258 if (!entry) {
259 continue;
260 }
261
262 if (!entry->received()) {
263 return false;
264 }
265 }
266
267 return true;
268}
269
270void OnboardLogController::_logData(uint32_t ofs, uint16_t id, uint8_t count, const uint8_t *data)
271{
272 if (!_downloadingLogs || !_downloadData || (_transport != Transport::Messages)) {
273 return;
274 }
275
276 id -= _apmOffset;
277 if (_downloadData->ID != id) {
278 qCWarning(OnboardLogControllerLog) << "Received log data for wrong log";
279 return;
280 }
281
282 if ((ofs % MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN) != 0) {
283 qCWarning(OnboardLogControllerLog) << "Ignored misaligned incoming packet @" << ofs;
284 return;
285 }
286
287 bool result = false;
288 if (ofs <= _downloadData->entry->size()) {
289 const uint32_t chunk = ofs / OnboardLogDownloadData::kChunkSize;
290 // qCDebug(OnboardLogControllerLog) << "Received data - Offset:" << ofs << "Chunk:" << chunk;
291 if (chunk != _downloadData->current_chunk) {
292 qCWarning(OnboardLogControllerLog) << "Ignored packet for out of order chunk actual:expected" << chunk << _downloadData->current_chunk;
293 return;
294 }
295
296 const uint16_t bin = (ofs - (chunk * OnboardLogDownloadData::kChunkSize)) / MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN;
297 if (bin >= _downloadData->chunk_table.size()) {
298 qCWarning(OnboardLogControllerLog) << "Out of range bin received";
299 } else {
300 _downloadData->chunk_table.setBit(bin);
301 }
302
303 if (_downloadData->file.pos() != ofs) {
304 if (!_downloadData->file.seek(ofs)) {
305 qCWarning(OnboardLogControllerLog) << "Error while seeking log file offset";
306 return;
307 }
308 }
309
310 if (_downloadData->file.write(reinterpret_cast<const char*>(data), count)) {
311 _downloadData->written += count;
312 _downloadData->rate_bytes += count;
313 _updateDataRate();
314
315 result = true;
316 _retries = 0;
317
318 _timer->start(kTimeOutMs);
319 if (_logComplete()) {
320 _downloadData->entry->setStatus(tr("Downloaded"));
321 _receivedAllData();
322 } else if (_chunkComplete()) {
323 _downloadData->advanceChunk();
324 _requestLogData(_downloadData->ID,
325 _downloadData->current_chunk * OnboardLogDownloadData::kChunkSize,
326 _downloadData->chunk_table.size() * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
327 } else if ((bin < (_downloadData->chunk_table.size() - 1)) && _downloadData->chunk_table.at(bin + 1)) {
328 // Likely to be grabbing fragments and got to the end of a gap
329 _findMissingData();
330 }
331 } else {
332 qCWarning(OnboardLogControllerLog) << "Error while writing log file chunk";
333 }
334 } else {
335 qCWarning(OnboardLogControllerLog) << "Received log offset greater than expected";
336 }
337
338 if (!result) {
339 _downloadData->entry->setStatus(tr("Error"));
340 }
341}
342
343void OnboardLogController::_findMissingData()
344{
345 if (_logComplete()) {
346 _receivedAllData();
347 return;
348 }
349
350 if (_chunkComplete()) {
351 _downloadData->advanceChunk();
352 }
353
354 _retries++;
355
356 _updateDataRate();
357
358 uint16_t start = 0, end = 0;
359 const int size = _downloadData->chunk_table.size();
360 for (; start < size; start++) {
361 if (!_downloadData->chunk_table.testBit(start)) {
362 break;
363 }
364 }
365
366 for (end = start; end < size; end++) {
367 if (_downloadData->chunk_table.testBit(end)) {
368 break;
369 }
370 }
371
372 const uint32_t pos = (_downloadData->current_chunk * OnboardLogDownloadData::kChunkSize) + (start * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
373 const uint32_t len = (end - start) * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN;
374 _requestLogData(_downloadData->ID, pos, len, _retries);
375}
376
377void OnboardLogController::_updateDataRate()
378{
379 constexpr uint kSizeUpdateThreshold = 102400; // 0.1 MB
380 const bool timeThresholdMet = _downloadData->elapsed.elapsed() >= kGUIRateMs;
381 const bool sizeThresholdMet = (_downloadData->written - _downloadData->last_status_written) >= kSizeUpdateThreshold;
382
383 if (!timeThresholdMet && !sizeThresholdMet) {
384 return;
385 }
386
387 QString status;
388 if (timeThresholdMet) {
389 // Update both rate and size
390 const qreal rate = _downloadData->rate_bytes / (_downloadData->elapsed.elapsed() / 1000.0);
391 _downloadData->rate_avg = (_downloadData->rate_avg * 0.95) + (rate * 0.05);
392 _downloadData->rate_bytes = 0;
393
394 status = QStringLiteral("%1 (%2/s)").arg(QGC::bigSizeToString(_downloadData->written),
395 QGC::bigSizeToString(_downloadData->rate_avg));
396 _downloadData->elapsed.start();
397 } else {
398 // Update size only, keep previous rate
399 status = QStringLiteral("%1 (%2/s)").arg(QGC::bigSizeToString(_downloadData->written),
400 QGC::bigSizeToString(_downloadData->rate_avg));
401 }
402
403 _downloadData->entry->setStatus(status);
404 _downloadData->last_status_written = _downloadData->written;
405}
406
407bool OnboardLogController::_chunkComplete() const
408{
409 return _downloadData->chunkEquals(true);
410}
411
412bool OnboardLogController::_logComplete() const
413{
414 return (_chunkComplete() && ((_downloadData->current_chunk + 1) == _downloadData->numChunks()));
415}
416
417void OnboardLogController::_receivedAllData()
418{
419 _timer->stop();
420 if (_prepareLogDownload()) {
421 _requestLogData(_downloadData->ID, 0, _downloadData->chunk_table.size() * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
422 _timer->start(kTimeOutMs);
423 } else {
424 _resetSelection();
425 _setDownloading(false);
426 }
427}
428
429bool OnboardLogController::_prepareLogDownload()
430{
431 _downloadData.reset();
432
433 QGCOnboardLogEntry *const entry = _getNextSelected();
434 if (!entry) {
435 return false;
436 }
437
438 entry->setSelected(false);
439 emit selectionChanged();
440
441 const QString ftime = (entry->time().date().year() >= 2010) ? entry->time().toString(QStringLiteral("yyyy-M-d-hh-mm-ss")) : QStringLiteral("UnknownDate");
442
443 _downloadData = std::make_unique<OnboardLogDownloadData>(entry);
444 _downloadData->filename = QStringLiteral("log_") + QString::number(entry->id()) + "_" + ftime;
445
446 if (_vehicle->firmwareType() == MAV_AUTOPILOT_PX4) {
447 const QString loggerParam = QStringLiteral("SYS_LOGGER");
448 ParameterManager *const parameterManager = _vehicle->parameterManager();
449 Fact *const loggerFact = parameterManager->parameterExists(ParameterManager::defaultComponentId, loggerParam) ? parameterManager->getParameter(ParameterManager::defaultComponentId, loggerParam) : nullptr;
450 if (loggerFact && (loggerFact->rawValue().toInt() == 0)) {
451 _downloadData->filename += ".px4log";
452 } else {
453 _downloadData->filename += ".ulg";
454 }
455 } else {
456 _downloadData->filename += ".bin";
457 }
458
459 _downloadData->file.setFileName(_downloadPath + _downloadData->filename);
460
461 if (_downloadData->file.exists()) {
462 uint32_t numDups = 0;
463 const QStringList filename_spl = _downloadData->filename.split('.');
464 do {
465 numDups += 1;
466 const QString filename = filename_spl[0] + '_' + QString::number(numDups) + '.' + filename_spl[1];
467 _downloadData->file.setFileName(_downloadPath + filename);
468 } while ( _downloadData->file.exists());
469 }
470
471 bool result = false;
472 if (!_downloadData->file.open(QIODevice::WriteOnly)) {
473 qCWarning(OnboardLogControllerLog) << "Failed to create log file:" << _downloadData->filename;
474 } else if (!_downloadData->file.resize(entry->size())) {
475 qCWarning(OnboardLogControllerLog) << "Failed to allocate space for log file:" << _downloadData->filename;
476 } else {
477 _downloadData->current_chunk = 0;
478 _downloadData->chunk_table = QBitArray(_downloadData->chunkBins(), false);
479 _downloadData->elapsed.start();
480 result = true;
481 }
482
483 if (!result) {
484 if (_downloadData->file.exists()) {
485 (void) _downloadData->file.remove();
486 }
487
488 _downloadData->entry->setStatus(tr("Error"));
489 _downloadData.reset();
490 }
491
492 return result;
493}
494
496{
497 _logEntriesModel->clearAndDeleteContents();
498 emit selectionChanged();
499
500 if (_vehicle && !_ftpDisabled && _vehicle->capabilitiesKnown() && (_vehicle->capabilityBits() & MAV_PROTOCOL_CAPABILITY_FTP)) {
501 qCDebug(OnboardLogControllerLog) << "refresh: using ftp transport";
502 _setTransport(Transport::Ftp);
503 _ftpStartListing();
504 } else {
505 qCDebug(OnboardLogControllerLog) << "refresh: using message transport - ftpDisabled:" << _ftpDisabled
506 << "capabilitiesKnown:" << (_vehicle && _vehicle->capabilitiesKnown())
507 << "ftpCapable:" << bool(_vehicle && (_vehicle->capabilityBits() & MAV_PROTOCOL_CAPABILITY_FTP));
508 _setTransport(Transport::Messages);
509 _requestLogList(0, 0xffff);
510 }
511}
512
513QGCOnboardLogEntry *OnboardLogController::_getNextSelected() const
514{
515 const int numLogs = _logEntriesModel->count();
516 for (int i = 0; i < numLogs; i++) {
517 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
518 if (!entry) {
519 continue;
520 }
521
522 if (entry->selected()) {
523 return entry;
524 }
525 }
526
527 return nullptr;
528}
529
531{
532 if (_transport == Transport::Ftp) {
533 if (_vehicle) {
534 if (_requestingLogEntries) {
535 // Idle first: cancelListDirectory() completes synchronously and the abort
536 // completion must not start the fallback-root listing
537 _ftpListState = FtpListState::Idle;
538 _ftpDirsToList.clear();
539 _vehicle->ftpManager()->cancelListDirectory();
540 _setListing(false);
541 }
542
543 if (_ftpDeleting) {
544 // The in-flight delete can't be aborted. Drop the queued deletes and let
545 // _ftpDeleteComplete finish the cycle with an automatic refresh.
546 _ftpDeleteQueue.clear();
547 _resetSelection(true);
548 return;
549 }
550
551 if (_downloadingLogs) {
552 _vehicle->ftpManager()->cancelDownload();
553 if (_ftpCurrentDownloadEntry) {
554 _ftpCurrentDownloadEntry->setStatus(tr("Canceled"));
555 _ftpCurrentDownloadEntry = nullptr;
556 }
557 _ftpDownloadQueue.clear();
558 }
559 }
560 } else {
561 _requestLogEnd();
562 _receivedAllEntries();
563
564 if (_downloadData) {
565 _downloadData->entry->setStatus(tr("Canceled"));
566 if (_downloadData->file.exists()) {
567 (void) _downloadData->file.remove();
568 }
569
570 _downloadData.reset();
571 }
572 }
573
574 _resetSelection(true);
575 _setDownloading(false);
576}
577
579{
580 const int count = _logEntriesModel->count();
581 for (int i = 0; i < count; i++) {
582 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
583 if (!entry || !entry->received()) {
584 continue;
585 }
586
587 if (entry->selected() != select) {
588 // Suppress the per-entry connection to avoid O(n²) allLogsSelected()
589 // re-evaluations. The entry still notifies its own QML bindings.
590 // A single selectionChanged() is emitted after the loop.
592 entry->setSelected(select);
594 }
595 }
596 emit selectionChanged();
597}
598
600{
601 int selected = 0;
602 const int count = _logEntriesModel->count();
603 for (int i = 0; i < count; i++) {
604 const QGCOnboardLogEntry *const entry = _logEntriesModel->value<const QGCOnboardLogEntry*>(i);
605 if (entry && entry->received() && entry->selected()) {
606 selected++;
607 }
608 }
609
610 return selected;
611}
612
614{
615 int selectable = 0;
616 int selected = 0;
617 const int count = _logEntriesModel->count();
618 for (int i = 0; i < count; i++) {
619 const QGCOnboardLogEntry *const entry = _logEntriesModel->value<const QGCOnboardLogEntry*>(i);
620 if (entry && entry->received()) {
621 selectable++;
622 if (entry->selected()) {
623 selected++;
624 }
625 }
626 }
627
628 return (selectable > 0) && (selected == selectable);
629}
630
632{
633 setSortAscending(!_sortAscending);
634}
635
637{
638 if (_sortAscending == ascending) {
639 return;
640 }
641
642 _sortAscending = ascending;
643 _sortEntriesByTimestamp();
645}
646
647void OnboardLogController::_resetSelection(bool canceled)
648{
649 const int num_logs = _logEntriesModel->count();
650 for (int i = 0; i < num_logs; i++) {
651 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
652 if (!entry) {
653 continue;
654 }
655
656 if (entry->selected()) {
657 if (canceled) {
658 entry->setStatus(tr("Canceled"));
659 }
660 entry->setSelected(false);
661 }
662 }
663
664 emit selectionChanged();
665}
666
667void OnboardLogController::_sortEntriesByTimestamp()
668{
669 QObjectList sortedEntries = *_logEntriesModel->objectList();
670 std::stable_sort(sortedEntries.begin(), sortedEntries.end(), [this](const QObject *lhsObj, const QObject *rhsObj) {
671 const QGCOnboardLogEntry *const lhs = qobject_cast<const QGCOnboardLogEntry*>(lhsObj);
672 const QGCOnboardLogEntry *const rhs = qobject_cast<const QGCOnboardLogEntry*>(rhsObj);
673 if (lhs == rhs) {
674 return false;
675 }
676 if (!lhs) {
677 return false;
678 }
679 if (!rhs) {
680 return true;
681 }
682
683 const bool lhsHasTime = lhs->received() && (lhs->time().toSecsSinceEpoch() > 0);
684 const bool rhsHasTime = rhs->received() && (rhs->time().toSecsSinceEpoch() > 0);
685 if (lhsHasTime != rhsHasTime) {
686 // Keep entries with valid timestamps grouped first.
687 return lhsHasTime;
688 }
689
690 if (lhsHasTime && rhsHasTime) {
691 if (lhs->time() == rhs->time()) {
692 return _sortAscending ? (lhs->id() < rhs->id()) : (lhs->id() > rhs->id());
693 }
694
695 return _sortAscending ? (lhs->time() < rhs->time()) : (lhs->time() > rhs->time());
696 }
697
698 // Fallback for entries with missing/invalid time.
699 return _sortAscending ? (lhs->id() < rhs->id()) : (lhs->id() > rhs->id());
700 });
701
702 (void) _logEntriesModel->swapObjectList(sortedEntries);
703}
704
706{
707 if (!_vehicle) {
708 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
709 return;
710 }
711
712 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
713 if (!sharedLink) {
714 qCWarning(OnboardLogControllerLog) << "Link Unavailable";
715 return;
716 }
717
718 mavlink_message_t msg{};
719 (void) mavlink_msg_log_erase_pack_chan(
722 sharedLink->mavlinkChannel(),
723 &msg,
724 _vehicle->id(),
725 _vehicle->defaultComponentId()
726 );
727
728 if (!_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg)) {
729 qCWarning(OnboardLogControllerLog) << "Failed to send";
730 return;
731 }
732
733 refresh();
734}
735
737{
738 if (!_vehicle) {
739 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
740 return;
741 }
742
743 if (_transport != Transport::Ftp) {
744 qCWarning(OnboardLogControllerLog) << "ftp: selective erase requires the FTP transport";
745 return;
746 }
747
748 _ftpDeleteQueue.clear();
749 const int numLogs = _logEntriesModel->count();
750 for (int i = 0; i < numLogs; i++) {
751 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
752 if (entry && entry->selected() && !entry->ftpPath().isEmpty()) {
753 _ftpDeleteQueue.enqueue(entry);
754 }
755 }
756
757 if (_ftpDeleteQueue.isEmpty()) {
758 qCWarning(OnboardLogControllerLog) << "ftp: no selected logs have FTP paths for erase";
759 return;
760 }
761
762 qCDebug(OnboardLogControllerLog) << "ftp: erasing" << _ftpDeleteQueue.size() << "selected logs";
763 _ftpDeleting = true;
764 _setDownloading(true);
765 _ftpDeleteNext();
766}
767
768void OnboardLogController::_ftpDeleteNext()
769{
770 if (_ftpDeleteQueue.isEmpty()) {
771 _ftpDeleting = false;
772 _setDownloading(false);
773 refresh();
774 return;
775 }
776
777 QGCOnboardLogEntry *const entry = _ftpDeleteQueue.dequeue();
778 entry->setSelected(false);
779 entry->setStatus(tr("Erasing"));
780
781 qCDebug(OnboardLogControllerLog) << "ftp: deleting" << entry->ftpPath();
782
783 if (!_vehicle->ftpManager()->deleteFile(MAV_COMP_ID_AUTOPILOT1, entry->ftpPath())) {
784 qCWarning(OnboardLogControllerLog) << "ftp: failed to start delete for" << entry->ftpPath();
785 entry->setStatus(tr("Error"));
786 _ftpDeleteNext();
787 }
788}
789
790void OnboardLogController::_ftpDeleteComplete(const QString &file, const QString &errorMsg)
791{
792 if (!_ftpDeleting) {
793 return;
794 }
795
796 if (!errorMsg.isEmpty()) {
797 qCWarning(OnboardLogControllerLog) << "ftp: delete error:" << file << errorMsg;
798 }
799
800 _ftpDeleteNext();
801}
802
803void OnboardLogController::_requestLogList(uint32_t start, uint32_t end)
804{
805 if (!_vehicle) {
806 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
807 return;
808 }
809
810 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
811 if (!sharedLink) {
812 qCWarning(OnboardLogControllerLog) << "Link Unavailable";
813 return;
814 }
815
816 mavlink_message_t msg{};
817 (void) mavlink_msg_log_request_list_pack_chan(
820 sharedLink->mavlinkChannel(),
821 &msg,
822 _vehicle->id(),
823 _vehicle->defaultComponentId(),
824 start,
825 end
826 );
827
828 if (!_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg)) {
829 qCWarning(OnboardLogControllerLog) << "Failed to send";
830 return;
831 }
832
833 qCDebug(OnboardLogControllerLog) << "Request onboard log entry list (" << start << "through" << end << ")";
834 _setListing(true);
835 _timer->start(kRequestLogListTimeoutMs);
836}
837
838void OnboardLogController::_requestLogData(uint16_t id, uint32_t offset, uint32_t count, int retryCount)
839{
840 if (!_vehicle) {
841 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
842 return;
843 }
844
845 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
846 if (!sharedLink) {
847 qCWarning(OnboardLogControllerLog) << "Link Unavailable";
848 return;
849 }
850
851 id += _apmOffset;
852 qCDebug(OnboardLogControllerLog) << "Request log data (id:" << id << "offset:" << offset << "size:" << count << "retryCount" << retryCount << ")";
853
854 mavlink_message_t msg{};
855 (void) mavlink_msg_log_request_data_pack_chan(
858 sharedLink->mavlinkChannel(),
859 &msg,
860 _vehicle->id(),
861 _vehicle->defaultComponentId(),
862 id,
863 offset,
864 count
865 );
866
867 if (!_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg)) {
868 qCWarning(OnboardLogControllerLog) << "Failed to send";
869 }
870}
871
872void OnboardLogController::_requestLogEnd()
873{
874 if (!_vehicle) {
875 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
876 return;
877 }
878
879 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
880 if (!sharedLink) {
881 qCWarning(OnboardLogControllerLog) << "Link Unavailable";
882 return;
883 }
884
885 mavlink_message_t msg{};
886 (void) mavlink_msg_log_request_end_pack_chan(
889 sharedLink->mavlinkChannel(),
890 &msg,
891 _vehicle->id(),
892 _vehicle->defaultComponentId()
893 );
894
895 if (!_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg)) {
896 qCWarning(OnboardLogControllerLog) << "Failed to send";
897 }
898}
899
900void OnboardLogController::_setDownloading(bool active)
901{
902 if (_downloadingLogs != active) {
903 _downloadingLogs = active;
904 if (_vehicle) {
906 }
908 }
909}
910
911void OnboardLogController::_setListing(bool active)
912{
913 if (_requestingLogEntries != active) {
914 _requestingLogEntries = active;
915 if (_vehicle) {
917 }
918 if (!active) {
919 _sortEntriesByTimestamp();
920 }
922 }
923}
924
925void OnboardLogController::_setTransport(Transport transport)
926{
927 if (_transport != transport) {
928 _transport = transport;
929 emit transportChanged();
930 }
931}
932
933void OnboardLogController::_ftpStartListing()
934{
935 _ftpDirsToList.clear();
936 _ftpLogIdCounter = 0;
937 _ftpLogRoot = QString::fromLatin1(kMavlinkLogRoot);
938 _ftpTriedFallbackRoot = false;
939
940 _setListing(true);
941 _ftpListRoot();
942}
943
944void OnboardLogController::_ftpListRoot()
945{
946 _ftpListState = FtpListState::ListingRoot;
947
948 qCDebug(OnboardLogControllerLog) << "ftp: listing root" << _ftpLogRoot;
949
950 if (!_vehicle->ftpManager()->listDirectory(MAV_COMP_ID_AUTOPILOT1, _ftpLogRoot)) {
951 qCWarning(OnboardLogControllerLog) << "ftp: failed to start root listing for" << _ftpLogRoot;
952 _ftpFallbackToMessages();
953 }
954}
955
956void OnboardLogController::_ftpListDirComplete(const QStringList &dirList, const QString &errorMsg)
957{
958 if (_ftpListState == FtpListState::Idle) {
959 return;
960 }
961
962 if (!errorMsg.isEmpty()) {
963 if ((_ftpListState == FtpListState::ListingRoot) && !_ftpTriedFallbackRoot && _vehicle) {
964 const char *fallback = nullptr;
965 if (_vehicle->px4Firmware()) {
966 fallback = kPx4LogRootFallback;
967 } else if (_vehicle->apmFirmware()) {
968 fallback = kApmLogRootFallback;
969 }
970
971 if (fallback) {
972 qCDebug(OnboardLogControllerLog) << "ftp: root listing of" << _ftpLogRoot << "failed (" << errorMsg
973 << "), falling back to" << fallback;
974 _ftpTriedFallbackRoot = true;
975 _ftpLogRoot = QString::fromLatin1(fallback);
976 _ftpListRoot();
977 return;
978 }
979 }
980
981 qCWarning(OnboardLogControllerLog) << "ftp: listing error:" << errorMsg;
982 _ftpFallbackToMessages();
983 return;
984 }
985
986 // Raw entries expose whether the server included the optional mtime field (date/time diagnosis)
987 qCDebug(OnboardLogControllerLog) << "ftp: raw entries for"
988 << ((_ftpListState == FtpListState::ListingRoot) ? _ftpLogRoot : (_ftpDirsToList.isEmpty() ? QString() : _ftpDirsToList.first()))
989 << dirList;
990
991 if (_ftpListState == FtpListState::ListingRoot) {
992 // The root listing may contain log files directly (flat layout, e.g. @MAV_LOG)
993 // and/or date subdirectories to descend into (PX4 fallback /fs/microsd/log).
994 const uint flatLogs = _ftpProcessFileEntries(dirList, QString());
995
996 for (const QString &entry : dirList) {
997 if (entry.startsWith(QLatin1Char('D'))) {
998 const QString dirName = entry.mid(1);
999 if (!dirName.isEmpty()) {
1000 _ftpDirsToList.append(dirName);
1001 }
1002 }
1003 }
1004
1005 _ftpDirsToList.sort();
1006 qCDebug(OnboardLogControllerLog) << "ftp: root listing of" << _ftpLogRoot
1007 << "found" << flatLogs << "flat logs and" << _ftpDirsToList.size() << "subdirectories";
1008
1009 _ftpListState = FtpListState::ListingSubdir;
1010 _ftpListNextSubdir();
1011 return;
1012 }
1013
1014 const QString currentDir = _ftpDirsToList.isEmpty() ? QString() : _ftpDirsToList.first();
1015 const uint logsFoundInDir = _ftpProcessFileEntries(dirList, currentDir);
1016
1017 qCDebug(OnboardLogControllerLog) << "ftp:" << currentDir << "->" << logsFoundInDir << "logs";
1018
1019 if (!_ftpDirsToList.isEmpty()) {
1020 _ftpDirsToList.removeFirst();
1021 }
1022
1023 _ftpListNextSubdir();
1024}
1025
1026uint OnboardLogController::_ftpProcessFileEntries(const QStringList &dirList, const QString &subdir)
1027{
1028 const QDate dirDate = subdir.isEmpty() ? QDate() : QDate::fromString(subdir, QStringLiteral("yyyy-MM-dd"));
1029 uint logsFound = 0;
1030
1031 for (const QString &entry : dirList) {
1032 if (!entry.startsWith(QLatin1Char('F'))) {
1033 continue;
1034 }
1035
1036 // Entry format is "F<name>\t<size>" and, when the server supports kCmdListDirectoryWithTime,
1037 // "F<name>\t<size>\t<modification time in seconds since UNIX epoch UTC>".
1038 const QString fileInfo = entry.mid(1);
1039 const int tabIdx = fileInfo.indexOf(QLatin1Char('\t'));
1040 if (tabIdx < 0) {
1041 continue;
1042 }
1043
1044 const QString fileName = fileInfo.left(tabIdx);
1045 const QString sizeStr = fileInfo.section(QLatin1Char('\t'), 1, 1);
1046 const QString mtimeStr = fileInfo.section(QLatin1Char('\t'), 2, 2);
1047
1048 if (!fileName.endsWith(QStringLiteral(".ulg"), Qt::CaseInsensitive) &&
1049 !fileName.endsWith(QStringLiteral(".bin"), Qt::CaseInsensitive)) {
1050 continue;
1051 }
1052
1053 bool sizeOk = false;
1054 const uint fileSize = sizeStr.toUInt(&sizeOk);
1055 if (!sizeOk) {
1056 continue;
1057 }
1058
1059 QDateTime dateTime;
1060
1061 // Prefer the modification time reported by the vehicle when available (0 means unknown).
1062 bool mtimeOk = false;
1063 const qint64 mtimeSecs = mtimeStr.toLongLong(&mtimeOk);
1064 if (mtimeOk && (mtimeSecs > 0)) {
1065 dateTime = QDateTime::fromSecsSinceEpoch(mtimeSecs, QTimeZone::UTC);
1066 }
1067
1068 // Otherwise reconstruct the date from the date sub-directory name and the filename time.
1069 if (!dateTime.isValid() && dirDate.isValid()) {
1070 const QString baseName = fileName.left(fileName.lastIndexOf(QLatin1Char('.')));
1071 const QTime fileTime = QTime::fromString(baseName, QStringLiteral("HH_mm_ss"));
1072 if (fileTime.isValid()) {
1073 dateTime = QDateTime(dirDate, fileTime, QTimeZone::UTC);
1074 } else {
1075 dateTime = QDateTime(dirDate, QTime(), QTimeZone::UTC);
1076 }
1077 }
1078
1079 const QString ftpPath = subdir.isEmpty()
1080 ? (_ftpLogRoot + QStringLiteral("/") + fileName)
1081 : (_ftpLogRoot + QStringLiteral("/") + subdir + QStringLiteral("/") + fileName);
1082
1083 QGCOnboardLogEntry *const logEntry = new QGCOnboardLogEntry(_ftpLogIdCounter++, dateTime, fileSize, true);
1084 logEntry->setFtpPath(ftpPath);
1085 logEntry->setStatus(tr("Available"));
1087 _logEntriesModel->append(logEntry);
1088 logsFound++;
1089 }
1090
1091 return logsFound;
1092}
1093
1094void OnboardLogController::_ftpListNextSubdir()
1095{
1096 if (_ftpDirsToList.isEmpty()) {
1097 qCDebug(OnboardLogControllerLog) << "ftp: listing complete, found" << _logEntriesModel->count() << "logs";
1098 _ftpFinishListing();
1099 return;
1100 }
1101
1102 const QString subdir = _ftpDirsToList.first();
1103 const QString path = _ftpLogRoot + QStringLiteral("/") + subdir;
1104
1105 qCDebug(OnboardLogControllerLog) << "ftp: listing subdir" << path;
1106
1107 if (!_vehicle->ftpManager()->listDirectory(MAV_COMP_ID_AUTOPILOT1, path)) {
1108 qCWarning(OnboardLogControllerLog) << "ftp: failed to list subdir" << path;
1109 _ftpDirsToList.removeFirst();
1110 _ftpListNextSubdir();
1111 }
1112}
1113
1114void OnboardLogController::_ftpFinishListing()
1115{
1116 _ftpListState = FtpListState::Idle;
1117
1118 // Firmware which NAKs kCmdListDirectoryWithTime (PX4 <= 1.17) reports no modification times
1119 // over FTP. Fall back to the message based transport where LOG_ENTRY reports the dates (issue #14789).
1120 if (_vehicle && _vehicle->ftpManager()->listDirectoryWithTimeUnsupported()) {
1121 _ftpFallbackToMessages();
1122 return;
1123 }
1124
1125 _setListing(false);
1126}
1127
1128void OnboardLogController::_ftpFallbackToMessages()
1129{
1130 qCDebug(OnboardLogControllerLog) << "ftp: transport failed, falling back to message based log download";
1131
1132 _ftpListState = FtpListState::Idle;
1133 _ftpDirsToList.clear();
1134 _ftpDisabled = true;
1135 _setTransport(Transport::Messages);
1136
1137 _logEntriesModel->clearAndDeleteContents();
1138 emit selectionChanged();
1139
1140 // Listing state stays active across the transport switch so the UI sees a single refresh
1141 _requestLogList(0, 0xffff);
1142}
1143
1144void OnboardLogController::_ftpDownloadToDirectory(const QString &dir)
1145{
1146 _downloadPath = dir;
1147 if (_downloadPath.isEmpty()) {
1148 return;
1149 }
1150
1151 if (!_downloadPath.endsWith(QDir::separator())) {
1152 _downloadPath += QDir::separator();
1153 }
1154
1155 _ftpDownloadQueue.clear();
1156 _ftpDownloadHadError = false;
1157 const int numLogs = _logEntriesModel->count();
1158 for (int i = 0; i < numLogs; i++) {
1159 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
1160 if (entry && entry->selected() && !entry->ftpPath().isEmpty()) {
1161 entry->setStatus(tr("Waiting"));
1162 _ftpDownloadQueue.enqueue(entry);
1163 }
1164 }
1165
1166 if (_ftpDownloadQueue.isEmpty()) {
1167 qCWarning(OnboardLogControllerLog) << "ftp: no selected logs have FTP paths for download";
1168 return;
1169 }
1170
1171 qCDebug(OnboardLogControllerLog) << "ftp: queued" << _ftpDownloadQueue.size() << "logs for download to" << _downloadPath;
1172 _setDownloading(true);
1173
1174 _ftpDownloadEntry(_ftpDownloadQueue.dequeue());
1175}
1176
1177void OnboardLogController::_ftpDownloadEntry(QGCOnboardLogEntry *entry)
1178{
1179 if (!entry || !_vehicle) {
1180 return;
1181 }
1182
1183 entry->setSelected(false);
1184
1185 _ftpCurrentDownloadEntry = entry;
1186 _ftpDownloadBytesAtLastUpdate = 0;
1187 _ftpDownloadRateAvg = 0.;
1188 _ftpDownloadElapsed.start();
1189
1190 entry->setStatus(tr("Downloading"));
1191
1192 // Save under the remote log file name
1193 QString localFilename = entry->ftpPath().section(QLatin1Char('/'), -1);
1194 if (localFilename.isEmpty()) {
1195 localFilename = QStringLiteral("log_") + QString::number(entry->id()) + QStringLiteral(".ulg");
1196 }
1197
1198 if (QFile::exists(_downloadPath + localFilename)) {
1199 const int dotIdx = localFilename.lastIndexOf(QLatin1Char('.'));
1200 const QString base = (dotIdx > 0) ? localFilename.left(dotIdx) : localFilename;
1201 const QString ext = (dotIdx > 0) ? localFilename.mid(dotIdx) : QString();
1202 uint numDups = 0;
1203 do {
1204 numDups++;
1205 localFilename = base + QStringLiteral("_") + QString::number(numDups) + ext;
1206 } while (QFile::exists(_downloadPath + localFilename));
1207 }
1208
1209 qCDebug(OnboardLogControllerLog) << "ftp: downloading" << entry->ftpPath() << "to" << _downloadPath + localFilename;
1210
1211 if (!_vehicle->ftpManager()->download(MAV_COMP_ID_AUTOPILOT1, entry->ftpPath(), _downloadPath, localFilename, true)) {
1212 qCWarning(OnboardLogControllerLog) << "ftp: failed to start download for" << entry->ftpPath();
1213 entry->setStatus(tr("Error"));
1214 _ftpCurrentDownloadEntry = nullptr;
1215 _ftpDownloadHadError = true;
1216 _ftpDownloadQueueNext();
1217 }
1218}
1219
1220void OnboardLogController::_ftpDownloadQueueNext()
1221{
1222 if (!_ftpDownloadQueue.isEmpty()) {
1223 _ftpDownloadEntry(_ftpDownloadQueue.dequeue());
1224 return;
1225 }
1226
1227 if (_ftpDownloadHadError) {
1228 qCDebug(OnboardLogControllerLog) << "ftp: download errors occurred, using message based transport for subsequent refreshes";
1229 _ftpDisabled = true;
1230 }
1231
1232 _setDownloading(false);
1233}
1234
1235void OnboardLogController::_ftpDownloadComplete(const QString &file, const QString &errorMsg)
1236{
1237 if (!_ftpCurrentDownloadEntry) {
1238 return;
1239 }
1240
1241 if (errorMsg.isEmpty()) {
1242 _ftpCurrentDownloadEntry->setStatus(tr("Downloaded"));
1243 qCDebug(OnboardLogControllerLog) << "ftp: download complete" << file;
1244 } else {
1245 _ftpCurrentDownloadEntry->setStatus(tr("Error"));
1246 _ftpDownloadHadError = true;
1247 qCWarning(OnboardLogControllerLog) << "ftp: download error:" << errorMsg;
1248 }
1249
1250 _ftpCurrentDownloadEntry = nullptr;
1251 _ftpDownloadQueueNext();
1252}
1253
1254void OnboardLogController::_ftpDownloadProgress(float value)
1255{
1256 if (!_ftpCurrentDownloadEntry) {
1257 return;
1258 }
1259
1260 if (_ftpDownloadElapsed.elapsed() < kGUIRateMs) {
1261 return;
1262 }
1263
1264 const size_t totalBytes = static_cast<size_t>(static_cast<qreal>(_ftpCurrentDownloadEntry->size()) * static_cast<qreal>(value));
1265 if (totalBytes < _ftpDownloadBytesAtLastUpdate) {
1266 // Guard against non-monotonic progress which would underflow the unsigned delta
1267 _ftpDownloadBytesAtLastUpdate = totalBytes;
1268 return;
1269 }
1270
1271 const size_t bytesSinceLastUpdate = totalBytes - _ftpDownloadBytesAtLastUpdate;
1272 const qreal elapsedSec = _ftpDownloadElapsed.elapsed() / 1000.0;
1273 const qreal rate = (elapsedSec > 0) ? (bytesSinceLastUpdate / elapsedSec) : 0;
1274 _ftpDownloadRateAvg = (_ftpDownloadRateAvg * 0.95) + (rate * 0.05);
1275 _ftpDownloadBytesAtLastUpdate = totalBytes;
1276 _ftpDownloadElapsed.start();
1277
1278 const QString status = QStringLiteral("%1 (%2/s)").arg(
1279 QGC::bigSizeToString(totalBytes),
1280 QGC::bigSizeToString(_ftpDownloadRateAvg));
1281
1282 _ftpCurrentDownloadEntry->setStatus(status);
1283}
1284
1286{
1287 if (_compressLogs != compress) {
1288 _compressLogs = compress;
1289 emit compressLogsChanged();
1290 }
1291}
1292
1293bool OnboardLogController::compressLogFile(const QString &logPath)
1294{
1295 Q_UNUSED(logPath)
1296 qCWarning(OnboardLogControllerLog) << "Log compression not yet implemented (decompression-only API)";
1297 return false;
1298}
1299
1301{
1302 // Not implemented - compression API is decompression-only
1303}
1304
1305void OnboardLogController::_handleCompressionProgress(qreal progress)
1306{
1307 Q_UNUSED(progress)
1308 // Not implemented - compression API is decompression-only
1309}
1310
1311void OnboardLogController::_handleCompressionFinished(bool success)
1312{
1313 Q_UNUSED(success)
1314 // Not implemented - compression API is decompression-only
1315}
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
static constexpr const char * kApmLogRootFallback
static constexpr const char * kPx4LogRootFallback
static constexpr const char * kMavlinkLogRoot
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QString logSavePath()
bool listDirectoryWithTimeUnsupported() const
true when the vehicle NAK'ed kCmdListDirectoryWithTime, i.e. directory listings carry no modification...
Definition FTPManager.h:52
void cancelDownload()
bool listDirectory(uint8_t fromCompId, const QString &fromURI)
bool deleteFile(uint8_t fromCompId, const QString &fromURI)
void commandProgress(float value)
void downloadComplete(const QString &file, const QString &errorMsg)
void cancelListDirectory()
void deleteComplete(const QString &file, const QString &errorMsg)
bool download(uint8_t fromCompId, const QString &fromURI, const QString &toDir, const QString &fileName="", bool checksize=true)
Definition FTPManager.cc:30
void listDirectoryComplete(const QStringList &dirList, const QString &errorMsg)
A Fact is used to hold a single value within the system.
Definition Fact.h:17
QVariant rawValue() const
Definition Fact.h:90
static int getComponentId()
static MAVLinkProtocol * instance()
int getSystemId() const
static MultiVehicleManager * instance()
void activeVehicleChanged(Vehicle *activeVehicle)
QString transport() const
Transport currently used to list/download logs: "messages" (LOG_* messages) or "ftp" (MAVLink FTP)
Q_INVOKABLE void selectAll(bool select)
Q_INVOKABLE void eraseAll()
Q_INVOKABLE void eraseSelected()
Q_INVOKABLE void download(const QString &path=QString())
Q_INVOKABLE void toggleSortByDate()
void setCompressLogs(bool compress)
void setSortAscending(bool ascending)
Q_INVOKABLE bool compressLogFile(const QString &logPath)
Compress a single log file.
Q_INVOKABLE void cancelCompression()
Cancel compression.
bool parameterExists(int componentId, const QString &paramName) const
Fact * getParameter(int componentId, const QString &paramName)
static constexpr int defaultComponentId
bool received() const
void setStatus(const QString &stat)
void setFtpPath(const QString &path)
void setSelected(bool sel)
bool selected() const
void setSize(uint size)
void setTime(const QDateTime &date)
void setReceived(bool rec)
QString ftpPath() const
Remote path when the entry was listed via MAVLink FTP; empty for message-based entries.
QDateTime time() const
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
T value(int index) const
int count() const override final
void clearAndDeleteContents() override final
Clears the list and calls deleteLater on each entry.
QList< QObject * > * objectList()
static SettingsManager * instance()
AppSettings * appSettings() const
WeakLinkInterfacePtr primaryLink() const
void setCommunicationLostEnabled(bool communicationLostEnabled)
bool px4Firmware() const
Definition Vehicle.h:498
uint64_t capabilityBits() const
Definition Vehicle.h:709
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:579
void logEntry(uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t last_log_num)
MAV_AUTOPILOT firmwareType() const
Definition Vehicle.h:431
int id() const
Definition Vehicle.h:429
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1386
bool apmFirmware() const
Definition Vehicle.h:499
int defaultComponentId() const
Definition Vehicle.h:682
ParameterManager * parameterManager()
Definition Vehicle.h:577
FTPManager * ftpManager()
Definition Vehicle.h:580
bool capabilitiesKnown() const
Definition Vehicle.h:708
void logData(uint32_t ofs, uint16_t id, uint8_t count, const uint8_t *data)
Definition APM.h:4
QString bigSizeToString(quint64 size)
Byte size with unit: B, KB, MB, GB, TB. 1 fractional digit above 1 KB.
Definition QGCFormat.cc:20
static const uint32_t kChunkSize