INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/IMU/FileReader/KvhFile.cpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 13 194 6.7%
Functions: 4 16 25.0%
Branches: 13 400 3.2%

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