QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
LogViewerDataFlashParser.cc
Go to the documentation of this file.
2
4
5#include <QtCore/QByteArray>
6#include <QtCore/QCoreApplication>
7#include <QtCore/QDateTime>
8#include <QtCore/QFile>
9#include <QtCore/QHash>
10#include <QtCore/QRegularExpression>
11#include <QtCore/QSet>
12#include <QtCore/QTimeZone>
13#include <QtCore/QVariantMap>
14
15#include <algorithm>
16#include <limits>
17
18namespace {
19
20int _leapSecondsTAI(int year, int month)
21{
22 const int yyyymm = year * 100 + month;
23 if (yyyymm >= 201701) return 37;
24 if (yyyymm >= 201507) return 36;
25 if (yyyymm >= 201207) return 35;
26 if (yyyymm >= 200901) return 34;
27 if (yyyymm >= 200601) return 33;
28 if (yyyymm >= 199901) return 32;
29 if (yyyymm >= 199707) return 31;
30 if (yyyymm >= 199601) return 30;
31 return 0;
32}
33
34int _leapSecondsGPS(int year, int month)
35{
36 return _leapSecondsTAI(year, month) - 19;
37}
38
39QString _vehicleTypeFromMessageText(const QString &messageText)
40{
41 const QString text = messageText.toLower();
42 if (text.contains(QStringLiteral("arducopter"))) { return QStringLiteral("ArduCopter"); }
43 if (text.contains(QStringLiteral("arduplane"))) { return QStringLiteral("ArduPlane"); }
44 if (text.contains(QStringLiteral("ardurover"))) { return QStringLiteral("ArduRover"); }
45 if (text.contains(QStringLiteral("ardusub"))) { return QStringLiteral("ArduSub"); }
46 return QString();
47}
48
49// Parse "major.minor" from firmware version strings like "ArduCopter V4.3.1" or "V4.5-stable"
50void _parseFirmwareVersionFromMessageText(const QString &messageText, int &major, int &minor)
51{
52 static const QRegularExpression re(QStringLiteral("V(\\d+)\\.(\\d+)"));
53 const QRegularExpressionMatch m = re.match(messageText);
54 if (m.hasMatch()) {
55 major = m.captured(1).toInt();
56 minor = m.captured(2).toInt();
57 }
58}
59
60QString _ardupilotModeName(const QString &vehicleType, int modeNumber)
61{
62 static const QHash<int, QString> planeModes = {
63 {0,QStringLiteral("Manual")},{1,QStringLiteral("CIRCLE")},{2,QStringLiteral("STABILIZE")},
64 {3,QStringLiteral("TRAINING")},{4,QStringLiteral("ACRO")},{5,QStringLiteral("FBWA")},
65 {6,QStringLiteral("FBWB")},{7,QStringLiteral("CRUISE")},{8,QStringLiteral("AUTOTUNE")},
66 {10,QStringLiteral("Auto")},{11,QStringLiteral("RTL")},{12,QStringLiteral("Loiter")},
67 {13,QStringLiteral("TAKEOFF")},{14,QStringLiteral("AVOID_ADSB")},{15,QStringLiteral("Guided")},
68 {17,QStringLiteral("QSTABILIZE")},{18,QStringLiteral("QHOVER")},{19,QStringLiteral("QLOITER")},
69 {20,QStringLiteral("QLAND")},{21,QStringLiteral("QRTL")},{22,QStringLiteral("QAUTOTUNE")},
70 {23,QStringLiteral("QACRO")},{24,QStringLiteral("THERMAL")},{25,QStringLiteral("Loiter to QLand")},
71 {26,QStringLiteral("AUTOLAND")},
72 };
73 static const QHash<int, QString> copterModes = {
74 {0,QStringLiteral("Stabilize")},{1,QStringLiteral("Acro")},{2,QStringLiteral("AltHold")},
75 {3,QStringLiteral("Auto")},{4,QStringLiteral("Guided")},{5,QStringLiteral("Loiter")},
76 {6,QStringLiteral("RTL")},{7,QStringLiteral("Circle")},{9,QStringLiteral("Land")},
77 {11,QStringLiteral("Drift")},{13,QStringLiteral("Sport")},{14,QStringLiteral("Flip")},
78 {15,QStringLiteral("AutoTune")},{16,QStringLiteral("PosHold")},{17,QStringLiteral("Brake")},
79 {18,QStringLiteral("Throw")},{19,QStringLiteral("Avoid_ADSB")},{20,QStringLiteral("Guided_NoGPS")},
80 {21,QStringLiteral("Smart_RTL")},{22,QStringLiteral("FlowHold")},{23,QStringLiteral("Follow")},
81 {24,QStringLiteral("ZigZag")},{25,QStringLiteral("SystemID")},{26,QStringLiteral("Heli_Autorotate")},
82 {27,QStringLiteral("Auto RTL")},{28,QStringLiteral("Turtle")},
83 };
84 if (vehicleType == QStringLiteral("ArduPlane")) {
85 return planeModes.value(modeNumber, QStringLiteral("Mode %1").arg(modeNumber));
86 }
87 if (vehicleType == QStringLiteral("ArduCopter")) {
88 return copterModes.value(modeNumber, QStringLiteral("Mode %1").arg(modeNumber));
89 }
90 return QStringLiteral("Mode %1").arg(modeNumber);
91}
92
93QString _ardupilotErrDescription(int subsystem, int ecode)
94{
95 QString sub;
96 switch (subsystem) {
97 case 1: sub = QCoreApplication::translate("LogFileParser", "Main"); break;
98 case 2: sub = QCoreApplication::translate("LogFileParser", "Radio"); break;
99 case 3: sub = QCoreApplication::translate("LogFileParser", "Compass"); break;
100 case 4: sub = QCoreApplication::translate("LogFileParser", "Optflow"); break;
101 case 5: sub = QCoreApplication::translate("LogFileParser", "Radio Failsafe"); break;
102 case 6: sub = QCoreApplication::translate("LogFileParser", "Battery Failsafe"); break;
103 case 7: sub = QCoreApplication::translate("LogFileParser", "GPS Failsafe"); break;
104 case 8: sub = QCoreApplication::translate("LogFileParser", "GCS Failsafe"); break;
105 case 9: sub = QCoreApplication::translate("LogFileParser", "Fence Failsafe"); break;
106 case 10: sub = QCoreApplication::translate("LogFileParser", "Flight mode"); break;
107 case 11: sub = QCoreApplication::translate("LogFileParser", "GPS"); break;
108 case 12: sub = QCoreApplication::translate("LogFileParser", "Crash Check"); break;
109 case 13: sub = QCoreApplication::translate("LogFileParser", "Flip"); break;
110 case 14: sub = QCoreApplication::translate("LogFileParser", "Autotune"); break;
111 case 15: sub = QCoreApplication::translate("LogFileParser", "Parachute"); break;
112 case 16: sub = QCoreApplication::translate("LogFileParser", "EKF Check"); break;
113 case 17: sub = QCoreApplication::translate("LogFileParser", "EKF Failsafe"); break;
114 case 18: sub = QCoreApplication::translate("LogFileParser", "Barometer"); break;
115 case 19: sub = QCoreApplication::translate("LogFileParser", "CPU Load Watchdog"); break;
116 case 20: sub = QCoreApplication::translate("LogFileParser", "ADSB Failsafe"); break;
117 case 21: sub = QCoreApplication::translate("LogFileParser", "Terrain Data"); break;
118 case 22: sub = QCoreApplication::translate("LogFileParser", "Navigation"); break;
119 case 23: sub = QCoreApplication::translate("LogFileParser", "Terrain Failsafe"); break;
120 case 24: sub = QCoreApplication::translate("LogFileParser", "EKF Primary"); break;
121 case 25: sub = QCoreApplication::translate("LogFileParser", "Thrust Loss Check"); break;
122 case 26: sub = QCoreApplication::translate("LogFileParser", "Sensor Failsafe"); break;
123 case 27: sub = QCoreApplication::translate("LogFileParser", "Leak Failsafe"); break;
124 case 28: sub = QCoreApplication::translate("LogFileParser", "Pilot Input"); break;
125 case 29: sub = QCoreApplication::translate("LogFileParser", "Vibration Failsafe"); break;
126 case 30: sub = QCoreApplication::translate("LogFileParser", "Internal Error"); break;
127 case 31: sub = QCoreApplication::translate("LogFileParser", "Deadreckon Failsafe"); break;
128 default: sub = QCoreApplication::translate("LogFileParser", "Subsystem %1").arg(subsystem); break;
129 }
130 return QStringLiteral("%1 (%2): Code %3").arg(sub).arg(subsystem).arg(ecode);
131}
132
133QString _ardupilotEventDescription(int eventId)
134{
135 switch (eventId) {
136 case 10: return QCoreApplication::translate("LogFileParser", "Armed");
137 case 11: return QCoreApplication::translate("LogFileParser", "Disarmed");
138 case 15: return QCoreApplication::translate("LogFileParser", "Auto Armed");
139 case 17: return QCoreApplication::translate("LogFileParser", "Land Complete Maybe");
140 case 18: return QCoreApplication::translate("LogFileParser", "Land Complete");
141 case 19: return QCoreApplication::translate("LogFileParser", "Lost GPS");
142 case 21: return QCoreApplication::translate("LogFileParser", "Flip Start");
143 case 22: return QCoreApplication::translate("LogFileParser", "Flip End");
144 case 25: return QCoreApplication::translate("LogFileParser", "Set Home");
145 case 26: return QCoreApplication::translate("LogFileParser", "Simple Mode Enabled");
146 case 27: return QCoreApplication::translate("LogFileParser", "Simple Mode Disabled");
147 case 28: return QCoreApplication::translate("LogFileParser", "Not Landed");
148 case 29: return QCoreApplication::translate("LogFileParser", "Super Simple Mode Enabled");
149 case 30: return QCoreApplication::translate("LogFileParser", "AutoTune Initialised");
150 case 31: return QCoreApplication::translate("LogFileParser", "AutoTune Off");
151 case 32: return QCoreApplication::translate("LogFileParser", "AutoTune Restart");
152 case 33: return QCoreApplication::translate("LogFileParser", "AutoTune Success");
153 case 34: return QCoreApplication::translate("LogFileParser", "AutoTune Failed");
154 case 35: return QCoreApplication::translate("LogFileParser", "AutoTune Reached Limit");
155 case 36: return QCoreApplication::translate("LogFileParser", "AutoTune Pilot Testing");
156 case 37: return QCoreApplication::translate("LogFileParser", "AutoTune Saved Gains");
157 case 38: return QCoreApplication::translate("LogFileParser", "Save Trim");
158 case 39: return QCoreApplication::translate("LogFileParser", "Save Waypoint Add");
159 case 41: return QCoreApplication::translate("LogFileParser", "Fence Enabled");
160 case 42: return QCoreApplication::translate("LogFileParser", "Fence Disabled");
161 case 43: return QCoreApplication::translate("LogFileParser", "Acro Trainer Off");
162 case 44: return QCoreApplication::translate("LogFileParser", "Acro Trainer Leveling");
163 case 45: return QCoreApplication::translate("LogFileParser", "Acro Trainer Limited");
164 case 46: return QCoreApplication::translate("LogFileParser", "Gripper Grab");
165 case 47: return QCoreApplication::translate("LogFileParser", "Gripper Release");
166 case 49: return QCoreApplication::translate("LogFileParser", "Parachute Disabled");
167 case 50: return QCoreApplication::translate("LogFileParser", "Parachute Enabled");
168 case 51: return QCoreApplication::translate("LogFileParser", "Parachute Released");
169 case 52: return QCoreApplication::translate("LogFileParser", "Landing Gear Deployed");
170 case 53: return QCoreApplication::translate("LogFileParser", "Landing Gear Retracted");
171 case 54: return QCoreApplication::translate("LogFileParser", "Motors Emergency Stopped");
172 case 55: return QCoreApplication::translate("LogFileParser", "Motors Emergency Stop Cleared");
173 case 56: return QCoreApplication::translate("LogFileParser", "Motors Interlock Disabled");
174 case 57: return QCoreApplication::translate("LogFileParser", "Motors Interlock Enabled");
175 case 58: return QCoreApplication::translate("LogFileParser", "Rotor Runup Complete");
176 case 59: return QCoreApplication::translate("LogFileParser", "Rotor Speed Below Critical");
177 case 60: return QCoreApplication::translate("LogFileParser", "EKF Altitude Reset");
178 case 61: return QCoreApplication::translate("LogFileParser", "Land Cancelled By Pilot");
179 case 62: return QCoreApplication::translate("LogFileParser", "EKF Yaw Reset");
180 case 63: return QCoreApplication::translate("LogFileParser", "ADSB Avoidance Enabled");
181 case 64: return QCoreApplication::translate("LogFileParser", "ADSB Avoidance Disabled");
182 case 65: return QCoreApplication::translate("LogFileParser", "Proximity Avoidance Enabled");
183 case 66: return QCoreApplication::translate("LogFileParser", "Proximity Avoidance Disabled");
184 case 67: return QCoreApplication::translate("LogFileParser", "GPS Primary Changed");
185 case 71: return QCoreApplication::translate("LogFileParser", "ZigZag Store A");
186 case 72: return QCoreApplication::translate("LogFileParser", "ZigZag Store B");
187 case 73: return QCoreApplication::translate("LogFileParser", "Land Repo Active");
188 case 74: return QCoreApplication::translate("LogFileParser", "Standby Enabled");
189 case 75: return QCoreApplication::translate("LogFileParser", "Standby Disabled");
190 case 76: return QCoreApplication::translate("LogFileParser", "Fence Alt Max Enabled");
191 case 77: return QCoreApplication::translate("LogFileParser", "Fence Alt Max Disabled");
192 case 78: return QCoreApplication::translate("LogFileParser", "Fence Circle Enabled");
193 case 79: return QCoreApplication::translate("LogFileParser", "Fence Circle Disabled");
194 case 80: return QCoreApplication::translate("LogFileParser", "Fence Alt Min Enabled");
195 case 81: return QCoreApplication::translate("LogFileParser", "Fence Alt Min Disabled");
196 case 82: return QCoreApplication::translate("LogFileParser", "Fence Polygon Enabled");
197 case 83: return QCoreApplication::translate("LogFileParser", "Fence Polygon Disabled");
198 case 85: return QCoreApplication::translate("LogFileParser", "EK3 Source Set: Primary");
199 case 86: return QCoreApplication::translate("LogFileParser", "EK3 Source Set: Secondary");
200 case 87: return QCoreApplication::translate("LogFileParser", "EK3 Source Set: Tertiary");
201 case 90: return QCoreApplication::translate("LogFileParser", "Airspeed Primary Changed");
202 case 163: return QCoreApplication::translate("LogFileParser", "Surfaced");
203 case 164: return QCoreApplication::translate("LogFileParser", "Not Surfaced");
204 case 165: return QCoreApplication::translate("LogFileParser", "Bottomed");
205 case 166: return QCoreApplication::translate("LogFileParser", "Not Bottomed");
206 default: return QCoreApplication::translate("LogFileParser", "Event %1").arg(eventId);
207 }
208}
209
210double _extractTimestampSeconds(const QMap<QString, QVariant> &values)
211{
212 if (values.contains(QStringLiteral("TimeUS"))) {
213 return values.value(QStringLiteral("TimeUS")).toDouble() / 1000000.0;
214 }
215 if (values.contains(QStringLiteral("TimeMS"))) {
216 return values.value(QStringLiteral("TimeMS")).toDouble() / 1000.0;
217 }
218 if (values.contains(QStringLiteral("Time"))) {
219 return values.value(QStringLiteral("Time")).toDouble() / 1000.0;
220 }
221 return -1.0;
222}
223
224void _appendEvent(QVariantList &events, double timestampSecs, const QString &type, const QString &description)
225{
226 if ((timestampSecs < 0.0) || description.isEmpty()) {
227 return;
228 }
229 QVariantMap eventRow;
230 eventRow[QStringLiteral("time")] = timestampSecs;
231 eventRow[QStringLiteral("type")] = type;
232 eventRow[QStringLiteral("description")] = description;
233 events.append(eventRow);
234}
235
236} // namespace
237
238namespace DataFlashParser {
239
240LogParseResult parseFile(const QString &filePath, const ProgressCallback &progressCallback, const CancelToken &cancelToken)
241{
242 LogParseResult result;
244
245 QFile file(filePath);
246 if (!file.open(QIODevice::ReadOnly)) {
247 result.errorMessage = QCoreApplication::translate("LogFileParser", "Failed to open file");
248 return result;
249 }
250
251 const qint64 fileSize = file.size();
252 if (fileSize <= 0) {
253 result.errorMessage = QCoreApplication::translate("LogFileParser", "File is empty");
254 return result;
255 }
256 if (fileSize > std::numeric_limits<qsizetype>::max()) {
257 result.errorMessage = QCoreApplication::translate("LogFileParser", "File is too large to parse");
258 return result;
259 }
260
261 uchar *const mappedData = file.map(0, fileSize);
262 if (mappedData == nullptr) {
263 result.errorMessage = QCoreApplication::translate("LogFileParser", "Failed to memory-map file");
264 return result;
265 }
266
267 struct ScopedUnmap {
268 QFile &file;
269 uchar *data = nullptr;
270 ~ScopedUnmap() { if (data) { file.unmap(data); } }
271 } scopedUnmap{file, mappedData};
272
273 const char *const raw = reinterpret_cast<const char *>(mappedData);
274
275 // Verify DataFlash magic
276 if (fileSize < 3 ||
277 static_cast<uint8_t>(raw[0]) != 0xA3 ||
278 static_cast<uint8_t>(raw[1]) != 0x95) {
279 result.errorMessage = QCoreApplication::translate("LogFileParser", "File does not appear to be a DataFlash log (invalid header)");
280 return result;
281 }
282
283 const QByteArray bytes = QByteArray::fromRawData(raw, static_cast<qsizetype>(fileSize));
284
285 QMap<uint8_t, APMDataFlashUtility::MessageFormat> formats;
286 if (!APMDataFlashUtility::parseFmtMessages(bytes.constData(), bytes.size(), formats)) {
287 result.errorMessage = QCoreApplication::translate("LogFileParser", "No valid FMT messages were found");
288 return result;
289 }
290
291 QSet<QString> fieldSet;
292 QSet<QString> plottableFieldSet;
293 double minTimestampSecs = -1.0;
294 double maxTimestampSecs = -1.0;
295 bool hasOpenModeSegment = false;
296 double modeSegmentStartSecs = -1.0;
297 QString currentModeName;
298
299 QHash<uint8_t, QHash<QString, QString>> fieldNameByCol;
300 fieldNameByCol.reserve(formats.size());
301 for (auto fmtIt = formats.cbegin(); fmtIt != formats.cend(); ++fmtIt) {
302 const APMDataFlashUtility::MessageFormat &fmt = fmtIt.value();
303 QHash<QString, QString> &perCol = fieldNameByCol[fmtIt.key()];
304 perCol.reserve(fmt.columns.size());
305 for (const QString &col : fmt.columns) {
306 perCol.insert(col, fmt.name + QLatin1Char('.') + col);
307 }
308 }
309
310 static const QString kPARM = QStringLiteral("PARM");
311 static const QString kMSG = QStringLiteral("MSG");
312 static const QString kMODE = QStringLiteral("MODE");
313 static const QString kERR = QStringLiteral("ERR");
314 static const QString kEV = QStringLiteral("EV");
315 static const QString kGPS = QStringLiteral("GPS");
316 static const QString kGPS2 = QStringLiteral("GPS2");
317
318 APMDataFlashUtility::iterateMessages(bytes.constData(), bytes.size(), formats,
319 [&](uint8_t msgType, const char *payload, int, const APMDataFlashUtility::MessageFormat &fmt) {
320 const QMap<QString, QVariant> values = APMDataFlashUtility::parseMessage(payload, fmt);
321 const double timestampSecs = _extractTimestampSeconds(values);
322 if (timestampSecs >= 0.0) {
323 if (minTimestampSecs < 0.0 || timestampSecs < minTimestampSecs) { minTimestampSecs = timestampSecs; }
324 maxTimestampSecs = std::max(maxTimestampSecs, timestampSecs);
325 }
326
327 if ((fmt.name == kGPS || fmt.name == kGPS2) && result.startTime.isNull()
328 && values.contains(QStringLiteral("GWk")) && values.contains(QStringLiteral("GMS"))
329 && timestampSecs >= 0.0) {
330 const int gwk = values.value(QStringLiteral("GWk")).toInt();
331 const int gms = values.value(QStringLiteral("GMS")).toInt();
332 if (gwk > 2000) {
333 const double gpsSecs = 315964800.0 + (7.0 * 24 * 60 * 60) * gwk + (gms / 1000.0);
334 const QDateTime gpsDateTime = QDateTime::fromMSecsSinceEpoch(
335 static_cast<qint64>(gpsSecs * 1000.0), QTimeZone::utc());
336 const int leapSecs = _leapSecondsGPS(gpsDateTime.date().year(), gpsDateTime.date().month());
337 const double utcSecs = gpsSecs - leapSecs;
338 result.startTime = QDateTime::fromMSecsSinceEpoch(
339 static_cast<qint64>((utcSecs - timestampSecs) * 1000.0), QTimeZone::utc());
340 }
341 }
342
343 if (fmt.name == kPARM) {
344 const QString paramName = values.value(QStringLiteral("Name")).toString();
345 const QVariant paramValue = values.contains(QStringLiteral("Value"))
346 ? values.value(QStringLiteral("Value"))
347 : values.value(QStringLiteral("Val"));
348 if (!paramName.isEmpty()) {
349 QVariantMap row;
350 row[QStringLiteral("name")] = paramName;
351 row[QStringLiteral("value")] = paramValue;
352 // DataFlash logs don't carry default value metadata
353 row[QStringLiteral("isFloat")] = paramValue.metaType() == QMetaType::fromType<float>()
354 || paramValue.metaType() == QMetaType::fromType<double>();
355 row[QStringLiteral("hasDefault")] = false;
356 row[QStringLiteral("defaultValue")] = QVariant();
357 row[QStringLiteral("isDefault")] = false;
358 result.parameters.append(row);
359 }
360 } else if (fmt.name == kMSG) {
361 const QString text = values.value(QStringLiteral("Message")).toString();
362 const QString detected = _vehicleTypeFromMessageText(text);
363 if (result.detectedVehicleType.isEmpty() && !detected.isEmpty()) {
364 result.detectedVehicleType = detected;
365 _parseFirmwareVersionFromMessageText(text, result.firmwareMajorVersion, result.firmwareMinorVersion);
366 }
367 if (!text.isEmpty()) {
368 QVariantMap row;
369 row[QStringLiteral("time")] = timestampSecs;
370 row[QStringLiteral("text")] = text;
371 result.messages.append(row);
372 }
373 } else if (fmt.name == kMODE) {
374 QString modeName = values.value(QStringLiteral("Mode")).toString();
375 bool isNumeric = false;
376 const int modeNumber = modeName.toInt(&isNumeric);
377 if (isNumeric) {
378 modeName = _ardupilotModeName(result.detectedVehicleType, modeNumber);
379 } else if (modeName.isEmpty()) {
380 modeName = QCoreApplication::translate("LogFileParser", "Unknown");
381 }
382 _appendEvent(result.events, timestampSecs, QStringLiteral("mode"),
383 QCoreApplication::translate("LogFileParser", "Mode: %1").arg(modeName));
384 if (timestampSecs >= 0.0) {
385 if (hasOpenModeSegment && (timestampSecs > modeSegmentStartSecs)) {
386 QVariantMap segment;
387 segment[QStringLiteral("mode")] = currentModeName;
388 segment[QStringLiteral("start")] = modeSegmentStartSecs;
389 segment[QStringLiteral("end")] = timestampSecs;
390 result.modeSegments.append(segment);
391 }
392 hasOpenModeSegment = true;
393 modeSegmentStartSecs = timestampSecs;
394 currentModeName = modeName;
395 }
396 } else if (fmt.name == kERR) {
397 const int subsystem = values.value(QStringLiteral("Subsys")).toInt();
398 const int ecode = values.value(QStringLiteral("ECode")).toInt();
399 _appendEvent(result.events, timestampSecs, QStringLiteral("error"),
400 _ardupilotErrDescription(subsystem, ecode));
401 } else if (fmt.name == kEV) {
402 const int eventId = values.value(QStringLiteral("Id"), values.value(QStringLiteral("Event"))).toInt();
403 _appendEvent(result.events, timestampSecs, QStringLiteral("event"),
404 _ardupilotEventDescription(eventId));
405 }
406
407 result.sampleCount++;
408
409 const QHash<QString, QString> &perCol = fieldNameByCol[msgType];
410 const bool haveTimestamp = (timestampSecs >= 0.0);
411 for (auto it = values.cbegin(); it != values.cend(); ++it) {
412 const auto nameIt = perCol.constFind(it.key());
413 if (nameIt == perCol.constEnd()) { continue; }
414 const QString &fieldName = nameIt.value();
415 fieldSet.insert(fieldName);
416 if (!haveTimestamp) { continue; }
417 const int typeId = it.value().metaType().id();
418 const bool numeric =
419 (typeId == QMetaType::Int) || (typeId == QMetaType::UInt) ||
420 (typeId == QMetaType::LongLong) || (typeId == QMetaType::ULongLong) ||
421 (typeId == QMetaType::Float) || (typeId == QMetaType::Double);
422 if (numeric) {
423 result.fieldSamples[fieldName].append(QPointF(timestampSecs, it.value().toDouble()));
424 plottableFieldSet.insert(fieldName);
425 }
426 }
427 return !cancelToken || !cancelToken->load(std::memory_order_relaxed);
428 }, progressCallback);
429
430 if (cancelToken && cancelToken->load(std::memory_order_relaxed)) {
431 return result; // cancelled; ok remains false
432 }
433
434 if (hasOpenModeSegment && (maxTimestampSecs >= modeSegmentStartSecs)) {
435 QVariantMap segment;
436 segment[QStringLiteral("mode")] = currentModeName;
437 segment[QStringLiteral("start")] = modeSegmentStartSecs;
438 segment[QStringLiteral("end")] = maxTimestampSecs;
439 result.modeSegments.append(segment);
440 }
441
442 result.availableFields = fieldSet.values();
443 std::sort(result.availableFields.begin(), result.availableFields.end());
444 result.plottableFields = plottableFieldSet.values();
445 std::sort(result.plottableFields.begin(), result.plottableFields.end());
446 result.minTimestamp = minTimestampSecs;
447 result.maxTimestamp = maxTimestampSecs;
448 result.ok = true;
449 return result;
450}
451
452} // namespace DataFlashParser
std::function< void(float)> ProgressCallback
std::shared_ptr< std::atomic< bool > > CancelToken
bool parseFmtMessages(const char *data, qint64 size, QMap< uint8_t, MessageFormat > &formats)
int iterateMessages(const char *data, qint64 size, const QMap< uint8_t, MessageFormat > &formats, const MessageCallback &callback, const std::function< void(float)> &progressCallback)
LogParseResult parseFile(const QString &filePath, const ProgressCallback &progressCallback, const CancelToken &cancelToken)
QVariantList modeSegments
QHash< QString, QVector< QPointF > > fieldSamples