QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
ULogFullHandler.cc
Go to the documentation of this file.
1#include "ULogFullHandler.h"
2
5
6#include <QtCore/QStringList>
7
8#include <algorithm>
9#include <stdexcept>
10
11#include <ulog_cpp/subscription.hpp>
12
13QGC_LOGGING_CATEGORY(ULogFullHandlerLog, "AnalyzeView.ULogFullHandler")
14
15namespace {
16
17QString _px4NavStateName(int state)
18{
19 switch (state) {
20 // Values from PX4 VehicleStatus.msg NAVIGATION_STATE_* constants
21 case 0: return QStringLiteral("Manual");
22 case 1: return QStringLiteral("Altitude");
23 case 2: return QStringLiteral("Position");
24 case 3: return QStringLiteral("Mission");
25 case 4: return QStringLiteral("Hold");
26 case 5: return QStringLiteral("Return");
27 case 6: return QStringLiteral("Position Slow");
28 case 8: return QStringLiteral("Altitude Cruise");
29 case 10: return QStringLiteral("Acro");
30 case 12: return QStringLiteral("Descend");
31 case 13: return QStringLiteral("Termination");
32 case 14: return QStringLiteral("Offboard");
33 case 15: return QStringLiteral("Stabilized");
34 case 17: return QStringLiteral("Takeoff");
35 case 18: return QStringLiteral("Land");
36 case 19: return QStringLiteral("Follow Target");
37 case 20: return QStringLiteral("Precision Land");
38 case 21: return QStringLiteral("Orbit");
39 case 22: return QStringLiteral("VTOL Takeoff");
40 default: return QStringLiteral("Mode %1").arg(state);
41 }
42}
43
44bool _isNumericScalarField(const ulog_cpp::Field &field)
45{
46 if (field.arrayLength() >= 0) {
47 return false; // arrays excluded from plottable fields
48 }
49 using BT = ulog_cpp::Field::BasicType;
50 switch (field.type().type) {
51 case BT::INT8:
52 case BT::UINT8:
53 case BT::INT16:
54 case BT::UINT16:
55 case BT::INT32:
56 case BT::UINT32:
57 case BT::INT64:
58 case BT::UINT64:
59 case BT::FLOAT:
60 case BT::DOUBLE:
61 case BT::BOOL:
62 return true;
63 default:
64 return false;
65 }
66}
67
68} // namespace
69
71 : _result(result)
72{
73}
74
75void ULogFullHandler::error(const std::string &msg, bool is_recoverable)
76{
77 const QString errorMessage = QString::fromStdString(msg);
78 if (!is_recoverable) {
79 _hadFatalError = true;
80 if (_result.errorMessage.isEmpty()) {
81 _result.errorMessage = errorMessage;
82 }
83 }
84 qCWarning(ULogFullHandlerLog) << "ULog parse error:" << errorMessage;
85}
86
87void ULogFullHandler::messageFormat(const ulog_cpp::MessageFormat &message_format)
88{
89 _formats[message_format.name()] = std::make_shared<ulog_cpp::MessageFormat>(message_format);
90}
91
92void ULogFullHandler::addLoggedMessage(const ulog_cpp::AddLoggedMessage &add_logged_message)
93{
94 const auto it = _formats.find(add_logged_message.messageName());
95 if (it != _formats.cend()) {
96 _subscriptions[add_logged_message.msgId()] = {
97 it->second,
98 add_logged_message.multiId(),
99 add_logged_message.messageName()
100 };
101 }
102}
103
105{
106 _headerComplete = true;
107 for (auto &[name, fmt] : _formats) {
108 fmt->resolveDefinition(_formats);
109 }
110}
111
112void ULogFullHandler::data(const ulog_cpp::Data &data)
113{
114 if (!_headerComplete) {
115 return;
116 }
117
118 const auto it = _subscriptions.find(data.msgId());
119 if (it == _subscriptions.cend()) {
120 return;
121 }
122
123 const SubscriptionInfo &sub = it->second;
124 if (!sub.format) {
125 return;
126 }
127
128 try {
129 const ulog_cpp::TypedDataView view(data, *sub.format);
130
131 // Extract timestamp (ULog convention: field named "timestamp", unit µs)
132 double timestampSecs = -1.0;
133 if (sub.format->fieldMap().count("timestamp") > 0) {
134 const uint64_t tsUs = view.at("timestamp").as<uint64_t>();
135 timestampSecs = static_cast<double>(tsUs) / 1e6;
136 _lastTimestampSecs = timestampSecs;
137 }
138
139 // Field name: "topic_name.field" or "topic_name[N].field" for multi-instance
140 const QString prefix = (sub.multiId > 0)
141 ? QStringLiteral("%1[%2].").arg(QString::fromStdString(sub.topicName)).arg(sub.multiId)
142 : QString::fromStdString(sub.topicName) + QLatin1Char('.');
143
144 for (const auto &field : sub.format->fields()) {
145 // Skip padding fields and the timestamp itself
146 if (field->name().rfind("_padding", 0) == 0) {
147 continue;
148 }
149 if (field->name() == "timestamp") {
150 continue;
151 }
152 if (!field->definitionResolved()) {
153 continue;
154 }
155
156 const QString fieldName = prefix + QString::fromStdString(field->name());
157 _fieldSet.insert(fieldName);
158
159 if (!_isNumericScalarField(*field) || timestampSecs < 0.0) {
160 continue;
161 }
162
163 const double value = view.at(field).as<double>();
164 _result.fieldSamples[fieldName].append(QPointF(timestampSecs, value));
165 _plottableFieldSet.insert(fieldName);
166 }
167
168 _result.sampleCount++;
169
170 if (timestampSecs >= 0.0) {
171 if (_result.minTimestamp < 0.0 || timestampSecs < _result.minTimestamp) {
172 _result.minTimestamp = timestampSecs;
173 }
174 _result.maxTimestamp = std::max(_result.maxTimestamp, timestampSecs);
175 }
176 } catch (const std::exception &e) {
177 qCWarning(ULogFullHandlerLog) << "Failed to decode data message:" << e.what();
178 }
179}
180
181void ULogFullHandler::logging(const ulog_cpp::Logging &logging)
182{
183 const double timestampSecs = static_cast<double>(logging.timestamp()) / 1e6;
184 const QString text = QString::fromStdString(logging.message());
185
186 if (text.isEmpty()) {
187 return;
188 }
189
190 QVariantMap msgRow;
191 msgRow[QStringLiteral("time")] = timestampSecs;
192 msgRow[QStringLiteral("text")] = text;
193 _result.messages.append(msgRow);
194
195 using Level = ulog_cpp::Logging::Level;
196 if (logging.logLevel() <= Level::Warning) {
197 const QString eventType = (logging.logLevel() <= Level::Error)
198 ? QStringLiteral("error")
199 : QStringLiteral("warning");
200
201 if (timestampSecs >= 0.0) {
202 QVariantMap eventRow;
203 eventRow[QStringLiteral("time")] = timestampSecs;
204 eventRow[QStringLiteral("type")] = eventType;
205 eventRow[QStringLiteral("description")] = text;
206 _result.events.append(eventRow);
207 }
208 }
209}
210
211void ULogFullHandler::parameter(const ulog_cpp::Parameter &param)
212{
213 const QString name = QString::fromStdString(param.field().name());
214 if (name.isEmpty()) {
215 return;
216 }
217
218 QVariant value;
219 try {
220 // ULog parameters are restricted to int32_t and float per spec.
221 // as<double>() safely static_casts either type.
222 value = param.value().as<double>();
223 } catch (const std::exception &) {
224 value = QVariant();
225 }
226
227 QVariantMap row;
228 row[QStringLiteral("name")] = name;
229 row[QStringLiteral("value")] = value;
230 _result.parameters.append(row);
231}
232
233void ULogFullHandler::dropout(const ulog_cpp::Dropout &dropout)
234{
235 if (_lastTimestampSecs < 0.0) {
236 return;
237 }
238
239 const double start = _lastTimestampSecs;
240 const double end = start + static_cast<double>(dropout.durationMs()) / 1000.0;
241 QVariantMap row;
242 row[QStringLiteral("start")] = start;
243 row[QStringLiteral("end")] = end;
244 _result.dropouts.append(row);
245}
246
248{
249 // Detect vehicle type from vehicle_status.vehicle_type
250 // PX4 vehicle_type enum: 0=Unknown, 1=Rotary Wing, 2=Fixed Wing, 3=Rover, 4=Airship
251 const auto vehicleTypeIt = _result.fieldSamples.constFind(QStringLiteral("vehicle_status.vehicle_type"));
252 if (vehicleTypeIt != _result.fieldSamples.cend() && !vehicleTypeIt->isEmpty()) {
253 const int vtype = static_cast<int>(vehicleTypeIt->first().y());
254 switch (vtype) {
255 case 1: _result.detectedVehicleType = QStringLiteral("Multirotor/Helicopter"); break;
256 case 2: _result.detectedVehicleType = QStringLiteral("Fixed Wing"); break;
257 case 3: _result.detectedVehicleType = QStringLiteral("Rover"); break;
258 case 4: _result.detectedVehicleType = QStringLiteral("Airship"); break;
259 default: break; // 0 = Unknown, leave empty so UI shows "Unknown"
260 }
261 }
262
263 // Derive mode segments from vehicle_status.nav_state samples.
264 // nav_state is a uint8_t mapped to the PX4 navigation_state enum.
265 const auto navStateIt = _result.fieldSamples.constFind(QStringLiteral("vehicle_status.nav_state"));
266 if (navStateIt != _result.fieldSamples.cend()) {
267 const QVector<QPointF> &samples = navStateIt.value();
268 int lastNavState = -1;
269 double segmentStart = -1.0;
270 QString segmentMode;
271
272 for (const QPointF &pt : samples) {
273 const int navState = static_cast<int>(pt.y());
274 if (navState != lastNavState) {
275 // Close the previous segment
276 if (lastNavState >= 0 && segmentStart >= 0.0) {
277 QVariantMap seg;
278 seg[QStringLiteral("mode")] = segmentMode;
279 seg[QStringLiteral("start")] = segmentStart;
280 seg[QStringLiteral("end")] = pt.x();
281 _result.modeSegments.append(seg);
282 }
283 lastNavState = navState;
284 segmentStart = pt.x();
285 segmentMode = _px4NavStateName(navState);
286 }
287 }
288
289 // Close the final open segment
290 if (lastNavState >= 0 && segmentStart >= 0.0 && _result.maxTimestamp >= segmentStart) {
291 QVariantMap seg;
292 seg[QStringLiteral("mode")] = segmentMode;
293 seg[QStringLiteral("start")] = segmentStart;
294 seg[QStringLiteral("end")] = _result.maxTimestamp;
295 _result.modeSegments.append(seg);
296 }
297 }
298
299 // Sort field lists for consistent display
300 _result.availableFields = _fieldSet.values();
301 std::sort(_result.availableFields.begin(), _result.availableFields.end());
302 _result.plottableFields = _plottableFieldSet.values();
303 std::sort(_result.plottableFields.begin(), _result.plottableFields.end());
304
305 _result.ok = true;
306}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void parameter(const ulog_cpp::Parameter &parameter) override
void addLoggedMessage(const ulog_cpp::AddLoggedMessage &add_logged_message) override
void error(const std::string &msg, bool is_recoverable) override
void logging(const ulog_cpp::Logging &logging) override
ULogFullHandler(LogParseResult &result)
void messageFormat(const ulog_cpp::MessageFormat &message_format) override
void data(const ulog_cpp::Data &data) override
void dropout(const ulog_cpp::Dropout &dropout) override
void headerComplete() override
QVariantList modeSegments
QStringList plottableFields
QStringList availableFields
QHash< QString, QVector< QPointF > > fieldSamples