INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/IMU/FileReader/KvhFile.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 13 192 6.8%
Functions: 4 16 25.0%
Branches: 13 394 3.3%

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 "KvhFile.hpp"
10
11 #include "util/Logger.hpp"
12
13 #include "internal/FlowManager.hpp"
14
15 #include "util/Vendor/KVH/KvhUtilities.hpp"
16
17 #include "NodeData/IMU/KvhObs.hpp"
18
19 114 NAV::KvhFile::KvhFile()
20
5/10
✓ 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.
✓ Branch 11 taken 114 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 114 times.
✗ Branch 15 not taken.
114 : Imu(typeStatic()), _sensor(typeStatic())
21 {
22 LOG_TRACE("{}: called", name);
23
24 114 _hasConfig = true;
25 114 _guiConfigDefaultWindowSize = { 380, 70 };
26
27
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("KvhObs", Pin::Type::Flow, { NAV::KvhObs::type() }, &KvhFile::pollData);
28
2/4
✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 114 times.
✗ Branch 7 not taken.
228 CreateOutputPin("Header Columns", Pin::Type::Object, { "std::vector<std::string>" }, &_headerColumns);
29 228 }
30
31 228 NAV::KvhFile::~KvhFile()
32 {
33 LOG_TRACE("{}: called", nameId());
34 228 }
35
36 342 std::string NAV::KvhFile::typeStatic()
37 {
38
1/2
✓ Branch 1 taken 342 times.
✗ Branch 2 not taken.
684 return "KvhFile";
39 }
40
41 std::string NAV::KvhFile::type() const
42 {
43 return typeStatic();
44 }
45
46 114 std::string NAV::KvhFile::category()
47 {
48
1/2
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
228 return "Data Provider";
49 }
50
51 void NAV::KvhFile::guiConfig()
52 {
53 if (auto res = FileReader::guiConfig(".csv,.*", { ".csv" }, size_t(id), nameId()))
54 {
55 LOG_DEBUG("{}: Path changed to {}", nameId(), _path);
56 flow::ApplyChanges();
57 if (res == FileReader::PATH_CHANGED)
58 {
59 doReinitialize();
60 }
61 else
62 {
63 doDeinitialize();
64 }
65 }
66
67 Imu::guiConfig();
68
69 if (_fileType == FileType::ASCII)
70 {
71 // Header info
72 if (ImGui::BeginTable(fmt::format("##VectorNavHeaders ({})", id.AsPointer()).c_str(), 2,
73 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
74 {
75 ImGui::TableSetupColumn("Basic", ImGuiTableColumnFlags_WidthFixed);
76 ImGui::TableSetupColumn("IMU", ImGuiTableColumnFlags_WidthFixed);
77 ImGui::TableHeadersRow();
78
79 auto TextColoredIfExists = [this](int index, const char* displayText, const char* searchText, bool alwaysNormal = false) {
80 ImGui::TableSetColumnIndex(index);
81 if (alwaysNormal || std::ranges::find(_headerColumns, searchText) != _headerColumns.end())
82 {
83 ImGui::TextUnformatted(displayText);
84 }
85 else
86 {
87 ImGui::TextDisabled("%s", displayText);
88 }
89 };
90
91 ImGui::TableNextRow();
92 TextColoredIfExists(0, "GpsTime", "GpsToW [s]");
93 TextColoredIfExists(1, "UnCompMag", "UnCompMagX [Gauss]");
94 ImGui::TableNextRow();
95 TextColoredIfExists(0, "TimeStartup", "TimeStartup [ns]");
96 TextColoredIfExists(1, "UnCompAcc", "UnCompAccX [m/s^2]");
97 ImGui::TableNextRow();
98 TextColoredIfExists(0, "Temperature", "Temperature [Celsius]");
99 TextColoredIfExists(1, "UnCompGyro", "UnCompGyroX [rad/s]");
100 ImGui::TableNextRow();
101 TextColoredIfExists(0, "Status", "Status");
102 ImGui::TableNextRow();
103 TextColoredIfExists(0, "SequenceNumber", "SequenceNumber");
104
105 ImGui::EndTable();
106 }
107 }
108 else if (_fileType == FileType::BINARY)
109 {
110 ImGui::TextUnformatted("Binary file");
111 }
112 }
113
114 [[nodiscard]] json NAV::KvhFile::save() const
115 {
116 LOG_TRACE("{}: called", nameId());
117
118 json j;
119
120 j["FileReader"] = FileReader::save();
121 j["Imu"] = Imu::save();
122
123 return j;
124 }
125
126 void NAV::KvhFile::restore(json const& j)
127 {
128 LOG_TRACE("{}: called", nameId());
129
130 if (j.contains("FileReader"))
131 {
132 FileReader::restore(j.at("FileReader"));
133 }
134 if (j.contains("Imu"))
135 {
136 Imu::restore(j.at("Imu"));
137 }
138 }
139
140 bool NAV::KvhFile::initialize()
141 {
142 LOG_TRACE("{}: called", nameId());
143
144 return FileReader::initialize();
145 }
146
147 void NAV::KvhFile::deinitialize()
148 {
149 LOG_TRACE("{}: called", nameId());
150
151 FileReader::deinitialize();
152 }
153
154 bool NAV::KvhFile::resetNode()
155 {
156 FileReader::resetReader();
157
158 return true;
159 }
160
161 std::shared_ptr<const NAV::NodeData> NAV::KvhFile::pollData()
162 {
163 std::shared_ptr<KvhObs> obs = nullptr;
164
165 if (_fileType == FileType::BINARY)
166 {
167 uint8_t i = 0;
168 std::unique_ptr<uart::protocol::Packet> packet = nullptr;
169 while (!eof() && read(reinterpret_cast<char*>(&i), 1))
170 {
171 packet = _sensor.findPacket(i);
172
173 if (packet != nullptr)
174 {
175 break;
176 }
177 }
178
179 if (!packet)
180 {
181 return nullptr;
182 }
183
184 obs = std::make_shared<KvhObs>(_imuPos, *packet);
185
186 // Check if package is empty
187 if (obs->raw.getRawDataLength() == 0)
188 {
189 return nullptr;
190 }
191
192 vendor::kvh::decryptKvhObs(obs);
193 }
194 else if (_fileType == FileType::ASCII)
195 {
196 obs = std::make_shared<KvhObs>(_imuPos);
197
198 // Read line
199 std::string line;
200 getline(line);
201 // Remove any starting non text characters
202 line.erase(line.begin(), std::ranges::find_if(line, [](int ch) { return std::isgraph(ch); }));
203
204 if (line.empty())
205 {
206 return nullptr;
207 }
208
209 // Convert line into stream
210 std::stringstream lineStream(line);
211 std::string cell;
212
213 std::optional<uint16_t> gpsCycle = 0;
214 std::optional<uint16_t> gpsWeek;
215 std::optional<long double> gpsToW;
216 std::optional<double> magX;
217 std::optional<double> magY;
218 std::optional<double> magZ;
219 std::optional<double> accelX;
220 std::optional<double> accelY;
221 std::optional<double> accelZ;
222 std::optional<double> gyroX;
223 std::optional<double> gyroY;
224 std::optional<double> gyroZ;
225
226 // Split line at comma
227 for (const auto& column : _headerColumns)
228 {
229 if (std::getline(lineStream, cell, ','))
230 {
231 // Remove any trailing non text characters
232 cell.erase(std::ranges::find_if(cell, [](int ch) { return std::iscntrl(ch); }), cell.end());
233 if (cell.empty())
234 {
235 continue;
236 }
237
238 if (column == "GpsCycle")
239 {
240 gpsCycle = static_cast<uint16_t>(std::stoul(cell));
241 }
242 else if (column == "GpsWeek")
243 {
244 gpsWeek = static_cast<uint16_t>(std::stoul(cell));
245 }
246 else if (column == "GpsToW [s]")
247 {
248 gpsToW = std::stold(cell);
249 }
250 else if (column == "MagX [Gauss]")
251 {
252 magX = std::stod(cell);
253 }
254 else if (column == "MagY [Gauss]")
255 {
256 magY = std::stod(cell);
257 }
258 else if (column == "MagZ [Gauss]")
259 {
260 magZ = std::stod(cell);
261 }
262 else if (column == "AccX [m/s^2]")
263 {
264 accelX = std::stod(cell);
265 }
266 else if (column == "AccY [m/s^2]")
267 {
268 accelY = std::stod(cell);
269 }
270 else if (column == "AccZ [m/s^2]")
271 {
272 accelZ = std::stod(cell);
273 }
274 else if (column == "GyroX [rad/s]")
275 {
276 gyroX = std::stod(cell);
277 }
278 else if (column == "GyroY [rad/s]")
279 {
280 gyroY = std::stod(cell);
281 }
282 else if (column == "GyroZ [rad/s]")
283 {
284 gyroZ = std::stod(cell);
285 }
286 else if (column == "Temperature [Celsius]")
287 {
288 obs->temperature.emplace(std::stod(cell));
289 }
290 else if (column == "Status")
291 {
292 obs->status = std::bitset<8>{ cell };
293 }
294 else if (column == "SequenceNumber")
295 {
296 obs->sequenceNumber = static_cast<uint8_t>(std::stoul(cell));
297 }
298 }
299 }
300
301 if (!gpsCycle || !gpsWeek || !gpsToW)
302 {
303 LOG_ERROR("{}: Fields 'GpsCycle', 'GpsWeek', 'GpsToW [s]' are needed.", nameId());
304 return nullptr;
305 }
306 if (!accelX || !accelY || !accelZ)
307 {
308 LOG_ERROR("{}: Fields 'AccX [m/s^2]', 'AccY [m/s^2]', 'AccZ [m/s^2]' are needed.", nameId());
309 return nullptr;
310 }
311 if (!gyroX || !gyroY || !gyroZ)
312 {
313 LOG_ERROR("{}: Fields 'GyroX [rad/s]', 'GyroY [rad/s]', 'GyroZ [rad/s]' are needed.", nameId());
314 return nullptr;
315 }
316 obs->insTime = InsTime(gpsCycle.value(), gpsWeek.value(), gpsToW.value());
317 obs->p_acceleration = { accelX.value(), accelY.value(), accelZ.value() };
318 obs->p_angularRate = { gyroX.value(), gyroY.value(), gyroZ.value() };
319
320 if (magX && magY && magZ)
321 {
322 obs->p_magneticField.emplace(magX.value(), magY.value(), magZ.value());
323 }
324 }
325
326 LOG_DATA("DATA({}): {}, {}, {}, {}, {}", name, obs->sequenceNumber, obs->temperature.value(),
327 obs->p_acceleration.x(), obs->p_acceleration.y(), obs->p_acceleration.z());
328
329 // Check if a packet was skipped
330 if (callbacksEnabled)
331 {
332 if (_prevSequenceNumber == UINT8_MAX)
333 {
334 _prevSequenceNumber = obs->sequenceNumber;
335 }
336 if (obs->sequenceNumber != 0 && (obs->sequenceNumber < _prevSequenceNumber || obs->sequenceNumber > _prevSequenceNumber + 2))
337 {
338 LOG_WARN("{}: Sequence Number changed from {} to {}", name, _prevSequenceNumber, obs->sequenceNumber);
339 }
340 _prevSequenceNumber = obs->sequenceNumber;
341 }
342
343 invokeCallbacks(OUTPUT_PORT_INDEX_KVH_OBS, obs);
344 return obs;
345 }
346
347 NAV::FileReader::FileType NAV::KvhFile::determineFileType()
348 {
349 LOG_TRACE("called for {}", name);
350
351 auto filestream = std::ifstream(getFilepath());
352 if (filestream.good())
353 {
354 union
355 {
356 std::array<char, 4> buffer;
357 uint32_t ui32;
358 } un{};
359
360 if (filestream.readsome(un.buffer.data(), sizeof(uint32_t)) == sizeof(uint32_t))
361 {
362 un.ui32 = uart::stoh(un.ui32, vendor::kvh::KvhUartSensor::ENDIANNESS);
363 if (un.ui32 == vendor::kvh::KvhUartSensor::HEADER_FMT_A
364 || un.ui32 == vendor::kvh::KvhUartSensor::HEADER_FMT_B
365 || un.ui32 == vendor::kvh::KvhUartSensor::HEADER_FMT_C
366 || un.ui32 == vendor::kvh::KvhUartSensor::HEADER_FMT_XBIT
367 || un.ui32 == vendor::kvh::KvhUartSensor::HEADER_FMT_XBIT2)
368 {
369 return FileType::BINARY;
370 }
371 }
372
373 filestream.seekg(0, std::ios_base::beg);
374 std::string line;
375 std::getline(filestream, line);
376 filestream.close();
377
378 auto n = std::ranges::count(line, ',');
379
380 if (n >= 3)
381 {
382 return FileType::ASCII;
383 }
384
385 LOG_ERROR("{} could not determine file type", name);
386 return FileType::NONE;
387 }
388
389 LOG_ERROR("{} could not open file {}", name, getFilepath());
390 return FileType::NONE;
391 }
392