INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/IMU/FileReader/UlogFile.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 14 661 2.1%
Functions: 4 22 18.2%
Branches: 17 1134 1.5%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "UlogFile.hpp"
10
11 #include "util/Logger.hpp"
12
13 #include <exception>
14
15 #include "internal/gui/widgets/FileDialog.hpp"
16
17 #include "internal/FlowManager.hpp"
18
19 #include "util/Vendor/Pixhawk/UlogFileFormat.hpp"
20 #include "NodeData/IMU/ImuObs.hpp"
21 #include "NodeData/State/PosVelAtt.hpp"
22 #include "Navigation/Transformations/Units.hpp"
23
24 #include <ctime>
25
26 // ----------------------------------------------------------- Basic Node Functions --------------------------------------------------------------
27
28 114 NAV::UlogFile::UlogFile()
29
3/6
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 114 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 114 times.
✗ Branch 9 not taken.
114 : Imu(typeStatic())
30 {
31 LOG_TRACE("{}: called", name);
32
33 114 _hasConfig = true;
34 114 _guiConfigDefaultWindowSize = { 589, 257 };
35
36 // All message types are polled from the first output pin, but then send out on the correct pin over invokeCallbacks
37
4/8
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 114 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 114 times.
✓ Branch 9 taken 114 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
342 CreateOutputPin("ImuObs #1", Pin::Type::Flow, { NAV::ImuObs::type() }, &UlogFile::pollData);
38
4/8
✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 114 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 114 times.
✓ Branch 10 taken 114 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
456 CreateOutputPin("ImuObs #2", Pin::Type::Flow, { NAV::ImuObs::type() });
39
4/8
✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 114 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 114 times.
✓ Branch 10 taken 114 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
456 CreateOutputPin("PosVelAtt", Pin::Type::Flow, { NAV::PosVelAtt::type() });
40 456 }
41
42 228 NAV::UlogFile::~UlogFile()
43 {
44 LOG_TRACE("{}: called", nameId());
45 228 }
46
47 228 std::string NAV::UlogFile::typeStatic()
48 {
49
1/2
✓ Branch 1 taken 228 times.
✗ Branch 2 not taken.
456 return "UlogFile";
50 }
51
52 std::string NAV::UlogFile::type() const
53 {
54 return typeStatic();
55 }
56
57 114 std::string NAV::UlogFile::category()
58 {
59
1/2
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
228 return "Data Provider";
60 }
61
62 void NAV::UlogFile::guiConfig()
63 {
64 if (auto res = FileReader::guiConfig(".ulg,.*", { ".ulg" }, size_t(id), nameId()))
65 {
66 LOG_DEBUG("{}: Path changed to {}", nameId(), _path);
67 flow::ApplyChanges();
68 if (res == FileReader::PATH_CHANGED)
69 {
70 doReinitialize();
71 }
72 else
73 {
74 doDeinitialize();
75 }
76 }
77
78 Imu::guiConfig();
79 }
80
81 [[nodiscard]] json NAV::UlogFile::save() const
82 {
83 LOG_TRACE("{}: called", nameId());
84
85 json j;
86
87 j["FileReader"] = FileReader::save();
88 j["Imu"] = Imu::save();
89
90 return j;
91 }
92
93 void NAV::UlogFile::restore(json const& j)
94 {
95 LOG_TRACE("{}: called", nameId());
96
97 if (j.contains("FileReader"))
98 {
99 FileReader::restore(j.at("FileReader"));
100 }
101 if (j.contains("Imu"))
102 {
103 Imu::restore(j.at("Imu"));
104 }
105 }
106
107 bool NAV::UlogFile::initialize()
108 {
109 LOG_TRACE("{}: called", nameId());
110
111 return FileReader::initialize();
112 }
113
114 void NAV::UlogFile::deinitialize()
115 {
116 LOG_TRACE("{}: called", nameId());
117
118 FileReader::deinitialize();
119 }
120
121 bool NAV::UlogFile::resetNode()
122 {
123 LOG_TRACE("{}: called", nameId());
124
125 FileReader::resetReader();
126
127 lastGnssTime.timeSinceStartup = 0;
128 _epochData.clear();
129 _subscribedMessages.clear();
130
131 return true;
132 }
133
134 // ------------------------------------------------------------ File Reading ---------------------------------------------------------------
135
136 NAV::FileReader::FileType NAV::UlogFile::determineFileType()
137 {
138 LOG_TRACE("{}: called", nameId());
139
140 std::filesystem::path filepath = getFilepath();
141
142 auto filestream = std::ifstream(filepath);
143
144 constexpr uint16_t BUFFER_SIZE = 10; // TODO: validate size
145
146 std::array<char, BUFFER_SIZE> buffer{};
147 if (filestream.good())
148 {
149 filestream.read(buffer.data(), BUFFER_SIZE);
150 filestream.close();
151 LOG_DEBUG("{} has the file type: CSV", nameId());
152 return FileType::BINARY;
153 }
154 filestream.close();
155
156 LOG_ERROR("{} could not open file", nameId(), filepath);
157 return FileType::NONE;
158 }
159
160 void NAV::UlogFile::readHeader()
161 {
162 LOG_TRACE("{}: called", nameId());
163
164 if (_fileType == FileType::BINARY)
165 {
166 union
167 {
168 std::array<char, 16> data{};
169 vendor::pixhawk::ulog_Header_s header;
170 } ulogHeader{};
171
172 read(ulogHeader.data.data(), ulogHeader.data.size());
173
174 // Check "ULog" at beginning of file
175 if ((ulogHeader.header.fileMagic[0] != 'U') || (ulogHeader.header.fileMagic[1] != 'L') || (ulogHeader.header.fileMagic[2] != 'o') || (ulogHeader.header.fileMagic[3] != 'g'))
176 {
177 LOG_WARN("{}: FileType is binary, but not ULog", nameId());
178 }
179
180 LOG_DEBUG("{}: version: {}", nameId(), static_cast<int>(ulogHeader.header.version));
181
182 LOG_DEBUG("{}: time stamp [µs]: {}", nameId(), ulogHeader.header.timeStamp);
183
184 // Read message header
185 union
186 {
187 std::array<char, 3> data{};
188 vendor::pixhawk::message_header_s msgHeader;
189 } ulogMsgHeader{};
190
191 while ((ulogMsgHeader.msgHeader.msg_type != 'A') && (ulogMsgHeader.msgHeader.msg_type != 'L'))
192 {
193 read(ulogMsgHeader.data.data(), ulogMsgHeader.data.size());
194
195 LOG_DATA("{}: msgSize: {}, msgType: {}", nameId(), ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
196
197 // Read definition message
198 // Flag bitset message
199 if (ulogMsgHeader.msgHeader.msg_type == 'B')
200 {
201 if (ulogMsgHeader.msgHeader.msg_size > 40)
202 {
203 LOG_WARN("{}: Exceeding bytes in 'flag bitset message' are ignored. Check for ULog file format update.", nameId());
204 }
205
206 union
207 {
208 std::array<char, 40> data{};
209 vendor::pixhawk::ulog_message_flag_bits_s ulogMsgFlagBits_s;
210 } ulogMsgFlagBits{};
211
212 read(ulogMsgFlagBits.data.data(), ulogMsgFlagBits.data.size() * sizeof(char)); // 'sizeof' is optional here, but it is the solution in general, since data types can be larger than one byte
213 }
214
215 // Format definition for a single (composite) type that can be logged or used in another definition as a nested type
216 else if (ulogMsgHeader.msgHeader.msg_type == 'F')
217 {
218 vendor::pixhawk::message_format_s messageFormat;
219 messageFormat.header = ulogMsgHeader.msgHeader;
220
221 LOG_DATA("{}: messageFormat.header.msg_size: {}", nameId(), messageFormat.header.msg_size);
222
223 messageFormat.format.resize(messageFormat.header.msg_size);
224 read(messageFormat.format.data(), ulogMsgHeader.msgHeader.msg_size);
225 LOG_DATA("{}: messageFormat.format.data(): {}", nameId(), messageFormat.format.data());
226
227 std::string msgName = messageFormat.format.substr(0, messageFormat.format.find(':'));
228
229 // Decoding data format fields
230 std::stringstream lineStream(messageFormat.format.substr(messageFormat.format.find(':') + 1));
231 std::string cell;
232
233 std::vector<DataField> msgDataFields;
234
235 while (std::getline(lineStream, cell, ';'))
236 {
237 DataField data_field{ .type = cell.substr(0, cell.find(' ')), .name = cell.substr(cell.find(' ') + 1) };
238 msgDataFields.push_back(data_field);
239 }
240
241 if (msgName == "sensor_accel")
242 {
243 _messageFormats.insert_or_assign(msgName, msgDataFields);
244 }
245 else if (msgName == "sensor_gyro")
246 {
247 _messageFormats.insert_or_assign(msgName, msgDataFields);
248 }
249 else if (msgName == "sensor_mag")
250 {
251 _messageFormats.insert_or_assign(msgName, msgDataFields);
252 }
253 else if (msgName == "vehicle_gps_position")
254 {
255 _messageFormats.insert_or_assign(msgName, msgDataFields);
256 }
257 else if (msgName == "vehicle_attitude")
258 {
259 _messageFormats.insert_or_assign(msgName, msgDataFields);
260 }
261 else if (msgName == "vehicle_air_data")
262 {
263 _messageFormats.insert_or_assign(msgName, msgDataFields);
264 }
265 else if (msgName == "vehicle_control_mode")
266 {
267 _messageFormats.insert_or_assign(msgName, msgDataFields);
268 }
269 else if (msgName == "vehicle_status")
270 {
271 _messageFormats.insert_or_assign(msgName, msgDataFields);
272 }
273 else if (msgName == "cpuload")
274 {
275 _messageFormats.insert_or_assign(msgName, msgDataFields);
276 }
277 else
278 {
279 LOG_ERROR("{}: Data format '{}' could not be decoded", nameId(), msgName);
280 }
281 }
282
283 // Information message
284 else if (ulogMsgHeader.msgHeader.msg_type == 'I')
285 {
286 readInformationMessage(ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
287 }
288
289 // Information message multi
290 else if (ulogMsgHeader.msgHeader.msg_type == 'M')
291 {
292 readInformationMessageMulti(ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
293 }
294
295 // Parameter message
296 else if (ulogMsgHeader.msgHeader.msg_type == 'P')
297 {
298 readParameterMessage(ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
299 }
300
301 // Parameter message default
302 else if (ulogMsgHeader.msgHeader.msg_type == 'Q')
303 {
304 readParameterMessageDefault(ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
305 }
306 }
307 seekg(-3, std::ios_base::cur); // 'msg_size' + 'msg_type' = 3 Byte
308 LOG_DEBUG("{}: Read 'Definitions Section' completed", nameId());
309 }
310 }
311
312 std::shared_ptr<const NAV::NodeData> NAV::UlogFile::pollData()
313 {
314 // Read message header
315 union
316 {
317 std::array<char, 3> data{};
318 vendor::pixhawk::message_header_s msgHeader;
319 } ulogMsgHeader{};
320
321 while (true)
322 {
323 read(ulogMsgHeader.data.data(), ulogMsgHeader.data.size());
324
325 LOG_DATA("{}: msgSize: {}", nameId(), ulogMsgHeader.msgHeader.msg_size);
326 LOG_DATA("{}: msgType: {}", nameId(), ulogMsgHeader.msgHeader.msg_type);
327
328 if (ulogMsgHeader.msgHeader.msg_type == 'A')
329 {
330 vendor::pixhawk::message_add_logged_s messageAddLog;
331 messageAddLog.header = ulogMsgHeader.msgHeader;
332 read(reinterpret_cast<char*>(&messageAddLog.multi_id), sizeof(messageAddLog.multi_id));
333 LOG_DATA("{}: multi_id: {}", nameId(), messageAddLog.multi_id);
334 read(reinterpret_cast<char*>(&messageAddLog.msg_id), sizeof(messageAddLog.msg_id));
335 LOG_DATA("{}: msg_id: {}", nameId(), messageAddLog.msg_id);
336
337 messageAddLog.msg_name.resize(messageAddLog.header.msg_size - 3);
338 read(messageAddLog.msg_name.data(), messageAddLog.header.msg_size - 3);
339 LOG_DATA("{}: messageAddLog.msg_name: {}", nameId(), messageAddLog.msg_name);
340
341 /// Combines (sensor-)message name with an ID that indicates a possible multiple of a sensor
342 _subscribedMessages.insert_or_assign(messageAddLog.msg_id, SubscriptionData{ .multi_id = messageAddLog.multi_id, .message_name = messageAddLog.msg_name });
343 }
344 else if (ulogMsgHeader.msgHeader.msg_type == 'R')
345 {
346 vendor::pixhawk::message_remove_logged_s messageRemoveLog;
347 messageRemoveLog.header = ulogMsgHeader.msgHeader;
348 read(reinterpret_cast<char*>(&messageRemoveLog.msg_id), sizeof(messageRemoveLog.msg_id));
349 LOG_DATA("{}: Removed message with 'msg_id': {}", nameId(), messageRemoveLog.msg_id);
350
351 _subscribedMessages.erase(messageRemoveLog.msg_id);
352 }
353 else if (ulogMsgHeader.msgHeader.msg_type == 'D')
354 {
355 vendor::pixhawk::message_data_s messageData;
356 messageData.header = ulogMsgHeader.msgHeader;
357 read(reinterpret_cast<char*>(&messageData.msg_id), sizeof(messageData.msg_id));
358 LOG_DATA("{}: msg_id: {}", nameId(), messageData.msg_id);
359
360 messageData.data.resize(messageData.header.msg_size - 2);
361 read(messageData.data.data(), messageData.header.msg_size - 2);
362 LOG_DATA("{}: messageData.header.msg_size: {}", nameId(), messageData.header.msg_size);
363
364 const auto& messageFormat = _messageFormats.at(_subscribedMessages.at(messageData.msg_id).message_name);
365
366 size_t currentExtractLocation = 0;
367 if (_subscribedMessages.at(messageData.msg_id).message_name == "sensor_accel")
368 {
369 SensorAccel sensorAccel{};
370 for (const auto& dataField : messageFormat)
371 {
372 char* currentData = messageData.data.data() + currentExtractLocation;
373 if (dataField.name == "timestamp")
374 {
375 std::memcpy(&sensorAccel.timestamp, currentData, sizeof(sensorAccel.timestamp));
376 LOG_DATA("{}: sensorAccel.timestamp: {}", nameId(), sensorAccel.timestamp);
377 currentExtractLocation += sizeof(sensorAccel.timestamp);
378 }
379 else if (dataField.name == "timestamp_sample")
380 {
381 std::memcpy(&sensorAccel.timestamp_sample, currentData, sizeof(sensorAccel.timestamp_sample));
382 LOG_DATA("{}: sensorAccel.timestamp_sample: {}", nameId(), sensorAccel.timestamp_sample);
383 currentExtractLocation += sizeof(sensorAccel.timestamp_sample);
384 }
385 else if (dataField.name == "device_id")
386 {
387 std::memcpy(&sensorAccel.device_id, currentData, sizeof(sensorAccel.device_id));
388 LOG_DATA("{}: sensorAccel.device_id: {}", nameId(), sensorAccel.device_id);
389 currentExtractLocation += sizeof(sensorAccel.device_id);
390 }
391 else if (dataField.name == "error_count")
392 {
393 std::memcpy(&sensorAccel.error_count, currentData, sizeof(sensorAccel.error_count));
394 LOG_DATA("{}: sensorAccel.error_count: {}", nameId(), sensorAccel.error_count);
395 currentExtractLocation += sizeof(sensorAccel.error_count);
396 }
397 else if (dataField.name == "x")
398 {
399 std::memcpy(&sensorAccel.x, currentData, sizeof(sensorAccel.x));
400 LOG_DATA("{}: sensorAccel.x: {}", nameId(), sensorAccel.x);
401 currentExtractLocation += sizeof(sensorAccel.x);
402 }
403 else if (dataField.name == "y")
404 {
405 std::memcpy(&sensorAccel.y, currentData, sizeof(sensorAccel.y));
406 LOG_DATA("{}: sensorAccel.y: {}", nameId(), sensorAccel.y);
407 currentExtractLocation += sizeof(sensorAccel.y);
408 }
409 else if (dataField.name == "z")
410 {
411 std::memcpy(&sensorAccel.z, currentData, sizeof(sensorAccel.z));
412 LOG_DATA("{}: sensorAccel.z: {}", nameId(), sensorAccel.z);
413 currentExtractLocation += sizeof(sensorAccel.z);
414 }
415 else if (dataField.name == "temperature")
416 {
417 std::memcpy(&sensorAccel.temperature, currentData, sizeof(sensorAccel.temperature));
418 LOG_DATA("{}: sensorAccel.temperature: {}", nameId(), sensorAccel.temperature);
419 currentExtractLocation += sizeof(sensorAccel.temperature);
420 }
421 else if (dataField.name == "clip_counter")
422 {
423 std::memcpy(sensorAccel.clip_counter.data(), currentData, sensorAccel.clip_counter.size());
424 LOG_DATA("{}: sensorAccel.clip_counter: {}", nameId(), fmt::join(sensorAccel.clip_counter, ", "));
425 currentExtractLocation += sensorAccel.clip_counter.size();
426 }
427 else if (dataField.name.compare(0, 7, "_padding")) // e.g. '_padding0', '_padding1'
428 {
429 currentExtractLocation += SensorAccel::padding; // Extraction Location should be moved to account for multiple padding
430 LOG_DATA("{}: sensorAccel: padding", nameId());
431 }
432 else
433 {
434 // FIXME: move 'currentExtractLocation', if yes, how far?
435 LOG_WARN("{}: dataField.name = '{}' or dataField.type = '{}' is unknown", nameId(), dataField.name, dataField.type);
436 }
437 }
438
439 LOG_DATA("{}: Inserting [{}] {}: {}", nameId(), sensorAccel.timestamp,
440 _subscribedMessages.at(messageData.msg_id).multi_id,
441 _subscribedMessages.at(messageData.msg_id).message_name);
442
443 _epochData.insert(std::make_pair(sensorAccel.timestamp,
444 MeasurementData{ .multi_id = _subscribedMessages.at(messageData.msg_id).multi_id,
445 .message_name = _subscribedMessages.at(messageData.msg_id).message_name,
446 .data = sensorAccel }));
447 }
448 else if (_subscribedMessages.at(messageData.msg_id).message_name == "sensor_gyro")
449 {
450 SensorGyro sensorGyro{};
451
452 for (const auto& dataField : messageFormat)
453 {
454 char* currentData = messageData.data.data() + currentExtractLocation;
455 if (dataField.name == "timestamp")
456 {
457 std::memcpy(&sensorGyro.timestamp, currentData, sizeof(sensorGyro.timestamp));
458 LOG_DATA("{}: sensorGyro.timestamp: {}", nameId(), sensorGyro.timestamp);
459 currentExtractLocation += sizeof(sensorGyro.timestamp);
460 }
461 else if (dataField.name == "timestamp_sample")
462 {
463 std::memcpy(&sensorGyro.timestamp_sample, currentData, sizeof(sensorGyro.timestamp_sample));
464 LOG_DATA("{}: sensorGyro.timestamp_sample: {}", nameId(), sensorGyro.timestamp_sample);
465 currentExtractLocation += sizeof(sensorGyro.timestamp_sample);
466 }
467 else if (dataField.name == "device_id")
468 {
469 std::memcpy(&sensorGyro.device_id, currentData, sizeof(sensorGyro.device_id));
470 LOG_DATA("{}: sensorGyro.device_id: {}", nameId(), sensorGyro.device_id);
471 currentExtractLocation += sizeof(sensorGyro.device_id);
472 }
473 else if (dataField.name == "x")
474 {
475 std::memcpy(&sensorGyro.x, currentData, sizeof(sensorGyro.x));
476 LOG_DATA("{}: sensorGyro.x: {}", nameId(), sensorGyro.x);
477 currentExtractLocation += sizeof(sensorGyro.x);
478 }
479 else if (dataField.name == "y")
480 {
481 std::memcpy(&sensorGyro.y, currentData, sizeof(sensorGyro.y));
482 LOG_DATA("{}: sensorGyro.y: {}", nameId(), sensorGyro.y);
483 currentExtractLocation += sizeof(sensorGyro.y);
484 }
485 else if (dataField.name == "z")
486 {
487 std::memcpy(&sensorGyro.z, currentData, sizeof(sensorGyro.z));
488 LOG_DATA("{}: sensorGyro.z: {}", nameId(), sensorGyro.z);
489 currentExtractLocation += sizeof(sensorGyro.z);
490 }
491 else if (dataField.name == "temperature")
492 {
493 std::memcpy(&sensorGyro.temperature, currentData, sizeof(sensorGyro.temperature));
494 LOG_DATA("{}: sensorGyro.temperature: {}", nameId(), sensorGyro.temperature);
495 currentExtractLocation += sizeof(sensorGyro.temperature);
496 }
497 else if (dataField.name == "error_count")
498 {
499 std::memcpy(&sensorGyro.error_count, currentData, sizeof(sensorGyro.error_count));
500 LOG_DATA("{}: sensorGyro.error_count: {}", nameId(), sensorGyro.error_count);
501 currentExtractLocation += sizeof(sensorGyro.error_count);
502 }
503 else
504 {
505 // FIXME: move 'currentExtractLocation', if yes, how far?
506 LOG_WARN("{}: dataField.name = '{}' or dataField.type = '{}' is unknown", nameId(), dataField.name, dataField.type);
507 }
508 }
509
510 LOG_DATA("{}: Inserting [{}] {}: {}", nameId(), sensorGyro.timestamp,
511 _subscribedMessages.at(messageData.msg_id).multi_id,
512 _subscribedMessages.at(messageData.msg_id).message_name);
513
514 _epochData.insert(std::make_pair(sensorGyro.timestamp,
515 MeasurementData{ .multi_id = _subscribedMessages.at(messageData.msg_id).multi_id,
516 .message_name = _subscribedMessages.at(messageData.msg_id).message_name,
517 .data = sensorGyro }));
518 }
519 else if (_subscribedMessages.at(messageData.msg_id).message_name == "sensor_mag")
520 {
521 SensorMag sensorMag{};
522 for (const auto& dataField : messageFormat)
523 {
524 char* currentData = messageData.data.data() + currentExtractLocation;
525 if (dataField.name == "timestamp")
526 {
527 std::memcpy(&sensorMag.timestamp, currentData, sizeof(sensorMag.timestamp));
528 LOG_DATA("{}: sensorMag.timestamp: {}", nameId(), sensorMag.timestamp);
529 currentExtractLocation += sizeof(sensorMag.timestamp);
530 }
531 else if (dataField.name == "timestamp_sample")
532 {
533 std::memcpy(&sensorMag.timestamp_sample, currentData, sizeof(sensorMag.timestamp_sample));
534 LOG_DATA("{}: sensorMag.timestamp_sample: {}", nameId(), sensorMag.timestamp_sample);
535 currentExtractLocation += sizeof(sensorMag.timestamp_sample);
536 }
537 else if (dataField.name == "device_id")
538 {
539 std::memcpy(&sensorMag.device_id, currentData, sizeof(sensorMag.device_id));
540 LOG_DATA("{}: sensorMag.device_id: {}", nameId(), sensorMag.device_id);
541 currentExtractLocation += sizeof(sensorMag.device_id);
542 }
543 else if (dataField.name == "x")
544 {
545 std::memcpy(&sensorMag.x, currentData, sizeof(sensorMag.x));
546 LOG_DATA("{}: sensorMag.x: {}", nameId(), sensorMag.x);
547 currentExtractLocation += sizeof(sensorMag.x);
548 }
549 else if (dataField.name == "y")
550 {
551 std::memcpy(&sensorMag.y, currentData, sizeof(sensorMag.y));
552 LOG_DATA("{}: sensorMag.y: {}", nameId(), sensorMag.y);
553 currentExtractLocation += sizeof(sensorMag.y);
554 }
555 else if (dataField.name == "z")
556 {
557 std::memcpy(&sensorMag.z, currentData, sizeof(sensorMag.z));
558 LOG_DATA("{}: sensorMag.z: {}", nameId(), sensorMag.z);
559 currentExtractLocation += sizeof(sensorMag.z);
560 }
561 else if (dataField.name == "temperature")
562 {
563 std::memcpy(&sensorMag.temperature, currentData, sizeof(sensorMag.temperature));
564 LOG_DATA("{}: sensorMag.temperature: {}", nameId(), sensorMag.temperature);
565 currentExtractLocation += sizeof(sensorMag.temperature);
566 }
567 else if (dataField.name == "error_count")
568 {
569 std::memcpy(&sensorMag.error_count, currentData, sizeof(sensorMag.error_count));
570 LOG_DATA("{}: sensorMag.error_count: {}", nameId(), sensorMag.error_count);
571 currentExtractLocation += sizeof(sensorMag.error_count);
572 }
573 else if (dataField.name == "is_external")
574 {
575 std::memcpy(&sensorMag.is_external, currentData, sizeof(sensorMag.is_external));
576 LOG_DATA("{}: sensorMag.is_external: {}", nameId(), sensorMag.is_external);
577 currentExtractLocation += sizeof(sensorMag.is_external);
578 }
579 else if (dataField.name.compare(0, 7, "_padding")) // e.g. '_padding0', '_padding1'
580 {
581 currentExtractLocation += SensorMag::padding; // Extraction Location should be moved to account for multiple padding
582 LOG_DATA("{}: sensorMag: padding", nameId());
583 }
584 else
585 {
586 // FIXME: move 'currentExtractLocation', if yes, how far?
587 LOG_WARN("{}: dataField.name = '{}' or dataField.type = '{}' is unknown", nameId(), dataField.name, dataField.type);
588 }
589 }
590
591 LOG_DATA("{}: Inserting [{}] {}: {}", nameId(), sensorMag.timestamp,
592 _subscribedMessages.at(messageData.msg_id).multi_id,
593 _subscribedMessages.at(messageData.msg_id).message_name);
594
595 _epochData.insert(std::make_pair(sensorMag.timestamp,
596 MeasurementData{ .multi_id = _subscribedMessages.at(messageData.msg_id).multi_id,
597 .message_name = _subscribedMessages.at(messageData.msg_id).message_name,
598 .data = sensorMag }));
599 }
600 else if (_subscribedMessages.at(messageData.msg_id).message_name == "vehicle_gps_position")
601 {
602 VehicleGpsPosition vehicleGpsPosition{};
603 for (const auto& dataField : messageFormat)
604 {
605 char* currentData = messageData.data.data() + currentExtractLocation;
606 if (dataField.name == "timestamp")
607 {
608 std::memcpy(&vehicleGpsPosition.timestamp, currentData, sizeof(vehicleGpsPosition.timestamp));
609 LOG_DATA("{}: vehicleGpsPosition.timestamp: {}", nameId(), vehicleGpsPosition.timestamp);
610 currentExtractLocation += sizeof(vehicleGpsPosition.timestamp);
611 }
612 else if (dataField.name == "time_utc_usec")
613 {
614 std::memcpy(&vehicleGpsPosition.time_utc_usec, currentData, sizeof(vehicleGpsPosition.time_utc_usec));
615 LOG_DATA("{}: vehicleGpsPosition.time_utc_usec: {}", nameId(), vehicleGpsPosition.time_utc_usec);
616 currentExtractLocation += sizeof(vehicleGpsPosition.time_utc_usec);
617 }
618 else if (dataField.name == "lat")
619 {
620 std::memcpy(&vehicleGpsPosition.lat, currentData, sizeof(vehicleGpsPosition.lat));
621 LOG_DATA("{}: vehicleGpsPosition.lat: {}", nameId(), vehicleGpsPosition.lat);
622 currentExtractLocation += sizeof(vehicleGpsPosition.lat);
623 }
624 else if (dataField.name == "lon")
625 {
626 std::memcpy(&vehicleGpsPosition.lon, currentData, sizeof(vehicleGpsPosition.lon));
627 LOG_DATA("{}: vehicleGpsPosition.lon: {}", nameId(), vehicleGpsPosition.lon);
628 currentExtractLocation += sizeof(vehicleGpsPosition.lon);
629 }
630 else if (dataField.name == "alt")
631 {
632 std::memcpy(&vehicleGpsPosition.alt, currentData, sizeof(vehicleGpsPosition.alt));
633 LOG_DATA("{}: vehicleGpsPosition.alt: {}", nameId(), vehicleGpsPosition.alt);
634 currentExtractLocation += sizeof(vehicleGpsPosition.alt);
635 }
636 else if (dataField.name == "alt_ellipsoid")
637 {
638 std::memcpy(&vehicleGpsPosition.alt_ellipsoid, currentData, sizeof(vehicleGpsPosition.alt_ellipsoid));
639 LOG_DATA("{}: vehicleGpsPosition.alt_ellipsoid: {}", nameId(), vehicleGpsPosition.alt_ellipsoid);
640 currentExtractLocation += sizeof(vehicleGpsPosition.alt_ellipsoid);
641 }
642 else if (dataField.name == "s_variance_m_s")
643 {
644 std::memcpy(&vehicleGpsPosition.s_variance_m_s, currentData, sizeof(vehicleGpsPosition.s_variance_m_s));
645 LOG_DATA("{}: vehicleGpsPosition.s_variance_m_s: {}", nameId(), vehicleGpsPosition.s_variance_m_s);
646 currentExtractLocation += sizeof(vehicleGpsPosition.s_variance_m_s);
647 }
648 else if (dataField.name == "c_variance_rad")
649 {
650 std::memcpy(&vehicleGpsPosition.c_variance_rad, currentData, sizeof(vehicleGpsPosition.c_variance_rad));
651 LOG_DATA("{}: vehicleGpsPosition.c_variance_rad: {}", nameId(), vehicleGpsPosition.c_variance_rad);
652 currentExtractLocation += sizeof(vehicleGpsPosition.c_variance_rad);
653 }
654 else if (dataField.name == "eph")
655 {
656 std::memcpy(&vehicleGpsPosition.eph, currentData, sizeof(vehicleGpsPosition.eph));
657 LOG_DATA("{}: vehicleGpsPosition.eph: {}", nameId(), vehicleGpsPosition.eph);
658 currentExtractLocation += sizeof(vehicleGpsPosition.eph);
659 }
660 else if (dataField.name == "epv")
661 {
662 std::memcpy(&vehicleGpsPosition.epv, currentData, sizeof(vehicleGpsPosition.epv));
663 LOG_DATA("{}: vehicleGpsPosition.epv: {}", nameId(), vehicleGpsPosition.epv);
664 currentExtractLocation += sizeof(vehicleGpsPosition.epv);
665 }
666 else if (dataField.name == "hdop")
667 {
668 std::memcpy(&vehicleGpsPosition.hdop, currentData, sizeof(vehicleGpsPosition.hdop));
669 LOG_DATA("{}: vehicleGpsPosition.hdop: {}", nameId(), vehicleGpsPosition.hdop);
670 currentExtractLocation += sizeof(vehicleGpsPosition.hdop);
671 }
672 else if (dataField.name == "vdop")
673 {
674 std::memcpy(&vehicleGpsPosition.vdop, currentData, sizeof(vehicleGpsPosition.lat));
675 LOG_DATA("{}: vehicleGpsPosition.vdop: {}", nameId(), vehicleGpsPosition.vdop);
676 currentExtractLocation += sizeof(vehicleGpsPosition.vdop);
677 }
678 else if (dataField.name == "noise_per_ms")
679 {
680 std::memcpy(&vehicleGpsPosition.noise_per_ms, currentData, sizeof(vehicleGpsPosition.noise_per_ms));
681 LOG_DATA("{}: vehicleGpsPosition.noise_per_ms: {}", nameId(), vehicleGpsPosition.noise_per_ms);
682 currentExtractLocation += sizeof(vehicleGpsPosition.noise_per_ms);
683 }
684 else if (dataField.name == "jamming_indicator")
685 {
686 std::memcpy(&vehicleGpsPosition.jamming_indicator, currentData, sizeof(vehicleGpsPosition.jamming_indicator));
687 LOG_DATA("{}: vehicleGpsPosition.jamming_indicator: {}", nameId(), vehicleGpsPosition.jamming_indicator);
688 currentExtractLocation += sizeof(vehicleGpsPosition.jamming_indicator);
689 }
690 else if (dataField.name == "vel_m_s")
691 {
692 std::memcpy(&vehicleGpsPosition.vel_m_s, currentData, sizeof(vehicleGpsPosition.vel_m_s));
693 LOG_DATA("{}: vehicleGpsPosition.vel_m_s: {}", nameId(), vehicleGpsPosition.vel_m_s);
694 currentExtractLocation += sizeof(vehicleGpsPosition.vel_m_s);
695 }
696 else if (dataField.name == "vel_n_m_s")
697 {
698 std::memcpy(&vehicleGpsPosition.vel_n_m_s, currentData, sizeof(vehicleGpsPosition.vel_n_m_s));
699 LOG_DATA("{}: vehicleGpsPosition.vel_n_m_s: {}", nameId(), vehicleGpsPosition.vel_n_m_s);
700 currentExtractLocation += sizeof(vehicleGpsPosition.vel_n_m_s);
701 }
702 else if (dataField.name == "vel_e_m_s")
703 {
704 std::memcpy(&vehicleGpsPosition.vel_e_m_s, currentData, sizeof(vehicleGpsPosition.vel_e_m_s));
705 LOG_DATA("{}: vehicleGpsPosition.vel_e_m_s: {}", nameId(), vehicleGpsPosition.vel_e_m_s);
706 currentExtractLocation += sizeof(vehicleGpsPosition.vel_e_m_s);
707 }
708 else if (dataField.name == "vel_d_m_s")
709 {
710 std::memcpy(&vehicleGpsPosition.vel_d_m_s, currentData, sizeof(vehicleGpsPosition.vel_d_m_s));
711 LOG_DATA("{}: vehicleGpsPosition.vel_d_m_s: {}", nameId(), vehicleGpsPosition.vel_d_m_s);
712 currentExtractLocation += sizeof(vehicleGpsPosition.vel_d_m_s);
713 }
714 else if (dataField.name == "cog_rad")
715 {
716 std::memcpy(&vehicleGpsPosition.cog_rad, currentData, sizeof(vehicleGpsPosition.cog_rad));
717 LOG_DATA("{}: vehicleGpsPosition.cog_rad: {}", nameId(), vehicleGpsPosition.cog_rad);
718 currentExtractLocation += sizeof(vehicleGpsPosition.cog_rad);
719 }
720 else if (dataField.name == "timestamp_time_relative")
721 {
722 std::memcpy(&vehicleGpsPosition.timestamp_time_relative, currentData, sizeof(vehicleGpsPosition.timestamp_time_relative));
723 LOG_DATA("{}: vehicleGpsPosition.timestamp_time_relative: {}", nameId(), vehicleGpsPosition.timestamp_time_relative);
724 currentExtractLocation += sizeof(vehicleGpsPosition.timestamp_time_relative);
725 }
726 else if (dataField.name == "heading")
727 {
728 std::memcpy(&vehicleGpsPosition.heading, currentData, sizeof(vehicleGpsPosition.heading));
729 LOG_DATA("{}: vehicleGpsPosition.heading: {}", nameId(), vehicleGpsPosition.heading);
730 currentExtractLocation += sizeof(vehicleGpsPosition.heading);
731 }
732 else if (dataField.name == "heading_offset")
733 {
734 std::memcpy(&vehicleGpsPosition.heading_offset, currentData, sizeof(vehicleGpsPosition.heading_offset));
735 LOG_DATA("{}: vehicleGpsPosition.heading_offset: {}", nameId(), vehicleGpsPosition.heading_offset);
736 currentExtractLocation += sizeof(vehicleGpsPosition.heading_offset);
737 }
738 else if (dataField.name == "fix_type")
739 {
740 std::memcpy(&vehicleGpsPosition.fix_type, currentData, sizeof(vehicleGpsPosition.fix_type));
741 LOG_DATA("{}: vehicleGpsPosition.fix_type: {}", nameId(), vehicleGpsPosition.fix_type);
742 currentExtractLocation += sizeof(vehicleGpsPosition.fix_type);
743 }
744 else if (dataField.name == "vel_ned_valid")
745 {
746 std::memcpy(&vehicleGpsPosition.vel_ned_valid, currentData, sizeof(vehicleGpsPosition.vel_ned_valid));
747 LOG_DATA("{}: vehicleGpsPosition.vel_ned_valid: {}", nameId(), vehicleGpsPosition.vel_ned_valid);
748 currentExtractLocation += sizeof(vehicleGpsPosition.vel_ned_valid);
749 }
750 else if (dataField.name == "satellites_used")
751 {
752 std::memcpy(&vehicleGpsPosition.satellites_used, currentData, sizeof(vehicleGpsPosition.satellites_used));
753 LOG_DATA("{}: vehicleGpsPosition.satellites_used: {}", nameId(), vehicleGpsPosition.satellites_used);
754 currentExtractLocation += sizeof(vehicleGpsPosition.satellites_used);
755 }
756 else if (dataField.name.compare(0, 7, "_padding")) // e.g. '_padding0', '_padding1'
757 {
758 currentExtractLocation += VehicleGpsPosition::padding; // Extraction Location should be moved to account for multiple padding
759 LOG_DATA("{}: vehicleGpsPosition: padding", nameId());
760 }
761 else
762 {
763 // FIXME: move 'currentExtractLocation', if yes, how far?
764 LOG_WARN("{}: dataField.name = '{}' or dataField.type = '{}' is unknown", nameId(), dataField.name, dataField.type);
765 }
766 }
767
768 if (lastGnssTime.timeSinceStartup)
769 {
770 [[maybe_unused]] auto newGnssTime = InsTime(1970, 1, 1, 0, 0, vehicleGpsPosition.time_utc_usec * 1e-6L);
771 LOG_DATA("{}: Updating GnssTime from {} to {} (Diff {} sec)", nameId(), lastGnssTime.gnssTime.toYMDHMS(), newGnssTime.toYMDHMS(), static_cast<double>((newGnssTime - lastGnssTime.gnssTime).count()));
772 LOG_DATA("{}: Updating tStartup from {} to {} (Diff {} sec)", nameId(), lastGnssTime.timeSinceStartup, vehicleGpsPosition.timestamp, (vehicleGpsPosition.timestamp - lastGnssTime.timeSinceStartup) * 1e-6L);
773 }
774
775 lastGnssTime.gnssTime = InsTime(1970, 1, 1, 0, 0, vehicleGpsPosition.time_utc_usec * 1e-6L);
776 lastGnssTime.timeSinceStartup = vehicleGpsPosition.timestamp;
777
778 while (true) // Delete all old VehicleGpsPosition entries
779 {
780 auto iter = std::ranges::find_if(_epochData, [](const std::pair<uint64_t, MeasurementData>& v) {
781 return std::holds_alternative<UlogFile::VehicleGpsPosition>(v.second.data);
782 });
783 if (iter == _epochData.end())
784 {
785 break;
786 }
787 _epochData.erase(iter);
788 }
789
790 LOG_DATA("{}: Inserting [{}] {}: {}", nameId(), vehicleGpsPosition.timestamp,
791 _subscribedMessages.at(messageData.msg_id).multi_id,
792 _subscribedMessages.at(messageData.msg_id).message_name);
793
794 _epochData.insert(std::make_pair(vehicleGpsPosition.timestamp,
795 MeasurementData{ .multi_id = _subscribedMessages.at(messageData.msg_id).multi_id,
796 .message_name = _subscribedMessages.at(messageData.msg_id).message_name,
797 .data = vehicleGpsPosition }));
798 }
799 else if (_subscribedMessages.at(messageData.msg_id).message_name == "vehicle_attitude")
800 {
801 VehicleAttitude vehicleAttitude{};
802 for (const auto& dataField : messageFormat)
803 {
804 char* currentData = messageData.data.data() + currentExtractLocation;
805 if (dataField.name == "timestamp")
806 {
807 std::memcpy(&vehicleAttitude.timestamp, currentData, sizeof(vehicleAttitude.timestamp));
808 LOG_DATA("{}: vehicleAttitude.timestamp: {}", nameId(), vehicleAttitude.timestamp);
809 currentExtractLocation += sizeof(vehicleAttitude.timestamp);
810 }
811 else if (dataField.name == "q")
812 {
813 std::memcpy(vehicleAttitude.q.data(), currentData, vehicleAttitude.q.size());
814 LOG_DATA("{}: vehicleAttitude.q: {}", nameId(), fmt::join(vehicleAttitude.q, ", "));
815 currentExtractLocation += vehicleAttitude.q.size();
816 }
817 else if (dataField.name == "delta_q_reset")
818 {
819 std::memcpy(vehicleAttitude.delta_q_reset.data(), currentData, vehicleAttitude.delta_q_reset.size());
820 LOG_DATA("{}: vehicleAttitude.delta_q_reset: {}", nameId(), fmt::join(vehicleAttitude.delta_q_reset, ", "));
821 currentExtractLocation += vehicleAttitude.delta_q_reset.size();
822 }
823 if (dataField.name == "quat_reset_counter")
824 {
825 std::memcpy(&vehicleAttitude.quat_reset_counter, currentData, sizeof(vehicleAttitude.quat_reset_counter));
826 LOG_DATA("{}: vehicleAttitude.quat_reset_counter: {}", nameId(), vehicleAttitude.quat_reset_counter);
827 currentExtractLocation += sizeof(vehicleAttitude.quat_reset_counter);
828 }
829 else if (dataField.name.compare(0, 7, "_padding")) // e.g. '_padding0', '_padding1'
830 {
831 currentExtractLocation += VehicleAttitude::padding; // Extraction Location should be moved to account for multiple padding
832 LOG_DATA("{}: VehicleAttitude: padding", nameId());
833 }
834 else
835 {
836 // FIXME: move 'currentExtractLocation', if yes, how far?
837 LOG_WARN("{}: dataField.name = '{}' or dataField.type = '{}' is unknown", nameId(), dataField.name, dataField.type);
838 }
839 }
840
841 while (true) // Delete all old VehicleAttitude entries
842 {
843 auto iter = std::ranges::find_if(_epochData, [](const std::pair<uint64_t, MeasurementData>& v) {
844 return std::holds_alternative<UlogFile::VehicleAttitude>(v.second.data);
845 });
846 if (iter == _epochData.end())
847 {
848 break;
849 }
850 _epochData.erase(iter);
851 }
852
853 LOG_DATA("{}: Inserting [{}] {}: {}", nameId(), vehicleAttitude.timestamp,
854 _subscribedMessages.at(messageData.msg_id).multi_id,
855 _subscribedMessages.at(messageData.msg_id).message_name);
856
857 _epochData.insert(std::make_pair(vehicleAttitude.timestamp,
858 MeasurementData{ .multi_id = _subscribedMessages.at(messageData.msg_id).multi_id,
859 .message_name = _subscribedMessages.at(messageData.msg_id).message_name,
860 .data = vehicleAttitude }));
861 }
862 else if (_subscribedMessages.at(messageData.msg_id).message_name == "vehicle_control_mode")
863 {
864 VehicleControlMode vehicleControlMode{};
865 for (const auto& dataField : messageFormat)
866 {
867 char* currentData = messageData.data.data() + currentExtractLocation;
868 if (dataField.name == "timestamp")
869 {
870 std::memcpy(&vehicleControlMode.timestamp, currentData, sizeof(vehicleControlMode.timestamp));
871 LOG_DATA("{}: vehicleControlMode.timestamp: {}", nameId(), vehicleControlMode.timestamp);
872 currentExtractLocation += sizeof(vehicleControlMode.timestamp);
873 }
874 else if (dataField.name == "flag_armed")
875 {
876 std::memcpy(&vehicleControlMode.flag_armed, currentData, sizeof(vehicleControlMode.flag_armed));
877 LOG_DATA("{}: vehicleControlMode.flag_armed: {}", nameId(), vehicleControlMode.flag_armed);
878 currentExtractLocation += sizeof(vehicleControlMode.flag_armed);
879 }
880 else if (dataField.name == "flag_external_manual_override_ok")
881 {
882 std::memcpy(&vehicleControlMode.flag_external_manual_override_ok, currentData, sizeof(vehicleControlMode.flag_external_manual_override_ok));
883 LOG_DATA("{}: vehicleControlMode.flag_external_manual_override_ok: {}", nameId(), vehicleControlMode.flag_external_manual_override_ok);
884 currentExtractLocation += sizeof(vehicleControlMode.flag_external_manual_override_ok);
885 }
886 else if (dataField.name == "flag_control_manual_enabled")
887 {
888 std::memcpy(&vehicleControlMode.flag_control_manual_enabled, currentData, sizeof(vehicleControlMode.flag_control_manual_enabled));
889 LOG_DATA("{}: vehicleControlMode.flag_control_manual_enabled: {}", nameId(), vehicleControlMode.flag_control_manual_enabled);
890 currentExtractLocation += sizeof(vehicleControlMode.flag_control_manual_enabled);
891 }
892 else if (dataField.name == "flag_control_auto_enabled")
893 {
894 std::memcpy(&vehicleControlMode.flag_control_auto_enabled, currentData, sizeof(vehicleControlMode.flag_control_auto_enabled));
895 LOG_DATA("{}: vehicleControlMode.flag_control_auto_enabled: {}", nameId(), vehicleControlMode.flag_control_auto_enabled);
896 currentExtractLocation += sizeof(vehicleControlMode.flag_control_auto_enabled);
897 }
898 else if (dataField.name == "flag_control_offboard_enabled")
899 {
900 std::memcpy(&vehicleControlMode.flag_control_offboard_enabled, currentData, sizeof(vehicleControlMode.flag_control_offboard_enabled));
901 LOG_DATA("{}: vehicleControlMode.flag_control_offboard_enabled: {}", nameId(), vehicleControlMode.flag_control_offboard_enabled);
902 currentExtractLocation += sizeof(vehicleControlMode.flag_control_offboard_enabled);
903 }
904 else if (dataField.name == "flag_control_rates_enabled")
905 {
906 std::memcpy(&vehicleControlMode.flag_control_rates_enabled, currentData, sizeof(vehicleControlMode.flag_control_rates_enabled));
907 LOG_DATA("{}: vehicleControlMode.flag_control_rates_enabled: {}", nameId(), vehicleControlMode.flag_control_rates_enabled);
908 currentExtractLocation += sizeof(vehicleControlMode.flag_control_rates_enabled);
909 }
910 else if (dataField.name == "flag_control_attitude_enabled")
911 {
912 std::memcpy(&vehicleControlMode.flag_control_attitude_enabled, currentData, sizeof(vehicleControlMode.flag_control_attitude_enabled));
913 LOG_DATA("{}: vehicleControlMode.flag_control_attitude_enabled: {}", nameId(), vehicleControlMode.flag_control_attitude_enabled);
914 currentExtractLocation += sizeof(vehicleControlMode.flag_control_attitude_enabled);
915 }
916 else if (dataField.name == "flag_control_yawrate_override_enabled")
917 {
918 std::memcpy(&vehicleControlMode.flag_control_yawrate_override_enabled, currentData, sizeof(vehicleControlMode.flag_control_yawrate_override_enabled));
919 LOG_DATA("{}: vehicleControlMode.flag_control_yawrate_override_enabled: {}", nameId(), vehicleControlMode.flag_control_yawrate_override_enabled);
920 currentExtractLocation += sizeof(vehicleControlMode.flag_control_yawrate_override_enabled);
921 }
922 else if (dataField.name == "flag_control_rattitude_enabled")
923 {
924 std::memcpy(&vehicleControlMode.flag_control_rattitude_enabled, currentData, sizeof(vehicleControlMode.flag_control_rattitude_enabled));
925 LOG_DATA("{}: vehicleControlMode.flag_control_rattitude_enabled: {}", nameId(), vehicleControlMode.flag_control_rattitude_enabled);
926 currentExtractLocation += sizeof(vehicleControlMode.flag_control_rattitude_enabled);
927 }
928 else if (dataField.name == "flag_control_force_enabled")
929 {
930 std::memcpy(&vehicleControlMode.flag_control_force_enabled, currentData, sizeof(vehicleControlMode.flag_control_force_enabled));
931 LOG_DATA("{}: vehicleControlMode.flag_control_force_enabled: {}", nameId(), vehicleControlMode.flag_control_force_enabled);
932 currentExtractLocation += sizeof(vehicleControlMode.flag_control_force_enabled);
933 }
934 else if (dataField.name == "flag_control_acceleration_enabled")
935 {
936 std::memcpy(&vehicleControlMode.flag_control_acceleration_enabled, currentData, sizeof(vehicleControlMode.flag_control_acceleration_enabled));
937 LOG_DATA("{}: vehicleControlMode.flag_control_acceleration_enabled: {}", nameId(), vehicleControlMode.flag_control_acceleration_enabled);
938 currentExtractLocation += sizeof(vehicleControlMode.flag_control_acceleration_enabled);
939 }
940 else if (dataField.name == "flag_control_velocity_enabled")
941 {
942 std::memcpy(&vehicleControlMode.flag_control_velocity_enabled, currentData, sizeof(vehicleControlMode.flag_control_velocity_enabled));
943 LOG_DATA("{}: vehicleControlMode.flag_control_velocity_enabled: {}", nameId(), vehicleControlMode.flag_control_velocity_enabled);
944 currentExtractLocation += sizeof(vehicleControlMode.flag_control_velocity_enabled);
945 }
946 else if (dataField.name == "flag_control_position_enabled")
947 {
948 std::memcpy(&vehicleControlMode.flag_control_position_enabled, currentData, sizeof(vehicleControlMode.flag_control_position_enabled));
949 LOG_DATA("{}: vehicleControlMode.flag_control_position_enabled: {}", nameId(), vehicleControlMode.flag_control_position_enabled);
950 currentExtractLocation += sizeof(vehicleControlMode.flag_control_position_enabled);
951 }
952 else if (dataField.name == "flag_control_altitude_enabled")
953 {
954 std::memcpy(&vehicleControlMode.flag_control_altitude_enabled, currentData, sizeof(vehicleControlMode.flag_control_altitude_enabled));
955 LOG_DATA("{}: vehicleControlMode.flag_control_altitude_enabled: {}", nameId(), vehicleControlMode.flag_control_altitude_enabled);
956 currentExtractLocation += sizeof(vehicleControlMode.flag_control_altitude_enabled);
957 }
958 else if (dataField.name == "flag_control_climb_rate_enabled")
959 {
960 std::memcpy(&vehicleControlMode.flag_control_climb_rate_enabled, currentData, sizeof(vehicleControlMode.flag_control_climb_rate_enabled));
961 LOG_DATA("{}: vehicleControlMode.flag_control_climb_rate_enabled: {}", nameId(), vehicleControlMode.flag_control_climb_rate_enabled);
962 currentExtractLocation += sizeof(vehicleControlMode.flag_control_climb_rate_enabled);
963 }
964 else if (dataField.name == "flag_control_termination_enabled")
965 {
966 std::memcpy(&vehicleControlMode.flag_control_termination_enabled, currentData, sizeof(vehicleControlMode.flag_control_termination_enabled));
967 LOG_DATA("{}: vehicleControlMode.flag_control_termination_enabled: {}", nameId(), vehicleControlMode.flag_control_termination_enabled);
968 currentExtractLocation += sizeof(vehicleControlMode.flag_control_termination_enabled);
969 }
970 else if (dataField.name == "flag_control_fixed_hdg_enabled")
971 {
972 std::memcpy(&vehicleControlMode.flag_control_fixed_hdg_enabled, currentData, sizeof(vehicleControlMode.flag_control_fixed_hdg_enabled));
973 LOG_DATA("{}: vehicleControlMode.flag_control_fixed_hdg_enabled: {}", nameId(), vehicleControlMode.flag_control_fixed_hdg_enabled);
974 currentExtractLocation += sizeof(vehicleControlMode.flag_control_fixed_hdg_enabled);
975 }
976 else if (dataField.name.compare(0, 7, "_padding")) // e.g. '_padding0', '_padding1'
977 {
978 currentExtractLocation += VehicleControlMode::padding; // Extraction Location should be moved to account for multiple padding
979 LOG_DATA("{}: VehicleControlMode: padding", nameId());
980 }
981 else
982 {
983 // FIXME: move 'currentExtractLocation', if yes, how far?
984 LOG_WARN("{}: dataField.name = '{}' or dataField.type = '{}' is unknown", nameId(), dataField.name, dataField.type);
985 }
986 }
987 // TODO: insert to _epochData necessary for 'vehicleControlMode'?
988 }
989 else if (_subscribedMessages.at(messageData.msg_id).message_name == "vehicle_air_data")
990 {
991 VehicleAirData vehicleAirData{};
992 for (const auto& dataField : messageFormat)
993 {
994 char* currentData = messageData.data.data() + currentExtractLocation;
995 if (dataField.name == "timestamp")
996 {
997 std::memcpy(&vehicleAirData.timestamp, currentData, sizeof(vehicleAirData.timestamp));
998 LOG_DATA("{}: vehicleAirData.timestamp: {}", nameId(), vehicleAirData.timestamp);
999 currentExtractLocation += sizeof(vehicleAirData.timestamp);
1000 }
1001 else if (dataField.name == "timestamp_sample")
1002 {
1003 std::memcpy(&vehicleAirData.timestamp_sample, currentData, sizeof(vehicleAirData.timestamp_sample));
1004 LOG_DATA("{}: vehicleAirData.timestamp_sample: {}", nameId(), vehicleAirData.timestamp_sample);
1005 currentExtractLocation += sizeof(vehicleAirData.timestamp_sample);
1006 }
1007 else if (dataField.name == "baro_device_id")
1008 {
1009 std::memcpy(&vehicleAirData.baro_device_id, currentData, sizeof(vehicleAirData.baro_device_id));
1010 LOG_DATA("{}: vehicleAirData.baro_device_id: {}", nameId(), vehicleAirData.baro_device_id);
1011 currentExtractLocation += sizeof(vehicleAirData.baro_device_id);
1012 }
1013 else if (dataField.name == "baro_alt_meter")
1014 {
1015 std::memcpy(&vehicleAirData.baro_alt_meter, currentData, sizeof(vehicleAirData.baro_alt_meter));
1016 LOG_DATA("{}: vehicleAirData.baro_alt_meter: {}", nameId(), vehicleAirData.baro_alt_meter);
1017 currentExtractLocation += sizeof(vehicleAirData.baro_alt_meter);
1018 }
1019 else if (dataField.name == "baro_temp_celcius")
1020 {
1021 std::memcpy(&vehicleAirData.baro_temp_celcius, currentData, sizeof(vehicleAirData.baro_temp_celcius));
1022 LOG_DATA("{}: vehicleAirData.baro_temp_celcius: {}", nameId(), vehicleAirData.baro_temp_celcius);
1023 currentExtractLocation += sizeof(vehicleAirData.baro_temp_celcius);
1024 }
1025 else if (dataField.name == "baro_pressure_pa")
1026 {
1027 std::memcpy(&vehicleAirData.baro_pressure_pa, currentData, sizeof(vehicleAirData.baro_pressure_pa));
1028 LOG_DATA("{}: vehicleAirData.baro_pressure_pa: {}", nameId(), vehicleAirData.baro_pressure_pa);
1029 currentExtractLocation += sizeof(vehicleAirData.baro_pressure_pa);
1030 }
1031 else if (dataField.name == "rho")
1032 {
1033 std::memcpy(&vehicleAirData.rho, currentData, sizeof(vehicleAirData.rho));
1034 LOG_DATA("{}: vehicleAirData.rho: {}", nameId(), vehicleAirData.rho);
1035 currentExtractLocation += sizeof(vehicleAirData.rho);
1036 }
1037 else if (dataField.name.compare(0, 7, "_padding")) // e.g. '_padding0', '_padding1'
1038 {
1039 currentExtractLocation += VehicleAirData::padding; // Extraction Location should be moved to account for multiple padding
1040 LOG_DATA("{}: VehicleControlMode: padding", nameId());
1041 }
1042 else
1043 {
1044 // FIXME: move 'currentExtractLocation', if yes, how far?
1045 LOG_WARN("{}: dataField.name = '{}' or dataField.type = '{}' is unknown", nameId(), dataField.name, dataField.type);
1046 }
1047 }
1048 // TODO: insert to _epochData necessary for 'vehicleAirData'?
1049 }
1050 else
1051 {
1052 LOG_DATA("{}: UKNOWN: _subscribedMessages.at(messageData.msg_id).message_name = {}", nameId(), _subscribedMessages.at(messageData.msg_id).message_name);
1053 }
1054
1055 // #########################################################################################################################################
1056 // Callbacks
1057 // #########################################################################################################################################
1058 // This is the hasEnoughData check for an ImuObs
1059 if (auto multi_id = enoughImuDataAvailable();
1060 multi_id >= 0)
1061 {
1062 LOG_DATA("{}: Construct ImuObs and invoke callback", nameId());
1063
1064 auto obs = std::make_shared<ImuObs>(this->_imuPos);
1065
1066 uint64_t timeSinceStartupNew{};
1067
1068 for ([[maybe_unused]] const auto& [timestamp, measurement] : _epochData)
1069 {
1070 LOG_DATA("{}: [{}] {}: {}", nameId(), timestamp, measurement.multi_id, measurement.message_name);
1071 }
1072
1073 bool accelFound = false;
1074 bool angularRateFound = false;
1075 // Construct ImuObs
1076 for (auto it = _epochData.begin(); it != _epochData.end();)
1077 {
1078 // Add accel data to ImuObs
1079 if (std::holds_alternative<SensorAccel>(it->second.data) && (it->second.multi_id == static_cast<uint8_t>(multi_id))
1080 && !accelFound)
1081 {
1082 accelFound = true;
1083 timeSinceStartupNew = it->first;
1084 float accelX = std::get<SensorAccel>(it->second.data).x;
1085 float accelY = std::get<SensorAccel>(it->second.data).y;
1086 float accelZ = std::get<SensorAccel>(it->second.data).z;
1087 obs->p_acceleration = { accelX, accelY, accelZ };
1088 auto delIt = it;
1089 ++it;
1090 _epochData.erase(delIt);
1091 LOG_DATA("{}: accelX = {}, accelY = {}, accelZ = {}", nameId(), accelX, accelY, accelZ);
1092 }
1093 // Add gyro data to ImuObs
1094 else if (std::holds_alternative<SensorGyro>(it->second.data) && (it->second.multi_id == static_cast<uint8_t>(multi_id))
1095 && !angularRateFound)
1096 {
1097 angularRateFound = true;
1098 timeSinceStartupNew = it->first;
1099 float gyroX = std::get<SensorGyro>(it->second.data).x;
1100 float gyroY = std::get<SensorGyro>(it->second.data).y;
1101 float gyroZ = std::get<SensorGyro>(it->second.data).z;
1102 obs->p_angularRate = { gyroX, gyroY, gyroZ };
1103 auto delIt = it;
1104 ++it;
1105 _epochData.erase(delIt);
1106 LOG_DATA("{}: gyroX = {}, gyroY = {}, gyroZ = {}", nameId(), gyroX, gyroY, gyroZ);
1107 }
1108 // Add mag data to ImuObs
1109 else if (std::holds_alternative<SensorMag>(it->second.data) && (it->second.multi_id == static_cast<uint8_t>(multi_id))
1110 && !obs->p_magneticField.has_value()) // TODO: Find out what is multi_id = 1. Px4 Mini is supposed to have only one magnetometer
1111 {
1112 float magX = std::get<SensorMag>(it->second.data).x;
1113 float magY = std::get<SensorMag>(it->second.data).y;
1114 float magZ = std::get<SensorMag>(it->second.data).z;
1115 obs->p_magneticField.emplace(magX, magY, magZ);
1116 auto delIt = it;
1117 ++it;
1118 _epochData.erase(delIt);
1119 LOG_DATA("{}: magX = {}, magY = {}, magZ = {}", nameId(), magX, magY, magZ);
1120 }
1121 else
1122 {
1123 ++it;
1124 }
1125 }
1126
1127 obs->insTime = lastGnssTime.gnssTime + std::chrono::microseconds(static_cast<int64_t>(timeSinceStartupNew) - static_cast<int64_t>(lastGnssTime.timeSinceStartup));
1128
1129 LOG_DATA("{}: Sending out ImuObs {}: {}", nameId(), multi_id, obs->insTime.toYMDHMS());
1130 if (multi_id == 0)
1131 {
1132 invokeCallbacks(OUTPUT_PORT_INDEX_IMUOBS_1, obs);
1133 }
1134 else if (multi_id == 1)
1135 {
1136 invokeCallbacks(OUTPUT_PORT_INDEX_IMUOBS_2, obs);
1137 }
1138 else
1139 {
1140 LOG_ERROR("{}: multi_id = {} is invalid", nameId(), multi_id);
1141 }
1142 return obs;
1143 }
1144 if (auto [gpsIter, attIter] = findPosVelAttData();
1145 gpsIter != _epochData.end() && attIter != _epochData.end())
1146 {
1147 LOG_DATA("{}: Construct PosVelAtt and invoke callback", nameId());
1148
1149 auto obs = std::make_shared<NAV::PosVelAtt>();
1150
1151 const auto& vehicleGpsPosition = std::get<VehicleGpsPosition>(gpsIter->second.data);
1152 const auto& vehicleAttitude = std::get<VehicleAttitude>(attIter->second.data);
1153
1154 obs->insTime = InsTime(0, 0, 0, 0, 0, vehicleGpsPosition.time_utc_usec * 1e-6L);
1155 obs->setPosVelAtt_n(Eigen::Vector3d{ deg2rad(1e-7 * static_cast<double>(vehicleGpsPosition.lat)), deg2rad(1e-7 * static_cast<double>(vehicleGpsPosition.lon)), 1e-3 * (static_cast<double>(vehicleGpsPosition.alt_ellipsoid)) },
1156 Eigen::Vector3d{ vehicleGpsPosition.vel_n_m_s, vehicleGpsPosition.vel_e_m_s, vehicleGpsPosition.vel_d_m_s },
1157 Eigen::Quaterniond{ vehicleAttitude.q.at(0), vehicleAttitude.q.at(1), vehicleAttitude.q.at(2), vehicleAttitude.q.at(3) });
1158 // TODO: Check order of w,x,y,z
1159 // TODO: Check if this is quaternion_nb
1160
1161 // Above it is ensured that only one gps and att element is present.
1162 // Here we still need to delete the used elements, otherwise the next iteration would find the elements again.
1163 _epochData.erase(gpsIter);
1164 _epochData.erase(attIter);
1165
1166 LOG_DATA("{}: Sending out PosVelAtt: {}", nameId(), obs->insTime.toYMDHMS());
1167 invokeCallbacks(OUTPUT_PORT_INDEX_POSVELATT, obs);
1168 return obs;
1169 }
1170 }
1171 else if (ulogMsgHeader.msgHeader.msg_type == 'L')
1172 {
1173 vendor::pixhawk::message_logging_s messageLog;
1174 messageLog.header = ulogMsgHeader.msgHeader;
1175 read(reinterpret_cast<char*>(&messageLog.log_level), sizeof(messageLog.log_level));
1176
1177 if (messageLog.log_level == 48) // '0'
1178 {
1179 LOG_DATA("{}: Log-level: EMERG - System is unusable", nameId());
1180 }
1181 else if (messageLog.log_level == 49) // '1'
1182 {
1183 LOG_DATA("{}: Log-level: ALERT - Action must be taken immediately", nameId());
1184 }
1185 else if (messageLog.log_level == 50) // '2'
1186 {
1187 LOG_DATA("{}: Log-level: CRIT - Critical conditions", nameId());
1188 }
1189 else if (messageLog.log_level == 51) // '3'
1190 {
1191 LOG_DATA("{}: Log-level: ERR - Error conditions", nameId());
1192 }
1193 else if (messageLog.log_level == 52) // '4'
1194 {
1195 LOG_DATA("{}: Log-level: WARNING - Warning conditions", nameId());
1196 }
1197 else if (messageLog.log_level == 53) // '5'
1198 {
1199 LOG_DATA("{}: Log-level: NOTICE - Normal but significant condition", nameId());
1200 }
1201 else if (messageLog.log_level == 54) // '6'
1202 {
1203 LOG_DATA("{}: Log-level: INFO - Informational", nameId());
1204 }
1205 else if (messageLog.log_level == 55) // '7'
1206 {
1207 LOG_DATA("{}: Log-level: DEBUG - Debug-level messages", nameId());
1208 }
1209 else
1210 {
1211 LOG_WARN("{}: Log-level is out of scope ({}) - possible data loss", nameId(), messageLog.log_level);
1212 }
1213
1214 read(reinterpret_cast<char*>(&messageLog.timestamp), sizeof(messageLog.timestamp));
1215 LOG_DATA("{}: messageLog.timestamp [µs]: {}", nameId(), messageLog.timestamp);
1216
1217 messageLog.message.resize(messageLog.header.msg_size - 9);
1218 read(messageLog.message.data(), messageLog.header.msg_size - 9);
1219 LOG_DATA("{}: messageLog.message: {}", nameId(), messageLog.message);
1220 }
1221 else if (ulogMsgHeader.msgHeader.msg_type == 'C')
1222 {
1223 vendor::pixhawk::message_logging_tagged_s messageLogTagged;
1224 messageLogTagged.header = ulogMsgHeader.msgHeader;
1225 read(reinterpret_cast<char*>(&messageLogTagged.log_level), sizeof(messageLogTagged.log_level));
1226
1227 if (messageLogTagged.log_level == 48) // '0'
1228 {
1229 LOG_DATA("{}: Log-level: EMERG - System is unusable", nameId());
1230 }
1231 else if (messageLogTagged.log_level == 49) // '1'
1232 {
1233 LOG_DATA("{}: Log-level: ALERT - Action must be taken immediately", nameId());
1234 }
1235 else if (messageLogTagged.log_level == 50) // '2'
1236 {
1237 LOG_DATA("{}: Log-level: CRIT - Critical conditions", nameId());
1238 }
1239 else if (messageLogTagged.log_level == 51) // '3'
1240 {
1241 LOG_DATA("{}: Log-level: ERR - Error conditions", nameId());
1242 }
1243 else if (messageLogTagged.log_level == 52) // '4'
1244 {
1245 LOG_DATA("{}: Log-level: WARNING - Warning conditions", nameId());
1246 }
1247 else if (messageLogTagged.log_level == 53) // '5'
1248 {
1249 LOG_DATA("{}: Log-level: NOTICE - Normal but significant condition", nameId());
1250 }
1251 else if (messageLogTagged.log_level == 54) // '6'
1252 {
1253 LOG_DATA("{}: Log-level: INFO - Informational", nameId());
1254 }
1255 else if (messageLogTagged.log_level == 55) // '7'
1256 {
1257 LOG_DATA("{}: Log-level: DEBUG - Debug-level messages", nameId());
1258 }
1259 else
1260 {
1261 LOG_WARN("{}: Log-level is out of scope ({}) - possible data loss", nameId(), messageLogTagged.log_level);
1262 }
1263
1264 read(reinterpret_cast<char*>(&messageLogTagged.tag), sizeof(messageLogTagged.tag));
1265 LOG_DATA("{}: messageLogTagged.tag: {}", nameId(), messageLogTagged.tag);
1266 read(reinterpret_cast<char*>(&messageLogTagged.timestamp), sizeof(messageLogTagged.timestamp));
1267 LOG_DATA("{}: messageLogTagged.timestamp [µs]: {}", nameId(), messageLogTagged.timestamp);
1268
1269 messageLogTagged.message.resize(messageLogTagged.header.msg_size - 11);
1270 read(messageLogTagged.message.data(), messageLogTagged.header.msg_size - 11);
1271 LOG_DATA("{}: messageLogTagged.header.msg_size: {}", nameId(), messageLogTagged.header.msg_size);
1272 }
1273 else if (ulogMsgHeader.msgHeader.msg_type == 'S')
1274 {
1275 vendor::pixhawk::message_sync_s messageSync;
1276 messageSync.header = ulogMsgHeader.msgHeader;
1277 read(reinterpret_cast<char*>(messageSync.syncMsg.data()), sizeof(messageSync.syncMsg));
1278 LOG_DATA("{}: messageSync.syncMsg[0]: {}", nameId(), messageSync.syncMsg[0]);
1279 }
1280 else if (ulogMsgHeader.msgHeader.msg_type == 'O')
1281 {
1282 vendor::pixhawk::message_dropout_s messageDropout;
1283 messageDropout.header = ulogMsgHeader.msgHeader;
1284 read(reinterpret_cast<char*>(&messageDropout.duration), sizeof(messageDropout.duration));
1285 LOG_WARN("{}: Dropout of duration: {} ms", nameId(), messageDropout.duration);
1286 }
1287 else if (ulogMsgHeader.msgHeader.msg_type == 'I')
1288 {
1289 readInformationMessage(ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
1290 }
1291 else if (ulogMsgHeader.msgHeader.msg_type == 'M')
1292 {
1293 readInformationMessageMulti(ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
1294 }
1295 else if (ulogMsgHeader.msgHeader.msg_type == 'P')
1296 {
1297 readParameterMessage(ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
1298 }
1299 else if (ulogMsgHeader.msgHeader.msg_type == 'Q')
1300 {
1301 readParameterMessageDefault(ulogMsgHeader.msgHeader.msg_size, ulogMsgHeader.msgHeader.msg_type);
1302 }
1303 else
1304 {
1305 std::string nextChars;
1306 [[maybe_unused]] auto unidentifiedPos = static_cast<uint64_t>(tellg());
1307 nextChars.resize(100);
1308 read(nextChars.data(), 100);
1309 LOG_WARN("{}: Message type not identified. Position: {}, The next 100 chars: {}", nameId(), unidentifiedPos, nextChars);
1310
1311 // Reset read cursor
1312 seekg(-100, std::ios_base::cur);
1313 }
1314
1315 if (!good() || eof())
1316 {
1317 break;
1318 }
1319 }
1320 return nullptr;
1321 }
1322
1323 void NAV::UlogFile::readInformationMessage(uint16_t msgSize, char msgType)
1324 {
1325 // Read msg size (2B) and type (1B)
1326 vendor::pixhawk::message_info_s messageInfo;
1327 messageInfo.header.msg_size = msgSize;
1328 messageInfo.header.msg_type = msgType;
1329 read(reinterpret_cast<char*>(&messageInfo.key_len), sizeof(messageInfo.key_len));
1330
1331 // Read 'key' identifier ('keylength' byte) and its associated 'value'
1332 messageInfo.key.resize(messageInfo.key_len);
1333 read(messageInfo.key.data(), messageInfo.key_len);
1334 // if (!good() || eof())
1335 // {
1336 // return false;
1337 // }
1338
1339 messageInfo.value.resize(static_cast<size_t>(messageInfo.header.msg_size - 1 - messageInfo.key_len)); // 'msg_size' contains key and value, but not header
1340 read(messageInfo.value.data(), messageInfo.header.msg_size - 1 - messageInfo.key_len);
1341 LOG_DATA("{}: Information message - key: {}", nameId(), messageInfo.key);
1342 LOG_DATA("{}: Information message - value: {}", nameId(), messageInfo.value);
1343 }
1344
1345 void NAV::UlogFile::readInformationMessageMulti(uint16_t msgSize, char msgType)
1346 {
1347 // Read msg size (2B) and type (1B)
1348 vendor::pixhawk::ulog_message_info_multiple_header_s messageInfoMulti;
1349 messageInfoMulti.header.msg_size = msgSize;
1350 messageInfoMulti.header.msg_type = msgType;
1351 read(reinterpret_cast<char*>(&messageInfoMulti.is_continued), sizeof(messageInfoMulti.is_continued));
1352 read(reinterpret_cast<char*>(&messageInfoMulti.key_len), sizeof(messageInfoMulti.key_len));
1353
1354 // Read 'key' identifier ('keylength' byte) and its associated 'value'
1355 messageInfoMulti.key.resize(messageInfoMulti.key_len);
1356 read(messageInfoMulti.key.data(), messageInfoMulti.key_len);
1357 messageInfoMulti.value.resize(static_cast<size_t>(messageInfoMulti.header.msg_size - 2 - messageInfoMulti.key_len)); // contains 'is_continued' flag in contrast to information message
1358 read(messageInfoMulti.value.data(), messageInfoMulti.header.msg_size - 2 - messageInfoMulti.key_len);
1359 LOG_DATA("{}: Information message multi - key_len: {}", nameId(), messageInfoMulti.key_len);
1360 LOG_DATA("{}: Information message multi - key: {}", nameId(), messageInfoMulti.key);
1361 LOG_DATA("{}: Information message multi - value: {}", nameId(), messageInfoMulti.value);
1362
1363 // TODO: Use 'is_continued' to generate a list of values with the same key
1364 }
1365
1366 void NAV::UlogFile::readParameterMessage(uint16_t msgSize, char msgType)
1367 {
1368 // Read msg size (2B) and type (1B)
1369 vendor::pixhawk::message_info_s messageParam;
1370 messageParam.header.msg_size = msgSize;
1371 messageParam.header.msg_type = msgType;
1372 read(reinterpret_cast<char*>(&messageParam.key_len), sizeof(messageParam.key_len));
1373
1374 // Read 'key' identifier ('keylength' byte) and its associated 'value'
1375 messageParam.key.resize(messageParam.key_len);
1376 read(messageParam.key.data(), messageParam.key_len);
1377
1378 if (!(messageParam.key.find("int32_t")) && !(messageParam.key.find("float")))
1379 {
1380 LOG_WARN("{}: Parameter message contains invalid data type. It is neither 'int32_t', nor 'float', instead: {}", nameId(), messageParam.key);
1381 }
1382
1383 if (messageParam.header.msg_size - 1 - messageParam.key_len < 0)
1384 {
1385 LOG_WARN("{}: Parameter msg has key_len: {}", nameId(), messageParam.key_len);
1386 }
1387 else
1388 {
1389 messageParam.value.resize(static_cast<size_t>(messageParam.header.msg_size - 1 - messageParam.key_len)); // 'msg_size' contains key and value, but not header
1390 read(messageParam.value.data(), messageParam.header.msg_size - 1 - messageParam.key_len);
1391 LOG_DATA("{}: Parameter message - key: {}", nameId(), messageParam.key);
1392 LOG_DATA("{}: Parameter message - value: {}", nameId(), messageParam.value);
1393 }
1394 }
1395
1396 void NAV::UlogFile::readParameterMessageDefault(uint16_t msgSize, char msgType)
1397 {
1398 vendor::pixhawk::ulog_message_parameter_default_header_s messageParamDefault;
1399 messageParamDefault.header.msg_size = msgSize;
1400 messageParamDefault.header.msg_type = msgType;
1401 read(reinterpret_cast<char*>(&messageParamDefault.default_types), sizeof(messageParamDefault.default_types));
1402 read(reinterpret_cast<char*>(&messageParamDefault.key_len), sizeof(messageParamDefault.key_len));
1403
1404 messageParamDefault.key.resize(messageParamDefault.key_len);
1405 read(messageParamDefault.key.data(), messageParamDefault.key_len);
1406 messageParamDefault.value.resize(static_cast<size_t>(messageParamDefault.header.msg_size - 2 - messageParamDefault.key_len));
1407 read(messageParamDefault.value.data(), messageParamDefault.header.msg_size - 2 - messageParamDefault.key_len);
1408 LOG_DEBUG("{}: Parameter default message - key: {}", nameId(), messageParamDefault.key);
1409 LOG_DEBUG("{}: Parameter default message - value: {}", nameId(), messageParamDefault.value);
1410
1411 // TODO: Restriction on '1<<0' and '1<<1'
1412 }
1413
1414 int8_t NAV::UlogFile::enoughImuDataAvailable()
1415 {
1416 std::array<bool, 2> accelHasData{};
1417 std::array<bool, 2> gyroHasData{};
1418
1419 for ([[maybe_unused]] const auto& [timestamp, measurement] : _epochData)
1420 {
1421 if (std::holds_alternative<SensorAccel>(measurement.data))
1422 {
1423 accelHasData.at(measurement.multi_id) = true;
1424 }
1425 else if (std::holds_alternative<SensorGyro>(measurement.data))
1426 {
1427 gyroHasData.at(measurement.multi_id) = true;
1428 }
1429 }
1430
1431 if (lastGnssTime.timeSinceStartup)
1432 {
1433 for (size_t i = 0; i < 2; ++i)
1434 {
1435 if (accelHasData.at(i) && gyroHasData.at(i))
1436 {
1437 return static_cast<int8_t>(i);
1438 }
1439 }
1440 }
1441
1442 return -1;
1443 }
1444
1445 std::array<std::multimap<uint64_t, NAV::UlogFile::MeasurementData>::iterator, 2> NAV::UlogFile::findPosVelAttData()
1446 {
1447 auto gpsIter = _epochData.end();
1448 auto attIter = _epochData.end();
1449
1450 for (auto iter = _epochData.begin(); iter != _epochData.end(); ++iter)
1451 {
1452 if (std::holds_alternative<VehicleGpsPosition>(iter->second.data))
1453 {
1454 gpsIter = iter;
1455 }
1456 else if (std::holds_alternative<VehicleAttitude>(iter->second.data))
1457 {
1458 attIter = iter;
1459 }
1460 }
1461
1462 return { gpsIter, attIter };
1463 }
1464