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 |