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 while (_prepareLogDownload()) {
421 if (_downloadData->chunk_table.isEmpty()) {
422 // Nothing to request (0 byte log): _logComplete() can never become true and the
423 // vehicle never answers a zero-length request, so the download would stall
424 // forever (issue #15068). The empty file already exists.
425 _downloadData->entry->setStatus(tr("Downloaded"));
426 _downloadData.reset();
427 continue;
428 }
429
430 _requestLogData(_downloadData->ID, 0, _downloadData->chunk_table.size() * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
431 _timer->start(kTimeOutMs);
432 return;
433 }
434
435 _resetSelection();
436 _setDownloading(false);
437}
438
439bool OnboardLogController::_prepareLogDownload()
440{
441 _downloadData.reset();
442
443 QGCOnboardLogEntry *const entry = _getNextSelected();
444 if (!entry) {
445 return false;
446 }
447
448 entry->setSelected(false);
449 emit selectionChanged();
450
451 const QString ftime = (entry->time().date().year() >= 2010) ? entry->time().toString(QStringLiteral("yyyy-M-d-hh-mm-ss")) : QStringLiteral("UnknownDate");
452
453 _downloadData = std::make_unique<OnboardLogDownloadData>(entry);
454 _downloadData->filename = QStringLiteral("log_") + QString::number(entry->id()) + "_" + ftime;
455
456 if (_vehicle->firmwareType() == MAV_AUTOPILOT_PX4) {
457 const QString loggerParam = QStringLiteral("SYS_LOGGER");
458 ParameterManager *const parameterManager = _vehicle->parameterManager();
459 Fact *const loggerFact = parameterManager->parameterExists(ParameterManager::defaultComponentId, loggerParam) ? parameterManager->getParameter(ParameterManager::defaultComponentId, loggerParam) : nullptr;
460 if (loggerFact && (loggerFact->rawValue().toInt() == 0)) {
461 _downloadData->filename += ".px4log";
462 } else {
463 _downloadData->filename += ".ulg";
464 }
465 } else {
466 _downloadData->filename += ".bin";
467 }
468
469 _downloadData->file.setFileName(_downloadPath + _downloadData->filename);
470
471 if (_downloadData->file.exists()) {
472 uint32_t numDups = 0;
473 const QStringList filename_spl = _downloadData->filename.split('.');
474 do {
475 numDups += 1;
476 const QString filename = filename_spl[0] + '_' + QString::number(numDups) + '.' + filename_spl[1];
477 _downloadData->file.setFileName(_downloadPath + filename);
478 } while ( _downloadData->file.exists());
479 }
480
481 bool result = false;
482 if (!_downloadData->file.open(QIODevice::WriteOnly)) {
483 qCWarning(OnboardLogControllerLog) << "Failed to create log file:" << _downloadData->filename;
484 } else if (!_downloadData->file.resize(entry->size())) {
485 qCWarning(OnboardLogControllerLog) << "Failed to allocate space for log file:" << _downloadData->filename;
486 } else {
487 _downloadData->current_chunk = 0;
488 _downloadData->chunk_table = QBitArray(_downloadData->chunkBins(), false);
489 _downloadData->elapsed.start();
490 result = true;
491 }
492
493 if (!result) {
494 if (_downloadData->file.exists()) {
495 (void) _downloadData->file.remove();
496 }
497
498 _downloadData->entry->setStatus(tr("Error"));
499 _downloadData.reset();
500 }
501
502 return result;
503}
504
506{
507 if (_downloadingLogs || _requestingLogEntries || _ftpDeleting) {
508 // Re-entering the page while a transfer is active must not clear the model:
509 // the transfer holds pointers into it (issue #14881)
510 qCDebug(OnboardLogControllerLog) << "refresh: ignored - transfer in progress";
511 return;
512 }
513
514 _logEntriesModel->clearAndDeleteContents();
515 emit selectionChanged();
516
517 if (_vehicle && !_ftpDisabled && _vehicle->capabilitiesKnown() && (_vehicle->capabilityBits() & MAV_PROTOCOL_CAPABILITY_FTP)) {
518 qCDebug(OnboardLogControllerLog) << "refresh: using ftp transport";
519 _setTransport(Transport::Ftp);
520 _ftpStartListing();
521 } else {
522 qCDebug(OnboardLogControllerLog) << "refresh: using message transport - ftpDisabled:" << _ftpDisabled
523 << "capabilitiesKnown:" << (_vehicle && _vehicle->capabilitiesKnown())
524 << "ftpCapable:" << bool(_vehicle && (_vehicle->capabilityBits() & MAV_PROTOCOL_CAPABILITY_FTP));
525 _setTransport(Transport::Messages);
526 _requestLogList(0, 0xffff);
527 }
528}
529
530QGCOnboardLogEntry *OnboardLogController::_getNextSelected() const
531{
532 const int numLogs = _logEntriesModel->count();
533 for (int i = 0; i < numLogs; i++) {
534 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
535 if (!entry) {
536 continue;
537 }
538
539 if (entry->selected()) {
540 return entry;
541 }
542 }
543
544 return nullptr;
545}
546
548{
549 if (_transport == Transport::Ftp) {
550 if (_vehicle) {
551 if (_requestingLogEntries) {
552 // Idle first: cancelListDirectory() completes synchronously and the abort
553 // completion must not start the fallback-root listing
554 _ftpListState = FtpListState::Idle;
555 _ftpDirsToList.clear();
556 _vehicle->ftpManager()->cancelListDirectory();
557 _setListing(false);
558 }
559
560 if (_ftpDeleting) {
561 // The in-flight delete can't be aborted. Drop the queued deletes and let
562 // _ftpDeleteComplete finish the cycle with an automatic refresh.
563 _ftpDeleteQueue.clear();
564 _resetSelection(true);
565 return;
566 }
567
568 if (_downloadingLogs) {
569 // cancelDownload completes synchronously; detach the entry first so it is not stamped as an error
570 if (_ftpCurrentDownloadEntry) {
571 _ftpCurrentDownloadEntry->setStatus(tr("Canceled"));
572 _ftpCurrentDownloadEntry = nullptr;
573 }
574 _ftpDownloadQueue.clear();
575 _vehicle->ftpManager()->cancelDownload();
576 }
577 }
578 } else {
579 _requestLogEnd();
580 _receivedAllEntries();
581
582 if (_downloadData) {
583 _downloadData->entry->setStatus(tr("Canceled"));
584 if (_downloadData->file.exists()) {
585 (void) _downloadData->file.remove();
586 }
587
588 _downloadData.reset();
589 }
590 }
591
592 _resetSelection(true);
593 _setDownloading(false);
594}
595
597{
598 const int count = _logEntriesModel->count();
599 for (int i = 0; i < count; i++) {
600 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
601 if (!entry || !entry->received()) {
602 continue;
603 }
604
605 if (entry->selected() != select) {
606 // Suppress the per-entry connection to avoid O(n²) allLogsSelected()
607 // re-evaluations. The entry still notifies its own QML bindings.
608 // A single selectionChanged() is emitted after the loop.
610 entry->setSelected(select);
612 }
613 }
614 emit selectionChanged();
615}
616
618{
619 int selected = 0;
620 const int count = _logEntriesModel->count();
621 for (int i = 0; i < count; i++) {
622 const QGCOnboardLogEntry *const entry = _logEntriesModel->value<const QGCOnboardLogEntry*>(i);
623 if (entry && entry->received() && entry->selected()) {
624 selected++;
625 }
626 }
627
628 return selected;
629}
630
632{
633 int selectable = 0;
634 int selected = 0;
635 const int count = _logEntriesModel->count();
636 for (int i = 0; i < count; i++) {
637 const QGCOnboardLogEntry *const entry = _logEntriesModel->value<const QGCOnboardLogEntry*>(i);
638 if (entry && entry->received()) {
639 selectable++;
640 if (entry->selected()) {
641 selected++;
642 }
643 }
644 }
645
646 return (selectable > 0) && (selected == selectable);
647}
648
650{
651 setSortAscending(!_sortAscending);
652}
653
655{
656 if (_sortAscending == ascending) {
657 return;
658 }
659
660 _sortAscending = ascending;
661 _sortEntriesByTimestamp();
663}
664
665void OnboardLogController::_resetSelection(bool canceled)
666{
667 const int num_logs = _logEntriesModel->count();
668 for (int i = 0; i < num_logs; i++) {
669 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
670 if (!entry) {
671 continue;
672 }
673
674 if (entry->selected()) {
675 if (canceled) {
676 entry->setStatus(tr("Canceled"));
677 }
678 entry->setSelected(false);
679 }
680 }
681
682 emit selectionChanged();
683}
684
685void OnboardLogController::_sortEntriesByTimestamp()
686{
687 QObjectList sortedEntries = *_logEntriesModel->objectList();
688 std::stable_sort(sortedEntries.begin(), sortedEntries.end(), [this](const QObject *lhsObj, const QObject *rhsObj) {
689 const QGCOnboardLogEntry *const lhs = qobject_cast<const QGCOnboardLogEntry*>(lhsObj);
690 const QGCOnboardLogEntry *const rhs = qobject_cast<const QGCOnboardLogEntry*>(rhsObj);
691 if (lhs == rhs) {
692 return false;
693 }
694 if (!lhs) {
695 return false;
696 }
697 if (!rhs) {
698 return true;
699 }
700
701 const bool lhsHasTime = lhs->received() && (lhs->time().toSecsSinceEpoch() > 0);
702 const bool rhsHasTime = rhs->received() && (rhs->time().toSecsSinceEpoch() > 0);
703 if (lhsHasTime != rhsHasTime) {
704 // Keep entries with valid timestamps grouped first.
705 return lhsHasTime;
706 }
707
708 if (lhsHasTime && rhsHasTime) {
709 if (lhs->time() == rhs->time()) {
710 return _sortAscending ? (lhs->id() < rhs->id()) : (lhs->id() > rhs->id());
711 }
712
713 return _sortAscending ? (lhs->time() < rhs->time()) : (lhs->time() > rhs->time());
714 }
715
716 // Fallback for entries with missing/invalid time.
717 return _sortAscending ? (lhs->id() < rhs->id()) : (lhs->id() > rhs->id());
718 });
719
720 (void) _logEntriesModel->swapObjectList(sortedEntries);
721}
722
724{
725 if (!_vehicle) {
726 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
727 return;
728 }
729
730 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
731 if (!sharedLink) {
732 qCWarning(OnboardLogControllerLog) << "Link Unavailable";
733 return;
734 }
735
736 mavlink_message_t msg{};
737 (void) mavlink_msg_log_erase_pack_chan(
740 sharedLink->mavlinkChannel(),
741 &msg,
742 _vehicle->id(),
743 _vehicle->defaultComponentId()
744 );
745
746 if (!_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg)) {
747 qCWarning(OnboardLogControllerLog) << "Failed to send";
748 return;
749 }
750
751 refresh();
752}
753
755{
756 if (!_vehicle) {
757 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
758 return;
759 }
760
761 if (_transport != Transport::Ftp) {
762 qCWarning(OnboardLogControllerLog) << "ftp: selective erase requires the FTP transport";
763 return;
764 }
765
766 _ftpDeleteQueue.clear();
767 const int numLogs = _logEntriesModel->count();
768 for (int i = 0; i < numLogs; i++) {
769 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
770 if (entry && entry->selected() && !entry->ftpPath().isEmpty()) {
771 _ftpDeleteQueue.enqueue(entry);
772 }
773 }
774
775 if (_ftpDeleteQueue.isEmpty()) {
776 qCWarning(OnboardLogControllerLog) << "ftp: no selected logs have FTP paths for erase";
777 return;
778 }
779
780 qCDebug(OnboardLogControllerLog) << "ftp: erasing" << _ftpDeleteQueue.size() << "selected logs";
781 _ftpDeleting = true;
782 _setDownloading(true);
783 _ftpDeleteNext();
784}
785
786void OnboardLogController::_ftpDeleteNext()
787{
788 if (_ftpDeleteQueue.isEmpty()) {
789 _ftpDeleting = false;
790 _setDownloading(false);
791 refresh();
792 return;
793 }
794
795 QGCOnboardLogEntry *const entry = _ftpDeleteQueue.dequeue();
796 entry->setSelected(false);
797 entry->setStatus(tr("Erasing"));
798
799 qCDebug(OnboardLogControllerLog) << "ftp: deleting" << entry->ftpPath();
800
801 if (!_vehicle->ftpManager()->deleteFile(MAV_COMP_ID_AUTOPILOT1, entry->ftpPath())) {
802 qCWarning(OnboardLogControllerLog) << "ftp: failed to start delete for" << entry->ftpPath();
803 entry->setStatus(tr("Error"));
804 _ftpDeleteNext();
805 }
806}
807
808void OnboardLogController::_ftpDeleteComplete(const QString &file, const QString &errorMsg)
809{
810 if (!_ftpDeleting) {
811 return;
812 }
813
814 if (!errorMsg.isEmpty()) {
815 qCWarning(OnboardLogControllerLog) << "ftp: delete error:" << file << errorMsg;
816 }
817
818 _ftpDeleteNext();
819}
820
821void OnboardLogController::_requestLogList(uint32_t start, uint32_t end)
822{
823 if (!_vehicle) {
824 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
825 return;
826 }
827
828 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
829 if (!sharedLink) {
830 qCWarning(OnboardLogControllerLog) << "Link Unavailable";
831 return;
832 }
833
834 mavlink_message_t msg{};
835 (void) mavlink_msg_log_request_list_pack_chan(
838 sharedLink->mavlinkChannel(),
839 &msg,
840 _vehicle->id(),
841 _vehicle->defaultComponentId(),
842 start,
843 end
844 );
845
846 if (!_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg)) {
847 qCWarning(OnboardLogControllerLog) << "Failed to send";
848 return;
849 }
850
851 qCDebug(OnboardLogControllerLog) << "Request onboard log entry list (" << start << "through" << end << ")";
852 _setListing(true);
853 _timer->start(kRequestLogListTimeoutMs);
854}
855
856void OnboardLogController::_requestLogData(uint16_t id, uint32_t offset, uint32_t count, int retryCount)
857{
858 if (!_vehicle) {
859 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
860 return;
861 }
862
863 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
864 if (!sharedLink) {
865 qCWarning(OnboardLogControllerLog) << "Link Unavailable";
866 return;
867 }
868
869 id += _apmOffset;
870 qCDebug(OnboardLogControllerLog) << "Request log data (id:" << id << "offset:" << offset << "size:" << count << "retryCount" << retryCount << ")";
871
872 mavlink_message_t msg{};
873 (void) mavlink_msg_log_request_data_pack_chan(
876 sharedLink->mavlinkChannel(),
877 &msg,
878 _vehicle->id(),
879 _vehicle->defaultComponentId(),
880 id,
881 offset,
882 count
883 );
884
885 if (!_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg)) {
886 qCWarning(OnboardLogControllerLog) << "Failed to send";
887 }
888}
889
890void OnboardLogController::_requestLogEnd()
891{
892 if (!_vehicle) {
893 qCWarning(OnboardLogControllerLog) << "Vehicle Unavailable";
894 return;
895 }
896
897 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
898 if (!sharedLink) {
899 qCWarning(OnboardLogControllerLog) << "Link Unavailable";
900 return;
901 }
902
903 mavlink_message_t msg{};
904 (void) mavlink_msg_log_request_end_pack_chan(
907 sharedLink->mavlinkChannel(),
908 &msg,
909 _vehicle->id(),
910 _vehicle->defaultComponentId()
911 );
912
913 if (!_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg)) {
914 qCWarning(OnboardLogControllerLog) << "Failed to send";
915 }
916}
917
918void OnboardLogController::_setDownloading(bool active)
919{
920 if (_downloadingLogs != active) {
921 _downloadingLogs = active;
922 if (_vehicle) {
924 }
926 }
927}
928
929void OnboardLogController::_setListing(bool active)
930{
931 if (_requestingLogEntries != active) {
932 _requestingLogEntries = active;
933 if (_vehicle) {
935 }
936 if (!active) {
937 _sortEntriesByTimestamp();
938 }
940 }
941}
942
943void OnboardLogController::_setTransport(Transport transport)
944{
945 if (_transport != transport) {
946 _transport = transport;
947 emit transportChanged();
948 }
949}
950
951void OnboardLogController::_ftpStartListing()
952{
953 _ftpDirsToList.clear();
954 _ftpLogIdCounter = 0;
955 _ftpLogRoot = QString::fromLatin1(kMavlinkLogRoot);
956 _ftpTriedFallbackRoot = false;
957
958 _setListing(true);
959 _ftpListRoot();
960}
961
962void OnboardLogController::_ftpListRoot()
963{
964 _ftpListState = FtpListState::ListingRoot;
965
966 qCDebug(OnboardLogControllerLog) << "ftp: listing root" << _ftpLogRoot;
967
968 if (!_vehicle->ftpManager()->listDirectory(MAV_COMP_ID_AUTOPILOT1, _ftpLogRoot)) {
969 qCWarning(OnboardLogControllerLog) << "ftp: failed to start root listing for" << _ftpLogRoot;
970 _ftpFallbackToMessages();
971 }
972}
973
974void OnboardLogController::_ftpListDirComplete(const QStringList &dirList, const QString &errorMsg)
975{
976 if (_ftpListState == FtpListState::Idle) {
977 return;
978 }
979
980 if (!errorMsg.isEmpty()) {
981 if ((_ftpListState == FtpListState::ListingRoot) && !_ftpTriedFallbackRoot && _vehicle) {
982 const char *fallback = nullptr;
983 if (_vehicle->px4Firmware()) {
984 fallback = kPx4LogRootFallback;
985 } else if (_vehicle->apmFirmware()) {
986 fallback = kApmLogRootFallback;
987 }
988
989 if (fallback) {
990 qCDebug(OnboardLogControllerLog) << "ftp: root listing of" << _ftpLogRoot << "failed (" << errorMsg
991 << "), falling back to" << fallback;
992 _ftpTriedFallbackRoot = true;
993 _ftpLogRoot = QString::fromLatin1(fallback);
994 _ftpListRoot();
995 return;
996 }
997 }
998
999 qCWarning(OnboardLogControllerLog) << "ftp: listing error:" << errorMsg;
1000 _ftpFallbackToMessages();
1001 return;
1002 }
1003
1004 // Raw entries expose whether the server included the optional mtime field (date/time diagnosis)
1005 qCDebug(OnboardLogControllerLog) << "ftp: raw entries for"
1006 << ((_ftpListState == FtpListState::ListingRoot) ? _ftpLogRoot : (_ftpDirsToList.isEmpty() ? QString() : _ftpDirsToList.first()))
1007 << dirList;
1008
1009 if (_ftpListState == FtpListState::ListingRoot) {
1010 // The root listing may contain log files directly (flat layout, e.g. @MAV_LOG)
1011 // and/or date subdirectories to descend into (PX4 fallback /fs/microsd/log).
1012 const uint flatLogs = _ftpProcessFileEntries(dirList, QString());
1013
1014 // A kCmdListDirectoryWithTime listing may give a directory the same trailing
1015 // fields as a file: "D<name>\t<size>\t<modification time>". Servers differ on
1016 // whether they send them (MAVSDK does, PX4 does not), so tolerate either. A
1017 // plain kCmdListDirectory entry is just "D<name>", where a tab would be part
1018 // of the name, so only strip the fields when times were asked for.
1019 const bool withTime = _vehicle && !_vehicle->ftpManager()->listDirectoryWithTimeUnsupported();
1020
1021 for (const QString &entry : dirList) {
1022 if (entry.startsWith(QLatin1Char('D'))) {
1023 QString dirName = entry.mid(1);
1024 if (withTime) {
1025 dirName = dirName.section(QLatin1Char('\t'), 0, 0);
1026 }
1027 // Some servers list "." and ".."; descending into those would
1028 // list this directory again, or its parent (ArduPilot sends
1029 // them, PX4 does not).
1030 if (!dirName.isEmpty() &&
1031 (dirName != QStringLiteral(".")) && (dirName != QStringLiteral(".."))) {
1032 _ftpDirsToList.append(dirName);
1033 }
1034 }
1035 }
1036
1037 _ftpDirsToList.sort();
1038 qCDebug(OnboardLogControllerLog) << "ftp: root listing of" << _ftpLogRoot
1039 << "found" << flatLogs << "flat logs and" << _ftpDirsToList.size() << "subdirectories";
1040
1041 _ftpListState = FtpListState::ListingSubdir;
1042 _ftpListNextSubdir();
1043 return;
1044 }
1045
1046 const QString currentDir = _ftpDirsToList.isEmpty() ? QString() : _ftpDirsToList.first();
1047 const uint logsFoundInDir = _ftpProcessFileEntries(dirList, currentDir);
1048
1049 qCDebug(OnboardLogControllerLog) << "ftp:" << currentDir << "->" << logsFoundInDir << "logs";
1050
1051 if (!_ftpDirsToList.isEmpty()) {
1052 _ftpDirsToList.removeFirst();
1053 }
1054
1055 _ftpListNextSubdir();
1056}
1057
1058uint OnboardLogController::_ftpProcessFileEntries(const QStringList &dirList, const QString &subdir)
1059{
1060 const QDate dirDate = subdir.isEmpty() ? QDate() : QDate::fromString(subdir, QStringLiteral("yyyy-MM-dd"));
1061 uint logsFound = 0;
1062
1063 for (const QString &entry : dirList) {
1064 if (!entry.startsWith(QLatin1Char('F'))) {
1065 continue;
1066 }
1067
1068 // Entry format is "F<name>\t<size>" and, when the server supports kCmdListDirectoryWithTime,
1069 // "F<name>\t<size>\t<modification time in seconds since UNIX epoch UTC>".
1070 const QString fileInfo = entry.mid(1);
1071 const int tabIdx = fileInfo.indexOf(QLatin1Char('\t'));
1072 if (tabIdx < 0) {
1073 continue;
1074 }
1075
1076 const QString fileName = fileInfo.left(tabIdx);
1077 const QString sizeStr = fileInfo.section(QLatin1Char('\t'), 1, 1);
1078 const QString mtimeStr = fileInfo.section(QLatin1Char('\t'), 2, 2);
1079
1080 if (!fileName.endsWith(QStringLiteral(".ulg"), Qt::CaseInsensitive) &&
1081 !fileName.endsWith(QStringLiteral(".bin"), Qt::CaseInsensitive)) {
1082 continue;
1083 }
1084
1085 bool sizeOk = false;
1086 const uint fileSize = sizeStr.toUInt(&sizeOk);
1087 if (!sizeOk) {
1088 continue;
1089 }
1090
1091 QDateTime dateTime;
1092
1093 // Prefer the modification time reported by the vehicle when available (0 means unknown).
1094 bool mtimeOk = false;
1095 const qint64 mtimeSecs = mtimeStr.toLongLong(&mtimeOk);
1096 if (mtimeOk && (mtimeSecs > 0)) {
1097 dateTime = QDateTime::fromSecsSinceEpoch(mtimeSecs, QTimeZone::UTC);
1098 }
1099
1100 // Otherwise reconstruct the date from the date sub-directory name and the filename time.
1101 if (!dateTime.isValid() && dirDate.isValid()) {
1102 const QString baseName = fileName.left(fileName.lastIndexOf(QLatin1Char('.')));
1103 const QTime fileTime = QTime::fromString(baseName, QStringLiteral("HH_mm_ss"));
1104 if (fileTime.isValid()) {
1105 dateTime = QDateTime(dirDate, fileTime, QTimeZone::UTC);
1106 } else {
1107 dateTime = QDateTime(dirDate, QTime(), QTimeZone::UTC);
1108 }
1109 }
1110
1111 const QString ftpPath = subdir.isEmpty()
1112 ? (_ftpLogRoot + QStringLiteral("/") + fileName)
1113 : (_ftpLogRoot + QStringLiteral("/") + subdir + QStringLiteral("/") + fileName);
1114
1115 QGCOnboardLogEntry *const logEntry = new QGCOnboardLogEntry(_ftpLogIdCounter++, dateTime, fileSize, true);
1116 logEntry->setFtpPath(ftpPath);
1117 logEntry->setStatus(tr("Available"));
1119 _logEntriesModel->append(logEntry);
1120 logsFound++;
1121 }
1122
1123 return logsFound;
1124}
1125
1126void OnboardLogController::_ftpListNextSubdir()
1127{
1128 if (_ftpDirsToList.isEmpty()) {
1129 qCDebug(OnboardLogControllerLog) << "ftp: listing complete, found" << _logEntriesModel->count() << "logs";
1130 _ftpFinishListing();
1131 return;
1132 }
1133
1134 const QString subdir = _ftpDirsToList.first();
1135 const QString path = _ftpLogRoot + QStringLiteral("/") + subdir;
1136
1137 qCDebug(OnboardLogControllerLog) << "ftp: listing subdir" << path;
1138
1139 if (!_vehicle->ftpManager()->listDirectory(MAV_COMP_ID_AUTOPILOT1, path)) {
1140 qCWarning(OnboardLogControllerLog) << "ftp: failed to list subdir" << path;
1141 _ftpDirsToList.removeFirst();
1142 _ftpListNextSubdir();
1143 }
1144}
1145
1146void OnboardLogController::_ftpFinishListing()
1147{
1148 _ftpListState = FtpListState::Idle;
1149
1150 // Firmware which NAKs kCmdListDirectoryWithTime (PX4 <= 1.17) reports no modification times
1151 // over FTP. Fall back to the message based transport where LOG_ENTRY reports the dates (issue #14789).
1152 if (_vehicle && _vehicle->ftpManager()->listDirectoryWithTimeUnsupported()) {
1153 _ftpFallbackToMessages();
1154 return;
1155 }
1156
1157 _setListing(false);
1158}
1159
1160void OnboardLogController::_ftpFallbackToMessages()
1161{
1162 qCDebug(OnboardLogControllerLog) << "ftp: transport failed, falling back to message based log download";
1163
1164 _ftpListState = FtpListState::Idle;
1165 _ftpDirsToList.clear();
1166 _ftpDisabled = true;
1167 _setTransport(Transport::Messages);
1168
1169 _logEntriesModel->clearAndDeleteContents();
1170 emit selectionChanged();
1171
1172 // Listing state stays active across the transport switch so the UI sees a single refresh
1173 _requestLogList(0, 0xffff);
1174}
1175
1176void OnboardLogController::_ftpDownloadToDirectory(const QString &dir)
1177{
1178 _downloadPath = dir;
1179 if (_downloadPath.isEmpty()) {
1180 return;
1181 }
1182
1183 if (!_downloadPath.endsWith(QDir::separator())) {
1184 _downloadPath += QDir::separator();
1185 }
1186
1187 _ftpDownloadQueue.clear();
1188 _ftpDownloadHadError = false;
1189 const int numLogs = _logEntriesModel->count();
1190 for (int i = 0; i < numLogs; i++) {
1191 QGCOnboardLogEntry *const entry = _logEntriesModel->value<QGCOnboardLogEntry*>(i);
1192 if (entry && entry->selected() && !entry->ftpPath().isEmpty()) {
1193 entry->setStatus(tr("Waiting"));
1194 _ftpDownloadQueue.enqueue(entry);
1195 }
1196 }
1197
1198 if (_ftpDownloadQueue.isEmpty()) {
1199 qCWarning(OnboardLogControllerLog) << "ftp: no selected logs have FTP paths for download";
1200 return;
1201 }
1202
1203 qCDebug(OnboardLogControllerLog) << "ftp: queued" << _ftpDownloadQueue.size() << "logs for download to" << _downloadPath;
1204 _setDownloading(true);
1205
1206 _ftpDownloadEntry(_ftpDownloadQueue.dequeue());
1207}
1208
1209void OnboardLogController::_ftpDownloadEntry(QGCOnboardLogEntry *entry)
1210{
1211 if (!entry || !_vehicle) {
1212 return;
1213 }
1214
1215 entry->setSelected(false);
1216
1217 _ftpCurrentDownloadEntry = entry;
1218 _ftpDownloadBytesAtLastUpdate = 0;
1219 _ftpDownloadRateAvg = 0.;
1220 _ftpDownloadElapsed.start();
1221
1222 entry->setStatus(tr("Downloading"));
1223
1224 // Save under the remote log file name
1225 QString localFilename = entry->ftpPath().section(QLatin1Char('/'), -1);
1226 if (localFilename.isEmpty()) {
1227 localFilename = QStringLiteral("log_") + QString::number(entry->id()) + QStringLiteral(".ulg");
1228 }
1229
1230 if (QFile::exists(_downloadPath + localFilename)) {
1231 const int dotIdx = localFilename.lastIndexOf(QLatin1Char('.'));
1232 const QString base = (dotIdx > 0) ? localFilename.left(dotIdx) : localFilename;
1233 const QString ext = (dotIdx > 0) ? localFilename.mid(dotIdx) : QString();
1234 uint numDups = 0;
1235 do {
1236 numDups++;
1237 localFilename = base + QStringLiteral("_") + QString::number(numDups) + ext;
1238 } while (QFile::exists(_downloadPath + localFilename));
1239 }
1240
1241 qCDebug(OnboardLogControllerLog) << "ftp: downloading" << entry->ftpPath() << "to" << _downloadPath + localFilename;
1242
1243 if (!_vehicle->ftpManager()->download(MAV_COMP_ID_AUTOPILOT1, entry->ftpPath(), _downloadPath, localFilename, true)) {
1244 qCWarning(OnboardLogControllerLog) << "ftp: failed to start download for" << entry->ftpPath();
1245 entry->setStatus(tr("Error"));
1246 _ftpCurrentDownloadEntry = nullptr;
1247 _ftpDownloadHadError = true;
1248 _ftpDownloadQueueNext();
1249 }
1250}
1251
1252void OnboardLogController::_ftpDownloadQueueNext()
1253{
1254 if (!_ftpDownloadQueue.isEmpty()) {
1255 _ftpDownloadEntry(_ftpDownloadQueue.dequeue());
1256 return;
1257 }
1258
1259 if (_ftpDownloadHadError) {
1260 qCDebug(OnboardLogControllerLog) << "ftp: download errors occurred, using message based transport for subsequent refreshes";
1261 _ftpDisabled = true;
1262 }
1263
1264 _setDownloading(false);
1265}
1266
1267void OnboardLogController::_ftpDownloadComplete(const QString &file, const QString &errorMsg)
1268{
1269 if (!_ftpCurrentDownloadEntry) {
1270 return;
1271 }
1272
1273 if (errorMsg.isEmpty()) {
1274 _ftpCurrentDownloadEntry->setStatus(tr("Downloaded"));
1275 qCDebug(OnboardLogControllerLog) << "ftp: download complete" << file;
1276 } else {
1277 _ftpCurrentDownloadEntry->setStatus(tr("Error"));
1278 _ftpDownloadHadError = true;
1279 qCWarning(OnboardLogControllerLog) << "ftp: download error:" << errorMsg;
1280 }
1281
1282 _ftpCurrentDownloadEntry = nullptr;
1283 _ftpDownloadQueueNext();
1284}
1285
1286void OnboardLogController::_ftpDownloadProgress(float value)
1287{
1288 if (!_ftpCurrentDownloadEntry) {
1289 return;
1290 }
1291
1292 if (_ftpDownloadElapsed.elapsed() < kGUIRateMs) {
1293 return;
1294 }
1295
1296 const size_t totalBytes = static_cast<size_t>(static_cast<qreal>(_ftpCurrentDownloadEntry->size()) * static_cast<qreal>(value));
1297 if (totalBytes < _ftpDownloadBytesAtLastUpdate) {
1298 // Guard against non-monotonic progress which would underflow the unsigned delta
1299 _ftpDownloadBytesAtLastUpdate = totalBytes;
1300 return;
1301 }
1302
1303 const size_t bytesSinceLastUpdate = totalBytes - _ftpDownloadBytesAtLastUpdate;
1304 const qreal elapsedSec = _ftpDownloadElapsed.elapsed() / 1000.0;
1305 const qreal rate = (elapsedSec > 0) ? (bytesSinceLastUpdate / elapsedSec) : 0;
1306 _ftpDownloadRateAvg = (_ftpDownloadRateAvg * 0.95) + (rate * 0.05);
1307 _ftpDownloadBytesAtLastUpdate = totalBytes;
1308 _ftpDownloadElapsed.start();
1309
1310 const QString status = QStringLiteral("%1 (%2/s)").arg(
1311 QGC::bigSizeToString(totalBytes),
1312 QGC::bigSizeToString(_ftpDownloadRateAvg));
1313
1314 _ftpCurrentDownloadEntry->setStatus(status);
1315}
1316
1318{
1319 if (_compressLogs != compress) {
1320 _compressLogs = compress;
1321 emit compressLogsChanged();
1322 }
1323}
1324
1325bool OnboardLogController::compressLogFile(const QString &logPath)
1326{
1327 Q_UNUSED(logPath)
1328 qCWarning(OnboardLogControllerLog) << "Log compression not yet implemented (decompression-only API)";
1329 return false;
1330}
1331
1333{
1334 // Not implemented - compression API is decompression-only
1335}
1336
1337void OnboardLogController::_handleCompressionProgress(qreal progress)
1338{
1339 Q_UNUSED(progress)
1340 // Not implemented - compression API is decompression-only
1341}
1342
1343void OnboardLogController::_handleCompressionFinished(bool success)
1344{
1345 Q_UNUSED(success)
1346 // Not implemented - compression API is decompression-only
1347}
@ Transport
The link could not read, write, or change its baud rate.
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:32
void listDirectoryComplete(const QStringList &dirList, const QString &errorMsg)
A Fact is used to hold a single value within the system.
Definition Fact.h:15
QVariant rawValue() const
Definition Fact.h:87
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:512
uint64_t capabilityBits() const
Definition Vehicle.h:727
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:597
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:445
int id() const
Definition Vehicle.h:443
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1401
bool apmFirmware() const
Definition Vehicle.h:513
int defaultComponentId() const
Definition Vehicle.h:700
ParameterManager * parameterManager()
Definition Vehicle.h:595
FTPManager * ftpManager()
Definition Vehicle.h:598
bool capabilitiesKnown() const
Definition Vehicle.h:726
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
GPSCommandSequence base(QByteArrayView port, const GPSBaseStationConfig &base)
Streams RTCM3 corrections and the PVTGeodetic survey status over port in the mode base selects.
Definition SBFPlan.cc:49
static const uint32_t kChunkSize