QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
LogFileParser.cc
Go to the documentation of this file.
1#include "LogFileParser.h"
2
7
8#include <QtConcurrent/QtConcurrent>
9#include <QtCore/QFileInfo>
10#include <QtCore/QFutureWatcher>
11
12#include <algorithm>
13#include <cmath>
14#include <limits>
15
16QGC_LOGGING_CATEGORY(LogFileParserLog, "AnalyzeView.LogFileParser")
17
18namespace {
19
20LogParseResult _parseFile(const QString &filePath)
21{
22 const QString suffix = QFileInfo(filePath).suffix().toLower();
23
24 if (suffix == QStringLiteral("bin") || suffix == QStringLiteral("log")) {
25 return DataFlashParser::parseFile(filePath);
26 }
27
28 if (suffix == QStringLiteral("ulg")) {
29 return ULogParser::parseFile(filePath);
30 }
31
32 const QString fileTypeDescription = suffix.isEmpty()
33 ? LogFileParser::tr("no extension")
34 : QStringLiteral(".%1").arg(suffix);
35
36 LogParseResult result;
37 result.errorMessage = LogFileParser::tr(
38 "Unsupported file type (%1) for file '%2'. Expected .bin, .log, or .ulg.")
39 .arg(fileTypeDescription, filePath);
40 return result;
41}
42
43} // namespace
44
45// ============================================================================
46// LogFileParser
47// ============================================================================
48
49LogFileParser::LogFileParser(QObject *parent)
50 : QObject(parent)
51{
52 qCDebug(LogFileParserLog) << this;
53}
54
55LogFileParser::~LogFileParser()
56{
57 qCDebug(LogFileParserLog) << this;
58}
59
60bool LogFileParser::parseFile(const QString &filePath)
61{
62 ++_parseRequestId;
63 clear();
64 const LogParseResult result = _parseFile(filePath);
65 if (!result.ok) {
66 _setParseError(result.errorMessage);
67 return false;
68 }
69 _applyResult(result);
70 qCDebug(LogFileParserLog) << "Parsed fields" << _availableFields.count()
71 << "parameters" << _parameters.count()
72 << "events" << _events.count();
73 return true;
74}
75
76void LogFileParser::parseFileAsync(const QString &filePath)
77{
78 const quint64 requestId = ++_parseRequestId;
79 clear();
80
81 auto *watcher = new QFutureWatcher<LogParseResult>(this);
82 (void) connect(watcher, &QFutureWatcher<LogParseResult>::finished, this,
83 [this, watcher, filePath, requestId]() {
84 const LogParseResult result = watcher->result();
85 watcher->deleteLater();
86
87 if (requestId != _parseRequestId) {
88 return;
89 }
90
91 if (!result.ok) {
92 _setParseError(result.errorMessage);
93 emit parseFileFinished(filePath, false, result.errorMessage);
94 return;
95 }
96
97 _applyResult(result);
98 emit parseFileFinished(filePath, true, QString());
99 });
100
101 watcher->setFuture(QtConcurrent::run([filePath]() {
102 return _parseFile(filePath);
103 }));
104}
105
106void LogFileParser::_applyResult(const LogParseResult &result)
107{
108 _availableFields = result.availableFields;
109 _plottableFields = result.plottableFields;
110 _parameters = result.parameters;
111 _events = result.events;
112 _messages = result.messages;
113 _modeSegments = result.modeSegments;
114 _dropouts = result.dropouts;
115 _fieldSamples = result.fieldSamples;
116 _sampleCount = result.sampleCount;
117 _detectedVehicleType = result.detectedVehicleType;
120 emit parametersChanged();
121 emit eventsChanged();
122 emit messagesChanged();
123 emit modeSegmentsChanged();
124 emit dropoutsChanged();
126 if (_minTimestamp != result.minTimestamp || _maxTimestamp != result.maxTimestamp) {
127 _minTimestamp = result.minTimestamp;
128 _maxTimestamp = result.maxTimestamp;
129 emit timeRangeChanged();
130 }
131 emit sampleCountChanged();
132
133 _parsed = true;
134 emit parsedChanged();
135}
136
137void LogFileParser::clear()
138{
139 const bool oldParsed = _parsed;
140 _parsed = false;
141 if (oldParsed) { emit parsedChanged(); }
142
143 if (!_parseError.isEmpty()) { _parseError.clear(); emit parseErrorChanged(); }
144 if (!_availableFields.isEmpty()) { _availableFields.clear(); emit availableFieldsChanged(); }
145 if (!_parameters.isEmpty()) { _parameters.clear(); emit parametersChanged(); }
146 if (!_events.isEmpty()) { _events.clear(); emit eventsChanged(); }
147 if (!_messages.isEmpty()) { _messages.clear(); emit messagesChanged(); }
148 if (!_modeSegments.isEmpty()) { _modeSegments.clear(); emit modeSegmentsChanged(); }
149 if (!_dropouts.isEmpty()) { _dropouts.clear(); emit dropoutsChanged(); }
150 if (!_detectedVehicleType.isEmpty()) { _detectedVehicleType.clear(); emit detectedVehicleTypeChanged(); }
151 if (!_plottableFields.isEmpty()) { _plottableFields.clear(); emit plottableFieldsChanged(); }
152
153 _fieldSamples.clear();
154 if (_minTimestamp != -1.0 || _maxTimestamp != -1.0) {
155 _minTimestamp = -1.0;
156 _maxTimestamp = -1.0;
157 emit timeRangeChanged();
158 }
159 if (_sampleCount != 0) { _sampleCount = 0; emit sampleCountChanged(); }
160}
161
162QVariantList LogFileParser::fieldSamples(const QString &fieldName) const
163{
164 QVariantList output;
165 const auto it = _fieldSamples.constFind(fieldName);
166 if (it == _fieldSamples.cend()) { return output; }
167 const QVector<QPointF> &points = it.value();
168 output.reserve(points.size());
169 for (const QPointF &p : points) { output.append(p); }
170 return output;
171}
172
173double LogFileParser::fieldValueAt(const QString &fieldName, double timestampSeconds) const
174{
175 const auto it = _fieldSamples.constFind(fieldName);
176 if (it == _fieldSamples.cend() || it->isEmpty()) {
177 return std::numeric_limits<double>::quiet_NaN();
178 }
179 const QVector<QPointF> &points = it.value();
180 const auto lower = std::lower_bound(points.cbegin(), points.cend(), timestampSeconds,
181 [](const QPointF &p, double t) { return p.x() < t; });
182
183 if (lower == points.cbegin()) { return lower->y(); }
184 if (lower == points.cend()) { return points.constLast().y(); }
185
186 const auto prev = std::prev(lower);
187 return (std::fabs(prev->x() - timestampSeconds) <= std::fabs(lower->x() - timestampSeconds))
188 ? prev->y() : lower->y();
189}
190
191QString LogFileParser::modeAt(double timestampSeconds) const
192{
193 for (const QVariant &v : _modeSegments) {
194 const QVariantMap seg = v.toMap();
195 const double start = seg.value(QStringLiteral("start")).toDouble();
196 const double end = seg.value(QStringLiteral("end")).toDouble();
197 if (timestampSeconds >= start && timestampSeconds <= end) {
198 return seg.value(QStringLiteral("mode")).toString();
199 }
200 }
201 return QString();
202}
203
204QVariantList LogFileParser::eventsNear(double timestampSeconds, double thresholdSeconds) const
205{
206 QVariantList matches;
207 const double threshold = std::max(0.0, thresholdSeconds);
208 const auto lower = std::lower_bound(_events.cbegin(), _events.cend(),
209 timestampSeconds - threshold,
210 [](const QVariant &v, double t) {
211 return v.toMap().value(QStringLiteral("time")).toDouble() < t;
212 });
213 for (auto it = lower; it != _events.cend(); ++it) {
214 const QVariantMap ev = it->toMap();
215 if (ev.value(QStringLiteral("time")).toDouble() > timestampSeconds + threshold) { break; }
216 matches.append(ev);
217 }
218 return matches;
219}
220
221void LogFileParser::_setParseError(const QString &error)
222{
223 if (_parseError != error) {
224 _parseError = error;
225 emit parseErrorChanged();
226 }
227}
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void parametersChanged()
void timeRangeChanged()
void parseErrorChanged()
void dropoutsChanged()
void detectedVehicleTypeChanged()
void sampleCountChanged()
void plottableFieldsChanged()
void availableFieldsChanged()
void modeSegmentsChanged()
void parsedChanged()
void parseFileFinished(const QString &filePath, bool ok, const QString &errorMessage)
void eventsChanged()
void messagesChanged()
LogParseResult parseFile(const QString &filePath)
LogParseResult parseFile(const QString &filePath)
QVariantList modeSegments
QStringList plottableFields
QStringList availableFields
QHash< QString, QVector< QPointF > > fieldSamples